Энкодер – общее название устройств, преобразующих одну величину в другую. В данном случае энкодер – это устройство, преобразующее вращательное механическое движение в цифровой сигнал, а сам энкодер в этом случае называется "поворотным" (вращательным, круговым).
Рассмотренный ниже инкрементальный поворотный энкодер с кнопкой служит очень удобным органом управления для электронного устройства и заменяет сразу несколько кнопок или джойстик, обеспечивая быструю навигацию по пунктам меню и изменение настроек: например в некоторых 3D принтерах с огромным сложным меню с кучей настроек навигация производится при помощи одного только энкодера! По сути похож на потенциометр, но не имеет ограничения по углу поворота, а само вращение рукоятки - "ступенчатое", что позволяет тактильно ощущать каждый шаг изменения значения.
![]() |
В наборе GyverKIT | START | IOT | EXTRA |
---|---|---|---|---|
Энкодер | ✔ | ✔ |
При вращении он генерирует квадратный сигнал со смещением на половину фазы - квадратурный сигнал:
Точно так же работают энкодеры, которые стоят на некоторых моторчиках, например популярных JGA25 и JGB37, которые часто используются для самодельных роботов:
Рассмотренные ниже алгоритмы можно использовать в том числе для таких моторов.
Подключение к Arduino #
Модуль #
Модуль подключается на питание, логические выводы – на любые цифровые пины. В случае с Wemos – на все кроме D8, так как подтяжка к VCC помешает МК запуститься. Подключу выводы энкодера на D2 и D3, а кнопку – на D4:
Особенности данного модуля:
- Качественный инкрементальный энкодер с кнопкой
- 20 "щелчков" на один оборот
- Выведено питание (5V, GND), два пина энкодера (S1, S2) и пин кнопки (KEY)
- Работает также от 3.3V (для Wemos)
- Все логические пины подтянуты к VCC резисторами на плате
- RC цепи гашения дребезга на выводах энкодера
В примерах ниже я использую такой модуль - пины подтянуты аппаратно, поэтому режим пинов оставляю INPUT
Голый #
Голый энкодер просто замыкает контакты, поэтому пинам нужна подтяжка: внешняя либо внутренняя. Также для избавления от дребезга контактов при вращении можно поставить RC цепи на пины:
Программирование #
Код Грея #
Рассмотрим самый эффективный алгоритм на основе кода Грея. Простейший случай - выводим в порт "направление" текущего поворота. Для работы алгоритма нужно хранить предыдущие состояния пинов:
#define ENC_A 2
#define ENC_B 3
bool p0, p1;
void pollEnc(bool e0, bool e1) {
if (p0 ^ p1 ^ e0 ^ e1) {
Serial.println(p1 ^ e0); // направление
p0 = e0;
p1 = e1;
}
}
void setup() {
Serial.begin(115200);
p0 = digitalRead(ENC_A); // стартовые значения
p1 = digitalRead(ENC_B);
}
void loop() {
pollEnc(digitalRead(ENC_A), digitalRead(ENC_B)); // постоянный опрос в loop
}
Можно опрашивать энкодер в прерывании - для этого нужно подключить оба пина на прерывание по CHANGE
. Чтобы не опрашивать каждый раз актуальные состояния пинов, можно просто считать, что они инвертируются:
#define ENC_A 2
#define ENC_B 3
volatile bool p0, p1;
void pollEnc(bool e0, bool e1) {
if (p0 ^ p1 ^ e0 ^ e1) {
Serial.println(p1 ^ e0);
p0 = e0;
p1 = e1;
}
}
void isrA() {
pollEnc(!p0, p1); // инверт p0
}
void isrB() {
pollEnc(p0, !p1); // инверт p1
}
void setup() {
Serial.begin(115200);
attachInterrupt(0, isrA, CHANGE);
attachInterrupt(1, isrB, CHANGE);
p0 = digitalRead(ENC_A);
p1 = digitalRead(ENC_B);
}
void loop() {
// тут ничего нет, опрос в прерывании
}
Вывод в Serial
в прерывании может работать не очень корректно, но тут суть в самом алгоритме. В реальном применении можно завести например счётчик и флаг срабатывания:
#define ENC_A 2
#define ENC_B 3
volatile bool p0, p1;
volatile bool flag;
volatile int counter;
void pollEnc(bool e0, bool e1) {
if (p0 ^ p1 ^ e0 ^ e1) {
(p1 ^ e0) ? ++counter : --counter;
flag = 1;
p0 = e0;
p1 = e1;
}
}
void isrA() {
pollEnc(!p0, p1);
}
void isrB() {
pollEnc(p0, !p1);
}
void setup() {
Serial.begin(115200);
attachInterrupt(0, isrA, CHANGE);
attachInterrupt(1, isrB, CHANGE);
p0 = digitalRead(ENC_A);
p1 = digitalRead(ENC_B);
}
void loop() {
if (flag) {
flag = false;
Serial.println(counter);
}
delay(200); // имитация "загруженной" программы
}
Изменение счётчика будет производиться даже в условиях "задержек".
Данный алгоритм:
- Считает каждое изменение с обоих выводов энкодера, т.е. обрабатывает поворот с максимальным "разрешением"
- Автоматически игнорирует шумы и некорректные сигналы, т.к. код Грея подразумевает только один способ перейти из текущего состояния в следующее
- Для работы на прерываниях требует подключить оба пина на прерывания по
CHANGE
Дата-клок #
Можно обрабатывать поворот более просто - один пин считать "клоком", а второй - направлением. Алгоритм после упрощения до одной строчки может выглядеть так:
#define ENC_A 2
#define ENC_B 3
bool prev;
void pollEnc(bool e0, bool e1) {
if (prev != e0) {
Serial.println(e1 ^ e0); // направление
prev = e0;
}
}
void setup() {
Serial.begin(115200);
prev = digitalRead(ENC_A);
}
void loop() {
pollEnc(digitalRead(ENC_A), digitalRead(ENC_B)); // постоянный опрос в loop
}
Для работы на прерываниях понадобится подключить один пин на CHANGE
. По аналогии с предыдущим примером - флаг и счётчик:
#define ENC_A 2
#define ENC_B 3
volatile bool p0;
volatile bool flag;
volatile int counter;
void isrA() {
p0 ^= 1;
counter += (digitalRead(ENC_B) ^ p0) ? 1 : -1;
flag = true;
}
void setup() {
Serial.begin(115200);
attachInterrupt(0, isrA, CHANGE);
}
void loop() {
if (flag) {
flag = false;
Serial.println(counter);
}
//delay(200); // имитация "загруженной" программы
}
Данный алгоритм, в отличие от предыдущего:
- Считает изменение только одного пина энкодера, то есть имеет "разрешение" поворота в 2 раза меньше
- Боится шумов и некачественных дребезжащих энкодеров - значение счётчика может "уплыть"
- Требует опроса состояния пина в прерывании, что может быть долго в Ардуино-реализации
- Для работы на прерываниях требует подключить только один пин, в этом его единственное преимущество

