У контроллера инкубатора, который я собирал прошлой осенью, к финальной сборке кончилась арифметика: под экран ушли шесть сигнальных линий, под реле переворота — две, под датчик температуры — ещё одна, и на кнопку меню осталось ровно ничего. Схема была честная, из урока про параллельное подключение по четырём проводам, экран работал безупречно — просто плата больше не вмещала ничего, кроме него. Выход был один: переезд экрана на шину I2C, где вместо шести сигнальных живут две линии, SDA и SCL, общие для всех устройств на плате. Ниже — как я переношу скетч с LiquidCrystal на LiquidCrystal_I2C за десять минут, и три грабли, на которых этот короткий путь тормозится.
Сначала честное замечание: параллельный интерфейс не хуже I2C — он быстрее и предсказуемее, и для макетки с одним экраном это нормальный выбор навсегда. Переезд покупает пины и платит скоростью, поэтому смысл он имеет там, где пины дороже: третий датчик, шилд, серва — и всё это на одной Uno. Занятость шины при этом переоценивают: полный кадр 16x2 на стандартных 100 кГц занимает единицы миллисекунд, и если вы обновляете экран по тику таймера, а не в каждом проходе loop, соседи по шине этого почти не замечают. Дисциплину обновлений я разбирал в уроке про тик таймера — перед переездом стоит провести ревизию loop, иначе на новой схеме поедут грабли другой природы.
План урока: что меняется в скетче — ровно три строки, что меняется в железе — два провода вместо шести, и таблица «было — стало» для сверки. В симуляторе движок поднимает экран только по I2C, поэтому стенд собран на LiquidCrystal_I2C, и оговорка здесь честная: на реальной плате до переезда ваш экран висел на шести пинах параллельно, в симуляторе вы видите уже пересаженную версию. Кнопка на стенде сидит на пине D2 не случайно: в классической распиновке этот пин занят линией данных, и кнопке там делать нечего — именно после переезда он становится первым свободным.
Что меняется в скетче: три строки
Первое — конструктор. LiquidCrystal lcd(12, 11, 5, 4, 3, 2) перечисляет шесть пинов: RS, E и четыре линии данных; LiquidCrystal_I2C lcd(0x27, 16, 2) перечисляет адрес на шине и геометрию экрана — пины из скетча исчезают целиком. Второе — инициализация: begin(16, 2) заменяется на init() плюс backlight(). Третье — include: вместо LiquidCrystal.h подключаются Wire.h и LiquidCrystal_I2C.h. Всё остальное — setCursor, print, clear, createChar — переезжает без единой правки: интерфейсы у пары библиотек созваны так близко, что вызовы совпадают один в один. Я вожу переезд одним проходом по скетчу сверху вниз, и на файле среднего размера это четыре правки вместе с include.
Грабля прячется не в коде, а в менеджере библиотек. У LiquidCrystal_I2C несколько форков, и они не взаимозаменяемы: в одной версии инициализация зовётся lcd.init(), в другой lcd.begin(16, 2), в третьей метода backlight() нет вовсе, и подсветку зажимают вручную. Скетч с чужого форума не собирается — и начинается охота на ведьм среди проводов, хотя виновата версия. Моё правило: одна библиотека на все проекты и знать её методы наизусть; та, которой пользуюсь я, из репозитория johnrickman — автор перенёс её с GitHub на GitLab и больше не обновляет. Для зрелой библиотеки это нормальная судьба, но старые инструкции с молчащими ссылками теперь встречаются сплошь и рядом.
В железе переезд даже короче кода. Снимаете шесть сигнальных проводов — RS, E, D4-D7 — и питание панели не трогаете: экран по-прежнему ест свои пять вольт, подсветка свои 20-25 мА. Четыре провода модуля — VCC, GND, SDA, SCL — уходят на шину: SDA на A4, SCL на A5 у Uno и Nano, где уже живут все остальные I2C-устройства, если они есть. Контраст никуда не делся: подстроечник стоит на плате адаптера, и первую настройку я делаю сразу, чтобы не путать пустой экран с пустой шиной, — механика та же, что в уроке про контраст и потенциометр. Экран молчит после сборки? Путь короткий: стенд диагностики из шести шагов.
Порядок действий при переезде у меня теперь жёсткий, потому что обратный я испытал на себе: сначала сканер, потом скетч. Кажется логичнее переехать скетчем и смотреть на экран, но при перепутанном адресе или недотянутом проводе SCL вы будете искать граблю в коде, который только что написали, — а код окажется невиновен. Сканер с парой строк в Serial отвечает на вопрос «жива ли шина» до всякой печати, и после зелёного ответа правка трёх строк скетча срабатывает с первого раза. Ток при переезде не меняется: логика адаптера добавляет миллиамперы-два, подсветка берёт свои 20-60 мА в зависимости от партии и размера панели, и бюджет питания прибора не переписывается.
#include <Wire.h>#include <LiquidCrystal_I2C.h> // Было:// #include <LiquidCrystal.h>// LiquidCrystal lcd(12, 11, 5, 4, 3, 2); LiquidCrystal_I2C lcd(0x27, 16, 2); // адрес и геометрия вместо шести пиновconst int btn = 2; // освободившийся пин — под кнопку int schet = 0;bool lastBtn = LOW; void setup() { lcd.init(); // было: lcd.begin(16, 2); lcd.backlight(); // новое: подсветка за битом шины Serial.begin(9600); lcd.setCursor(0, 0); lcd.print("PEREJEZD GOTOV"); lcd.setCursor(0, 1); lcd.print("nazhatij: 0");} void loop() { bool b = digitalRead(btn); if (b == HIGH && lastBtn == LOW) { schet = schet + 1; lcd.setCursor(0, 1); lcd.print("nazhatij: "); lcd.print(schet); lcd.print(" "); } lastBtn = b; delay(20);}Про скорость без иллюзий. Параллельный обмен в 4-битном режиме шлёт байт двумя тетрадами со стробом — это микросекунды на символ; адаптер возит те же тетрады по шине на 100 кГц, и полный кадр 16x2 стоит единицы миллисекунд. Для счётчиков, меню и термометров разницы нет: обновление раз в секунду, даже десять раз в секунду, шину не напрягает. Я почувствовал разницу только на змейке с перерисовкой двадцати ячеек каждые 300 мс и на прокрутке длинных строк: там параллельный вариант остаётся плавнее. Если проект крутит анимации, не переезжайте ради моды — переезд покупает пины, а платит именно скоростью.
Скорость — не единственная плата. На шину смотрит ещё один собеседник, и при добавлении второго экрана или датчика придётся разводить адреса: механику я разбирал в уроке про два экрана на одной шине. Зато освободившиеся пины — честная валюта: в инкубаторе на место линий данных встали кнопка меню и зуммер, и схема закрылась без покупки Mega. Стенд ниже показывает ровно этот момент — счётчик нажатий на кнопке, которая в старой распиновке была линией данных D7. Serial печатает старую и новую разводку рядом, чтобы разница была перед глазами, а не по памяти.
Арифметика пинов и первый запуск
Считаем честно. Ушли шесть сигнальных линий — RS, E и D4-D7; пришли две шинные, SDA и SCL, которые на Uno и Nano делятся со всеми I2C-устройствами разом. Чистый выигрыш — четыре пина, и на Uno это четверть всех цифровых: такой переезд окупается раньше, чем замена платы на Mega. Питание экрана — VCC и GND — остаётся на месте и в арифметику не входит: это не сигнальные линии, а питание, оно было и будет. В скетче же изменений меньше всего: печать, курсор и свои глифы живут как жили — всё различие между библиотеками упирается в три строки из начала урока.
Первый запуск после переезда заканчивайте сканером, а не надеждой. Если в скетче 0x27, а модуль отвечает на 0x3F, картина совпадёт с любой адресной граблёй: подсветка горит, печати нет, и виноватым назначат только что отпаянные провода. Сетки обеих партий переходников разобраны в уроке про выбор между 0x27 и 0x3F — минута сканера закрывает вопрос до разборки корпуса. И последняя валюта переезда — жгут: вместо двух шлейфов данных по всей макетке остаются две линии, которые можно вести в одном канале с питанием. Чем короче жгут, тем меньше наводок и тем спокойнее прибор в корпусе.
И про то, что переносится само. Свои глифы живут в знакогенераторе панели, а адаптер — просто носильщик байтов: createChar и все восемь слотов переезжают без единой правки. Кириллица тоже — таблица знакогенератора не зависит от способа подключения, и русский текст после переезда выглядит так же; механика знакогенератора разобрана в уроке про кириллицу. Единственное, что стоит перепроверить после переезда, — тайминги анимаций: то, что рисовалось плавно на параллельном интерфейсе, на шине может отставать, и кадр придётся разредить. Это не поломка, а та самая плата скоростью, о которой шла речь в начале урока.
| Что меняется | Было: LiquidCrystal | Стало: LiquidCrystal_I2C |
|---|---|---|
| Конструктор | lcd(12, 11, 5, 4, 3, 2) | lcd(0x27, 16, 2) |
| Инициализация | lcd.begin(16, 2) | lcd.init() + lcd.backlight() |
| Сигнальные провода | Шесть: RS, E, D4-D7 | Два: SDA и SCL |
| setCursor, print, clear | Как было | Как было — без правок |
| Подсветка | Горит всегда от 5 В | За ключом, битом шины |
- 1. Сколько пинов освобождает переезд с 4-битного параллельного подключения на I2C?
- 2. Что заменяет lcd.begin(16, 2) при переезде на LiquidCrystal_I2C?
- 3. Экран после переезда печатает текст, но подсветка тёмная. Причина?
- 4. Скетч с форума на LiquidCrystal_I2C не собирается. С чего начать?
- 5. Когда параллельный интерфейс честнее переезда на I2C?
- Переезд — три строки: конструктор, init() с backlight() вместо begin(16, 2), два include
- Чистый выигрыш — четыре пина: минус шесть сигнальных, плюс две линии шины
- Подсветка на I2C за ключом на шине: без backlight() экран жив, но тёмен
- У LiquidCrystal_I2C несколько несовместимых форков: держите одну библиотеку и знайте её методы
- setCursor, print, clear переезжают без правок; параллельный остаётся честнее для анимаций
Частые вопросы по теме урока
- Сколько пинов освобождает переезд с 4-битного параллельного подключения на I2C?
- Уходят RS, E и D4-D7, приходят SDA и SCL. Чистый выигрыш — четыре пина, и они общие с остальными I2C-устройствами. Четыре: минус шесть сигнальных, плюс две линии шины
- Что заменяет lcd.begin(16, 2) при переезде на LiquidCrystal_I2C?
- У I2C-версии init() сообщает контроллеру геометрию, backlight() зажигает подсветку за ключом на шине. begin остаётся методом параллельной библиотеки. lcd.init() плюс lcd.backlight()
- Экран после переезда печатает текст, но подсветка тёмная. Причина?
- На параллельной схеме подсветка горела без кода, на I2C она за битом шины. Пропущенный backlight() даёт живой текст в полумраке. В setup не вызван lcd.backlight()
- Скетч с форума на LiquidCrystal_I2C не собирается. С чего начать?
- У LiquidCrystal_I2C несколько несовместимых форков: в одной lcd.init(), в другой lcd.begin(16, 2). Держите одну библиотеку и знайте её методы. Проверить версию библиотеки: форки различаются методами init и begin
- Когда параллельный интерфейс честнее переезда на I2C?
- Переезд покупает пины и платит скоростью: для меню и термометров разницы нет, для змейки и прокрутки она заметна. Когда проект гонит анимации и важна скорость перерисовки