Датчик повешен, числа в Serial бегут — остался последний шаг, из которого прибор становится прибором: вынести показания на экран так, чтобы их читали без подхода и без прищура. Сводку для комнаты и теплицы я собираю по одной схеме несколько лет: температура — огромными цифрами, влажность — поменьше, служебные строки — внизу мелко. Сенсор на D2 будет DHT22: он умеет минус, десятые доли градуса и влажность с точностью до процента — всё, чего не хватает младшему брату DHT11. Если датчик уже стоит по урокам раздела про DHT, схема не меняется: те же три провода и подтяжка 10 кОм на сигнальной ножке; экран сядет на A4 и A5, и с цифровым пином датчика они не конфликтуют — I2C и отдельная ножка живут независимо.
В этой связке два технических сюжета. Первый — раскладка: как поделить 64 пикселя высоты между зонами так, чтобы большая цифра дышала, а мелочь не наезжала. Второй — честный таймер: DHT22 отвечает раз в две секунды, и вот эти две секунды нельзя конвертировать в delay(2000), если в приборе живёт хоть что-то ещё, кроме экрана. Оба сюжета короткие, но именно на них спотыкается каждая вторая сборка, которую мне приносили на разбор.
Склейка при этом честная: и датчик, и экран уже разобраны по своим урокам, здесь нет нового железа — только компоновка и таймер. Ни одной новой детали в макетку докупать не придётся, что делает сводку самым дешёвым прибором серии. Если соберёте её и остановитесь, вы уже получите работающее устройство; финальные абзацы — куда дорастить, когда захочется большего.
Раскладка: кто сколько пикселей занял
Всё складывается из ячеек, которые мы считали в уроке про размеры шрифта. Подпись зоны живёт на размере 1: строка 0, восемь пикселей высоты, до 21 символа. Температура — на размере 3: ячейка 18×24, то есть три высоты мелкой строки, и семь символов по ширине. Строка «23.5 C» занимает шесть — цифры, точка, пробел и литера C; минусовая «-19.5 C» влезает вплотную, на все семь. Влажность отдана размеру 2: высота 16 пикселей, десять символов в строке — «H: 45%» занимает семь, и ещё три символа остаются запасом на двузначную сотню процентов. Запас у температуры — тоже не блажь: минусовая строка «-19.5 C» вытягивается до семи знаков и влезает уже без зазора — на размерах крупнее тройки такой строке уже не место.
По вертикали раскладка у меня такая: 0 — подпись, 12–35 — большая цифра, 44 — строка влажности высотой в 16 пикселей. Смещение 44 может показаться странным, но это старт четвёртой строки двойки минус зазор, и кадр заканчивается ровно на 60-м пикселе, не задевая край. Числа у вас могут быть другими — правило одно: считайте высоты зон до кода и оставляйте между ними хотя бы пару пикселей, иначе скругления букв размера 3 будут царапать соседнюю строку. Я пробовал прижимать влажность вплотную к температуре, стартом с 36-го пикселя, — на глаз зоны сливались в одну кучу, и сводка терялась. Восемь пикселей зазора возвращают читаемость, а четыре нижних пикселя остатка всё равно никому не нужны.
#include <Wire.h>#include <Adafruit_GFX.h>#include <Adafruit_SSD1306.h>#include <DHT.h> Adafruit_SSD1306 display(128, 64, &Wire, -1);DHT dht(2, DHT22); // сенсор на D2, подтяжка 10 кОм на плюс unsigned long lastPoll = 0;unsigned long PERIOD = 2000; // паспортный минимум DHT22 - две секунды void setup() { Serial.begin(9600); dht.begin(); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.setTextColor(SSD1306_WHITE); // заставка на время стартового цикла сенсора display.setTextSize(1); display.setCursor(0, 0); display.print("Zhdem pervy opros..."); display.display();} void loop() { if (millis() - lastPoll >= PERIOD) { // между опросами loop свободен lastPoll = millis(); float t = dht.readTemperature(); float h = dht.readHumidity(); if (isnan(t) || isnan(h)) { // сенсор молчит - честно напишем display.clearDisplay(); display.setTextSize(1); display.setCursor(0, 0); display.print("DHT22 ne otvechaet"); display.display(); return; } display.clearDisplay(); // зона 1: подпись, размер 1, строка 0 display.setTextSize(1); display.setCursor(0, 0); display.print("Temp segodnya"); // зона 2: температура, размер 3, 7 символов в строке display.setTextSize(3); display.setCursor(0, 12); display.print(t, 1); display.print(" C"); // зона 3: влажность, размер 2, 10 символов в строке display.setTextSize(2); display.setCursor(0, 44); display.print("H:"); display.print(h, 0); display.print("%"); display.display(); }}В симуляторе скетч собирается без переноса на плату: тот же DHT dht(2, DHT22), тот же период 2000, те же три зоны. Датчик в тренажёре шумит на десятые доли вокруг выставленных ползунков — настоящие сенсоры шумят так же, поэтому строка с температурой дышит в последнем разряде. Кадр перерисовывается только в момент опроса: сколько ни двигайте ползунки между отметками, экран стоит на старых числах до следующей двухсекундной границы — это поведение millis-таймера, а не застывшая отрисовка.
Влажность печатается целым числом: display.print(h, 0) без дробной части. У DHT22 десятые доли процента честно измеряются, но на размере 2 каждая лишняя буква — из десяти доступных, и «H: 45.3%» съедает запас, оставленный под сотню процентов. Целых хватает с избытком: паспортная погрешность сенсора по влажности — около двух процентов, и десятые доли тонули бы в ней. Температуре десятые оставлены: там они стоят крупно и по делу.
Таймер опроса — четыре строки, но именно они отличают прибор от демонстрации. lastPoll и период 2000: как только millis() отошёл от прошлой отметки на две секунды, читаем сенсор и перерисовываем кадр; в остальное время loop крутится впустую и остаётся живым для кнопок и Serial. Почему именно две секунды: это паспортный период DHT22, при более частом опросе библиотека начнёт отдавать кэш или nan — интервалы и их следствия считались в уроке про опрос DHT. Само чтение небыстрое: сенсор отвечает по одному биту, и библиотека ждёт его паузы — порядка сотен миллисекунд на попытку, поэтому в каждое прохождение loop readTemperature совать нельзя.
Перерисовка в этой схеме совпадает с опросом — раз в две секунды. Кадр 128×64 — это 1024 байта по шине, около 92 мс на 100 кГц; раз в две секунды шина занята кадром четыре-пять процентов времени, и соседи по I2C этого не заметят. Если захочется обновлять только изменившиеся зоны без полного кадра — это частичное обновление из ОЗУ. А сам шаблон таймера без delay разобран по шагам в уроке про millis вместо delay. И ещё: кадр можно собирать реже опроса — раз в пять секунд, например: числа на сводке меняются медленно, а шина отдыхает. Я держал на той же шине часы DS3231 и вторую страницу сводки — соседями по I2C они друг друга не чувствуют: адреса разнесены, 0x3C против 0x68, и паузы у каждого свои.
От сводки к станции
Эта сводка — каркас, на который навешивается всё остальное. Первым делом я добавляю строку комфорта или точку росы: по той же температуре и влажности она считается одной формулой, и для теплицы это полезнее лишнего знака после запятой — расчёт по формуле Магнуса есть в уроке про точку росы. Потом стоит добавить историю: буфер последних значений и график прямо на экране — приём разобран в уроке про метео-панель с живыми значениями, там же зональная сетка, на которой держатся и мои сводки. Сводка из этого урока — нижний этаж той станции: когда зоны уже посчитаны, график встаёт третьей зоной без пересчёта всей раскладки.
И две оговорки напоследок. Подписи в кадре у меня латиницей, потому что стандартный шрифт SSD1306 кириллицу не умеет — что с этим делать, если русские надписи на экране принципиальны, рассказано в уроке про кириллицу на U8g2. А если сводка переезжает с макетки на постоянное место, вспомните про питание сенсора: DHT22 формально ест от 3,3 до 5,5 В, но дешёвые клоны на 3,3 В срываются через раз — на пяти вольтах они спокойны. И не стремитесь обновлять сводку быстрее двух секунд: сенсор всё равно не отдаст новые числа, а глаз их не читает — период отработан годами и менять его не ради чего.
| Элемент сводки | Размер | Высота зоны, px | Символов доступно |
|---|---|---|---|
| Подпись зоны | 1 | 8 | 21 |
| Температура | 3 | 24 | 7 — строка «23.5 C» занимает 6 |
| Влажность | 2 | 16 | 10 — строка «H: 45%» занимает 7 |
| Статус или время | 1 | 8 | 21 |
- 1. Минимальный интервал между чтениями DHT22 по документации:
- 2. Строка «23.5 C» на размере 3 — сколько она занимает и влезает ли?
- 3. Почему экран не мигает при каждой перерисовке?
- 4. Проверка millis() - lastPoll >= 2000 в начале loop — это:
- 5. Сенсор отвалился, readTemperature вернёт nan. Что покажет экран?
- DHT22 отвечает раз в две секунды: интервал опроса меньше паспортного даёт кэш или nan.
- Раскладка на 128×64: подпись размером 1, значение размером 3 — до 7 символов, вторичное — двойкой, до 10.
- Опрос — на millis-таймере: между чтениями loop свободен, экран перерисовывается только по факту нового опроса.
- Чтение DHT небыстрое — сотни миллисекунд на попытку; совать его в каждое прохождение loop нельзя.
- isnan-проверка обязательна: отвалившийся сенсор без неё печатает nan прямо на стекло.
Частые вопросы по теме урока
- Минимальный интервал между чтениями DHT22 по документации:
- Период опроса DHT22 — две секунды: при более частом библиотека отдаст кэш или nan. DHT11 хватало одной секунды; живость сенсора проверяется Serial-тестом — кэш повторяется, живой дрожит в десятых. 2 секунды
- Строка «23.5 C» на размере 3 — сколько она занимает и влезает ли?
- На размере 3 в строке семь ячеек: цифры, точка, пробел и C — шесть. Минусовая строка вида «-19.5 C» займёт все семь. 6 символов из 7 доступных — влезает
- Почему экран не мигает при каждой перерисовке?
- clearDisplay, печать зон, display(): стекло получает готовый кадр одной посылкой — механика разбиралась в уроке про буфер. Кадр собирается в буфере и уходит одной посылкой display()
- Проверка millis() - lastPoll >= 2000 в начале loop — это:
- Неблокирующий таймер: между опросами loop живёт — кнопки, Serial и другие датчики не замерзают, как на delay. Таймер, между срабатываниями которого loop крутится без блокировки
- Сенсор отвалился, readTemperature вернёт nan. Что покажет экран?
- С проверкой isnan экран честно пишет причину, а скетч ждёт следующего опроса; без неё nan попадает прямо на стекло. Ветку с isnan — строку о молчащем сенсоре