From 12d2ffdbfb70c032238ec25a0bfd75d9fe7c19c6 Mon Sep 17 00:00:00 2001 From: Stefan Brand Date: Thu, 4 Feb 2021 17:50:36 +0100 Subject: [PATCH] Add Support for MH-Z19C CO2 Sensor --- lib/MHZ19C/MHZ19C.cpp | 103 ++++++++++++++++++++++++++++++++++++++++++ lib/MHZ19C/MHZ19C.h | 58 ++++++++++++++++++++++++ src/config.h.example | 1 + src/main.cpp | 5 +- 4 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 lib/MHZ19C/MHZ19C.cpp create mode 100644 lib/MHZ19C/MHZ19C.h diff --git a/lib/MHZ19C/MHZ19C.cpp b/lib/MHZ19C/MHZ19C.cpp new file mode 100644 index 0000000..f97fda6 --- /dev/null +++ b/lib/MHZ19C/MHZ19C.cpp @@ -0,0 +1,103 @@ +/* + MHZ19C.cpp - MHZ19C Sensor Library + Copyright (c) 2020-2021, Stefan Brand + All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include "MHZ19C.h" + +// Constructor - Inititalize Hardware UART +MHZ19C::MHZ19C(void) { + Serial.begin(9600); + Serial.setTimeout(MHZ19C_READ_TIMEOUT); +} + +void MHZ19C::getSensorData(lora_data &loradata) { + write(MHZ19C_CMD_GET_PPM, 0x00); + delay(50); + uint8_t readBytes = read(); + + loradata.ppm = 0; + if (readBytes > 0) { + switch(buffer[1]) { + case 0x86: + loradata.ppm = (buffer[2]*256) + buffer[3]; + break; + } + } +} + +// Turn Self Calibration Routine On or Off +void MHZ19C::setSelfCalibration(bool state) { + if (state) { + write(0x79, 0xA0); + } else { + write(0x79, 0x00); + } +} + +// Write a Command to the Sensor +void MHZ19C::write(uint8_t cmd, uint8_t arg) { + uint8_t _cmd[9] = {0xFF, 0x01, cmd, arg, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t crc = crc8(_cmd); + _cmd[8] = crc; + while (Serial.available() > 0) Serial.read(); + Serial.write(_cmd, 9); + Serial.flush(); +} + +// Read a Sensor Response +uint8_t MHZ19C::read() { + uint8_t ret = 0; + zeroBuffer(); + + // Read Available Bytes + if (Serial.available() > 0) { + ret = Serial.readBytes(buffer, MHZ19C_SER_BUF_LEN); + } + + // Check Sync Bit and CRC + if (buffer[0] != 0xFF || buffer [8] != crc8(buffer)) + return 0; + + // Return Read Bytes + return ret; +} + +// Fill the Internal Buffer with Zeroes +void MHZ19C::zeroBuffer() { + for (int i=0; i < MHZ19C_SER_BUF_LEN; i++) + buffer[i] = 0x00; +} + +// Calculate 8Bit CRC of Messages and Commands +uint8_t MHZ19C::crc8(uint8_t *paket){ + uint8_t i, checksum = 0x00; + for( i = 1; i < 8; i++) + checksum += paket[i]; + + checksum = 0xff - checksum; + checksum += 1; + return checksum; +} \ No newline at end of file diff --git a/lib/MHZ19C/MHZ19C.h b/lib/MHZ19C/MHZ19C.h new file mode 100644 index 0000000..9e76e0f --- /dev/null +++ b/lib/MHZ19C/MHZ19C.h @@ -0,0 +1,58 @@ +/* + MHZ19C.h - MHZ19C Sensor Library + Copyright (c) 2020-2021, Stefan Brand + All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef MHZ19C_H +#define MHZ19C_H + +// Data Structure for the LoRa Packet +struct lora_data { + uint8_t bat; + int16_t ppm; +} __attribute__ ((packed)); + +#define MHZ19C_READ_TIMEOUT 500 // Timeout for Serial Communication +#define MHZ19C_SER_BUF_LEN 9 // Length of the Internal Serial Message Buffer + +#define MHZ19C_CMD_SET_AUTOCAL 0x79 // Turn Self Calibration on/off +#define MHZ19C_CMD_GET_PPM 0x86 // Get Current PPM Reading + +class MHZ19C { + private: + uint8_t buffer[MHZ19C_SER_BUF_LEN]; + + void write(byte cmd, byte arg); + uint8_t read(); + void zeroBuffer(void); + uint8_t crc8(uint8_t *paket); + uint16_t getPPM(void); + + public: + MHZ19C(void); + void getSensorData(lora_data &loradata); + void setSelfCalibration(bool state); +}; + +#endif \ No newline at end of file diff --git a/src/config.h.example b/src/config.h.example index 3bc075a..e74e72f 100644 --- a/src/config.h.example +++ b/src/config.h.example @@ -11,6 +11,7 @@ #define HAS_BME280 // #define HAS_SHT21 // #define HAS_SG112A +// #define HAS_MHZ19C // #define HAS_NO_SENSOR // How many minutes to sleep between Measuring/Sending diff --git a/src/main.cpp b/src/main.cpp index 5950170..4fe72f0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,6 +35,9 @@ void blink(uint8_t num) { struct lora_data { uint8_t bat; } __attribute ((packed)); +#elif defined HAS_MHZ19C + #include + MHZ19C sensor; #elif defined HAS_SG112A #include SG112A sensor; @@ -71,7 +74,7 @@ const lmic_pinmap lmic_pins = { }; // List of unused Pins - will be disabled for Power Saving -#if defined DEBUG || defined HAS_SG112A +#if defined DEBUG || defined HAS_SG112A || defined HAS_MHZ19C const int disabledPins[] = {PIN_PB5, PIN_PB4, PIN_PB1, PIN_PB0, PIN_PC3, PIN_PC2, PIN_PC1, PIN_PC0}; #else const int disabledPins[] = {PIN_PB5, PIN_PB4, PIN_PB3, PIN_PB2, PIN_PB1, PIN_PB0, PIN_PC3, PIN_PC2, PIN_PC1, PIN_PC0};