Carbon Monoxide Oxygen Analyser
A low-cost DIY analyser that measures oxygen and carbon monoxide in diving gas at the same time, built around an ESP32-C3.
Why
CO analysers sold for diving cost far more than comparable industrial safety analysers. So I built a dual analyser that tests a gas for O2 and CO in one go. The O2 side reuses a standard R22D oxygen cell that came out of my CCR after 12 months, which still has plenty of life left.
What it does
O2 and CO together
One sample, both readings: O2 % and CO ppm in large type, with the O2 cell's millivolts and the CO sensor's temperature.
Steady O2 reading
The O2 cell is read through a 16-bit ADC and averaged over 40 samples.
One-button calibration
Calibrate on air or pure oxygen; the analyser works out which from the cell's output and remembers it.
Factory-calibrated CO
Electrochemical CO sensor: 0–1000 ppm at 1 ppm resolution, expected life over 2 years.
Rechargeable
Runs on an 18650 cell, charged through a USB-C socket on the case.
Simple wiring
The display, ADC and CO sensor all share one I2C bus.
Calibration
Press CAL while the O2 cell is exposed to the calibration gas. The firmware decides which gas it is from the cell's output:
| Cell output | Treated as | Calibration value |
|---|---|---|
| 7–13 mV | Air | 20.7% O2 |
| Above 44 mV | Oxygen | 98% O2 |
| Anything else | Error | Shows CAL ERROR and stops until restarted |
The calibration is saved to flash and loaded at start-up. On the very first start, the analyser calibrates straight away.
Photos
Parts
| Part | Notes |
|---|---|
| ESP32-C3 Super Mini | Microcontroller |
| DFRobot CO sensor (SEN0466) | I2C, address 0x74. Factory calibrated, 0–1000 ppm, 1 ppm resolution, expected life over 2 years |
| R22D oxygen cell | Reclaimed from a CCR; connected with a Molex connector |
| ADS1115 16-bit ADC | I2C; reads the O2 cell differentially on A0–A1 |
| 1.5" 128×128 OLED | SH1107 driver, I2C |
| 18650 battery holder / UPS module | With an 18650 cell |
| USB-C socket | For charging |
| M16 toggle switch | Power |
| M16 momentary switch | Calibration |
| Pelican-style case | Cheap, but surprisingly decent |
| 3D-printed internal assembly | Holds everything in the case; M4 threaded inserts |
| Stainless barbs, 4 × M6 | Gas sample connections |
Wiring
| ESP32-C3 pin | Connects to |
|---|---|
| GPIO8 (SDA) | SDA on the OLED, ADS1115 and CO sensor |
| GPIO9 (SCL) | SCL on the OLED, ADS1115 and CO sensor |
| GPIO2 | Calibration button to GND (internal pull-up) |
| ADS1115 A0 / A1 | O2 cell + / − |
Build and flash
The firmware is a basic Arduino-framework C++ program, written in VS Code with PlatformIO. It should port to other IDEs.
pio run -t upload pio device monitor
Libraries: U8g2, Adafruit ADS1X15, RunningAverage and EasyButton, installed by PlatformIO. The DFRobot multi-gas sensor library (MIT licence) is included in src.
3D-print files (STL and Fusion 360): 3D-Printed-Assembly.zip
Known limitations
- The code could do with some optimisation.
- It's lightly tested. Check it against a known gas before trusting a reading.
- CO temperature compensation is turned off in the firmware.