Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 76 additions & 99 deletions README.md

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions examples/Example01_BasicReadings/Example01_BasicReadings.ino
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@
#include <SparkFun_GMX02B.h>

// Uncomment the board you're using
// SfeGM102BArdI2C mySensor; // GM-102B - nitrogen dioxide (NO2)
// SfeGM202BArdI2C mySensor; // GM-202B - alcohol, smoke
// SfeGM302BArdI2C mySensor; // GM-302B - ethanol
// SfeGM402BArdI2C mySensor; // GM-402B - methane, propane
// SfeGM502BArdI2C mySensor; // GM-502B - VOCs
// SfeGM512BArdI2C mySensor; // GM-512B - breath odor
SfeGM602BArdI2C mySensor; // GM-602B - hydrogen sulfide (H2S)
// SfeGM702BArdI2C mySensor; // GM-702B - carbon monoxide (CO)
// SfeGM802BArdI2C mySensor; // GM-802B - ammonia (NH3)
// SfeGM102BArd mySensor; // GM-102B - nitrogen dioxide (NO2)
// SfeGM202BArd mySensor; // GM-202B - alcohol, smoke
// SfeGM302BArd mySensor; // GM-302B - ethanol
// SfeGM402BArd mySensor; // GM-402B - methane, propane
// SfeGM502BArd mySensor; // GM-502B - VOCs
// SfeGM512BArd mySensor; // GM-512B - breath odor
SfeGM602BArd mySensor; // GM-602B - hydrogen sulfide (H2S)
// SfeGM702BArd mySensor; // GM-702B - carbon monoxide (CO)
// SfeGM802BArd mySensor; // GM-802B - ammonia (NH3)

void setup()
{
Expand All @@ -55,7 +55,7 @@ void setup()
{
Serial.print("No ");
Serial.print(mySensor.getSensorName());
Serial.println(" found - check your wiring!");
Serial.println(" found, check your wiring!");
delay(1000);
}

Expand Down
24 changes: 12 additions & 12 deletions examples/Example02_CleanAirResponse/Example02_CleanAirResponse.ino
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@

Eight of the nine sensing elements detect reducing gases: their resistance FALLS as gas rises,
so the datasheet's Rs/R0 ratio drops below 1.0. The GM-102B detects NO2, an oxidizing gas, and
its resistance RISES - so its Rs/R0 climbs ABOVE 1.0 instead. A sketch that tests
"ratio < 0.5 means gas" is exactly backwards on that one part.
its resistance RISES, so its Rs/R0 climbs ABOVE 1.0 instead. A sketch that tests
"ratio < 0.5 means gas" is backwards on that part.

getGasResponse() removes the trap: it is R0/Rs on falling-Rs parts and Rs/R0 on rising-Rs parts,
getGasResponse() handles this: it is R0/Rs on falling-Rs parts and Rs/R0 on rising-Rs parts,
so it sits near 1.0 in clean air and RISES with target gas on all nine. It is also the direction
the datasheets quote sensitivity in, so it compares directly against getRatedSensitivity():

Expand Down Expand Up @@ -43,15 +43,15 @@
#include <SparkFun_GMX02B.h>

// Uncomment the board you're using
// SfeGM102BArdI2C mySensor; // GM-102B - nitrogen dioxide (NO2)
// SfeGM202BArdI2C mySensor; // GM-202B - alcohol, smoke
// SfeGM302BArdI2C mySensor; // GM-302B - ethanol
// SfeGM402BArdI2C mySensor; // GM-402B - methane, propane
// SfeGM502BArdI2C mySensor; // GM-502B - VOCs
// SfeGM512BArdI2C mySensor; // GM-512B - breath odor
SfeGM602BArdI2C mySensor; // GM-602B - hydrogen sulfide (H2S)
// SfeGM702BArdI2C mySensor; // GM-702B - carbon monoxide (CO)
// SfeGM802BArdI2C mySensor; // GM-802B - ammonia (NH3)
// SfeGM102BArd mySensor; // GM-102B - nitrogen dioxide (NO2)
// SfeGM202BArd mySensor; // GM-202B - alcohol, smoke
// SfeGM302BArd mySensor; // GM-302B - ethanol
// SfeGM402BArd mySensor; // GM-402B - methane, propane
// SfeGM502BArd mySensor; // GM-502B - VOCs
// SfeGM512BArd mySensor; // GM-512B - breath odor
SfeGM602BArd mySensor; // GM-602B - hydrogen sulfide (H2S)
// SfeGM702BArd mySensor; // GM-702B - carbon monoxide (CO)
// SfeGM802BArd mySensor; // GM-802B - ammonia (NH3)

// Flag for an elevated reading. Tune this for your environment. getRatedSensitivity() is a
// reasonable upper anchor (the response this part is specified to reach at its rated ppm).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
/*
Example 03 - Gas Alert Threshold

Hardware gas threshold detection with no polling. The onboard ADC101C021 can watch the sensor
entirely by itself: in automatic conversion mode it converts on its own schedule, compares every
Hardware gas threshold detection with no polling. The onboard ADC101C021 can monitor the sensor
on its own: in automatic conversion mode it converts on its own schedule, compares every
result against a programmable limit, and raises a flag (and optionally its ALERT pin) when the
limit is crossed.

setGasAlertResponse() takes a threshold in the same units as getGasResponse() - 1.0 is clean air,
larger means more gas - and writes it to whichever limit register means "more gas" for your part.
That matters: on the GM-102B, resistance RISES with NO2, so ADC counts FALL and the gas-side trip
setGasAlertResponse() takes a threshold in the same units as getGasResponse() (1.0 is clean air,
larger means more gas) and writes it to whichever limit register means "more gas" for your part.
On the GM-102B, resistance RISES with NO2, so ADC counts FALL and the gas-side trip
point is the LOW limit, not the high one. getGasAlert() maps the status flags back the same way.
Neither call requires your sketch to know which direction its sensor moves.

If you want to work in raw ADC counts instead, setAlertLowLimit() / setAlertHighLimit() /
getAlertStatus() are still there and are direction-neutral - but then the direction is yours to
handle. rsRisesWithGas() tells you which way this part goes.
getAlertStatus() are also available. They are direction-neutral, so your sketch has to handle the
direction; rsRisesWithGas() tells you which way this part goes.

This is also the one example that checks return codes on every call. The others skip it: once
begin() has succeeded, the rest is reliable enough that error handling is just clutter. Here it
earns its place, because a threshold set against a missing clean-air reference would silently
never fire.
This example checks return codes on every call. The other examples skip this for brevity, but it
matters here: a threshold set without a clean-air reference would never fire.

SparkFun Electronics
Date: 2026
Expand All @@ -30,10 +28,10 @@
IoT RedBoard --> GM-x02B Breakout
QWIIC --> QWIIC

By default this sketch polls the alert over I2C with getGasAlert(), which needs no extra wiring
at all. Uncomment USE_ALERT_PIN below to read the ADC's hardware ALERT pin instead.
By default this sketch polls the alert over I2C with getGasAlert(), which needs no extra wiring.
Uncomment USE_ALERT_PIN below to read the ADC's hardware ALERT pin instead.

IMPORTANT - THE BOARD DOES NOT POPULATE A PULL-UP ON ALERT. The ADC101C021's ALERT is an open
IMPORTANT: THE BOARD DOES NOT POPULATE A PULL-UP ON ALERT. The ADC101C021's ALERT is an open
drain output in BOTH polarity modes (setAlertPinActiveHigh() changes only the logic sense, not
the output structure), so nothing holds it high on its own. Without a pull-up the pin floats and
reads as garbage. The sketch enables the MCU's internal pull-up for you; see the notes at
Expand All @@ -48,23 +46,23 @@
#include <SparkFun_GMX02B.h>

// Uncomment the board you're using
// SfeGM102BArdI2C mySensor; // GM-102B - nitrogen dioxide (NO2)
// SfeGM202BArdI2C mySensor; // GM-202B - alcohol, smoke
// SfeGM302BArdI2C mySensor; // GM-302B - ethanol
// SfeGM402BArdI2C mySensor; // GM-402B - methane, propane
// SfeGM502BArdI2C mySensor; // GM-502B - VOCs
// SfeGM512BArdI2C mySensor; // GM-512B - breath odor
SfeGM602BArdI2C mySensor; // GM-602B - hydrogen sulfide (H2S)
// SfeGM702BArdI2C mySensor; // GM-702B - carbon monoxide (CO)
// SfeGM802BArdI2C mySensor; // GM-802B - ammonia (NH3)

// Trip when the response reaches twice its clean-air value. Tune this for your application -
// SfeGM102BArd mySensor; // GM-102B - nitrogen dioxide (NO2)
// SfeGM202BArd mySensor; // GM-202B - alcohol, smoke
// SfeGM302BArd mySensor; // GM-302B - ethanol
// SfeGM402BArd mySensor; // GM-402B - methane, propane
// SfeGM502BArd mySensor; // GM-502B - VOCs
// SfeGM512BArd mySensor; // GM-512B - breath odor
SfeGM602BArd mySensor; // GM-602B - hydrogen sulfide (H2S)
// SfeGM702BArd mySensor; // GM-702B - carbon monoxide (CO)
// SfeGM802BArd mySensor; // GM-802B - ammonia (NH3)

// Trip when the response reaches twice its clean-air value. Tune this for your application;
// getRatedSensitivity() is a reasonable upper anchor (the response the part is specified to
// reach at getRatedConcentration_ppm()).
const float kAlertResponse = 2.0;

// Hysteresis, so the alert does not chatter right at the threshold. Set in millivolts of sensor
// output rather than raw ADC counts - setAlertHysteresis_mV() does the conversion.
// output rather than raw ADC counts; setAlertHysteresis_mV() does the conversion.
const float kHysteresis_mV = 32.0;

// ---------------------------------------------------------------------------------------------
Expand All @@ -78,12 +76,12 @@ const float kHysteresis_mV = 32.0;
// GPIO wired to the board's ALERT pad. Change to a pin your board actually has.
const uint8_t kAlertPin = 2;

// ALERT is open drain and the board has NO pull-up fitted, so one has to come from somewhere.
// ALERT is open drain and the board has NO pull-up fitted, so one must be provided.
// The MCU's internal pull-up is enough for a single board on a short wire:
//
// ALERT leaks at most 1 uA when high-impedance (IOZ, datasheet), so even a weak 50 kOhm
// internal pull-up droops only 50 kOhm x 1 uA = 50 mV below the rail - still a solid logic high.
// Pulling low is never the problem: VOL is 0.4 V max at 3 mA, and the internal pull-up sources
// internal pull-up droops only 50 kOhm x 1 uA = 50 mV below the rail, still a solid logic high.
// Pulling low is not an issue: VOL is 0.4 V max at 3 mA, and the internal pull-up sources
// only 3.3 V / 50 kOhm = 66 uA.
//
// Set this false and fit a discrete resistor from ALERT to 3.3V instead when your MCU pin has no
Expand All @@ -92,8 +90,8 @@ const uint8_t kAlertPin = 2;
//
// USE 10 kOhm. Sink current 3.3 V / 10 kOhm = 330 uA (well inside the 3 mA VOL spec), droop
// 10 kOhm x 1 uA = 10 mV, and an edge of 10 kOhm x ~20 pF = 0.2 us.
// Floor 1 kOhm - 3.3 mA sink, already past the 0.4 V VOL test point.
// Ceiling 100 kOhm - 100 mV of leakage droop and a ~2 us edge.
// Floor: 1 kOhm, 3.3 mA sink, already past the 0.4 V VOL test point.
// Ceiling: 100 kOhm, 100 mV of leakage droop and a ~2 us edge.
//
const bool kUseInternalPullup = true;

Expand Down Expand Up @@ -137,7 +135,7 @@ void setup()
Serial.print("R0 (ohm): ");
Serial.println(r0, 0);

// Set the trip point, then report where it landed in raw counts so you can see the mapping.
// Set the trip point, then report it as a sensor output voltage.
if (mySensor.setGasAlertResponse(kAlertResponse) != ksfTkErrOk)
{
Serial.println("Failed to set the alert threshold!");
Expand All @@ -150,8 +148,8 @@ void setup()
Serial.print("Alerting above a response of ");
Serial.print(kAlertResponse, 1);
Serial.print(" (");
Serial.print(counts);
Serial.print(" ADC counts), hysteresis ");
Serial.print(mySensor.countsToVoltage_V(counts), 3);
Serial.print(" V), hysteresis ");
Serial.print(kHysteresis_mV, 0);
Serial.println(" mV");

Expand All @@ -175,8 +173,8 @@ void setup()
: " (expecting an external 10 kOhm pull-up to 3.3V)");
#endif

// Hand the sensor over to the ADC. The slowest cycle time is plenty for a MOS gas sensor and
// uses the least power.
// Put the ADC in automatic conversion mode. The slowest cycle time is sufficient for a MOS gas
// sensor and uses the least power.
mySensor.setCycleTime(GM_CYCLE_TIME_2048);

Serial.println();
Expand All @@ -189,8 +187,8 @@ void loop()
bool gasDetected = false;

#ifdef USE_ALERT_PIN
// Configured active low, so a LOW pin means the ADC has flagged an alert. No I2C traffic at
// all until we want to know why.
// Configured active low, so a LOW pin means the ADC has flagged an alert. No I2C traffic is
// needed to detect it.
gasDetected = (digitalRead(kAlertPin) == LOW);
#else
if (mySensor.getGasAlert(gasDetected) != ksfTkErrOk)
Expand All @@ -203,7 +201,7 @@ void loop()

if (gasDetected)
{
// Read the current value for context - the alert itself came from the hardware.
// Read the current value for context; the alert itself came from the hardware.
float response = 0.0;
mySensor.readMeasurement();

Expand Down
36 changes: 18 additions & 18 deletions examples/Example04_PeakTracking/Example04_PeakTracking.ino
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@

The ADC101C021 keeps two extra registers in hardware: the lowest and the highest conversion it
has seen since each was last cleared. In automatic conversion mode they update on every
conversion with no I2C traffic at all, so a gas spike between your polls still gets recorded.
conversion with no I2C traffic, so a gas spike between your polls still gets recorded.

Which register holds "most gas" depends on the part. On the eight reducing-gas sensors, more gas
means lower Rs, which means HIGHER ADC counts - so peak gas is the highest conversion. On the
GM-102B (NO2) it is the opposite, and peak gas is the LOWEST conversion. getPeakGasCounts() and
clearPeakGasCounts() pick the right register for you; getLowestConversion() and
means lower Rs and a HIGHER output voltage, so peak gas is the highest conversion. On the
GM-102B (NO2) it is the opposite, and peak gas is the LOWEST conversion. getPeakGasVoltage_V()
and clearPeakGasCounts() pick the right register for you; getLowestConversion() and
getHighestConversion() remain available if you want the raw registers.

SparkFun Electronics
Expand All @@ -29,15 +29,15 @@
#include <SparkFun_GMX02B.h>

// Uncomment the board you're using
// SfeGM102BArdI2C mySensor; // GM-102B - nitrogen dioxide (NO2)
// SfeGM202BArdI2C mySensor; // GM-202B - alcohol, smoke
// SfeGM302BArdI2C mySensor; // GM-302B - ethanol
// SfeGM402BArdI2C mySensor; // GM-402B - methane, propane
// SfeGM502BArdI2C mySensor; // GM-502B - VOCs
// SfeGM512BArdI2C mySensor; // GM-512B - breath odor
SfeGM602BArdI2C mySensor; // GM-602B - hydrogen sulfide (H2S)
// SfeGM702BArdI2C mySensor; // GM-702B - carbon monoxide (CO)
// SfeGM802BArdI2C mySensor; // GM-802B - ammonia (NH3)
// SfeGM102BArd mySensor; // GM-102B - nitrogen dioxide (NO2)
// SfeGM202BArd mySensor; // GM-202B - alcohol, smoke
// SfeGM302BArd mySensor; // GM-302B - ethanol
// SfeGM402BArd mySensor; // GM-402B - methane, propane
// SfeGM502BArd mySensor; // GM-502B - VOCs
// SfeGM512BArd mySensor; // GM-512B - breath odor
SfeGM602BArd mySensor; // GM-602B - hydrogen sulfide (H2S)
// SfeGM702BArd mySensor; // GM-702B - carbon monoxide (CO)
// SfeGM802BArd mySensor; // GM-802B - ammonia (NH3)

// Report and reset the peak every 10 seconds.
const unsigned long kWindowMillis = 10000;
Expand Down Expand Up @@ -73,19 +73,19 @@ void setup()
mySensor.clearPeakGasCounts();
windowStart = millis();

Serial.println("Current\t\tPeak (10 s window)");
Serial.println("Current (V)\tPeak (V, 10 s window)");
}

void loop()
{
uint16_t peak;
float peak;

mySensor.readMeasurement();
mySensor.getPeakGasCounts(peak);
mySensor.getPeakGasVoltage_V(peak);

Serial.print(mySensor.getRaw());
Serial.print(mySensor.getVoltage_V(), 3);
Serial.print("\t\t");
Serial.print(peak);
Serial.print(peak, 3);

if (millis() - windowStart >= kWindowMillis)
{
Expand Down
Loading
Loading