254 lines
12 KiB
Markdown
254 lines
12 KiB
Markdown
# ESP32-C3 CO₂ & Environment Sensor Board
|
||
|
||
A small environmental monitoring board: **ESP32-C3** module with a **Sensirion S8** NDIR CO₂ sensor
|
||
(UART) and a **BME280** temperature / humidity / pressure sensor (I²C). Flashed with ESPHome and
|
||
integrated into Home Assistant.
|
||
|
||
**Status:** prototype v1 — designed in KiCad, fabricated at PCBWay, hand assembled, powered up and
|
||
running.
|
||
|
||
| | |
|
||
|---|---|
|
||
| Board size | 50.05 × 40.05 mm |
|
||
| Layers | 2 |
|
||
| Assembly | through-hole / pin-header plug-in modules |
|
||
| Firmware | ESPHome (see [`firmware/`](firmware/)) |
|
||
| Schematic | [`docs/esp-sensair-schematic.pdf`](docs/esp-sensair-schematic.pdf) |
|
||
| Fabrication data | Gerber X2 + Excellon + job file — attached to the [latest release](https://git.honney333.work/honney/esp_sensair/releases/latest) (`esp-sensair-v1.0.0-gerbers.zip`) |
|
||
|
||

|
||
|
||
## Photos
|
||
|
||
Photos of the prototype live in [`photos/`](photos/), are embedded below, and are shown
|
||
in the [v1.0.0 release notes](https://git.honney333.work/honney/esp_sensair/releases/tag/v1.0.0).
|
||
|
||
Bare fabricated board, straight from the fab (front and back):
|
||
|
||
| | |
|
||
|---|---|
|
||
|  |  |
|
||
| Front side | Back side |
|
||
|
||
Assembled and soldered (front and back):
|
||
|
||
| | |
|
||
|---|---|
|
||
|  |  |
|
||
| Front side, soldered | Back side, soldered |
|
||
|
||
Running, with an active CO₂ measurement:
|
||
|
||

|
||
|
||
Live values on the Home Assistant dashboard:
|
||
|
||

|
||
|
||
## Block diagram
|
||
|
||
```
|
||
+5 V
|
||
|
|
||
+-------------------------+
|
||
| |
|
||
| +----------v----------+
|
||
| | ESP32-C3 module |--- 3.3 V out --+
|
||
| | (onboard 3V3 LDO) | |
|
||
+--------------+ | |
|
||
| UART 9600 +----------+----------+ |
|
||
| | |
|
||
+-----v-------+ +------v-------+ v
|
||
| Sensirion S8| | BME280 |<--- I2C (SDA/SCL) -+
|
||
| NDIR CO2 | | T / RH / P |
|
||
+-------------+ +--------------+
|
||
|
||
SW1 (calibration button) -- RC (1 k / 100 n) -- Sensirion S8 bCal_in (pin 8)
|
||
5 V enters at the ESP32-C3 devkit's USB-C connector and is passed to the S8 via the 5 V net.
|
||
```
|
||
|
||
## Connections
|
||
|
||
| Signal | ESP32-C3 module | Sensirion S8 | BME280 |
|
||
|---|---|---|---|
|
||
| `+5V` | pad 11 | pad 0 (heater supply) | — |
|
||
| `GND` | pad 12 | pad 1 | pads 0, 4 |
|
||
| `+3V3` | pad 13 (module output) | — | pads 1, 5 |
|
||
| `/TX` | pad 21 | pad 6 (`UART_RxD`) | — |
|
||
| `/RX` | pad 20 | pad 5 (`UART_TxD`) | — |
|
||
| `/SDA` | pad 7 | — | pad 2 |
|
||
| `/SCL` | pad 8 | — | pad 3 |
|
||
| `bCal_in` | — | pad 8 (`bCal_in`) | — |
|
||
|
||
The calibration button SW1 is wired to the **S8's own `bCal_in` input**, not to the MCU: pulling
|
||
`bCal_in` low starts the sensor's background calibration. C1 (100 nF) and R1 (1 kΩ to GND) form the
|
||
debounce/RC network on that line.
|
||
|
||
Sensirion S8 pins `Alarm_OC`, `PWM_1kHz`, `DVCC_out` and `UART_R/T` are left unconnected.
|
||
|
||
## Bill of materials
|
||
|
||
| Ref | Part | Package | Function |
|
||
|---|---|---|---|
|
||
| U1 | ESP32-C3 Wi-Fi/BLE module (16 pads) | custom `esp_sensair_stuff:ESP32-C3` | MCU, radio, 3.3 V regulator on module |
|
||
| U2 | Sensirion S8 NDIR CO₂ sensor | custom `esp_sensair_stuff:Sensair S8` | CO₂ measurement, UART, 5 V heater |
|
||
| U3 | BME280 (plug-in breakout) | custom `esp_sensair_stuff:BME280` | temperature, relative humidity, pressure, I²C |
|
||
| SW1 | Tactile push button 6 mm | `Button_Switch_THT:SW_PUSH_6mm` | S8 manual (background) calibration trigger |
|
||
| R1 | 1 kΩ | `Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal` | button debounce, low-pass to GND |
|
||
| C1 | 100 nF | `Capacitor_THT:C_Disc_D10.5mm_W5.0mm_P5.00mm` | button debounce / supply decoupling |
|
||
|
||
Machine-readable BOM: [`bom/esp-sensair-bom.csv`](bom/esp-sensair-bom.csv)
|
||
|
||
## Fabrication
|
||
|
||
Order data used for the v1 prototype:
|
||
|
||
| Parameter | Value |
|
||
|---|---|
|
||
| Fabricator | PCBWay |
|
||
| Layers | 2 |
|
||
| Board size (ordered) | 40 × 50 mm |
|
||
| Board size (design) | 40.05 × 50.05 mm |
|
||
| Board material | FR-4 |
|
||
| Board thickness | 1.6 mm |
|
||
| Finished copper weight | 1 oz (35 µm) |
|
||
| Surface finish | HASL with lead |
|
||
| Design rules used | 0.15 mm track / 0.15 mm clearance, via 0.7 mm / 0.3 mm drill |
|
||
| Drill sizes present | 0.8 mm, 1.0 mm, 1.1 mm (39 plated holes, 4 vias) |
|
||
| Non-plated holes | none |
|
||
| Solder mask / silkscreen | as ordered (see `gerber/`) |
|
||
| Stencil | not ordered (THT assembly) |
|
||
| Qty / lead time / cost | 5 pcs; 24 h build + ~2 weeks shipping; $10.36 incl. shipping & tax |
|
||
|
||
Fabrication files are **derived artifacts** and are not committed to the tree. They are
|
||
published as assets on the matching release:
|
||
|
||
- `esp-sensair-v1.0.0-gerbers.zip` — Gerber X2 plot + Excellon drill files + job file, ready to
|
||
upload (attached to the [latest release](https://git.honney333.work/honney/esp_sensair/releases/latest))
|
||
|
||
To regenerate them from the design: open `esp-sensair.kicad_pro` in KiCad, plot Gerber X2 +
|
||
drills with the project's stored export settings, and zip the result.
|
||
|
||
> **Note on the job file:** the v1 project was started from a 4-layer KiCad stackup, so
|
||
> `esp-sensair-job.gbrjob` declares `LayerNumber: 4` and labels the copper layers `L1`/`L4` even
|
||
> though only the outer layers are used. The board was fabricated correctly as a 2-layer board.
|
||
> For the next revision: set the stackup to 2 layers in *Board Setup → Stackup* before exporting,
|
||
> so the job file is self-consistent for anyone re-ordering the board.
|
||
|
||
## Assembly
|
||
|
||
All parts are through-hole or plug-in modules on pin headers; hand soldered with a soldering iron.
|
||
The Sensirion S8 requires a 5 V supply (its NDIR heater draws current pulses of up to ~150 mA) and
|
||
about 30 s of preheat time before it reports valid CO₂ values.
|
||
|
||
## Grounding and copper
|
||
|
||
Both copper layers carry a **GND copper pour** (one zone on `F.Cu` + `B.Cu`, filled, thermal-relief
|
||
connection to pads with 0.5 mm gap, 0.5 mm thermal bridge). Four rule-area keep-outs (pads and
|
||
footprints excluded, copper pour allowed) mark the module areas, including the ESP32-C3 antenna
|
||
region.
|
||
|
||
## Firmware
|
||
|
||
ESPHome on `esp32-c3-devkitm-1`, framework **esp-idf**. Config template:
|
||
[`firmware/esp-sensair.yaml.example`](firmware/esp-sensair.yaml.example), secrets template:
|
||
[`firmware/secrets.yaml.example`](firmware/secrets.yaml.example). The flashed config and
|
||
`secrets.yaml` are git-ignored — **never commit live keys.**
|
||
|
||
| Item | Value |
|
||
|---|---|
|
||
| I²C | `SDA = GPIO7`, `SCL = GPIO8`, bus scan enabled |
|
||
| BME280 | I²C address `0x76`, temperature / pressure / humidity, 60 s interval |
|
||
| Sensirion S8 | UART `TX = GPIO21`, `RX = GPIO20`, 9600 baud, 60 s interval |
|
||
| Home Assistant | native ESPHome API with noise encryption, OTA updates |
|
||
| Network | static IP, `fast_connect`, fallback hotspot + captive portal |
|
||
|
||
## Known issues and lessons learned (v1 → v2)
|
||
|
||
1. **Power comes in through the ESP32-C3 devkit's USB-C 5 V pin.** The Sensirion S8 heater draws
|
||
current pulses of up to ~150 mA, and that current travels through the module's USB connector, its
|
||
5 V rail and its onboard regulator path. v2: a **dedicated 5 V power input** (2.54 mm header,
|
||
screw terminal or DC barrel jack) wired straight to the 5 V net, independent of the module's
|
||
USB-C, which then stays a programming/debug connection. Add to that: 220–470 µF bulk plus 100 nF
|
||
at the S8 supply pins, reverse-polarity protection (series Schottky or P-MOSFET), a jumper to
|
||
back-feed 5 V from the module's USB for programming-only setups, and a power-good LED. Before
|
||
changing anything: measure the ripple and droop on the 5 V rail with an oscilloscope while the S8
|
||
heater fires, so v2 fixes a quantified problem.
|
||
2. **Single 100 nF decoupling capacitor** for the whole board. v2: 100 nF per supply pin, bulk
|
||
capacitance at each sensor, and supply stitching vias into the ground pour.
|
||
3. **No mounting holes.** The board is held by its connectors only. v2: four M2.5 holes.
|
||
4. **No I²C pull-ups populated.** The design relies on the pull-ups of the BME280 breakout.
|
||
v2: add 4.7 kΩ pull-up footprints on `SDA`/`SCL`.
|
||
5. **Schematic is not annotated** (references `U`, `U`, `U`), so KiCad BOM export is unusable.
|
||
v2: annotate, set MPNs, and export the BOM from the schematic.
|
||
6. **Leaded HASL** is not RoHS-compliant. v2: HASL lead-free, and ENIG for any impedance-controlled
|
||
design.
|
||
7. **Project name contained `+`.** It propagated into the Gerber file names
|
||
(`esp+sensair-Front.gbr`), which some fabricator upload portals and drill parsers reject.
|
||
Fixed: the project and all fabrication files are now named `esp-sensair`.
|
||
8. **Antenna keep-out** for the ESP32-C3 module is defined as a rule area (pads and footprints
|
||
excluded, copper pour allowed). v2: verify it against the module datasheet's antenna keep-out
|
||
figure, and check the pour's clearance to the antenna region specifically.
|
||
|
||
## Repository layout
|
||
|
||
```
|
||
.
|
||
├── esp-sensair.kicad_pro KiCad project
|
||
├── esp-sensair.kicad_sch schematic
|
||
├── esp-sensair.kicad_pcb board layout
|
||
├── esp_sensair_stuff.kicad_sym custom symbols (ESP32-C3, Sensirion S8, BME280)
|
||
├── esp_sensair_stuff.pretty/ custom footprints, drawn from component datasheets
|
||
├── fp-lib-table project-local footprint library table (${KIPRJMOD})
|
||
├── sym-lib-table project-local symbol library table (${KIPRJMOD})
|
||
├── bom/ bill of materials (CSV)
|
||
├── docs/ schematic PDF, layer plots, 3D render
|
||
├── firmware/ ESPHome config templates, secrets templates, notes
|
||
├── photos/ photographs of the assembled board (also release assets)
|
||
├── CHANGELOG.md release history
|
||
├── LICENSE CERN-OHL-S-2.0 (hardware)
|
||
└── LICENSE.MIT MIT (firmware config, BOM, docs)
|
||
|
||
(Fabrication outputs — Gerber X2, Excellon drills, job file, upload zip — are generated
|
||
from the design and published as release assets, not committed to the tree.)
|
||
```
|
||
|
||
## Rebuilding the board
|
||
|
||
1. Open `esp-sensair.kicad_pro` in KiCad 10.
|
||
2. Run DRC; fix anything you change.
|
||
3. Export Gerbers/drill (the settings are stored in the project) and upload the resulting zip to
|
||
your fabricator with the parameters from the fabrication table.
|
||
4. Order parts per BOM, solder through-hole parts and pin headers.
|
||
5. Flash the ESPHome configuration and add the device to Home Assistant.
|
||
|
||
## License
|
||
|
||
Dual licensed, SPDX identifiers:
|
||
|
||
| Scope | Licence | File |
|
||
|---|---|---|
|
||
| Hardware: schematic, layout, symbols, footprints, fabrication data | **CERN-OHL-S-2.0** (CERN Open Hardware Licence v2, strongly reciprocal) | [`LICENSE`](LICENSE) |
|
||
| Firmware configuration, BOM, scripts, documentation | **MIT** | [`LICENSE.MIT`](LICENSE.MIT) |
|
||
|
||
SPDX notices to keep in derived files:
|
||
|
||
```
|
||
SPDX-License-Identifier: CERN-OHL-S-2.0
|
||
SPDX-FileCopyrightText: 2026 Hannes Gehrmann
|
||
```
|
||
|
||
Making a board from these files and distributing it is explicitly allowed; if you modify the design
|
||
and convey it, CERN-OHL-S requires you to publish the modified design under the same licence and to
|
||
mark what you changed (sections 3.2 and 3.3).
|
||
|
||
## Acknowledgements
|
||
|
||
The hardware design — schematic, layout, symbols, footprints, fabrication data — and the firmware
|
||
configuration are by **Hannes Gehrmann**.
|
||
|
||
The documentation and repository tooling in the v1.0.0 release — this README, `CHANGELOG.md`, the
|
||
project-local library tables (`fp-lib-table`, `sym-lib-table`), the `.gitignore`, and the release
|
||
packaging (project rename, moving fabrication outputs to release assets) — were drafted with
|
||
**OpenCode** running the **Qwen3.8 Flash Next** model, and reviewed by the maintainer.
|