Cảm biến khí MQ-2 và MQ-135 với ESP32: làm nóng, hiệu chuẩn R0, ước tính ppm và cảnh báo khí gas
Cảm biến khí MQ hoạt động thế nào, cầu phân áp cho chân AO, tính Rs, hiệu chuẩn R0 trong không khí sạch, đổi ra ppm gần đúng, cảnh báo có ngưỡng trễ và giới hạn của MQ-135.
Module cảm biến khí MQ-2 (khí gas, khói) và MQ-135 (chất lượng không khí) rẻ và dễ mua, nhưng hay bị dùng sai: nối thẳng chân AO vào ESP32, đọc ngay khi vừa cấp điện, rồi tin con số "ppm" như máy đo thật. Bài này giải thích cảm biến hoạt động thế nào, vì sao phải làm nóng, cách tính điện trở Rs và hiệu chuẩn R0 trong không khí sạch, cách đổi ra nồng độ gần đúng và cách làm cảnh báo có ngưỡng trễ, cùng những giới hạn cần biết.
Lưu ý an toàn: mạch trong bài dùng để học và thử nghiệm. Nơi có bếp gas, bình gas cần đầu báo rò rỉ gas được chứng nhận, lắp đúng vị trí theo hướng dẫn của nhà sản xuất.
1. Cảm biến khí MQ hoạt động thế nào

Bên trong vỏ lưới thép là một ống gốm phủ lớp thiếc oxit (SnO2), có dây nung ở giữa giữ lớp này ở vài trăm độ C. Ở nhiệt độ đó, oxy trong không khí bám lên bề mặt và giữ electron, nên điện trở Rs của lớp SnO2 cao. Khi có khí khử (gas LPG, khí metan, khói, hơi cồn, CO, H2), các khí này phản ứng với oxy trên bề mặt, trả electron lại, Rs giảm xuống. Càng nhiều khí, Rs càng nhỏ.
Hai loại khác nhau ở công thức lớp nhạy và độ nhạy với từng khí:
- MQ-2: nhạy với LPG, propan, metan, H2, khói. Hợp để thử nghiệm phát hiện rò rỉ gas, khói.
- MQ-135: nhạy với NH3, hơi cồn, benzen, khói và nhiều khí khác. Hay được gọi là cảm biến "chất lượng không khí", nhưng nó không cho biết đó là khí gì.
Dây nung cần khoảng 150 mA ở 5 V. Datasheet khuyên cấp điện liên tục 24–48 giờ trong lần dùng đầu tiên để cảm biến ổn định; những lần sau vẫn phải chờ vài phút sau khi cấp điện mới đọc.
2. Mạch đo trên module và cầu phân áp

Trên module, cảm biến Rs và điện trở tải (thường 1 kΩ, ghi chữ 102) nối thành cầu phân áp từ 5 V xuống GND. Điện áp ở giữa là chân AO:
- AO = 5 V × R tải / (Rs + R tải): ít khí, Rs lớn, AO thấp; nhiều khí, Rs nhỏ, AO lên gần 5 V.
- Đảo lại: Rs = R tải × (5 V − AO) / AO. Chương trình đo AO rồi tính Rs theo công thức này.
Vì AO có thể lên gần 5 V, không nối thẳng AO vào ESP32. Dùng cầu phân áp 10 kΩ / 20 kΩ: chân ESP32 chỉ nhận 2/3 điện áp AO, tối đa khoảng 3,33 V. Chương trình nhân lại với 1,5 để ra điện áp AO thật. Cách đọc ADC và cầu phân áp được giải thích kỹ hơn trong bài đọc ADC trên ESP32 bằng cầu phân áp.
Chân DO là ngõ ra so sánh (IC LM393): lên hoặc xuống mức khi AO vượt ngưỡng chỉnh bằng biến trở trên module. DO cũng kéo lên 5 V, và chương trình đã tự so ngưỡng nên bài không dùng chân này.
3. Linh kiện cần chuẩn bị
- ESP32 DevKit V1 30 chân (hoặc loại 38 chân) và dây USB nạp code.
- Module MQ-2 và/hoặc MQ-135; mỗi module hai điện trở 10 kΩ và 20 kΩ.
- Breadboard và dây cắm.
- Hai dây nung tốn khoảng 300 mA: nếu cổng USB yếu, dùng nguồn 5 V riêng, ví dụ hạ áp từ 12 V bằng mạch LM2596, nối GND chung với ESP32.
4. Nối MQ-2 và MQ-135 với ESP32

