CO sensor - MICS-5524 or MICS-6814

Hi,

Has someone already integrated this kind of sensors?

I’m looking for a smaller form factor carbon monoxide sensors then the mq-7.

Thanks

2 Likes

Push this question up

If you have the MQ-7 or the MQ-2 up and running on an ESP32 for ESPHome, would you mind sharing that code?
And a follow-up Q; If I need to calibrate these components, do you might have some advice for me as to where to find such code too?

MQ sensors are VERY bad ones. Forget it.

What sensor do you recommend to an ESP32 and ESPHome?

1 Like

Why are MQ7 “so bad”? I mean - in what way? Accuracy? I just installed one in my basement where i have my pellets stove for central heating. I don’t need accuracy there, i just need to know if i’ll “die or stay alive”… :rofl:

I successfully integrated the MICS 5524 sensor. As it gives an analog output, we can use the Analog To Digital Sensor, it’s just a bit tricky to figure out the conversion from analog voltage to CO level.

Here's how I got the conversion from the datasheet

Converting Sensor Voltage to CO level

This relation is not straight up given in the datasheet, but there is a diagram that shows the change in resistance relative to the CO-level. There will probably be significant deviations from this graph from sensor to sensor, but this is all we have to get an estimate.

Resistance Rs to CO

Datasheet shows line in log-log scale, so that means that y = a * x^k, and in our case CO = a * (Rs/R0)^k.

To determine our constants we will take two measurements in the diagram:

  1. CO = 10 ppm @ Rs/R0 = 0.5

  2. CO = 1000 ppm @ Rs/R0 = 0.01

This gives us the following solution:

  • a = 6.3

  • k = -1.1

And thus CO = 6.3 * (Rs/R0)^-1.1

Rs is the measured sensing resistance and R0 is its resistance in normal air (at apparently 4.5 ppm CO). The datasheet states that it is somewhere between 100 kΩ and 1500 kΩ, so I assumed R0 = 700 kΩ.

Measuring Rs

On the usual PCB-Modules, the sensor is typically set up to form a voltage divider with a 10 kΩ resistor to ground. This means that the voltage on the Aout pin is determined by:

Aout = 5V * (10 kΩ) / (Rs + 10 kΩ).

Now, to determine our CO level with our handy formula, we need to calculate the current resistance of Rs, so we rearrange to get this:

Rs = (5V * (10 kΩ) / Aout) - 10 kΩ

Final Formula

Now we can just combine everything into one final formula, to get the CO level of the measured Aout voltage:

CO = 6.3 * (((5V * (10 kΩ) / Aout) - 10 kΩ)/(700 kΩ))^-1.1

Anyways, you’re probably just interested in the configuration, so here it is.

Warning: This sensor is not suitable to get accurate measurements, this configuration will only indicate a rough estimate! Individual sensors may vary significantly in offset and sensitivity, a low reading can not be trusted!

Note: The normal ESP32 can’t read voltages below 0.075V, which results in a minimum measuered CO level of 7 ppm.

sensor:
  - platform: adc
    pin: GPIOXX
    name: "CO"
    update_interval: 30s
    attenuation: auto
    unit_of_measurement: "ppm"
    accuracy_decimals: 0
    filters:
      - lambda: return 6.3f * pow(((5.0f * 10.0f / x) - 10.0f) / 700.0f, -1.1f);

I can confirm that it reacts to alcohol, but I don’t have the means to test and verify that it actually produces semi-accurate CO level readings, so use with caution!

Hi guys, thanks @JensKolb for all your pre-work! I typed in your formula into: Desmos | Graphing Calculator and found your formula did not align with the Grid on the datasheet, so I did a bit of trial and error on the above website to find a formula that is a bit closer to the chart from the MiCS 5524 datasheet.:
3.5 * (Rs)-0.85 or in code terms:

