Курс Arduino с нуля · Индикаторы и экраны

Урок 28. Проект: домашняя метеостанция

Соединяем экран и датчик в самостоятельный прибор: значки-символы, честный знак вопроса при сбое и память рекордов. В конце — фокус: станция работает от зарядки телефона, без компьютера.

Урок 28 из 31⏱ примерно 55 минутОбновлено: 30 июля 2026

Что понадобится

  • Плата Uno из набора
  • Кабель USB
  • Макетная плата
  • Экран LCD1602
  • Датчик температуры и влажности DHT11
  • Потенциометр 10 кОм
  • Резистор 10 кОм
  • Тактовая кнопка
  • 22 провода «папа-папа»

Все детали есть в обучающем наборе, по которому идёт курс.

Два прошлых урока дали нам всё, что нужно: экран умеет показывать текст, датчик знает температуру и влажность. Сегодня соединим их и соберём настоящую метеостанцию — прибор, который живёт на столе сам по себе, без компьютера.

Новых деталей не будет. Будет другое: первый раз мы собираем устройство из двух готовых узлов и разбираемся с тем, что при этом вылезает — детали начинают спорить за выводы платы.

Что построим

23°C41%Min21°CMax26°Cзначок номер 0 — вы нарисовали его самизнак градуса живёт в экране под номером 223значок номер 1 — капля влажностипри сбое датчика тут загорается ?каждые4 секундыСтраница «сейчас»Страница «рекорды»самая низкая и самая высокая температурас момента включенияОбе страницы рисует одна функция drawScreen — она смотрит, какая страница сейчас выбрана.
У станции две страницы: текущая погода со значками и рекорды температуры. Каждые четыре секунды они сменяют друг друга.

План такой, как в любом взрослом приборе:

  • измерить — раз в две секунды спросить датчик;
  • проверить и запомнить — не поверить сбойному ответу и обновить рекорды;
  • показать — красиво вывести всё на экран.

Это тот же скелет «собрать данные → решить → показать», что был в умном ночнике из урока 18. Он подходит почти для любого устройства.

Кто на каком выводе: детали спорят за место

В уроке 27 датчик сидел на выводе 2. Но теперь вывод 2 занят: на нём один из шести проводов данных экрана. Если оставить датчик на месте, экран и датчик будут дёргать один и тот же вывод — не заработает ни тот, ни другой.

Поэтому датчик переезжает на свободный вывод 8. Ему всё равно, где жить, — библиотека работает с любым выводом, поменяется одна цифра в программе: iarduino_DHT sensor(8);

Запомните этот приём: когда деталей становится много, первым делом составьте список — кто какой вывод занимает. Экран: 12, 11, 5, 4, 3, 2. Датчик: 8. Свободны: 6, 7, 9, 10, 13.

Собираем схему

+abcdefghi23°C41%1234567891011121314151615101520252628301234561Экран, контраст и их 16 проводов — в точности как в уроке 26, тут ничего не менялось.2Потенциометр по-прежнему задаёт контраст: без него на экране ничего не увидеть.3Датчик — решёткой к себе в ряд a, столбики 25–28: столбик 25 на «+», 28 на «−», 27 пустой.4Резистор 10 кОм перекинут через канавку из столбика 26 — подтяжка линии данных, как в уроке 27.5Снизу резистор соединяется со столбиком 17: этот столбик уже подключён к «+» для экрана.6Провод данных из столбика 26 идёт на вывод 8: вывод 2, где датчик жил раньше, занят экраном.Питание всей макетки берётся с верхних шин — нижние закрыты корпусом экрана.Провод соединён только там, где на нём светлая шляпка; шины он пересекает между отверстиями.
Экран с контрастом стоят как в уроке 26. Датчик добавился справа: столбики 25–28, подтяжка через канавку и провод данных на вывод 8.

Если схема урока 26 у вас ещё собрана — не разбирайте, мы только добавим датчик. Отключите USB и дальше по шагам.

  1. Проверьте узлы из урока 26: экран в нижнем ряду, потенциометр контраста, питание макетки с платы — 5V на «+» верхней шины, GND на «−».
  2. Вставьте датчик решёткой к себе в ряд a, выводы в столбики 25, 26, 27 и 28.
  3. Столбик 25 (вывод 1 датчика) — проводом на «+», столбик 28 (вывод 4) — на «−». Столбик 27 остаётся пустым.
  4. Резистор 10 кОм — через канавку: одна ножка в столбик 26 сверху, вторая в столбик 26 снизу.
  5. От нижней ножки резистора — провод в столбик 17. Хитрость: этот столбик уже соединён с «+» (по нему питается подсветка экрана), так что тянуться к шине не нужно.
  6. И провод данных: из столбика 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 имеет точность плюс-минус два градуса. Отодвиньте датчик от платы подальше — показания станут ближе к правде.

← Все уроки курса