Как подключить энкодер к ардуино
Перейти к содержимому

Как подключить энкодер к ардуино

  • автор:

Подключение энкодера к Ардуино и полнофункциональный код обработки для него

Энкодер — это устройство преобразования механического перемещения или угловых изменений положения в цифровой сигнал. В статье рассматривается самый популярный в DIY сообществе инкрементальный энкодер EC11 с кнопкой. При его вращении на выходах A и B формируются TTL сигналы в виде импульсов сдвинутые между собой по фазе на 90 градусов. Таким образом с его помощью, можно определить направление и скорость вращения, а так же рассчитать угол поворота. В отличие от потенциометров, энкодер KY-040 гораздо надежней и долговечный.

Немного подробностей

Собирая один из проектов с использованием encoder. Я не смог найти код для Ардуино выполняющий все мои условия. Так как для проекта нужно обрабатывать следующие команды: «Вращение без нажатия», «Вращение с нажатием», «Нажатие» и «Длинное нажатие», а так же требуется стабильная работа энкодера. Скетчи использующие один пин с прерыванием INT0 или INT1, работают отвратительно и при вращении вала энкодера вылетает очень много ошибок. Код без использования прерываний работает стабильно, но он не работает в фоновом режиме, его нужно встраивать в тело основной программы, что в свою очередь приводит к не своевременному срабатыванию обработчика и пропускам при вращении энкодера. Еще хуже обстоят дела с обработкой нажатия с вращением вала энкодера и обычным с нажатием. Пришлось написать свой код обработки, который исключает описанные выше проблемы. С дребезгом контактов я не стал бороться программно, так как это приводит к задержкам обработки. Проще и надежней использовать керамические конденсаторы.

Схема подключения энкодера к Ардуино

Для считывания сигналов с выходов EC-11, нужно использовать три цифровых входа Arduino. В схеме подключения я использовал редко используемые мной в своих проектах выводы Arduino(A1, A2 и A3). Внешние подтягивающие резисторы отсутствуют, так как я использовал внутреннюю подтяжку микроконтроллера. Конденсаторы нужны для гашения импульсов дребезга контактов. Если у вас новый и хороший энкодер, то можно обойтись и без них. Но на кнопку в любом случае потребуется конденсатор, так как ее дребезг неизбежен.

Используемые в схеме компоненты:

Скетч для Ардуино

Для того что бы отслеживать изменение положения энкодера в фоновом режиме, я использую прерывание 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<//**************************************** 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) 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; //сохраняем текущее состояние энкодера >

Заключение

Результат работы кода меня порадовал и теперь я могу продолжить работу над своим новым проектом, который скоро здесь выложу. Надеюсь эта короткая статья вам понравилась и вы сможете воспользоваться моей наработкой в своих самоделках.

Если у Вас остались вопросы и замечания, пишите их в комментариях. Я с удовольствием на них отвечу.

Arduino + encoder — обработка высоких оборотов

Небольшой очерк как решить простую практическую задачу по обработке показаний с инкрементарного энкодера (E6B2 -CWZ1X) на arduino. Данная задача возникла в связи с необходимостью точного измерения пройденного расстояния в помещении. Энкодер соединен с колесом достаточно большого диаметра через редуктор. Размеры колеса, редуктора для целей задачи пока не имеют значение. Первично — считывать показания энкодера на достаточно больших оборотах.

Шаг первый. Uno

За основу был взят «золотой стандарт»: arduino uno и код, скорость работы которого, не подвергалась скептическому анализу:

код для arduino

