Моя дачная метеостанция всю ночь писала показания как по учебнику, а утром вместо цифр я увидел столбик nan. К обеду всё само заработало, и я на неделю успокоился — потом nan вернулся на весь вторник, исчез на два дня и снова пришёл. Это худшее свойство nan: он приходит и уходит, и руки опускаются чинить то, что «иногда работает». На деле всё жёстче и проще: nan всегда значит, что библиотека не получила валидный ответ от сенсора — не проснулся, не долетели импульсы или не сошлась контрольная сумма. Причина почти всегда железная, и она одна из шести.
В этом уроке — моя системная диагностика, выстраданная за пару лет таких эпизодов. Сначала разберём, что nan значит на уровне библиотеки и как его честно ловить в коде. Потом пройдём чек-лист от проверок по пять секунд к проверкам по пять минут: питание, частота опроса, подтяжка, провод, тип в конструкторе, begin. Порядок важен: пять секунд с мультиметром экономят вечер с паяльником. Схему вы собирали в уроке про подключение DHT11 и DHT22, так что большинство пунктов будут узнаваемыми — теперь проверим их на живом датчике.
Почему nan — это вежливый отказ, а не поломка
Вспомним, как идёт обмен. Контроллер роняет линию данных на 18–20 миллисекунд, сенсор просыпается и отвечает сорока импульсами, где каждый бит закодирован длительностью высокого уровня: около 27 микросекунд для нуля и 70 для единицы. Библиотека отмеряет эти длительности, собирает два байта влажности, два байта температуры и проверяет контрольную сумму. Если сенсор не проснулся, импульсы рассыпались от наводки или сумма не сошлась — честного числа нет. Библиотека не выбрасывает ошибку и не печатает диагностику: она тихо возвращает nan, специальное значение «not a number». Датчик при этом жив; он просто не смог ответить на этот конкретный вопрос. Время всего обмена — 4–5 миллисекунд, и всё это время процессор занят только подсчётом импульсов.
Штатный способ поймать отказ — функция isnan(): принимает float, возвращает true, если внутри nan. Ставится она до использования числа: сорванное чтение печатается отдельной строкой и пропускается, а не едет дальше в логику термостата и не портит ему среднее. Я ставлю isnan во все DHT-скетчи без исключений — по той же причине, по которой пристёгиваюсь в машине: без проверки едешь ровно до первого сбоя. Если в вашем скетче nan уже едет в расчёты, средние за сутки будут испорчены: чинить надо на входе, а не на выходе.
#include "DHT.h" #define DHTPIN 2#define DHTTYPE DHT11 DHT dht(DHTPIN, DHTTYPE); unsigned long lastRead = 0; void setup() { Serial.begin(9600); dht.begin();} void loop() { if (millis() - lastRead < 2000) return; // не чаще 2 секунд lastRead = millis(); float t = dht.readTemperature(); float h = dht.readHumidity(); if (isnan(t) || isnan(h)) { Serial.println("FAIL: сенсор не ответил"); return; } Serial.print("t = "); Serial.println(t); Serial.print("h = "); Serial.println(h);}В этом скетче две конструкции тянут на привычку всей жизни. Опрос по millis() вместо delay: loop() крутится свободно, а датчик читается строго раз в две секунды — для метеостанции разница невидима, а для проекта с кнопками и дисплеем это граница между живым и зависшим интерфейсом. Если связка с lastRead выглядит незнакомо, вернитесь к уроку про millis — мы разбирали её по шагам.
Вторая привычка — считать nan отдельной строкой журнала, а не нулём. Если сорванное чтение записать как 0, среднее за ночь просядет, минимум за зиму окажется фальшивым, а график получит яму на ровном месте. Отдельная строка FAIL с меткой времени — уже диагноз в миниатюре: если такие строки кучкуются по ночам, первым подозреваемым будет питание, а не сенсор. Отдельная глава — проводка: тонкая перемычка с плохим контактом в колодке даёт ровно такой эпизодический nan, как в моей истории; как выбирать провода и укладывать их на макетке, разобрано в отдельном уроке.
Чек-лист: шесть причин nan в порядке вероятности
| Причина | Типичный симптом | Проверка | Лечение |
|---|---|---|---|
| Питание 3.3 В вместо 5 В | nan у клонов эпизодами или с самого старта | Мультиметром 4.8–5.2 В на ножках сенсора | Переткнуть в 5 В; на 3.3 В живут не все экземпляры |
| Опрос чаще разрешённого | nan среди нормальных строк, раз в несколько строк | Посчитать delay() в loop() | DHT11 — 1 с, DHT22 — 2 с, больше не чаще |
| Нет подтяжки 10 кОм | nan на голом сенсоре с четырьмя ножками | Осмотреть плату и схему, маркировка 103 | Резистор 10 кОм между VCC и DATA |
| Плохой контакт, длинный провод | nan случайно, при встряхивании макетки | Пошевелить перемычки, укоротить линию | Заменить провод; дальше метра — витая пара или экран |
| Тип датчика в конструкторе | nan всегда, с первой строки | Сверить DHT dht(пин, DHT11) с корпусом | Поправить второй аргумент конструктора |
| Забыт dht.begin() | nan с самого старта, каждая строка | Открыть setup() | Добавить dht.begin(); один раз |
- Уменьшите в симуляторе delay(2000) до delay(1000): строки пойдут чаще. На настоящем DHT22 тот же трюк закончится nan между нормальными строками — вы своими руками увидите вторую строку таблицы.
- Добавьте в скетч счётчик срывов: переменную fail, которая растёт на единицу в каждой ветке ERR. Подсказка: печатайте её парой fail = … после ok. За минуту наблюдения посчитайте, сколько чтений из десяти портились бы на реальном железе.
- На живом датчике снимите подтяжку 10 кОм и загляните в монитор: часть строк станет FAIL. Верните резистор и убедитесь, что журнал успокоился — третья строка чек-листа проверена.
Ловим отказ в симуляторе: проверка диапазона
Проверка диапазона — не замена isnan, а его родственник, и путать их нельзя. В симуляторе она ловит мусорное значение, вышедшее за физические пределы датчика. На настоящем железе всё хитрее: любое сравнение с nan возвращает false, поэтому nan проходит через условие if (t < -40.0 || t > 80.0) как ни в чём не бывало — компилятор не предупредит, а график получит выброс. Поэтому на реальном скетче сначала isnan(), как в листинге выше, и только потом проверки диапазона. Второй рубеж остаётся полезным: он ловит датчик, который ответил числом, но числом невозможным — такое случается с перепутанными линиями и самодельными удлинителями. Правило двух рубежей я формулирую так: нечисловой мусор и невозможное число — разные неисправности, и ловятся по-разному.
Если все шесть строк таблицы проверены, а nan держится — сенсор, скорее всего, мёртв: клоны DHT умирают от конденсата, перегрева и времени. Но есть плановое решение, к которому я прихожу раньше, чем датчик успевает умереть. Самодельный однопроводной протокол DHT с таймингами в микросекунды — устаревшая конструкция: сенсор чувствителен к длине провода, качеству питания и загруженности процессора в момент чтения. I2C-датчики — AHT20, SHT31, BME280 — живут на шине из двух проводов, отвечают стабильно и стоят сопоставимо. Моё правило с границей: DHT11 в комнате на макетке — нормальный выбор на годы; на улице, в теплице или дальше метра от платы я сразу ставлю AHT20, и историй с nan у меня с тех пор ноль. Это не эстетика: на шине I2C не ловятся микросекундные тайминги, и длина провода почти не играет роли. Если шина I2C вам пока незнакома, начните с урока про два провода.
Когда nan побеждён, показания наконец становятся данными, которым можно верить. Дальше маршрут такой: термостат с гистерезисом превращает температуру в реле вентилятора, а журнал на SD-карте копит строки в файл, который потом открывается таблицей. И ставьте isnan сразу — тогда однажды утром в логе будет не столбик nan, а одна строка FAIL с точным временем, после которой останется проверить питание, а не переписывать код. Если журнал копится на карте, эти строки FAIL позже превратятся в статистику: сколько срывов за ночь — готовый график здоровья питания.
- 1. Что возвращает библиотека, когда сенсор не ответил на чтение?
- 2. С какой частотой разрешено опрашивать DHT22?
- 3. Почему проверка диапазона не ловит настоящий nan на железе?
- 4. nan появляется эпизодами, чаще ночью. Первая проверка?
- 5. На голом сенсоре с четырьмя ножками нет подтяжки. Что сделать?
- nan значит «на это чтение сенсор не ответил»: не сошлись 40 импульсов или контрольная сумма.
- На железе — isnan() перед использованием числа; nan проходит любую проверку диапазона, потому что сравнения с ним ложны.
- Опрос: DHT11 — раз в секунду, DHT22 — раз в две секунды; чаще — nan или кэш.
- Чек-лист по вероятности: питание, частота, подтяжка 10 кОм, провод, тип в конструкторе, dht.begin().
- DHT на улице или дальше метра — повод перейти на I2C-датчик AHT20 или SHT31.
Частые вопросы по теме урока
- Что возвращает библиотека, когда сенсор не ответил на чтение?
- Библиотека молча возвращает nan, не выбрасывая ошибку. Ловится функцией isnan() до использования числа. nan — специальное значение «не число»
- С какой частотой разрешено опрашивать DHT22?
- DHT22 пересчитывает показания раз в две секунды, DHT11 — раз в секунду. Более частый опрос даёт nan или кэш. Не чаще раза в две секунды
- Почему проверка диапазона не ловит настоящий nan на железе?
- Сравнение с nan всегда ложно, поэтому мусорное значение проходит сквозь if. На железе ставят isnan(), а диапазон — вторым рубежом. Любое сравнение с nan возвращает false, и условие не срабатывает
- nan появляется эпизодами, чаще ночью. Первая проверка?
- Ночной характер указывает на питание: потребление при опросе подскакивает, слабый блок проседает. Мультиметр на ножки сенсора под нагрузкой. Питание: не проседает ли напряжение ниже 4.5 В при опросе
- На голом сенсоре с четырьмя ножками нет подтяжки. Что сделать?
- Линия данных должна иметь уровень в покое: резистор 10 кОм между питанием и данными делает фронты сигнала чёткими. Поставить резистор 10 кОм между VCC и DATA