Небольшой очерк как решить простую практическую задачу по обработке показаний с инкрементарного энкодера (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() {
Serial.println(encCounter);
delay(100);
}
*ссылка на оригинал кода и статью.
Код работал без нареканий, однако после крепления энкодера на вал (через редуктор), выяснилось следующее. При движении, энкодер шлет слишком большой поток показаний (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) << 1; // digitalRead хорошо бы заменить чем-нибудь более быстрым
if (resetFlag && curState == 0b11) {
if (prevState == 0b10) encCounter++;
if (prevState == 0b01) encCounter--;
resetFlag = 0;
flag = true;
}
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.h>
EncButton<EB_TICK, 2, 3, 4> enc; // энкодер с кнопкой <A, B, KEY>
//EncButton<EB_TICK, 2, 3> enc; // просто энкодер <A, B>
//EncButton<EB_TICK, 4> enc; // просто кнопка <KEY>
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 с помощью кода на
bash
#!/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 чтения с энкодера здесь.