KY-040: подключение к ESP32 и Arduino

Энкодер с кнопкой

Ручка без упоров: крутится бесконечно в обе стороны, щёлкает по 20 шагов на оборот и нажимается. Для меню и настроек.

Как работает

Энкодер выдаёт две последовательности импульсов, сдвинутые на четверть периода, — CLK и DT. По тому, какая меняется первой, определяют направление: на спаде CLK смотрят уровень DT — высокий означает одно направление, низкий — другое. SW — кнопка на валу, замыкает на землю.

Опрашивать можно в loop, но надёжнее — по прерыванию на CLK: быстрый поворот иначе теряет шаги.

Выводы

ВыводНазначение
CLKвыход A
DTвыход B — по нему определяют направление
SWкнопка на валу, замыкает на GND
VCCпитание (для подтягивающих резисторов модуля)
GNDобщий провод

Подключение к ESP32

KY-040ESP32
CLKGPIO18
DTGPIO19
SWGPIO21
VCC3V3
GNDGND

Схема из примера «Энкодер KY-040: счётчик щелчков и кнопка».

Энкодер KY-040: счётчик щелчков и кнопка

Открыть пример целиком
Код скетча
// Энкодер KY-040 на ESP32.
//
// Два контакта внутри замыкаются со сдвигом: по часовой стрелке первым
// опускается CLK, против — DT. На спаде CLK достаточно посмотреть на
// DT: единица — по часовой.
//
// У ESP32 прерывание вешается на любой вывод, а обработчик должен
// лежать в IRAM (IRAM_ATTR): прерывание может прийти, пока флеш занята,
// и код из флеша тогда не выполнится.
//
// На схеме: возьмите ручку мышью и тяните по кругу — по часовой или
// против; кнопки ↺ ↻ под ручкой — по щелчку (держите — повторяются);
// колесо мыши над ручкой — тоже поворот; середина ручки — кнопка.

const int CLK = 18, DT = 19, SW = 21;

volatile long position = 0;

void IRAM_ATTR onClk() {
  if (digitalRead(DT)) position++; else position--;
}

void setup() {
  Serial.begin(115200);
  pinMode(CLK, INPUT);        // подтяжка на CLK и DT стоит на модуле
  pinMode(DT, INPUT);
  pinMode(SW, INPUT_PULLUP);  // на кнопке её нет
  attachInterrupt(CLK, onClk, FALLING);
  Serial.println("крутите ручку; нажатие — сброс");
}

long shown = 0;
bool lastSw = HIGH;

void loop() {
  long p = position;          // long на ESP32 читается за одну команду
  if (p != shown) {
    Serial.printf("%s  позиция %ld\n", p > shown ? "по часовой  " : "против часовой", p);
    shown = p;
  }
  bool sw = digitalRead(SW);
  if (sw == LOW && lastSw == HIGH) {
    position = 0;
    Serial.println("сброс");
  }
  lastSw = sw;
  delay(5);
}

Подключение к Arduino Uno

KY-040Arduino Uno
CLKD2
DTD3
SWD4
VCC5V
GNDGND

Схема из примера «Arduino Uno: меню на LCD и энкодере».

Arduino Uno: меню на LCD и энкодере

Открыть пример целиком
Код скетча
#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; }
}

В железе

На модуле стоят подтягивающие резисторы 10 кОм на CLK и DT, а на SW — не у всех: для кнопки включайте INPUT_PULLUP.

Частые ошибки

Другие детали