Giter Site home page Giter Site logo

tobiasschuerg / mh-z-co2-sensors Goto Github PK

View Code? Open in Web Editor NEW
74.0 9.0 40.0 61 KB

Arduino imeplementation for CO2 sensors of the MH-Z series (Intelligent Infrared CO2 Module)

License: MIT License

C++ 100.00%
arduino mh-z19 mh-z14a co2-sensor esp8266 sensor hacktoberfest mh-z19b mh-z19c mhz19b

mh-z-co2-sensors's Introduction

MH-Z14A, MH-Z19B CO2 ... Module

This repository contains an Arduino implementation for MH-Z CO2 sensors, including MH-Z14A, MH-Z19B, and MH-Z19C. It aims to consolidate information on these sensors in one place for easy access.

The sensor is available for ~20 bucks at the usual places.

Implementation

Refer to the provided example for implementation details.

PPMuart: 602, PPMpwm: 595, Temperature: 23

Implementation details

C ppm = 5000 * (T_high - 2 ms) / (T_high + T_low - 4ms)

The implementation primarily draws from this Arduino forum post.

Usage

By default, the PWM range value is set to 5000. You do not need to change anything in the class constructor if the Cppm value is within the expected range of 400-1000. However, if necessary, you can test it with a 2000 range value:

Read co2 via PWM

#include <MHZ.h>
#define CO2_IN 9

MHZ co2(CO2_IN, MHZ::MHZ19B); // here the range value is set to 5000 by default (RANGE_5K)
int ppm = co2.readCO2PWM();

Read co2 via UART

#include <MHZ.h>
#define MH_Z19_RX 10
#define MH_Z19_TX 11
MHZ co2(MH_Z19_RX, MH_Z19_TX, MHZ19B);
int ppm = co2.readCO2UART();

Supported Sensors

All MH sensors work mostly the same. They only differ in detection range and timings. Here is a list of all explicitly supported sensors:

Sensor Detection Range Reference / Datasheet
MH-Z14A 400~10000ppm https://www.winsen-sensor.com/product/mh-z14a.html
MH-Z14B 400~50000ppm https://www.winsen-sensor.com/product/mh-z19b.html
MH-Z16 400~100000ppm https://www.winsen-sensor.com/product/mh-z16.html
MH-Z1911A 0~10000ppm https://www.winsen-sensor.com/product/mh-z1911a.html
MH-Z19B 400~10000ppm https://www.winsen-sensor.com/product/mh-z19b.html
MH-Z19C 400~10000ppm https://www.winsen-sensor.com/product/mh-z19c.html
MH-Z19D 400~10000ppm https://www.winsen-sensor.com/product/mh-z19d.html
MH-Z19E 400~10000ppm https://www.winsen-sensor.com/product/mh-z19e.html

Here is also a complete overview of all CO2 sensors.

Resources:

Good overview of the different sensor types, how they work and calibrate: https://www.winsen-sensor.com/knowledge/what-is-co2-sensor.html

More infos about the MHZ sensors an wiring:

Further reading:

(russuian, but google translate does a good job)

mh-z-co2-sensors's People

Contributors

4m1g0 avatar ahorn42 avatar bodensee avatar chrschultz avatar collieiscute avatar drleavsy avatar flatsiedatsie avatar johndoe8967 avatar mariusebastian avatar overflowerror avatar per1234 avatar portagoras avatar tobiasschuerg avatar vlkozl avatar vmarseguerra avatar wer-is-paul avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mh-z-co2-sensors's Issues

16bit integer constant on Arduino Uno

I have changed type of the constants to long, otherwise Arduino Uno waits forever

const long MHZ14A_PREHEATING_TIME = 3 * 60 * 1000L;
const long MHZ19B_PREHEATING_TIME = 3 * 60 * 1000L;

const long MHZ14A_RESPONSE_TIME = 60 * 1000L;
const long MHZ19B_RESPONSE_TIME = 120 * 1000L;

PPMUart && Temperature N/A

Hi,

I get this in the serial monitor.

----- Time from start: 1099 s
PPMuart: n/a, PPMpwm: 634, Temperature: n/a

Running this on a Wemos D1

With this sketch

#include <ESP8266WiFi.h>
#include <SoftwareSerial.h>
#include <MHZ.h>

// pin for pwm reading
#define CO2_IN 16

