Плёнку с моего первого LCD 16x2 я снимал с предвкушением мини-монитора: думал, сейчас будет картинка, хотя бы синусоиду нарисую. А получил два ряда квадратных ячеек, в которых буквы собраны из крупных кубиков, будто выложены детским конструктором. Разочарование прошло, когда я посчитал: экран 16x2 — это 32 знакоместа, в каждом сидит сетка 5 на 8 точек, то есть 40 пикселей на букву и всего 1280 видимых точек на весь дисплей. Ни о какой графике речи, зато каждая точка под контролем: поняв внутренности этого экрана, я перестал с ним воевать и начал договариваться.
В этом уроке вскроем дисплей без отвёртки: узнаем, какой чип им командует, откуда берутся буквы, почему у экрана памяти больше, чем видно глазу, и куда пропадает семнадцатый символ. Если совсем недавно ваш главный успех был в первом мигании светодиодом, этот урок станет логичным следующим шагом: тот же дух эксперимента, только вместо точки света — целый экран. Закрепим знание практикой — счётчиком на дисплее с дублированием в Serial. Это фундамент: без него настройка курсора и меню дальше превращается в суеверное перебирание команд.
Контроллер HD44780: сорокалетний стандарт
Внутри почти каждого символьного LCD из наборов живёт контроллер HD44780 от Hitachi. Схему выпустили в конце восьмидесятых, и она оказалась настолько живучей, что современные дисплеи строят на её клонах — KS0066, ST7066 и подобных. Клоны понимают те же однобайтовые команды, поэтому для всех них работает одна и та же библиотека LiquidCrystal, причём она уже встроена в Arduino IDE: ничего искать и устанавливать не нужно.
Важный момент, который я долго не осознавал: сама стеклянная панель глупая, как табло на вокзале. Она не умеет ничего, кроме отрисовки знакомест по командам с параллельной шины. Все мозги — в контроллере рядом с панелью. Даже у модулей с I2C-переходником, о которых я писал в уроке LCD 16x2 по I2C: текст за пять минут, внутри сидит тот же HD44780 — просто команды до него носит маленький расширитель портов, а экран об этом не знает.
Питание — 5 В, обмен — потоком коротких команд: очистить экран, поставить курсор в такую-то ячейку, нарисовать символ с таким-то кодом. Скорости у контроллера с запасом: типовая команда исполняется около 37 микросекунд, самая долгая, очистка, — примерно полторы миллисекунды. Поэтому обновлять экран пару раз в секунду, как мы будем в эксперименте ниже, — это издевательски ленивый темп: за полсекунды паузы дисплей успел бы перерисоваться сотни раз.
Семейство этим не исчерпывается: по той же шине и тем же командам работают однострочные 16x1, вытянутые 20x4 и даже 40x2. Логика везде одна, меняется только геометрия, которую скетч объявляет при старте. Отдельные редкие модели носят шрифт 5x10 — их символы занимают десять точек по вертикали и выглядят заметно аккуратнее, но в стартовых наборах такие почти не попадаются, поэтому дальше речь о классике 5x8.
Знакогенератор: откуда берутся буквы
Буквы не рисуются кодом по точкам — они уже зашиты в память самого дисплея. Эта таблица называется знакогенератором, или CGROM: постоянное запоминающее устройство, где каждому символу соответствует битовая карта 5 на 8 точек. В стандартной таблице около двух сотен знаков: латиница, цифры, знаки препинания, стрелки, греческие буквы и даже несколько символов катаканы. Кириллицы в базовой раскладке нет — отсюда моё рабочее правило писать экранные тексты латиницей, если заранее не уверен в конкретном модуле.
Рядом с постоянной таблицей лежит маленькая перепрограммируемая память CGRAM — восемь слотов с кодами 0–7. Туда можно записать собственный глиф: те же 40 точек, восемь байт на знак. Градус, стрелка вверх, колокольчик — всё это рисуется своими руками и затем используется как обычный символ. Механику своих глифов мы разберём отдельно, а пока запомните сам факт: восемь слотов, не больше. На практике их хватает с запасом — прибор редко использует больше двух-трёх своих глифов.
И самая незаметная «буква» этой таблицы — пробел с кодом 32. Пустое знакоместо на экране — не дырка, а честно отрисованный пробел. Эта мелочь станет главным инструментом дальше: затирать старые цифры мы будем именно пробелом, потому что он стирает, оставаясь обычным символом. Строго говоря, пробел поверх цифры — это не пропуск такта, а честная перерисовка ячейки пустым глифом.
| Память | Тип | Объём | Что хранит |
|---|---|---|---|
| CGROM | ПЗУ, прошита на заводе | около 208 знаков | битовые карты символов 5x8 |
| CGRAM | ОЗУ, пишется скетчем | 8 слотов, коды 0-7 | свои глифы, 8 байт на слот |
| DDRAM | ОЗУ | 80 байт | коды символов в знакоместах, видно 32 из 80 |
Третья память, DDRAM, — рабочая: в ней лежит то, что показано на экране прямо сейчас. Её объём — 80 байт, а видимых знакомест у 16x2 всего 32. Адресация не сплошная: первая строка занимает адреса от 0x00 до 0x0F, вторая начинается с 0x40 и занимает 0x40–0x4F. Между ними — пропасть в адресах, и это не прихоть: так контроллер обслуживает и 20x4, и однострочные модели с той же памятью. У 20x4 адреса строк переплетены причудливее: видимых двадцать ячеек на строку, скрытых — ещё по двадцать.
Из 80 байт 48 — за кадром. Адреса 0x10–0x27 продолжают первую строку, 0x50–0x67 — вторую, но за краем стекла эти ячейки не видны. Отсюда и загадка из подзаголовка: если напечатать подряд 20 символов в первой строке, семнадцатый не перелезет на вторую — он уйдёт в скрытую зону 0x10 и молча исчезнет из вида. Точно так же легальна команда setCursor(16, 0): курсор честно встанет за правый край, просто результата глаз не увидит. Память при этом честно заполнится — глюка нет, есть геометрия.
Практический вывод: экран 16x2 никогда не «переносит» текст сам. Перенос строки — всегда ручная работа: остановил печать, поставил курсор в начало второй строки setCursor(0, 1), продолжил. Мы сделаем именно так в уроке про begin, setCursor и print, а сейчас проверим скрытую зону демонстрацией.
#include <LiquidCrystal.h> LiquidCrystal lcd(12, 11, 5, 4, 3, 2); void setup() { lcd.begin(16, 2); lcd.setCursor(0, 0); lcd.print("VIDNO 16 ZNAKOV"); lcd.setCursor(16, 0); // за правым краем: на железе — скрытая DDRAM lcd.print("SKRYTO"); // на стекле пусто, в памяти символы живут} void loop() { // в симуляторе координаты за краем обрезаются: движок мягче реального железа}Эксперимент: счётчик на дисплее и в Serial
Соберём скромный, но честный прибор: на верхней строке — подпись, на нижней — счётчик проходов loop, и каждое значение дублируется в Serial. Дублирование не блажь: экран показывает одно текущее число, а лог в Serial-мониторе хранит историю, с которой удобно сравнивать. Печатать значения будем парами имя=значение, вроде n=5, — такой формат безболезненно читает и Serial Plotter, иначе несколько величин слипаются в позиционные ряды. Мы закладывали этот порядок ещё в первом знакомстве с Serial-монитором.
В симуляторе виджет дисплея заводится автоматически, как только интерпретатор встречает LiquidCrystal в коде — расставлять компоненты руками не нужно. Заодно потренируем привычку из урока про analogRead и потенциометр: любое изменяющееся число сначала смотрят в логе, потом уже на экране, — так видно, что врёт: скетч или отрисовка.
Разбор, к которому готовимся. Подпись сверху печатается один раз в setup() и больше не трогается — DDRAM помнит её сама, это обычные ячейки памяти, а не кадр, который надо перерисовывать. Обновляется только нижняя строка: setCursor(0, 1), новое значение, хвост из пяти пробелов. Без хвоста счётчик 9, сменившись на 10, оставил бы мусор: старая цифра осталась бы справа, ведь пробелы и цифры для дисплея равноправные символы.
Темп в 500 мс выбран не случайно. Две отрисовки в секунду глазу более чем достаточно, а контроллер в паузах фактически спит: по даташиту типовая команда занимает около 37 мкс, значит, за полсекунды он успел бы исполнить их порядка тринадцати тысяч. Когда дойдёт до проектов с датчиками, эта разница превратится в свободу: на отрисовку уходит доля процента времени, остальное можно отдать измерениям и логике.
- 1. Что хранит DDRAM у LCD 16x2?
- 2. Сколько слотов в CGRAM под свои собственные символы?
- 3. Куда попадёт 17-й символ, напечатанный подряд в первой строке 16x2?
- 4. Из скольких точек состоит один символ в стандартном знакогенераторе?
- 5. Сколько байт DDRAM одновременно видно на экране 16x2?
- Внутри символьного LCD работает контроллер HD44780 или его клоны (KS0066, ST7066); библиотека LiquidCrystal уже встроена в Arduino IDE
- Каждый знак — битовая карта 5x8 из CGROM; кириллицы в базовой таблице нет, свои глифы пишут в CGRAM — 8 слотов с кодами 0–7
- DDRAM — 80 байт, из них видно 32: строка 0 — адреса 0x00–0x0F, строка 1 — 0x40–0x4F
- Текст не переносится сам: 17-й символ подряд уходит в скрытую зону 0x10–0x27, перенос делается setCursor(0, 1) вручную
- Типовая команда дисплея — около 37 мкс, очистка — около 1,5 мс; обновлять экран пару раз в секунду — расточительство с огромным запасом
- Пробел — такой же символ, им затирают устаревшие цифры без полной перерисовки
Частые вопросы по теме урока
- Что хранит DDRAM у LCD 16x2?
- DDRAM — рабочая память дисплея: в её ячейках лежат коды символов, которые контроллер отрисовывает в знакоместах. Графику символьный экран не хранит вовсе. Коды символов в знакоместах
- Сколько слотов в CGRAM под свои собственные символы?
- Восемь слотов с кодами 0–7, по восемь байт на каждый — ровно столько нужно на карту 5x8. Больше восьми своих глифов одновременно не удержать. 8
- Куда попадёт 17-й символ, напечатанный подряд в первой строке 16x2?
- Первая строка занимает адреса 0x00–0x27, из которых видны только 0x00–0x0F. Лишние символы записываются в невидимую часть и на экран не попадают. В скрытую зону DDRAM за краем экрана
- Из скольких точек состоит один символ в стандартном знакогенераторе?
- Стандартный глиф HD44780 — сетка 5x8 точек, сорок пикселей. Поэтому буквы дисплея такие угловатые: это честная растровая карта, а не шрифт сглаживания. 5 на 8
- Сколько байт DDRAM одновременно видно на экране 16x2?
- 32 знакоместа — 16 столбцов на 2 строки. Полный объём памяти 80 байт, остальное — скрытые зоны за краем стекла. 32