Алгоритм для надёжного считывания показаний энкодера угла поворота для Arduino


SAGE


В процессе экспериментов с аналогом платы Arduino Uno выяснилось, что нет достаточно надёжного кода для чтения показаний механического энкодера угла поворота, извлечённого из мышки. Представлено лишь вагон и маленькая тележка полурабочих примеров, работающих крайне нестабильно. Пришлось устранить эту досадную неприятность и написать этот код. Проблема взаимодействия с механическим энкодером заключается в том, что надо эффективно решить проблему дребезга контактов, которая присуща этому устройству, так же как и кнопкке. Для кнопки достаточно устроить задержку в несколько миллисекунд. Для энкодера же надо анализировать его состояние с высокой частотой, и задержки тут сразу снизят эффективность. В примерах достаточно недвусмысленно дают понять, что более эффективный алгоритм дожен использовать прерывания. Чем я и воспользовался.

Чтение состояния выходов энкодера осуществляется 2-мя прерываниями по изменению уровня. Каждое событие прерывания снабжается уникальным идентификатором разрядностью в 1 байт, чего вполне достаточно, если в основном цикле Arduino анализировать поступившие события без дополнительных задержек. Для идентификации используется однобайтовое число со знаком (short). Постоянный инкремент его значения естественно даёт переполнение, но в этом нет ничего страшного если знать как с такими числами правильно работать. В частности, не смотря на переполнение, мы можем ввести для каждого из двух чисел получающейся последовательности, если они не слишком удалены друг от друга, такую операцию как расстояние между двумя такими числами (функция EventsBetween).

Через каждую 1 мс осуществляется анализ поступивших событий за этот период. Анализируется количество событий (используем EventsBetween) и специальная сумма, помогающая определить, какой уровень входного сигнала в данных событиях преобладал. Всякое событие понижения уровня входит в сумму как слагаемое, равное -1, а всякое событие повышения уровня как слагаемое, равное +1. Таким образом, если сумма получилась отрицательной вероятнее всего произошло понижение уровня, если сумма положительна - скорее всего произошло повышение уровня, если сумма оказалась равной 0, такой результат не несёт никакой определённой полезной информации об уровне (является просто шумом) на данном входе и отбрасывается, а за уровень соответвующего выхода энкодера принимается предыдущее значение. Т.е. для рассмотрения событий в качестве достоверного источника информации необходимо что-бы были зафиксированны какие-то события (счётчик количества событий не равен 0) и сумма по уровням этих событий тоже не была равна 0. Этот приём эффективно решает проблему дребезга контактов и не требует каких-либо дополнительных временных задержек. Анализ состояния через каждые 1 мс показал высокую эффективность. Т.е. опрос энкодера идёт реально с максимальной частотой с которой микроконтроллер способен обрабатывать прерывания, а анализ информации, накопленной при обработке прерываний происходит с частотой порядка 1 кГц, чего более чем достаточно. Во время отладки алгоритма было видно, что за период 1 мс может происходить до 40-50 прерываний и предусмотренные мною приёмы обрабатывают такой значительный для микроконтроллера поток информации достаточно эффективно.

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

// Encoder pins
#define encoder0PinA 2
#define encoder0PinB 3
// LED pin
#define LED_PIN 9

// Encoder inputs in high state
unsigned short A_high, B_high;
// Count of encoder events between two timer events
unsigned short A_between = 0, B_between = 0;
// Previous decission on encoder state
unsigned short A_statePrev = 0, B_statePrev = 0;

// Current event id for each encoder input
short A_event = 0, B_event = 0;
// Id of event that already accounted
short A_eventPrev = 0, B_eventPrev = 0;
// Timestamps for time measurements
unsigned long timeCur, timePrev;
// Value for estimation of dominant inputs state
short A_level = 0, B_level = 0;

int encoder0Pos = 127;
int fadeAmount = 10;

// Number of events between two event id's
int EventsBetween(short iCur, short iPrev) {
  if ((iCur < 0) && (iPrev < 0)) {
    return (int(iCur) + 256) - (int(iPrev) + 256);
  } else if (iCur < 0) {
    return (int(iCur) + 256) - iPrev;
  }
  return iCur - iPrev;
}

// Keep value in limits
int clamp(int value, int low, int high) {
  if (value < low) {
    return low;
  }
  if (value > high) {
    return high;
  }
  return value;
}

void setup() {
  pinMode(encoder0PinA, INPUT); 
  pinMode(encoder0PinB, INPUT);
  pinMode(LED_PIN, OUTPUT);
  // encoder pin on interrupt 0 (pin 2)
  attachInterrupt(0, doEncoderA, CHANGE);
  // encoder pin on interrupt 1 (pin 3)
  attachInterrupt(1, doEncoderB, CHANGE);
  //Serial.begin(9600);
  analogWrite(LED_PIN, encoder0Pos);
  timePrev = millis();
}

void loop(){ 
  timeCur = millis();
  if (timeCur > timePrev) {
    timePrev = timeCur;
    boolean A_change = A_between && A_level;
    boolean B_change = B_between && B_level;
    if (A_change || B_change) {
      boolean A_state, B_state;
      if (A_change) {
        A_state = A_level > 0;
      } else {
        A_state = A_statePrev;
      }
      if (B_change) {
        B_state = B_level > 0;
      } else {
        B_state = B_statePrev;
      }
      if ((A_change && ((!A_state && A_statePrev && B_state) || (A_state && !A_statePrev && !B_state))) ||
        (B_change && ((!B_state && B_statePrev && !A_state) || (B_state && !B_statePrev && A_state)))) {
        encoder0Pos += fadeAmount;
      }
      if ((A_change && ((!A_state && A_statePrev && !B_state) || (A_state && !A_statePrev && B_state))) ||
        (B_change && ((!B_state && B_statePrev && A_state) || (B_state && !B_statePrev && !A_state)))){
        encoder0Pos -= fadeAmount;
      }
      encoder0Pos = clamp(encoder0Pos, 0, 255);
      analogWrite(LED_PIN, encoder0Pos);
      A_statePrev = A_state;
      B_statePrev = B_state;
    }
    A_between = 0;
    B_between = 0;
    A_level = 0;
    B_level = 0;
  }
  else
  {
    if (EventsBetween(A_event, A_eventPrev) >= 1) {
      A_eventPrev = A_event;
      if (A_high) {
        A_level++;
      } else {
        A_level--;
      }
      A_between++;
    }
    if (EventsBetween(B_event, B_eventPrev) >= 1) {
      B_eventPrev = B_event;
      if (B_high) {
        B_level++;
      } else {
        B_level--;
      }
      B_between++;
    }
  }
}

// Interrupt on A changing state
void doEncoderA(){
  A_high = (digitalRead(encoder0PinA) == HIGH);
  A_event++;
}

// Interrupt on B changing state
void doEncoderB(){
  B_high = (digitalRead(encoder0PinB) == HIGH);
  B_event++;
}




Дата последнего обновления: 24-1-16 20:14:15


 

Copyright © 2005-2017 SAGE. Все права защищены.