MQ135 и Arduino: датчик качества воздуха, калибровка и расчёт ppm
MQ135 и Arduino: какие газы он чувствует, прожиг 24–48 ч, калибровка R0 и формула ppm, измеряет ли он CO2, управление вентилятором, запись данных на ESP32 и сравнение с CCS811.
Проекты MQ135 на Arduino — первая остановка для всех, кто хочет дёшево и просто следить за тем, насколько чистый воздух в комнате. MQ-135 — это металлооксидный датчик газа с нагревателем, который реагирует на такие газы, как аммиак, оксиды азота, бензол, спирт и дым. В наборах для хобби он обычно продаётся в виде маленького модуля с микросхемой компаратора и подстроечным потенциометром; к Arduino он подключается через аналоговый выход и читается одним вызовом analogRead(). Но чтобы превратить число, которое выдаёт датчик, в осмысленное измерение, нужно знать немного больше, чем объясняет большинство руководств.
В этом руководстве ты узнаешь, что происходит внутри датчика, насколько он чувствителен к разным газам и почему перед первым настоящим использованием его нужно прогревать 24–48 часов. Затем шаг за шагом построим расчёт ppm на основе нагрузочного резистора (RL), сопротивления датчика (Rs) и опорного сопротивления (R0), сделаем калибровку R0 на чистом воздухе и честно ответим на самый частый вопрос: правда ли MQ-135 измеряет CO2? В конце автоматически включим вентилятор, будем записывать показания на ESP32 в файл с метками времени, напишем код на MicroPython для Raspberry Pi Pico и сравним MQ-135 с CCS811 и NDIR-датчиками CO2.

