Зачем эта поделка, а не «ещё один blink»
Первые три статьи блога разбирали выбор чипа, питание и маркировку резисторов по отдельности. Четвёртая учила читать номинал с корпуса. Настало время собрать это в одно устройство, которое реально стоит на столе и которое вы будете смотреть каждый день, а не один раз прошить и убрать в ящик.
Получается настольный термогигрометр: датчик измеряет температуру и влажность, маленький OLED‑дисплей показывает их крупными цифрами, а последовательный порт дублирует показания для отладки. Проект намеренно сделан на Arduino Uno/Nano, потому что это самый доступный «первый контроллер», но в конце покажу, почему ровно эта же поделка естественно переезжает на ESP32‑S3, если захотеть телеметрию в сеть.
Главная ценность здесь не в «ещё одном скетче», а в том, что в схеме осознанно стоят те самые компоненты, о которых мы говорили раньше: подтягивающие резисторы, чьи коды вы теперь умеете читать, и развязывающие конденсаторы, без которых дисплей и датчик ведут себя как «плавающий» баг.
Что понадобится
Список компонентов — ровно из того, что лежит в магазине радиодеталей, без экзотики:
- Плата Arduino Uno или Nano (или совместимая; для Mega пины I2C другие, см. карту ниже).
- Датчик температуры и влажности DHT22 (он же AM2302) — точнее DHT11, о различиях ниже. DHT11 тоже подойдёт, если точность не критична.
- OLED‑модуль на контроллере SSD1306, разрешение 128×64, интерфейс I2C.
- Резистор 4.7 кОм или 10 кОм для подтяжки линии DATA датчика (если на модуле датчика нет встроенной подтяжки).
- Резисторы 4.7 кОм для подтяжки SDA и SCL шины I2C (если на модуле дисплея нет встроенных подтяжек).
- Керамический конденсатор 100 нФ (0.1 мкФ) для развязки датчика и ещё один для развязки дисплея.
- Макетная плата или небольшая паяная плата, провода, источник питания 5 В (USB) или 3.3 В логика — см. нюанс про уровни ниже.
Коды подтягивающих резисторов, которые вы будете читать с корпуса: 4.7 кОм в трёхзначном формате — это 472 (47 × 10²), 10 кОм — это 103 (10 × 10³). Если резистор прецизионный и код четырёхзначный, 4.7 кОм будет 4701, а 10 кОм — 1002. Это ровно те правила разбора, которые разбирались в статье про маркировку SMD‑резисторов, — здесь они применяются не в теории, а в реальной пайке.
Карта подключений
Ниже — текстовая карта пинов, чтобы не держать в голове таблицу. Для Uno/Nano шина I2C живёт на аналоговых пинах A4 (SDA) и A5 (SCL); на Mega это пины 20 (SDA) и 21 (SCL); на ESP32 — назначаются программно.
Arduino Uno/Nano -> DHT22 (AM2302)
3.3V -> VCC (или 5V, см. нюанс про уровни)
GND -> GND
D2 -> DATA
между DATA и VCC -> подтягивающий резистор 4.7-10 кОм (если нет onboard)
между VCC и GND -> керамика 100 нФ вплотную к датчику
Arduino Uno/Nano -> OLED SSD1306 128x64 (I2C)
3.3V -> VCC (или 5V, но держите единое напряжение логики)
GND -> GND
A4 (SDA) -> SDA
A5 (SCL) -> SCL
SDA -> VCC и SCL -> VCC -> подтяжки 4.7 кОм (если нет onboard на модуле)
между VCC и GND -> керамика 100 нФ вплотную к модулюПочему я рекомендую питать модули от 3.3 В на Uno/Nano: логические уровни I2C совпадут с напряжением питания модуля, и не придётся гадать, терпит ли конкретный SSD1306 5 В на линиях при 3.3 В питании. Многие модули это терпят на практике, но точные допуски по уровням — в даташите вашего модуля, и упрощать себе отладку единообразием напряжений дешевле, чем проверять толерантность каждого экземпляра.
Нюансы датчика DHT, о которых молчат туториалы
DHT‑семейство капризно к таймингам и к обвязке, и именно здесь в статью про питание и про резисторы встаёт во весь рост.
Подтягивающий резистор на линии DATA обязателен: протокол датчика — open‑drain‑подобный, и без подтяжки к VCC линия «висит», чтения плывут или не работают вовсе. Типовое значение — 4.7–10 кОм. Важный момент экономии нервов: многие готовые модули DHT22 (не голый датчик, а плата с тремя выводами) уже имеют встроенную подтяжку. Тогда внешний резистор не нужен, и паивать второй — значит ставить параллельно два резистора и менять эффективное сопротивление; проверьте, есть ли onboard‑подтяжка на вашем модуле, прежде чем паять.
Развязывающий конденсатор 100 нФ между VCC и GND вплотную к датчику — это ровно та «керамика рядом с чипом» из статьи про питание: она держит напряжение на пинах датчика в моменты, когда он активен, и не даёт коротким просадкам испортить чтение. Один большой электролит вдали от датчика эту роль не сыграет.
Частота опроса: DHT нельзя опрашивать чаще, чем примерно раз в 2 секунды (0.5 Гц). Более частые опросы дают невалидные данные или зависания, потому что датчику нужно время на внутренний АЦП и стабилизацию. В скетче ниже я использую паузу 2 секунды именно поэтому, а не «для красоты».
Разница DHT11 и DHT22 в цифрах — типовые значения из даташитов производителя, а не гарантия для конкретного экземпляра, и перед покупкой партии сверяйтесь с документом на неё:
- DHT11: температура примерно 0…50 °C с точностью порядка ±2 °C; влажность примерно 20…90 % RH с точностью порядка ±5 % RH; питание примерно 3.3…5.5 В.
- DHT22 (AM2302): температура примерно −40…80 °C с точностью порядка ±0.5 °C; влажность примерно 0…100 % RH с точностью порядка ±2…5 % RH; питание примерно 3.3…5 В.
Для настольной станции DHT22 заметнее приятнее: и диапазон шире, и по температуре точнее вдвое‑втрое. DHT11 имеет смысл, если бюджет жмёт и точность не важна.
Нюансы дисплея и шины I2C
SSD1306 по I2C — удобный дисплей, но у него есть два подводных камня, которые ломают «первый запуск».
Первый — адрес. Большинство модулей 128×64 имеют I2C‑адрес 0x3C, но встречаются и 0x3D, и на некоторых модулях адрес выбирается перемычкой/резистором. Если скетч «не видит» дисплей (в Serial пишется, что SSD1306 не найден), первым делом попробуйте второй адрес, а не перепаивайте всё.
Второй — подтяжки шины. I2C требует pull‑up на SDA и SCL к шине питания. На большинстве готовых OLED‑модулей они уже распаяны (обычно около 4.7 кОм), и дополнительно ставить не нужно. Но если модуль без них, или шина длинная/на навесном монтаже, или вы питаете логику не тем напряжением, — без подтяжек шина не работает. Это снова те же 4.7 кОм = 472, что и у датчика, и правило чтения кода из статьи про SMD применяется повторно.
Потребление и пики: модуль SSD1306 при включении и при обновлении кадра даёт короткие скачки тока. Если он сидит на одной шине с датчиком и платой без развязки, просадка может проявиться как зависание дисплея или рестарт платы в момент обновления экрана — ровно тот класс «плавающих» глюков, который разбирается в статье про питание. Поэтому второй конденсатор 100 нФ вплотную к VCC/GND модуля дисплея — не паранойя, а дешёвая страховка.
Библиотеки: что ставить и почему именно эти
Проект использует четыре библиотеки, все — от Adafruit, все ставятся через менеджер библиотек Arduino IDE (Sketch → Include Library → Manage Libraries), и это основной и самый надёжный путь установки, потому что менеджер подтянет зависимости и покажет актуальные версии:
- DHT sensor library (Adafruit) — драйвер датчика.
- Adafruit Unified Sensor — базовый слой, от которого зависит DHT‑библиотека; менеджер обычно предложит поставить её автоматически.
- Adafruit GFX Library — графическое ядро для дисплея.
- Adafruit SSD1306 — драйвер конкретного контроллера дисплея.
Первоисточники кода этих библиотек — репозитории Adafruit на GitHub: DHT sensor library, Adafruit Unified Sensor, Adafruit GFX Library, Adafruit SSD1306. Но ставить я рекомендую именно через менеджер IDE, а не клонировать руками: так вы гарантированно получите согласованные версии и зависимости, а не смесь из разных ревизий.
Отдельная честная рамка: скетч ниже написан по стабильному API этих библиотек (конструктор дисплея с четырьмя аргументами, begin с SSD1306_SWITCHCAPVCC и адресом, readHumidity/readTemperature у DHT, проверка на isnan). Этот API держится годами, но библиотеки развиваются, и если после обновления версии что‑то не скомпилируется — первым делом откройте примеры из установленной библиотеки (File → Examples → Adafruit SSD1306 → ssd1306128x64i2c и Adafruit DHT → dhttest) и сверьте сигнатуры с моим кодом, а не правьте схему. Я не утверждаю, что скетч переживёт любую будущую мажорную версию без изменений; я утверждаю, что он корректен для текущего стабильного API и повторяет структуру официальных примеров.
Скетч целиком
Ниже — полный скетч. Скопируйте в Arduino IDE, выберите свою плату и порт, зашейте. Он читает DHT22 на пине 2, инициализирует SSD1306 по адресу 0x3C, рисует две крупные строки (температура и влажность) и дублирует в Serial. Пауза 2 секунды — из‑за ограничения частоты опроса DHT, о котором выше.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHT.h>
// --- Датчик ---
#define DHTPIN 2
#define DHTTYPE DHT22 // для DHT11 замените на DHT11
DHT dht(DHTPIN, DHTTYPE);
// --- Дисплей ---
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1 // -1, если пин сброса не подключён
#define SSD1306_I2C_ADDR 0x3C // если дисплей молчит, попробуйте 0x3D
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// --- Период опроса ---
// DHT нельзя опрашивать чаще ~0.5 Гц (раз в 2 секунды).
const unsigned long READ_INTERVAL_MS = 2000;
unsigned long lastReadMs = 0;
void setup() {
Serial.begin(115200);
dht.begin();
if (!display.begin(SSD1306_SWITCHCAPVCC, SSD1306_I2C_ADDR)) {
Serial.println(F("SSD1306 не найден. Проверьте wiring, адрес (0x3C/0x3D) и подтяжки I2C."));
for (;;); // стоп: без дисплея показывать нечего
}
// Заставка
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println(F("Radio Store"));
display.println(F("Thermo-Hygrometer"));
display.display();
delay(1000);
}
void loop() {
unsigned long now = millis();
if (now - lastReadMs < READ_INTERVAL_MS) {
return; // не спим busy-wait, но и не опрашиваем DHT чаще нормы
}
lastReadMs = now;
float h = dht.readHumidity();
float t = dht.readTemperature(); //摄氏度; для Фаренгейта readTemperature(true)
if (isnan(h) || isnan(t)) {
Serial.println(F("Ошибка чтения DHT: проверьте подтяжку DATA, развязку 100 нФ и частоту опроса."));
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.println(F("DHT read error"));
display.display();
return;
}
Serial.print(F("T="));
Serial.print(t, 1);
Serial.print(F(" C RH="));
Serial.print(h, 1);
Serial.println(F(" %"));
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(2);
display.setCursor(0, 4);
display.print(t, 1);
display.print(F(" C"));
display.setCursor(0, 30);
display.print(h, 1);
display.print(F(" %RH"));
display.setTextSize(1);
display.setCursor(0, 54);
display.print(F("Arduino + DHT22 + SSD1306"));
display.display();
}Разбор ключевых мест, чтобы код не был «чёрным ящиком»:
if (now - lastReadMs < READ_INTERVAL_MS) return;— неблокирующий таймер наmillis(). В учебном проекте можно было бы написатьdelay(2000), ноdelayблокирует всё остальное; привычка наmillis()сразу приучает к тому, как делают реальные устройства, где параллельно нужно ещё и кнопку читать, и сеть обслужить.isnan(h) || isnan(t)— обязательная проверка. DHT при проблемах с обвязкой возвращаетNaN, и без проверки вы бы рисовали «nan» на дисплее и не поняли, что дело в подтяжке или конденсаторе, а не в коде.for (;;)после неудачногоdisplay.begin— намеренный стоп: если дисплея нет, крутить цикл чтения бессмысленно, и лучше сразу сообщить в Serial причину, чем показывать пустой экран.- Комментарий про
readTemperature(true)— переключение в Фаренгейты одной буквой; полезно, если станция для аудитории, где не метрическая система.
Калибровка и самонагрев: почему «врет» и что с этим делать
Даже correctly собранный DHT22 может показывать температуру чуть выше реальной, и это не бап пайки. Причина — самонагрев: датчик потребляет ток, и часть этого тока выделяется на самом чувствительном элементе, особенно при питании 5 В и частом опросе. Величина эффекта зависит от условий, корпуса, продуваемости и напряжения питания; в сети и в заметках производителя встречается оценка порядка долей градуса — единицы градусов при 5 В постоянном опросе, но это ориентир, а не константа: для вашего экземпляра и вашего режима питания измерьте сравнением с заведомо точным термометром.
Что с этим делают на практике:
- Питают DHT22 от 3.3 В вместо 5 В — самонагрев меньше.
- Опрашивают редко (наша пауза 2 секунды уже ближе к нижней границе; для «часов на столе» можно и 5–10 секунд, датчик инерционный).
- Вводят программную поправку‑смещение, измеренную сравнением с эталоном, а не «на глаз».
- Не герметизируют датчик вплотную к нагревающимся элементам платы (стабилизатору, DC‑DC), иначе греется не только сам датчик.
Влажность тоже имеет свойство «плыть» после длительного хранения в сухом/влажном environment; для бытовой станции это несущественно, для чего‑то критичного — калибруют по эталонному гигрометру.
Расширения: куда расти из этой поделки
Проект сознательно сделан минимальным, но он хорошо расширяется, и каждое расширение снова цепляет предыдущие статьи:
- Перенос «мозга» на ESP32‑S3. Тот же датчик и тот же дисплей, но с Wi‑Fi: показания улетают в домашний сервер/облако, а станция становится сетевым устройством. При переходе помните про питание: ESP32‑S3 в моменты радиоактивности даёт пики тока, и статья про питание объясняет, почему нельзя просто воткнуть его в тот же слабый 3.3 В без развязки. Разбор отличий S3 от других чипов — в статье про ESP32‑S3.
- Часы реального времени (RTC) рядом. Если захотите, чтобы станция знала время и логировала показания с меткой времени, добавляют I2C‑RTC; шина уже разведена, подтяжки общие, и это хороший урок по тому, как несколько устройств делят одну I2C‑шину по разным адресам.
- Логирование на SD/флеш. Для «а как вела себя влажность ночью» добавляют модуль SD (SPI, отдельные пины) или пишут в память ESP. Тут снова вступает в силу тема питания и развязки: кардридеры и Wi‑Fi любят пики.
- Телеметрия и «умный дом». Если цель — не дисплей, а данные в систему автоматизации, ESP32‑S3 с Wi‑Fi/BLE или мультипротокольный чип из статьи про новинки процессоров становятся естественным следующим шагом, а дисплей превращается в опциональную локальную индикацию.
Чек-лист сборки перед первым включением
Прогоните это по порядку — половина «не работает» ловится здесь, а не в коде:
- Прочитали номиналы подтягивающих резисторов с корпуса по правилам из статьи про SMD (4.7 кОм =
472, 10 кОм =103), убедились, что не перепутали 100 Ом и 10 Ом. - Проверили, есть ли onboard‑подтяжка на модуле датчика и на модуле дисплея, и не поставили двойную подтяжку там, где она уже есть.
- Поставили по керамике 100 нФ вплотную к VCC/GND датчика и дисплея, дорожки короткие.
- Держите единое напряжение логики и питания модулей (для Uno/Nano безопаснее 3.3 В на обоих модулях), чтобы не гадать про толерантность уровней.
- Выбрали правильный адрес дисплея (0x3C, при неудаче 0x3D) и правильные пины I2C для вашей платы (Uno/Nano — A4/A5).
- Не опрашиваете DHT чаще раза в 2 секунды.
- Перед прошивкой сверили, что в менеджере библиотек стоят DHT sensor library, Adafruit Unified Sensor, Adafruit GFX и Adafruit SSD1306, и что скетч компилируется с примерами из этих же версий.
- При «странной» температуре вспомнили про самонагрев DHT22 на 5 В и про поправку‑смещение по эталону, а не стали искать баг в коде.
Итог
Эта поделка ценна не тем, что показывает температуру, а тем, что в ней впервые сходятся все четыре предыдущие темы сразу: вы читаете коды подтягивающих резисторов с корпуса, ставите развязку там, где объясняли просадки, уважаете тайминги датчика и понимаете, почему дисплей «глючит» без конденсатора, а не из‑за прошивки. Arduino здесь — удобная точка входа, но логика схемы (подтяжки, развязка, частоты, уровни) переносится на любой контроллер один в один, и когда вы решите добавить Wi‑Fi, вы уже будете знать, куда смотреть в питании и в выборе чипа.
Дальше по цепочке: если захотите «мозг» с радио для телеметрии — начните со статьи ESP32-S3: двухъядерный контроллер с Wi-Fi, Bluetooth 5 LE и встроенным USB и загляните в Новинки процессоров: RISC-V в микроконтроллерах, мультипротокольность и NPU на краю за пониманием, какие классы чипов бывают. Перед пайкой и отладкой обязательно перечитайте Питание ESP32 и ESP32-S3: почему модуль молча перезагружается и как это исправить — именно питание объясняет 80% «плавающих» отказов в таких сборках. А чтобы не перепутать номинал подтяжки при заказе и пайке — Маркировка SMD-резисторов: как читать номинал с корпуса и не перепутать 100 с 10 Ом.
Официальные ориентиры по протоколу и обвязке DHT, по контроллеру SSD1306 и по API библиотек — в даташитах производителей компонентов и в примерах/документации самих библиотек Adafruit (репозитории выше); конкретные точности, диапазоны, адреса модулей и наличие onboard‑подтяжек всегда сверяйте с документом на вашу партию, а не по общей статье.