/* Максимально быстрый универсальный код для обработки энкодера Работает на перывании (используется одно) Тут код построен на 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()

*ссылка на оригинал кода и статью.

Код работал без нареканий, однако после крепления энкодера на вал (через редуктор), выяснилось следующее. При движении, энкодер шлет слишком большой поток показаний (ticks) и вывод быстро ими забивается и виснет. Это связано, как выяснилось, не только с самой моделью энкодера, который выдавал 1000 ticks на оборот, но и с микроконтроллером arduino.
Были предприняты попытки выводить не все шаги энкодера, а каждый 10 или каждый 100 шаг, заменить arduino uno на nano, увеличить скорость serial portа до максимума, использовать иные варианты кода для arduino. Однако проблему это не решило, и arduino все так же умирал на высоких оборотах энкодера.

Встал вопрос: брать энкодер с меньшим количеством шагов (минимальный 100 против текущих 1000) у того же производителя либо заменить arduino на что-то еще. Пошли по второму пути, поглядывая на первый.

Шаг второй. Чем заменить Uno

Выбор пал на достаточно доступную в продаже Nodemcu v.3 на esp8266, у которой и достаточное количество пинов и частота (тактовая частота: 80 – 160 МГц против 16 МГц arduino). Однако найти внятный, быстрый код под плату не удалось, а колхозить не было времени и желания. Кроме того, плата оказалась с дефектом и не работала через micro-usb.

Очень интересной показалась Wemos ESP32, на которой еще и уютно расположился micro-display, но на шаге вытянутой руки ее не было, и тут на глаза попалась raspberry pico. Но, с pico тоже оказалось не все так гладко в части скорости работы с прерываниями — »
*фото из видео.

Поэтому решили временно в ее сторону не смотреть.

Самый мелкий arduino

В итоге, как всегда, остановились на том, что было «под рукой» — на Seeeduino-XIAO, который по размерам чем-то напоминает digispark, но выгодно отличается по характеристикам (до 48 МГц против 16 МГц).

Подробно о том как с ним работать можно почитать на странице разработчика.

Так как данный микроконтроллер можно условно отнести к семейству arduino, предыдущий код на нем заработал с небольшими косметическими правками.

Код для xiao

#define ENC_A 0 // пин энкодера #define ENC_B 1 // пин энкодера volatile int encCounter; volatile boolean flag, resetFlag; volatile byte curState, prevState; void setup() < Serial1.begin(115200); while (!Serial); attachInterrupt(0, int0, CHANGE); attachInterrupt(1, int0, CHANGE); >void int0() < encTick(); >// алгоритм со сбросом от Ярослава Куруса void encTick() < curState = digitalRead(ENC_A) | digitalRead(ENC_B) if (curState == 0b00) resetFlag = 1; prevState = curState; > void loop() < if (flag) < Serial1.println(encCounter); flag = 0; >> 

Вместо serial — serial1, пины 0,1. Не все digital пины можно использовать, как оказалось: 4-й, 5й и 7й одновременно. Сам seral port «висит» на 6,7 пинах. Однако, этого достаточно, так как для общения с энкодером нужно 2 пина.

Еще один момент который необходимо иметь в виду, это то, что xiao использует 3,3 V логику и подключать напрямую к нему энкодер небезопасно. Поэтому использовался логический согласователь уровней 5V-3,3V.

Общая схема сопряжения выглядит так:

При работе с xiao также есть небольшие особенности. Контроллер имеет type-C вход и на это сразу «покупаешься», когда видишь. Однако, этот вход только для питания и прошивки. Как serial port его использовать нельзя, а жаль.

Также для перезагрузки xiao нет никакой внешней кнопки (в виду размеров самого контроллера видимо) и чтобы его перезагрузить необходимо замкнуть два контакта на лицевой стороне либо на оборотной стороне (об этом написано на странице разработчика). В остальном работа с ним такая же как и с другими представителями семейства arduino.

Итог

В результате замены arduino uno на собрата меньшего размера с лучшими характеристиками проблема была решена. Задержек при выводе в serial и подвисаний не выявлено:

Продолжение. Назад к истокам.

В работу по проекту в итоге пошло arduino nano со следующим

#include EncButton enc; // энкодер с кнопкой //EncButton enc; // просто энкодер //EncButton enc; // просто кнопка int32_t a = 100; int32_t b = -100; void setup() < Serial.begin(115200); // ещё настройки //enc.counter = 100; // изменение счётчика энкодера //enc.setHoldTimeout(500); // установка таймаута удержания кнопки //enc.setButtonLevel(HIGH); // LOW - кнопка подключает GND (умолч.), HIGH - кнопка подключает VCC >void loop() < enc.tick(); // опрос происходит здесь // =============== ЭНКОДЕР =============== // обычный поворот if (enc.turn()) < //Serial.println("turn"); if (enc.counter==a)< // можно опросить ещё: Serial.println(enc.counter); // вывести счётчик //Serial.println(enc.fast()); // проверить быстрый поворот //Serial.println(enc.getDir()); // направление поворота a+=100; >else if (enc.counter==b) < Serial.println(enc.counter); // вывести счётчик b-=100; >> > 

Библиотеку для кода можно взять отсюда.
Ссылка на оригинал библиотеки — здесь.

Однако, чтобы энкодер отсчитывал более 32767 шагов и не сбрасывал далее, необходимо в самой библиотеке EncButton.h поправить тип переменной:
— найти установленную библиотеку в системе (например, D:\arduino-scetches\libraries\EncButton-main\src\EncButton.h);
— заменить в 254 строке int16_t на int32_t (сама строка после изменения: int32_t counter = 0;).

Подключение к arduino nano 2,3 ноги arduino к черному и белому проводам энкодера, gnd arduino к синему, 5v к коричневому.

«Читать» на стороне raspberry pi или иного пк можно по usb с помощью кода на

#!/bin/bash #from encoder to console while [ true ] do stty -F /dev/ttyACM0 cs8 9600 ignbrk -brkint -icrnl -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke noflsh -ixon -crtscts raw while read LINE do echo $LINE done < /dev/ttyACM0 /bin/sleep 3 done 

Код python чтения с энкодера здесь.

Arduino и энкодер

Энкодер – общее название устройств, преобразующих одну величину в другую. В данном случае энкодер – это устройство, преобразующее вращательное механическое движение в цифровой сигнал, а сам энкодер в этом случае называется “поворотным” (вращательным, круговым). В наборе GyverKIT идёт инкрементальный поворотный энкодер с кнопкой, который служит очень удобным органом управления для электронного устройства и заменяет сразу несколько кнопок или джойстик, обеспечивая быструю навигацию по пунктам меню и изменение настроек. По сути похож на потенциометр, но не имеет ограничения по углу поворота. Особенности данного модуля:

  • Качественный инкрементальный энкодер с кнопкой
  • 20 “щелчков” на один оборот
  • Выведено питание (5V, GND), два пина энкодера (S1, S2) и пин кнопки (KEY)
  • Работает также от 3.3V (для Wemos)
  • Все логические пины подтянуты к VCC резисторами на плате
  • RC цепи гашения дребезга на выводах энкодера

Инкрементальный энкодер выдаёт импульсные логические сигналы со своих пинов на каждый щелчок. Сигналы отличаются по фазе, что позволяет определить направление текущего щелчка.

Алгоритмы опроса энкодера мы рассматривать не будем, это тема для отдельного серьёзного урока, поэтому научимся подключать модуль и работать с готовой библиотекой.

Подключение

Модуль подключается на питание, логические выводы – на любые цифровые пины. В случае с Wemos – на все кроме D8, так как подтяжка к VCC помешает МК запуститься. Подключу выводы энкодера на D2 и D3, а кнопку – на D4.

Библиотеки

Существует несколько библиотек для работы с энкодером, например:

В примерах на этом сайте мы будем использовать EncButton, т.к. она имеет самый большой набор вариантов опроса энкодера (поворот, нажатый поворот, быстрый поворот), а также хорошо оптимизирована и поддерживает расширенную работу с кнопкой. Библиотека идёт в архиве к набору GyverKIT, а свежую версию всегда можно установить/обновить из встроенного менеджера библиотек Arduino по названию EncButton. Краткая документация находится по ссылке выше, базовые примеры есть в самой библиотеке.

Внимание! 22.08.2023 библиотека EncButton обновилась и работа с ней чуть отличается от старой версии, см. документацию по ссылке выше.

Примеры

Меняем значение переменной

/* Меняем значение переменной при помощи энкодера Обычный поворот +-1 "Нажатый" поворот +-5 */ #include EncButton enc(2, 3, 4); void setup() < // порт для связи Serial.begin(9600); >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); >>

Подключение энкодера к Ардуино (KY-040)

Подключение энкодера к Ардуино

Для работы с энкодером потребуется установить библиотеку RotaryEncoder.h, скачать архив можно здесь. Если у вас возникли трудности с установкой библиотеки для энкодера, то рекомендуем ознакомиться с информацией, как установить библиотеку в Arduino IDE. Для подключения KY-040 к Arduino можно использовать цифровые и аналоговые пины микроконтроллера, в наших примерах используются пины A2-A3.

Скетч. Пример Ардуино энкодер прерывание

Разберем для начала самый простой пример управления энкодером от Ардуино с помощью прерываний, где вся черновая работа по определению положения ручки энкодера выполняется с помощью библиотеки RotaryEncoder.h. Мы лишь выводим данные на монитор порта Arduino IDE. Соберите схему с энкодером, как показано на картинке выше и загрузите в микроконтроллер Arduino следующую программу.

#include "RotaryEncoder.h" // библиотека для энкодера RotaryEncoder encoder(A2, A3); // пины подключение энкодера (DT, CLK) static int pos = 0; // задаем начальное положение энкодера int newPos; void setup() < Serial.begin(9600); Serial.println(pos); // выводим на монитор начальное значение > void loop() < // проверяем положение ручки энкодера encoder.tick(); newPos = encoder.getPosition(); // если положение изменилось - выводим на монитор if (pos != newPos) < Serial.println(newPos); pos = newPos; > >
Пояснения к коду:
  1. начальное положение ручки равно нулю static int pos = 0; и это значение выводится на мониторе порта в процедуре void setup в начале программы;
  2. в примере программы для энкодера нет ограничения по положению рукоятки датчика KY-040, положение может быть положительным и отрицательным.

