Курс Arduino с нуля · Индикаторы и экраны
Урок 26. Экран LCD: выводим текст и цифры
Подключаем экран LCD1602 на две строки по шестнадцать знаков. Разбираемся, зачем ему столько проводов и потенциометр, и учим плату писать слова, показывать числа и рисовать свои значки.
Что понадобится
- Плата Uno из набора
- Кабель USB
- Макетная плата
- Экран LCD1602
- Потенциометр 10 кОм
- Тактовая кнопка
- Фоторезистор и резистор 10 кОм
- 16 проводов «папа-папа»
Все детали есть в обучающем наборе, по которому идёт курс.
В прошлом уроке индикатор показывал одну цифру. Сегодня возьмём деталь посерьёзнее — экран LCD1602. На нём помещаются две строки по шестнадцать знаков: уже можно написать слово, показать температуру с подписью или вывести меню.
Урок будет чуть длиннее обычного: проводов к экрану идёт много. Зато потом всё окупится — любое ваше устройство сможет объясняться словами, а не морганием.
Что это за деталь
Название читается просто: LCD — жидкокристаллический экран, 16 знаков в строке, 02 строки. Сзади у него зелёная подсветка, поэтому надписи видно и в темноте.
Сверху — ряд из шестнадцати контактов с тем же шагом, что у отверстий макетной платы. Это удобно: экран просто вставляется в макетку, как большая микросхема.
Контакты пронумерованы прямо на плате экрана: у крайнего напечатана единица или подпись VSS, у противоположного — 16 или K. С этого края и начинаем счёт.
Как экран рисует буквы
Экран не умеет рисовать что угодно в любом месте, как монитор компьютера. Он разделён на клетки, и в каждую клетку помещается ровно один знак — буква, цифра или пробел.
Самое приятное: рисунки букв и цифр экран уже знает наизусть. Вам не нужно объяснять ему, как выглядит буква «A» — достаточно отправить слово, а точки он зажжёт сам.
Почему проводов так много
К светодиоду шло два провода, к кнопке — два. К экрану пойдёт двенадцать. Разберёмся, откуда столько, чтобы это не выглядело загадкой.
- Четыре провода — питание. Контакт 1 на минус, контакт 2 на плюс, и ещё два на подсветку: контакт 15 на плюс, контакт 16 на минус.
- Один провод — контраст. Контакт 3 идёт не на плату, а на среднюю ножку потенциометра. Им вы настраиваете, насколько тёмными выглядят буквы.
- Один провод — на минус. Контакт 5 (он подписан RW) выбирает, читаем мы с экрана или пишем на него. Читать нам незачем, поэтому он навсегда прижат к минусу.
- Шесть проводов — данные и команды. Вот они и идут к плате: контакты 4 и 6 говорят экрану «сейчас будет команда» и «принимай», а контакты 11–14 несут сами буквы.
А контакты 7, 8, 9, 10 остаются пустыми. Экран умеет принимать букву целиком за один раз — по восьми проводам, но тогда пришлось бы занять ещё четыре вывода платы. Поэтому он умеет и по-другому: принимать букву двумя половинками по четырём проводам. Чуть медленнее, зато экономно. Такой способ и называют четырёхпроводным режимом, и им пользуются почти все.
Собираем схему
Отключите плату от USB и соберите схему по шагам. Первым делом вставьте экран: гребёнкой в самый нижний ряд макетки, корпусом вниз, чтобы он не закрывал остальные отверстия. Запомните столбик, в который попал первый контакт, — дальше считать удобно от него.
- Экран вставлен в нижний ряд. Провода к нему подключаются не в те же отверстия, а в любые свободные того же столбика — они соединены внутри макетки.
- Потенциометр: крайние ножки на «+» и «−», средняя — проводом на контакт 3 экрана. Это регулятор контраста.
- Питание с платы: гнездо 5V на шину «+», гнездо GND на шину «−».
- Питание экрана: контакты 1, 5 и 16 на «−», контакты 2 и 15 на «+». Пять коротких проводов.
- Шесть проводов данных: контакт 4 на вывод 12, контакт 6 на вывод 11, контакты 11, 12, 13, 14 — на выводы 5, 4, 3, 2.
Проверьте перед включением: контакт 1 экрана уходит на «−», контакт 2 — на «+», контакт 5 — тоже на «−»; средняя ножка потенциометра идёт на контакт 3, а крайние — на разные шины; шесть проводов данных сидят по порядку, без сдвига на столбик; и на макетке есть питание с платы — 5V на «+», GND на «−».
Внимательнее всего пересчитайте именно шесть проводов данных: если сдвинуть их на один столбик, экран включится, но вместо слов покажет бессмыслицу. И только теперь включайте USB.
Шаг 1. Первые слова
Экран — деталь непростая, и разговаривать с ним по проводам вручную было бы мучением. За нас это делает библиотека — готовый набор команд. Нужная библиотека называется LiquidCrystal и уже встроена в Trema IDE: ставить ничего не надо.
// Урок 26. Первые слова на экране.
#include <LiquidCrystal.h>
// В скобках — выводы платы по порядку: RS, E, D4, D5, D6, D7.
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
void setup() {
lcd.begin(16, 2); // экран на 16 знаков и 2 строки
lcd.print("Privet!");
}
void loop() {
}
Разберём по строчкам.
#include <LiquidCrystal.h>— подключаем библиотеку. Эта строка всегда идёт первой.LiquidCrystal lcd(12, 11, 5, 4, 3, 2);— говорим, к каким выводам подключён экран. Порядок строгий: сначала контакт 4 (RS), потом контакт 6 (E), потом четыре провода данных. Словоlcd— это имя, которым мы дальше зовём экран.lcd.begin(16, 2);— сообщаем размер: 16 знаков, 2 строки. Без этой строки экран останется пустым.lcd.print("Privet!");— печатаем слово. Текст обязательно в двойных кавычках.
Заметьте: всё написано в setup(), а loop() пустой. Так и надо — надпись достаточно вывести один раз, дальше она держится на экране сама.
Экран включился, но пустой? Не спешите искать ошибку в программе — почти наверняка дело в контрасте. Медленно покрутите потенциометр от края до края: где-то посередине буквы проявятся. В одном крайнем положении экран выглядит чистым, в другом — заполнен рядами тёмных квадратов.
Шаг 2. Курсор: пишем куда нужно
Команда print пишет с того места, где сейчас стоит курсор, — как мигающая палочка в текстовом редакторе. Сразу после включения курсор в левом верхнем углу. Передвинуть его можно командой setCursor: сначала номер столбца, потом номер строки.
// Урок 26. Две строки: ставим курсор и пишем куда нужно.
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
void setup() {
lcd.begin(16, 2);
lcd.setCursor(0, 0); // столбец 0, строка 0 — левый верхний угол
lcd.print("Trema IDE");
lcd.setCursor(4, 1); // столбец 4, строка 1 — ниже и правее
lcd.print("Urok 26");
}
void loop() {
}
Счёт идёт с нуля — как у ячеек массива из урока про бегущий огонёк. Столбцы нумеруются от 0 до 15, строки — 0 и 1. Значит, setCursor(4, 1) — это пятая клетка второй строки.
Шаг 3. Показываем меняющееся число
Пока на экране надпись, всё просто. А теперь пусть плата считает секунды и показывает их. И тут ждёт ловушка, о которой стоит узнать заранее.
// Урок 26. Счётчик секунд: число печатается в одно и то же место.
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
int seconds = 0;
void setup() {
lcd.begin(16, 2);
lcd.print("Sekundy:"); // подпись пишем один раз
}
void loop() {
lcd.setCursor(0, 1);
lcd.print(seconds);
lcd.print(" "); // пробелы затирают хвост прошлого числа
seconds++;
delay(1000);
}
Экран ничего не стирает сам. Он просто зажигает точки там, куда вы напечатали, — а всё остальное остаётся как было. Когда после числа 10 вы печатаете 9, ноль от прошлого числа никуда не денется, и на экране получится 90.
Поэтому после числа мы печатаем несколько пробелов: lcd.print(" "). Пробел — тоже знак, он затирает клетку начисто. Второй способ — команда lcd.clear(), она стирает весь экран разом. Но пользоваться ей на каждом круге не стоит: экран заметно мигает, потому что вместе с числом стирается и подпись.
Правило. Подписи, которые не меняются, печатайте один раз в setup(). В loop() обновляйте только то, что действительно изменилось.
Шаг 4. Экран считает нажатия
Соберём знакомое: кнопка из уроков про кнопки плюс экран. Кнопку ставим одной парой ножек сверху от канавки, другой снизу; сигнальный провод — на вывод 7, второй вывод кнопки — на шину «−».
Почему не вывод 2? Раньше кнопка всегда жила на выводе 2, но теперь он занят экраном — на нём висит контакт 14. Так бывает всегда, когда деталей становится много: свободные выводы приходится распределять заново.
// Урок 26. Экран считает нажатия кнопки.
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
const int BUTTON_PIN = 7; // вывод 2 занят экраном, кнопка переехала на 7
int count = 0;
bool wasPressed = false;
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
lcd.begin(16, 2);
lcd.print("Nazhatiy:");
showCount();
}
void loop() {
bool pressed = (digitalRead(BUTTON_PIN) == LOW);
if (pressed && !wasPressed) {
count++;
showCount();
delay(50); // пережидаем дребезг
}
wasPressed = pressed;
}
void showCount() {
lcd.setCursor(0, 1);
lcd.print(count);
lcd.print(" ");
}
Приём тот же, что и раньше: мы ловим момент нажатия, сравнивая нынешнее состояние кнопки с прошлым. Новое здесь только одно — печать вынесена в свою функцию showCount(), и вызывается она лишь тогда, когда число изменилось. Экран не дёргается зря.
Шаг 5. Показываем датчик: число и полоска
Потенциометр в наборе один, и он теперь занят контрастом. Поэтому возьмём датчик света из пятнадцатого урока: фоторезистор идёт от шины «+» в свободный столбик, из того же столбика резистор 10 кОм уходит на «−», а от середины провод на вход A0.
// Урок 26. Датчик света: число и полоска-шкала на экране.
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
const int SENSOR_PIN = A0;
void setup() {
lcd.begin(16, 2);
}
void loop() {
int value = analogRead(SENSOR_PIN);
int blocks = map(value, 0, 1023, 0, 16); // сколько квадратиков закрасить
lcd.setCursor(0, 0);
lcd.print("Svet: ");
lcd.print(value);
lcd.print(" ");
lcd.setCursor(0, 1);
for (int i = 0; i < 16; i++) {
if (i < blocks) {
lcd.write((byte)255); // знак 255 — сплошной чёрный квадрат
} else {
lcd.print(" ");
}
}
delay(100);
}
В первой строке — число, во второй — полоска из чёрных квадратиков: чем светлее в комнате, тем она длиннее. Квадратик — это знак с номером 255, он есть в памяти экрана наравне с буквами. Команда lcd.write печатает знак по его номеру, а map из урока про потенциометр переводит 0…1023 в 0…16 квадратиков.
Получился настоящий индикатор уровня — такие рисуют на регуляторах громкости. Точно так же можно показать заряд, температуру или скорость.
Шаг 6. Свой символ
Букв и цифр экран знает много, но иногда хочется своего: значок градуса, стрелку, батарейку, сердечко. Экран умеет запомнить восемь ваших рисунков.
// Урок 26. Свой символ: рисуем сердечко по точкам.
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
// Восемь строк по пять точек. 1 — точка горит, 0 — не горит.
byte heart[8] = {
B00000,
B01010,
B11111,
B11111,
B01110,
B00100,
B00000,
B00000
};
void setup() {
lcd.begin(16, 2);
lcd.createChar(0, heart); // запоминаем рисунок под номером 0
lcd.setCursor(0, 0);
lcd.print("Arduino ");
lcd.write((byte)0); // показываем свой символ
}
void loop() {
}
Массив heart — это и есть картинка: смотрите на нули и единицы прищурившись, и увидите сердечко. Команда createChar(0, heart) отправляет рисунок в экран и запоминает его под номером 0, а lcd.write((byte)0) показывает его в том месте, где стоит курсор.
Номера ваших символов — от 0 до 7, всего восемь. Придумывать рисунок удобно на клетчатой бумаге: расчертите поле 5 на 8 и закрасьте нужные клетки.
А русские буквы?
Здесь придётся честно сказать: обычный LCD1602 русского алфавита не знает. В его памяти латинские буквы, цифры, знаки препинания и немного значков. Если напечатать русское слово, экран покажет случайный набор символов.
Поэтому в уроке мы пишем латиницей: Privet, Temperatura. Это обычная практика для таких экранов. А если очень нужны русские буквы — их можно нарисовать самим, теми самыми восемью своими символами. Правда, восьми букв хватит не на всякое слово.
Не работает? Разбираемся
- Экран светится, но пустой — крутите потенциометр контраста. Это причина девяти неудач из десяти.
- Видно ряд тёмных квадратов и больше ничего — контраст выкручен в другую сторону, и вдобавок программа не дошла до
lcd.begin(). Проверьте, что скетч загрузился. - Экран совсем тёмный, подсветки нет — проверьте контакты 15 и 16: один на «+», другой на «−». И заодно контакты 1 и 2 — это питание самого экрана.
- Вместо слов бессмыслица — перепутаны провода данных. Пересчитайте: контакт 11 на вывод 5, 12 на 4, 13 на 3, 14 на 2. Их легко сдвинуть на один столбик.
- Экран ведёт себя странно: то пишет, то нет — проверьте контакт 5 (RW). Если он висит в воздухе, экран не понимает, писать ему или читать. Его место — на минусе.
- Надпись мигает — вы зовёте
lcd.clear()в каждом кругеloop(). Печатайте только то, что изменилось. - Экран не реагирует вообще — выньте и вставьте его в макетку заново. Из-за длинной гребёнки он часто садится не до конца, и половина контактов повисает в воздухе.
Задания для тренировки
- РазминкаВыведите в первой строке своё имя, а во второй — сегодняшнюю дату. Обе надписи неподвижные, значит, обе печатаются в
setup(). - ПосложнееСделайте «часы, которые идут»: пусть во второй строке каждую секунду обновляется число секунд с момента включения, а в первой стоит подпись
Rabotaem:. - Со звёздочкой ⭐Секундомер. Кнопка запускает отсчёт, второе нажатие останавливает, третье — запускает снова с того же места. Время показывайте как
минуты:секунды, например01:07. Разбор ниже.
Показать разбор секундомера
Главная мысль: экран должен показывать время, но millis() считает от включения платы и остановить его нельзя. Значит, считать будем сами — двумя переменными. startedAt помнит, когда нажали «старт», а collected — сколько времени накопилось за прошлые запуски.
// Урок 26. Разбор задания: секундомер. Кнопка запускает и останавливает счёт.
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
const int BUTTON_PIN = 7;
bool running = false; // идёт ли отсчёт прямо сейчас
bool wasPressed = false;
unsigned long startedAt = 0; // когда нажали «старт»
unsigned long collected = 0; // сколько накопили до остановки
int lastShown = -1; // какие секунды уже на экране
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
lcd.begin(16, 2);
lcd.print("Sekundomer");
}
void loop() {
bool pressed = (digitalRead(BUTTON_PIN) == LOW);
if (pressed && !wasPressed) {
if (running) {
collected = collected + (millis() - startedAt);
running = false;
} else {
startedAt = millis();
running = true;
}
delay(50);
}
wasPressed = pressed;
unsigned long total = collected;
if (running) {
total = total + (millis() - startedAt);
}
showTime(total);
}
void showTime(unsigned long ms) {
int allSeconds = ms / 1000;
if (allSeconds == lastShown) {
return; // ничего не изменилось — экран не трогаем
}
lastShown = allSeconds;
int minutes = allSeconds / 60;
int seconds = allSeconds % 60;
lcd.setCursor(0, 1);
if (minutes < 10) {
lcd.print("0");
}
lcd.print(minutes);
lcd.print(":");
if (seconds < 10) {
lcd.print("0");
}
lcd.print(seconds);
}
Пока секундомер идёт, к накопленному прибавляем время с момента старта. Когда его останавливают, это время переезжает в collected насовсем.
Вторая хитрость — в функции showTime. Она сравнивает секунды с теми, что уже на экране, и если ничего не изменилось, сразу выходит по return. Иначе экран обновлялся бы тысячи раз в секунду и заметно дрожал.
И мелочь, которая делает вид аккуратным: если число меньше десяти, перед ним печатается ноль. Иначе вместо 01:07 получилось бы 1:7.
Что мы узнали
- LCD1602 — экран на две строки по 16 знаков; каждый знак собирается из точек в своей клетке.
- Экран подключают двенадцатью проводами: питание, подсветка, контраст через потенциометр и шесть проводов данных.
- Работать с ним помогает встроенная библиотека
LiquidCrystal: командыbegin,print,setCursor,clear. - Столбцы и строки считаются с нуля, а старые знаки нужно затирать пробелами.
- Восемь своих символов можно нарисовать по точкам командой
createChar.
В следующем уроке подключим датчик DHT11 — он умеет измерять температуру и влажность. А показывать его цифры будет как раз наш новый экран.
Частые вопросы
Как подключить экран LCD1602 к Arduino?
Экран вставляют в макетную плату и соединяют двенадцатью проводами: питание и подсветка идут на шины, контакт 3 — на среднюю ножку потенциометра 10 кОм, а шесть проводов данных — на выводы платы 12, 11, 5, 4, 3 и 2.
Почему экран светится, но ничего не показывает?
Чаще всего дело в контрасте: покрутите потенциометр от края до края, буквы проявятся где-то посередине. Если не помогло, проверьте контакт 5 — он должен быть соединён с минусом.
Зачем экрану потенциометр?
Он задаёт контраст. Без него экран показывает либо чистое поле, либо ряды тёмных квадратов. Крайние ножки потенциометра идут на плюс и минус, средняя — на контакт 3 экрана.
Какая библиотека нужна для LCD1602?
LiquidCrystal — она уже встроена в Trema IDE, ставить ничего не нужно. Достаточно строки include в начале программы и одной строки с перечислением выводов.
Можно ли выводить на LCD1602 русские буквы?
Обычный экран знает только латиницу, цифры и знаки препинания, поэтому надписи пишут латинскими буквами. Отдельные русские буквы можно нарисовать самому — экран запоминает восемь своих символов.
