# 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`) | ![3D render](docs/esp-sensair-3d-top.png) ## Photos Photos of the assembled 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). | | | |---|---| | ![Assembled board, front side](photos/frontside.jpg) | ![Assembled board, back side](photos/backside.jpg) | | Front side (assembled) | Back side (assembled) | | | | |---|---| | ![Front side, soldered](photos/frontside_soldered.jpg) | ![Back side, soldered](photos/backside_soldered.jpg) | | Front side, soldered | Back side, soldered | Running, with an active CO₂ measurement: ![Active CO₂ measurement](photos/active_co2_measurement.jpg) Live values on the Home Assistant dashboard: ![Home Assistant dashboard](photos/sensor_current_homeassistant.png) ## 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 | _fill in from your order confirmation_ | 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.