Last active
July 17, 2023 16:42
-
-
Save buyoh/c43f8b254d0b131aa88aa9af84773a37 to your computer and use it in GitHub Desktop.
Arduino IDE + M5stickCPlus + MHZ19B (co2 concentration)(MHZ19C)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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