// pin for uart reading
#define MH_Z19_RX D1 // D7
#define MH_Z19_TX D2 // D6

MHZ co2(MH_Z19_RX, MH_Z19_TX, CO2_IN, MHZ19B);

void setup() {
Serial.begin(9600);
pinMode(CO2_IN, INPUT);
delay(100);
Serial.println("MHZ 19B");

// enable debug to get addition information
// co2.setDebug(true);

if (co2.isPreHeating()) {
Serial.print("Preheating");
while (co2.isPreHeating()) {
Serial.print(".");
delay(5000);
}
Serial.println();
}
}

void loop() {
Serial.print("\n----- Time from start: ");
Serial.print(millis() / 1000);
Serial.println(" s");

int ppm_uart = co2.readCO2UART();
Serial.print("PPMuart: ");

if (ppm_uart > 0) {
Serial.print(ppm_uart);
} else {
Serial.print("n/a");
}

int ppm_pwm = co2.readCO2PWM();
Serial.print(", PPMpwm: ");
Serial.print(ppm_pwm);

int temperature = co2.getLastTemperature();
Serial.print(", Temperature: ");

if (temperature > 0) {
Serial.println(temperature);
} else {
Serial.println("n/a");
}

Serial.println("\n------------------------------");
delay(5000);
}

Preheat constant calculation overflows

On ATmega an int is only 16 bits, so the preheating constants get bad values ( -16608), and the preheat check does not work as intended. I had to change the declarations like this to make it work:

const unsigned long MHZ14A_PREHEATING_TIME = 3 * 60 * 1000ul;

No data read from sensor

Hello Tobias,

Thank you for sharing your project !

I am trying to hget it to work but can't seem to be able to read any data from the sensor. Debug :

14:47:50.895 -> M5Stack initializing...OK
14:47:51.833 -> MHZ 19B
14:47:51.833 -> MHZ: debug mode ENABLED
14:48:03.844 -> -- read CO2 uart ---
14:48:03.844 -> >> Sending CO2 request............No response after 10 seconds
14:48:06.048 -> -- read CO2 uart ---
14:48:06.048 -> >> Sending CO2 request............No response after 10 seconds
14:48:08.251 -> -- read CO2 uart ---
14:48:08.251 -> >> Sending CO2 request............No response after 10 seconds

I see the red led flashing on the sensor every xx seconds so communications seems fine. I tried with and without the PWM Line (used PIN 26) but no result.

Any clues what I might try next ?

Thanks and kind regards,

Tony

Update Readme/Documentation

Documentation is quite outdated

  • With #46 new parameters were added which should be documented

MH-Z-CO2-Sensors/MHZ.cpp

Lines 285 to 288 in c83ba47

void MHZ::setBypassCheck(boolean isBypassPreheatingCheck, boolean isBypassResponseTimeCheck) {
_isBypassPreheatingCheck = isBypassPreheatingCheck;
_isBypassResponseTimeCheck = isBypassResponseTimeCheck;
}

  • Added devices should be added to README

  • Preheating shoud also be documented

Change library name back

          Sorry, we can't fix this. The owner (@tobiasschuerg) changed the library name from `MH-Z CO2 Sensors` to `MH-Z CO2 Sensors (MH-Z14A, MH-Z14B, MH-Z19A,MH-Z19B, MH-Z19C)` which is totally different package according to the declaration rules.

Once the library changes its name to MH-Z CO2 Sensors it will be automatically updated.

@tobiasschuerg , could you move (MH-Z14A, MH-Z14B, MH-Z19A,MH-Z19B, MH-Z19C) to the description field?

Originally posted by @ivankravets in platformio/platformio-registry#78 (comment)

Pin configuration for Wemos D1 mini

Hi,

all I am getting as output is

17:45:17.352 -> ..................................
17:48:07.348 ->
17:48:07.348 -> ----- Time from start: 180 s
17:48:08.567 -> PPMuart: n/a

I have tried every combination of pins I could think of and tried to change them. Besides that the code is unchanged. Controller is a D1 mini.

Is there anything I am doing wrong?

Sloppy interpretation of response time and preheating time?

Hi :)

The code is using the response time of the sensors as a recovery time. In my understanding the response time T90 is the time needed by the sensors to get a change between 0ppm to 90% of max range. So it does not make sense to wait T90 between two measurements, because it just tells something about the accuracy of a measurement taken, while the ppm concentration is changing. Close the same, but slightly different is the manner preheating is handled. It does not make sense to wait for an UART request, while the other two sources PWM and ADC are being readed. Like for the response time it just says something about the accurancy of the measurements within this period.

