Метеостанцию для балкона я собрал за вечер и гордился ей ровно сутки. Утром второго дня заметил, что читать с неё невозможно: последняя цифра температуры дрожала — 24,7, 24,6, 24,7 — каждую долю секунды, и глаз всё время цеплялся за мельтешение вместо значения. Вдобавок после первого заморозка, когда градусы ушли с 9,5 на минус, на экране остался призрак: «9,55» из хвоста старого показания. Скетч был простейший — readTemperature в loop и print — и ровно поэтому всё выглядело сломанным: датчик опрашивался тысячи раз в секунду, экран перерисовывался вместе с ним, а шум датчика печатался на экран живьём.
В этом уроке лечим все три болезни сразу: перерисовку в каждом обороте, дрожание последней цифры и хвосты старых символов. Получится прибор, который приятно читать издалека: температура и влажность на первой строке, по кнопке — экран минимума и максимума за период наблюдения. Датчик возьмём DHT11 — как подключать его к плате, мы уже разбирали, а первый скетч с библиотекой DHT и разбором ответа датчика есть в уроке про библиотеку DHT — здесь же сосредоточимся на экране и дисциплине обновлений. Приём с порогом смены универсальный: он же лечит дрожащий дальномер, дрожащий вольтметр и любой другой датчик, который вы захотите поставить на дисплей.
Три болезни дёргающегося экрана
Первая болезнь — перерисовка без пауз. Печать строки по I2C небыстрая: каждый байт едет примерно девяносто микросекунд на стандартных ста килогерцах, плюс подтверждение, плюс накладные расходы — строка из шестнадцати знакомест занимает около полутора миллисекунд чистой передачи. В loop без задержек это тысячи перерисовок в секунду: экран вечно занят, глаз видит мельтешение, а ничего полезного за это время не происходит. Лечение — таймер на millis(): читаем датчик и перерисовываем экран раз в секунду, больше — незачем.
Вторая болезнь — шум датчика. Даже в комнате с постоянной температурой два соседних чтения DHT гуляют на 0,1–0,3 градуса: это нормально для дешёвого сенсора, он так устроен. Печатая каждое чтение, вы показываете не температуру, а шум. Третья болезнь — хвосты: новая запись короче старой, и всё, что правее, остаётся на экране. Пробелы затирания после каждого значения решают это дешевле, чем clear() целиком, — тем более что clear() на большинстве библиотек ещё и возвращает курсор в угол, чего нам тут не нужно. Если хвосты и разряды путаются в голове, перечитайте разбор печати чисел на LCD — там каждая болезнь разряда разобрана на живых примерах.
| Симптом на экране | Причина | Лечение |
|---|---|---|
| Мельтешение всей строки | Печать в каждом обороте loop без таймера | Обновление раз в секунду по millis() |
| Дрожит последняя цифра | Шум датчика 0,1–0,3 °C от чтения к чтению | Порог смены 0,3–0,5 °C между показанным и новым |
| Призрак старого разряда | Новая запись короче предыдущей | Пробелы затирания после каждого значения |
| Значения вообще не меняются | Чтение чаще, чем датчик успевает обновиться | DHT11 — раз в секунду, DHT22 — раз в две |
Порог смены — центральный приём урока. Показанное значение живёт на экране, пока новое чтение не отличается от него больше чем на 0,3 градуса; отличается — принимаем новое и перерисовываем. Шум в 0,1–0,2 градуса через такой порог не пролезает вовсе, а реальное изменение на полградуса и больше видно уже на следующем чтении. Границы подбираются под задачу: для комнаты я ставлю 0,2–0,3, для теплицы 0,5, чтобы видеть динамику прогрева; у DHT11 с его шагом в целый градус порог почти не нужен, а вот DHT22 с десятыми долями без него дрожит на глазах.
Минимум и максимум копим строго по показанному значению, а не по сырому чтению. Звучит как мелочь, пока не оставишь такой термометр на ночь: шум за восемь часов растащит крайние отметки на градус в каждую сторону, и утром на экране будет Min 22,4 при ровных 23,5 всю ночь. Показанное значение фильтровано порогом, поэтому его крайности — настоящие события, а не шёрстка шума. Тот же принцип, кстати, экономит и внимание: на экране минимума-максимума интересны только реальные пики.
#include <Wire.h>#include <LiquidCrystal_I2C.h>#include <DHT.h> LiquidCrystal_I2C lcd(0x27, 16, 2);DHT dht(2, DHT11); float shown = -1000; // что сейчас на экранеunsigned long lastRead = 0; void setup() { lcd.init(); lcd.backlight(); dht.begin();} void loop() { // читаем датчик раз в секунду — DHT11 быстрее не успевает if (millis() - lastRead >= 1000) { lastRead = millis(); float t = dht.readTemperature(); // порог смены: печать только реального изменения if (shown < -900 || t - shown > 0.3 || shown - t > 0.3) { shown = t; lcd.setCursor(0, 0); lcd.print("T: "); lcd.print(shown, 1); lcd.print(" C "); // хвост из пробелов держит строку чистой } }}Разбор по строкам. shown = -1000 — метка «на экране ещё ничего нет»: первое же чтение пройдёт любой порог и попадёт на дисплей. Сам порог записан двумя сравнениями: t - shown > 0.3 и shown - t > 0.3 — на случай и нагрева, и остывания; есть и готовая fabs(), но два сравнения читаются без справочника. И следите за lastRead: он обновляется в момент чтения, а не в момент печати — пока порог не пропускает мелочь, датчик всё равно опрашивается раз в секунду, и резкий скачок, скажем от открытой форточки, вы увидите с первой же проверки.
Полный прибор с экраном Min/Max и кнопкой собран в тренажёре ниже. Там redraw() намеренно не читает датчик: она только печатает уже отфильтрованные shown и humShown, поэтому переключение экрана не порождает лишних обращений к DHT. Минимум и максимум заполняются вместе с первым показанным значением, а не раньше, так что экран крайностей пустым не бывает. Такие мелочи не меняют схему, но именно по ним прибор читается как взрослый: цифры стоят на местах, призраков нет, а мигания ровно столько, сколько требует смысл.
Тренажёр: датчик, кнопка и два экрана
В тренажёре стоит DHT на D2: температуру и влажность можно крутить прямо на компоненте, как будто вы дышите на датчик или занесли его с мороза. Кнопка на D3 переключает экран текущих показаний и экран Min/Max — с окном тишины из урока про счётчик, один щелчок равен одной смене экрана. Порог здесь 0,3 градуса: подвигайте датчик на десятую долю — цифра не дрогнет, это шум; сдвиньте на градус — покажет уже на следующем чтении, через секунду. Откройте Min/Max, нагрейте датчик, вернитесь — крайности обновились по показанным значениям. Если захочется точнее шага в целый градус, у нас есть сравнение DHT11 и DHT22 — там про разницу в десятых долях и в цене.
- 1. Как часто DHT11 успевает выдавать свежие показания?
- 2. Экран мерцает целиком, значения сменяются сотни раз в секунду. Главная причина?
- 3. Зачем порог 0,3 °C между показанным и новым значением?
- 4. На экране было 21,3, стало 9,5. Что появится без пробелов затирания?
- 5. lcd.write(223) на настоящем HD44780 выводит...
- Экран обновляется по таймеру раз в секунду; печать в каждом loop — источник мельтешения
- Порог смены 0,3 °C: шум датчика не дёргает цифру, реальные изменения видны сразу
- DHT11 опрашивают не чаще раза в секунду, DHT22 — раз в две; чаще — устаревшие данные
- Min/Max копи по показанному значению — иначе шум за ночь растащит крайние отметки
- Хвосты старых цифр затирают пробелами; значок градуса на настоящем железе — lcd.write(223)
Частые вопросы по теме урока
- Как часто DHT11 успевает выдавать свежие показания?
- По даташиту DHT11 обновляет значение не чаще раза в секунду, DHT22 — раз в две секунды. Чаще — только устаревшие данные. Не чаще примерно раза в секунду
- Экран мерцает целиком, значения сменяются сотни раз в секунду. Главная причина?
- Loop без задержек долбит экран тысячами перерисовок. Таймер на millis() сокращает это до разумного раза в секунду. Печать на LCD в каждом обороте loop без таймера
- Зачем порог 0,3 °C между показанным и новым значением?
- Порог пропускает на экран только изменения больше шума. Реальное потепление на полградуса видно уже на следующем чтении. Шум в 0,1–0,2 градуса перестаёт дёргать цифру
- На экране было 21,3, стало 9,5. Что появится без пробелов затирания?
- Новая запись короче старой, последний разряд старой остаётся. Пробелы после значения переписывают знакоместа поверх призрака. 9,53 — хвост старой цифры
- lcd.write(223) на настоящем HD44780 выводит...
- Позиция 223 знакогенератора HD44780 — глиф градуса. В симуляторе глифы не моделируются, там пишут просто C. Значок градуса