- Add project-local sym-lib-table so the custom esp_sensair_stuff symbols resolve on a fresh clone (fp-lib-table already covered footprints) - Add CHANGELOG.md documenting the v1.0.0 release - Expand .gitignore: KiCad lock files, ESPHome build artifacts, Python cache, editor/OS noise, logs, merge artifacts - README: list sym-lib-table and CHANGELOG.md in the repository layout
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/) |
| Schematic | docs/esp-sensair-schematic.pdf |
| Fabrication data | gerber/ (Gerber X2 + Excellon + ODB job) |
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
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 | fill in from your order confirmation |
Fabrication files:
gerber/esp-sensair.zip— Gerber X2 plot + drill files, ready to uploadgerber/esp+sensair-job.gbrjob— Gerber job / stackup description
Note on the job file: the v1 project was started from a 4-layer KiCad stackup, so
esp+sensair-job.gbrjobdeclaresLayerNumber: 4and labels the copper layersL1/L4even 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, secrets template:
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)
- 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.
- 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.
- No mounting holes. The board is held by its connectors only. v2: four M2.5 holes.
- 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. - 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. - Leaded HASL is not RoHS-compliant. v2: HASL lead-free, and ENIG for any impedance-controlled design.
- Project name contains
+. It propagates into the Gerber file names (esp+sensair-Front.gbr), which some fabricator upload portals and drill parsers reject. v2:esp-sensair. - 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})
├── gerber/ Gerber X2, Excellon drills, job file, ready-to-upload zip
├── 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
├── CHANGELOG.md release history
├── LICENSE CERN-OHL-S-2.0 (hardware)
└── LICENSE.MIT MIT (firmware config, BOM, docs)
Rebuilding the board
- Open
esp+sensair.kicad_proin KiCad 10. - Run DRC; fix anything you change.
- Export Gerbers/drill (the settings are stored in the project) and upload
gerber/*.zipto your fabricator with the parameters from the fabrication table. - Order parts per BOM, solder through-hole parts and pin headers.
- 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 |
| Firmware configuration, BOM, scripts, documentation | 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).