sensor:
  - platform: adc
    pin: GPIO32
    id: volt
    update_interval: 1s
    attenuation: auto
    internal: False
    name: "Volt"
    unit_of_measurement: "V"
    accuracy_decimals: 5
  - platform: template
    id: RsR0
    name: Rs/R0
    internal: False
    update_interval: 1s
    #unit_of_measurement: "ppm"
    accuracy_decimals: 5
    lambda: |-
      return (((5.0f * 10.0f / id(volt).state) - 10.0f) / 700.0F);
  - platform: template
    id: CO
    name: Carbon Monoxide
    internal: False
    update_interval: 1s
    unit_of_measurement: "ppm"
    accuracy_decimals: 0
    lambda: |-
      float co = 3.5f * pow(id(RsR0).state, -0.85f);
      if(co < 7) co = 0.0;
      if(co > 10000.0) co = 10000.0;
      return co;

If somebody have the time to align the other gases, this is how to set up the graph, you just have to play around with the 3.5 and -0.85 to change the graph, it actually is pretty easy now:

Thanks for the heads-up! I quietly switched x and y of the formula (CO and Rs/R0) for simpler transformation.
To recreate the graph in the datasheet, the exact formula would be
(RS/R0) = 3.53553*CO-0.849485.

But we need it the other way around: CO = a*(RS/R0)^k, and that’s what I tried to calculate.
However, I apparently did an error, because the correct values would be a=4.42214, k=-1.17718

I am testing this two sensors MICS-4514 and MICS-5524 and managed to make them work ,this is my config for now

esphome:
  name: gas-sensor
  friendly_name: "Multi-Gas Detector"
  on_boot:
    priority: 600
    then:
      - output.turn_on: heater_control_4514
      - output.set_level:
          id: heater_control_4514
          level: 1.0  # 100% duty cycle for MICS-4514 heater
      - output.turn_on: enable_5524
      - logger.log: "MICS-4514 and MICS-5524 enabled at boot"
esp32:
  board: esp-wrover-kit
  framework:
    type: esp-idf
    sdkconfig_options:
      CONFIG_ADC_CAL_EFUSE_TP_ENABLE: y
      CONFIG_ADC_CAL_EFUSE_VREF_ENABLE: y
      CONFIG_ADC_CAL_LUT_ENABLE: y
ota:
  platform: esphome
  password: !secret ota_password
web_server:
  version: 3


logger:
  level: INFO
  baud_rate: 0  # Disables UART logging (optional)

wifi:
  networks:
  - ssid: !secret wifi3_ssid
    password: !secret wifi3_password
  - ssid: !secret wifi_ssid
    password: !secret wifi_password
  ap:
    ssid: "Gas Sensor Fallback"
    password: "xxxxxxxx"

api:
  encryption:
    key: !secret api_key
  services:
    - service: restart
      then:
        - logger.log: "Restart requested via Home Assistant"
        - delay: 1s

i2c:
  sda: GPIO21
  scl: GPIO22
  scan: true
  id: i2c_bus

ads1115:
  - address: 0x48
    id: ads
    continuous_mode: true

output:
  - platform: ledc
    pin: GPIO2
    id: heater_control_4514
    frequency: 100Hz  # Suitable for MICS-4514 heater control
  - platform: gpio
    pin: GPIO4
    id: enable_5524

binary_sensor:
  - platform: status
    name: "Sensor Status"

button:
  - platform: template
    name: "Start Calibration"
    id: start_calibration
    on_press:
      then:
        - output.turn_on: heater_control_4514
        - output.set_level:
            id: heater_control_4514
            level: 1.0
        - output.turn_on: enable_5524
        - delay: 180s  # 3-minute warm-up
        - lambda: |-
            float rs_red = id(mics_rs_red_4514).state;
            float rs_ndx = id(mics_rs_ndx_4514).state;
            float rs_5524 = id(mics_rs_5524).state;
            if (!isnan(rs_red) && rs_red > 1000 && rs_red < 50000 &&
                !isnan(rs_ndx) && rs_ndx > 1000 && rs_ndx < 50000 &&
                !isnan(rs_5524) && rs_5524 > 1000 && rs_5524 < 50000) {
              id(mics_ro_red_4514) = rs_red;
              id(mics_ro_ndx_4514) = rs_ndx;
              id(mics_ro_5524) = rs_5524;
              ESP_LOGI("calib", "New baselines: RED_4514=%.0f Ohms, NDX_4514=%.0f Ohms, 5524=%.0f Ohms", rs_red, rs_ndx, rs_5524);
            } else {
              ESP_LOGE("calib", "Invalid Rs: RED_4514=%.0f Ohms, NDX_4514=%.0f Ohms, 5524=%.0f Ohms", rs_red, rs_ndx, rs_5524);
            }