Тип энкодера #
Аппаратно энкодеры могут быть устроены по разному, а именно - точка фиксации рукоятки может находиться в разных местах сигнала. В своей библиотеке EncButton я классифицирую их следующим образом, пунктиром отмечена точка фиксации рукоятки:
Рассмотренный выше алгоритм обрабатывает каждое изменение сигнала, то есть энкодер с типом EB_STEP4_LOW
будет "срабатывать" 4 раза за один щелчок - именно таким является энкодер на круглой плате. Вы скорее всего заметили это, если загружали пример. Для удобства работы с энкодером применительно к навигации по меню электронного устройства нужно пропустить лишние срабатывания. Реализацию можно посмотреть например здесь.
Библиотеки #
В рамках примеров и проектов будем использовать библиотеку EncButton, она позволяет работать как отдельно с энкодером, так и с энкодером+кнопкой для сложных сценариев управления и выбора. Её можно установить/обновить из встроенного менеджера библиотек Arduino по названию EncButton. Краткая документация находится по ссылке выше, базовые примеры есть в самой библиотеке.
Для работы библиотеки нужно вызывать метод tick()
в loop()
и опрашивать нужные события. Их там много - смотрите документацию.
Меняем значение переменной
/*
Меняем значение переменной при помощи энкодера
Обычный поворот +-1
"Нажатый" поворот +-5
*/
#include <EncButton.h>
EncButton enc(2, 3, 4); // выводы энкодера на 2 и 3, кнопка на 4
void setup() {
Serial.begin(115200);
}
int val = 0; // будем управлять этой переменной
void loop() {
// опрос энкодера происходит тут
enc.tick();
// вправо
if (enc.right()) {
val += 1;
Serial.println(val);
}
// влево
if (enc.left()) {
val -= 1;
Serial.println(val);
}
// вправо нажатый
if (enc.rightH()) {
val += 5;
Serial.println(val);
}
// влево нажатый
if (enc.leftH()) {
val -= 5;
Serial.println(val);
}
}
Меняем яркость и состояние светодиода
/*
Меняем яркость светодиода на пине 13 (программный ШИМ)
Клик - переключить состояние (вкл выкл)
*/
#include <EncButton.h>
EncButton enc(2, 3, 4); // выводы энкодера на 2 и 3, кнопка на 4
void setup() {
// пин 13 как выход (для мигания светодиодом)
pinMode(13, OUTPUT);
}
int bright = 128; // храним яркость
bool state = 1; // состояние светодиода
void loop() {
// опрос энкодера происходит тут
enc.tick();
// передаём яркость, умноженную на state (0 или 1)
// то есть получится bright или 0 в зависимости от state
softPWM(13, bright * state);
// вправо - увеличиваем на 10
if (enc.right()) bright = constrain(bright + 10, 0, 255);
// влево - уменьшаем на 10
if (enc.left()) bright = constrain(bright - 10, 0, 255);
// клик - переключаем состояние по клику
if (enc.click()) state = !state;
}
// софт шим
void softPWM(byte pin, byte val) {
static byte count;
count++;
if (count == 0 && val != 0) digitalWrite(pin, 1);
if (count == val) digitalWrite(pin, 0);
}
Дополнительный контент доступен владельцам набора GyverKIT и по подписке, подробнее читай здесь. Блок содержит:
- Дополнительно, Пример со светодиодом
- 1 изображений
- 1 блоков кода
Полезные страницы #
- Набор GyverKIT – наш большой стартовый набор Arduino, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])
- Поддержать автора за работу над уроками
