Будильник для бабушки я собирал на ATtiny13 — крошечном чипе с килобайтом флеша. Скетч с LiquidCrystal в него не влез: библиотека вместе с инициализацией съедала больше половины памяти. Пришлось выкинуть её и разговаривать с дисплеем напрямую — и только тогда я впервые увидел, что такое lcd.print(): два полубайта, два строба, пауза. Второй тупик случился раньше, ещё на Uno: я подключил все восемь линий данных по картинке из интернета, а экран молчал — скетч работал в 4-битном режиме и младшие линии не читал. Обоих граблей ниже будет отдельный разговор.
Разбирать HD44780 без библиотеки стоит не только ради сэкономленного килобайта — ради понимания. После этого урока lcd.init() перестаёт быть магией: вы будете знать, какие байты уходят в дисплей при включении, почему после подачи питания надо ждать сорок миллисекунд и чем команда отличается от данных. Контроллер у 16x2 и 20x4 один и тот же, мы уже гуляли по его памяти в уроке про устройство дисплея (/lesson/lcd/hd44780-chto-vnutri-lcd/) — теперь поговорим с ним напрямую.
Шесть проводов и строб E
В 4-битном режиме у дисплея шесть рабочих проводов: RS, E и линии D4–D7. Седьмой, RW — чтение/запись, — я всегда сажаю на землю: мы только пишем. RS — переключатель смысла байта: при LOW дисплей трактует байт как команду, при HIGH — как код символа для экрана. E — строб: уровни на D4–D7 выставляются заранее, а по спаду E контроллер защёлкивает полубайт; импульс должен держаться дольше 450 наносекунд — при digitalWrite с его парой микросекунд это выполняется само. Полный байт уходит за два такта: старший полубайт, строб, младший полубайт, строб. Схему этих шести проводов мы раскладывали в уроке про 4 бита (/lesson/lcd/lcd-4-bita-shema-provodov/) — теперь она оживёт.
У каждой команды есть время исполнения: мелочи считаются около 37 микросекунд, а clear и home — целых 1,52 миллисекунды. Библиотека после каждой команды просто ждёт; продвинутые реализации читают флаг занятости — но для этого нужен RW на управлении и D7 на чтение, то есть два лишних провода. Моё правило с границей: когда пинов мало — а в самоделках их всегда мало, — честные паузы по даташиту стоят дешевле; чтение флага берите, когда дисплей в проекте один, а свободных ног много.
Ещё нюанс, который стоит проговорить заранее: полубайты кладут именно на D4–D7, а не на D0–D3. Это не требование контроллера, а договорённость классической схемы — все библиотеки по умолчанию живут с ней, и все уроки, включая этот, пишутся под неё. Если ваш экран торчит из самодельной платы с другими линиями — просто поправьте номера пинов в одном месте: в массиве, который вы увидите в коде ниже.
| Байт | Что делает | Сколько ждать |
|---|---|---|
| 0x01 | очистить экран, курсор в 0,0 | 1,52 мс |
| 0x02 | курсор в 0,0 без стирания текста | 1,52 мс |
| 0x04 / 0x06 | после символа курсор влево / вправо (обычный режим — 0x06) | 37 мкс |
| 0x08 / 0x0C | экран выключен / включён (подсветка — совсем другое) | 37 мкс |
| 0x0E / 0x0F | показать курсор / курсор с миганием | 37 мкс |
| 0x18 / 0x1C | сдвинуть весь экран влево / вправо | 37 мкс |
| 0x80 + адрес | курсор в ячейку памяти DDRAM — это и есть setCursor | 37 мкс |
Самое загадочное место — старт. После подачи питания контроллер дисплея просыпается в 8-битном режиме и ничего о вас не знает. Даташит велит: подождать 40 миллисекунд; послать полубайт 0x3 и подождать 4,1 миллисекунды; послать 0x3 снова и подождать 100 микросекунд; послать 0x3 в третий раз; и только теперь 0x2 — дисплей переключился в 4-битный режим. Троекратное повторение не ритуал, а синхронизация: какой бы фазой внутреннего автомата ни проснулся контроллер, первая посылка его выравнивает. Дальше скучно и красиво: 0x28 — четыре бита, две строки, знаки 5x8; 0x0C — экран включён, курсор невидим; 0x06 — курсор шагает вправо после каждого символа; 0x01 — чистый экран; 0x80 — курсор в начало. Ровно эти байты внутри себя шлёт lcd.init() — теперь её исходник читается без переводчика. Запись вида 0x28 — шестнадцатеричная система, которую мы раскладывали в уроке про системы счисления (/lesson/serial/bin-hex-dec-serial/).
// Пины: RS 7, E 8, D4-D7 = 9, 10, 11, 12. RW садим на GND.const int RS = 7;const int E = 8;int dataPins[4] = {9, 10, 11, 12}; // один полубайт: уровни на D4-D7, затем импульс на Evoid sendNibble(byte n) { for (int i = 0; i < 4; i++) { digitalWrite(dataPins[i], (n >> i) & 1); } digitalWrite(E, HIGH); delayMicroseconds(2); // строб дольше 450 нс - с запасом digitalWrite(E, LOW); // HD44780 защёлкивает полубайт по спаду delayMicroseconds(50);} // байт за два такта: старший полубайт, потом младшийvoid sendByte(byte b, bool isData) { digitalWrite(RS, isData); // LOW - команда, HIGH - символ sendNibble(b >> 4); sendNibble(b & 0x0F); delayMicroseconds(50); // команда исполняется около 37 мкс} // Старт по даташиту: delay(40); sendNibble(0x3); delay(5);// sendNibble(0x3); delayMicroseconds(100); sendNibble(0x3);// sendNibble(0x2); затем sendByte(0x28) и sendByte(0x0C).Тренажёр: шина оживает по кнопке
Тренажёр проигрывает инициализацию шаг за шагом. Кнопка D2 — следующий шаг; Serial показывает, что ушло бы в шину: rs=0 — значит команда, полубайты hi= и lo=, и сколько ждать после шага. Светодиод D12 мигает на каждом стробе E — два мигания на полный байт и одно на полубайт подбора. Тумблер D3 включает двоичный показ: строка bity=101000 — это 0x28 в двоичном виде, причём ведущие нули Serial не печатает, не удивляйтесь короткой записи. После девятого шага — 0x80, курсор в нулевую ячейку — дисплей настроен, и следующие нажатия шлют данные: rs=1, байт 0x41 — латинская буква A; на настоящем экране она появилась бы в верхнем левом углу. Экран стенда дублирует номер шага и имя команды — по нему удобно следить, где вы в последовательности.
Порядок шагов в тренажёре совпадает с даташитом один в один, поэтому по Serial удобно сверять чужие библиотеки: если ваша реализация шлёт что-то другое в первые четыре шага, она либо нечестно ждёт, либо рассчитывает на 8-битный модуль. Самая частая самоделка, которую я встречал, — пропущенная пауза 4,1 мс после первого полубайта: на Uno с быстрым стартом везёт, а на плате с медленно поднимающимся питанием дисплей после включения показывает ряд квадратов и отказывается отвечать.
Команда 0x80 стоит отдельного разговора: она не просто ставит курсор в угол — она записывает адрес в памяти знакомест, DDRAM. Адрес второй строки у 16x2 — 0x40, а у 20x4 строки лежат в памяти вразнобой: 0x00, 0x40, 0x14, 0x54 — именно поэтому печать без setCursor иногда уезжает не туда, подробности в уроке про адресацию строк (/lesson/lcd/lcd-20x4-adresaciya-strok/). Свои символы из урока про createChar (/lesson/lcd/createchar-svoi-simvoly-lcd/) тоже шьются этими же командами: 0x40 плюс адрес слота в CGRAM, затем восемь байтов пикселей уже с rs=1. А пара 0x18 и 0x1C двигает весь экран влево и вправо — готовая бегущая строка без единой перерисовки: напечатайте фразу длиннее экрана и шлите 0x1C каждые 300 миллисекунд. И последнее: если дисплей вдруг ведёт себя странно после старта — проверьте, что в скетче есть вся стартовая последовательность, а не только 0x28: половина загадочных квадратов в моей практике родилась из урезанного init (/lesson/lcd/lcd-adres-0x27-ili-0x3f/).
- Шесть проводов: RS (команда или символ), E (строб дольше 450 нс), D4–D7; RW — на землю
- Байт уходит за два строба: старший полубайт, затем младший
- Старт: 40 мс после питания, полубайт 0x3 трижды (4,1 мс, 100 мкс), затем 0x2 — и вы в 4-битном режиме
- 0x28, 0x0C, 0x06, 0x01, 0x80 — Function set, Display on, Entry mode, Clear, курсор в 0,0
- Команда — около 37 мкс, clear и home — 1,52 мс: честные паузы дешевле чтения флага занятости
- 1. Чем для дисплея отличается байт при RS=LOW от байта при RS=HIGH?
- 2. Сколько стробов E уходит на один полный байт в 4-битном режиме?
- 3. Зачем после подачи питания полубайт 0x3 шлётся три раза подряд?
- 4. Сколько исполняется команда 0x01 (clear)?
- 5. Нужно, чтобы надпись ползла по экрану вправо. Что и с какой периодичностью шлём?
Частые вопросы по теме урока
- Чем для дисплея отличается байт при RS=LOW от байта при RS=HIGH?
- RS — переключатель смысла байта: LOW — команда контроллеру, HIGH — код знака для экрана. Остальные линии при этом те же. При LOW байт — команда, при HIGH — код символа
- Сколько стробов E уходит на один полный байт в 4-битном режиме?
- Байт режется на два полубайта: сначала старший, строб, потом младший, ещё строб. Итого два импульса E на байт. Два
- Зачем после подачи питания полубайт 0x3 шлётся три раза подряд?
- Контроллер просыпается в 8-битном режиме в непредсказуемой фазе автомата: тройная посылка выравнивает его, после чего 0x2 переводит в 4 бита. Так контроллер синхронизируется из любого стартового состояния
- Сколько исполняется команда 0x01 (clear)?
- Clear и home — самые медленные команды: 1,52 мс. Остальные мелкие команды укладываются примерно в 37 мкс. 1,52 мс
- Нужно, чтобы надпись ползла по экрану вправо. Что и с какой периодичностью шлём?
- 0x1C сдвигает весь экран вправо — контроллер делает это сам, без перерисовки. 0x06 — лишь направление курсора после печати. 0x1C каждые 300 мс