ПОДКЛЮЧЕНИЕ ЭНКОДЕРА К ARDUINO

Энкодер (от англ. encode – преобразовывать) – это устройство для преобразования угловых положений или линейных перемещений в цифровой сигнал, т.е. энкодер – это датчик угла или линейного перемещения, соответственно есть крутильные и линейные энкодеры. Принцип работы энкодера заключается в преобразовании механического перемещения в электрические сигналы, у обычного инкрементального энкодера, который мы будем рассматривать, этот сигнал представляет собой два квадратных сигнала (при равномерном вращении), сдвинутых по фазе на 90 градусов.
Большая куча теории “как это работает” с примерами прошивок (работоспособность не гарантируется) – ссылка. Так как мы рассматриваем применение энкодера в паре с Arduino – логичнее будет рассматривать готовые энкодерные модули, коих у китайцев есть несколько разных.

Самый классический энкодерный модуль KY-040. Имеет 28 тиков на оборот, рукоятка является кнопкой (отдельный выход). Тип энкодера – 1 или 2 импульсный, китайцы могут прислать любой (о типах читайте ниже)

aliexpress
aliexpress
рукоятки

Более новый модуль, гораздо меньший процент брака. Имеет 28 тиков на оборот, рукоятка является кнопкой (отдельный выход). Тип энкодера – 2 импульсный.

aliexpress
aliexpress
aliexpress

Промышленный энкодер – надёжная и точная штука: металлический корпус, подшипниковый узел. Имеет 100, 200, 300, 360, 400, 600, 1000 тиков на оборот (на выбор). Тип энкодера – 2.

aliexpress

ПОДКЛЮЧЕНИЕ

Подключается модуль энкодера очень просто: питание на питание (GND и VCC), логические пины CLK, DT (тактовые выводы энкодера) и SW (вывод кнопки) на любые пины Arduino (D или A). У круглых модулей выводы энкодера подписаны как S1 и S2, а вывод кнопки как Key, подключаются точно так же. От порядка подключения тактовых выводов энкодера зависит “направление” его работы, но это можно поправить в программе.
У модулей энкодера тактовые выводы подтянуты к земле и дают высокий сигнал при срабатывании, иакже на них стоят конденсаторы. Вывод кнопки никуда не подтянут! Промышленный энкодер подключается точно так же, чёрный и красный провода у него питание, остальные – тактовые выходы.

ПРОГРАММИРОВАНИЕ

Начнём с того, что бывает два типа энкодеров, я назвал их одноимпульсные и двухимпульсные, тип энкодера можно определить по внешнему виду самого энкодера:

Чем отличаются энкодеры на практике: если опрашивать одноимпульсный энкодер как двухимпульсный, то для отработки одного тика нужно повернуть рукоятку на два тика. Если опрашивать двухимпульсный как одноимпульсный, то для отработки одного тика нужно повернуть рукоятку на два тика. То есть при неправильном использовании причина сразу видна.

Я не нашёл в интернете нормальных библиотек для энкодера с хорошей функциональностью, поэтому написал свою, GyverEncoder. Что умеет:

  • Отработка поворота рукоятки энкодера
  • Отработка “нажатого поворота” энкодера
  • Отработка “быстрого поворота” энкодера
  • Работа с двумя типами энкодеров
  • Отработка нажатия/клика/удержания кнопки с антидребезгом контактов

Внимание! Параметры защиты от дребезга контактов и таймаут удержания кнопки настраивается в файле .h библиотеки почти в самом начале.

Скачать библиотеку GyverEncoder можно по кнопке ниже, она входит в пак библиотек GyverLibs.

скачать с github

Основные функции и методы библиотеки

