График температуры на моей даче полгода выглядел как кардиограмма не потому, что погода прыгала. Я хотел плавную кривую и поставил delay(200) «для точности» — через полчаса наблюдений в мониторе каждое третье значение было nan, а график рисовал лесенку из одинаковых чисел с редкими провалами. Я заподозрил датчик, заказал второй экземпляр, воткнул его в ту же схему — и получил те же nan на том же месте. Хорошо, что до возврата по гарантии дело не дошло: второй датчик не понадобился, проблема сидела в одной строке моего скетча.
Датчик не может измерять быстрее, чем он устроен. В этом уроке разберём, что происходит внутри DHT между опросами, почему более частое чтение не даёт свежих данных, а возвращает nan или молча старые значения, и как построить опрос по таймеру на millis, чтобы loop оставался живым для остальных задач. В конце — таблица интервалов под разные задачи, от живого графика до автономного логгера, которую я вывел из своих же граблей.
Что датчик делает между вашими опросами
Внутри корпуса DHT11 стоит ёмкостный сенсор влажности и термистор, но главный тормоз — его собственный крошечный контроллер. Он пересчитывает показания не мгновенно и не по запросу, а по собственному расписанию: у DHT11 свежие значения появляются примерно раз в секунду, у DHT22 — раз в две секунды. Ваш скетч на это расписание повлиять не может: датчик не «измеряет по команде», он лишь отдаёт последнее готовое число, когда вы его спрашиваете. Плата при этом исправна — она честно спрашивает быстрее, чем датчик успевает думать.
Отсюда простая и неприятная арифметика. Если опрашивать чаще, чем датчик обновляет данные, вы не получите свежих чисел — вы либо прочитаете то же самое, либо застанете сенсор в момент внутреннего пересчёта. А в момент пересчёта датчик не отвечает на стартовый импульс вовсе: библиотека ждёт ответ, упирается в таймаут и возвращает nan. Добавьте сюда, что сам обмен — это 40 импульсов на линии данных и занимает около 4–5 миллисекунд, на которые скетч блокируется: частые чтения ещё и съедают процессорное время у остального кода.
В тренажёре ниже показания слегка дрожат вокруг выставленных ползунками значений — на десятые доли. Это не баг, а честное поведение: настоящий DHT11 тоже шумит на сотые-десятые градуса от чтения к чтению. И именно поэтому ровно одинаковые строки подряд — тревожный признак, к которому мы вернёмся ниже.
| Задача | Мой интервал опроса | Почему именно так |
|---|---|---|
| Живой вывод в Serial, DHT11 | 2000 мс | минимум по даташиту — раз в секунду, беру с запасом |
| Живой вывод в Serial, DHT22 | 2500–3000 мс | две секунды на пересчёт плюс время на сам обмен |
| График в Serial Plotter | 2000–3000 мс | быстрее точек всё равно не станет, а nan ломают линию |
| Лог на SD-карту | 60 000 мс и реже | температура в комнате меняется медленнее, чем раз в минуту |
| Автономный логгер со сном | раз в 10 минут | батарею съедает не опрос, а минуты бодрствования между ним |
Что происходит при слишком частом опросе
Нарушить интервал можно двумя способами, и они выглядят по-разному. Первый — датчик честно не отвечает: вы застигли его в момент пересчёта, стартовый импульс ушёл в пустоту, библиотека отработала таймаут и вернула nan. Так ведут себя клоны и альтернативные библиотеки без обработки кэша — в мониторе это выглядит как nan через одну-две строки. Моя история с delay(200) была именно про этот вариант.
Второй способ хитрее. Библиотека DHT sensor library от Adafruit знает про ограничение и защищается от невежд: между чтениями она выдерживает внутреннюю паузу около двух секунд, и если вы просите данные раньше — молча отдаёт прошлый результат. Ошибки не будет, nan не будет, будет замороженная картина: график ползёт, но цифры не меняются. Это хуже nan, потому что выглядит как «всё работает»: я потратил вечер на перепрошивку, пока не заметил, что температура за окном на графике не двигается, хотя на улице как раз начался дождь. На клонах без кэша картина грубее: между таймаутами проскальзывают старые значения, и отличить живое чтение от замороженного сложнее. Поэтому тайминг я сначала проверяю на примере из урока про установку библиотеки и первый скетч, а правки переношу в свой код уже после — так сразу ясно, виноват скетч или железо.
Отсюда диагностическое правило, которое экономит часы: если показания повторяются точь-в-точь больше двух-трёх строк подряд, вы читаете кэш, а не датчик. Живой сенсор дрожит хотя бы на сотые доли — и в тренажёре, и на реальном железе. Точная диагностика nan со схемой, пинами и подтяжкой разобрана в отдельном уроке про nan, здесь же нас интересует только тайминг. А почувствовать разницу между стоящим и свободным loop можно в песочнице ниже: там две одинаковые мигалки — delay-версия теряет нажатия кнопки, millis-версия ловит каждое; потормайте обе.
Блокирующая пауза против неблокирующего таймера: нажмите кнопку и сравните.
Подгружаем интерактив…
#include "DHT.h" DHT dht(2, DHT11); // пин данных — D2 unsigned long lastPoll = 0;unsigned long INTERVAL = 2000; // DHT11: безопасно; для DHT22 ставьте 3000 void setup() { Serial.begin(9600); dht.begin(); lastPoll = millis();} void loop() { if (millis() - lastPoll >= INTERVAL) { lastPoll = millis(); float t = dht.readTemperature(); float h = dht.readHumidity(); Serial.print("ms = "); Serial.print(millis()); Serial.print(" t = "); Serial.print(t); Serial.print(" h = "); Serial.println(h); } // сюда же, без задержек, ложится весь остальной код}Разберём механику. Метка lastPoll запоминает момент прошлого опроса, и как только millis() − lastPoll переваливает за INTERVAL — читаем датчик и обновляем метку. Между опросами loop крутится вхолостую миллионами итераций, и в этом холостом ходу спокойно живут кнопки, дисплеи и реле. Задержка delay(2000) заморозила бы всё: она честно ждёт две секунды, не отпуская процессор. Мы это уже делали в уроке про millis вместо delay, здесь — прикладное применение. И ещё одно требование интервала: первое чтение после подачи питания имеет смысл делать не раньше, чем через секунду после begin — сенсору нужен стартовый прогрев, иначе вы получите nan и заподозрите поломку зря.
Строка ms = 2000 в начале каждой записи — не украшение. По ней видно, что опрос идёт ровно по расписанию: в мониторе каждое значение ms отличается от предыдущего на величину INTERVAL. Расстояние по времени между строками Serial — бесплатный осциллограф для любого скетча с таймерами, я проверяю им интервалы ещё до того, как что-то пойдёт не так. А пары имя=значение вроде t = 24.10 выбраны ещё и потому, что их без переделок читает Serial Plotter — включите его, и интервал опроса станет виден как шаг графика.
Ещё один нюанс раз в жизни экономит день: метку lastPoll я обновляю в момент чтения, а не после него. Если внутри ветки опроса появится тяжёлый код — печать на дисплей, запись на карту — интервал всё равно останется честным: отсчёт следующей паузы начнётся после завершения всех дел. Начинать отсчёт до чтения — значит получить интервал INTERVAL минус время дел, и на медленных операциях эта разница накапливается.
Какие интервалы ставлю я и когда этого мало
Мои рабочие числа простые: 2000 мс для DHT11 и 2500–3000 мс для DHT22 везде, где данные идут на экран или в Serial. Для записи на карту интервалы шагают на минуты — в уроке про логирование на SD мы писали строку раз в минуту, и для комнатного климата это уже избыточно: реальная температура в комнате меняется на градус за час, а не за секунду.
Важная оговорка про управление. Термостату, который включает обогрев или осушитель, частота опроса нужна не для красоты данных, а для скорости реакции — но и там чаще двух секунд опрашивать DHT бессмысленно, правильнее настроить гистерезис термостата и не дёргать нагрузку на каждой десятой доле градуса. Интервал опроса и скорость реакции тут не спорят: опрос раз в две секунды означает, что термостат заметит перегрев максимум за две секунды — для комнатных процессов быстрее и не надо. А если проекту нужны показания чаще раза в секунду — например, для регистрации порывов ветра в метеокоробке, — я не мучаю DHT, а ставлю датчик на шине I2C: он отдаёт данные за миллисекунды и умеет чаще. DHT хорош ровно там, где физические изменения видны раз в несколько секунд: комнаты, теплицы, погреба, коробки с техникой. Это граница его применимости, и она совпадает с границей большинства бытовых задач.
- 1. С какой минимальной частотой разрешено опрашивать DHT22?
- 2. Что произойдёт, если вызывать readTemperature() каждые 200 мс с библиотекой Adafruit?
- 3. Почему чтение DHT нельзя ужать до микросекунд?
- 4. Что в мониторе порта выдаёт слишком частый опрос?
- 5. Скетч с опросом по millis печатает ms = 4000, затем ms = 8000. Всё ли в порядке?
- DHT11 обновляет данные раз в секунду, DHT22 — раз в две; опрос быстрее возвращает nan или кэш, но не свежие числа.
- Опрос стройте на millis: метка lastPoll плюс INTERVAL; delay(2000) замораживает весь loop целиком.
- Каждое чтение — 40 импульсов и 4–5 мс блокировки: частые опросы съедают время у остального кода.
- Одинаковые значения больше двух строк подряд — вы читаете кэш библиотеки, а не датчик.
- Мой рабочий интервал: 2000 мс для DHT11 и 2500–3000 мс для DHT22; нужны данные чаще — берите датчик на I2C.
Частые вопросы по теме урока
- С какой минимальной частотой разрешено опрашивать DHT22?
- DHT22 обновляет внутренние показания раз в две секунды, DHT11 — раз в секунду. Чаще спрашивать можно, но новых данных это не принесёт. Раз в две секунды
- Что произойдёт, если вызывать readTemperature() каждые 200 мс с библиотекой Adafruit?
- Библиотека выдерживает внутреннюю паузу около двух секунд и между ними отдаёт прошлый результат. Ошибки нет — есть замороженные показания. Библиотека молча вернёт прошлое значение из кэша
- Почему чтение DHT нельзя ужать до микросекунд?
- Протокол — 40 импульсов с таймингами в десятки микросекунд, вся передача занимает 4–5 мс. Это физический минимум, который не сжать. Обмен идёт 40 импульсами и занимает около 4–5 миллисекунд
- Что в мониторе порта выдаёт слишком частый опрос?
- Два симптома нарушенного интервала: замороженный кэш библиотеки (одинаковые числа) и таймауты на клонах (nan). Живой датчик дрожит на десятых. Значения повторяются точь-в-точь или появляются nan
- Скетч с опросом по millis печатает ms = 4000, затем ms = 8000. Всё ли в порядке?
- Разница между метками равна INTERVAL — 4000 мс. Если задать интервал 4000, то и разница будет такой. Ровный шаг меток — признак здорового таймера. Да: интервал держится ровно, опрос идёт по расписанию