Read the gas concentration
What this example shows
The module's electrochemical-bridgeBoard.py example starts the breakout through its ATtiny404 bridge and prints the gas concentration in ppm every 2.5 seconds. It creates the sensor on address 0x30, where this board answers until you move its ADDRESS switch.
The example uses the module's ozone preset, SENSOR_O3. SENSOR_NH3 is the module's preset for the SGX-4NH3 cell, with 40 nA per ppm, a 35 kΩ amplifier gain and the ADC's 4.096 V range. The ppm conversion uses the preset's sensitivity: -1000 nA/ppm for SENSOR_O3, 40 nA/ppm for SENSOR_NH3. As shipped, the example's readings are therefore not ammonia ppm. To read ammonia, change SENSOR_O3 to SENSOR_NH3 in the import line and in the line that creates the sensor. The example opens I2C bus 0 on pin 22 (SCL) and pin 21 (SDA); change the pins if your board uses others.
The example's comments say to set the address with jumpers on the back of the board. On this board the address is set with the three-position ADDRESS switch. The 0x49/0x4A/0x4B the comments mention are ADS1115 addresses, not addresses this board answers on. Where the comments say LMPEN is hardwired to GND, on this board a 10 kΩ resistor pulls it to GND: leave it unconnected.
The example code
# FILE: electrochemical-bridgeBoard.py
# AUTHOR: Fran Fodor @ Soldered
# BRIEF: Single sensor PPM reading example for the ATtiny404 bridge board revision,
# which allows daisy-chaining multiple boards on one I2C bus.
# LAST UPDATED: 2026-07-23
# Connecting diagram:
#
# Qwiic------------------------>Qwiic
#
# Note: on this board revision, LMPEN is hardwired to GND, so there's no
# config_pin to set - it's ignored whenever adc_addr falls in the bridge
# range (0x30-0x37). Set the desired address using the jumpers on the back
# of the board before powering it on.
from machine import I2C, Pin
import time
from ElectrochemicalGasSensor import ElectrochemicalGasSensor, SENSOR_O3
# Initialize I2C
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
# (address = 0x30 + jumper offset, giving a range of 0x30-0x37).
# Any additional boards on the same bus just need a different jumper
# setting, same as twoSensors.py does for legacy boards with 0x49/0x4A/0x4B.
sensor = ElectrochemicalGasSensor(SENSOR_O3, i2c, adc_addr=0x30)
# -------------------------------------------------------------------------
# Setup
# -------------------------------------------------------------------------
print("Electrochemical Gas Sensor - ATtiny Bridge Board Example")
if not sensor.begin():
print("ERROR: Can't init the sensor! Check connections and jumper address!")
while True:
time.sleep_ms(100)
print("Sensor initialized successfully!")
# -------------------------------------------------------------------------
# Main loop
# -------------------------------------------------------------------------
while True:
reading = sensor.getPPM()
# If PPB is more relevant for your sensor, use:
# reading = sensor.getPPB()
print("Sensor reading: {:.5f} PPM".format(reading))
time.sleep_ms(2500)
🔗Open the complete example
electrochemical-bridgeBoard.py at commit 623e1e6
Expected result:
Electrochemical Gas Sensor - ATtiny Bridge Board Example
Sensor initialized successfully!
After that, a line starting with Sensor reading: follows every 2.5 seconds, with the concentration in ppm to five decimals.
Functions used
ElectrochemicalGasSensor(sensor_type, i2c=None, adc_addr=0x49, config_pin=None)returns ElectrochemicalGasSensorCreates the sensor object. adc_addr decides how begin() talks to the board: from 0x30 to 0x37 it uses the ATtiny bridge protocol; any other value is used as the ADC's own I2C address.
ReturnsThe sensor object.
Parameters
| Parameter | Type | Description |
|---|---|---|
sensor_type | SensorConfig | The cell preset. SENSOR_NH3 is the preset for this board's cell. |
i2c | I2C | Your I2C object. With None, the module picks the pins itself only on the few boards it recognises, for example I2C(0, scl=Pin(22), sda=Pin(21)) on ESP32 and I2C(scl=Pin(5), sda=Pin(4)) on ESP8266. On a board it does not recognise it raises an exception, so pass your own I2C object. |
adc_addr | int | The address the module talks to. Default 0x49. The example passes 0x30, the board's address as shipped; the ADDRESS switch sets it from 0x30 to 0x37. |
config_pin | int or None | A GPIO pin number the module sets low while it configures the LMP91000 and high afterwards, when adc_addr is outside 0x30 to 0x37. Default None. Not used from 0x30 to 0x37. |
begin()returns boolWith an address from 0x30 to 0x37, checks that the bridge answers and sends it the ADC and analog front end settings from the preset. With any other address, starts the ADS1115 on that address directly and configures the LMP91000. Call it once before any reading.
ReturnsTrue when every step succeeded. False when the bridge did not answer OK within 500 ms or reported an error, when starting the ADS1115 failed, or when configuring the analog front end failed.
getPPM()returns floatTakes one ADC reading, removes the front end's internal zero, adds the zero calibration, and converts the cell current to ppm. A result below zero is returned as 0.0.
ReturnsThe gas concentration in ppm.
getPPB()returns floatThe same reading as getPPM(), in parts per billion. The example shows it in a comment as an alternative.
ReturnsgetPPM() multiplied by 1000.
Putting it together
Create the sensor with a preset and the switch's address, call begin() once, then call getPPM() whenever you want a reading. If begin() returns False, check the cable and the address first.