Skip to content

Instantly share code, notes, and snippets.

@buyoh
Last active July 17, 2023 16:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save buyoh/c43f8b254d0b131aa88aa9af84773a37 to your computer and use it in GitHub Desktop.
Save buyoh/c43f8b254d0b131aa88aa9af84773a37 to your computer and use it in GitHub Desktop.
Arduino IDE + M5stickCPlus + MHZ19B (co2 concentration)(MHZ19C)
#include <M5StickCPlus.h>
// -------
// https://www.winsen-sensor.com/d/files/infrared-gas-sensor/mh-z19b-co2-ver1_0.pdf
// Checksum
// The length of buff must be 9
uint8_t MHZ19B_getCheckSum(const uint8_t* buff) {
uint8_t sum = 0;
for (size_t i = 0; i < 8; ++i) sum += buff[i];
uint8_t csum = ~sum;
// csum += 1; // ??? spec?
return csum;
}
// 0x86: Read CO2 concentration
bool MHZ19B_readCO2Concentration(uint16_t& out) {
uint8_t request[9] = {0xff, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79u};
Serial1.write(request, sizeof(request));
delay(50);
uint8_t buff[9];
size_t l = Serial1.readBytes(buff, 9);
if (l != 9) {
// No responce
return false;
}
if (!(buff[0] == 0xff && buff[1] == 0x86)) {
// Incorrect responce
return false;
}
uint8_t chk = MHZ19B_getCheckSum(buff);
if (chk != buff[8]) {
// Bad checksum
return false;
}
out = ((uint16_t)buff[2] << 8) | (uint16_t)buff[3];
return true;
}
// 0x87: Zero point calibration
// ZERO POINT is 400PPM, PLS MAKE SURE THE SENSOR HAD BEEN WORKED
// UNDER 400PPM FOR OVER 20MINUTES
void MHZ19B_zeroPointCalibration() {
uint8_t request[9] = {0xff, 0x01, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
Serial1.write(request, sizeof(request));
delay(50);
// No response
}
// 0x88: Span point calibration
// span[ppm]
// Pls do ZERO calibration before span calibration
// Please make sure the sensor worked under a certain level co2 for over 20 minutes.
// Suggest using 2000ppm as span, at least 1000ppm
void MHZ19B_spanPointCalibration(uint16_t span) {
uint8_t request[9] = {0xff, 0x01, 0x88, span >> 8, span & 0xff, 0x00, 0x00, 0x00, 0xff};
request[8] = MHZ19B_getCheckSum(request);
Serial1.write(request, sizeof(request));
delay(50);
// No response
}
// 0x79: ABC logic on/off
// Automatic Baseline Correction (ABC logic function)
void MHZ19B_updateABClogic(bool enable) {
uint8_t payload = enable ? 0xA0 : 0x00;
uint8_t request[9] = { 0xFF, 0x01, 0x79, payload, 0x00, 0x00, 0x00, 0x00, 0xff };
request[8] = MHZ19B_getCheckSum(request);
Serial1.write(request, sizeof(request));
delay(50);
// No response
}
// ---------------------------------------------------------------------
void setup() {
M5.begin();
// M5.Power.begin();
Serial.begin(115200);
// 240, 160, 80, 40, 20, 10
setCpuFrequencyMhz(20);
M5.Lcd.setCursor(10, 10);
M5.Lcd.setTextSize(2);
M5.Lcd.setTextColor(WHITE, BLACK);
Serial1.begin(9600, SERIAL_8N1, 36, 0);
delay(500);
// Consume dust
while (Serial1.available()) {
Serial1.read();
}
// Auto calibration
MHZ19B_updateABClogic(true);
delay(500);
}
constexpr long k_frequency_ms = 2*1000;
constexpr size_t k_history_co2_max_size = 720;
size_t history_co2_head = 0;
size_t history_co2_tail = 0;
uint16_t history_co2[k_history_co2_max_size];
uint16_t min_co2 = 65535;
uint16_t max_co2 = 0;
size_t getHistoryLastIndex() {
size_t lastp = history_co2_tail;
if (lastp == 0) lastp += k_history_co2_max_size;
lastp -= 1;
return lastp;
}
size_t getHistorySize() {
return (history_co2_head < history_co2_tail)
? (history_co2_tail - history_co2_head)
: (history_co2_tail + k_history_co2_max_size - history_co2_head);
}
bool isHistoryEmpty() {
return history_co2_tail == history_co2_head;
}
void addHistoryValue(uint16_t val) {
history_co2[history_co2_tail] = val;
history_co2_tail++;
if (history_co2_tail >= k_history_co2_max_size)
history_co2_tail = 0;
if (history_co2_head == history_co2_tail) {
history_co2_head += 1;
if (history_co2_head >= k_history_co2_max_size)
history_co2_head = 0;
}
}
void loop() {
M5.update(); // update button state
M5.Lcd.setCursor(10, 10);
M5.Lcd.setTextColor(WHITE, BLACK);
{
bool insert = false;
uint16_t co2;
if (MHZ19B_readCO2Concentration(co2)) {
M5.Lcd.printf("ok ");
M5.Lcd.setCursor(10, 40);
M5.Lcd.printf("co2 = %d", co2);
insert = true;
} else {
// ERROR
M5.Lcd.printf("bad");
if (!isHistoryEmpty()) {
// history is not empty
// Use last value
size_t lastp = getHistoryLastIndex();
co2 = history_co2[lastp];
insert = true;
}
}
if (insert) {
min_co2 = min(min_co2, co2);
max_co2 = max(max_co2, co2);
addHistoryValue(co2);
}
}
M5.Lcd.setCursor(0, 65);
M5.Lcd.printf("%5d:%5d", min_co2, max_co2);
M5.Lcd.fillRect(5, 100, 125, 140, DARKGREY);
{
int width = 125;
int height = 140;
int hsize = getHistorySize();
if (hsize > 2) {
int i = 0;
int lastx = -1;
int lasty = 0;
for (uint16_t p = history_co2_head; p != history_co2_tail; p = (p+1)%k_history_co2_max_size) {
int v = history_co2[p];
int x = long(width)*i/(hsize-1);
int y = max_co2 != min_co2 ?
height - long(height)*long(v-min_co2)/(max_co2-min_co2):
height / 2;
if (x == lastx) {
// TODO: accumulate invisible values
M5.Lcd.drawLine(5+x, 100+y, 5+lastx, 100+lasty, PURPLE);
} else if (lastx != -1) {
M5.Lcd.drawLine(5+x, 100+y, 5+lastx, 100+lasty, PURPLE);
}
lastx = x;
lasty = y;
++i;
}
M5.Lcd.setCursor(0, 150);
M5.Lcd.setTextColor(BLUE);
M5.Lcd.printf("tim%4.1f", float(long(hsize)*k_frequency_ms/1000)/60 );
}
}
delay(k_frequency_ms - 50);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment