В наборе GyverKIT | START | IOT | EXTRA | |
---|---|---|---|---|
![]() |
Arduino NANO | ✔ | ||
![]() |
Макетная плата | ✔ | ✔ | |
![]() |
Энкодер | ✔ | ✔ | |
![]() |
Светодиод 5мм | ✔ | ||
![]() |
Резистор 220 Ом | ✔ |
Энкодер #
Инкрементальный энкодер по сути заменяет собой две кнопки - при вращении в одну сторону нажимается одна, в другую - вторая. При вращении он генерирует квадратный сигнал со смещением на половину фазы, поэтому для обработки нужен специальный алгоритм.
Подключение #
Голый энкодер просто замыкает контакты, поэтому пинам нужна подтяжка: внешняя либо внутренняя. Также для избавления от дребезга контактов при вращении можно поставить на пины RC цепи:
Примерно по этой схеме сделан вот такой популярный магазинный модуль, он не самый дешёвый - но своих денег однозначно стоит:
Модуль подключается к питанию и любым цифровым пинам, пин KEY - вывод кнопки энкодера, его можно не подключать если не используется:
В примерах ниже я использую такой модуль - пины подтянуты аппаратно, поэтому режим пинов оставляю INPUT
.
Программирование #
Рассмотрим самый эффективный алгоритм на основе кода Грея. Простейший случай - выводим в порт "направление" текущего поворота. Для работы алгоритма нужно хранить предыдущие состояния пинов:
#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);
}
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() {
// тут ничего нет, опрос в прерывании
}
Вывод в 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); // имитация "загруженной" программы
}
Изменение счётчика будет производиться даже в условиях "задержек".
Тип энкодера #
Аппаратно энкодеры могут быть устроены по разному, а именно - точка фиксации рукоятки может находиться в разных местах сигнала. В своей библиотеке EncButton я классифицирую их следующим образом, пунктиром отмечена точка фиксации рукоятки:
Рассмотренный выше алгоритм обрабатывает каждое изменение сигнала, то есть энкодер с типом EB_STEP4_LOW
будет "срабатывать" 4 раза за один щелчок - именно таким является энкодер на круглой плате. Вы скорее всего заметили это, если загружали пример. Для удобства работы с энкодером применительно к навигации по меню электронного устройства нужно пропустить лишние срабатывания. Реализацию можно посмотреть например здесь.
Библиотеки #
Для более удобной работы с энкодером любого типа без изобретения алгоритмов можно использовать библиотеку EncButton - она имеет огромное количество возможностей и сценариев использования для энкодера с кнопкой, см. полную документацию по ссылке. Самый простой пример:
#include <EncButton.h>
EncButton eb(2, 3, 4);
void setup() {
Serial.begin(115200);
}
void loop() {
eb.tick(); // опрос тут
// обработка поворота раздельная
// if (eb.left()) Serial.println("Лево");
// if (eb.right()) Serial.println("Право");
// if (eb.leftH()) Serial.println("Лево нажатый");
// if (eb.rightH()) Serial.println("Право нажатый");
// обработка поворота общая
if (eb.turn()) {
Serial.print("направление ");
Serial.print(eb.dir());
Serial.print(", быстрый ");
Serial.print(eb.fast());
Serial.print(", кнопка ");
Serial.print(eb.pressing());
Serial.print(", счётчик ");
Serial.println(eb.counter);
}
}
Управляем светодиодом #
Напишем пример, в котором яркость светодиода на D5 управляется энкодером с кнопкой:
- Поворот - изменение яркости с шагом 5
- Поворот с удержанием кнопки - изменение яркости с шагом 20
- Клик - включить/выключить
#include <EncButton.h>
EncButton eb(2, 3, 4);
#define LED_PIN 5
// по умолч. включен с яркостью 128
bool state = 1;
int bright = 128;
void updateLED() {
analogWrite(LED_PIN, state ? bright : 0);
}
void setup() {
updateLED();
}
void loop() {
eb.tick();
if (eb.click()) {
state = !state;
updateLED();
}
if (eb.turn()) {
bright += (eb.pressing() ? 20 : 5) * eb.dir();
bright = constrain(bright, 0, 255);
updateLED();
}
}
Полезные страницы #
- Набор GyverKIT – наш большой стартовый набор Arduino, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])
- Поддержать автора за работу над уроками