Experiences with an MH-Z19 CO2 sensor

Erland LewinErland Lewin wrote 09/30/2016 at 09:58 • 1 min read • Like

My experiences with the MH-Z19 NDIR CO2 sensor

Warning: I just destroyed my MH-Z19 sensor when soldering, I was changing connectors with lead free solder, iron set to 380 C. I had to heat for a little while to remove the old header. Now the sensor only gives a very low value, both on PWM and serial output.

Before destroying the device, I was getting strange values around 900 ppm when I should have been getting around 400 ppm. PWM and serial values were consistent but wrong.

My guess is that the firmware in the device for some reason thought it had a 2000 ppm range instead of 5000. Scaling the 900 ppm by 2000 / 5000 gives 360 ppm. I tried playing with the reserved "SR" pin (thinking it might mean "set range", but with no success.

Maybe I'll open my sensor up to see how it works on the inside. In any case, I'll order another sensor.



FredWalks wrote 08/21/2017 at 10:16 point

I am having similar issues using MH Z19B via PWM. Over long time run it shows normal CO2 ppm and sometimes bounces to double value of ppm ( compared with Testo CO2 meter ) with no changes in program nor restart of the system. I also suspect the sensor to change range with no reason and was also to check pwm ratio measurement procedure ( which is the typical one found on internet but using unsigned long ).

Done a quick check today, and it looks like pulseIn function does not always work fine in my code.

I've replaced the measurement bit, wiring the sensor on pin 2 ( int 0 ) for an Arduino and used the following code :

// I have not displayed the usual setup for pin IO configuration and interruption 0 raising triggering first time.

// Reading of measurement also does require to reset bRu to true for next measurement sequence.

unsigned long prev = 0L;
unsigned long th   = 0L;
unsigned long tl   = 0L;
bool      bHi = false;
bool      bLo = false;
bool      bRu = true;
void falling();
void rising();

void rising() {
  attachInterrupt(0, falling, FALLING);
  if (!bRu) return;
  if ((bHi==false)&&(bLo==false)) { prev = micros(); bHi = true; bLo = false; }
  if ((bHi==false)&&(bLo==true )) { tl = micros()-prev; bRu = false; bHi = false; bLo = false; }
void falling() {
  attachInterrupt(0, rising, RISING);
 if (!bRu) return;  
 if ((bHi==true )&&(bLo==false)) { th = micros()-prev; prev = micros(); bHi = false; bLo = true;};


it has two benefits. First it's no longer a blocking code ( for open loop code, much better timings are obtained like this ) and second it seems the readings are stable and aligned to what I'm using for comparison.

Hope it can help

  Are you sure? yes | no

Erland Lewin wrote 12/13/2016 at 23:00 point

Thanks for the link. Very interesting!

I've not spent any more time on this, but I'll let you know when I have pictures. 

  Are you sure? yes | no

lukie80 wrote 12/13/2016 at 22:32 point

There seems to be a way of setting the range by a serial command: 

Do you have photos of the interior of the sensor?

  Are you sure? yes | no