| Module | Chân | ESP32 DevKit V1 |
|---|---|---|
| MQ-2, MQ-135 | VCC | VIN (5 V khi cắm USB) |
| MQ-2, MQ-135 | GND | GND |
| MQ-2 | AO | 10 kΩ → D34; D34 → 20 kΩ → GND |
| MQ-135 | AO | 10 kΩ → D35; D35 → 20 kΩ → GND |
| MQ-2, MQ-135 | DO | Để trống |
D34, D35 là chân chỉ nhận vào, thuộc khối ADC1 nên vẫn đọc được khi bật Wi-Fi. Đèn xanh trên bo (GPIO2) dùng làm đèn cảnh báo.
5. Hiệu chuẩn R0 và đổi ra nồng độ

Mỗi cảm biến có Rs khác nhau dù cùng loại, nên datasheet không cho Rs mà cho tỉ số Rs/R0, trong đó R0 là Rs của chính cảm biến đó ở một nồng độ chuẩn. Đồ thị datasheet còn cho biết tỉ số này trong không khí sạch: khoảng 9,83 với MQ-2 và 3,6 với MQ-135. Từ đó:
- Làm nóng cảm biến, đặt ở nơi thoáng, không khí sạch (không gần bếp, không có cồn, nước hoa).
- Đo Rs, lấy trung bình 50 lần: đó là Rs trong không khí sạch.
- R0 = Rs không khí sạch / 9,83 (MQ-2), hoặc / 3,6 (MQ-135).
Chương trình lưu R0 vào bộ nhớ flash bằng thư viện Preferences (xem bài lưu dữ liệu khi mất điện bằng Preferences), nên không phải hiệu chuẩn lại mỗi lần cấp điện. Điện trở tải R tải có mặt ở cả Rs đo được lẫn R0, nên giá trị của nó không làm sai tỉ số Rs/R0, chỉ làm sai số Rs in ra.
Trên đồ thị log-log, đường của mỗi khí gần như thẳng, nên viết được thành ppm ≈ a × (Rs/R0)b. Với LPG của MQ-2, chương trình dùng a = 574,25, b = −2,222 (đường khớp dùng phổ biến, lấy từ đồ thị datasheet). Đồ thị gốc chỉ có từ 200 đến 10 000 ppm, nên dưới 200 ppm chương trình chỉ in "duoi 200 ppm" thay vì một con số đoán.
MQ-135 thì chương trình không đổi ra ppm, chỉ in Rs so với lúc không khí sạch: 1,00 là như lúc hiệu chuẩn, càng nhỏ càng có nhiều khí lạ. Nhiều bài trên mạng dùng MQ-135 để "đo CO2", nhưng cảm biến này không chọn lọc CO2 và gần như không phản ứng với CO2 ở nồng độ trong nhà; muốn đo CO2 cần cảm biến hồng ngoại (NDIR).
6. Chương trình
Chương trình chỉ dùng thư viện có sẵn trong gói esp32. Ba phút đầu chỉ in điện áp AO để thấy cảm biến đang nóng lên; sau đó nếu chưa có R0 thì nhắc hiệu chuẩn. Gõ C trên Serial Monitor khi cảm biến ở không khí sạch.
// PYWORLD.VN - Cảm biến khí MQ-2 và MQ-135 với ESP32: làm nóng, hiệu chuẩn R0 trong không khí sạch, ước tính nồng độ và cảnh báo
// Module cấp 5 V. AO của MQ-2 qua cầu phân áp 10 kΩ / 20 kΩ vào D34, AO của MQ-135 qua cầu phân áp tương tự vào D35. Đèn báo GPIO2
// Gõ C trên Serial Monitor khi cả hai cảm biến đang ở không khí sạch để hiệu chuẩn; R0 được lưu lại, mất điện không mất
#include <Preferences.h>
const float VCC_MODULE = 5.0; // điện áp cấp cho module (đo lại chân VCC nếu muốn chính xác hơn)
const float HE_SO_PHAN_AP = 1.5; // (10 kΩ + 20 kΩ) / 20 kΩ: điện áp AO thật = điện áp đo được × 1,5
const float R_TAI_KOHM = 1.0; // điện trở tải trên module (chữ 102 = 1 kΩ, 103 = 10 kΩ); chỉ ảnh hưởng số Rs in ra
const uint32_t THOI_GIAN_NONG_MS = 180000; // chờ 3 phút cho dây nung ổn định trước khi đo
const int CHAN_DEN = 2;
struct CamBien {
const char *ten;
int chan;
float tiSoKhongKhi; // Rs/R0 trong không khí sạch, đọc từ đồ thị datasheet
float a, b; // ppm ≈ a × (Rs/R0)^b, đường thẳng trên đồ thị log-log (chỉ dùng cho MQ-2)
float r0; // kΩ, có sau khi hiệu chuẩn
};
CamBien mq[2] = {
{"MQ-2", 34, 9.83, 574.25, -2.222, 0}, // đường LPG
{"MQ-135", 35, 3.6, 0, 0, 0}, // không đổi ra ppm: chỉ so với lúc không khí sạch
};
const float NGUONG_BAO_LPG = 1000; // ppm: bật cảnh báo (khoảng 5% giới hạn cháy nổ dưới của LPG)
const float NGUONG_TAT_LPG = 800; // ppm: tắt cảnh báo (có khoảng trễ để đèn không nhấp nháy ở ngưỡng)
Preferences boNho;
bool canhBao = false;
float docAoV(int chan) { // trung bình 64 lần đọc, trả về điện áp thật ở chân AO
uint32_t tong = 0;
for (int i = 0; i < 64; i++) tong += analogReadMilliVolts(chan);
return tong / 64.0f / 1000.0f * HE_SO_PHAN_AP;
}
// Module là cầu phân áp: VCC → Rs (cảm biến) → AO → R_tải → GND, nên Rs = R_tải × (VCC − AO) / AO.
float docRs(const CamBien &c) {
float v = docAoV(c.chan);
if (v < 0.01f) v = 0.01f;
return R_TAI_KOHM * (VCC_MODULE - v) / v;
}
void hieuChuan() {
for (int k = 0; k < 2; k++) {
float tong = 0;
for (int i = 0; i < 50; i++) {
tong += docRs(mq[k]);
delay(20);
}
mq[k].r0 = tong / 50 / mq[k].tiSoKhongKhi; // R0 = Rs trong không khí sạch / tỉ số của datasheet
boNho.putFloat(mq[k].ten, mq[k].r0);
Serial.printf("Hieu chuan %s: R0 = %.2f kOhm\n", mq[k].ten, mq[k].r0);
}
}
void setup() {
Serial.begin(115200);
pinMode(CHAN_DEN, OUTPUT);
analogSetAttenuation(ADC_11db); // đọc được tới khoảng 3,1 V
boNho.begin("mq", false);
for (int k = 0; k < 2; k++) mq[k].r0 = boNho.getFloat(mq[k].ten, 0);
Serial.println("Dang lam nong day nung, cho 3 phut...");
}
void loop() {
if (Serial.available()) {
char c = Serial.read();
if ((c == 'C' || c == 'c') && millis() >= THOI_GIAN_NONG_MS) hieuChuan();
}
static uint32_t lanIn = 0;
if (millis() - lanIn < 2000) return;
lanIn = millis();
if (millis() < THOI_GIAN_NONG_MS) {
Serial.printf("Lam nong: con %lu giay | MQ-2 AO %.2f V | MQ-135 AO %.2f V\n",
(unsigned long)((THOI_GIAN_NONG_MS - millis()) / 1000), docAoV(mq[0].chan), docAoV(mq[1].chan));
return;
}
if (mq[0].r0 <= 0 || mq[1].r0 <= 0) {
Serial.println("Chua hieu chuan: dat cam bien o khong khi sach roi go C");
return;
}
float rs2 = docRs(mq[0]), rs135 = docRs(mq[1]);
float tiSo2 = rs2 / mq[0].r0, tiSo135 = rs135 / mq[1].r0;
float lpg = mq[0].a * powf(tiSo2, mq[0].b);
float sachTuongDoi = tiSo135 / mq[1].tiSoKhongKhi; // 1,00 = như lúc hiệu chuẩn; càng nhỏ càng nhiều khí lạ
if (!canhBao && lpg >= NGUONG_BAO_LPG) canhBao = true;
if (canhBao && lpg <= NGUONG_TAT_LPG) canhBao = false;
digitalWrite(CHAN_DEN, canhBao);
char chuLpg[24]; // đường cong chỉ có từ 200 ppm: dưới đó không ghi số
if (lpg < 200) snprintf(chuLpg, sizeof(chuLpg), "duoi 200 ppm");
else snprintf(chuLpg, sizeof(chuLpg), "~%.0f ppm", lpg);
Serial.printf("MQ-2: Rs %.1f kOhm, Rs/R0 %.2f, LPG %s | MQ-135: Rs/Rs(sach) %.2f%s\n", rs2, tiSo2, chuLpg,
sachTuongDoi, canhBao ? " | CANH BAO KHI GAS" : "");
}
docAoV()lấy trung bình 64 lầnanalogReadMilliVolts()(đã bù sai lệch ADC theo dữ liệu hiệu chuẩn trong chip) rồi nhân 1,5.docRs()tính Rs theo công thức cầu phân áp;hieuChuan()lấy trung bình 50 lần, chia cho tỉ số không khí sạch và lưu vào flash.- Cảnh báo bật khi LPG ước tính từ 1 000 ppm (khoảng 5% giới hạn cháy nổ dưới của LPG), tắt khi xuống 800 ppm. Hai ngưỡng khác nhau tạo khoảng trễ, để đèn không bật tắt liên tục khi số đo dao động quanh ngưỡng.
7. Kết quả mong đợi

