Created
March 19, 2026 13:35
-
-
Save woodif/c18a41dbdde40107c1588686e22031d4 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| /* | |
| ENS160 eCO2 Sensor with AHT21 T/H Sensor | |
| ens160.ino | |
| Reads TVOC/eCO2/AQI and compensates with AHT21 T/RH | |
| Requires SparkFun_ENS160 Library | |
| Requires Adafruit_AHTX0 Library | |
| Uses Seeeduino XIAO ESP32-S3, adjust pins for other ESP32 | |
| DroneBot Workshop 2025 | |
| https://dronebotworkshop.com | |
| */ | |
| // Include required libraries | |
| #include <Wire.h> | |
| #include "SparkFun_ENS160.h" | |
| #include <Adafruit_AHTX0.h> | |
| // I2C pins (adjust for different ESP32) | |
| #define SDA_PIN 21 // XIAO D4 | |
| #define SCL_PIN 22 // XIAO D5 | |
| // Objects for ENS160 & AHT21 | |
| SparkFun_ENS160 ens; | |
| Adafruit_AHTX0 aht; | |
| // Output formatting | |
| const uint8_t HEADER_EVERY = 12; // reprint table header every N lines | |
| uint8_t lineCount = 0; | |
| // AQI Rating Function | |
| const char* aqiText(uint8_t aqi) { | |
| switch (aqi) { | |
| case 1: return "Excellent"; | |
| case 2: return "Good"; | |
| case 3: return "Moderate"; | |
| case 4: return "Poor"; | |
| case 5: return "Unhealthy"; | |
| default: return "?"; | |
| } | |
| } | |
| // Ventilation Hint Function | |
| const char* ventHint(uint16_t eco2_ppm) { | |
| if (eco2_ppm >= 1500) return "Ventilate now"; | |
| if (eco2_ppm >= 1000) return "Consider ventilation"; | |
| return "OK"; | |
| } | |
| // Header Print Function | |
| void printHeader() { | |
| Serial.println(); | |
| Serial.println(F("+----------+---------+--------+-----------+-----------+-----+--------------+")); | |
| Serial.println(F("| Time (s) | Temp C | RH % | TVOC ppb | eCO2 ppm | AQI | AQI (text) |")); | |
| Serial.println(F("+----------+---------+--------+-----------+-----------+-----+--------------+")); | |
| } | |
| void setup() { | |
| // Start Serial Monitor | |
| Serial.begin(115200); | |
| delay(200); | |
| // Start I2C | |
| Wire.begin(SDA_PIN, SCL_PIN); | |
| Wire.setClock(400000); | |
| // Start ENS160 measurements | |
| if (!ens.begin()) { | |
| Serial.println("ENS160 not found (0x53/0x52). Check wiring/ADD pin."); | |
| while (1) delay(100); | |
| } | |
| ens.setOperatingMode(SFE_ENS160_STANDARD); | |
| // Start AHT21 | |
| if (!aht.begin()) { | |
| Serial.println("AHT21 not found (0x38). Check wiring."); | |
| while (1) delay(100); | |
| } | |
| Serial.println(F("ENS160 + AHT21 ready. ENS160 needs a short warm-up for stable readings.")); | |
| printHeader(); | |
| } | |
| void loop() { | |
| // Read AHT21 T/RH for display (and to show alongside ENS160 data) | |
| sensors_event_t humidity, temp; | |
| aht.getEvent(&humidity, &temp); | |
| float tC = temp.temperature; | |
| float rh = humidity.relative_humidity; | |
| // When ENS160 data is ready (about once per second), print a row | |
| if (ens.checkDataStatus()) { | |
| uint8_t aqi = ens.getAQI(); // 1..5 | |
| uint16_t tvoc = ens.getTVOC(); // ppb | |
| uint16_t eco2 = ens.getECO2(); // ppm (equivalent; VOC-derived) | |
| if (lineCount % HEADER_EVERY == 0) printHeader(); | |
| // Row | |
| Serial.printf("| %8lu | %7.2f | %6.1f | %9u | %9u | %u | %-12s |\n", | |
| millis() / 1000UL, tC, rh, tvoc, eco2, aqi, aqiText(aqi)); | |
| // One-line hint (eCO2 is an estimate) | |
| Serial.printf(" Note: AQI=%u (%s), eCO2=%u ppm → %s (eCO2 is estimated)\n\n", | |
| aqi, aqiText(aqi), eco2, ventHint(eco2)); | |
| lineCount++; | |
| } | |
| delay(200); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment