Подключение энкодера к Ардуино и новый код обработки для него
Энкодер – это устройство преобразования механического перемещения или угловых изменений положения в цифровой сигнал. В статье рассматривается самый популярный в DIY сообществе инкрементальный энкодер EC11 с кнопкой. При его вращении на выходах A и B формируются TTL сигналы в виде импульсов сдвинутые между собой по фазе на 90 градусов. Таким образом с его помощью, можно определить направление и скорость вращения, а так же рассчитать угол поворота. В отличие от потенциометров, KY-040 гораздо надежней и долговечней.
Немного подробностей
Собирая один из проектов с использованием encoder. Я не смог найти код для Ардуино выполняющий все мои условия. Так как для проекта нужно обрабатывать следующие команды: “Вращение без нажатия”, “Вращение с нажатием”, “Нажатие” и “Длинное нажатие”, а так же требуется стабильная работа энкодера. Скетчи использующие один пин с прерыванием INT0 или INT1, работают отвратительно и при вращении вала энкодера вылетает очень много ошибок. Код без использования прерываний работает стабильно, но он не работает в фоновом режиме, его нужно встраивать в тело основной программы, что в свою очередь приводит к не своевременному срабатыванию обработчика и пропускам при вращении энкодера. Еще хуже обстоят дела с обработкой нажатия с вращением вала энкодера и обычным с нажатием. Пришлось написать свой код обработки, который исключает описанные выше проблемы. С дребезгом контактов я не стал бороться программно, так как это приводит к задержкам обработки. Проще и надежней использовать керамические конденсаторы.
Схема подключения энкодера к Ардуино
Для считывания сигналов с выходов EC-11, нужно использовать три цифровых входа Arduino.
В схеме подключения я использовал редко используемые мной в своих проектах выводы Arduino(A1, A2 и A3). Внешние подтягивающие резисторы отсутствуют, так как я использовал внутреннюю подтяжку микроконтроллера. Конденсаторы нужны для гашения импульсов дребезга контактов. Если у вас новый и хороший энкодер, то можно обойтись и без них. Но на кнопку в любом случае потребуется конденсатор, так как ее дребезг неизбежен.
Все используемые в тестировании компоненты из магазина duino.ru
Arduino nano – 1 шт.
Энкодер EC11 – 1 шт.
Соединительные повода – 4 шт.
Керамические конденсаторы 0,1 мкФ – 3 шт.
Скетч для Ардуино
Для того что бы отслеживать изменение положения энкодера в фоновом режиме, я использую прерывание 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.
/* Код написан при содействии магазина DUINO.RU и является его собственностью. При публичном размещении кода ссылка на первоисточник обязательна. */ #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; //сохраняем текущее состояние энкодера }
Заключение
Результат работы кода меня порадовал и теперь я могу продолжить работу над своим новым проектом, который скоро здесь выложу. Надеюсь эта короткая статья вам понравилась и вы сможете воспользоваться моей наработкой в своих самоделках.
Если у Вас остались вопросы и замечания, пишите их в комментариях. Я с удовольствием на них отвечу.
2 комментария