Encoder(uint8_t clk, uint8_t dt);
// CLK, DT	
Encoder(uint8_t, uint8_t, uint8_t);
// CLK, DT, SW
Encoder(uint8_t, uint8_t, uint8_t, boolean);
// CLK, DT, SW, тип (TYPE1 / TYPE2): TYPE1 одношаговый, TYPE2 двухшаговый. Если ваш энкодер работает странно, смените тип
void tick();                             // опрос энкодера, нужно вызывать постоянно или в прерывании
void setType(boolean type);              // TYPE1 / TYPE2 - тип энкодера TYPE1 одношаговый, TYPE2 двухшаговый. Если ваш энкодер работает странно, смените тип
void setTickMode(boolean tickMode);      // MANUAL / AUTO - ручной или автоматический опрос энкодера функцией tick(). (по умолчанию ручной)
void setDirection(boolean direction);    // NORM / REVERSE - направление вращения энкодера
void setFastTimeout(int timeout);        // установка таймаута быстрого поворота
boolean isTurn();                        // возвращает true при любом повороте, сама сбрасывается в false
boolean isRight();                       // возвращает true при повороте направо, сама сбрасывается в false
boolean isLeft();                        // возвращает true при повороте налево, сама сбрасывается в false
boolean isRightH();                      // возвращает true при удержании кнопки и повороте направо, сама сбрасывается в false
boolean isLeftH();                       // возвращает true при удержании кнопки и повороте налево, сама сбрасывается в false
boolean isFastR();                       // возвращает true при быстром повороте
boolean isFastL();                       // возвращает true при быстром повороте
boolean isPress();                       // возвращает true при нажатии кнопки, сама сбрасывается в false
boolean isRelease();                     // возвращает true при отпускании кнопки, сама сбрасывается в false
boolean isClick();                       // возвращает true при нажатии и отпускании кнопки, сама сбрасывается в false
boolean isHolded();                      // возвращает true при удержании кнопки, сама сбрасывается в false
boolean isHold();                        // возвращает true при удержании кнопки, НЕ СБРАСЫВАЕТСЯ

ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ

Библиотека GyverEncoder


