Добавить в избранное

Энкодер KY-040 с Arduino: ручка громкости, меню и навигация

Чтение ≈ 4 мин

Подключаем роторный энкодер KY-040 к Arduino: как он кодирует поворот, чтение по опросу и по прерываниям, кнопка на энкодере, меню и регулировка — вся работа с энкодером в одном месте.

Что такое энкодер

Роторный энкодер — ручка-крутилка как на магнитоле: крутится в обе стороны без конца, щёлкает по фиксированным позициям, часто имеет встроенную кнопку (нажали — щёлк). KY-040 — популярный модуль: энкодер + подтяжки + кнопка, 5 выводов.

Принцип: при повороте два сигнальных контакта (CLK и DT) выдают прямоугольные импульсы со сдвигом фазы. По тому, какой канал «поднялся» первым — определяем направление. Кнопка — отдельный вывод SW.

Подключение KY-040

KY-040 +  ── 5V
KY-040 GND ── GND
KY-040 CLK ── Arduino D2   (прерывание!)
KY-040 DT  ── Arduino D3   (прерывание)
KY-040 SW  ── Arduino D4   (кнопка)

CLK и DT лучше сажать на пины с прерываниями (2 и 3 на Uno) — тогда поворот ловится мгновенно, даже когда loop занят. Подтяжки на модуле уже есть — пины настраиваем как обычные INPUT.

Чтение без прерываний — простой опрос

const int CLK = 2, DT = 3;
int lastClk;

void setup() {
    pinMode(CLK, INPUT);
    pinMode(DT, INPUT);
    lastClk = digitalRead(CLK);
    Serial.begin(9600);
}

void loop() {
    int clk = digitalRead(CLK);
    if (clk != lastClk) {                // произошёл фронт
        if (digitalRead(DT) != clk) {
            Serial.println("по часовой");
        } else {
            Serial.println("против часовой");
        }
    }
    lastClk = clk;
}

Работает, но теряет быстрые повороты — если крутануть резко, импульсы пропадут между опросами.

Чтение по прерываниям — надёжно

const int CLK = 2, DT = 3;
volatile int counter = 0;
volatile bool changed = false;

void isrClk() {
    if (digitalRead(CLK) == digitalRead(DT)) counter--;
    else counter++;
    changed = true;
}

void setup() {
    pinMode(CLK, INPUT);
    pinMode(DT, INPUT);
    attachInterrupt(digitalPinToInterrupt(CLK), isrClk, CHANGE);
    Serial.begin(9600);
}

void loop() {
    if (changed) {
        noInterrupts();
        int c = counter;
        changed = false;
        interrupts();
        Serial.println(c);
    }
}

Прерывание по CHANGE на CLK срабатывает на каждом фронте. counter — volatile (изменяется в прерывании), чтение — под noInterrupts, чтобы не поймать полуслово.

Каждый щелчок KY-040 даёт 4 перехода — делите на 4 или фильтруйте, чтобы «один щелчок = один шаг»:

int steps = counter / 4;   // полный щелчок = 4 перехода

Кнопка энкодера

const int SW = 4;
pinMode(SW, INPUT_PULLUP);

if (digitalRead(SW) == LOW) {   // нажата
    // обработка — с антидребезгом
}

Практическое применение: регулятор громкости

int volume = 50;   // 0..100

void loop() {
    if (changed) {
        int delta = counter; counter = 0; changed = false;
        volume = constrain(volume + delta / 4, 0, 100);
        Serial.println(volume);
    }
}

Меню на энкодере

Классический сценарий — навигация по пунктам:

const char* items[] = {"Настройки", "Яркость", "Таймер", "Выход"};
const int N = 4;
int pos = 0;

void loop() {
    if (changed) {
        pos += counter / 4; counter = 0; changed = false;
        pos = (pos % N + N) % N;             // циклически
        showItem(pos);
    }
    if (digitalRead(SW) == LOW) {            // выбор
        delay(30);                           // антидребезг
        if (digitalRead(SW) == LOW) enterItem(pos);
    }
}

