Мало хто турбується про якість повітря в приміщені, що може негативно вплинути на наше з вами самопочуття, особливо, це важливо в холодну пору року, коли вікна закриті для збереження тепла. Тому в мене виникла ідея зібрати сенсор якості повітря, який буде сигналізувати блиманням червоного діода про порушення прийнятних для здоров'я значеннь. Побачивши відхилення будемо проводити провітрювання приміщення.
З цим завданням нам допоможе мікроконтролер ESP32 C3 super mini та датчик CO2 SCD40.
Список компонентів
- Мікроконтролер ESP32 C3 super mini
- Сенсор CO2 SCD40
- Екран OLED 1.3"
- Модуль зарядки Lion акумулятора 3.7В
- Акумулятор 16340
- Міні вимикач
- Червоний діод
- Резистор 330Ом
- Дріт використовував від мережевого кабелю (звита пара)
Схема підключення

Програмний код
З коду прибрав скрін, де вказано, що це подарунок для моєї донечки. Все решта без змін.
main.cpp for PlatformIO
#include
#include
#include
#include
// --- CONFIGURATION ---
#define SCREEN_SWITCH_INTERVAL 4000 // Rotate screens every 4 seconds
#define BLINK_INTERVAL 500 // LED blink speed (ms)
#define OLED_SDA 8
#define OLED_SCL 9
#define LED_PIN 5
// --- CO2 THRESHOLDS ---
#define CO2_ALERT_START 1100 // Alarm starts here
#define CO2_ALERT_STOP 1050 // Alarm stops here (Hysteresis)
// --- GRAPHICS DATA ---
// A custom 11x10 pixel Heart icon
#define HEART_WIDTH 11
#define HEART_HEIGHT 11
static const unsigned char heart_bits[] U8X8_PROGMEM = {
0x08,0x00,
0xDE,0x07,
0xFE,0x03,
0xFF,0x07,
0xFE,0x07,
0xFF,0x07,
0xFE,0x03,
0xFC,0x01,
0xF8,0x00,
0x70,0x00,
0x00,0x00
};
// Initialize display
U8G2_SH1106_128X64_NONAME_F_HW_I2C display(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
SensirionI2cScd4x co2;
// Variables
unsigned long lastSwitchTime = 0;
unsigned long lastBlinkTime = 0;
int currentScreen = 0;
uint16_t valCo2 = 0;
float valTemp = 0.0;
float valHum = 0.0;
bool ledState = LOW;
bool isAlarmActive = false;
// --- STARTUP SCREEN: HEALTHY VALUES ---
void showReferenceScreen() {
display.clearBuffer();
// Title
display.setFont(u8g2_font_helvB08_tr);
display.drawStr(25, 12, "HEALTHY VALUES");
display.drawHLine(0, 15, 128);
// Values List
display.setFont(u8g2_font_profont12_tf);
display.drawStr(10, 30, "CO2: < 1100 ppm");
display.drawStr(10, 45, "Temp: 18 - 26 C");
display.drawStr(10, 60, "Hum: 40 - 60 %");
display.sendBuffer();
delay(3000); // Show for 3 seconds
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
Wire.begin(OLED_SDA, OLED_SCL);
co2.begin(Wire, SCD40_I2C_ADDR_62);
co2.startPeriodicMeasurement();
display.begin();
// --- SHOW STARTUP SCREENS ---
showReferenceScreen();
// Reset timer so the first data screen doesn't switch immediately
lastSwitchTime = millis();
}
void blinkWarningLed() {
if (millis() - lastBlinkTime >= BLINK_INTERVAL) {
lastBlinkTime = millis();
ledState = !ledState;
digitalWrite(LED_PIN, ledState);
}
}
void stopLed() {
digitalWrite(LED_PIN, LOW);
ledState = LOW;
}
void drawBigScreen(String label, String value, String unit, bool isWarning) {
display.clearBuffer();
// Label
display.setFont(u8g2_font_profont12_tf);
int wLabel = display.getStrWidth(label.c_str());
display.setCursor((128 - wLabel) / 2, 12);
display.print(label);
// Value
display.setFont(u8g2_font_logisoso32_tf);
int wValue = display.getStrWidth(value.c_str());
display.setCursor((128 - wValue) / 2, 50);
display.print(value);
// Unit
display.setFont(u8g2_font_profont12_tf);
int wUnit = display.getStrWidth(unit.c_str());
display.setCursor(128 - wUnit - 4, 62);
display.print(unit);
// Warning Styles
if (isWarning) {
display.drawFrame(0, 0, 128, 64);
display.drawFrame(1, 1, 126, 62);
display.setFont(u8g2_font_open_iconic_embedded_1x_t);
display.drawGlyph(2, 10, 73);
display.drawGlyph(118, 10, 73);
} else {
unsigned long elapsed = millis() - lastSwitchTime;
int progressWidth = map(elapsed, 0, SCREEN_SWITCH_INTERVAL, 0, 128);
display.drawHLine(0, 63, progressWidth);
}
display.sendBuffer();
}
void loop() {
// 1. Get Data
bool dataReady = false;
uint16_t error = co2.getDataReadyStatus(dataReady);
if (error == 0 && dataReady) {
co2.readMeasurement(valCo2, valTemp, valHum);
}
// 2. Alarm Logic
if (valCo2 > CO2_ALERT_START) {
isAlarmActive = true;
} else if (valCo2 < CO2_ALERT_STOP) {
isAlarmActive = false;
}
// 3. Display Logic
if (isAlarmActive) {
drawBigScreen("HIGH CO2 LEVEL", String(valCo2), "ppm", true);
blinkWarningLed();
}
else {
stopLed();
if (millis() - lastSwitchTime > SCREEN_SWITCH_INTERVAL) {
lastSwitchTime = millis();
currentScreen++;
if (currentScreen > 2) currentScreen = 0;
}
switch (currentScreen) {
case 0:
drawBigScreen("AIR QUALITY", String(valCo2), "ppm", false);
break;
case 1:
drawBigScreen("TEMPERATURE", String(valTemp, 1), "\260C", false);
break;
case 2:
drawBigScreen("HUMIDITY", String(valHum, 0), "%", false);
break;
}
}
delay(50);
}
Корпус
Корпус розмірами 45х45 був спеціально розроблений під використані компоненти.
Відео на youtube
Підтримка