#define CLK 7
#define DT 8
#define SW 9
#include "GyverEncoder.h"
Encoder enc1(CLK, DT, SW);
void setup() {
Serial.begin(9600);
}
void loop() {
// обязательная функция отработки. Должна постоянно опрашиваться
enc1.tick();
if (enc1.isTurn()) {     // если был совершён поворот (индикатор поворота в любую сторону)
// ваш код
}
if (enc1.isRight()) Serial.println("Right");         // если был поворот
if (enc1.isLeft()) Serial.println("Left");
if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот
if (enc1.isLeftH()) Serial.println("Left holded");
if (enc1.isPress()) Serial.println("Press");         // нажатие на кнопку (+ дебаунс)
if (enc1.isRelease()) Serial.println("Release");     // отпускание кнопки (+ дебаунс)
if (enc1.isHolded()) Serial.println("Holded");       // если была удержана и энк не поворачивался
//if (enc1.isHold()) Serial.println("Hold");         // возвращает состояние кнопки
}
#define CLK 7
#define DT 8
#define SW 9
#include "GyverEncoder.h"
Encoder enc1(CLK, DT, SW);
int value = 0;
void setup() {
Serial.begin(9600);
enc1.setType(TYPE2);    // тип энкодера TYPE1 одношаговый, TYPE2 двухшаговый. Если ваш энкодер работает странно, смените тип
}
void loop() {
// обязательная функция отработки. Должна постоянно опрашиваться
enc1.tick();
if (enc1.isRight()) value++;      // если был поворот направо, увеличиваем на 1
if (enc1.isLeft()) value--;	    // если был поворот налево, уменьшаем на 1
if (enc1.isRightH()) value += 5; 	// если было удержание + поворот направо, увеличиваем на 5
if (enc1.isLeftH()) value -= 5;	// если было удержание + поворот налево, уменьшаем на 5  
if (enc1.isTurn()) {       // если был совершён поворот (индикатор поворота в любую сторону)
Serial.println(value);   // выводим значение при повороте
}  
}
#define CLK 2
#define DT 3
#define SW 4
#include "GyverEncoder.h"
Encoder enc1(CLK, DT, SW);
int value = 0;
void setup() {
Serial.begin(9600);
enc1.setType(TYPE1);        // тип энкодера TYPE1 одношаговый, TYPE2 двухшаговый. Если ваш энкодер работает странно, смените тип\=
enc1.setFastTimeout(40);    // таймаут на скорость isFastR. По умолч. 50
}
void loop() {
// обязательная функция отработки. Должна постоянно опрашиваться
enc1.tick();
if (enc1.isRight()) value++;        // если был поворот направо, увеличиваем на 1
if (enc1.isLeft()) value--;         // если был поворот налево, уменьшаем на 1
if (enc1.isRightH()) value += 5;    // если было удержание + поворот направо, увеличиваем на 5
if (enc1.isLeftH()) value -= 5;     // если было удержание + поворот налево, уменьшаем на 5
if (enc1.isFastR()) value += 10;    // если был быстрый поворот направо, увеличиваем на 10
if (enc1.isFastL()) value -= 10;    // если был быстрый поворот налево, уменьшаем на 10
if (enc1.isTurn()) {                // если был совершён поворот (индикатор поворота в любую сторону)
Serial.println(value);            // выводим значение при повороте
}
}
// два энкодера
#include "GyverEncoder.h"
Encoder enc1(4, 3, 2);
Encoder enc2(7, 6, 5);
void setup() {
Serial.begin(9600);
}
void loop() {
// обязательная функция отработки. Должна постоянно опрашиваться
enc1.tick();
enc2.tick();
if (enc1.isLeft()) Serial.println("enc 1 left");
if (enc1.isRight()) Serial.println("enc 1 right");
if (enc2.isLeft()) Serial.println("enc 2 left");
if (enc2.isRight()) Serial.println("enc 2 right");
}
/*
В последнее время китайцы стали делать одинаковые модули (ку 40)
с разными типами энкодеров - полный период и полпериода.
Если ваш энкодер ведёт себя странно (один тик считает за два поворота),
то смените тип энкодера
*/
#define CLK 4
#define DT 3
#define SW 2
#include "GyverEncoder.h"
Encoder enc1(CLK, DT, SW);
void setup() {
Serial.begin(9600);
enc1.setType(TYPE2);    // тип энкодера TYPE1 одношаговый, TYPE2 двухшаговый. Если ваш энкодер работает странно, смените тип
}
void loop() {
// обязательная функция отработки. Должна постоянно опрашиваться
enc1.tick();
if (enc1.isRight()) Serial.println("Right");         // если был поворот
if (enc1.isLeft()) Serial.println("Left");
if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот
if (enc1.isLeftH()) Serial.println("Left holded");
}
#define CLK 6
#define DT 5
#define SW 4
#include "GyverEncoder.h"
Encoder enc1(CLK, DT, SW);
void setup() {
Serial.begin(9600);
enc1.setTickMode(AUTO);
}
void loop() {
// enc1.tick();  // не нужна, в этом режиме (AUTO) она входит в каждую функцию!
if (enc1.isTurn()) {     // если был совершён поворот (индикатор поворота в любую сторону)
// ваш код
}
if (enc1.isRight()) Serial.println("Right");         // если был поворот
if (enc1.isLeft()) Serial.println("Left");
if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот
if (enc1.isLeftH()) Serial.println("Left holded");
if (enc1.isPress()) Serial.println("Press");         // нажатие на кнопку (+ дебаунс)
if (enc1.isRelease()) Serial.println("Release");     // отпускание кнопки (+ дебаунс)
if (enc1.isHolded()) Serial.println("Holded");       // если была удержана и энк не поворачивался
//if (enc1.isHold()) Serial.println("Hold");         // возвращает состояние кнопки
}
/*
Пример работы с энкодером с прерыванием. Максимальная чёткость работы
в любом быдлокоде!
*/
#define CLK 2
#define DT 3
#define SW 4
#include "GyverEncoder.h"
Encoder enc1(CLK, DT, SW);
void setup() {
Serial.begin(9600);
attachInterrupt(0, isr, CHANGE);    // прерывание на 2 пине! CLK у энка
}
void isr() {
enc1.tick();  // отработка в прерывании
}
void loop() {
enc1.tick();  // отработка
if (enc1.isRight()) Serial.println("Right");         // если был поворот
if (enc1.isLeft()) Serial.println("Left");
if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот
if (enc1.isLeftH()) Serial.println("Left holded");
}
/*
* Отработка по прерыванию таймера
*/
#define CLK 7
#define DT 8
#define SW 9
#include "GyverEncoder.h"
#include "TimerOne.h"
Encoder enc1(CLK, DT, SW);
void setup() {
Serial.begin(9600);
enc1.setType(TYPE2);    // тип энкодера TYPE1 одношаговый, TYPE2 двухшаговый. Если ваш энкодер работает странно, смените тип
Timer1.initialize(1000);            // установка таймера на каждые 1000 микросекунд (= 1 мс)
Timer1.attachInterrupt(timerIsr);   // запуск таймера
}
void timerIsr() {   // прерывание таймера
enc1.tick();     // отработка теперь находится здесь
}
void loop() {
if (enc1.isRight()) Serial.println("Right");         // если был поворот
if (enc1.isLeft()) Serial.println("Left");
if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот
if (enc1.isLeftH()) Serial.println("Left holded");  
}

