en EN   ru RU   uk UK

Головна

ОС A2

ДРАКОН

Arduino

Encoder

Програми

Утиліти

Посилання

Веб-майстру

Зв'язок з автором

Резюме


A2 OS forum

 
  Версія для друку

Алгоритм для надійного зчитування показань енкодера кута повороту для 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++;
}




Дата останнього оновлення: 23-2-20 23:11:02


 

alt CodeTyphon

Copyright © 2005-2020 SAGE. Всі права захищено.