Термогигрометр с OLED-дисплеем на Arduino: собираем поделку, которая использует всё, что мы уже разобрали

Термогигрометр с OLED-дисплеем на Arduino: собираем поделку, которая использует всё, что мы уже разобрали

Зачем эта поделка, а не «ещё один 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‑подтяжек всегда сверяйте с документом на вашу партию, а не по общей статье.