Platform.io warning: Could not find the linux_x86_64 package

Using platformio if I include the library using URL https://github.com/tobiasschuerg/MH-Z-CO2-Sensors/archive/refs/tags/1.4.0.zip all works and I don't get any warnings or errors

If I include using tobiasschuerg/MH-Z-CO2-Sensors @ ^1.4.0 then I get the warning shown below

Library Manager: Installing tobiasschuerg/MH-Z-CO2-Sensors @ ^1.4.0
Warning! Could not find the package with 'tobiasschuerg/MH-Z-CO2-Sensors @ ^1.4.0' requirements for your system 'linux_x86_64'

It's only a warning, I just wanted to give you feedback

Warm Regards

Ben

millis overflow bug

Thank you for your library.

This bug is untested, this came up when I reviewed the code.

...
return lastRequest < millis() - MHZ14A_RESPONSE_TIME;
...
lastRequest = millis(); // runs only if above returns true

millis() overflows after ~50 days of runtime (2**32 / 1000/60/60/24 days) and wraps around to 0.

If the readCO2UART is called regularly enough, then in the transition period where millis() has just wrapped around, the term millis() - MHZ14A_RESPONSE_TIME will wrap around backwards and be larger than lastRequest, the condition will succeed, and lastRequest will be set to a wrapped around millis() value and all is fine.

However, if after the wraparound, readCO2UART is not called again until after such time as millis() > MHZ14A_RESPONSE_TIME, then millis() - MHZ14A_RESPONSE_TIME will be less than lastRequest for the next ~50 days, and readCO2UART will not query the sensor again in that time. This is a bug. For each subsequent wraparound we'll be in the same situation as before, where the library can either work or break for the next 50 days, depending only on the timing of calls to readCO2UART.

The easiest fix would be to restructure the condition like so:

return millis() - lastRequest >= MHZ14A_RESPONSE_TIME;

now - last >= interval is pretty much the standard pattern for wrap-around-safety. Obviously it assumes that millis() and lastRequest are both unsigned, and that lastRequest is only ever set to the current (or recent past) time, never to a future time. That is both true here.

Constant in `readCO2PWM` depends on range

Hi!

Just started working with MH-Z sensors family.
After reading MH-Z19 sensors datasheet and examing some libraries I've noticed that every one copies exactly same mistake.

ppm_pwm = 2000 * (th - 2) / (th + tl - 4);
          ^^^^--- depends on range                                     

Instead of constant 2000 there should be current range. So, if sensor was configured with i.e. 5000 range, this formula will lower the value of measurement.

Call of overloaded 'MHZ(const uint8_t&, const uint8_t&, const int&)' is ambiguous

nodemcuWeatherAtHome:75:37: error: call of overloaded 'MHZ(const uint8_t&, const uint8_t&, const int&)' is ambiguous
75 | MHZ co2(MH_Z19_RX, MH_Z19_TX, MHZ19B);
| ^
In file included from /home/christoph/Arduino/nodeMcuWeatherAtHome/src/nodemcuWeatherAtHome/nodemcuWeatherAtHome.ino:17:
/home/christoph/Arduino/libraries/MH-Z_CO2_Sensors/MHZ.h:36:3: note: candidate: 'MHZ::MHZ(uint8_t, uint8_t, uint16_t)'
36 | MHZ(uint8_t pwmpin, uint8_t type, uint16_t range = RANGE_5K);
| ^~~
/home/christoph/Arduino/libraries/MH-Z_CO2_Sensors/MHZ.h:35:3: note: candidate: 'MHZ::MHZ(uint8_t, uint8_t, uint8_t)'
35 | MHZ(uint8_t rxpin, uint8_t txpin, uint8_t type);
| ^~~
exit status 1
call of overloaded 'MHZ(const uint8_t&, const uint8_t&, const int&)' is ambiguous

Compiler can not distinguish between HZ::MHZ(uint8_t, uint8_t, uint8_t) and MHZ::MHZ(uint8_t, uint8_t, uint16_t)

Documentation about how to change from MH-Z19B to MH-Z14A module is missing

tested this library with a MH-Z14A module.

