Энкодер - это устройство преобразования механического перемещения или угловых изменений положения в цифровой сигнал. В статье рассматривается самый популярный в DIY сообществе инкрементальный энкодер EC11 с кнопкой. При его вращении на выходах A и B формируются TTL сигналы в виде импульсов сдвинутые между собой по фазе на 90 градусов. Таким образом с его помощью, можно определить направление и скорость вращения, а так же рассчитать угол поворота. В отличие от потенциометров, энкодер KY-040 гораздо надежней и долговечный.
![](https://habrastorage.org/getpro/habr/upload_files/22a/5ff/285/22a5ff285e867efc5728c1d43ee1c0b2.jpg)
Немного подробностей
Собирая один из проектов с использованием encoder. Я не смог найти код для Ардуино выполняющий все мои условия. Так как для проекта нужно обрабатывать следующие команды: "Вращение без нажатия", "Вращение с нажатием", "Нажатие" и "Длинное нажатие", а так же требуется стабильная работа энкодера. Скетчи использующие один пин с прерыванием INT0 или INT1, работают отвратительно и при вращении вала энкодера вылетает очень много ошибок. Код без использования прерываний работает стабильно, но он не работает в фоновом режиме, его нужно встраивать в тело основной программы, что в свою очередь приводит к не своевременному срабатыванию обработчика и пропускам при вращении энкодера. Еще хуже обстоят дела с обработкой нажатия с вращением вала энкодера и обычным с нажатием. Пришлось написать свой код обработки, который исключает описанные выше проблемы. С дребезгом контактов я не стал бороться программно, так как это приводит к задержкам обработки. Проще и надежней использовать керамические конденсаторы.
Схема подключения энкодера к Ардуино
Для считывания сигналов с выходов EC-11, нужно использовать три цифровых входа Arduino. В схеме подключения я использовал редко используемые мной в своих проектах выводы Arduino(A1, A2 и A3). Внешние подтягивающие резисторы отсутствуют, так как я использовал внутреннюю подтяжку микроконтроллера. Конденсаторы нужны для гашения импульсов дребезга контактов. Если у вас новый и хороший энкодер, то можно обойтись и без них. Но на кнопку в любом случае потребуется конденсатор, так как ее дребезг неизбежен.
Используемые в схеме компоненты:
Arduino nano - 1 шт.
Энкодер EC11 -1 шт.
Соединительные повода - 4 шт.
Керамические конденсаторы 0,1 мкФ - 3 шт.
![](https://habrastorage.org/getpro/habr/upload_files/730/c5d/f74/730c5df74f85f6718abbd82ba75e8564.jpg)
Скетч для Ардуино
Для того что бы отслеживать изменение положения энкодера в фоновом режиме, я использую прерывание PCINT1. Обработка всех функций происходит в прерывании, обработчик в зависимости от произошедшего действия изменяет переменную enc_state. Если значение переменной enc_state=0 - ничего не произошло, enc_state=1 - экодер вращался без нажатия, enc_state=2 - экодер вращался с нажатием, enc_state=3 - было нажатие на кнопку, enc_state=4 - было длинное нажатие на кнопку, Прерывание будет срабатывать каждый раз по изменению состояния входов, как с высокого уровня на низкий, так и наоборот. То есть при одном щелчке энкодера прерывание сработает 4 раза. Или по 2 раза для каждого из входов. Но обработчик выдаст сигнал поворота только 1 раз на все 4 прерывания.
Код обработчика при каждом срабатывании записывает в переменную lastcomb состояние входов, к которым подключен энкодер. И ждет состояние когда выходы A и B будут замкнуты на GND, это гарантированный сигнал того, что энкодер вращается. После того как этот сигнал получен, обработчик проверяет в какую сторону было вращение. Для этого он сравнивает его предыдущее значение из переменной lastcomb и в зависимости от фазы сдвига определит в какую сторону был поворот ротора. Как я писал ранее, сложнее всего отслеживать нажатие кнопки.
Так как использовать определенные тайминги я не планировал, потому, что они неизбежно приводят длительным задержкам работы обработчика и основной программы, или требуют использование таймера, которых в микроконтроллере всего 3 шт. их, как правило никогда не хватает. Собственно проблема состояла в том, чтобы разделить "нажатие с последующим вращением" от простого нажатия. В итоге как вы уже можете убедиться, я решил эту задачу. Оптимизацией кода я не стал заниматься, потому как все работает и меня все устраивает. Для наглядности в коде все действия с энкодером, отображаются в Serial мониторе программы Adruino IDE.
/*
При публичном размещении кода ссылка на первоисточник обязательна.
*/
#define btn_long_push 1000 // Длительность долинного нажатия кнопки
volatile uint8_t lastcomb=7, enc_state, btn_push=0;
volatile int enc_rotation=0, btn_enc_rotate=0;
volatile boolean btn_press=0;
volatile uint32_t timer;
//********************************
void setup()
{
pinMode(A1,INPUT_PULLUP); // ENC-A
pinMode(A2,INPUT_PULLUP); // ENC-B
pinMode(A3,INPUT_PULLUP); // BUTTON
PCICR = 0b00000010; // PCICR |= (1<<PCIE1); Включить прерывание PCINT1
PCMSK1 = 0b00001110; // Разрешить прерывание для A1, A2, A3
Serial.begin(115200);
}
//****************************************
void loop()
{
switch (enc_state)
{
case 1: {
Serial.print("Вращение без нажатия ");
Serial.println(enc_rotation);
}
break;
case 2: {
Serial.print("Вращение с нажатием ");
Serial.println(btn_enc_rotate);
}
break;
case 3: Serial.println("Нажатие кнопки ");
break;
case 4: Serial.println("Длинное нажатие кнопки ");
break;
}
enc_state=0; //обнуляем статус энкодера
}
//****************************************
ISR (PCINT1_vect) //Обработчик прерывания от пинов A1, A2, A3
{
uint8_t comb = bitRead(PINC, 3) << 2 | bitRead( PINC, 2)<<1 | bitRead(PINC, 1); //считываем состояние пинов энкодера и кнопки
if (comb == 3 && lastcomb == 7) btn_press=1; //Если было нажатие кнопки, то меняем статус
if (comb == 4) //Если было промежуточное положение энкодера, то проверяем его предыдущее состояние
{
if (lastcomb == 5) --enc_rotation; //вращение по часовой стрелке
if (lastcomb == 6) ++enc_rotation; //вращение против часовой
enc_state=1; // был поворот энкодера
btn_enc_rotate=0; //обнулить показания вращения с нажатием
}
if (comb == 0) //Если было промежуточное положение энкодера и нажатие, то проверяем его предыдущее состояние
{
if (lastcomb == 1) --btn_enc_rotate; //вращение по часовой стрелке
if (lastcomb == 2) ++btn_enc_rotate; //вращение против частовой
enc_state=2; // был поворот энкодера с нажатием
enc_rotation=0; //обнулить показания вращения без нажатия
btn_press=0; //обнулить показания кнопки
}
if (comb == 7 && lastcomb == 3 && btn_press) //Если было отпускание кнопки, то проверяем ее предыдущее состояние
{
if (millis() - timer > btn_long_push) // проверяем сколько прошло миллисекунд
{
enc_state=4; // было длинное нажатие
} else {
enc_state=3; // было нажатие
}
btn_press=0; //обнулить статус кнопки
}
timer = millis(); //сброс таймера
lastcomb = comb; //сохраняем текущее состояние энкодера
}
Заключение
Результат работы кода меня порадовал и теперь я могу продолжить работу над своим новым проектом, который скоро здесь выложу. Надеюсь эта короткая статья вам понравилась и вы сможете воспользоваться моей наработкой в своих самоделках.
Если у Вас остались вопросы и замечания, пишите их в комментариях. Я с удовольствием на них отвечу.