-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathSD_MHZ19B.cpp
260 lines (193 loc) · 9.45 KB
/
SD_MHZ19B.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
/**
# SD_MHZ19B Library for Winsen MH-Z19B NDIR CO2 Module
by **[email protected]**, JULY 2020
github: https://github.com/ShaggyDog18/SD_MHZ19B
Arduino library for **MH-Z19B NDIR CO2 Module** by [Zhengzhou Winsen Electronics Technology Co., Ltd](www.winsen-sensor.com)
License: [GNU GPLv3](https://choosealicense.com/licenses/gpl-3.0/)
MH-Z19B NDIR infrared gas module is a common type, small size sensor, using non-dispersive infrared (NDIR) principle to detect the existence of CO 2 in the air, with good selectivity, non-oxygen dependent and long life. Built-in temperature compensation; and it has UART output and PWM output.
This library features access to all module features though an abstracted class and methods to manage the module implemented in user-level functionality.
The library does not work with PWM module signal. Communicates with the module by Hardware or Software UART.
CO2 Measurement Range 0~2000ppm or 0~5000ppm. Accuracy: ±(50ppm+3%).
## Library Methods
- `SD_MHZ19B( Stream& serial )` - Class Constructor
- `~SD_MHZ19B()` - Class Distructor
- `uint16_t getPPM(void)` - read data from the module; returns value of CO2 concentration in ppm; Data are verified and valid (validate by calculating checkSum of the data received). Retunt `FALSE` i.e. ZERO value if communication or CRC error.
- `int8_t getTemp(void)` - returns module temperature in degrees Celcium. Should be called after getPPM() function; otherwise will return a previous value. Rather inaccurate ±2*C; is used for internal compensation.
- `void setAutoCalibration( bool _autoCalib )` - toggles Auto Calibration (Automatic Baseline Correction - ABC) ON/OFF. Set `true` for Enabled/ON; `false` for Disabled/OFF (default). Refer to Auto Calibration Notes below.
- `bool getABCstatus(void)` - gets Auto Calibration ABC status (undocumented feature). Returns `true` for Enabled/ON, `false` - Disabled/OFF.
- `void calibrateZeroPoint(void)` - calibrate Zero point. During the zero point calibration, the sensor must work in stable gas environment (400ppm) for over 20 minutes.
- `bool calibrateSpanPoint( uint16_t _spanPoint )`- do `ZeroPoint` calibration before `Span` calibration. Make sure the sensor worked under a certain level co2 for over 20 minutes. Suggest using 2000ppm as span, at least 1000ppm. Default value is 2000ppm. Returns true if the requested Span value is OK.
- `bool setDetectionRange( uint16_t _detectionRange )` - sets detection range, default is 0~2000ppm. The range could be 2,000-5,000ppm. Returns `true` if the requested range value is OK.
- `uint8_t getStatus(void)` - Reads module status (undocumented feature), Returns 0 if OK; The value is available after successful getPPM() reading only; otherwise, will return previous value; to be called after getPPM().
For more details on the library use refer to the example that utilizes major library methods.
## Auto Calibration Notes
if Auto Calibration (Automatic Baseline Correction - ABC) is ON, the sensor itself performs a zero point judgment and automatic calibration procedure intelligently after a continuous operation period. The automatic calibration cycle is every 24 hours after powered on. The zero point for automatic calibration is **400ppm**.
This function is usually suitable for indoor air quality monitor such as offices and homes, not suitable for greenhouse, farm and refrigeratory where this function should be off. Please do zero calibration timely, such as manual or command calibration.
## Compatibility
The library developed for Arduino UNO, NANO, Pro Mini, ESP8266, etc.
**If you like and use this library please consider making a small donation using [PayPal](https://paypal.me/shaggyDog18/3USD)**
*/
#include "Arduino.h"
#include <SD_MHZ19B.h>
//#define DEBUG // for debug only
extern "C" {
#include <stdlib.h>
#include <stdio.h>
}
/**
* @brief Constructor for SD_MHZ19B class
* @param a Stream ({Software/Hardware}Serial) object.
* @note The serial stream should be already initialized
* @return void
*/
SD_MHZ19B::SD_MHZ19B( Stream& serial ): _serial(serial) {
_serial.setTimeout(100);
setAutoCalibration( false ); // default settings - off autocalibration
setDetectionRange(); // default range of 2000 ppm
}
// Class Destructor
SD_MHZ19B::~SD_MHZ19B() {
//_serial = nullptr;
}
uint16_t SD_MHZ19B::getPPM(void) {
// request data: send a request command
uint8_t cmd[4] = {0x86, 0x00, 0x00, 0x79}; // last byte is a checkSum
_sendCmd( cmd );
if( _readData() ) return _unionFrame.MHZ19B.co2concPPM;
return false; // return 0
}
// UNDOCUMENTED 125 // Get ABC status: true - enabled, false - disabled
bool SD_MHZ19B::getABCstatus(void) { // last byte is a pre-calculated checkSum
uint8_t cmd[4] = {0x7D, 0x00, 0x00, 0x82};
//cmd[3] = _calcCmdCheckSum( cmd );
//Serial.print( "getABCstatus check sum" ); Serial.println( cmd[3], HEX);
_sendCmd( cmd );
if( _readData() ) return (bool) _unionFrame.MHZ19B.ABCstatus;
#ifdef DEBUG
Serial.println( "Error getting ABC Status" );
#endif
return false;
}
// UNDOCUMENTED 155 // Get detection range
uint16_t SD_MHZ19B::getDetectionRange(void) { // last byte is a pre-calculated checkSum
/*
uint8_t cmd[4] = {0x9B, 0x00, 0x00, 0x64};
//cmd[3] = _calcCmdCheckSum( cmd );
//Serial.print( "getDetectionRange checkSum" ); Serial.println( cmd[3], HEX);
_sendCmd( cmd );
if( _readData() ) return _unionFrame.buffer[3];
*/
return false;
}
/*
// returns `Warming` status of the module (not documented, does not work).
bool SD_MHZ19::isWarming(void) {
return( getStatus() <= 1 );
}
*/
void SD_MHZ19B::setAutoCalibration( bool _autoCalib ) { // true for ON; false for OFF
uint8_t cmd[4] = {0x79, 0x00, 0x00, 0x86}; // checkSum is knows for the off command, no need to calculate
if( _autoCalib ) {
cmd[1] = 0xA0;
//cmd[3] = _calcCmdCheckSum( cmd );
cmd[3] = 0xE6; // checksum is known, no need to calculate
}
_sendCmd( cmd );
}
void SD_MHZ19B::calibrateZeroPoint(void) { // last byte is a pre-calculated checkSum
uint8_t cmd[4] = {0x87, 0x00, 0x00, 0x78};
_sendCmd( cmd );
}
bool SD_MHZ19B::calibrateSpanPoint( uint16_t _spanPoint ) {
if( _spanPoint < 1000 || _spanPoint > RANGE_TOP_LIMIT ) return false; // Suggest using 2000ppm as span, at least 1000ppm
uint8_t cmd[4];
cmd[0] = 0x88;
cmd[1] = _spanPoint / 256;
cmd[2] = _spanPoint % 256;
cmd[3] = _calcCmdCheckSum( cmd );
_sendCmd( cmd );
return true;
}
bool SD_MHZ19B::setDetectionRange( uint16_t _detectionRange ) {
if( _detectionRange < 2000 || _detectionRange > RANGE_TOP_LIMIT ) return false; // Suggest using 2000ppm as default
uint8_t cmd[4];
cmd[0] = 0x99;
cmd[1] = _detectionRange / 256;
cmd[2] = _detectionRange % 256;
cmd[3] = _calcCmdCheckSum( cmd );
_sendCmd( cmd );
return true;
}
//----------------------------
// internal private methods
//----------------------------
bool SD_MHZ19B::_readData(void) {
delay(20);
// expect data header to fly in
while( (_serial.peek() != 0xFF) && _serial.available() ) {
_serial.read(); // read untill we get the data header 0xff
}
#ifdef DEBUG
Serial.print("available to read: "); Serial.println( _serial.available() );
//Serial.print(("Sizeof frame struct:"); Serial.println( sizeof(MHZ19frameStruct_t) ); // debug only
#endif
if( _serial.available() < SIZEOF_FRAME ) { //overall/at least 9 bytes should be available
#ifdef DEBUG
Serial.println( "Err:Insufficiend buffer length" );
#endif
return false;
}
// read the entire buffer
_serial.readBytes( _unionFrame.buffer, SIZEOF_FRAME );
#ifdef DEBUG
Serial.print("MH-Z19 Header:"); Serial.print( _unionFrame.MHZ19B.header[0], HEX );
Serial.print( ":" ); Serial.println( _unionFrame.MHZ19B.header[1], HEX );
#endif
// re-sort the buffer: swap high and low bytes since they are not in the "machine" order
{
uint8_t tmp; // tmp does not exist outside of the {} scope
tmp = _unionFrame.buffer[2];
_unionFrame.buffer[2] = _unionFrame.buffer[3];
_unionFrame.buffer[3] = tmp;
}
// Debug - print received buffer
#ifdef DEBUG
Serial.println("Read from module:");
for( uint8_t i=0; i < SIZEOF_FRAME; i++ ) {
Serial.print(":"); Serial.print( _unionFrame.buffer[i], HEX );
}
Serial.println();
#endif
uint8_t calcCheckSum = 0; // refer to datasheet for check sum calculation
for( uint8_t i=1; i < SIZEOF_FRAME-1; i++ ) calcCheckSum += _unionFrame.buffer[i];
calcCheckSum = (~calcCheckSum) + 1;
if( calcCheckSum != _unionFrame.MHZ19B.checkSum ) {
#ifdef DEBUG
Serial.println( "Check Sum Error" );
#endif
return false;
}
return true;
}
uint8_t SD_MHZ19B::_calcCmdCheckSum( const uint8_t _cmd[] ) {
uint8_t checkSum = 0x01; // byte #1 is always 0x01 in the command
for( uint8_t i=0; i<3; i++ ) checkSum += _cmd[i];
checkSum = (~checkSum)+1;
#ifdef DEBUG
Serial.print( "CMD checkSum:" ); Serial.println( checkSum, HEX );
#endif
return checkSum;
}
void SD_MHZ19B::_sendCmd( const uint8_t _cmd[] ) { // 4 bytes command
_serial.flush(); // clear buffer, Waits for the transmission of outgoing serial data to complete
// send command header
_serial.write((uint8_t)0xFF);
_serial.write((uint8_t)0x01);
// send command
for( uint8_t i=0; i<3; i++ ) _serial.write( _cmd[i] );
// send reserved 3 x zeros
for( uint8_t i=0; i<3; i++ ) _serial.write( (uint8_t)0x00 );
//send command tail/check value
_serial.write( _cmd[3] );
_serial.flush(); // clear buffer, Waits for the transmission of outgoing serial data to complete
}