Погодную станцию на даче я вылизывал два вечера, а на третий заметил призрака. На экране 20x4 первая строка держала заголовок, третья — температуру, и иногда между ними всплывала каша: обрезок числа, который я не писал ни в одну из строк. Сначала грешил на помехи, потом на брак панели, а виноват оказался мой же Serial-лог, напечатанный на первую строку: часть строк журнала была длиннее двадцати символов, и хвост уходил не на соседнюю строку, как я считал по-человечески, а на третью — через одну. Дисплей делал ровно то, что приказывает ему его память, просто я эту память не читал.
Это главная тема урока: четырёхстрочный экран почти ничего не требует от вашего кода, но таит одну ловушку, о которой молчат большинство инструкций. Разберём, чем 20x4 отличается от 16x2 с точки зрения библиотеки, как задать размеры в обеих версиях — LiquidCrystal и I2C, — почему длинная строка перетекает именно на третье поле, и какие раскладки на четырёх строках работают без боли.
Меняем три строки, а не весь скетч
С точки зрения API разница между 16x2 и 20x4 умещается в числах. У I2C-варианта размеры задаются в конструкторе: LiquidCrystal_I2C lcd(0x27, 20, 4). У параллельного — в lcd.begin(20, 4): та же пара чисел, те же шесть проводов RS, E и D4–D7, что мы раскладывали в уроке про подключение без I2C. Всё остальное — setCursor, print, затирание пробелами — переезжает один в один, потому что библиотека лишь сообщает контроллеру геометрию, а рисует по-прежнему сам HD44780. Диапазоны запомнить просто: столбцы от 0 до 19, строки от 0 до 3, и setCursor(0, 3) — это левый нижний угол.
Интересное начинается в памяти дисплея. Знакоместа живут в DDRAM, и у экрана 16x2 её ровно столько же, сколько у 20x4 — 80 байт. У 16x2 половина невидимая: в каждой видимой строке 16 символов, а скрытых ещё по 24. Четырёхстрочному всё нужнее, но раскладывает их контроллер по своей внутренней логике, доставшейся от эпохи сорокасимвольных панелей: первая строка занимает адреса 0x00–0x13, вторая — 0x40–0x53, третья — 0x14–0x27, четвёртая — 0x54–0x67. То есть первая и третья строки лежат в памяти подряд, как один длинный кусок, и вторая с четвёртой — тоже.
| Экран | Строк begin() | Начальные адреса DDRAM строк | Полезных знакомест |
|---|---|---|---|
| 16x2 | (16, 2) | 0x00 и 0x40 (ещё по 24 скрытых символа на строку) | 32 из 80 |
| 20x4 | (20, 4) | 0x00, 0x40, 0x14, 0x54 — вторая половина «перекошена» | 80 из 80 |
| 40x2 | (40, 2) | 0x00 и 0x40 — обе строки видны целиком | 80 из 80 |
#include <LiquidCrystal.h> // RS, E, D4, D5, D6, D7 — та же схема, что и для 16x2LiquidCrystal lcd(12, 11, 5, 4, 3, 2); void setup() { lcd.begin(20, 4); // четыре строки по двадцать lcd.setCursor(0, 0); lcd.print("Stroka 0: zagolovok"); lcd.setCursor(0, 1); lcd.print("Stroka 1: dannye"); lcd.setCursor(0, 2); lcd.print("Stroka 2: status"); lcd.setCursor(0, 3); lcd.print("Stroka 3: dno ekrana");} void loop() {}Из раскладки адресов следует практический вывод, который стоит прожить руками: перетекание идёт через строку. Напечатайте на первую строку 21 символ — двадцатый останется на своём месте, а двадцать первый появится в начале третьей. Хвост второй строки уползает в начало четвёртой. Для односимвольного случайного текста это выглядит как порча данных, для систематического журнала — как «призрак» из моей истории. Лечится дисциплиной: длинные строки либо обрезаются до двадцати символов, либо печатаются с переносом вручную — по 20 позиций, с явным setCursor на нужную строку. Автопереноса, дружелюбного к человеку, в HD44780 не завезли.
Обратная сторона той же памяти — скрытые строки 16x2. Помните, что print длиннее шестнадцати символов на двухстрочном экране не пропадает, а уходит в невидимую зону? Мы это мельком затрагивали в первом уроке про I2C: текст пишется в DDRAM, просто не виден. На 20x4 такой комфорт исчез — любой символ сверх двадцати уже виден, но не там, где вы ждали. Считать длину строк до печати — не паранойя, а гигиена.
Четыре строки в работе: раскладки, которые живут
На двух строках интерфейс выживает за счёт компромиссов, а на четырёх появляется место для привычной структуры: заголовок сверху, данные в середине, статус внизу. Мой типовой шаблон — строка 0 под название прибора и режим, строки 1 и 2 под два-три одновременно читаемых значения, строка 3 под состояние: «работает», «ошибка», время до следующего события. Четыре пункта — это ещё и полноэкранное меню без прокрутки, о прокрутке подробно говорит урок про меню и курсор. Часы на 20x4 с датой и днями недели из урока про RTC DS3231 помещаются целиком, без сокращений до аббревиатур.
При обновлении четырёх строк по кругу правило затирания пробелами остаётся прежним, но растёт в цене: переписывать все 80 знакомест каждый цикл — значит дёргать дисплей заметно чаще. Прикиньте сами: цикл раз в 250 мс — это четыре сотни байтов через шину в секунду, хотя реально меняются два-три числа. Поэтому я держу два правила: стационарный текст (заголовки, подписи единиц) печатается один раз в setup, а в loop перерисовываются только цифры, и перед каждой цифрой стоит setCursor, а не lcd.clear(). Полное стирание — только при смене экрана-страницы. Про то, как выстроить обновление без delay и без мерцания, я писал в уроке про millis: дисплею всё равно, а батарейке и глазам — нет.
Маленькая честная оговорка к симуляции: движок раскладывает строки линейно — переполнение первой уехало бы во вторую. Настоящий контроллер с адресами 0x00 и 0x14 делает это иначе, через строку, поэтому тест на перетекание из note-блока проводите на железе. Геометрия 20 на 4, порядок строк и все позиции setCursor в симуляторе при этом соответствуют плате один в один.
Данные для четырёх строк удобно брать по-настоящему разные: температуру — из урока про DHT11, напряжение — с делителя, статус — из логики вашей программы. Больше строк — это не «больше той же мороки», а шанс расставить значения по своим местам, чтобы человек на даче читал экран с метра, а не всматривался.
Итог урока короткий: четыре строки — это одна строчка кода на входе и одна привычка на выходе, проверять, куда деваются символы за двадцатым столбцом. Адресная карта DDRAM выглядит как археология, но именно она объясняет и «призраков» на третьей строке, и скрытые поля 16x2. Следующий шаг в владении дисплеем — рисовать то, чего в знакогенераторе нет: значок градуса, стрелку, сердце. Про восемь слотов CGRAM и createChar — последний урок тройки.
- Напечатайте в setup тестовую строку из 25 символов на строку 0 и посмотрите в симуляторе, куда денется хвост; затем проверьте на реальном 20x4 — он положит хвост на третью строку, движок на вторую.
- Соберите экран-паспорт: строка 0 — название проекта, строка 1 — версия прошивки, строка 2 — время работы в формате MM:SS, строка 3 — количество нажатий кнопки.
- Переделайте скетч так, чтобы раз в секунду перерисовывалась только строка 1, а остальные не трогались вовсе — и убедитесь, что ничего не мигает.
- Выведите строку ровно в 20 символов на строку 3, а потом на 21-й символ: на симуляторе он появится на строке 0 — экран циклически вернулся к началу.
- 20x4 отличается от 16x2 числами в конструкторе I2C (0x27, 20, 4) или в lcd.begin(20, 4); остальной код переезжает без изменений
- Столбцы 0–19, строки 0–3; setCursor(0, 3) — левый нижний угол, всего 80 знакомест
- DDRAM раскладка строк 20x4: 0x00, 0x40, 0x14, 0x54 — первая и третья строки лежат в памяти подряд
- Ловушка: 21-й символ первой строки появляется на третьей, хвост второй — на четвёртой; длинные строки режьте или переносите вручную
- Заголовки и подписи печатайте один раз в setup, в loop обновляйте только изменяющиеся цифры через setCursor — без lcd.clear()
- У 16x2 та же DDRAM на 80 байт: по 24 скрытых символа за каждой видимой строкой
- 1. Сколько всего знакомест у экрана 20x4?
- 2. Как в параллельной библиотеке LiquidCrystal сообщить размеры четырёхстрочного экрана?
- 3. Куда на реальном 20x4 уедет 21-й символ строки, напечатанной с setCursor(0, 0)?
- 4. Что делает setCursor(0, 3) на экране 20x4?
- 5. Как разумно обновлять панель из четырёх строк раз в секунду?
Частые вопросы по теме урока
- Сколько всего знакомест у экрана 20x4?
- 20 столбцов на 4 строки — ровно 80 знакомест, и все они видимые. У 16x2 DDRAM тоже 80 байт, но видимы из них только 32. 80
- Как в параллельной библиотеке LiquidCrystal сообщить размеры четырёхстрочного экрана?
- У параллельной версии размеры передаются в begin(столбцы, строки). У I2C-библиотеки та же пара чисел живёт в конструкторе. lcd.begin(20, 4) в setup
- Куда на реальном 20x4 уедет 21-й символ строки, напечатанной с setCursor(0, 0)?
- Первая и третья строки лежат в DDRAM подряд (0x00 и 0x14), поэтому переполнение первой строки всплывает на третьей. Это фирменная ловушка HD44780. В начало третьей строки
- Что делает setCursor(0, 3) на экране 20x4?
- Первый аргумент — столбец, второй — строка, нумерация с нуля. Строка 3 — последняя, значит получаем левый нижний угол. Ставит курсор в начало последней, четвёртой строки
- Как разумно обновлять панель из четырёх строк раз в секунду?
- Точечное обновление через setCursor без clear не мерцает и не грузит шину. Полное стирание оставьте для смены экрана-страницы. Стационарный текст один раз в setup, в loop — setCursor и цифры точечно