H₂S Gas Sensor
STM32G431KB · Firmware v0.1.0
Calibration

Calibration System

Two-point linear calibration — zero point, span point, and PPM conversion.

Principle

The firmware implements two-point linear calibration with hardware baseline subtraction.

ChB (DAC channel B) injects an analogue offset into the signal path to cancel the sensor’s clean-air baseline voltage at the ADC. After zero calibration, the ADC reads ≈ 0 mV in clean air. ChA (DAC channel A) scales the remaining gas signal to achieve a target sensitivity of 10 mV/ppm.

PPM conversion is therefore:

ppm = V_adc / slope        slope = 10 mV/ppm

No software baseline subtraction is needed — ChB handles it in hardware.

Calibration State Machine

┌──────────────┐
│ Uncalibrated │
└──────┬───────┘
       │  ZERO command (stability required)

┌─────────────────────┐
│  ZeroCalibrated     │
│  baseline_code      │
│  ChB offset applied │
└──────┬──────────────┘
       │  SPAN:<ppm> command

┌─────────────────────┐
│  FullyCalibrated    │
│  slope = 10 mV/ppm  │
│  ChA gain applied   │
└─────────────────────┘
pub enum CalibrationState {
    Uncalibrated,
    ZeroCalibrated { data: CalibrationData },
    FullyCalibrated { data: CalibrationData },
}

pub struct CalibrationData {
    pub baseline_mv:   f32,   // Sensor voltage in clean air (mV)
    pub baseline_code: u16,   // ADC code at zero
    pub slope:         f32,   // mV per ppm (target: 10.0)
}

Step 1: Zero Calibration

Requirement: Sensor stable for ≥ 30 seconds (30-sample window, rate-of-change ≤ 0.1 mV).

Procedure:

  1. Place sensor in clean air (0 ppm)
  2. Wait until STABILITY reports is_stable=1
  3. Send ZERO

What happens:

  • Firmware reads the current mV mean from StabilityMonitor, converts to ADC code
  • Stores as baseline_code
  • Calculates ChB percentage and writes it to TARGET_DAC

ChB Offset Calculation

ChB full scale = 2000 mV. The required offset equals the sensor baseline voltage:

chb_percent = baseline_mv / 2000 × 100
            = baseline_mv / 20
pub fn compute_chb_percent(baseline_code: u16) -> f32 {
    (adc_code_to_mv(baseline_code) / 20.0).clamp(0.0, 100.0)
}
Baseline (mV)ChB %
50025%
100050%
150075%
2000100%

After zero calibration the ADC should read ≈ 0 mV in clean air.

Step 2: Span Calibration

Requirement: Zero calibration already done.

Procedure:

  1. Expose sensor to a known concentration (e.g. 25 ppm certified cylinder)
  2. Wait for sensor to stabilise (T90 ≈ 30–35 s)
  3. Send SPAN:25

What happens:

  • Firmware reads the current ADC code (span_mv = code × VDD / 4095)
  • Calculates the ChA gain required to normalise to 10 mV/ppm:
    cha_percent = ppm × 10 / span_mv × 100
    
  • Stores slope = span_mv × cha_percent/100 / ppm = 10.0 mV/ppm
  • Writes ChA to TARGET_DAC and resets the stability buffer

The sensor’s raw sensitivity is always > 10 mV/ppm, so ChA will always be ≤ 100%.

Example

span_mv at 25 ppm = 350 mV

cha_percent = 25 × 10 / 350 × 100 = 71.4%

slope = 350 × 0.714 / 25 = 10.0 mV/ppm  ✓

PPM Conversion

pub fn mv_to_ppm(&self, mv: f32) -> Option<f32> {
    match self {
        FullyCalibrated { data } => Some(mv / data.slope),
        _ => None,
    }
}

GAS responses use the 30-sample stability mean (mV) for noise rejection. If the stability buffer is empty, the latest raw ADC code is used as a fallback.

ADC Voltage Reference

VDD is determined at boot using the STM32G4 factory-calibrated VREFINT value stored at 0x1FFF75AA (measured at VDD = 3.0 V, 30 °C):

let cal = unsafe { (*(0x1FFF75AA as *const u16)) as u32 & 0xFFF };
let actual_vdd = (3000 * cal) / vrefint_code;

This eliminates the per-chip VREFINT offset (typically ±10 mV) and provides ratiometric VDD noise compensation. VREFINT is sampled with CYCLES247_5 (≈ 15.5 µs) to meet the STM32G4 minimum 4 µs stabilisation requirement.

Calibration Procedure

1. Power on → wait 30+ s for electrochemical warm-up
2. Apply zero air (0 ppm)
3. Poll:  STABILITY  →  wait until is_stable=1
4. Send:  ZERO       →  stores baseline, sets ChB
5. Apply known-concentration gas (e.g. 25 ppm cylinder)
6. Wait T90 (30–35 s)
7. Send:  SPAN:25    →  sets ChA, slope stored as 10 mV/ppm
8. Send:  GAS        →  verify shows ≈ 25 ppm
9. Remove gas        →  verify return to ≈ 0 ppm

Data Persistence

Calibration is saved to flash (page 63, address 0x0801_F800) after both ZERO and SPAN.

FieldTypeDescription
magicu320xCAFEBABE — validity check
baseline_codeu16ADC code at zero
slopef32mV per ppm (10.0 after span)
is_calibratedu81 when span is done

Last updated: March 2026