Desk CO₂ Monitor: ESP32-C3, SCD41 and E-Paper
A CO₂ meter that sits on the desk, wakes every few minutes, takes one reading and goes back to sleep. No backlight, no fan, no cloud account: an e-paper panel holds the last number without drawing anything, and the battery is meant to last long enough that charging it is a rare event rather than a habit.
This page is a test record for the site build — the structure is real, the build is not finished. Every number below is either quoted from a datasheet, with the source linked at the end, or marked as still to be measured.
What it does
- Measures CO₂, temperature and relative humidity with a single Sensirion SCD41.
- Shows the last reading on a 2.9″ e-paper panel, which keeps the image with the power off.
- Sleeps between readings. The ESP32-C3 wakes on the RTC timer, takes one single-shot measurement and goes back down.
- Runs from one 18650 cell through a protection board. USB-C for charging and for flashing.
- Needs no network. Wi-Fi is on the chip and stays off unless you want the readings logged somewhere.
Parts
| Qty | Part | Why this one |
|---|---|---|
| 1 | ESP32-C3 module board with USB-C | Single RISC-V core is plenty for one sensor and one panel, and the chip has native USB — no serial adapter on the board. |
| 1 | Sensirion SCD41 | True NDIR CO₂, not a VOC sensor guessing at it. Single-shot mode is what makes the sleep cycle possible. |
| 1 | 2.9″ e-paper panel, SPI, 296 × 128 | Holds the last frame with no power. A reading that updates every few minutes does not need anything faster. |
| 1 | 18650 cell + protection board | Cheap, replaceable, and standard enough that anyone repeating this already has one. |
| 2 | 4.7 kΩ resistors | I²C pull-ups, if the sensor breakout does not carry its own. |
| 1 | Printed enclosure, FDM | Two parts, no supports. Vents on the back so the sensor sees room air and not its own board. |
How it goes together
- Wire the SCD41 to the I²C pins and the panel to the SPI pins. Keep the sensor away from the regulator — its own heat lands in the temperature reading.
- Check the sensor answers on the bus before anything else. If the scan finds nothing, it is the pull-ups or the wiring, never the firmware.
- Flash the firmware and confirm one full cycle on USB power: wake, read, draw, sleep.
- Only then move to the cell. A build that misbehaves on battery is much harder to read than one that misbehaves on a bench supply.
- Print the enclosure, fit the panel behind its window, and leave the vents clear.
Firmware
The whole loop is four steps, and the interesting part is that there is no loop at all: deep sleep resets the chip, so every wake starts at the top.
// Pins and timing, in one place so the rest of the sketch never hardcodes them.
#define SDA_PIN 6
#define SCL_PIN 7
#define EPD_CS_PIN 10
#define EPD_DC_PIN 3
#define SLEEP_SECONDS 300
void setup() {
Wire.begin(SDA_PIN, SCL_PIN);
// Single shot: the sensor is powered up, measured once, and left alone.
scd4x.measureSingleShot();
scd4x.readMeasurement(co2, temperature, humidity);
drawPanel(co2, temperature, humidity);
esp_sleep_enable_timer_wakeup(SLEEP_SECONDS * 1000000ULL);
esp_deep_sleep_start();
}
void loop() {
// Never reached: deep sleep restarts the chip at setup().
}
Why the panel keeps a shadow of the previous reading
E-paper does not repaint a pixel, it moves particles, and a partial refresh only moves the ones it has to. Do that a few dozen times and the digits that stopped changing leave a faint outline behind. The fix is a full refresh — slower, and it flashes the whole panel black and white — every so many partial ones. How often is a judgement call: too rare and the ghosting stays, too often and the display blinks at you all day.
Numbers
| Figure | Value | Where it comes from |
|---|---|---|
| CO₂ measurement range | 400–5000 ppm | SCD4x datasheet |
| CO₂ accuracy | ±75 ppm at 400–1000 ppm | SCD4x datasheet |
| CPU | 32-bit RISC-V, single core, up to 160 MHz | ESP32-C3 datasheet |
| SRAM | 400 KB, plus 8 KB RTC FAST retained in deep sleep | ESP32-C3 datasheet |
| Programmable GPIOs | 22 | ESP32-C3 datasheet |
| Deep-sleep current, chip only | 5 µA typical, RTC timer and RTC memory on | ESP32-C3 datasheet |
| Deep-sleep current, whole board | to be measured | depends on the module’s regulator and USB bridge |
| Battery life on one 18650 | to be measured | follows from the board current above |
| Full panel refresh time | to be measured | panel and driver dependent |
The chip datasheet tells you what the silicon does in deep sleep. It says nothing about the regulator, the USB bridge and the status LED your board vendor put next to it — and on a build like this, those three are the entire battery life.
Sources
- Sensirion SCD4x datasheet — measurement range, accuracy, single-shot mode.
- Espressif ESP32-C3 series datasheet — core, memory, GPIO count, deep-sleep current.