Что такое MQ-135 и как он работает?
За сеткой из нержавеющей стали находится маленькая керамическая трубка, покрытая слоем диоксида олова (SnO2), а проходящая сквозь неё нагревательная спираль разогревает этот слой до нескольких сотен градусов Цельсия. В чистом воздухе молекулы кислорода оседают на поверхности SnO2, захватывают свободные электроны материала, и сопротивление слоя остаётся высоким. Когда в воздухе появляется газ-восстановитель, например аммиак, спирт или бензол, он вступает в реакцию с кислородом на поверхности, электроны освобождаются, и сопротивление падает. Чем выше концентрация газа, тем ниже сопротивление. Модуль включает это сопротивление в делитель напряжения вместе с нагрузочным резистором, поэтому с ростом концентрации газа напряжение на пине AO растёт.
| Параметр | Значение |
|---|---|
| Чувствительный материал | Диоксид олова (SnO2), керамическая трубка с нагревателем |
| Напряжение нагревателя | 5 V ± 0,1 V |
| Сопротивление и ток нагревателя | 33 Ω ± 5%, около 150 mA |
| Мощность нагревателя | До 800 mW |
| Диапазон обнаружения | NH3 10–300 ppm, бензол 10–1000 ppm, спирт 10–300 ppm |
| Первичный прогрев | Более 24 часов |
| Условия работы | От −10 до 45 °C, относительная влажность ниже 95% |
| Выходы модуля | AO (аналоговый) и DO (цифровой порог через LM393) |
Какие газы обнаруживает MQ-135?
MQ-135 — не селективный датчик, который измеряет один газ; он одновременно реагирует на множество газов, вступающих в реакцию с нагретой поверхностью SnO2, и не умеет их различать. Производитель описывает его как особенно чувствительный к аммиаку, соединениям серы и парам бензольного ряда; заметно он реагирует и на спирт, дым и оксиды азота. Поэтому MQ-135 лучше всего воспринимать как «индикатор загрязнения воздуха»: когда значение растёт, он сообщает, что в воздухе что-то изменилось, но не говорит, что именно. Для утечек природного газа и сжиженного газа (пропан-бутан) гораздо лучше подходит датчик газа MQ-2, созданный как раз для этой задачи.
| Газ | Откуда берётся дома? | Реакция MQ-135 |
|---|---|---|
| Аммиак (NH3) | Чистящие средства, кошачий наполнитель, удобрения | Сильная |
| Бензол, толуол и растворители | Краска, растворитель, клей, сигаретный дым | Сильная |
| Пары спирта | Антисептик для рук, одеколон, духи | Сильная |
| Дым | Готовка, сигареты, свечи | Средняя |
| Оксиды азота (NOx) | Газовая плита, выхлопные газы | Средняя |
| Углекислый газ (CO2) | Дыхание людей, закрытые и людные комнаты | Слабая, косвенная |
Измеряет ли MQ-135 CO2?
Коротко: нет, по-настоящему не измеряет, а оценивает. В даташите MQ-135 есть кривая для CO2, и большинство библиотек в интернете вычисляет по ней значение «CO2 ppm». Но CO2 — химически стабильный газ, и с поверхностью SnO2 он реагирует очень слабо. В закрытой комнате, пока люди дышат, вместе с CO2 растут влажность и летучие органические соединения, которые выделяет тело, и именно на них на самом деле реагирует MQ-135. Так что «CO2», который показывает датчик, — это на деле оценка по общему загрязнению воздуха в комнате. Самое простое доказательство: открой рядом флакон одеколона и посмотри, как значение «CO2» взлетает до 5000 ppm.
Это не значит, что MQ-135 бесполезен. Он отлично подходит для относительных решений вроде «в комнате становится душно, открой окно» или «на кухне дым, включи вытяжку». Но если нужно знать реальное значение CO2 в классе, офисе или спальне, стоит взять один из NDIR-датчиков CO2 из сравнения в конце этого руководства. Таблица ниже показывает, что означают показания настоящего измерителя CO2; оценки MQ-135 можно трактовать по тем же диапазонам.
| CO2 (ppm) | Что это значит? |
|---|---|
| 400–450 | Чистый уличный воздух |
| 450–800 | Хорошо проветриваемое помещение |
| 800–1000 | Допустимо, стоит подумать о проветривании |
| 1000–1500 | Душно, может падать концентрация внимания; проветри |
| 1500–2000 | Могут появиться головная боль и сонливость |
| 5000 | Предел воздействия на рабочем месте за 8 часов |
Подключение MQ-135 к Arduino
| Пин модуля | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| VCC (включая нагреватель) | 5V | VIN (5 V от USB) | VBUS (5 V от USB) |
| GND | GND | GND | GND |
| AO (аналоговый выход) | A0 | GPIO 34 через делитель 10 kΩ / 20 kΩ | GP26 (ADC0) через делитель 10 kΩ / 20 kΩ |
| DO (цифровой порог, по желанию) | D2 | Любой GPIO через делитель | Любой пин GP через делитель |
Прогрев: прожиг 24–48 часов и ежедневный прогрев
У датчиков серии MQ два разных прогрева, и их часто путают. Первый — первичный прогрев (прожиг, burn-in): на поверхности совсем нового датчика или датчика, который долго пролежал на складе без дела, скапливаются влага и загрязнения. Производитель требует перед стандартными измерениями держать датчик под питанием больше 24 часов; на практике, если гонять его непрерывно 24–48 часов, видно, как значения медленно дрейфуют и в конце концов стабилизируются. Если сделать калибровку R0 до окончания этого срока, через несколько дней все показания уплывут. Второй — ежедневный прогрев: даже датчику, который уже прошёл прожиг, при каждом включении нужно несколько минут, чтобы нагреватель вышел на равновесие; значения в первую минуту заметно завышены или занижены. Поэтому в коде есть ожидание минимум 2–3 минуты, а для точных измерений лучше подождать 10–20 минут.
Rs, RL и R0: как считать ppm с MQ-135?
Внутри модуля сопротивление датчика Rs и нагрузочный резистор на плате RL соединены последовательно и делят между собой 5 V. Напряжение на пине AO равно Vout = 5 × RL / (Rs + RL). Если перевернуть формулу, по измеренному напряжению получится текущее сопротивление датчика: Rs = RL × (5 − Vout) / Vout. На Arduino Uno АЦП использует те же 5 V как опорное напряжение, поэтому даже переводить в вольты не нужно: Rs = RL × (1023 − raw) / raw, где raw — «сырое» значение АЦП. Само по себе Rs мало что значит, потому что сопротивление сильно различается от датчика к датчику. Важно отношение Rs к его значению в воздухе известного состава, которое называют R0.
Кривые в даташите связывают отношение Rs/R0 с концентрацией газа и в логарифмическом масштабе по обеим осям выглядят почти как прямые. Значит, их можно описать степенной функцией: ppm = A × (Rs/R0)^−B. Популярные библиотеки для MQ-135 используют для кривой CO2 коэффициенты A = 116.602 и B = 2.769. Чтобы найти R0, датчик выносят на чистый уличный воздух; сейчас CO2 на улице около 420 ppm, поэтому формулу переворачивают и считают R0 = Rs × (420 / A)^(1/B) ≈ Rs × 1,59. Помни, что для других газов коэффициенты другие, а результат — лишь оценка.
Калибровка R0 для MQ-135 шаг за шагом
- Подключи VCC модуля к 5V, GND к GND, а AO к A0 и проведи первичный прогрев: датчик должен непрерывно проработать 24–48 часов.
- Прочитай код на нагрузочном резисторе на обратной стороне платы и выставь под него значение RL_KOHM в коде ниже.
- Вынеси схему на чистый уличный воздух, например на балкон или к открытому окну, подальше от машин, сигарет и кухни.
- Загрузи код калибровки, открой монитор порта на скорости 9600 бод и подожди минимум 10 минут.
- Когда среднее R0 на экране несколько минут почти не меняется, запиши его; это и есть R0 твоего датчика.
1const byte MQ_PIN = A0; // MQ-135 AO
2const float RL_KOHM = 1.0; // karttaki yük direnci: 102 = 1, 103 = 10 / load resistor on the board
3const float PARA = 116.6020682; // CO2 eğrisi katsayıları / CO2 curve coefficients
4const float PARB = 2.769034857;
5const float CLEAN_AIR_PPM = 420.0; // dış havadaki CO2 / CO2 in outdoor air
6
7float r0Average = 0;
8
9float readRs() {
10 long sum = 0;
11 for (int i = 0; i < 50; i++) { // 50 ölçüm, yaklaşık 1 saniye / 50 readings, about 1 second
12 sum += analogRead(MQ_PIN);
13 delay(20);
14 }
15 float raw = sum / 50.0;
16 if (raw < 1) raw = 1; // sıfıra bölmeyi önle / avoid dividing by zero
17 return RL_KOHM * (1023.0 - raw) / raw; // Rs = RL × (Vc − Vout) / Vout
18}
19
20void setup() {
21 Serial.begin(9600);
22 Serial.println(F("Temiz havada en az 10 dk bekle / wait at least 10 min in clean air"));
23}
24
25void loop() {
26 float rs = readRs();
27 float r0 = rs * pow(CLEAN_AIR_PPM / PARA, 1.0 / PARB); // R0 = Rs × (420 / A)^(1/B)
28 if (r0Average == 0) r0Average = r0;
29 r0Average = r0Average * 0.95 + r0 * 0.05; // yavaş ortalama / slow running average
30
31 Serial.print(F("Rs = "));
32 Serial.print(rs, 2);
33 Serial.print(F(" kOhm R0 = "));
34 Serial.print(r0, 2);
35 Serial.print(F(" ortalama / average R0 = "));
36 Serial.println(r0Average, 2);
37}Каждую секунду код усредняет 50 измерений, считает по ним Rs и текущее R0, а затем добавляет это значение в медленное скользящее среднее. Скользящее среднее не даёт короткому порыву выхлопных газов, принесённому ветром, испортить результат. Найденное R0 относится только к этому датчику и этому значению RL; если поменять датчик, калибровку нужно повторить. За месяцы, по мере старения датчика, R0 медленно уплывает, поэтому в проектах, которые работают долго, полезно повторять калибровку раз в несколько месяцев.
Код измерения: оценка ppm и уровень качества воздуха
Найденное R0 вписывается в код измерения. Сначала код 3 минуты ждёт прогрева, а затем каждые 2 секунды выводит в монитор порта отношение Rs/R0, оценку ppm и метку качества воздуха по таблице выше. На чистом воздухе отношение около 0,6; чем оно меньше, тем больше газа в воздухе. Для проверки капни немного одеколона на ватный шарик рядом с датчиком: за несколько секунд отношение падает, а ppm растёт, а если убрать вату, за несколько минут значения возвращаются к норме.
1const byte MQ_PIN = A0;
2const float RL_KOHM = 1.0; // kalibrasyondaki RL ile aynı / same RL as in calibration
3const float R0_KOHM = 3.6; // kendi kalibrasyon sonucunla değiştir / replace with your calibration result
4const float PARA = 116.6020682;
5const float PARB = 2.769034857;
6const unsigned long WARMUP_MS = 180000; // 3 dk ısınma / 3 min warm-up
7
8float readRs() {
9 long sum = 0;
10 for (int i = 0; i < 20; i++) {
11 sum += analogRead(MQ_PIN);
12 delay(10);
13 }
14 float raw = sum / 20.0;
15 if (raw < 1) raw = 1;
16 return RL_KOHM * (1023.0 - raw) / raw;
17}
18
19const __FlashStringHelper* airLabel(float ppm) {
20 if (ppm < 800) return F("Iyi / good");
21 if (ppm < 1000) return F("Orta / moderate");
22 if (ppm < 1500) return F("Havalandir / ventilate");
23 return F("Kotu / poor");
24}
25
26void setup() {
27 Serial.begin(9600);
28 Serial.println(F("Isiniyor / warming up..."));
29 while (millis() < WARMUP_MS) delay(100);
30}
31
32void loop() {
33 float ratio = readRs() / R0_KOHM; // gaz arttıkça küçülür / falls as gas rises
34 float ppm = PARA * pow(ratio, -PARB); // tahmini CO2 eşdeğeri / estimated CO2 equivalent
35
36 Serial.print(F("Rs/R0 = "));
37 Serial.print(ratio, 2);
38 Serial.print(F(" ~"));
39 Serial.print(ppm, 0);
40 Serial.print(F(" ppm "));
41 Serial.println(airLabel(ppm));
42 delay(2000);
43}Как температура и влажность влияют на MQ-135?
Сопротивление металлооксидных датчиков зависит не только от газа, но и от температуры и влажности в комнате. Согласно кривой из даташита, в одном и том же воздухе отношение Rs/R0 заметно падает, когда температура поднимается с 20 °C до 40 °C, и падает также с ростом влажности; датчик воспринимает это как «больше газа». В результате ppm сами по себе растут в комнате, куда днём светит солнце, или в ванной после душа. Уменьшить этот эффект можно двумя способами: калибровать при температуре и влажности, близких к тем, где будет работать датчик, или измерять температуру и влажность датчиком DHT22 или DHT11 и применять формулу коррекции из библиотек. Именно это делают функции вроде getCorrectedPPM(температура, влажность) в готовых библиотеках MQ135. Если нужно измерять температуру и влажность точнее, чем DHT11, можно взять датчик BME280.
Автоматическое управление вентилятором
Самая полезная работа MQ-135 — самостоятельно включать вентилятор или вытяжку, когда воздух становится спёртым. Здесь важны две детали. Первая — гистерезис: вентилятор включается выше 1000 ppm, но выключается, только когда значение опустится ниже 800 ppm; с одним порогом реле щёлкало бы каждую секунду, пока значение колеблется вокруг него. Вторая — минимальное время работы: включившись, вентилятор работает не меньше 2 минут, чтобы воздух в комнате действительно сменился. Кроме того, код сглаживает показания, чтобы кратковременные всплески не запускали вентилятор.
1const byte MQ_PIN = A0;
2const byte FAN_PIN = 7; // röle modülü IN / relay module IN
3const bool RELAY_ACTIVE_LOW = true; // çoğu modül LOW ile çeker / most modules switch on with LOW
4const float RL_KOHM = 1.0;
5const float R0_KOHM = 3.6; // kendi kalibrasyon sonucun / your calibration result
6const float PARA = 116.6020682;
7const float PARB = 2.769034857;
8const float FAN_ON_PPM = 1000; // bunun üstünde fanı aç / fan on above this
9const float FAN_OFF_PPM = 800; // bunun altında fanı kapat / fan off below this
10const unsigned long MIN_RUN_MS = 120000; // fan en az 2 dk çalışır / fan runs at least 2 min
11const unsigned long WARMUP_MS = 180000; // 3 dk ısınma / 3 min warm-up
12
13bool fanOn = false;
14unsigned long fanStart = 0;
15float smoothPpm = 0;
16
17void setFan(bool on) {
18 fanOn = on;
19 if (on) fanStart = millis();
20 bool level = RELAY_ACTIVE_LOW ? !on : on;
21 digitalWrite(FAN_PIN, level ? HIGH : LOW);
22}
23
24float readPpm() {
25 long sum = 0;
26 for (int i = 0; i < 20; i++) {
27 sum += analogRead(MQ_PIN);
28 delay(10);
29 }
30 float raw = sum / 20.0;
31 if (raw < 1) raw = 1;
32 float rs = RL_KOHM * (1023.0 - raw) / raw;
33 return PARA * pow(rs / R0_KOHM, -PARB);
34}
35
36void setup() {
37 digitalWrite(FAN_PIN, RELAY_ACTIVE_LOW ? HIGH : LOW); // önce kapalı seviye, sonra çıkış / off level first, then output
38 pinMode(FAN_PIN, OUTPUT);
39 Serial.begin(9600);
40 while (millis() < WARMUP_MS) delay(100);
41 smoothPpm = readPpm();
42}
43
44void loop() {
45 smoothPpm = smoothPpm * 0.8 + readPpm() * 0.2; // ani sıçramaları yumuşat / smooth out spikes
46
47 if (!fanOn && smoothPpm > FAN_ON_PPM) {
48 setFan(true);
49 } else if (fanOn && smoothPpm < FAN_OFF_PPM && millis() - fanStart > MIN_RUN_MS) {
50 setFan(false);
51 }
52
53 Serial.print(F("~"));
54 Serial.print(smoothPpm, 0);
55 Serial.print(F(" ppm fan: "));
56 Serial.println(fanOn ? F("ACIK / ON") : F("KAPALI / OFF"));
57 delay(1000);
58}Пин IN модуля реле подключается к D7, а VCC и GND — к Arduino; вентилятор подключается к собственному источнику питания через контакты COM и NO реле. Если используется компьютерный вентилятор на 12 V, MOSFET тише и долговечнее реле; схема та же, что и у мини-насоса для воды. Если нужно коммутировать прибор на 230 V, например вытяжной вентилятор в ванной, строго соблюдай правила безопасности из руководства по модулю реле: убери сетевую часть в закрытый корпус и никогда не трогай схему под напряжением. Если в setup() сначала выставить пин в выключенное состояние и только потом сделать его выходом, реле с активным LOW не будут на мгновение срабатывать при включении питания.
Запись качества воздуха на ESP32
Увидеть, как качество воздуха меняется в течение дня, гораздо полезнее, чем смотреть на одно показание: спёртый воздух, который к утру накапливается в спальне, всплеск на кухне во время готовки или эффект от открытого окна видны только по журналу. Код для ESP32 ниже делает измерение раз в минуту, получает время из интернета (NTP), чтобы поставить дату и время в каждой строке, и сохраняет результат в формате CSV в файловую систему LittleFS во встроенной флеш-памяти ESP32. Если открыть IP-адрес ESP32 в браузере, видно последнее измерение, а весь журнал можно скачать одним нажатием в виде CSV-файла, который открывается в Excel. Дополнительная библиотека не нужна.
1#include <WiFi.h>
2#include <WebServer.h>
3#include <LittleFS.h>
4#include <time.h>
5
6const char* WIFI_SSID = "AgAdi"; // Wi-Fi adın / your Wi-Fi name
7const char* WIFI_PASS = "Sifre123"; // Wi-Fi şifren / your Wi-Fi password
8const int MQ_PIN = 34; // AO, 10k/20k bölücü üzerinden (ADC1) / through a 10k/20k divider (ADC1)
9const float DIVIDER = 1.5; // (10k + 20k) / 20k
10const float RL_KOHM = 1.0;
11const float R0_KOHM = 3.6; // ESP32 ile yeniden kalibre et / recalibrate with the ESP32
12const float PARA = 116.6020682;
13const float PARB = 2.769034857;
14const unsigned long LOG_INTERVAL = 60000; // dakikada bir kayıt / one entry per minute
15
16WebServer server(80);
17unsigned long lastLog = 0;
18float lastPpm = 0;
19
20float readPpm() {
21 uint32_t mv = 0;
22 for (int i = 0; i < 20; i++) {
23 mv += analogReadMilliVolts(MQ_PIN); // kalibre edilmiş mV / calibrated mV
24 delay(5);
25 }
26 float vout = (mv / 20.0) / 1000.0 * DIVIDER; // sensör çıkışı (V) / sensor output (V)
27 if (vout < 0.05) vout = 0.05;
28 float rs = RL_KOHM * (5.0 - vout) / vout;
29 return PARA * pow(rs / R0_KOHM, -PARB);
30}
31
32void handleRoot() {
33 String html = "<!DOCTYPE html><html><head><meta charset='utf-8'>"
34 "<meta name='viewport' content='width=device-width,initial-scale=1'></head>"
35 "<body style='font-family:sans-serif'><h2>MQ-135</h2><p>Son ölçüm / last reading: ~";
36 html += String(lastPpm, 0);
37 html += " ppm</p><p><a href='/log.csv'>CSV indir / download</a> | "
38 "<a href='/clear'>Kaydı sil / clear log</a></p></body></html>";
39 server.send(200, "text/html", html);
40}
41
42void handleCsv() {
43 File f = LittleFS.open("/log.csv", "r");
44 if (!f) {
45 server.send(404, "text/plain", "Kayit yok / no log yet");
46 return;
47 }
48 server.streamFile(f, "text/csv");
49 f.close();
50}
51
52void handleClear() {
53 LittleFS.remove("/log.csv");
54 server.sendHeader("Location", "/");
55 server.send(303);
56}
57
58void setup() {
59 Serial.begin(115200);
60 analogSetPinAttenuation(MQ_PIN, ADC_11db); // yaklaşık 0-3,1 V ölçüm aralığı / about 0-3.1 V range
61 if (!LittleFS.begin(true)) { // true: ilk açılışta biçimlendir / format on first use
62 Serial.println("LittleFS hatasi / error");
63 }
64 WiFi.mode(WIFI_STA);
65 WiFi.begin(WIFI_SSID, WIFI_PASS);
66 while (WiFi.status() != WL_CONNECTED) {
67 delay(500);
68 Serial.print(".");
69 }
70 Serial.println();
71 Serial.println(WiFi.localIP());
72 configTime(3 * 3600, 0, "pool.ntp.org"); // UTC+3; kendi saat dilimine göre değiştir / change to your time zone
73
74 server.on("/", handleRoot);
75 server.on("/log.csv", handleCsv);
76 server.on("/clear", handleClear);
77 server.begin();
78 lastLog = millis(); // ilk kayıt 1 dk sonra, sensör ısınırken / first entry after 1 min
79}
80
81void loop() {
82 server.handleClient();
83 if (millis() - lastLog >= LOG_INTERVAL) {
84 lastLog = millis();
85 lastPpm = readPpm();
86 char stamp[20] = "saat-yok";
87 struct tm now;
88 if (getLocalTime(&now, 100)) {
89 strftime(stamp, sizeof(stamp), "%Y-%m-%d %H:%M", &now);
90 }
91 File f = LittleFS.open("/log.csv", "a"); // dosyanın sonuna ekle / append to the file
92 if (f) {
93 f.printf("%s,%.0f\n", stamp, lastPpm);
94 f.close();
95 }
96 Serial.printf("%s ~%.0f ppm\n", stamp, lastPpm);
97 }
98}АЦП ESP32 измеряет иначе, чем у Uno, поэтому калибровку R0 лучше повторить с этой схемой; достаточно переделать функцию readRs() в коде калибровки по логике readPpm() отсюда. Подключи датчик к одному из пинов ADC1 в диапазоне GPIO 32–39: пины ADC2 нельзя использовать, пока включён Wi-Fi. Каждая строка занимает около 22 байт; при одной записи в минуту за сутки набирается примерно 32 КБ данных, и области 1,5 МБ в стандартной схеме разделов хватит примерно на полтора месяца. Для более длинных журналов можно взять модуль SD-карты, а если хочется отправлять данные прямо в таблицу, посмотри способ ESP32 и Google Таблицы.
MQ-135 с Raspberry Pi Pico (MicroPython)
На Pico модуль питается от 5 V с пина VBUS, а выход AO подключается к GP26 снова через делитель 10 kΩ / 20 kΩ. АЦП Pico возвращает 16-битное значение (0–65535) относительно 3,3 V; код переводит его сначала в напряжение, а затем, с учётом коэффициента делителя, в напряжение на выходе датчика. Формула та же, что и на Arduino. Напряжение VBUS может меняться от 4,8 до 5,1 V в зависимости от USB-источника, поэтому для точных результатов измерь VBUS мультиметром и исправь значение 5.0 в коде.
1from machine import ADC
2import time
3
4adc = ADC(26) # GP26 = ADC0, AO bölücü üzerinden / AO through a divider
5DIVIDER = 1.5 # (10k + 20k) / 20k
6RL = 1.0 # kΩ, karttaki yük direnci / load resistor on the board
7R0 = 3.6 # kΩ, kendi kalibrasyon sonucun / your calibration result
8PARA, PARB = 116.6020682, 2.769034857
9VCC = 5.0 # VBUS gerilimi / VBUS voltage
10
11def read_rs():
12 total = 0
13 for _ in range(20):
14 total += adc.read_u16()
15 time.sleep_ms(5)
16 v = total / 20 / 65535 * 3.3 * DIVIDER # sensör çıkışı (V) / sensor output (V)
17 v = max(v, 0.05) # sıfıra bölmeyi önle / avoid dividing by zero
18 return RL * (VCC - v) / v
19
20print("Isiniyor / warming up...")
21time.sleep(180) # 3 dk ısınma / 3 min warm-up
22while True:
23 ratio = read_rs() / R0
24 ppm = PARA * ratio ** -PARB # tahmini CO2 eşdeğeri / estimated CO2 equivalent
25 print("Rs/R0 = %.2f ~%d ppm" % (ratio, ppm))
26 time.sleep(2)MQ-135, CCS811 и NDIR-датчики CO2: сравнение
Есть три класса датчиков качества воздуха, и каждый отвечает на свой вопрос. MQ-135 дешёвый, аналоговый и простой; он показывает общие изменения воздуха. CCS811 тоже металлооксидный датчик, но внутри у него свой микроконтроллер, он сам управляет нагревателем и по I2C сразу выдаёт TVOC (общее содержание летучих органических соединений) и eCO2 (эквивалент CO2); о нём мы рассказываем в руководстве по датчику качества воздуха CCS811. Единственная группа, которая измеряет настоящий CO2, — датчики NDIR (недисперсионные инфракрасные): модули вроде MH-Z19B или SenseAir S8 измеряют, сколько инфракрасного света с длиной волны 4,26 µm поглощают молекулы CO2, и почти не реагируют на другие газы.
| Параметр | MQ-135 | CCS811 | NDIR (MH-Z19B, SenseAir S8) |
|---|---|---|---|
| Принцип измерения | Сопротивление нагретого SnO2 | Металлооксидный + встроенный микроконтроллер | Поглощение инфракрасного света |
| Измеряет настоящий CO2? | Нет, оценивает | Нет, вычисляет eCO2 по ЛОС | Да |
| Выход | Аналоговый (плюс цифровой порог) | I2C | UART или ШИМ |
| Питание | 5 V, около 150 mA | 3,3 V, в среднем около 30 mA | 5 V, в среднем меньше 60 mA |
| Прогрев | 24–48 ч для нового, затем несколько минут | 48 ч для нового, затем 20 минут | Около 3 минут |
| Точность | Низкая, для относительных изменений | Средняя, для относительного отслеживания ЛОС | Около ±50 ppm + 3–5% от показания |
| Цена | Самый дешёвый | Средняя | Самый дорогой |
Коротко: чтобы учиться, запускать сигнализацию или вентилятор и следить за изменениями воздуха, MQ-135 достаточно. Если нужно отслеживать пары краски, клея или чистящих средств, CCS811 даёт более ровные и стабильные данные. Если для класса, офиса или спальни нужно надёжное число, чтобы ответить на вопрос «не пора ли открыть окно?», придётся вложиться в NDIR-датчик CO2. Многие используют оба: NDIR-датчик следит за CO2, а MQ-135 ловит внезапные события вроде дыма или запаха растворителя на кухне.
Идеи проектов с MQ-135
- Индикатор воздуха в комнате: RGB-светодиод, который светится зелёным, жёлтым или красным в зависимости от качества воздуха, или живой график на OLED-дисплее.
- Умная кухонная вытяжка: система, которая включает вытяжку, когда во время готовки растёт количество дыма и пара, и выключает её, когда воздух очистится.
- Предупреждение для гаража и покрасочной мастерской: схема, которая подаёт сигнал зуммером и запускает вентиляцию, когда растёт концентрация паров растворителя.
- Контроль кошачьего лотка: проект на ESP32, который присылает уведомление на телефон, когда растёт уровень аммиака и пора менять наполнитель.
- Табло воздуха в классе: график, который записывает показания на ESP32 и сопоставляет их с расписанием уроков, чтобы показать привычки проветривания.
- Домашняя автоматизация: отправляй значения в Home Assistant по MQTT и включай очиститель воздуха через умную розетку, когда воздух ухудшается.
Частые ошибки и как их исправить
| Симптом | Вероятная причина | Решение |
|---|---|---|
| Значения часами продолжают уплывать | Не завершён первичный прогрев | Дай датчику непрерывно поработать 24–48 часов, затем калибруй |
| Сразу после включения значения слишком высокие или низкие | Не выдержан ежедневный прогрев | Перед измерением подожди минимум 3 минуты, лучше 10–20 |
| Тысячи ppm даже на чистом воздухе | Неверное R0, или калибровка сделана в помещении | Повтори калибровку на чистом уличном воздухе |
| На ESP32 значения всегда на максимуме | AO доходит до 5 V, или датчик питается от 3,3 V | Питай датчик от 5 V и поставь на AO делитель 10k/20k |
| Arduino греется или отваливается USB | Нагреватель на 150 mA вместе с другими нагрузками перегружает USB | Используй внешний источник 5 V и объедини земли |
| ppm растёт на солнце или после душа | Влияние температуры и влажности | Калибруй в похожих условиях или применяй коррекцию по DHT22 |
Если знать его пределы, MQ-135 — отличный стартовый датчик, чтобы разобраться в качестве воздуха и автоматизировать вентиляцию: питай его от 5 V, дай ему 24–48 часов прогрева, найди R0 на чистом воздухе, оценивай результаты относительно и переходи на NDIR-датчик, когда нужен настоящий CO2. В приложении BlueGrays есть проекты «Работа с датчиком газа», «Сигнализация пламени и газа» и «Сигнализация газа и дыма MQ-2 на ESP32», а также проекты измерения температуры и влажности на DHT11 и DHT22 и регистратора температуры на ESP32, с пошаговыми схемами подключения и кодом.
Частые вопросы
Может ли MQ135 измерять CO2?
MQ-135 по-настоящему не измеряет CO2, а оценивает его. На CO2 датчик реагирует лишь слабо; в основном он реагирует на такие газы, как спирт, аммиак и бензол, и на летучие соединения, которые выделяют люди. Значение CO2 ppm, которое показывают библиотеки, вычисляется по этому общему загрязнению; для настоящего CO2 нужен NDIR-датчик, например MH-Z19B.
Сколько прогревается MQ135?
Новому MQ-135 нужен первичный прогрев 24–48 часов, и его обязательно нужно завершить до калибровки. После этого датчик при каждом включении готов к работе примерно через 2–3 минуты; для самых стабильных значений лучше подождать 10–20 минут.
Как найти значение R0 для MQ135?
Вынеси прошедший прогрев датчик на чистый уличный воздух, посчитай его сопротивление по формуле Rs = RL × (1023 − raw) / raw и, считая, что CO2 на улице около 420 ppm, вычисли R0 = Rs × (420 / 116.6)^(1 / 2.769). Усредни показания за несколько минут. Найденное R0 относится только к этому датчику и этому нагрузочному резистору.
Чем MQ135 отличается от MQ2?
Оба — металлооксидные датчики с нагревателем, но настроены на разные газы. MQ-2 подходит для сжиженного газа, пропана, метана, водорода и дыма, то есть для сигнализации утечки газа. MQ-135 чувствителен к загрязнителям воздуха, таким как аммиак, бензол, спирт, NOx и дым, и используется для контроля качества воздуха.
Можно ли использовать MQ135 с ESP32?
Да, но следи за двумя вещами. Нагревателю датчика нужно 5 V, поэтому питай модуль от пина VIN на ESP32. Выход AO тоже может подниматься до 5 V, так что понизь его до 3,3 V делителем напряжения из 10 kΩ и 20 kΩ и подключи к пину ADC1 в диапазоне GPIO 32–39.
Какие газы обнаруживает MQ135?
MQ-135 сильнее всего реагирует на аммиак, соединения серы и ароматические пары вроде бензола. Заметно он реагирует и на спирт, дым и оксиды азота, а на CO2 и угарный газ — слабо. Различать эти газы он не умеет и показывает только общее изменение воздуха.
Что собрать после прочтения
Все проекты
Температура и влажность с DHT11
Насколько тепло в твоей комнате и влажный ли воздух или сухой? Датчик DHT11 измеряет температуру и влажность, а Arduino выводит значения на двухстрочный LCD-экран и обновляет их каждые пару секунд. Ес

