Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

scd41-sensor

Indoor air-quality logger: a Raspberry Pi Pico 2 W reads CO₂, temperature and humidity from a Sensirion SCD41 over I²C and posts each measurement to a small Flask service, which stores it in SQLite.

The sensor driver is written directly against the datasheet rather than pulled from a library, so every command word, CRC check and timing constraint is visible in one file.

Hardware

Part Notes
Raspberry Pi Pico 2 W RP2350, running MicroPython
Sensirion SCD41 photoacoustic NDIR CO₂ sensor, I²C address 0x62

Wiring uses the default I²C0 pins — SDA on GP0, SCL on GP1 — at 100 kHz.

How it works

The Pico connects to WiFi, brings the sensor to a known state, and then loops: read, POST, sleep 60 s. The server timestamps each reading on arrival and appends it to a readings table.

A few details are worth pointing out, because they are the difference between a demo and something that runs unattended for weeks.

Every word is CRC-checked. The SCD41 returns each 16-bit word followed by a CRC-8 byte (polynomial 0x31, init 0xFF, no reflection, no final XOR — datasheet §3.11). All three words of a measurement frame are validated, so a corrupted frame is rejected instead of being silently used.

co2 == 0 is a sentinel, not a reading. The sensor reports zero when it cannot produce a valid CO₂ figure — for example in a draught near an open window — even though temperature and humidity remain good. Such samples are discarded and the read is retried, up to five times per cycle, with 5 s between attempts because periodic mode produces a fresh sample roughly every 5 s.

Settings can only be touched in idle mode. Periodic measurement is stopped before configuration is read or written, which is why startup begins with a stop command and a 500 ms wait.

Automatic self-calibration is verified, not assumed. On startup the ASC flag is read back from the sensor; if it is off, it is enabled and persisted to EEPROM. persist_settings is deliberately called only when the flag actually changed — the EEPROM is rated for about 2000 write cycles.

Calibration

The SCD41 drifts, and there are two ways to correct it.

ASC (automatic self-calibration) assumes the sensor sees fresh outdoor air — about 420 ppm — at some point over a week, and quietly corrects itself towards that minimum. It is enabled by default here and needs no intervention, but it produces wrong results in a room that is never ventilated.

FRC (forced recalibration) corrects the sensor immediately against a known reference. The full documented procedure (datasheet §3.7.1) is implemented in run_forced_recalibration:

  1. run periodic measurement for more than three minutes in stable air of known CO₂ concentration,
  2. stop periodic measurement and wait 500 ms,
  3. issue the FRC command and read back the correction that was applied.

To use it, put the sensor in fresh outdoor air, set RUN_FRC_ON_START = True, power the board, wait for the correction to be printed, then set the flag back to False. The routine raises if the sensor reports 0xFFFF, which means the recalibration was rejected.

Layout

File Contents
main.py Runs on the Pico: WiFi, SCD41 driver, calibration, measurement loop
server.py Flask service — POST /api/scd41, appends to SQLite
flash.sh Copies main.py to the board with mpremote
install-mpremote.sh Installs mpremote on the host
install-py-deps.sh Creates a virtualenv and installs Flask

Setup

1 — Flash MicroPython. Hold BOOTSEL, plug the board in, and copy the .uf2 image onto the drive that appears.

2 — Install the host tools.

./install-mpremote.sh
./install-py-deps.sh

3 — Configure. In main.py set WIFI_SSID, WIFI_PASSWORD and POST_URL; in server.py set LISTEN_HOST and LISTEN_PORT to match that URL.

⚠️ WiFi credentials live in main.py in plain text and are copied onto the board as part of the source. Do not publish the file, and do not commit real credentials.

4 — Start the server.

source venv/bin/activate
./server.py

5 — Flash the board.

./flash.sh

main.py runs automatically on boot. Attach a serial terminal to watch the log — the ASC state, each reading and the HTTP status are printed there.

Limitations

  • The server binds to a fixed address on the local network, has no authentication and no TLS. It is intended for a private LAN only.
  • There is no buffering on the device: if the server is unreachable, that measurement is lost rather than retried later.
  • The stored data is not exposed anywhere — reading it back means querying scd41.db directly.

About

CO2/temperature/humidity logger — Pico 2 W and a Sensirion SCD41, MicroPython driver written from the datasheet (CRC-8, ASC/FRC), Flask + SQLite backend.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages