Problem with CO2 sensor MH-Z19B - cannot read values

I am trying to use MH-Z19B CO2 sensor (spec: http://www.winsen-sensor.com/d/files/infrared-gas-sensor/mh-z19b-co2-ver1_0.pdf) with Arduino UNO. But asbolutely no luck.

Wiring:
sensor Vin → Arduino pin 5V
sensor GND → Arduino pin GND
sensor PWM → Arduino pin 9
sensor RX → Arduino pin 6
sensor TX → Arduino pin 7

#include <SoftwareSerial.h>
SoftwareSerial co2Serial(7, 6); // define MH-Z19 RX TX
 
void setup() {
  Serial.begin(9600);
  co2Serial.begin(9600);
  pinMode(9, INPUT);
}
 
void loop() {
  readCO2();
  delay(3000);
}

int readCO2(){
  byte cmd[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79};
  byte response[9]; // for answer

  co2Serial.write(cmd, 9); //request PPM CO2
  memset(response, 0, 9);
  co2Serial.readBytes(response, 9);

 // print response in hexa
  for (int i = 0; i < 9; i++) {
    Serial.print(String(response[i], HEX));
    Serial.print("   ");
  }
  Serial.println("");

 //CO2 via pwm
 unsigned long th, tl, ppm;
  do {
    th = pulseIn(9, HIGH, 1004000) / 1000;
    tl = 1004 - th;
    ppm = 5000 * (th-2)/(th+tl-4);
  } while (th == 0);
  Serial.println(ppm);
  
  return;
}

Code is only for debugging purposes as the UART response is mostly all zeroes. (only at the beginning there are some values). And the PWM returns something between 20 to 40.

ff   0   0   0   0   0   0   0   0   
20
ff   ff   0   0   0   0   0   0   0   
20
ff   ff   ff   0   0   0   0   0   0   
20
ff   ff   0   0   0   0   0   0   0   
20
ff   18   6   10   38   ff   0   0   0   
20
0   0   0   0   0   0   0   0   0   
30
0   0   0   0   0   0   0   0   0   
20
0   0   0   0   0   0   0   0   0   
35
0   0   0   0   0   0   0   0   0   
20
0   0   0   0   0   0   0   0   0   
20

Sensor LEDs are blinking so it looks it is alive. But have no idea how to check it better. I have also tried to wait until the sensor will heat up (about 3 mins according to spec), but also no luck.

Hope my wiring or code have some big mistake you will see (and I cannot see after several hours of try-and-fail).

Thanks a lot.

The sensor specs say it can draw up to 150ma, which is WAY MORE than the Arduino 5 volt can supply. Try connecting the sensor directly to a 5 volt supply. Your software looks ok.

Paul

(deleted)

Thanks a lot for your responses.

  1. Maximum current for Arduino Uno for 5V pin (not a digital pin, but a power pin) should be at least 500mA - it is the maximum of on board regulator and also for the USB power from computer (according to this: Max current out of 5v pin on UNO? - LEDs and Multiplexing - Arduino Forum or this http://robotics.lib-ieronimoub.gr/?p=715)

  2. I have tried to change the code to better handle the serial communication, but still no luck.

BUT, in the sensor specs there is written "UART (TTL interface level 3.3V)"... And I am using UNO which has 5V level. So I tried to play a little bit with a level converter, but still nothing. As the PWM reading also gives very strange values even after the sensor heating up, it looks like there is really something wrong. I tried also to change RX/TX wires (to be sure they are not vice versa).

My plan:

  • try HW serial (but for monitoring I need to use some display)
  • play a little bit with the power
  • try another controller (I have some NodeMCUs and similar 3.3V controllers)
  • check, if there is not a problem between the chair and the sensor (let my friend to check the sensor) :slight_smile:

  1. I have tried to change the code to better handle the serial communication, but still no luck.

Please post your revised code and the Serial output.

Thanks a lot for your comments.
After many hours of another tries-and-fails I decided to completely rebuild the wiring and also the code. Too many lines of (un)commented code, too many wires.

And voilà, it works. Unfortunately I cannot say, what was wrong. Maybe some bad joint. The wiring is the same as simple as on the beginning (without level converter, so 5V level of UNO). The code is only little better than it was. Using SoftwareSerial.

Here is the code (it has many debugging lines)

#include <SoftwareSerial.h>
SoftwareSerial co2Serial(7, 6); // define MH-Z19 RX TX
unsigned long startTime = millis();
 
void setup() {
  Serial.begin(9600);
  co2Serial.begin(9600);
  pinMode(9, INPUT);
}
 
void loop() {
  Serial.println("------------------------------");
  Serial.print("Time from start: ");
  Serial.print((millis() - startTime) / 1000);
  Serial.println(" s");
  int ppm_uart = readCO2UART();
  int ppm_pwm = readCO2PWM();
  delay(5000);
}

int readCO2UART(){
  byte cmd[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79};
  byte response[9]; // for answer

  Serial.println("Sending CO2 request...");
  co2Serial.write(cmd, 9); //request PPM CO2

  // clear the buffer
  memset(response, 0, 9);
  int i = 0;
  while (co2Serial.available() == 0) {
//    Serial.print("Waiting for response ");
//    Serial.print(i);
//    Serial.println(" s");
    delay(1000);
    i++;
  }
  if (co2Serial.available() > 0) {
      co2Serial.readBytes(response, 9);
  }
  // print out the response in hexa
  for (int i = 0; i < 9; i++) {
    Serial.print(String(response[i], HEX));
    Serial.print("   ");
  }
  Serial.println("");

  // checksum
  byte check = getCheckSum(response);
  if (response[8] != check) {
    Serial.println("Checksum not OK!");
    Serial.print("Received: ");
    Serial.println(response[8]);
    Serial.print("Should be: ");
    Serial.println(check);
  }
  
  // ppm
  int ppm_uart = 256 * (int)response[2] + response[3];
  Serial.print("PPM UART: ");
  Serial.println(ppm_uart);

  // temp
  byte temp = response[4] - 40;
  Serial.print("Temperature? ");
  Serial.println(temp);

  // status
  byte status = response[5];
  Serial.print("Status? ");
  Serial.println(status); 
  if (status == 0x40) {
    Serial.println("Status OK"); 
  }
  
  return ppm_uart;
}

byte getCheckSum(char *packet) {
  byte i;
  unsigned char checksum = 0;
  for (i = 1; i < 8; i++) {
    checksum += packet[i];
  }
  checksum = 0xff - checksum;
  checksum += 1;
  return checksum;
}

int readCO2PWM() {
  unsigned long th, tl, ppm_pwm = 0;
  do {
    th = pulseIn(9, HIGH, 1004000) / 1000;
    tl = 1004 - th;
    ppm_pwm = 5000 * (th-2)/(th+tl-4);
  } while (th == 0);
  Serial.print("PPM PWM: ");
  Serial.println(ppm_pwm);
  return ppm_pwm;  
}

and here are the results example

------------------------------
Time from start: 1916 s
Sending CO2 request...
ff   86   6   af   43   0   0   0   82   
PPM UART: 1711
Temperature? 27
Status? 0
PPM PWM: 1690
------------------------------
Time from start: 2076 s
Sending CO2 request...
ff   86   6   ba   43   0   0   0   77   
PPM UART: 1722
Temperature? 27
Status? 0
PPM PWM: 1700
------------------------------

If anyone will find this thread in future, here are very nice resources for using the MH-Z19 sensor:

MHZ19 - RevSpace - experimenting with the sensor, trying to understand the meaning of undocumented values. I tried to read the temperature and status, but it looks the MH-Z19B is not the same as MH-Z19 and returns always status zero and temperature looks maybe 10 degrees higher (so no value minus 40, but maybe value minus 50).

arduino - MH-Z19 CO2 sensor giving diferent values using UART and PWM - Electrical Engineering Stack Exchange - thread about how to read PWM values and diff to UART values.

Hope it can help to anyone. Now I need to study calibrations. I need to put it outside for some 400 ppm values, but its freezing and sensor should be above zero. Must wait :slight_smile:

(deleted)

maple37:
Thanks a lot for your comments.
After many hours of another tries-and-fails I decided to completely rebuild the wiring and also the code. Too many lines of (un)commented code, too many wires.

And voilà, it works. Unfortunately I cannot say, what was wrong. Maybe some bad joint. The wiring is the same as simple as on the beginning (without level converter, so 5V level of UNO). The code is only little better than it was. Using SoftwareSerial.

Here is the code (it has many debugging lines)

#include <SoftwareSerial.h>

SoftwareSerial co2Serial(7, 6); // define MH-Z19 RX TX
unsigned long startTime = millis();

void setup() {
  Serial.begin(9600);
  co2Serial.begin(9600);
  pinMode(9, INPUT);
}

void loop() {
  Serial.println("------------------------------");
  Serial.print("Time from start: ");
  Serial.print((millis() - startTime) / 1000);
  Serial.println(" s");
  int ppm_uart = readCO2UART();
  int ppm_pwm = readCO2PWM();
  delay(5000);
}

int readCO2UART(){
  byte cmd[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79};
  byte response[9]; // for answer

Serial.println("Sending CO2 request...");
  co2Serial.write(cmd, 9); //request PPM CO2

// clear the buffer
  memset(response, 0, 9);
  int i = 0;
  while (co2Serial.available() == 0) {
//    Serial.print("Waiting for response ");
//    Serial.print(i);
//    Serial.println(" s");
    delay(1000);
    i++;
  }
  if (co2Serial.available() > 0) {
      co2Serial.readBytes(response, 9);
  }
  // print out the response in hexa
  for (int i = 0; i < 9; i++) {
    Serial.print(String(response[i], HEX));
    Serial.print("  ");
  }
  Serial.println("");

// checksum
  byte check = getCheckSum(response);
  if (response[8] != check) {
    Serial.println("Checksum not OK!");
    Serial.print("Received: ");
    Serial.println(response[8]);
    Serial.print("Should be: ");
    Serial.println(check);
  }
 
  // ppm
  int ppm_uart = 256 * (int)response[2] + response[3];
  Serial.print("PPM UART: ");
  Serial.println(ppm_uart);

// temp
  byte temp = response[4] - 40;
  Serial.print("Temperature? ");
  Serial.println(temp);

// status
  byte status = response[5];
  Serial.print("Status? ");
  Serial.println(status);
  if (status == 0x40) {
    Serial.println("Status OK");
  }
 
  return ppm_uart;
}

byte getCheckSum(char *packet) {
  byte i;
  unsigned char checksum = 0;
  for (i = 1; i < 8; i++) {
    checksum += packet[i];
  }
  checksum = 0xff - checksum;
  checksum += 1;
  return checksum;
}

int readCO2PWM() {
  unsigned long th, tl, ppm_pwm = 0;
  do {
    th = pulseIn(9, HIGH, 1004000) / 1000;
    tl = 1004 - th;
    ppm_pwm = 5000 * (th-2)/(th+tl-4);
  } while (th == 0);
  Serial.print("PPM PWM: ");
  Serial.println(ppm_pwm);
  return ppm_pwm; 
}



and here are the results example



Time from start: 1916 s
Sending CO2 request...
ff  86  6  af  43  0  0  0  82 
PPM UART: 1711
Temperature? 27
Status? 0
PPM PWM: 1690

Time from start: 2076 s
Sending CO2 request...
ff  86  6  ba  43  0  0  0  77 
PPM UART: 1722
Temperature? 27
Status? 0
PPM PWM: 1700




If anyone will find this thread in future, here are very nice resources for using the MH-Z19 sensor:

https://revspace.nl/MHZ19 - experimenting with the sensor, trying to understand the meaning of undocumented values. I tried to read the temperature and status, but it looks the MH-Z19B is not the same as MH-Z19 and returns always status zero and temperature looks maybe 10 degrees higher (so no value minus 40, but maybe value minus 50).

https://electronics.stackexchange.com/questions/262473/mh-z19-co2-sensor-giving-diferent-values-using-uart-and-pwm - thread about how to read PWM values and diff to UART values.

Hope it can help to anyone. Now I need to study calibrations. I need to put it outside for some 400 ppm values, but its freezing and sensor should be above zero. Must wait :)

Hi Maple

Have you done with your callibration? Cz I wait your next step. Hehehe

Hi everyone

What am I doing wrong?

I'm using arduino pro mini 3.3 v
For the sensor I use an external power supply of 5 V
and I get this

Time from start: 133 s
Sending CO2 request...
ff 0 0 0 0 0 0 0 0
PPM UART: 0
Temperature? 216
Status? 0
PPM PWM: 1285

Time from start: 142 s
Sending CO2 request...
ff ff ff 0 0 0 0 0 0
Checksum not OK!
Received: 0
Should be: 2
PPM UART: 4294967040
Temperature? 216
Status? 0
PPM PWM: 1275

Hello, I am trying to read ppm with the MH-z19 several days ago without success. I'm testing with the code and connections mentioned in this thread, but I still can't succeed.

It should be noted that I am using an arduino mega 2560, connecting the RX of the sensor to the TX3 14 port; and the RX to port RX3 15. The PWM port to port 9 of the arduino MEGA.

I am a beginner and maybe this problem exceeds me, possibly I am missing something important.

In the program I am changing the tx and rx ports to the ones I am using.

The only result I get is


Time from start: 0 s
Sending CO2 request ...

I appreciate any help since I have been with this topic for several days. :cry:

Thank you!

It should be noted that I am using an arduino mega 2560, connecting the RX of the sensor to the TX3 14 port; and the RX to port RX3 15.

You've got module RX twice so it's not clear how you are connected. You need to cross connect module RX to Mega TX and module TX to Mega RX.

Did you change the software serial to Serial3?

Please post your code.

I am currently using this code, I only get a response from ppm2, according to the serial port.

I don't know if the sensor is broken that I can't get the other readings, however the measurements make sense.

Now I am trying to program to turn on a relay that will be the commander of a solenoid valve, so that it loses when the ppm is less than 1000.

I am having some problems with this since I should use the function millis and several conditionals.

Another problem is that the measurements come with approximately 15 seconds of delay, given the change in CO2.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <SoftwareSerial.h>

#define pwmPin 10

SoftwareSerial mySerial(15, 14); // RX, TX
LiquidCrystal_I2C lcd(0x27,20,4);

byte cmd[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79};
unsigned char response[9]; 
unsigned long th, tl, ppm, ppm2, ppm3, tpwm, p1, p2 = 0;
unsigned int ppmm;
const int pinRELE = 40;



void setup() {
  Serial.begin(9600); 
  mySerial.begin(9600); 
  pinMode(pwmPin, INPUT);
  th = pulseIn(pwmPin, HIGH, 3000000); // use microseconds
  tl = pulseIn(pwmPin, LOW, 3000000);
  tpwm = th + tl; // actual pulse width
  Serial.print("PWM-time: ");
  Serial.print(tpwm);
  Serial.println(" us");
  p1 = tpwm/502; // start pulse width
  p2 = tpwm/251; // start and end pulse width combined
  pinMode(pinRELE, OUTPUT);
  
}

Respuesta serial:

PWM-time: 995368 us
0
394
-----------

What do you see if you run the original successful sketch converted to hardware Serial3 of the Mega, and pin 10 for your pulse in reading.

//#include <SoftwareSerial.h>
//SoftwareSerial co2Serial(7, 6); // define MH-Z19 RX TX
unsigned long startTime = millis();

void setup() {
  Serial.begin(9600);
  //co2Serial.begin(9600);
  //Arduino Mega Serial3 TX3 = 14 Connect to Sensor RX
  //Arduino Mega Serial3 RX3 = 15 Connect to Sensor TX
  Serial3.begin(9600);
  pinMode(10, INPUT); //usisng Pin10 for pulse read with pulseIn()
}

void loop() {
  Serial.println("------------------------------");
  Serial.print("Time from start: ");
  Serial.print((millis() - startTime) / 1000);
  Serial.println(" s");
  int ppm_uart = readCO2UART();
  int ppm_pwm = readCO2PWM();
  delay(5000);
}

int readCO2UART() {
  byte cmd[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
  byte response[9]; // for answer

  Serial.println("Sending CO2 request...");
  //co2Serial.write(cmd, 9); //request PPM CO2
  Serial3.write(cmd, 9); //request PPM CO2

  // clear the buffer
  memset(response, 0, 9);
  int i = 0;
  //while (co2Serial.available() == 0) {
  while (Serial3.available() == 0) {
    //    Serial.print("Waiting for response ");
    //    Serial.print(i);
    //    Serial.println(" s");
    delay(1000);
    i++;
  }
  //if (co2Serial.available() > 0) {
  //  co2Serial.readBytes(response, 9);
  if (Serial3.available() > 0) {
    Serial3.readBytes(response, 9);
  }
  // print out the response in hexa
  for (int i = 0; i < 9; i++) {
    Serial.print(String(response[i], HEX));
    Serial.print("   ");
  }
  Serial.println("");

  // checksum
  byte check = getCheckSum(response);
  if (response[8] != check) {
    Serial.println("Checksum not OK!");
    Serial.print("Received: ");
    Serial.println(response[8]);
    Serial.print("Should be: ");
    Serial.println(check);
  }

  // ppm
  int ppm_uart = 256 * (int)response[2] + response[3];
  Serial.print("PPM UART: ");
  Serial.println(ppm_uart);

  // temp
  byte temp = response[4] - 40;
  Serial.print("Temperature? ");
  Serial.println(temp);

  // status
  byte status = response[5];
  Serial.print("Status? ");
  Serial.println(status);
  if (status == 0x40) {
    Serial.println("Status OK");
  }

  return ppm_uart;
}

byte getCheckSum(char *packet) {
  byte i;
  unsigned char checksum = 0;
  for (i = 1; i < 8; i++) {
    checksum += packet[i];
  }
  checksum = 0xff - checksum;
  checksum += 1;
  return checksum;
}

int readCO2PWM() {
  unsigned long th, tl, ppm_pwm = 0;
  do {
    th = pulseIn(10, HIGH, 1004000) / 1000;
    tl = 1004 - th;
    ppm_pwm = 5000 * (th - 2) / (th + tl - 4);
  } while (th == 0);
  Serial.print("PPM PWM: ");
  Serial.println(ppm_pwm);
  return ppm_pwm;
}

Thank you very much for the advice on Serial3 !!!

I get these results, they are not consistent with reality but now that it works, I can dedicate myself to calibrate.

------------------------------
Time from start: 1139 s
Sending CO2 request...
ff   86   4   e2   3a   0   0   0   5a   
PPM UART: 1250
Temperature? 18
Status? 0
PPM PWM: 1235

Good progress. :slight_smile:

The MH-Z19B has three different ways it returns the ppm value. There is serial output of the ppm value, a pulse length determined by the ppm value, and an analog voltage output determined by the ppm value.

I think that if your application can use the PPM UART response on Serial3, there is no need to use the PPM PWM using the pulseIn(). pulseIn() is a blocking function and can create problems for your final sketch. There is no need to use it.

AFAIK these sensors generate PWM or analog signal from the digital one, the one that you get from UART.
I believe that it's better to get the UART one, if possible because you avoid the two conversion.

Hello,
After trying to get a single data for days, with this code I could able to get my first data, which at least proved that my mh-z19 sensor is alive! Thank you so much !

Now I want to ask something.
According to the sensor's data sheet, (Page 7 - https://www.winsen-sensor.com/d/files/PDF/Infrared%20Gas%20Sensor/NDIR%20CO2%20SENSOR/MH-Z19%20CO2%20Ver1.0.pdf ) it is said that we can read CO2 directly with UART without calculation. How can we turn this code to UART connection without calculation?

Thank you.

Board: Arduino Mega2560
Co2 Sensor: MH-Z19

cattledog:
What do you see if you run the original successful sketch converted to hardware Serial3 of the Mega, and pin 10 for your pulse in reading.

//#include <SoftwareSerial.h>

//SoftwareSerial co2Serial(7, 6); // define MH-Z19 RX TX
unsigned long startTime = millis();

void setup() {
 Serial.begin(9600);
 //co2Serial.begin(9600);
 //Arduino Mega Serial3 TX3 = 14 Connect to Sensor RX
 //Arduino Mega Serial3 RX3 = 15 Connect to Sensor TX
 Serial3.begin(9600);
 pinMode(10, INPUT); //usisng Pin10 for pulse read with pulseIn()
}

void loop() {
 Serial.println("------------------------------");
 Serial.print("Time from start: ");
 Serial.print((millis() - startTime) / 1000);
 Serial.println(" s");
 int ppm_uart = readCO2UART();
 int ppm_pwm = readCO2PWM();
 delay(5000);
}

int readCO2UART() {
 byte cmd[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
 byte response[9]; // for answer

Serial.println("Sending CO2 request...");
 //co2Serial.write(cmd, 9); //request PPM CO2
 Serial3.write(cmd, 9); //request PPM CO2

// clear the buffer
 memset(response, 0, 9);
 int i = 0;
 //while (co2Serial.available() == 0) {
 while (Serial3.available() == 0) {
   //    Serial.print("Waiting for response ");
   //    Serial.print(i);
   //    Serial.println(" s");
   delay(1000);
   i++;
 }
 //if (co2Serial.available() > 0) {
 //  co2Serial.readBytes(response, 9);
 if (Serial3.available() > 0) {
   Serial3.readBytes(response, 9);
 }
 // print out the response in hexa
 for (int i = 0; i < 9; i++) {
   Serial.print(String(response[i], HEX));
   Serial.print("   ");
 }
 Serial.println("");

// checksum
 byte check = getCheckSum(response);
 if (response[8] != check) {
   Serial.println("Checksum not OK!");
   Serial.print("Received: ");
   Serial.println(response[8]);
   Serial.print("Should be: ");
   Serial.println(check);
 }

// ppm
 int ppm_uart = 256 * (int)response[2] + response[3];
 Serial.print("PPM UART: ");
 Serial.println(ppm_uart);

// temp
 byte temp = response[4] - 40;
 Serial.print("Temperature? ");
 Serial.println(temp);

// status
 byte status = response[5];
 Serial.print("Status? ");
 Serial.println(status);
 if (status == 0x40) {
   Serial.println("Status OK");
 }

return ppm_uart;
}

byte getCheckSum(char *packet) {
 byte i;
 unsigned char checksum = 0;
 for (i = 1; i < 8; i++) {
   checksum += packet[i];
 }
 checksum = 0xff - checksum;
 checksum += 1;
 return checksum;
}

int readCO2PWM() {
 unsigned long th, tl, ppm_pwm = 0;
 do {
   th = pulseIn(10, HIGH, 1004000) / 1000;
   tl = 1004 - th;
   ppm_pwm = 5000 * (th - 2) / (th + tl - 4);
 } while (th == 0);
 Serial.print("PPM PWM: ");
 Serial.println(ppm_pwm);
 return ppm_pwm;
}

How can we turn this code to UART connection without calculation?

As far as I know, the code you presented does send the correct commands to the device and reads the concentration from the response over the UART.

The only calculation involved is to piece the data together from two bytes of the response

// ppm
  int ppm_uart = 256 * (int)response[2] + response[3];
  Serial.print("PPM UART: ");
  Serial.println(ppm_uart);

I'm not certain what question you are asking?

Hello,

I want to use just UART option not with UART + PWM together. Can we revise code according to this option ?

I think you just need to comment out this line and the pulse width value of ppm will not be read or printed.

//int ppm_pwm = readCO2PWM();