Skip to content

PWM support.

Dr. Takeyuki Ueda edited this page May 7, 2024 · 8 revisions

PWM is supported at version 3.0.0 and later.

Basic Idea

According to the data sheet, the MH_Z19 sensor module periodically provide a CO2 concentration value in the manner they called "PWM" as follows:

  1. cycle The signal is started with a 2 millisecond high-level of Cycle start mark, and following high levels duration which is 500 or fewer times of 2millisecond, then following the low-level signals until 1000 millisecond from the end of Cycle start mark, then following 2millisecond low level. Illustrated as follows:
 <-><---------------------- 1000ms ---------------------------------------------------------------><->Next cycle
 2ms high-level of Cycle start mark and following N (500 or less) 2ms high-levels                 2ms low-level of Cycle end mark  
_|¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|_________ __ |¯| 
 2ms                                                                                              2ms

With N as the number of 2ms high-level signal (not include start mark) is in a range of 0 - 500.

  1. The number of the 2ms high-level indicate co2 concentration as follows:
co2 concentration = N * current setting range /500

The range setting can be any of 2000, 5000, 10000.

Cabling

Connect with 3 wires as follows:

RPi MH_Z19
5V Vin
GND GND
Any gpio, 12(BCM) is recommended) PWM

How to use as command.

  1. with --pwm, using default gpio(12 by BCM) and range(5000)
pi@raspberrypi:~/mh-z19 $ python -m mh_z19 --pwm
{'co2': 540}
  1. To specify gpio and/or range, use --pwm_gpio and/or --pwm_range
python -m mh_z19 --pwm --pwm_gpio 12 --pwm_range 2000
{'co2': 220}
  1. Return timeout error as following in case can't catch any PWM signal.
pi@raspberrypi:~/mh-z19 $ python -m mh_z19 --pwm --pwm_gpio 13
Traceback (most recent call last):
  File "/usr/lib/python2.7/runpy.py", line 174, in _run_module_as_main
    "__main__", fname, loader, pkg_name)
  File "/usr/lib/python2.7/runpy.py", line 72, in _run_code
    exec code in run_globals
  File "/home/pi/mh-z19/mh_z19.py", line 324, in <module>
    print read_from_pwm(gpio=args.pwm_gpio, range=args.pwm_range, )
  File "/home/pi/mh-z19/mh_z19.py", line 212, in read_from_pwm
    raise GPIO_Edge_Timeout("gpio {} edge timeout".format(gpio))
__main__.GPIO_Edge_Timeout: gpio 13 edge timeout

The possible cause of the above timeout is cabling problem including the wrong pin specification, or a physical problem of the sensor module.

  1. The gpio pin number is specified by the BCM system. For example, the pin GPIO 12 is specified as the 5th pin from the bottom right to top.
pi@raspberrypi:~/mh-z19 $ gpio readall
 +-----+-----+---------+------+---+---Pi B+--+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 |     |     |    3.3v |      |   |  1 || 2  |   |      | 5v      |     |     |
 |   2 |   8 |   SDA.1 | ALT0 | 1 |  3 || 4  |   |      | 5v      |     |     |
 |   3 |   9 |   SCL.1 | ALT0 | 1 |  5 || 6  |   |      | 0v      |     |     |
 |   4 |   7 | GPIO. 7 |   IN | 0 |  7 || 8  | 1 | ALT0 | TxD     | 15  | 14  |
 |     |     |      0v |      |   |  9 || 10 | 1 | ALT0 | RxD     | 16  | 15  |
 |  17 |   0 | GPIO. 0 |   IN | 0 | 11 || 12 | 0 | IN   | GPIO. 1 | 1   | 18  |
 |  27 |   2 | GPIO. 2 |   IN | 0 | 13 || 14 |   |      | 0v      |     |     |
 |  22 |   3 | GPIO. 3 |   IN | 0 | 15 || 16 | 0 | IN   | GPIO. 4 | 4   | 23  |
 |     |     |    3.3v |      |   | 17 || 18 | 0 | IN   | GPIO. 5 | 5   | 24  |
 |  10 |  12 |    MOSI |   IN | 0 | 19 || 20 |   |      | 0v      |     |     |
 |   9 |  13 |    MISO |   IN | 0 | 21 || 22 | 0 | IN   | GPIO. 6 | 6   | 25  |
 |  11 |  14 |    SCLK |   IN | 0 | 23 || 24 | 1 | IN   | CE0     | 10  | 8   |
 |     |     |      0v |      |   | 25 || 26 | 1 | IN   | CE1     | 11  | 7   |
 |   0 |  30 |   SDA.0 | ALT0 | 1 | 27 || 28 | 1 | ALT0 | SCL.0   | 31  | 1   |
 |   5 |  21 | GPIO.21 |   IN | 0 | 29 || 30 |   |      | 0v      |     |     |
 |   6 |  22 | GPIO.22 |   IN | 1 | 31 || 32 | 0 | IN   | GPIO.26 | 26  | 12  | <= GPIO 12 by BCM system
 |  13 |  23 | GPIO.23 |   IN | 0 | 33 || 34 |   |      | 0v      |     |     |
 |  19 |  24 | GPIO.24 |   IN | 0 | 35 || 36 | 0 | IN   | GPIO.27 | 27  | 16  |
 |  26 |  25 | GPIO.25 |   IN | 0 | 37 || 38 | 0 | IN   | GPIO.28 | 28  | 20  |
 |     |     |      0v |      |   | 39 || 40 | 1 | IN   | GPIO.29 | 29  | 21  |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+---Pi B+--+---+------+---------+-----+-----+

How to use from your application.

Import mh_z19 and call read_from_pwm as follows:

pi@raspberrypi:~/mh-z19 $ python
Python 2.7.16 (default, Oct 10 2019, 22:02:15) 
[GCC 8.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import mh_z19
>>> mh_z19.read_from_pwm()
{'co2': 620}
>>> mh_z19.read_from_pwm(range=2000)
{'co2': 244}
>>> mh_z19.read_from_pwm(gpio=12, range=2000)
{'co2': 244}
>>> 

The function is defined as follows:

def read_from_pwm(gpio=12, range=5000):

PWM pro & cons

pro

  • Simple to use
    • Only 3 cables are required, despite 4 cables are necessary by the serial protocol.
    • No need to prepare any additional drivers and to stop/start any services.
    • Can run without root privilege without any settings.

cons

  • Slow, in about 1 second necessary to get data.
  • Low resolution

Changed gpio library from RPi.GPIO to gpiozero

Since version 3.1.5, using gpio library has changed from the RPi.GPIO to the gpiozero because of an issue report #53 which reports that RPi.GPIO is not working on the RPi 5.

gpiozero's API is quite different from that of other gpio libraries; it is not for reading whether a specific GPIO is turned on or off, or waiting for it to rise or fall, instead gpiozero has many classes for specific devices like LED, Motion Sensor, and so on, and has many hi level functionalities to handle these devices. For that reason, at first I was confused as to how to migrate from RPi.GPIO to gpiozero.

Fortunately, I was able to find the document of the Migrating from RPi.GPIO right away, but I saw soon that it was a quirky article that said, "When you used RPi.GPIO to read values, use Button class after migrating to gpiozero."

Therefore, for the purpose of making it easier for kind reviewers to see the migration details, and for the sake of reference for new migraters who feel the same confusion as me, I commented out old lines and added new lines in the same place as here. Any comments on this would be greatly appreciated, thx!