Lúc mới cấp điện, AO thường cao rồi giảm dần trong vài phút: dây nung đang đốt sạch hơi ẩm, khí bám trên bề mặt. Để thử, nhấn nhẹ van bật lửa gas cho xì một chút gas (không đánh lửa) cách cảm biến vài cm: Rs giảm mạnh, đèn cảnh báo sáng, rồi tự tắt khi gas tan. Làm thử ở nơi thoáng, xa lửa.
Rs còn thay đổi theo nhiệt độ và độ ẩm không khí: trời nồm, độ ẩm cao làm Rs thấp hơn cùng một nồng độ khí. Số ppm vì vậy chỉ nên xem là mức tương đối "ít, nhiều, tăng, giảm".
8. Xử lý lỗi thường gặp
| Hiện tượng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| AO luôn gần 0 V | Dây nung không nóng: thiếu nguồn 5 V, cổng USB yếu | Đo 5 V ở VCC module; dùng nguồn 5 V riêng, GND chung |
| Số đo trôi mãi không ổn định | Cảm biến mới, chưa qua thời gian cấp điện ban đầu | Cấp điện liên tục 24–48 giờ rồi hiệu chuẩn lại |
| Vừa hiệu chuẩn đã báo nhiều khí | Hiệu chuẩn khi chưa nóng đủ hoặc ở nơi có khí | Chờ hết 3 phút, ra chỗ thoáng, gõ C lại |
| AO trong không khí sạch dưới 0,2 V, số đo nhảy | Rs rất lớn so với R tải; ADC kém chính xác ở mức thấp | Chấp nhận độ phân giải thấp ở vùng sạch, hoặc thay R tải lớn hơn (10 kΩ) rồi hiệu chuẩn lại |
| Đèn cảnh báo sáng khi xịt nước hoa, lau cồn | Cảm biến nhạy với mọi khí khử | Bình thường với cảm biến loại này; đặt xa nguồn hơi cồn |
| ESP32 khởi động lại khi cắm module | Dây nung kéo dòng lớn lúc đầu, sụt áp USB | Nguồn 5 V riêng đủ dòng; thêm tụ 470 µF ở 5 V |
9. Xem thêm
- ESP32 điều khiển module relay an toàn: bật quạt hút khi có cảnh báo.
- Dùng MQTT với ESP32: gửi cảnh báo về điện thoại.
- An toàn khi dùng máy khắc laser và CNC: khói độc khi khắc, nơi một cảm biến khói chỉ là lớp phòng hộ phụ.
Xem thêm