The MQ-3 is a heated tin-oxide sensor that reacts most strongly to ethanol vapour, which makes it the classic part for “drunk-driving prevention” and breath-alcohol student projects.
On the ESP32 DevKit V1 it needs the same two changes as any MQ sensor: the heater runs from VIN (5 V), and the analog output goes through a 10 kΩ / 20 kΩ divider into D35, an ADC1 pin. The sketch then turns the board into a small breath-test demo: after a two-minute warm-up it records a clean-air baseline, and every press of the BOOT button starts a 5-second window in which it keeps the peak reading.
This is a demo, not a breathalyser. The value depends on breath temperature, humidity, distance and how hard someone blows, and it isn’t calibrated in mg/L. Never use it to decide whether anyone is fit to drive.
Wiring
| ESP32 DevKit V1 | MQ-3 alcohol (ethanol) sensor module | Wire | Notes |
|---|---|---|---|
| VIN | VCC | red | The heater needs 5 V and about 150 mA. VIN carries USB 5 V; 3V3 can’t heat it properly. |
| GND | GND | black | |
| D35 | AO | yellow | Via 10 kΩ / 20 kΩ divider — 10 kΩ from AO to D35, 20 kΩ from D35 to GND: 5 V at AO becomes 3.33 V at the pin. The sketch multiplies back by 1.5. |
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 is driven from VCC — divide it on 3.3 V boards too.
Board quirks
- D35 is an ADC1 pin, so the reading keeps working if you later add Wi-Fi (ADC2 pins such as D4, D13 and D25–D27 return nothing while Wi-Fi is on). It is input-only, which is all an analog input needs.
- The sketch uses the on-board BOOT button (GPIO0) as its “start test” button, so no extra parts are needed. GPIO0 is only a strapping pin at reset; reading it later is safe — just don’t hold BOOT while pressing EN.
- Use 10 kΩ / 20 kΩ, not 1 kΩ / 2 kΩ: the module’s load resistor is only a few kΩ, and a low-value divider would drag its output down.
analogReadMilliVolts()uses the chip’s factory calibration (core 3.x). With the default attenuation the useful range is roughly 0.15–3.1 V at the pin, which the divider maps to about 0.2–4.6 V at AO.- Warm breath alone raises the reading a little (heat and humidity). The sketch reports the rise over the clean-air baseline; blow from the same distance (about 5 cm) every time and compare with a no-alcohol breath first.
- A new MQ-3 needs 24–48 hours of powered burn-in, and at least two minutes of warm-up after every power-on. Until then the baseline drifts.
Code
// MQ-3 alcohol sensor on an ESP32 DevKit V1 — breath-test demo started with the BOOT button.
// VCC → VIN (5 V heater), GND → GND, AO → 10 kΩ → D35 (GPIO35), 20 kΩ from D35 to GND.
// After the warm-up, press BOOT (GPIO0) and blow gently from about 5 cm for 5 seconds.
// A demo of "more alcohol = higher reading" — NOT a breathalyser, never use it to judge fitness to drive.
constexpr uint8_t MQ_PIN = 35; // ADC1_CH7: keeps working when Wi-Fi is on; input-only is fine for analog
constexpr uint8_t BOOT_BTN = 0; // on-board BOOT button, reads LOW while pressed
constexpr float DIVIDER = 1.5f; // (10 kΩ + 20 kΩ) / 20 kΩ
constexpr uint32_t BLOW_MS = 5000;
float baselineV = 0;
float readAoVolts() {
uint32_t sum = 0;
for (int i = 0; i < 16; i++) sum += analogReadMilliVolts(MQ_PIN); // calibrated mV at the pin
return (sum / 16.0f) / 1000.0f * DIVIDER; // volts at the module's AO
}
void setup() {
Serial.begin(115200);
pinMode(BOOT_BTN, INPUT_PULLUP);
Serial.println("MQ-3 on GPIO35 — heater warming up (120 s)");
for (int s = 120; s > 0; s -= 20) {
Serial.printf(" %d s…\n", s);
delay(20000);
}
float sum = 0;
for (int i = 0; i < 10; i++) {
sum += readAoVolts();
delay(300);
}
baselineV = sum / 10;
Serial.printf("Clean-air baseline: AO %.2f V\n", baselineV);
Serial.println("Press BOOT, then blow for 5 s from about 5 cm");
}
void loop() {
if (digitalRead(BOOT_BTN) == LOW) {
Serial.println("Blow now…");
float peak = 0;
uint32_t t0 = millis();
while (millis() - t0 < BLOW_MS) {
float v = readAoVolts();
if (v > peak) peak = v;
delay(50);
}
float rise = peak - baselineV;
// Relative bands only: they depend on this sensor, its burn-in and the pot setting.
const char *band = rise < 0.3f ? "none detected" : rise < 1.0f ? "low" : rise < 2.0f ? "medium" : "high";
Serial.printf("Peak AO %.2f V (+%.2f V over baseline) alcohol vapour: %s\n", peak, rise, band);
Serial.println("Wait until AO is back near the baseline before the next test");
while (digitalRead(BOOT_BTN) == LOW) delay(10);
}
static uint32_t last = 0;
if (millis() - last >= 2000) {
last = millis();
Serial.printf("AO %.2f V\n", readAoVolts());
}
}// MQ-3 alcohol sensor on an ESP32 DevKit V1 — breath-test demo started with the BOOT button.
// VCC → VIN (5 V heater), GND → GND, AO → 10 kΩ → D35 (GPIO35), 20 kΩ from D35 to GND.
// After the warm-up, press BOOT (GPIO0) and blow gently from about 5 cm for 5 seconds.
// A demo of "more alcohol = higher reading" — NOT a breathalyser, never use it to judge fitness to drive.
constexpr uint8_t MQ_PIN = 35; // ADC1_CH7: keeps working when Wi-Fi is on; input-only is fine for analog
constexpr uint8_t BOOT_BTN = 0; // on-board BOOT button, reads LOW while pressed
constexpr float DIVIDER = 1.5f; // (10 kΩ + 20 kΩ) / 20 kΩ
constexpr uint32_t BLOW_MS = 5000;
float baselineV = 0;
float readAoVolts() {
uint32_t sum = 0;
for (int i = 0; i < 16; i++) sum += analogReadMilliVolts(MQ_PIN); // calibrated mV at the pin
return (sum / 16.0f) / 1000.0f * DIVIDER; // volts at the module's AO
}
void setup() {
Serial.begin(115200);
pinMode(BOOT_BTN, INPUT_PULLUP);
Serial.println("MQ-3 on GPIO35 — heater warming up (120 s)");
for (int s = 120; s > 0; s -= 20) {
Serial.printf(" %d s…\n", s);
delay(20000);
}
float sum = 0;
for (int i = 0; i < 10; i++) {
sum += readAoVolts();
delay(300);
}
baselineV = sum / 10;
Serial.printf("Clean-air baseline: AO %.2f V\n", baselineV);
Serial.println("Press BOOT, then blow for 5 s from about 5 cm");
}
void loop() {
if (digitalRead(BOOT_BTN) == LOW) {
Serial.println("Blow now…");
float peak = 0;
uint32_t t0 = millis();
while (millis() - t0 < BLOW_MS) {
float v = readAoVolts();
if (v > peak) peak = v;
delay(50);
}
float rise = peak - baselineV;
// Relative bands only: they depend on this sensor, its burn-in and the pot setting.
const char *band = rise < 0.3f ? "none detected" : rise < 1.0f ? "low" : rise < 2.0f ? "medium" : "high";
Serial.printf("Peak AO %.2f V (+%.2f V over baseline) alcohol vapour: %s\n", peak, rise, band);
Serial.println("Wait until AO is back near the baseline before the next test");
while (digitalRead(BOOT_BTN) == LOW) delay(10);
}
static uint32_t last = 0;
if (millis() - last >= 2000) {
last = millis();
Serial.printf("AO %.2f V\n", readAoVolts());
}
}Expected serial output
MQ-3 on GPIO35 — heater warming up (120 s)
120 s…
Clean-air baseline: AO 0.61 V
Press BOOT, then blow for 5 s from about 5 cm
Blow now…
Peak AO 0.78 V (+0.17 V over baseline) alcohol vapour: none detected
Blow now…
Peak AO 2.05 V (+1.44 V over baseline) alcohol vapour: medium
Wait until AO is back near the baseline before the next testMQ-3 on GPIO35 — heater warming up (120 s) 120 s… Clean-air baseline: AO 0.61 V Press BOOT, then blow for 5 s from about 5 cm Blow now… Peak AO 0.78 V (+0.17 V over baseline) alcohol vapour: none detected Blow now… Peak AO 2.05 V (+1.44 V over baseline) alcohol vapour: medium Wait until AO is back near the baseline before the next test
Common errors
AO reads about 4.6 V or more and never changes
Something saturates the sensor — hand sanitiser, a perfume or a spirit bottle nearby. Ventilate the room and wait. If it stays high, check that the 20 kΩ goes from D35 to GND, not to 3V3.
The reading falls for many minutes after power-on
Normal heater warm-up. A brand-new sensor drifts for a day or two; leave it powered, then press EN to take a fresh baseline.
The ESP32 resets when the MQ-3 is connected
The cold heater draws a surge on top of the ESP32’s own current. Use a short, thick USB cable and a good port, or feed the MQ-3 from a separate 5 V supply with its GND joined to the ESP32’s GND.