Типичные проблемы

  • Пропуски шагов: опрос без прерываний не успевает — переходите на attachInterrupt.
  • Один щелчок = 4 шага: делите на 4 или фильтруйте по полному циклу.
  • Счётчик гуляет при отпускании: у дешёвых энкодеров дребезг контактов — добавьте аппаратный фильтр (RC на CLK/DT) или программный дебаунс 2–5 мс.
  • Направление перепутано: поменяйте CLK и DT местами или знак счётчика.
  • Кнопка читает наоборот: INPUT_PULLUP — нажатие это LOW.

Итог

Энкодер — отличный ввод для меню и регулировок: CLK/DT на прерывания, кнопка на INPUT_PULLUP, делить на 4 перехода за щелчок, volatile для счётчика и антидребезг для кнопки. С этим строят регуляторы громкости, настройки температуры, навигацию по экрану — привычный «крутилочный» интерфейс.

Полный проект: энкодер как регулятор

const int CLK = 2, DT = 3, SW = 4;
volatile int counter = 0;
int value = 0;
bool lastSw = HIGH;

void isr() {
    if (digitalRead(CLK) == digitalRead(DT)) counter--;
    else counter++;
}

void setup() {
    pinMode(CLK, INPUT);
    pinMode(DT, INPUT);
    pinMode(SW, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(CLK), isr, CHANGE);
    Serial.begin(9600);
}

void loop() {
    noInterrupts();
    int delta = counter;
    counter = 0;
    interrupts();

    value = constrain(value + delta / 4, 0, 100);   // 4 перехода за щелчок
    if (delta) Serial.println(value);

    bool sw = digitalRead(SW);
    if (sw == LOW && lastSw == HIGH) {              // нажали
        Serial.println("клик");
    }
    lastSw = sw;
}

Меню на энкодере

const char* items[] = {"Назад", "Яркость", "Температура", "Режим"};
int pos = 0;

void loop() {
    if (delta) {
        pos += delta / 4;
        pos = (pos % 4 + 4) % 4;
        lcd.clear();
        lcd.print(items[pos]);
    }
    if (clicked()) enterItem(pos);
}

Энкодер с точной позицией

Для задач «крутилка знает свою позицию» (например, вал управления двигателем) берут энкодеры с Z-каналом (индексный импульс на оборот) или абсолютные энкодеры — I2C/SPI интерфейс, выдают угол напрямую.

Проблемы и решения

  • Шагов больше чем щелчков: один щелчок = 4 перехода — делите на 4.
  • Пропуски при быстрой крутке: опрос без прерываний не успевает — attachInterrupt на CLK.
  • Дребезг: RC-цепь на CLK/DT или программный дебаунс.
  • Направление перепутано: поменяйте CLK и DT местами или знак.
  • ESP32: используйте PCNT аппаратный счётчик импульсов — лучше программного подсчёта.

Альтернативы

  • Потенциометр — проще, но не бесконечное вращение.
  • Энкодер с абсолютной позицией — AS5600 (I2C) для точного угла.
  • Сенсорная крутилка — для премиальных интерфейсов.

Полный проект: энкодер + LCD

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);
const char* items[] = {"Назад", "Яркость", "Температура", "Режим"};
int pos = 0;

void loop() {
    if (encoderDelta()) {
        pos = (pos + encoderDelta() + 4) % 4;
        lcd.clear();
        lcd.print(">");
        lcd.print(items[pos]);
    }
    if (buttonPressed()) enterItem(pos);
}

Энкодер с абсолютной позицией

Для задач «крутилка знает свой угол» — AS5600 (I2C, 12 бит) или энкодеры с Z-каналом. KY-040 — инкрементальный, не знает абсолютную позицию.

Альтернативы

  • Потенциометр — проще, но не бесконечное вращение.
  • AS5600 — абсолютный угол по I2C.
  • Сенсорная крутилка — для премиальных интерфейсов.
Поделиться
Понравился материал?

Комментарии

Добавить комментарий