Настройка порога влажности в поливном контроллере доводила меня до бешенства: кнопки «плюс» и «минус» из урока про счётчик с антидребезгом сдвигали значение по единице, и чтобы пройти от 20% до 60%, надо было нажать сорок раз — в перчатках, на морозе, ещё и попадая по нужной из двух кнопок. Лечится это ручкой: энкодер KY-040 даёт двадцать щелчков на оборот, а при быстрой крутке шаг можно разгонять, и весь путь занимает четверть оборота. Ниже — связка «энкодер плюс LCD», где ручка крутит значение, экран его показывает крупно, а заодно разберём граблю, специфичную именно для I2C-экранов.
Механика у KY-040 — честная квадратура: ручка выдаёт два сигнала, CLK и DT, сдвинутые на четверть периода, и один механический щелчок — это полный цикл из четырёх состояний пары. Для крутилки значений всё упрощается до максимума: ловим фронт на CLK и смотрим DT — высокий значит крутят в одну сторону, низкий в другую. На один щелчок такой код срабатывает один раз, хотя фронтов внутри щелчка четыре, — и это нормально: остатки дребезга съедает короткая пауза. Полный разбор квадратуры и счёт без потерь на прерываниях я делал в уроке про энкодер EC11 — отсюда берём один вывод: один фронт на щелчок, всё остальное шум.
По пинам конфликтов нет: шина I2C живёт на A4 и A5, энкодеру хватает любых цифровых — в моём стенде CLK на D2, DT на D3, нажатие ручки SW на D4. План урока: крутилка значения с крупным выводом на экране, разгон шага при быстром вращении, нажатие ручки как вход в правку и выход из неё — и главный LCD-специфичный сюрприз, почему печать на экран по шине заставляет крутку щёлкать через раз. В симуляторе ручка настоящая: крутите кнопками по часовой и против, нажимайте SW.
Крутилка значения на экране
Значение живёт своей строкой и печатается в фиксированном месте — это первое правило крутилки: число не двигается по экрану никогда. Я ставлю его в колонки 0-3 нижней строки и затираю хвост пробелами при переходе 99 на 100, иначе на экране остаётся призрак последней цифры. Механику обновления только изменённых ячеек я разбирал в уроке про точечную перерисовку — крутилка живёт по тем же правилам: обновлять только ячейки с цифрами, clear() не звать вовсе. Подпись единиц ставьте прямо на экран: «POROG 45%» читается без инструкции, а голое «45» через неделю после сборки загадочно. На верхней строке удобно держать название параметра — ручка одна, а параметров в приборе бывает несколько, и между ними переключает та же ручка нажатием.
Разгон шага — то, что отличает удобную крутилку от тупой. Ловим интервал между щелчками через millis, как в уроке про millis вместо delay: прошло больше 300 мс — шаг единица, человек крутит аккуратно и ждёт точности; щелчки идут чаще 100 мс — шаг растёт до пяти и десяти. Арифметики три строки, а настройка из «сорока нажатий» превращается в полтора оборота. Тот же трюк стоит в кнопочном меню с вложенными настройками — там разгоняет автоповтор кнопки, здесь ручка сама показывает темп, и автоповтор не нужен вовсе.
Масштаб правки стоит показывать на экране: при разгоне шага печатайте рядом точку или звёздочку — «45% •» значит крутите быстро и шаг пятёрка, пустое место — шаг единица. Без этой метки человек не понимает, почему значение прыгает, и винит ручку; с меткой разгон читается честно. Границы диапазона тоже показывайте: при упоре в ноль или сотню мигание значения или рамка из знаков говорят «дальше некуда» без единого слова. Такие мелочи не стоят ни байта памяти, а прибор с ними заканчивает жизнь на полке, а не в ящике с пометкой «непонятный».
Теперь сюрприз, о который я споткнулся первым. На параллельном экране крутилка работала идеально, после переезда на I2C — щёлкала через раз: печать кадра по шине на 100 кГц занимает единицы миллисекунд, и пока loop занят шиной, фронты на CLK никто не читает. При быстрой крутке за эти миллисекунды ручка успевает проскочить два-три щелчка, и они теряются безвозвратно — значение отстаёт от ручки, и прибора хочется швырнуть. Лечится в порядке дешевизны: печатать только изменённые ячейки вместо clear, обновлять экран не чаще раза за 100 мс, и если крутка всё ещё рвётся — переносить чтение энкодера на прерывание, где фронты не зависят от загруженности loop. До прерываний я дошёл только на змейке с перерисовкой двадцати ячеек каждые 300 мс; для меню и настроек хватает точечной печати.
#include <Wire.h>#include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27, 16, 2);const int CLK = 2; // фронт — на этот пинconst int DT = 3; // направление — на этомconst int SW = 4; // нажатие ручки int znachenie = 40; // порог влажности, %bool lastCLK = HIGH;bool lastSW = HIGH;unsigned long tKlik = 0; void vyvod() { lcd.setCursor(0, 1); if (znachenie < 100) lcd.print(" "); // затирание хвоста: 99 -> 100 if (znachenie < 10) lcd.print(" "); lcd.print(znachenie); lcd.print("% ");} void setup() { pinMode(CLK, INPUT); pinMode(DT, INPUT); pinMode(SW, INPUT_PULLUP); lcd.init(); lcd.backlight(); Serial.begin(9600); lcd.setCursor(0, 0); lcd.print("POROG VLAGI:"); vyvod();} void loop() { bool clk = digitalRead(CLK); if (clk != lastCLK && clk == HIGH && millis() - tKlik > 5) { if (digitalRead(DT) == HIGH) znachenie = znachenie + 1; else znachenie = znachenie - 1; if (znachenie > 100) znachenie = 100; if (znachenie < 0) znachenie = 0; vyvod(); tKlik = millis(); } lastCLK = clk; bool sw = digitalRead(SW); if (sw == LOW && lastSW == HIGH) { // нажатие — сброс к заводскому znachenie = 40; vyvod(); } lastSW = sw; delay(5);}Нажатие ручки — недооценённая кнопка: она экономит и пин, и действие. В моих приборах SW закрывает вход в правку и выход из неё: короткое нажатие подтверждает значение и возвращает ручку к листанию пунктов, длинное — выходит без сохранения. Так в устройстве остаётся один орган управления вместо трёх кнопок, и в корпусе под него сверлится одно отверстие. Единицы измерения и границы я подписываю на той же строке: ручка крутит от нуля до сотни, и зажим на границах обязателен — крутилка без зажима уводит значение в минус, и термостат с порогом «минус сорок процентов» весело поливает грядку.
После выключения питания крутилка обнуляется — значение стоит в переменной, которая умирает с последним вольтом. Если настройка должна переживать розетку, сохраняйте её в EEPROM, но помните про ресурс: правка порога случается раз в месяц, и запись по нажатию SW расходует ячейку так медленно, что ресурса хватит на жизнь прибора. Не пишите на каждый щелчок — сотня щелчков настройки убьёт ячейку быстрее, чем сотня дней работы: дисциплина «запись по команде» здесь та же, что у одометра из соседнего урока. При старте читайте ячейку и проверяйте диапазон — пустая EEPROM читается как 255, и зажим вернёт значение в границы.
Кнопки против ручки
Моё правило после трёх приборов: если параметров больше двух и крутить приходится в рабочих рукавицах — энкодер, и не спорьте. Ручка работает вслепую, щелчки дают тактильный счёт без взгляда на экран, одна ось заменяет четыре кнопки, а SW закрывает вход-выход. Кнопки остаются честнее там, где важен фиксированный набор: меню со стрелками вверх-вниз проще в отладке, и для трёх-пяти пунктов я до сих пор собираю на кнопках. Максимум ума — гибрид: кнопки листают пункты, ручка крутит значение внутри пункта; автомат состояний для такой схемы соберите на готовом каркасе из урока про конечный автомат — там же про переключение режимов без кучи флагов.
Три способа ввода значений на LCD-приборе я гонял в разных комбинациях, и потолки у них разные, причём не там, где я ждал. Джойстик выигрывает по скорости листания длинных списков, но у него мёртвая зона и дребезг осей; матрица клавиатуры даёт кучу кнопок на восьми пинах, но требует платы; ручка требует места в корпусе и честного фильтра дребезга. Смотрите по своим рукам и по месту установки: для уличного щитка в перчатках мой выбор известен, для настольного стенда с тремя кнопками — тоже.
Ручкой крутят не только пороги. Яркость подсветки — первое, что просится на ручку: экран в спальне днём и ночью требует разных уровней, и связка «энкодер плюс ШИМ на ключе подсветки» собирается из этого урока и уроков о подсветке за один вечер. Таймер, громкость зуммера, уставки термостата — везде та же крутилка с тем же кодом, меняются подпись и границы. Заведите себе шаблон: функция vyvod() печатает значение, обработчик щелчка меняет его с зажимом и разгоном, SW подтверждает — и новый параметр приживается в приборе за десять минут. Шаблон окупается к третьему прибору: код крутилки кочует из проекта в проект почти без правок.
| Способ ввода | Сильная сторона | Где сдаётся |
|---|---|---|
| Две кнопки плюс-минус | Просто в отладке, два пина | Длинные диапазоны: десятки нажатий |
| Энкодер KY-040 | 20 щелчков на оборот, счёт вслепую, разгон шага | Место в корпусе, дребезг, фронтов на щелчок четыре |
| Джойстик XY | Быстрое листание длинных списков | Мёртвая зона, дребезг осей, три пина |
| Матрица 4x4 | Шестнадцать кнопок на восьми пинах | Отдельная плата, ввод выходит из строя целиком |
- 1. Один механический щелчок KY-040 — это сколько полных состояний пары CLK/DT?
- 2. Крутка на I2C-экране щёлкает через раз при быстром вращении. Первое дешёвое лечение?
- 3. Как DT подсказывает направление вращения на фронте CLK?
- 4. Что даёт разгон шага при быстрой крутке?
- 5. Зачем в крутилке зажим на границах диапазона?
- KY-040: 20 щелчков на оборот, фронт на CLK плюс чтение DT — по одному срабатыванию на щелчок
- Значение в фиксированном месте строки, хвост затирается пробелами при переходе 99 на 100
- Интервал между щелчками в millis управляет шагом: медленно — единица, быстро — пять и десять
- Печать по I2C занимает миллисекунды: точечная перерисовка и лимит обновлений спасают щелчки
- SW закрывает вход-выход правки; кнопки остаются честнее для коротких фиксированных меню
Частые вопросы по теме урока
- Один механический щелчок KY-040 — это сколько полных состояний пары CLK/DT?
- Квадратура даёт четыре состояния на щелчок. Крутилка ловит один фронт на CLK и читает DT — остальные события считаются шумом. Четыре: полный цикл квадратуры
- Крутка на I2C-экране щёлкает через раз при быстром вращении. Первое дешёвое лечение?
- Печать по шине занимает миллисекунды, и фронты теряются. Точечная перерисовка и лимит обновлений освобождают loop, прерывание — запасной ход. Точечная печать и обновление экрана не чаще раза за 100 мс
- Как DT подсказывает направление вращения на фронте CLK?
- Сигналы сдвинуты на четверть периода: в момент фронта CLK уровень DT указывает сторону вращения. Это и есть квадратура. Высокий — одна сторона, низкий — другая
- Что даёт разгон шага при быстрой крутке?
- Редкие щелчки — шаг единица, частые — пять и десять. Настройка из сорока нажатий превращается в полтора оборота без потери точности у цели. Длинные диапазоны проходят быстро, точность у цели сохраняется
- Зачем в крутилке зажим на границах диапазона?
- Без зажима ручка уводит значение за диапазон, и прибор получает бессмысленные параметры. Зажим — две строки if, а спасает грядку. Чтобы значение не ушло за допустимые пределы при инерции ручки