Код скетча
#include <LiquidCrystal_I2C.h>
// Меню из одной ручки — так устроены паяльные станции, 3D-принтеры и
// лабораторные блоки питания.
//
// Два состояния: «листаем пункты» и «правим значение». Поворот в
// первом двигает курсор, во втором меняет число; нажатие переключает
// состояния. Всё управление — один энкодер и одна переменная editing.
//
// Надписи на экране латинские: в знакогенераторе HD44780 кириллицы
// нет, и русская строка превратилась бы в значки.
LiquidCrystal_I2C lcd(0x27, 16, 2);
const int CLK = 2, DT = 3, SW = 4;
volatile int clicks = 0;
void onClk() { if (digitalRead(DT)) clicks++; else clicks--; }
const char *NAMES[] = { "Temp", "Fan", "Light" };
const int LIMIT[] = { 99, 100, 255 };
int values[] = { 40, 50, 128 };
const int COUNT = 3;
int item = 0;
bool editing = false;
bool dirty = true;
void draw() {
lcd.setCursor(0, 0);
lcd.print(editing ? "Set " : "Menu");
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print(editing ? "*" : ">");
lcd.print(NAMES[item]);
lcd.print(": ");
lcd.print(values[item]);
lcd.print(" ");
}
void setup() {
lcd.init();
lcd.backlight();
pinMode(CLK, INPUT);
pinMode(DT, INPUT);
pinMode(SW, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(CLK), onClk, FALLING);
}
bool lastSw = HIGH;
void loop() {
noInterrupts();
int n = clicks;
clicks = 0;
interrupts();
if (n) {
if (editing) values[item] = constrain(values[item] + n, 0, LIMIT[item]);
else item = (item + n % COUNT + COUNT) % COUNT;
dirty = true;
}
// Нажатие — по спаду, а не по уровню: иначе удержание кнопки
// переключало бы режим на каждом проходе loop().
bool sw = digitalRead(SW);
if (sw == LOW && lastSw == HIGH) {
editing = !editing;
dirty = true;
delay(20); // дребезг контактов
}
lastSw = sw;
if (dirty) { draw(); dirty = false; }
}