Небольшой очерк как решить простую практическую задачу по обработке показаний с инкрементарного энкодера (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 тоже оказалось не все так гладко в части скорости работы с прерываниями — "
*фото из видео.
Поэтому решили временно в ее сторону не смотреть.
Самый мелкий arduin.
В итоге, как всегда, остановились на том, что было «под рукой» — на 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 и подвисаний не выявлено: