Балконная метеостанция была собрана и работала, но смотреть на неё было больно: экран 16x2 раз в полсекунды вздрагивал, текст на мгновение исчезал и рисовался заново. Температуру при этом показывал верную, датчик жил, кнопки работали — а я уже неделю перепаивал провода питания, потому что был уверен в плохом контакте. Контакт был ни при чём: в loop() у меня стояла связка lcd.clear(), setCursor() и полный вывод обеих строк. Цикл крутился каждые 100 миллисекунд, и каждый раз дисплей сначала стирал всё, а потом рисовал заново — почти треть времени экран был пустым, и глаз честно видел это как мерцание. Одна удалённая строка вылечила то, что я искал паяльником.
В этом уроке разберём три разных вида моргания LCD — у них разные причины и разное лечение, и лечить не тем средством значит потерять вечер. Соберём правило вывода без clear(), поговорим про цифры-хвосты и про частоту обновления, а в конце сделаем в симуляторе стенд, где светодиод мигает ровно с частотой перерисовки — мерцание станет видимым в буквальном смысле. Если вы ещё не выводили текст на LCD вовсе, начните с урока про первый текст на I2C — там схема из четырёх проводов и первые команды.
Найдите свой вид мерцания
| Симптом на экране | Причина | Лечение |
|---|---|---|
| Весь текст мигает раз за цикл: исчез — появился | lcd.clear() и полная перерисовка в каждом проходе loop() | Убрать clear(), печатать поверх, хвосты затирать пробелами |
| После короткого числа остались старые цифры: 9 стало 10, а на экране 100 | Переменная длина числа при перезаписи поверх | Паддинг: дописывать пробелы до фиксированной ширины |
| Буквы дрожат и рябят, как за мутным стеклом | Вывод десятки раз в секунду, каждый проход loop() | Обновлять по изменению значения или по таймеру 200–500 мс |
| Экран гаснет целиком и просыпается от нажатия | Сон подсветки по таймеру бездействия — штатная механика | Это не поломка; таймаут настраивается в скетче |
Первые три строки таблицы — про одно и то же семейство: скетч слишком много рисует. Разберём, откуда берутся миллисекунды. Команда clear() у контроллера HD44780 — это не «стереть кнопку на экране», а записать пробелы во все 80 ячеек памяти DDRAM: 16 столбцов на 2 строки. Каждый знак по шине I2C через переходник PCF8574 — это отдельный обмен, при стандартных 100 кГц на символ уходит около половины миллисекунды. Получаем арифметику: clear плюс перерисовка 32 знаков текста — это примерно 25–35 миллисекунд непрерывной передачи. Если цикл обновления 100 мс, экран четверть-треть времени проводит в состоянии «стираю-рисую», и мерцание — не баг, а честная съёмка этой работы. Как устроена эта память и почему курсор после clear возвращается в левый верхний угол, я разбирал в уроке про внутренности HD44780.
Моё правило с границей применимости: clear() остаётся только для смены экрана целиком — переход в другое меню, экран game over, перезапись после сна. Тогда он выполняется один раз по событию, и глаз его не видит. А для показаний, которые обновляются на одном и том же месте, вместо очистки печатаем поверх: шапку выводим один раз в setup(), меняем только цифры. Дёшево, скучно и работает.
#include <Wire.h>#include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27, 16, 2); void setup() { lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print("Temp:"); // шапка рисуется один раз lcd.setCursor(0, 1); lcd.print("Vlaga:");} void loop() { int t = 25; // пусть это показание с датчика lcd.setCursor(6, 0); // встали ровно на место цифр lcd.print(t); lcd.print(" C "); // пробелы затирают след прошлой цифры delay(250); // и так не каждый проход loop}Второй источник «мерцания», который на самом деле им не является: число меняет длину. Показания 9, потом 10, потом 100 — при перезаписи поверх старые цифры никуда не деваются, и на экране красуется 9100. Лечится паддингом: печатаем значение, а следом — пробелы, дополняющие поле до фиксированной ширины. Для диапазона 0–999 это два пробела после числа от 0 до 9 и один после 10–99; для температуры с одним знаком после запятой хватает одного. Я собрал себе привычку всегда печатать числа на LCD через маленькую функцию-обёртку, которая дополняет поле, — и граблю с «хвостами» перестала существовать. Подробный разбор ширины поля, дробей и ведущих нулей живёт в отдельном уроке про числа на LCD.
Теперь частота. Датчик меняет показания хоть каждый проход loop(), но человек читает экран глазами, которым хватает двух-четырёх обновлений в секунду. Мой ориентир: метеостанции и термометры — раз в 500 мс, таймеры и часы — раз в 100–250 мс, счётчик оборотов — по факту изменения. Техника одна и та же: таймер на millis() без единого delay() в цикле, как в уроке про millis вместо delay. Но здесь есть и вторая сторона, на которой я обжигался: если обновлять только по изменению значения, дребезжащий датчик между двумя целыми градусами заставит экран перерисовываться по сто раз в секунду. Поэтому для шумных показаний я совмещаю оба фильтра — и по времени, и по изменению: рисуем, если прошло 250 мс И значение изменилось хотя бы на единицу.
И только когда код вычищен, а экран всё равно подёргивается, приходит черёд физики. Признак железной причины простой: мерцает даже при почти пустом loop(). Шевелите жгут проводов у дисплея: dupont-перемычки на макетке, особенно длинные, любят дрожать от малейшего сквозняка, и пропадающий на миллисекунду контакт выглядит как рябь на экране — это самый коварный вариант, потому что виноват не код и не дисплей. Разбор таких живодных случаев с проводами и контактами я делал в уроке про плохой контакт в макетке. Отдельно держите в голове штатное погасание: если экран гаснет целиком через десять минут бездействия и просыпается от кнопки — это не мерцание, это сон подсветки, механика и токоподводы которой разобраны в уроке про подсветку и сон дисплея.
Стенд: светодиод показывает частоту перерисовки
Мерцание удобно изучать, когда оно стало видимым не только на дисплее. В стенде ниже светодиод на D12 вспыхивает на каждый полный вывод значения — то есть частота его миганий в точности равна частоте перерисовки экрана. Тумблер на D3 включает «плохой режим» — clear() и перерисовка каждые 200 мс, как в моей балконной истории. В честном режиме вывод происходит раз в секунду при смене значения, плюс по кнопке на D2 — перерисовка по событию. В Serial печатается счётчик перерисовок: запустите и сравните цифры двух режимов.
Что вы только что видели в цифрах: в плохом режиме счётчик перерисовок растёт примерно в пять раз быстрее, и светодиод моргает так же часто, как моргал бы реальный экран. При этом показание — секунды — меняется в обоих режимах одинаково: один раз в секунду. Восемьдесят процентов работы в плохом режиме были выброшены в пустоту. Отсюда два вопроса, которые я теперь задаю любому дисплею перед выводом: что именно изменилось с прошлой перерисовки, и как часто человек способен это прочитать. Если ответы «почти ничего» и «раз в полсекунды» — а скетч стирает и рисует всё десять раз за это время, вы нашли своё мерцание. Этот же скетч на настоящей Uno с реальным LCD работает один в один: пины и команды честные.
- Мерцание почти всегда значит «скетч стирает и рисует больше, чем нужно»: clear() в цикле — главный подозреваемый
- Вместо clear() печатайте поверх: шапку один раз в setup(), значения — по месту, хвосты затирая пробелами
- Числа переменной длины оставляют «хвосты» из старых цифр — лечится паддингом до фиксированной ширины поля
- Обновляйте экран по таймеру 200–500 мс или по изменению значения; для шумных показаний — оба фильтра сразу
- Если моргает при пустом loop() — виноваты контакты и питание, а не код; гаснущий целиком экран — это сон подсветки, а не поломка
- 1. Скетч каждые 100 мс вызывает lcd.clear() и печатает обе строки заново. Что увидит глаз?
- 2. Показание выросло с 9 до 10, а на экране при перезаписи поверх осталось 910. Что происходит?
- 3. Как правильно выводить показания термометра на LCD, чтобы экран не дрожал?
- 4. Код вычищен: clear() убран, вывод раз в 500 мс. Экран всё равно слегка рябит при пустом остальном цикле. Куда смотреть?
- 5. Экран гаснет целиком через десять минут бездействия и оживает от кнопки. Поломка ли это?
Частые вопросы по теме урока
- Скетч каждые 100 мс вызывает lcd.clear() и печатает обе строки заново. Что увидит глаз?
- Clear плюс перерисовка 32 знаков по I2C — это 25–35 мс передачи. При цикле 100 мс экран заметную долю времени проводит пустым — это и видно как мерцание. Экран вздрагивает: часть цикла он пустой или в процессе перерисовки
- Показание выросло с 9 до 10, а на экране при перезаписи поверх осталось 910. Что происходит?
- Перезапись поверх затирает только те ячейки, куда попали новые символы. Двузначное число занимает на клетку больше — лишняя клетка хранит прошлую цифру. Лечится паддингом пробелами. Старые цифры никуда не деваются: число стало длиннее на один знак
- Как правильно выводить показания термометра на LCD, чтобы экран не дрожал?
- Человеку хватает двух-четырёх обновлений в секунду. Таймер на millis() не блокирует цикл, печать поверх без clear() не даёт экрану пустеть, пробелы убирают хвосты цифр. По таймеру на millis() раз в 250–500 мс, без clear(), с затиранием пробелами
- Код вычищен: clear() убран, вывод раз в 500 мс. Экран всё равно слегка рябит при пустом остальном цикле. Куда смотреть?
- Если программа почти ничего не делает, а рябь осталась — это железо: дрожащие dupont-перемычки или плохой контакт в макетке выглядят на LCD как мерцание. В контакты: шевелить провода у дисплея, проверить питание
- Экран гаснет целиком через десять минут бездействия и оживает от кнопки. Поломка ли это?
- Автосон подсветки — полезная механика для сборок на батарейках: экран гаснет по таймеру бездействия и будится событием. Таймаут настраивается в скетче. Нет: это штатный сон подсветки по таймеру бездействия