Как стать автором
Обновить

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

Время на прочтение 5 мин
Количество просмотров 11K


Небольшой очерк как решить простую практическую задачу по обработке показаний с инкрементарного энкодера (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 чтения с энкодера здесь.
Теги:
Хабы:
Всего голосов 20: ↑8 и ↓12 -4
Комментарии 94
Комментарии Комментарии 94

Публикации

Истории