Работа с датчиком газа
Маленький страж, который замечает утечку газа на кухне раньше тебя. Датчик газа всё время «принюхивается» к воздуху; пока всё в порядке, горит зелёный светодиод, а когда газа в воздухе становится боль

Сигнализация пламени и газа
Маленький страж, который сразу предупреждает, когда где-то утекает газ или появляется пламя. Датчик газа следит за газом в воздухе, а датчик пламени — за светом огня; пока всё в порядке, горит зелёный

Сигнализация газа и дыма MQ-2 на ESP32
В этом проекте модуль датчика газа MQ-2 обнаруживает горючие газы (сжиженный газ, метан, пропан) и дым, а при обнаружении пассивный зуммер играет тревогу, похожую на сирену. Цифровой пороговый выход датчика (DO) подключён к GPIO 4 у ESP32; когда газ или дым превышают порог, DO переходит в LOW, и срабатывает тревога. Чувствительность настраивается потенциометром модуля. Пассивный зуммер управляется через tone() и даёт сирену на двух частотах.

Температура и влажность с DHT22 на ESP32 (с LCD-дисплеем)
Этот проект измеряет температуру и влажность воздуха датчиком DHT22 и в реальном времени показывает данные на I2C LCD 16x2. Данные читаются с датчика по однопроводному протоколу через библиотеку Adafruit DHT. Проект служит основой для IoT-метеостанций, умного дома или систем мониторинга среды в теплицах. Связь с DHT22 идёт через пин GPIO 4 у ESP32.