Мини-библиотека


Также у меня есть мини-версия библиотеки энкодера, у неё меньше возможностей, но она гораздо “легче”. Скачать релиз можно вот по этой ссылке, в нём есть пример – encTest. “Библиотека” лежит рядом со скетчем, файл encMinim.h.

// пины энкодера
#define SW 0
#define DT 2
#define CLK 3
#include "encMinim.h"
encMinim enc(CLK, DT, SW, 1, 1);
// пин clk, пин dt, пин sw, направление (0/1), тип (0/1)
void setup() {
Serial.begin(9600);
}
void loop() {
enc.tick();
if (enc.isTurn()) Serial.println("turn");
if (enc.isLeft()) Serial.println("left");
if (enc.isRight()) Serial.println("right");
if (enc.isLeftH()) Serial.println("leftH");
if (enc.isRightH()) Serial.println("rightH");
if (enc.isClick()) Serial.println("click");
}

 

Лёгкий быстрый код


Если вам важна не функциональность библиотеки, а максимальная скорость работы с энкодером и минимальное время его опроса, предлагаю следующий код. Здесь используется аппаратное прерывание (одно на один энкодер), для увеличения скорости выполнения прерывания используется функция bitRead вместо digitalRead (скорость выполнения разнится в десятки раз). Также в этом коде предусмотрен выбор типа энкодера соответствующей настройкой. Отработка энкодера идёт параллельно выполнению скетча, переменная encCounter меняет своё значение при повороте рукоятки и выводится через порт.

/*
Максимально быстрый универсальный код для обработки энкодера
Работает на перывании (используется одно)
Тут код построен на bitRead(PIND..) - только для Arduino NANO!
*/
#define ENC_A 2       // пин энкодера
#define ENC_B 4       // пин энкодера
#define ENC_TYPE 1    // тип энкодера, 0 или 1
volatile int encCounter;
volatile boolean state0, lastState, turnFlag;
void setup() {
Serial.begin(9600);
attachInterrupt(0, int0, CHANGE);
}
void int0() {
state0 = bitRead(PIND, ENC_A);
if (state0 != lastState) {
#if (ENC_TYPE == 1)
turnFlag = !turnFlag;
if (turnFlag)
encCounter += (bitRead(PIND, ENC_B) != lastState) ? -1 : 1;
#else
encCounter += (bitRead(PIND, ENC_B) != lastState) ? -1 : 1;
#endif
lastState = state0;
}
}
void loop() {
Serial.println(encCounter);
delay(100);
}

/*
Максимально быстрый универсальный код для обработки энкодера
Работает на перывании (используется одно)
Тут код построен на digitalRead, что делает его универсальным для всех плат Arduino
*/
#define ENC_A 2       // пин энкодера
#define ENC_B 4       // пин энкодера
#define ENC_TYPE 1    // тип энкодера, 0 или 1
volatile int encCounter;
volatile boolean state0, lastState, turnFlag;
void setup() {
Serial.begin(9600);
attachInterrupt(0, int0, CHANGE);
}
void int0() {
state0 = digitalRead(ENC_A);
if (state0 != lastState) {
#if (ENC_TYPE == 1)
turnFlag = !turnFlag;
if (turnFlag)
encCounter += (digitalRead(ENC_B) != lastState) ? -1 : 1;
#else
encCounter += (digitalRead(ENC_B) != lastState) ? -1 : 1;
#endif
lastState = state0;
}
}
void loop() {
Serial.println(encCounter);
delay(100);
}

2019-08-22T09:36:47+03:00