Skip to content

Commit 177b16f

Browse files
committed
add demo MHZ19XI2C
1 parent 10e38d8 commit 177b16f

6 files changed

Lines changed: 416 additions & 0 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
; SPDX-FileCopyrightText: 2025 smdn <smdn@smdn.jp>
2+
; SPDX-License-Identifier: MIT
3+
4+
[env:esp32dev]
5+
platform = espressif32
6+
board = esp32dev
7+
framework = arduino
8+
monitor_port = /dev/ttyUSB-FOR-MONITOR
9+
monitor_speed = 115200
10+
upload_port = /dev/ttyUSB-FOR-UPLOAD
11+
12+
lib_deps =
13+
https://github.com/smdn/MHZ19X-ArduinoFramework
14+
https://github.com/garretlab/SO2002A_I2C.git
15+
lib_extra_dirs =
16+
../
17+
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// SPDX-FileCopyrightText: 2025 smdn <smdn@smdn.jp>
2+
// SPDX-License-Identifier: MIT
3+
#include <Arduino.h>
4+
#include <Wire.h>
5+
#include <MHZ19XI2C.hpp>
6+
7+
#define ENABLE_OLED_DISPLAY 0
8+
#if ENABLE_OLED_DISPLAY
9+
#include <SO2002A_I2C.h>
10+
11+
auto display = SO2002A_I2C(0x3C);
12+
#endif
13+
14+
void setup()
15+
{
16+
Serial.begin(115200);
17+
18+
while (!Serial)
19+
;
20+
21+
#if ENABLE_OLED_DISPLAY
22+
display.begin(20, 2);
23+
display.clear();
24+
display.print("setting up ...");
25+
#endif
26+
27+
Wire.begin();
28+
29+
Wire.beginTransmission(MHZ19XI2C_DEVICE_ADDRESS);
30+
Wire.write((uint8_t)MHZ19XI2C_command_t::write);
31+
Wire.write((uint8_t)MHZ19XI2C_register_t::enable_self_calibration);
32+
Wire.write((uint8_t)0);
33+
Wire.endTransmission(true);
34+
35+
delay(100);
36+
37+
Wire.beginTransmission(MHZ19XI2C_DEVICE_ADDRESS);
38+
Wire.write((uint8_t)MHZ19XI2C_command_t::write);
39+
Wire.write((uint8_t)MHZ19XI2C_register_t::interval);
40+
Wire.write((uint8_t)3);
41+
Wire.endTransmission(true);
42+
43+
delay(100);
44+
45+
Wire.beginTransmission(MHZ19XI2C_DEVICE_ADDRESS);
46+
Wire.write((uint8_t)MHZ19XI2C_command_t::read);
47+
Wire.write((uint8_t)MHZ19XI2C_register_t::enable_self_calibration);
48+
Wire.endTransmission(false);
49+
50+
if (1 == Wire.requestFrom(MHZ19XI2C_DEVICE_ADDRESS, (uint8_t)1)) {
51+
Serial.print("self calibration enabled: ");
52+
Serial.println(Wire.read() == 0 ? "false" : "true");
53+
}
54+
55+
delay(100);
56+
57+
Wire.beginTransmission(MHZ19XI2C_DEVICE_ADDRESS);
58+
Wire.write((uint8_t)MHZ19XI2C_command_t::read);
59+
Wire.write((uint8_t)MHZ19XI2C_register_t::interval);
60+
Wire.endTransmission(false);
61+
62+
if (1 == Wire.requestFrom(MHZ19XI2C_DEVICE_ADDRESS, (uint8_t)1)) {
63+
Serial.print("interval: ");
64+
Serial.println(Wire.read(), DEC);
65+
}
66+
67+
delay(100);
68+
69+
Wire.beginTransmission(MHZ19XI2C_DEVICE_ADDRESS);
70+
Wire.write((uint8_t)MHZ19XI2C_command_t::read);
71+
Wire.write((uint8_t)MHZ19XI2C_register_t::error_code);
72+
Wire.endTransmission(false);
73+
74+
if (1 == Wire.requestFrom(MHZ19XI2C_DEVICE_ADDRESS, (uint8_t)1)) {
75+
Serial.print("error code: ");
76+
Serial.println(Wire.read(), HEX);
77+
}
78+
79+
Serial.println("---------");
80+
}
81+
82+
void loop()
83+
{
84+
uint16_t co2ppm = 0;
85+
uint8_t error_code = 0;
86+
constexpr uint8_t error_code_i2c = 0xFF;
87+
88+
Wire.beginTransmission(MHZ19XI2C_DEVICE_ADDRESS);
89+
Wire.write((uint8_t)MHZ19XI2C_command_t::read);
90+
Wire.write((uint8_t)MHZ19XI2C_register_t::latest_co2ppm);
91+
Wire.endTransmission(false);
92+
93+
if (2 == Wire.requestFrom(MHZ19XI2C_DEVICE_ADDRESS, (uint8_t)2)) {
94+
co2ppm = Wire.read() << 8;
95+
co2ppm |= Wire.read();
96+
97+
Serial.print("co2ppm: ");
98+
Serial.println(co2ppm);
99+
}
100+
101+
delay(100);
102+
103+
Wire.beginTransmission(MHZ19XI2C_DEVICE_ADDRESS);
104+
Wire.write((uint8_t)MHZ19XI2C_command_t::read);
105+
Wire.write((uint8_t)MHZ19XI2C_register_t::error_code);
106+
Wire.endTransmission(true);
107+
108+
if (1 == Wire.requestFrom(MHZ19XI2C_DEVICE_ADDRESS, (uint8_t)1)) {
109+
error_code = Wire.read();
110+
111+
Serial.print("error_code: ");
112+
Serial.println(error_code, HEX);
113+
}
114+
else {
115+
error_code = error_code_i2c;
116+
}
117+
118+
Serial.println("-----------");
119+
120+
#if ENABLE_OLED_DISPLAY
121+
if (0 == error_code) {
122+
display.clear();
123+
display.print("CO2: ");
124+
display.print(co2ppm, DEC);
125+
display.print(" ppm");
126+
}
127+
else {
128+
display.clear();
129+
130+
switch (error_code) {
131+
case 1:
132+
display.print("write fail");
133+
break;
134+
case 2:
135+
display.print("read timeout");
136+
break;
137+
case 3:
138+
display.print("read fail");
139+
break;
140+
case 4:
141+
display.print("start byte");
142+
break;
143+
case 5:
144+
display.print("command byte");
145+
break;
146+
case 6:
147+
display.print("checksum error");
148+
break;
149+
case error_code_i2c:
150+
display.print("I2C error");
151+
break;
152+
default:
153+
display.print("FAILURE");
154+
break;
155+
}
156+
}
157+
#endif
158+
159+
delay(2000);
160+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
; SPDX-FileCopyrightText: 2025 smdn <smdn@smdn.jp>
2+
; SPDX-License-Identifier: MIT
3+
4+
[env:ATtiny402]
5+
platform = atmelmegaavr
6+
platform_packages = platformio/framework-arduino-megaavr-megatinycore@^2.6.7
7+
board = ATtiny402
8+
framework = arduino
9+
board_build.mcu = attiny402
10+
board_build.f_cpu = 20000000L
11+
12+
lib_deps =
13+
https://github.com/smdn/MHZ19X-ArduinoFramework
14+
lib_extra_dirs =
15+
../
16+
17+
upload_protocol = custom
18+
upload_port = /dev/ttyUSB
19+
upload_speed = 115200
20+
upload_flags =
21+
--tool
22+
uart
23+
--device
24+
$BOARD
25+
--uart
26+
$UPLOAD_PORT
27+
--baudrate
28+
$UPLOAD_SPEED
29+
30+
upload_command = python3 ${platformio.packages_dir}/framework-arduino-megaavr-megatinycore/tools/prog.py --action write --filename $SOURCE $UPLOAD_FLAGS
31+
32+
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// SPDX-FileCopyrightText: 2025 smdn <smdn@smdn.jp>
2+
// SPDX-License-Identifier: MIT
3+
#include <Wire.h>
4+
#include <MHZ19X.h>
5+
#include <MHZ19XI2C.hpp>
6+
7+
// MHZ19XI2C_register_t::enable_self_calibration
8+
volatile bool enableSelfCalibration = true;
9+
10+
// MHZ19XI2C_register_t::latest_co2ppm
11+
uint16_t latestCO2ppm = 0;
12+
13+
// MHZ19XI2C_register_t::interval
14+
volatile uint8_t getCO2ConcentrationIntervalInSeconds = 5;
15+
16+
// MHZ19XI2C_register_t::error_code
17+
MHZ19X_error_t getCO2ConcentrationResult = MHZ19X_error_t::success;
18+
19+
auto sensor = MHZ19C<PIN_PA6, PIN_PA7>();
20+
21+
// requested register address for reading
22+
MHZ19XI2C_register_t i2cCommandRegisterAddress = (MHZ19XI2C_register_t)0;
23+
24+
void receiveEvent(int);
25+
void requestEvent();
26+
27+
void setup()
28+
{
29+
sensor.begin();
30+
31+
auto switchSelfCalibrationTo = enableSelfCalibration;
32+
33+
sensor.switchSelfCalibration(switchSelfCalibrationTo);
34+
35+
Wire.begin(MHZ19XI2C_DEVICE_ADDRESS);
36+
Wire.onReceive(receiveEvent);
37+
Wire.onRequest(requestEvent);
38+
}
39+
40+
void loop()
41+
{
42+
static bool isSelfCalibrationEnabled = enableSelfCalibration;
43+
44+
noInterrupts();
45+
46+
auto intervalInMilliseconds = getCO2ConcentrationIntervalInSeconds * 1000;
47+
auto switchSelfCalibrationTo = enableSelfCalibration;
48+
49+
interrupts();
50+
51+
if (intervalInMilliseconds == 0)
52+
return; // disable retrieving measurement value
53+
54+
// apply changes to the value of isSelfCalibrationEnabled
55+
if (isSelfCalibrationEnabled != switchSelfCalibrationTo) {
56+
sensor.switchSelfCalibration(switchSelfCalibrationTo);
57+
58+
isSelfCalibrationEnabled = switchSelfCalibrationTo;
59+
}
60+
61+
delay(intervalInMilliseconds);
62+
63+
// retrieve the latest measurement value from sensor
64+
uint16_t co2ppm = 0;
65+
66+
getCO2ConcentrationResult = sensor.getCO2Concentration(co2ppm);
67+
68+
if (MHZ19X_error_t::success == getCO2ConcentrationResult)
69+
latestCO2ppm = co2ppm; // set the latest measurement value
70+
else
71+
latestCO2ppm = 0;
72+
}
73+
74+
void receiveEvent(int numberOfBytes)
75+
{
76+
if (numberOfBytes < 1)
77+
return; // too short
78+
79+
switch ((MHZ19XI2C_command_t)Wire.read()) { // switch by requested command
80+
case MHZ19XI2C_command_t::read:
81+
if (numberOfBytes < 2)
82+
return; // too short
83+
84+
i2cCommandRegisterAddress = (MHZ19XI2C_register_t)Wire.read();
85+
86+
break;
87+
88+
case MHZ19XI2C_command_t::write:
89+
if (numberOfBytes < 2)
90+
return; // too short
91+
92+
getCO2ConcentrationIntervalInSeconds = numberOfBytes;
93+
94+
switch ((MHZ19XI2C_register_t)Wire.read()) { // switch by requested register address
95+
case MHZ19XI2C_register_t::enable_self_calibration:
96+
if (numberOfBytes < 3)
97+
return; // too short
98+
99+
// false for 0, otherwise true
100+
enableSelfCalibration = (Wire.read() != 0);
101+
102+
break;
103+
104+
case MHZ19XI2C_register_t::interval:
105+
if (numberOfBytes < 3)
106+
return; // too short
107+
108+
getCO2ConcentrationIntervalInSeconds = Wire.read();
109+
110+
break;
111+
}
112+
113+
break;
114+
115+
default:
116+
break; // ignore unknown command
117+
}
118+
}
119+
120+
void requestEvent()
121+
{
122+
switch (i2cCommandRegisterAddress) {
123+
case MHZ19XI2C_register_t::enable_self_calibration:
124+
Wire.write((uint8_t)(enableSelfCalibration ? 1 : 0));
125+
break;
126+
127+
case MHZ19XI2C_register_t::latest_co2ppm:
128+
Wire.write((uint8_t)((latestCO2ppm & 0xFF00u) >> 8));
129+
Wire.write((uint8_t)(latestCO2ppm & 0x00FFu));
130+
break;
131+
132+
case MHZ19XI2C_register_t::interval:
133+
Wire.write((uint8_t)getCO2ConcentrationIntervalInSeconds);
134+
break;
135+
136+
case MHZ19XI2C_register_t::error_code:
137+
Wire.write((uint8_t)getCO2ConcentrationResult);
138+
break;
139+
140+
default:
141+
break; // ignore unknown register
142+
}
143+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// SPDX-FileCopyrightText: 2025 smdn <smdn@smdn.jp>
2+
// SPDX-License-Identifier: MIT
3+
#include <stdint.h>
4+
5+
#ifndef MHZ19XI2C_hpp
6+
#define MHZ19XI2C_hpp
7+
8+
// this value can be configured to any I2C device address
9+
constexpr uint8_t MHZ19XI2C_DEVICE_ADDRESS = 0x19;
10+
11+
enum class MHZ19XI2C_command_t : uint8_t {
12+
read = 0x00,
13+
write = 0x01,
14+
};
15+
16+
enum class MHZ19XI2C_register_t : uint8_t {
17+
enable_self_calibration = 0x79, // read/write
18+
latest_co2ppm = 0x86, // read only
19+
interval = 0xF0, // read/write
20+
error_code = 0xFE, // read only
21+
};
22+
23+
#endif // MHZ19XI2C_hpp

0 commit comments

Comments
 (0)