Монитор порта привязан к компьютеру шнуром, и это всплывает в самый неподходящий момент. У меня рассада жила в кладовке, а ноутбука там не было и не предвиделось: первое время я носил его туда дважды в день, открывал монитор и переписывал цифры на бумажку. Считать вручную дважды в сутки — значит узнавать о ночных скачках в обед. Через четыре дня я понял, что построил компьютер, который требует человека-курьера, и повесил на стену дисплей.
Прибор без экрана — просто датчик с проводами. LCD 1602 с модулем I2C стоит копейки, показывает обе цифры крупно и не требует ни компьютера, ни интернета, ни батарейки-таблетки. Экран обновляется сам, а человек читает его, проходя мимо, — как и должно быть у настенного термометра.
Почему LCD, а не что-то красивее — честный вопрос. OLED ярче и тоньше, но в два-три раза дороже, а на этой задаче его преимущества не работают: показания меняются раз в секунду, смотреть на них полминуты никто не стоит, и 1602 с жёлто-зелёной подсветкой читается из любого угла кладовки. Я ставлю LCD везде, где нужен ответ «какая цифра», и оставляю OLED проектам с графиками и меню.
Соберём термогигрометр целиком: датчик DHT на пине 2, дисплей по шине I2C на A4/A5, кнопка на пине 3 переключает страницы — цифры и вердикт «в норме или нет». Схема устройства два в одном: термогигрометр и индикатор комфорта из прошлого урока, только вместо светодиодов — строка текста.
Шина I2C: два провода данных
Обычный LCD в четырёхбитном режиме съедает шесть проводов: четыре линии данных, плюс RS, плюс E. Модуль с чипом PCF8574 на обратной стороне экрана превращает его в устройство шины I2C: линия SDA идёт на A4, SCL — на A5, плюс питание и земля. I2C — шина с адресами, на тех же двух проводах может висеть и датчик, и часы, и память — каждому устройству достаётся свой адрес, провода общие.
Типовые адреса модулей — 0x27 и 0x3F, и это первая причина «пустого экрана»: скетч стучится на один адрес, модуль слушает другой. Если подсветка горит, а текста нет, в девяти случаях из десяти виноват адрес — сканер шины I2C находит правильный за десять секунд. Вторая причина — контрастная крутилка на модуле, про неё ниже, в таблице диагнозов.
Проводка в реальности занимает минуту: VCC на 5 В, GND на землю, SDA на A4, SCL на A5 — четыре провода вместо шести-семи у экрана без модуля. До полуметра обычные макетные перемычки работают без вопросов; дальше растёт ёмкость линии, и обмен начинает сыпаться мусорными символами — берите витую пару или короткий отвод. На модуле бывает перемычка подсветки: снял — экран живёт, но текст читается только фонариком.
Из библиотеки нужны три вещи. Конструктор LiquidCrystal_I2C lcd(0x27, 16, 2) — адрес, столбцы, строки. lcd.init() и lcd.backlight() в setup() — старт и включение подсветки, оба зовутся один раз. Если после них на экране появилось хоть что-то, шина жива, и все дальнейшие проблемы — уже про данные, а не про подключение. Полный первый запуск экрана разобран в уроке про первый текст по I2C.
#include <Wire.h>#include <LiquidCrystal_I2C.h>#include "DHT.h" #define DHTPIN 2#define DHTTYPE DHT11#define BTN_PIN 3 DHT dht(DHTPIN, DHTTYPE);LiquidCrystal_I2C lcd(0x27, 16, 2); int page = 0; // 0 — цифры, 1 — вердиктint lastBtn = 1; // подтяжка: разомкнутая кнопка читается как HIGH void setup() { Serial.begin(9600); pinMode(BTN_PIN, INPUT_PULLUP); dht.begin(); lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print("Termo-gigrometr"); Serial.println("start: lcd plus button");} void loop() { // фронт кнопки: ловим переход HIGH -> LOW, а не уровень int now = digitalRead(BTN_PIN); if (lastBtn == 1 && now == 0) { page = page + 1; if (page > 1) page = 0; lcd.clear(); } lastBtn = now; float t = dht.readTemperature(); float h = dht.readHumidity(); lcd.setCursor(0, 0); if (page == 0) { lcd.print("T: "); lcd.print(t, 1); // один знак после запятой lcd.write(223); // глиф градуса в знакогенераторе HD44780 lcd.print("C "); // хвост-пробелы стирают прошлые цифры lcd.setCursor(0, 1); lcd.print("H: "); lcd.print(h, 0); lcd.print("% "); } else { int ok = 1; if (t < 20.0 || t > 26.0 || h < 30.0 || h > 60.0) ok = 0; if (ok == 1) { lcd.print("Zona: v norme"); } else { lcd.print("Zona: vnimanie"); } lcd.setCursor(0, 1); lcd.print("t="); lcd.print(t, 1); lcd.print(" h="); lcd.print(h, 0); lcd.print(" "); } Serial.print("page="); Serial.println(page); Serial.print("t="); Serial.println(t); delay(1000);}Пройдём по экранной части. lcd.setCursor(столбец, строка) ставит курсор: нумерация с нуля, верхняя строка — нулевая, экран 1602 — это 16 столбцов на 2 строки. Температура на верхней строке, влажность на нижней — пара, которую глаз находит мгновенно. lcd.print(t, 1) печатает float с одним знаком после запятой, для влажности хватает целых — lcd.print(h, 0). Подробнее про форматы печати чисел на этом экране — в уроке про числа, дроби и нули на LCD, там же про то, почему 23,50 иногда печатается как 23,49.
Кнопка работает через подтяжку: pinMode(BTN_PIN, INPUT_PULLUP), разомкнутая кнопка читается как HIGH, нажатая — как LOW. Переключаю страницы по фронту: переменная lastBtn хранит прошлое чтение, страница листается только на переходе 1→0, а не пока держишь. При опросе раз в секунду дребезг успевает затухнуть между чтениями — debounce из урока про кнопку понадобится, когда опрос станет чаще сотни раз в секунду.
Вторая страница — вердикт по зонам: 20–26 °C и 30–60 %, те же границы, что мы обсуждали в уроке про индикацию комфорта. При смене страницы вызывается lcd.clear() — иначе вердикт напечатается поверх старой строки и получилась бы каша из двух текстов. Одной clear на переключение достаточно: цифры внутри страницы всегда перепечатываются целиком.
А зачем в скетче Serial, если экран уже показывает всё? Для вас. Строки page= и t= в мониторе — журнал прибора: видно, когда страница переключилась и какое число прибор посчитал в тот момент, когда вы смотрели в другую сторону. На настенном приборе без компьютера этот блок никому не мешает, а на столе с подключённым портом экономит те самые полчаса угадывания.
Что покажет симулятор
В симуляторе собрана та же схема: датчик на D2, кнопка на D3, дисплей рисуется по-настоящему — каждый setCursor и print ложатся в сетку 16 на 2, как на реальном HD44780. Крутите ползунки температуры и влажности над макеткой: цифры поедут с шагом опроса раз в секунду, и в десятых долях будет заметна лёгкая дрожь — это честный шум сенсора, у настоящего DHT так же.
Нажмите кнопку — страница сменится, вердикт пересчитается по порогам: уведите температуру за 26 и вторая страница перестанет говорить «в норме». Влажность меняется — вердикт пересчитается по своим порогам 30 и 60 %. Символ градуса печатается через lcd.write(223) — это код глифа в знакогенераторе экрана, на реальном дисплее работает точно так же. Обратите внимание на монитор рядом: строки page= и t= идут в такт экрану, и переключение страницы видно в журнале раньше, чем глазом.
| Симптом на экране | Причина | Лечение |
|---|---|---|
| Подсветка горит, текста нет | скетч стучится не на тот адрес: 0x27 вместо 0x3F или наоборот | сканер I2C найдёт адрес за 10 секунд |
| Две строки чёрных квадратов | не выставлен контраст или он в нуле | крутилка на модуле I2C, крутить при работающем экране |
| Мусорные символы вместо текста | перепутаны SDA и SCL или провода длиннее полуметра | поменять местами, укоротить провода |
| Хвост прошлой цифры после числа | print пишет поверх и не стирает | хвост из пробелов после единицы измерения |
| nan вместо числа | опрос чаще 1 Гц или нет подтяжки на линии данных | delay(1000) в loop, резистор 10 кОм на голом сенсоре |
Про честность прибора. DHT11 пересчитывает показания внутри корпуса примерно раз в секунду, DHT22 — раз в две. Обновлять экран чаще — печатать одно и то же число по кругу: я ставлю delay(1000) для DHT11 и delay(2000) для DHT22, и это потолок живости экрана, а не лень. Заменить датчик — одна строка DHTTYPE, всё остальное переезжает без правок, мы это делали при первом подключении DHT.
Питание узла скромное: подсветка дисплея ест около 25 мА, датчик в опросе — единицы миллиампер. Любая зарядка на 5 В или старый USB-блок тянут конструкцию с запасом, но на тонком длинном кабеле первой проседает подсветка: экран жив, а буквы тускнеют. Для настенной версии берите короткий кабель и аккуратную коробку — и помните, что A4 и A5 уже заняты шиной: следующий I2C-датчик вешается на те же два провода, а не на другие пины.
- 1. Сколько проводов данных тянет LCD 1602 с модулем I2C (без учёта питания и земли)?
- 2. Подсветка горит, но текста на экране нет. Что проверить первым?
- 3. На экране осталось 9.55 °C после того, как температура упала с 23.5 до 9.5. Откуда хвост?
- 4. Зачем в скетче переменная lastBtn рядом с digitalRead кнопки?
- 5. Что делает второй аргумент в lcd.print(t, 1)?
- LCD 1602 по I2C: SDA на A4, SCL на A5, адрес 0x27 или 0x3F; init и backlight зовутся один раз в setup.
- Перед каждой печатью setCursor(столбец, строка); нумерация с нуля, экран — 16 столбцов на 2 строки.
- lcd.print не стирает: короткое число оставляет хвост — добивайте пробелами после единицы измерения.
- Страницы листаются по фронту кнопки через lastBtn; при опросе раз в секунду дребезг не мешает.
- DHT11 обновляет данные раз в секунду, DHT22 — раз в две; чаще экран не оживить никаким кодом.
Термогигрометр уже полезен, но экран — только витрина. Хотите, чтобы он ещё и управлял теплом, — добавьте реле и гистерезис из урока про термостат: пороги у вас уже есть. Если пойдёте дальше по экранной линии, подключение LiquidCrystal_I2C закроет вопрос подключения в любых вариантах.
Частые вопросы по теме урока
- Сколько проводов данных тянет LCD 1602 с модулем I2C (без учёта питания и земли)?
- Модуль с PCF8574 сжимает обмен до шины I2C: SDA на A4 и SCL на A5. Внутри чип сам мультиплексирует восемь линий экрана в два провода. Шесть проводов нужны экрану без модуля. Два: SDA и SCL
- Подсветка горит, но текста на экране нет. Что проверить первым?
- Рабочая подсветка означает, что питание подано. Пустой текст почти всегда — промах по адресу, сканер I2C находит верный. Адрес модуля: 0x27 или 0x3F
- На экране осталось 9.55 °C после того, как температура упала с 23.5 до 9.5. Откуда хвост?
- Печать идёт посимвольно поверх сетки. Более короткое число оставляет чужие цифры справа — лечится хвостом из пробелов после единицы измерения. lcd.print пишет поверх старых символов и не стирает
- Зачем в скетче переменная lastBtn рядом с digitalRead кнопки?
- Сравнение прошлого и текущего чтения выделяет момент нажатия. Иначе страница листалась бы каждую секунду, пока кнопка держится. При опросе раз в секунду дребезг успевает затухнуть между чтениями — грубость здесь работает на нас. Чтобы ловить фронт нажатия — переход HIGH в LOW, а не держание уровня
- Что делает второй аргумент в lcd.print(t, 1)?
- Для float второй аргумент — число десятичных знаков. lcd.print(t, 1) даст 23.5, lcd.print(t, 0) — целое 24. На двух строках экрана два знака после запятой уже едятся. Оставляет один знак после запятой