Регистратор температуры на ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
Собери на ESP32 регистратор температуры с отметками времени! Каждую минуту читай температуру и влажность с DHT22, бери реальное время из DS1307 RTC и записывай всё на карту microSD в формате CSV. OLED показывает текущую температуру (крупным шрифтом), влажность в процентах, время и счётчик записей. Файл /temp_log.csv на SD-карте открывается в Excel, Google Sheets или Python pandas — идеально для теплиц, комнат, лабораторий и хранения продуктов.

I2C LCD-дисплей (16x2) на ESP32
В этом проекте символьный LCD-экран 16x2 подключается к ESP32 по протоколу I2C и выводит текст. Благодаря I2C экраном можно управлять всего двумя пинами данных (SDA и SCL), не тратя 6+ пинов, как при классическом параллельном подключении. Код печатает постоянное сообщение в первой строке и счётчик, растущий каждую секунду, во второй. Кроме того, при запуске работает сканер I2C, который выводит адрес дисплея в монитор порта.

Включение лампы 220V кнопкой (удержание)
Пока ты держишь кнопку, горит настоящая лампа на 220 вольт, а как только отпустишь — гаснет. Слабый сигнал Arduino не может зажечь лампу напрямую, поэтому между ними стоит реле — выключатель с электри
Другие статьи
Приложение BlueGrays
Собери эту схему шаг за шагом в телефоне
Схема подключения, код и список деталей на одном экране. 200+ проектов на Arduino, ESP32 и Pico готовы к сборке.
Наведи камеру телефона
Наведи камеру на код — откроется магазин приложений на телефоне.
или открыть магазин

