Skip to content

Instantly share code, notes, and snippets.

@woodif
Created March 19, 2026 13:35
Show Gist options
  • Select an option

  • Save woodif/c18a41dbdde40107c1588686e22031d4 to your computer and use it in GitHub Desktop.

Select an option

Save woodif/c18a41dbdde40107c1588686e22031d4 to your computer and use it in GitHub Desktop.
/*
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