Два экрана 16x2 с одной барахолки, в одинаковых корпусах, с одинаковыми надписями на рамке — а русский текст на них включается по-разному: на первом код 0x80 рисует заглавную «А», на втором — пустое место. Я потратил вечер на перепрошивку собственных подозрений: менял библиотеки, перетыкал провода, перепроверял адреса — пока не понял главного. У HD44780 нет «русского стандарта»: производитель шьёт в постоянную память ту таблицу символов, которую заказал торговец, и партии расходятся. Вся кириллица на LCD — вопрос не библиотеки, а вопрос к вашему конкретному стеклу: какая таблица лежит именно в нём.
В этом уроке заглянем в ROM знакогенератора: что там лежит по умолчанию, чем русские прошивки отличаются друг от друга и почему таблицы кодов из интернета срабатывают через раз. Потом соберём сканер знакогенератора — кнопка листает страницы кодов, светодиод отмечает кириллические, серийный порт печатает границы — и за пять минут составим карту своего экрана. Она занимает полстраницы блокнота и экономит целые вечера. Физику самого контроллера разбирал отдельный урок про HD44780, здесь она понадобится в одной точке: печать байта.
ROM знакогенератора: что там лежит
Каждый байт, который Arduino отправляет дисплею как данные, — это не буква, а номер строки в таблице знаков, зашитой в постоянную память контроллера. В стандартной прошивке A00, которую клепали ещё в восьмидесятые, на кодах 0x20–0x7E лежат латиница, цифры и пунктуация, а на 0xA1–0xDF — японская катакана. Кириллицы там нет ни в одном байте — потому lcd.print("PRIVET") работает, а lcd.print("ПРИВЕТ") рисует что попало: каждый байт строки интерпретируется номером ячейки, и на экран вываливается мусор из таблицы.
Дисплеи для нашего рынка продаются с «русскими» прошивками, но раскладки у них расходятся от партии к партии. На моей панели заглавные А–Я нашлись на кодах 0x80–0x9F, строчные а–п — на 0xA0–0xAF, р–я — на 0xE0–0xEF: раскладка близка к таблице CP866. Экран соседа, купленный в другом магазине, показал тот же диапазон, но с переставленными буквами. Гарантий нет, поэтому доверять «правильной таблице из статьи» я перестал — таблицу своего экрана надо снять один раз и один раз. Вариативность партий — та же история, что с адресами 0x27 против 0x3F на I2C-переходниках: урок про развилку адресов кончался одинаково — проверяй железо, а не верь маркировке.
Важное следствие устройства памяти: таблицу ROM перепрошить нельзя — она литая, как гравюра. Что можно — перезаписать восемь ячеек CGRAM, и это тема урока про свои символы: глифы закрывают дыры любой раскладки, но таблицу целиком не заменяют. Отсюда моя стратегия по умолчанию: латиница и цифры — как есть, кириллица — по карте своего экрана, всё, чего в карте не нашлось, — глифами.
| Диапазон кодов | Английская прошивка (A00) | Русская прошивка: что показал сканер |
|---|---|---|
| 0x20–0x7E | Латиница, цифры, пунктуация | То же самое — эта часть совпадает у всех |
| 0x80–0x9F | Служебные символы | На моей панели — заглавные А–Я; на других партиях встречается иная раскладка |
| 0xA0–0xAF | Начало катаканы | На моей панели — строчные а–п |
| 0xE0–0xEF | Пунктуация и знаки | На моей панели — строчные р–я |
| 0xB0–0xBF | Продолжение катаканы | На разных партиях — продолжение алфавита или знаки: сверяйте сканером |
Метод снятия карты простой: печатать подряд по шестнадцать байтов, а коды перелистывать кнопкой. Экран при этом подключён по I2C из первого урока раздела, а печать на позициях — setCursor и print из урока про begin и печать. Печать одного байта идёт через lcd.write(): в отличие от print, который шлёт строки и числа, write отправляет сырой байт — номер ячейки знакогенератора, и экран рисует то, что в этой ячейке шито. Отдельная грабля с нулевым кодом: внутри print ноль обрывается как конец строки, поэтому глиф ячейки 0 печатают строго lcd.write(byte(0)).
Коды удобно печатать в серийный порт в шестнадцатеричном виде — так же, как в даташите: Serial.print(x, HEX) делает это одной командой, основы порта — в уроке про монитор. Светодиод в сканере выполняет работу метки: горит на страницах, где я жду кириллицу, — рука не тянется к ноутбуку, чтобы понять, докрутил ли я листание.
Код: сканер знакогенератора
#include <Wire.h>#include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27, 16, 2); int page = 0; // открытая страница знакогенератораint bases[4] = {0x41, 0x80, 0xA0, 0xE0};int cyr[4] = {0, 1, 1, 1}; // кириллическая ли страницаbool lastBtn = HIGH; void setup() { pinMode(2, INPUT_PULLUP); // кнопка листает страницы pinMode(12, OUTPUT); // LED горит на кириллических страницах Serial.begin(9600); lcd.init(); lcd.backlight(); showPage();} void showPage() { lcd.clear(); lcd.setCursor(0, 0); lcd.print("Kod 0x"); lcd.print(bases[page], HEX); lcd.print("-"); lcd.print(bases[page] + 15, HEX); lcd.setCursor(0, 1); for (int i = 0; i < 16; i++) { lcd.write(bases[page] + i); } if (cyr[page] == 1) digitalWrite(12, HIGH); else digitalWrite(12, LOW); Serial.print("page="); Serial.print(page); Serial.print(" base=0x"); Serial.println(bases[page], HEX);} void loop() { bool btn = digitalRead(2); if (btn == LOW && lastBtn == HIGH) { page = page + 1; if (page > 3) page = 0; showPage(); delay(250); // антидребезг на грубую } lastBtn = btn;}Смотреть здесь стоит на три места. Массив bases — стартовые коды страниц: латиница для калибровки и три диапазона, где у моей панели нашлась кириллица; на вашем экране они будут другими — в этом и смысл сканера. Массив cyr — флаги для LED: на каких страницах горит метка. И сам вывод: цикл печатает write(bases[page] + i) шестнадцать клеток подряд — курсор ползёт вправо сам, а верхняя строка показывает диапазон кодов через lcd.print(x, HEX) — без префикса 0x, который я дописываю руками. Кнопка листает страницы по кругу: страница четвёртая возвращает первую, сброс не нужен.
Работа со сканером занимает пять минут: листаешь страницы, записываешь на бумажку, какой код какую букву рисует. У меня эта карта живёт в заметках телефона с пометкой «экран из синего набора» — второй экран из другой партии потребовал второй карты. Без карты каждая строка с русским текстом — лотерея; с картой — переписывание готового.
Пара слов о том, чего сканер не делает. Он не чинит кириллицу и не добавляет букв: он снимает карту, а печатать по ней — дело вашего скетча. Не покажет он и ячейки с кодами ниже 0x20 — там управляющие команды дисплея, а не знаки, и страница пустоты в начале таблицы — норма, а не поломка. И последнее: если экран при листании теряет символы или мигает, сначала проверьте контраст и питание из урока про потенциометр на V0 — карта, снятая с глючного экрана, врёт вместе с ним.
Симулятор: стенд инвентаризации
В тренажёре логика сканера та же: кнопка на D2 листает четыре страницы, LED на D12 горит на кириллических, серийный порт печатает границу кодов. Одна честная оговорка про картинку: движок симулятора не воспроизводит таблицы HD44780 — он рисует Unicode. Поэтому латинская страница выглядит как в жизни, а на кириллических клетках — чужие символы или пустота: ровно то, что увидит владелец экрана с английской прошивкой, запустив этот же сканер. На живой плате с русской прошивкой те же страницы развернутся в буквы — их раскладку и запишет ваша карта.
Зато серийный порт в тренажёре честный: каждая страница печатает base=0x80, base=0xA0 и так далее — по этим границам удобно вносить правки в массив bases, когда ваша карта покажет другие диапазоны.
- 1. Сколько кириллицы лежит в стандартной прошивке A00 контроллера HD44780?
- 2. Чем lcd.write(0xB0) отличается от lcd.print("текст")?
- 3. Почему готовая таблица кодов из статьи может не заработать на вашем экране?
- 4. Как правильно напечатать глиф из нулевой ячейки CGRAM?
- 5. Что напечатает Serial.print(176, HEX)?
- Кириллицы в стандартной прошивке A00 нет — там катакана; русский текст дают только «русские» партии экранов.
- Раскладка знакогенератора варьируется от партии к партии: сканер + своя карта на бумажке вместо чужих таблиц.
- Печать байта — lcd.write(): сырой номер ячейки; нулевую ячейку печатают write(byte(0)), потому что print обрывается на нуле.
- Serial.print(x, HEX) печатает коды в даташитном виде — сверяйте карту с датаполистом прямо в мониторе порта.
- Делясь проектом, прикладывайте сканер знакогенератора, а не таблицу кодов: у читателя другая партия.
Частые вопросы по теме урока
- Сколько кириллицы лежит в стандартной прошивке A00 контроллера HD44780?
- Стандартная прошивка содержит латиницу, цифры, пунктуацию и японскую катакану. Кириллица есть только в «русских» прошивках, которые шьют отдельные партии. Ни одной буквы: там латиница, цифры и катакана
- Чем lcd.write(0xB0) отличается от lcd.print("текст")?
- print шлёт строки и числа как есть, write — сырой байт-номер ячейки таблицы. Именно поэтому печать кодов знакогенератора идёт через write. write отправляет сырой байт — номер ячейки знакогенератора, экран рисует её содержимое
- Почему готовая таблица кодов из статьи может не заработать на вашем экране?
- Производитель шьёт в ROM ту таблицу, которую заказал торговец, и партии расходятся. Единственный надёжный путь — сканер и карта своего экрана. Партии экранов шьются разными раскладками знакогенератора
- Как правильно напечатать глиф из нулевой ячейки CGRAM?
- Нулевой байт внутри print обрывается как конец строки, поэтому печать пустая. Глиф нулевой ячейки выводят строго через lcd.write(byte(0)). lcd.write(byte(0)) — сырым байтом
- Что напечатает Serial.print(176, HEX)?
- Второй аргумент HEX задаёт шестнадцатеричный вид: печатается B0. Префикс 0x при этом не выводится — его дописывают руками. B0 — шестнадцатеричный вид числа