The MQ-7 is the carbon-monoxide member of the MQ family, and CO is a real danger in Indian homes: a generator or inverter-charging setup in a closed room, a charcoal sigri during winter, or a gas geyser in a bathroom without ventilation.
On the ESP32 DevKit V1 the module runs from VIN (5 V), and its analog output reaches D34 through a 10 kΩ / 20 kΩ divider, because AO can rise to 5 V and the ESP32’s pins stop at 3.3 V. The sketch warms the heater for three minutes, takes a clean-air reference, then prints the sensor’s Rs/R0 ratio and lights the blue LED when it falls well below the start value.
Not a CO alarm. CO can kill while you sleep. Use this only as an extra, experimental indicator next to a certified CO detector — never as the only protection near a generator, geyser or stove.
Wiring
| ESP32 DevKit V1 | MQ-7 carbon monoxide (CO) sensor module | Wire | Notes |
|---|---|---|---|
| VIN | VCC | red | Heater and load circuit both need 5 V (about 150 mA). VIN carries USB 5 V. |
| GND | GND | black | |
| D34 | AO | yellow | Via 10 kΩ / 20 kΩ divider — 10 kΩ from AO to D34, 20 kΩ from D34 to GND. AO can reach ~5 V; the pin sees at most 3.33 V. |
5 V (heater: about 150 mA) part, 3.3 V board. The heater needs 5 V. AO can reach about 5 V, so on ESP32 and Pico feed AO through a 10 kΩ / 20 kΩ divider to stay under 3.3 V. DO swings to VCC too — divide it on 3.3 V boards.
Board quirks
- The cheap module skips the MQ-7’s heater cycle. The datasheet heats at 5 V for 60 s, then 1.4 V for 90 s, and reads CO at the end of the low phase. The common red module ties the heater to a constant 5 V, so the sensor reacts to CO and other gases. This guide treats it honestly as a relative “bad air is rising” indicator, not a CO meter in ppm.
- The sketch reports Rs/R0, the ratio the datasheet curves use. Because Rs = RL × (5 V − V) / V, the load resistor RL cancels out of the ratio — you don’t need to read the tiny SMD resistor on your module. A falling ratio means more gas.
- R0 is taken at start-up, so start the board in clean air (outdoors or by an open window). Starting next to a running generator makes everything afterwards look “clean”.
- D34 is an ADC1 pin: readings keep working with Wi-Fi on, which you need for a phone alert later. ADC2 pins (D4, D13, D25–D27…) don’t.
- USB 5 V is rarely exactly 5.0 V (often 4.7–5.1 V). That shifts the ratio a little; for a better number measure VIN with a multimeter and set
VCin the sketch. - For a datasheet-style reading, the heater must be switched separately from the load circuit (a logic-level MOSFET on the heater and a modified module). That is beyond this guide — and even then, a DIY sensor is not a CO alarm.
Code
// MQ-7 CO sensor module on an ESP32 DevKit V1 — relative "bad air" trend as an Rs/R0 ratio.
// VCC → VIN (5 V heater), GND → GND, AO → 10 kΩ → D34 (GPIO34), 20 kΩ from D34 to GND.
// The common module keeps the heater at a constant 5 V, so this is a relative indicator,
// not a CO measurement in ppm and NOT a CO alarm. Fit a certified CO detector for safety.
constexpr uint8_t MQ_PIN = 34; // ADC1_CH6: works with Wi-Fi on
constexpr uint8_t LED_PIN = 2; // on-board blue LED
constexpr float DIVIDER = 1.5f; // (10 kΩ + 20 kΩ) / 20 kΩ
constexpr float VC = 5.0f; // module supply (USB 5 V — real value 4.7–5.1 V)
constexpr float ALARM_RATIO = 0.6f; // Rs/R0 below this = clearly more gas than at start-up
float r0Term = 0; // (VC - V0) / V0 in clean air; RL cancels out of the ratio
float readAoVolts() {
uint32_t sum = 0;
for (int i = 0; i < 32; i++) sum += analogReadMilliVolts(MQ_PIN);
return (sum / 32.0f) / 1000.0f * DIVIDER;
}
// Rs = RL × (VC − V) / V, so Rs/R0 = [(VC − V)/V] / [(VC − V0)/V0] — no need to know RL.
float rsTerm(float v) { return (VC - v) / max(v, 0.01f); }
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
Serial.println("MQ-7 on GPIO34 — heater warming up (3 min). Start in clean outdoor air.");
for (int s = 180; s > 0; s -= 30) {
Serial.printf(" %d s…\n", s);
delay(30000);
}
float sum = 0;
for (int i = 0; i < 20; i++) {
sum += readAoVolts();
delay(500);
}
float v0 = sum / 20;
r0Term = rsTerm(v0);
Serial.printf("Clean-air reference: AO %.2f V (R0 taken here)\n", v0);
}
void loop() {
static float smooth = -1;
float v = readAoVolts();
smooth = smooth < 0 ? v : smooth * 0.8f + v * 0.2f; // slow the reading a little
float ratio = rsTerm(smooth) / r0Term; // falls as CO (and other gases) rise
bool alarm = ratio < ALARM_RATIO;
digitalWrite(LED_PIN, alarm);
Serial.printf("AO %.2f V Rs/R0 %.2f%s\n", smooth, ratio, alarm ? " RISING GAS — ventilate, check a real CO alarm" : "");
delay(2000);
}// MQ-7 CO sensor module on an ESP32 DevKit V1 — relative "bad air" trend as an Rs/R0 ratio.
// VCC → VIN (5 V heater), GND → GND, AO → 10 kΩ → D34 (GPIO34), 20 kΩ from D34 to GND.
// The common module keeps the heater at a constant 5 V, so this is a relative indicator,
// not a CO measurement in ppm and NOT a CO alarm. Fit a certified CO detector for safety.
constexpr uint8_t MQ_PIN = 34; // ADC1_CH6: works with Wi-Fi on
constexpr uint8_t LED_PIN = 2; // on-board blue LED
constexpr float DIVIDER = 1.5f; // (10 kΩ + 20 kΩ) / 20 kΩ
constexpr float VC = 5.0f; // module supply (USB 5 V — real value 4.7–5.1 V)
constexpr float ALARM_RATIO = 0.6f; // Rs/R0 below this = clearly more gas than at start-up
float r0Term = 0; // (VC - V0) / V0 in clean air; RL cancels out of the ratio
float readAoVolts() {
uint32_t sum = 0;
for (int i = 0; i < 32; i++) sum += analogReadMilliVolts(MQ_PIN);
return (sum / 32.0f) / 1000.0f * DIVIDER;
}
// Rs = RL × (VC − V) / V, so Rs/R0 = [(VC − V)/V] / [(VC − V0)/V0] — no need to know RL.
float rsTerm(float v) { return (VC - v) / max(v, 0.01f); }
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
Serial.println("MQ-7 on GPIO34 — heater warming up (3 min). Start in clean outdoor air.");
for (int s = 180; s > 0; s -= 30) {
Serial.printf(" %d s…\n", s);
delay(30000);
}
float sum = 0;
for (int i = 0; i < 20; i++) {
sum += readAoVolts();
delay(500);
}
float v0 = sum / 20;
r0Term = rsTerm(v0);
Serial.printf("Clean-air reference: AO %.2f V (R0 taken here)\n", v0);
}
void loop() {
static float smooth = -1;
float v = readAoVolts();
smooth = smooth < 0 ? v : smooth * 0.8f + v * 0.2f; // slow the reading a little
float ratio = rsTerm(smooth) / r0Term; // falls as CO (and other gases) rise
bool alarm = ratio < ALARM_RATIO;
digitalWrite(LED_PIN, alarm);
Serial.printf("AO %.2f V Rs/R0 %.2f%s\n", smooth, ratio, alarm ? " RISING GAS — ventilate, check a real CO alarm" : "");
delay(2000);
}Expected serial output
MQ-7 on GPIO34 — heater warming up (3 min). Start in clean outdoor air.
180 s…
Clean-air reference: AO 0.42 V (R0 taken here)
AO 0.43 V Rs/R0 0.97
AO 0.91 V Rs/R0 0.41 RISING GAS — ventilate, check a real CO alarmMQ-7 on GPIO34 — heater warming up (3 min). Start in clean outdoor air. 180 s… Clean-air reference: AO 0.42 V (R0 taken here) AO 0.43 V Rs/R0 0.97 AO 0.91 V Rs/R0 0.41 RISING GAS — ventilate, check a real CO alarm
Common errors
Rs/R0 stays near 1.0 even next to exhaust smoke
Expected to a degree with a constant-5 V heater: the sensor is far less CO-selective than the datasheet suggests. Check the module’s power LED (heater on), and let a new sensor burn in for 48 hours.
Ratio is below 0.6 right after start-up
R0 was taken before the heater settled or in dirty air. Leave it powered for 10 minutes in fresh air, then press EN to take a new reference.
AO reads 0 V
The 10 kΩ and 20 kΩ are swapped, or the heater isn’t powered. 10 kΩ goes from AO to D34; 20 kΩ from D34 to GND.