Как подключить энкодер с кнопкой

Скетч. Управление энкодером Ардуино светодиодом

Следующий пример позволит управлять яркостью светодиода с помощью ШИМ сигнала, а также включать и выключать второй светодиод с помощью нажатия кнопки энкодера. Для этого соберите схему, размещенную выше. Обратите внимание, что порт SW энкодера подключен к пину 2 Arduino, а светодиоды к 12 и 11 пину. После сборки схемы загрузите следующий код управления светодиодами на Arduino с энкодером.

#include "RotaryEncoder.h" // библиотека для энкодера RotaryEncoder encoder(A2, A3); // пины подключение энкодера (DT, CLK) // задаем шаг энкодера и макс./мин. значение #define STEPS 5 #define POSMIN 0 #define POSMAX 255 int lastPos, newPos; void setup() < pinMode(2, INPUT_PULLUP); // пин подключения кнопки энкодера (SW) pinMode(11, OUTPUT); // пины для подключения светодиодов pinMode(12, OUTPUT); Serial.begin(9600); encoder.setPosition(10 / STEPS); > void loop() < // проверяем положение ручки энкодера encoder.tick(); newPos = encoder.getPosition() * STEPS; if (newPos < POSMIN) < encoder.setPosition(POSMIN / STEPS); newPos = POSMIN; >else if (newPos > POSMAX) < encoder.setPosition(POSMAX / STEPS); newPos = POSMAX; >// если положение изменилось - выводим на монитор if (lastPos != newPos) < Serial.println(newPos); lastPos = newPos; > // включаем первый светодиод с установленной яркостью analogWrite(11, lastPos); // включаем второй светодиод с помощью нажатия кнопки if (digitalRead(2)==HIGH) < digitalWrite(12, LOW); > if (digitalRead(2)==LOW) < digitalWrite(12, HIGH); > >
Пояснения к коду:
  1. для пина 2 используется конфигурация INPUT_PULLUP, так как кнопка энкодера подключена к GND и при ее нажатии на цифровом входе будет сигнал LOW;
  2. в программе можно изменить максимальное и минимальное значение поворота энкодера, а также шаг вращения ручки датчика ky-040.

Скетч. Управление серво мотором от энкодера

Управление серво от энкодера

Следующий скетч демонстрирует подключение серво с энкодером. Логический провод микросерво (желтый или оранжевый цвет) подключается к ШИМ порту 11 микроконтроллера. В программе задается угол поворота серво с помощью поворота ручки модуля ky-040. Работа программы похожа на управление потенциометром серво, но в случае с энкодером, можно задавать максимально точный угол поворота.

#include "RotaryEncoder.h" // библиотека для энкодера RotaryEncoder encoder(A2, A3); // пины подключение энкодера (DT, CLK) #include Servo.h> // библиотека для сервопривода Servo servo; // задаем шаг энкодера, макс./мин. значение поворота #define STEPS 10 #define POSMIN 0 #define POSMAX 180 int lastPos, newPos; void setup() < servo.attach(11); // пин для подключения серво Serial.begin(9600); encoder.setPosition(10 / STEPS); > void loop() < // проверяем положение ручки энкодера encoder.tick(); newPos = encoder.getPosition() * STEPS; if (newPos < POSMIN) < encoder.setPosition(POSMIN / STEPS); newPos = POSMIN; >else if (newPos > POSMAX) < encoder.setPosition(POSMAX / STEPS); newPos = POSMAX; >// если положение изменилось - выводим на монитор if (lastPos != newPos) < Serial.println(newPos); lastPos = newPos; > // устанавливаем угол поворота микросерво servo.write(lastPos); >
Пояснения к коду:
  1. шаг поворота качалки серво Ардуино составляет 10 градусов. Шаг поворота и максимальный угол поворота можно изменить в программе.

Заключение. Мы рассмотрели несколько вариантов подключения модуля энкодера к Arduino: пример с прерыванием и управлением светодиодом, пример с управлением мотором серво. Также модуль можно использовать для управления меню на дисплее 1602 LCD Ардуино. Мы использовали библиотеку RotaryEncoder.h, но есть еще несколько библиотек энкодера для Ардуино, которые в одном обзоре не уместить.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *