Введение
LCD1602 (16 символов, 2 строки) — один из самых распространённых символьных дисплеев в стартовых наборах. В чистом виде он требует 6+ цифровых пинов Arduino, что быстро исчерпывает их количество при сборке сложной схемы — поэтому в комплекте идёт I2C-конвертер (обычно на чипе PCF8574), сокращающий подключение до двух проводов (SDA/SCL). В статье разберём вывод текста и собственных символов через библиотеку LiquidCrystal_I2C, и приём текста для отображения от Qt-приложения.
Концепция
I2C-конвертер транслирует команды по двум проводам в параллельный интерфейс, который ожидает сам LCD1602 — с точки зрения кода это полностью скрыто библиотекой LiquidCrystal_I2C, интерфейс которой почти идентичен классической LiquidCrystal, но с указанием I2C-адреса вместо номеров пинов. Дисплей поддерживает до 8 пользовательских символов (8×5 точек), что удобно для простых иконок (значок градуса, стрелки, индикатор Wi-Fi), создаваемых через createChar() с заданием битовой маски каждой строки символа.
Пример кода
// firmware/src/main.cpp — LCD1602 с приёмом текста для отображения от Qt
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2); // типичный адрес модуля — 0x27 или 0x3F, проверяется сканером I2C
// Битовая маска символа "градус" — 5 столбцов x 8 строк
byte degreeSymbol[8] = {
0b00110, 0b01001, 0b01001, 0b00110,
0b00000, 0b00000, 0b00000, 0b00000
};
void setup()
{
Serial.begin(9600);
lcd.init();
lcd.backlight();
lcd.createChar(0, degreeSymbol); // регистрируем пользовательский символ под индексом 0
lcd.setCursor(0, 0);
lcd.print("Initializing...");
delay(100);
Serial.println("READY");
}
void loop()
{
static String inputBuffer = "";
while (Serial.available() > 0) {
char c = Serial.read();
if (c == 'n') {
processCommand(inputBuffer);
inputBuffer = "";
} else if (inputBuffer.length() < 64) {
inputBuffer += c;
}
}
}
void processCommand(String command)
{
command.trim();
if (command.startsWith("LCD:LINE1:")) {
displayLine(0, command.substring(10));
} else if (command.startsWith("LCD:LINE2:")) {
displayLine(1, command.substring(10));
} else if (command == "PING") {
Serial.println("PONG");
}
}
void displayLine(int row, String text)
{
lcd.setCursor(0, row);
lcd.print(" "); // очистка строки перед выводом нового текста
lcd.setCursor(0, row);
// Подстановка специального маркера "{deg}" на пользовательский символ градуса
text.replace("{deg}", String((char)0));
lcd.print(text.substring(0, 16)); // обрезка до ширины дисплея, иначе текст переносится некорректно
}
// Отправка текста на дисплей из Qt-приложения
void updateDisplayLines(ArduinoConnection *connection, const QString &line1, const QString &line2)
{
connection->sendCommand("LCD:LINE1:" + line1.left(16));
connection->sendCommand("LCD:LINE2:" + line2.left(16));
}
Пояснения к коду
I2C-адрес 0x27 указывается в конструкторе LiquidCrystal_I2C — конкретное значение зависит от модели конвертера и положения перемычек на нём, и при первом подключении нового модуля стоит явно проверить адрес через отдельный скетч «сканер шины I2C», а не полагаться на типичное значение из примера. createChar(0, degreeSymbol) регистрирует битовую маску как символ с индексом 0 — после регистрации он выводится так же, как обычный символ, через lcd.write(byte(0)) или, как в примере, через String((char)0).
displayLine() показывает практичный приём для построчного текстового дисплея — перед выводом нового текста строка сначала «затирается» пробелами на всю ширину, иначе более короткий новый текст не полностью перекрыл бы более длинный предыдущий, оставив «хвост» старого текста на экране. Обрезка text.substring(0, 16) защищает от попытки вывести текст длиннее физической ширины дисплея, что иначе привело бы не к переносу строки (LCD1602 этого не делает автоматически), а к обрезанию информации по правому краю или прокрутке буфера на лежащую «под капотом» вторую логическую строку, не отображаемую напрямую.
Подводные камни
- Неверно определённый I2C-адрес конвертера — самая частая первая проблема: при неправильном адресе дисплей либо вообще не реагирует, либо показывает случайный набор символов/чёрные блоки на всех ячейках; диагностика через отдельный скетч-сканер I2C-адресов обязательна перед первым использованием нового модуля.
- Отсутствие очистки строки перед выводом более короткого текста оставляет визуальные «остатки» предыдущего, более длинного сообщения — это особенно заметно при частом обновлении показаний (например, температуры), где количество цифр может меняться (10.5 → 9.8 — одинаковая длина, но 100.5 → 9.8 — короче), и без явной очистки строка выглядит «захламлённой».
- Контрастность дисплея, регулируемая отдельным потенциометром на самой плате LCD1602 (а не программно) — если контраст выставлен неправильно физическим потенциометром на модуле, дисплей может казаться полностью пустым или полностью затемнённым (сплошные чёрные прямоугольники), что новички иногда принимают за программную ошибку, хотя реальная причина — чисто аппаратная настройка, не связанная с кодом вовсе.
- Превышение количества пользовательских символов сверх восьми доступных (
createChar()принимает индекс от 0 до 7) — попытка зарегистрировать больше восьми кастомных символов одновременно невозможна на этом контроллере, и для проектов с большим количеством нужных пиктограмм нужно либо переиспользовать индексы динамически (перерегистрируя символ под тем же номером при необходимости показать другую иконку), либо ограничиться действительно необходимым минимумом значков.