comment about how to change from MZ-H19B to MH-Z14A module is missing.

delays hided away in a library are bad coding practice!!

To the author: If your library is even available through the arduino-library-manager you should add INTENSIVE DOCUMENTAION

and having done a lot of tests including all kinds of extraordinary circumstances to make your library HIGH-reliable

GRRRR!!!!

 It did not work reliable. After a few answers  no more responds from the module shown in the serial monitor

So I used this code for testing (pre-heating and calibrating are disabled through commenting out

Anyway this code is aber to display the answer of the module on eah request

I uploaded this code new to my ESP8266 nodeMCU for re-checking if the code is working directly before copy & pasting this code here. So there is a high chance that this code will work
`/***************************************************************************
Sample sketch for using a mh-z14a co2 with a ESP8266
for TESTING a MH_Z14A-sensor

Written by Erik Lemcke, combined out of the following samples:

https://www.letscontrolit.com/forum/viewtopic.php?f=2&t=1785&start=40, calibration sample by s3030150
https://www.home-assistant.io/blog/2015/10/11/measure-temperature-with-esp8266-and-report-to-mqtt/, home assistant mqqt by Paulus Schoutsen

modified by Stefan Ludwig (StefanL38)
wiring:
nodeMCU mh-z14a
D7(GPIO13)--> Rx
D6(GPIO12)<-- Tx
GND GND

use the nodeMCU-Vin pin to supply with 5V, the mh-z14a needs 5v
***************************************************************************/

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

#define INTERVAL 5000

//Tx_pin of MH_Z is connected to nodeMCU-RX-pin D6 (GPIO12)
#define nodeMCU_D6_GPIO12 12
#define MH_Z14_TX nodeMCU_D6_GPIO12

//Rx_pin of MH_Z is connected to nodeMCU-TX-pin D7 (GPIO13)
#define nodeMCU_D7_GPIO13 13
#define MH_Z14_R_Pin nodeMCU_D7_GPIO13

byte mhzResp[9]; // 9 bytes bytes response
byte mhzCmdReadPPM[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79};
byte mhzCmdCalibrateZero[9] = {0xFF,0x01,0x87,0x00,0x00,0x00,0x00,0x00,0x78};
byte mhzCmdABCEnable[9] = {0xFF,0x01,0x79,0xA0,0x00,0x00,0x00,0x00,0xE6};
byte mhzCmdABCDisable[9] = {0xFF,0x01,0x79,0x00,0x00,0x00,0x00,0x00,0x86};
byte mhzCmdReset[9] = {0xFF,0x01,0x8d,0x00,0x00,0x00,0x00,0x00,0x72};
byte mhzCmdMeasurementRange1000[9] = {0xFF,0x01,0x99,0x00,0x00,0x00,0x03,0xE8,0x7B};
byte mhzCmdMeasurementRange2000[9] = {0xFF,0x01,0x99,0x00,0x00,0x00,0x07,0xD0,0x8F};
byte mhzCmdMeasurementRange3000[9] = {0xFF,0x01,0x99,0x00,0x00,0x00,0x0B,0xB8,0xA3};
byte mhzCmdMeasurementRange5000[9] = {0xFF,0x01,0x99,0x00,0x00,0x00,0x13,0x88,0xCB};

int shifts = 0 ;
int co2ppm;

long previousMillis = 0;

// the softwareserial interface is defined from the view of the nodeMCU
// nodeMCUs Rx is MH_Zs Tx and vice versa nodeMCUs Tx is MH_Zs Rx

//basic definition of Software-serial is SoftwareSerial MySerial(Rx,Tx)

// from the nodeMCUs view using nodeMCU-pins
SoftwareSerial co2Serial(nodeMCU_D6_GPIO12, nodeMCU_D7_GPIO13); // define MH-Z14A

// from the MH_Zs view using the MH_Z-pins
//SoftwareSerial co2Serial(MH_Z14_TX, MH_Z14_R_Pin); // define MH-Z14A

byte checksum(byte response[9]){
byte crc = 0;
for (int i = 1; i < 8; i++) {
crc += response[i];
}
crc = 255 - crc + 1;
return crc;
}

void disableABC() {
co2Serial.write(mhzCmdABCDisable, 9);
}

void enableABC() {
co2Serial.write(mhzCmdABCEnable, 9);
}

void setRange5000() {
co2Serial.write(mhzCmdMeasurementRange5000, 9);
}

void calibrateZero(){
co2Serial.write(mhzCmdCalibrateZero, 9);
}

int readCO2() {
byte cmd[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
byte response[9];
co2Serial.write(cmd, 9);
// The serial stream can get out of sync. The response starts with 0xff, try to resync.
while (co2Serial.available() > 0 && (unsigned char)co2Serial.peek() != 0xFF) {
co2Serial.read();
yield();
shifts++;
}

memset(response, 0, 9);
co2Serial.readBytes(response, 9);

for (int i = 0; i < 9; i++) {
Serial.print(" 0x");
Serial.print(response[i], HEX);
}
Serial.println(" Response OK. Shifts=" + String(shifts));

if (response[1] != 0x86)
{
Serial.println(" Invalid response from co2 sensor!");
delay(1000);
return -1;
}

if (response[8] == checksum(response)) {
int responseHigh = (int) response[2];
int responseLow = (int) response[3];
int ppm = (256 * responseHigh) + responseLow;
return ppm;
} else {
Serial.println("CRC error!");
return -1;
}
}

void setup() {

Serial.begin(115200);
Serial.println("Startup");
co2Serial.begin(9600);
Serial.println("co2Serial.begin(9600);");

unsigned long previousMillis = millis();
delay(500);
/*
Serial.println("Disabling ABC");
disableABC();
/
Serial.println("Setting range to 5000");
setRange5000();
/

Serial.println("Waiting half an hour before calibrating zero");
delay(1800000);
calibrateZero();
Serial.println("Zero was calibrated");
*/
Serial.println("leaving Setup");

}

long lastMsg = 0;

void loop() {
unsigned long now = millis();
//send a meaage every minute
if (now - lastMsg > 5 * 1000) {
lastMsg = now;
unsigned long currentMillis = millis();
if (abs(currentMillis - previousMillis) > INTERVAL)
{
previousMillis = currentMillis;
Serial.print("Requesting CO2 concentration...");
co2ppm = -999;
co2ppm = readCO2();

  //If no proper co2 value is returned, try again
  while (co2ppm == -1){
    yield();
    Serial.print("re-Requesting CO2 concentration...");
    co2ppm = readCO2();  
  }
  
  Serial.println("  PPM = " + String(co2ppm));
}

}
}`

getLastTemperature using isReady

getLastTemperature returns not ready (-5) if it is called immediately after readCO2UART. This happens because getLastTemperature() calls isReady() which will return false if called too often compared to the MHZxxx_RESPONSE_TIME. It should not be necessary to add a delay between reading uart value and returning the 4th byte og said value.

It should not be necessary to call isReady() in getLastTemperature() as it only returns pre stored values. Propose to remove the call or call isPreHeating() instead.

Debug status byte

The if statement in line 156 of mhz.cpp makes the debug info "maybe not ok" as long as debug == TRUE.

Suggest changing to && instead of ||

adjust measurement range (5000 ppm or 2000 ppm)

I would like to adjust the measurement range from 5000 to 2000.
quote:According to the MH-Z19B datasheet, you can configure the measurement range by putting the desired range in byte 3 and 4. However, unlike what the MH-Z19B datasheet says, you can set the range using the following command (in this case 0x07d0 = 2000 ppm in byte 6 and 7):
0xFF 0x01 0x99 0x00 0x00 0x00 0x07 0xD0 0x8F
^-range-^
unquote

How would a sketch look like to send this command to the MH-Z19B ?


#include <SoftwareSerial.h>
#include <MHZ.h>
// pin for pwm reading
#define CO2_IN 10
// pin for uart reading
#define MH_Z19_RX D7  // D7 D4
#define MH_Z19_TX D6  // D6 D0
MHZ co2(MH_Z19_RX, MH_Z19_TX, CO2_IN, MHZ19B);
void setup() {
  Serial.begin(9600);
  pinMode(CO2_IN, INPUT);
  delay(100);
  Serial.println("MHZ 19B");
  // enable debug to get addition information
  // co2.setDebug(true);
  if (co2.isPreHeating()) {
    Serial.print("Preheating");
    while (co2.isPreHeating()) {
      Serial.print(".");
      delay(5000);
    }
    Serial.println();
  }
}

void loop() {
???
0xFF 0x01 0x99 0x00 0x00 0x00 0x07 0xD0 0x8F
???
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.