time:
  - platform: homeassistant
    id: hass_time

globals:
  - id: invalid_reading_minutes
    type: int
    restore_value: no
    initial_value: '0'
  - id: mics_ro_red_4514
    type: float
    restore_value: true
    initial_value: '10000.0'
  - id: mics_ro_ndx_4514
    type: float
    restore_value: true
    initial_value: '10000.0'
  - id: mics_ro_5524
    type: float
    restore_value: true
    initial_value: '10000.0'

interval:
  - interval: 1min
    then:
      - lambda: |-
          if (isnan(id(mics_rs_red_4514).state) || isnan(id(mics_rs_ndx_4514).state) || isnan(id(mics_rs_5524).state)) {
            id(invalid_reading_minutes) += 1;
            ESP_LOGW("check", "Rs is invalid for %d minute(s)", id(invalid_reading_minutes));
          } else {
            id(invalid_reading_minutes) = 0;
          }
          if (id(invalid_reading_minutes) >= 5) {
            ESP_LOGE("check", "Rs invalid for 5+ minutes. Restarting...");
            id(invalid_reading_minutes) = 0;
            App.reboot();
          }
bme68x_bsec2_i2c:
  address: 0x77
  model: bme680
  operating_age: 28d
  sample_rate: LP
  supply_voltage: 3.3V
sensor:
  - platform: ads1115
    ads1115_id: ads
    multiplexer: A0_GND
    gain: 4.096  # Suitable for 0–4.096V; use 6.144 if Vout exceeds 4.096V
    update_interval: 5s
    id: mics_vout_red_4514
    name: "MICS-4514 RED Vout"
    unit_of_measurement: "V"
    accuracy_decimals: 4
    filters:
      - median:
          window_size: 5
          send_every: 2
      - sliding_window_moving_average:
          window_size: 10
          send_every: 5

  - platform: ads1115
    ads1115_id: ads
    multiplexer: A1_GND
    gain: 4.096
    update_interval: 5s
    id: mics_vout_ndx_4514
    name: "MICS-4514 NDX Vout"
    unit_of_measurement: "V"
    accuracy_decimals: 4
    filters:
      - median:
          window_size: 5
          send_every: 2
      - sliding_window_moving_average:
          window_size: 10
          send_every: 5

  - platform: ads1115
    ads1115_id: ads
    multiplexer: A2_GND
    gain: 4.096
    update_interval: 5s
    id: mics_vout_5524
    name: "MICS-5524 Vout"
    unit_of_measurement: "V"
    accuracy_decimals: 4
    filters:
      - median:
          window_size: 5
          send_every: 2
      - sliding_window_moving_average:
          window_size: 10
          send_every: 5
  - platform: bme68x_bsec2
    temperature:
      name: "BME68x Temperature"
    pressure:
      name: "BME68x Pressure"
    humidity:
      name: "BME68x Humidity"
    iaq:
      name: "BME68x IAQ"
      id: iaq
    co2_equivalent:
      name: "BME68x CO2 Equivalent"
    breath_voc_equivalent:
      name: "BME68x Breath VOC Equivalent"
  - platform: wifi_signal
    name: "WiFi Signal"
    update_interval: 60s

  - platform: uptime
    name: "Uptime"

  - platform: template
    name: "MICS-4514 RED Rs"
    id: mics_rs_red_4514
    unit_of_measurement: "Ohms"
    accuracy_decimals: 0
    update_interval: 10s
    lambda: |-
      float vout = id(mics_vout_red_4514).state;
      if (vout > 0.1 && vout < 4.9) {
        return 820.0 * (5.0 / vout - 1);
      }
      ESP_LOGE("sensor", "Invalid MICS-4514 RED Vout: %.2fV", vout);
      return NAN;

  - platform: template
    name: "MICS-4514 NDX Rs"
    id: mics_rs_ndx_4514
    unit_of_measurement: "Ohms"
    accuracy_decimals: 0
    update_interval: 10s
    lambda: |-
      float vout = id(mics_vout_ndx_4514).state;
      if (vout > 0.1 && vout < 4.9) {
        return 820.0 * (5.0 / vout - 1);
      }
      ESP_LOGE("sensor", "Invalid MICS-4514 NDX Vout: %.2fV", vout);
      return NAN;

  - platform: template
    name: "MICS-5524 Rs"
    id: mics_rs_5524
    unit_of_measurement: "Ohms"
    accuracy_decimals: 0
    update_interval: 10s
    lambda: |-
      float vout = id(mics_vout_5524).state;
      if (vout > 0.1 && vout < 4.9) {
        return 820.0 * (5.0 / vout - 1);
      }
      ESP_LOGE("sensor", "Invalid MICS-5524 Vout: %.2fV", vout);
      return NAN;

  - platform: template
    name: "MICS-4514 RED Rs/Ro Ratio"
    id: rs_ro_ratio_red_4514
    accuracy_decimals: 4
    update_interval: 10s
    lambda: |-
      float rs = id(mics_rs_red_4514).state;
      float ro = id(mics_ro_red_4514);
      if (isnan(rs) || isnan(ro) || ro <= 0) {
        return NAN;
      }
      return rs / ro;

  - platform: template
    name: "MICS-4514 NDX Rs/Ro Ratio"
    id: rs_ro_ratio_ndx_4514
    accuracy_decimals: 4
    update_interval: 10s
    lambda: |-
      float rs = id(mics_rs_ndx_4514).state;
      float ro = id(mics_ro_ndx_4514);
      if (isnan(rs) || isnan(ro) || ro <= 0) {
        return NAN;
      }
      return rs / ro;

  - platform: template
    name: "MICS-5524 Rs/Ro Ratio"
    id: rs_ro_ratio_5524
    accuracy_decimals: 4
    update_interval: 10s
    lambda: |-
      float rs = id(mics_rs_5524).state;
      float ro = id(mics_ro_5524);
      if (isnan(rs) || isnan(ro) || ro <= 0) {
        return NAN;
      }
      return rs / ro;

  - platform: template
    name: "MICS-4514 Carbon Monoxide (CO)"
    id: co_ppm_4514
    unit_of_measurement: "ppm"
    accuracy_decimals: 1
    update_interval: 10s
    lambda: |-
      float ratio = id(rs_ro_ratio_red_4514).state;
      return isnan(ratio) ? NAN : 0.08 * pow(ratio, -2.95) * exp(1.77 * log(ratio));

  - platform: template
    name: "MICS-4514 Methane (CH4)"
    id: ch4_ppm_4514
    unit_of_measurement: "ppm"
    accuracy_decimals: 1
    update_interval: 10s
    lambda: |-
      float ratio = id(rs_ro_ratio_red_4514).state;
      return isnan(ratio) ? NAN : 0.85 * pow(ratio, -2.61) * exp(1.62 * log(ratio));

  - platform: template
    name: "MICS-4514 Ethanol (C2H5OH)"
    id: ethanol_ppm_4514
    unit_of_measurement: "ppm"
    accuracy_decimals: 1
    update_interval: 10s
    lambda: |-
      float ratio = id(rs_ro_ratio_red_4514).state;
      return isnan(ratio) ? NAN : 0.10 * pow(ratio, -3.14) * exp(2.43 * log(ratio));

  - platform: template
    name: "MICS-4514 Hydrogen (H2)"
    id: h2_ppm_4514
    unit_of_measurement: "ppm"
    accuracy_decimals: 1
    update_interval: 10s
    lambda: |-
      float ratio = id(rs_ro_ratio_red_4514).state;
      return isnan(ratio) ? NAN : 0.04 * pow(ratio, -1.8) * exp(0.9 * log(ratio));

  - platform: template
    name: "MICS-4514 Ammonia (NH3)"
    id: nh3_ppm_4514
    unit_of_measurement: "ppm"
    accuracy_decimals: 1
    update_interval: 10s
    lambda: |-
      float ratio = id(rs_ro_ratio_red_4514).state;
      return isnan(ratio) ? NAN : 0.10 * pow(ratio, -1.67) * exp(0.87 * log(ratio));

  - platform: template
    name: "MICS-4514 Nitrogen Dioxide (NO2)"
    id: no2_ppm_4514
    unit_of_measurement: "ppm"
    accuracy_decimals: 2
    update_interval: 10s
    lambda: |-
      float ratio = id(rs_ro_ratio_ndx_4514).state;
      return isnan(ratio) ? NAN : 1.8 * pow(ratio, 1.18) * exp(-0.35 * log(ratio));

  - platform: template
    name: "MICS-5524 Carbon Monoxide (CO)"
    id: co_ppm_5524
    unit_of_measurement: "ppm"
    accuracy_decimals: 1
    update_interval: 10s
    lambda: |-
      float ratio = id(rs_ro_ratio_5524).state;
      return isnan(ratio) ? NAN : 0.25 * pow(ratio, -2.95) * exp(1.77 * log(ratio));

  - platform: template
    name: "MICS-5524 Methane (CH4)"
    id: ch4_ppm_5524
    unit_of_measurement: "ppm"
    accuracy_decimals: 1
    update_interval: 10s
    lambda: |-
      float ratio = id(rs_ro_ratio_5524).state;
      return isnan(ratio) ? NAN : 2.4 * pow(ratio, -2.61) * exp(1.62 * log(ratio));

  - platform: template
    name: "MICS-5524 Ethanol (C2H5OH)"
    id: ethanol_ppm_5524
    unit_of_measurement: "ppm"
    accuracy_decimals: 1
    update_interval: 10s
    lambda: |-
      float ratio = id(rs_ro_ratio_5524).state;
      return isnan(ratio) ? NAN : 0.15 * pow(ratio, -3.14) * exp(2.43 * log(ratio));

  - platform: template
    name: "MICS-5524 Hydrogen (H2)"
    id: h2_ppm_5524
    unit_of_measurement: "ppm"
    accuracy_decimals: 1
    update_interval: 10s
    lambda: |-
      float ratio = id(rs_ro_ratio_5524).state;
      return isnan(ratio) ? NAN : 0.10 * pow(ratio, -1.8) * exp(0.9 * log(ratio));

  - platform: template
    name: "MICS-5524 Ammonia (NH3)"
    id: nh3_ppm_5524
    unit_of_measurement: "ppm"
    accuracy_decimals: 1
    update_interval: 10s
    lambda: |-
      float ratio = id(rs_ro_ratio_5524).state;
      return isnan(ratio) ? NAN : 0.17 * pow(ratio, -1.67) * exp(0.87 * log(ratio));

  - platform: template
    name: "MICS-5524 Nitrogen Dioxide (NO2)"
    id: no2_ppm_5524
    unit_of_measurement: "ppm"
    accuracy_decimals: 2
    update_interval: 10s
    lambda: |-
      float ratio = id(rs_ro_ratio_5524).state;
      return isnan(ratio) ? NAN : 0.13 * pow(ratio, 1.18) * exp(-0.35 * log(ratio));
2 Likes

1 Like

Hardware Setup

  • MICS-4514 Connections:
    • VCC: Connect to ESP32 5V
    • GND: Connect to ESP32 GND
    • RED: Connect to ADS1115 A0 (reducing gases: CO, CH4, Ethanol, H2, NH3).
    • NDX: Connect to ADS1115 A1 (oxidizing gases: NO2).
    • RPE: Connect to ESP32 GPIO2 (PWM for heater control pin).
  • MICS-5524 Connections:
    • VCC: Connect to ESP32 5V.
    • GND: Connect to ESP32 GND.
    • Vout: Connect to ADS1115 A2 (single output for all gases).
    • Enable: Connect to ESP32 GPIO4 (set low to enable).
  • ADS1115 Connections:
    • VCC: Connect to ESP32 3.3V or 5V (match sensor VCC).
    • GND: Connect to ESP32 GND.
    • SDA: Connect to ESP32 GPIO21 (default I2C SDA).
    • SCL: Connect to ESP32 GPIO22 (default I2C SCL).
    • ADDR: Connect to GND (I2C address 0x48).
1 Like