Компоненты · 12 мин чтения

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
Модуль MQ-135: металлооксидный датчик с нагревателем, компаратор LM393, потенциометр чувствительности и пины VCC, GND, DO, AO.

Что такое 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 UnoESP32Raspberry Pi Pico
VCC (включая нагреватель)5VVIN (5 V от USB)VBUS (5 V от USB)
GNDGNDGNDGND
AO (аналоговый выход)A0GPIO 34 через делитель 10 kΩ / 20 kΩGP26 (ADC0) через делитель 10 kΩ / 20 kΩ
DO (цифровой порог, по желанию)D2Любой GPIO через делительЛюбой пин GP через делитель
Один только нагреватель MQ-135 потребляет около 150 mA, и ему обязательно нужно 5 V; при питании от пина 3V3 на ESP32 или Pico датчик не прогреется как следует, и показания будут бессмысленными. Когда модуль работает от 5 V, выход AO может подниматься до 5 V, а это повреждает вход АЦП на 3,3 V у ESP32 и Pico. Собери между AO и GND делитель напряжения из 10 kΩ + 20 kΩ и подключи среднюю точку к пину АЦП: 5 V × 20 / 30 = 3,33 V. Другие пары резисторов можно проверить в утилите делителя напряжения. Работающий датчик нагревается так, что его не удержать в руке; не трогай его сетку.

Прогрев: прожиг 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. Помни, что для других газов коэффициенты другие, а результат — лишь оценка.

Один из маленьких SMD-резисторов на обратной стороне модуля — это RL, и код на нём показывает номинал: 102 = 1 kΩ, 103 = 10 kΩ, 203 = 20 kΩ. Эти коды мы разбираем в статье коды SMD-резисторов. Неверно указанный RL не меняет результат в ppm, потому что R0 считается с тем же RL и отношение остаётся прежним; масштабируются только числа Rs и R0 на экране. Но если RL очень маленький (1 kΩ), на чистом воздухе напряжение AO остаётся очень низким, и страдает разрешение АЦП; если видно, что значения дрожат, хорошо помогает усреднение.

Калибровка R0 для MQ-135 шаг за шагом

  1. Подключи VCC модуля к 5V, GND к GND, а AO к A0 и проведи первичный прогрев: датчик должен непрерывно проработать 24–48 часов.
  2. Прочитай код на нагрузочном резисторе на обратной стороне платы и выставь под него значение RL_KOHM в коде ниже.
  3. Вынеси схему на чистый уличный воздух, например на балкон или к открытому окну, подальше от машин, сигарет и кухни.
  4. Загрузи код калибровки, открой монитор порта на скорости 9600 бод и подожди минимум 10 минут.
  5. Когда среднее R0 на экране несколько минут почти не меняется, запиши его; это и есть R0 твоего датчика.
C++ (Arduino IDE)
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 растёт, а если убрать вату, за несколько минут значения возвращаются к норме.

C++ (Arduino IDE)
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 минут, чтобы воздух в комнате действительно сменился. Кроме того, код сглаживает показания, чтобы кратковременные всплески не запускали вентилятор.

C++ (Arduino IDE)
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. Дополнительная библиотека не нужна.

C++ (Arduino IDE)
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 в коде.

MicroPython
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-135CCS811NDIR (MH-Z19B, SenseAir S8)
Принцип измеренияСопротивление нагретого SnO2Металлооксидный + встроенный микроконтроллерПоглощение инфракрасного света
Измеряет настоящий CO2?Нет, оцениваетНет, вычисляет eCO2 по ЛОСДа
ВыходАналоговый (плюс цифровой порог)I2CUART или ШИМ
Питание5 V, около 150 mA3,3 V, в среднем около 30 mA5 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 не как абсолютное измерение, а как изменение относительно базового уровня. Когда датчик прогреется, прими значение, измеренное в момент чистого воздуха, за базовый уровень и принимай решения по относительным порогам вроде «на 30% выше базового уровня»; такой подход гораздо устойчивее к ошибкам калибровки, старению датчика и сезонным изменениям. Скользящее среднее и медианный фильтр, которыми можно сгладить показания, мы разбираем в статье фильтрация данных с датчиков.

Если знать его пределы, 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
#55Средний

Температура и влажность с DHT11

Насколько тепло в твоей комнате и влажный ли воздух или сухой? Датчик DHT11 измеряет температуру и влажность, а Arduino выводит значения на двухстрочный LCD-экран и обновляет их каждые пару секунд. Ес

4.7(47)
73
Работа с датчиком газа
#51Новичок
Premium

Работа с датчиком газа

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

4.6(37)
50
Сигнализация пламени и газа
#52Продвинутый
Premium

Сигнализация пламени и газа

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

4.8(52)
67
Сигнализация газа и дыма MQ-2 на ESP32
#122Средний

Сигнализация газа и дыма MQ-2 на ESP32

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

4.7(28)
43
Температура и влажность с DHT22 на ESP32 (с LCD-дисплеем)
#117Средний

Температура и влажность с DHT22 на ESP32 (с LCD-дисплеем)

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

4.8(38)
56
Регистратор температуры на ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
#144Продвинутый
Premium

Регистратор температуры на ESP32 (DHT22 + DS1307 RTC + microSD + OLED)

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

4.7(7)
12
I2C LCD-дисплей (16x2) на ESP32
#119Средний
Premium

I2C LCD-дисплей (16x2) на ESP32

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

4.9(23)
40
Включение лампы 220V кнопкой (удержание)
#19Продвинутый
Premium

Включение лампы 220V кнопкой (удержание)

Пока ты держишь кнопку, горит настоящая лампа на 220 вольт, а как только отпустишь — гаснет. Слабый сигнал Arduino не может зажечь лампу напрямую, поэтому между ними стоит реле — выключатель с электри

4.4(30)
38