Курс Arduino с нуля · Индикаторы и экраны
Урок 28. Проект: домашняя метеостанция
Соединяем экран и датчик в самостоятельный прибор: значки-символы, честный знак вопроса при сбое и память рекордов. В конце — фокус: станция работает от зарядки телефона, без компьютера.
Что понадобится
- Плата Uno из набора
- Кабель USB
- Макетная плата
- Экран LCD1602
- Датчик температуры и влажности DHT11
- Потенциометр 10 кОм
- Резистор 10 кОм
- Тактовая кнопка
- 22 провода «папа-папа»
Все детали есть в обучающем наборе, по которому идёт курс.
Два прошлых урока дали нам всё, что нужно: экран умеет показывать текст, датчик знает температуру и влажность. Сегодня соединим их и соберём настоящую метеостанцию — прибор, который живёт на столе сам по себе, без компьютера.
Новых деталей не будет. Будет другое: первый раз мы собираем устройство из двух готовых узлов и разбираемся с тем, что при этом вылезает — детали начинают спорить за выводы платы.
Что построим
План такой, как в любом взрослом приборе:
- измерить — раз в две секунды спросить датчик;
- проверить и запомнить — не поверить сбойному ответу и обновить рекорды;
- показать — красиво вывести всё на экран.
Это тот же скелет «собрать данные → решить → показать», что был в умном ночнике из урока 18. Он подходит почти для любого устройства.
Кто на каком выводе: детали спорят за место
В уроке 27 датчик сидел на выводе 2. Но теперь вывод 2 занят: на нём один из шести проводов данных экрана. Если оставить датчик на месте, экран и датчик будут дёргать один и тот же вывод — не заработает ни тот, ни другой.
Поэтому датчик переезжает на свободный вывод 8. Ему всё равно, где жить, — библиотека работает с любым выводом, поменяется одна цифра в программе: iarduino_DHT sensor(8);
Запомните этот приём: когда деталей становится много, первым делом составьте список — кто какой вывод занимает. Экран: 12, 11, 5, 4, 3, 2. Датчик: 8. Свободны: 6, 7, 9, 10, 13.
Собираем схему
Если схема урока 26 у вас ещё собрана — не разбирайте, мы только добавим датчик. Отключите USB и дальше по шагам.
- Проверьте узлы из урока 26: экран в нижнем ряду, потенциометр контраста, питание макетки с платы — 5V на «+» верхней шины, GND на «−».
- Вставьте датчик решёткой к себе в ряд
a, выводы в столбики 25, 26, 27 и 28. - Столбик 25 (вывод 1 датчика) — проводом на «+», столбик 28 (вывод 4) — на «−». Столбик 27 остаётся пустым.
- Резистор 10 кОм — через канавку: одна ножка в столбик 26 сверху, вторая в столбик 26 снизу.
- От нижней ножки резистора — провод в столбик 17. Хитрость: этот столбик уже соединён с «+» (по нему питается подсветка экрана), так что тянуться к шине не нужно.
- И провод данных: из столбика 26 сверху — на вывод 8 платы.
Проверьте перед включением: решётка датчика смотрит на вас; крайний левый вывод на «+», крайний правый на «−»; резистор соединяет столбик 26 с плюсовым столбиком 17; провод данных идёт со столбика 26 на вывод 8; и все провода экрана на месте — их удобно пересчитать по схеме урока 26.
Шаг 1. Сначала — датчик отдельно
Правило из урока про ночник: собрал схему — сначала проверь каждый узел по отдельности. Экран мы проверим старой программой из урока 26 (например, «Privet!»), а для датчика на новом месте — короткая проверка через монитор порта.
// Урок 28. Проверяем датчик на новом месте — выводе 8.
#include <iarduino_DHT.h>
iarduino_DHT sensor(8);
void setup() {
Serial.begin(9600);
delay(1000);
}
void loop() {
if (sensor.read() == DHT_OK) {
Serial.print("Температура: ");
Serial.print(sensor.tem, 1);
Serial.print(" C, влажность: ");
Serial.print(sensor.hum, 0);
Serial.println(" %");
} else {
Serial.println("Датчик не ответил");
}
delay(2000);
}
Если в мониторе идут температура и влажность — датчик переехал удачно. Если «Датчик не ответил» — провод данных не на выводе 8 или забыта подтяжка. Не идите дальше, пока этот шаг не заработает: искать ошибку в целой станции гораздо труднее, чем в одном узле.
Шаг 2. Числа появляются на экране
// Урок 28. Температура и влажность появляются на экране.
#include <LiquidCrystal.h>
#include <iarduino_DHT.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
iarduino_DHT sensor(8);
void setup() {
lcd.begin(16, 2);
lcd.print("Meteo start");
delay(1000);
}
void loop() {
if (sensor.read() == DHT_OK) {
lcd.setCursor(0, 0);
lcd.print("Temp: ");
lcd.print(sensor.tem, 0);
lcd.print(" C ");
lcd.setCursor(0, 1);
lcd.print("Vlaga: ");
lcd.print(sensor.hum, 0);
lcd.print(" % ");
}
delay(2000);
}
Всё знакомо: подключены обе библиотеки, у экрана свои команды, у датчика свои. Обратите внимание на lcd.print(sensor.tem, 0) — ноль в конце значит «без цифр после точки». Датчик всё равно меряет целыми градусами, так что мы ничего не теряем.
Хвосты из пробелов после чисел — приём из урока 26: они стирают следы старых, более длинных чисел.
Шаг 3. Значки и настоящий знак градуса
«Temp» и «Vlaga» — это работает, но некрасиво. Сделаем как у настоящих приборов: значок термометра, значок капли и правильный знак градуса.
// Урок 28. Значки: термометр, капля и настоящий знак градуса.
#include <LiquidCrystal.h>
#include <iarduino_DHT.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
iarduino_DHT sensor(8);
byte gradusnik[8] = {
B00100,
B01010,
B01010,
B01110,
B01110,
B11111,
B11111,
B01110
};
byte kaplya[8] = {
B00100,
B00100,
B01010,
B01010,
B10001,
B10001,
B10001,
B01110
};
void setup() {
lcd.begin(16, 2);
lcd.createChar(0, gradusnik);
lcd.createChar(1, kaplya);
lcd.setCursor(0, 0); // после createChar обязательно вернуть курсор
}
void loop() {
if (sensor.read() == DHT_OK) {
lcd.setCursor(0, 0);
lcd.write((byte)0); // наш термометр
lcd.print(" ");
lcd.print(sensor.tem, 0);
lcd.write((byte)223); // знак градуса из памяти экрана
lcd.print("C ");
lcd.setCursor(0, 1);
lcd.write((byte)1); // наша капля
lcd.print(" ");
lcd.print(sensor.hum, 0);
lcd.print("% ");
}
delay(2000);
}
- Термометр и каплю мы рисуем сами по точкам через
createChar— как сердечко в уроке 26. ПослеcreateCharне забудьтеsetCursor, иначе печать уйдёт не туда. - А вот знак градуса рисовать не пришлось: он уже есть в памяти экрана под номером 223. Команда
lcd.write((byte)223)печатает его в текущую клетку.
Теперь на экране «🌡 23°C» и «💧 41%» — почти как у покупной станции.
Шаг 4. Станция не должна врать
Что будет, если датчик разок не ответит? В программе выше — ничего страшного: числа на экране просто останутся старыми. Плохо то, что мы об этом не узнаем: станция показывает «23», а на самом деле молчит уже полчаса.
Честный прибор сознаётся, что данные устарели. Заведём в правом верхнем углу знак вопроса: сбой — знак горит, всё наладилось — гаснет. Заодно уберём delay и перейдём на часы millis, как в уроке 27, — скоро плате понадобится успевать и другое.
// Урок 28. Станция не врёт: при сбое в углу загорается знак вопроса.
#include <LiquidCrystal.h>
#include <iarduino_DHT.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
iarduino_DHT sensor(8);
byte gradusnik[8] = {B00100, B01010, B01010, B01110, B01110, B11111, B11111, B01110};
byte kaplya[8] = {B00100, B00100, B01010, B01010, B10001, B10001, B10001, B01110};
unsigned long lastRead = 0;
void setup() {
lcd.begin(16, 2);
lcd.createChar(0, gradusnik);
lcd.createChar(1, kaplya);
lcd.setCursor(0, 0);
lcd.print("Meteo start");
}
void loop() {
if (millis() - lastRead >= 2000) {
lastRead = millis();
if (sensor.read() == DHT_OK) {
showWeather();
lcd.setCursor(15, 0);
lcd.print(" "); // всё хорошо — гасим знак вопроса
} else {
lcd.setCursor(15, 0);
lcd.print("?"); // сбой: показания на экране старые
}
}
}
void showWeather() {
lcd.setCursor(0, 0);
lcd.write((byte)0);
lcd.print(" ");
lcd.print(sensor.tem, 0);
lcd.write((byte)223);
lcd.print("C ");
lcd.setCursor(0, 1);
lcd.write((byte)1);
lcd.print(" ");
lcd.print(sensor.hum, 0);
lcd.print("% ");
}
Вывод на экран переехал в свою функцию showWeather — дальше она пригодится ещё раз. Проверьте знак вопроса: выньте провод данных из макетки — через пару секунд в углу загорится «?», вставьте обратно — погаснет.
Шаг 5. Рекорды и вторая страница
Осталось самое приятное. Станция будет помнить самую низкую и самую высокую температуру с момента включения — приём из задания урока 27: первый удачный замер задаёт оба рекорда, дальше они только улучшаются. И раз в четыре секунды экран будет листать страницы: погода — рекорды — погода…
// Урок 28. Метеостанция целиком: рекорды и две страницы экрана.
#include <LiquidCrystal.h>
#include <iarduino_DHT.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
iarduino_DHT sensor(8);
byte gradusnik[8] = {B00100, B01010, B01010, B01110, B01110, B11111, B11111, B01110};
byte kaplya[8] = {B00100, B00100, B01010, B01010, B10001, B10001, B10001, B01110};
float lowest = 0;
float highest = 0;
bool haveNumbers = false; // был ли уже удачный замер
bool showRecords = false; // какая страница на экране
unsigned long lastRead = 0;
unsigned long lastFlip = 0;
void setup() {
lcd.begin(16, 2);
lcd.createChar(0, gradusnik);
lcd.createChar(1, kaplya);
lcd.setCursor(0, 0);
lcd.print("Meteo start");
}
void loop() {
unsigned long now = millis();
if (now - lastRead >= 2000) {
lastRead = now;
if (sensor.read() == DHT_OK) {
if (!haveNumbers) {
lowest = sensor.tem; // первый замер задаёт оба рекорда
highest = sensor.tem;
haveNumbers = true;
}
if (sensor.tem < lowest) {
lowest = sensor.tem;
}
if (sensor.tem > highest) {
highest = sensor.tem;
}
drawScreen();
lcd.setCursor(15, 0);
lcd.print(" ");
} else {
lcd.setCursor(15, 0);
lcd.print("?");
}
}
if (haveNumbers && now - lastFlip >= 4000) {
lastFlip = now; // раз в 4 секунды листаем страницу
showRecords = !showRecords;
lcd.clear();
drawScreen();
}
}
void drawScreen() {
if (showRecords) {
lcd.setCursor(0, 0);
lcd.print("Min ");
lcd.print(lowest, 0);
lcd.write((byte)223);
lcd.print("C ");
lcd.setCursor(0, 1);
lcd.print("Max ");
lcd.print(highest, 0);
lcd.write((byte)223);
lcd.print("C ");
} else {
lcd.setCursor(0, 0);
lcd.write((byte)0);
lcd.print(" ");
lcd.print(sensor.tem, 0);
lcd.write((byte)223);
lcd.print("C ");
lcd.setCursor(0, 1);
lcd.write((byte)1);
lcd.print(" ");
lcd.print(sensor.hum, 0);
lcd.print("% ");
}
}
Здесь всё уже знакомое, только собрано вместе:
- два будильника на
millis: один раз в 2 секунды спрашивает датчик, другой раз в 4 секунды листает страницу; - рисованием занята одна функция
drawScreen— она смотрит наshowRecordsи выводит нужную страницу; - при смене страницы —
lcd.clear(), чтобы от прошлой не осталось следов.
И главный фокус напоследок. Выдерните USB из компьютера и воткните в зарядку от телефона или повербанк. Станция оживёт сама: программа записана в плату насовсем, компьютер ей больше не нужен. Поставьте её на полку — пусть работает.
Не работает? Разбираемся
- Экран светится, но пустой — первым делом покрутите потенциометр контраста. Это лечит большинство «поломок» экрана.
- Датчик молчал ещё в шаге 1 — провод данных не в столбике 26 или не на выводе 8; проверьте и подтяжку: резистор должен соединять столбик 26 со столбиком 17.
- Вместо значков — кракозябры — «рисунки» символов 0 и 1 не загружены: проверьте, что оба
createCharстоят вsetupдо первой печати. - Вместо знака градуса — другой символ — встречаются экраны с другой таблицей знаков. Не беда: нарисуйте градус сами через
createChar, как термометр. - В углу горит «?» — станция честно говорит, что датчик не отвечает. Пошевелите провод данных и резистор: чаще всего это контакт.
- Числа не меняются — подышите на датчик: влажность должна подскочить за пару секунд. Если нет — смотрите на угол экрана, там наверняка «?».
- От повербанка станция гаснет — умные повербанки отключаются, когда ток совсем маленький, а наша станция потребляет крохи. Возьмите обычную зарядку от телефона — с ней такого не бывает.
Задания для тренировки
- РазминкаПоменяйте страницы местами: пусть рекорды показываются дольше — 6 секунд, а текущая погода — 4. Подсказка: понадобятся два разных числа вместо одного 4000.
- ПосложнееДобавьте на страницу рекордов влажность: минимум и максимум для неё тоже. Понадобятся ещё две переменные-«памятки» рядом с lowest и highest.
- Со звёздочкой ⭐Уберите автоматическое листание и поставьте кнопку на вывод 7: пока она нажата — на экране рекорды, отпустили — снова погода. Кнопку подключите как в уроке 11: одна ножка на вывод, другая на «−».
Разбор задания со звёздочкой
Кнопка — на INPUT_PULLUP, как во всех уроках с урока 11: нажата — на выводе LOW. Сажайте её верхом на канавку в свободных столбиках 20–22: сигнальный провод — сверху от канавки на вывод 7, а «минус» — снизу, из столбика 18: он уже соединён с «минусом» через контакт 16 экрана. Страницу выбирает сама кнопка, а перерисовываем экран только в момент смены — иначе он будет мигать на каждом круге loop.
// Урок 28. Разбор задания: рекорды показываются, пока держишь кнопку.
#include <LiquidCrystal.h>
#include <iarduino_DHT.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
iarduino_DHT sensor(8);
const int BUTTON_PIN = 7;
byte gradusnik[8] = {B00100, B01010, B01010, B01110, B01110, B11111, B11111, B01110};
byte kaplya[8] = {B00100, B00100, B01010, B01010, B10001, B10001, B10001, B01110};
float lowest = 0;
float highest = 0;
bool haveNumbers = false;
bool showRecords = false;
unsigned long lastRead = 0;
void setup() {
lcd.begin(16, 2);
lcd.createChar(0, gradusnik);
lcd.createChar(1, kaplya);
lcd.setCursor(0, 0);
lcd.print("Meteo start");
pinMode(BUTTON_PIN, INPUT_PULLUP);
}
void loop() {
if (millis() - lastRead >= 2000) {
lastRead = millis();
if (sensor.read() == DHT_OK) {
if (!haveNumbers) {
lowest = sensor.tem;
highest = sensor.tem;
haveNumbers = true;
}
if (sensor.tem < lowest) {
lowest = sensor.tem;
}
if (sensor.tem > highest) {
highest = sensor.tem;
}
drawScreen();
}
}
// Кнопка выбирает страницу: нажата — рекорды, отпущена — погода.
bool wantRecords = digitalRead(BUTTON_PIN) == LOW;
if (wantRecords != showRecords) {
showRecords = wantRecords;
lcd.clear();
drawScreen();
}
}
void drawScreen() {
if (showRecords) {
lcd.setCursor(0, 0);
lcd.print("Min ");
lcd.print(lowest, 0);
lcd.write((byte)223);
lcd.print("C ");
lcd.setCursor(0, 1);
lcd.print("Max ");
lcd.print(highest, 0);
lcd.write((byte)223);
lcd.print("C ");
} else {
lcd.setCursor(0, 0);
lcd.write((byte)0);
lcd.print(" ");
lcd.print(sensor.tem, 0);
lcd.write((byte)223);
lcd.print("C ");
lcd.setCursor(0, 1);
lcd.write((byte)1);
lcd.print(" ");
lcd.print(sensor.hum, 0);
lcd.print("% ");
}
}
Обратите внимание: опрос датчика остался на своём будильнике и никак не мешает кнопке — страница переключается мгновенно, хоть датчик и отвечает раз в две секунды. Это и есть выигрыш от millis вместо delay.
Что мы узнали
- Устройства собираются из готовых узлов: экран + датчик = метеостанция. Каждый узел сначала проверяется отдельно.
- Детали спорят за выводы — перед сборкой составьте список, кто где сидит.
- У экрана есть готовый знак градуса — номер 223, а свои значки рисуются через
createChar. - Честный прибор показывает, что данные устарели, а не делает вид, что всё хорошо.
- Несколько дел в одной программе — это несколько будильников на
millis. - Прошитая программа живёт в плате без компьютера — достаточно любой USB-зарядки.
Блок про индикаторы и экраны на этом закрыт. Впереди — финальный блок курса: большие проекты. Первым будет сейф с цифровым кодом.
Частые вопросы
Как подключить DHT11 и экран LCD1602 к Arduino одновременно?
Экран занимает выводы 12, 11, 5, 4, 3 и 2, поэтому датчик переезжает на свободный вывод 8. Библиотеке iarduino_DHT всё равно, какой вывод использовать, — меняется одна цифра в программе. Подтягивающий резистор 10 кОм на линии данных остаётся обязательным.
Как вывести знак градуса на экран LCD1602?
Он уже есть в памяти экрана под номером 223: команда lcd.write((byte)223) печатает его в текущую клетку. Если на вашем экране под этим номером другой символ, знак градуса можно нарисовать самому через createChar.
Может ли метеостанция работать без компьютера?
Да. Программа записана в плату насовсем, компьютер нужен только для загрузки. Воткните плату в зарядку от телефона или повербанк — станция заработает сама.
Почему станция гаснет при питании от повербанка?
Многие повербанки отключаются, если ток слишком маленький, а станция потребляет совсем немного. Возьмите обычную зарядку от телефона — с ней такого не случается.
Почему температура на станции выше, чем на термометре?
Датчик стоит рядом с платой, которая слегка греется, и сам DHT11 имеет точность плюс-минус два градуса. Отодвиньте датчик от платы подальше — показания станут ближе к правде.
