Arduino a rotačný enkodér

Zápisník experimentátora

Hierarchy: Rotačný enkodér

Skúmať vlastnosti rotačného enkodéra môžeme pohodlne pomocou Arduina. Na pár pinov si pripojíme rotačný enkodér a prečítané údaje si odošleme pomocou sériového portu do počítača. V článku sa pozrieme na rotačný enkodér bez ošetrenia zákmitov aj s ošetrením zákmitov. A na záver to vylepšíme pomocou prerušení.

Súčiastky

Budeme potrebovať:

  • Arduino Uno alebo Mega
  • Rotačný enkodér - Napríklad tento z Banggood alebo z Ebay. Keď som kupoval ja, najvýhodnejšie sa javilo kúpiť 5 kusov na Ebay. Tie s tlačidlom sú drahšie, ale máte zase funkciu naviac, ktorá sa dá aj využiť.
  • Skúšobné pole - Zastrčenie enkodéra je trošku problematické. Musel som na ňom odštiknúť dva bočné pliešky, ktoré sa používajú na pevnejšie uchytenie enkodéra do plošného spoja. A piny na enkodéri sú trochu hrubšie.

Schéma

V programe budeme používať interné pull-up rezistory, preto si to môžeme dovoliť zapojiť takto. Tieto rezistory nám zodvihnú napätie na bodoch A a B na 5 V. Pri otáčaní hriadeľom sa budú skratovať na GND a dávať nám signály pre Arduino.

Bez ošetrenia zákmitov

V prvom programe chceme dosiahnuť zákmity na kontaktoch. Preto nebude výsledok pri točení hriadeľom v jednom smere konzistentný. Občas sa vyskytne aj falošný signál. Nie vždy je ale nevyhnutné mať všetko dokonalé. Napríklad pri nastavovaní LED diódy cez PWM by ste ani pri takýchto náhodných chybách nevideli na jase diódy nejaké závažné defekty. Tento kód treba brať len ako cvičenie pred elegantnejším spracovaním signálov. Z kódu je totiž dosť dobre vidno, akou logikou sa signál spracováva.

int pinA = 3;  // Connected to A
int pinB = 4;  // Connected to B
int encoderPosCount = 0;
int pinALast;
int aVal;
boolean bCW;

void setup() {
  pinMode(pinA, INPUT_PULLUP);
  pinMode(pinB, INPUT_PULLUP);
  pinALast = digitalRead(pinA);
  Serial.begin(9600);
  Serial.println("Rotary Encoder No Debounce");
}

void loop() {
  aVal = digitalRead(pinA);
  if (aVal != pinALast) { // Means the knob is rotating
    // if the knob is rotating, we need to determine direction
    // We do that by reading pin B.
    if (digitalRead(pinB) != aVal) {  // Means pin A Changed first - We're Rotating Clockwise
      encoderPosCount++;
      bCW = true;
    } else {// Otherwise B changed first and we're moving CCW
      bCW = false;
      encoderPosCount--;
    }
    Serial.print("Rotated: ");
    if (bCW) {
      Serial.println("clockwise");
    } else {
      Serial.println("counterclockwise");
    }
    Serial.print("Encoder Position: ");
    Serial.println(encoderPosCount);
  }
  pinALast = aVal;
}

S ošetrením zákmitov

V druhom programe ošetríme zákmity pomocou špeciálneho algoritmu, ktorý je implementovaný v šikovnej knižnici. Nájdete ju na stránke Rotary encoders, done properly. Keď sa pozriete do jej zdrojového kódu, nájdete tam jednoduchý algoritmus, ktorý je založený na tabuľke možných prechodov medzi stavmi. Iba tie, ktoré zodpovedajú očakávanému správaniu, dávajú signály. Ostatné stavy sú ignorované, pretože sa jedná o stavy, ktoré vzniknú pri zákmitoch a v programe ich chcete odfiltrovať.

Samotný program je potom veľmi jednoduchý. Iba sa pravidelne kotroluje signál z rotačného enkodéra a podľa signálu sa pripočíta alebo odpočíta hodnota v premennej a do PC sa odošlú ladiace informácie.

#include <rotary.h>

Rotary r = Rotary(3, 4);
int encoderPosCount = 0;

void setup() {
  Serial.begin(9600);
  Serial.println("Rotary Encoder Debounce");
}

void loop() {
  char result = r.process();
  if (result) {
    if (result == DIR_CCW) encoderPosCount--;
    if (result == DIR_CW) encoderPosCount++;

    Serial.print("Rotated: ");
    if (result == DIR_CW) {
      Serial.println("clockwise");
    } else {
      Serial.println("counterclockwise");
    }
    Serial.print("Encoder Position: ");
    Serial.println(encoderPosCount);
  }
}

Vylepšenie spracovania pomocou prerušenia

V treťom programe doplníme predchádzajúci program o prerušenia. Ak si pozorne preštudujete predchádzajúci program, zistíte, že celý čas nerobí nič iné, iba kontroluje, či sa hriadeľ rotačného enkodéra nepohol. A to sa deje neustále, bez ohľadu nato, či s hriadeľom niečo robíte alebo nie. Ako možno niečo takého vylepšiť, aby nám v hlavnom programe ostal čas na niečo iné?

Odpoveďou sú prerušenia, konkrétne pin change prerušenia. Tie môžu byť sledované na každom pine a dajú nám signál na spracovanie iba vtedy, keď sa niečo deje. Podrobnosti o takýchto prerušeniach si môžete nájsť na samostatnej stránke. V tomto príklade budeme zase používať iba jeden rotačný enkodér, ale nič nám nebráni v prerušení spracovať aj viac enkodérov.

V tabuľke prerušení si nájdeme, ktoré bity musíme nastaviť pre pin 3 a 4. Vidíme, že budeme musieť spracovať prerušenie PCINT2. Pretože budeme pristupovať ku premenným aj z hlavného programu aj z prerušenia, deklarujeme premenné ako volatile. Spracovanie presunieme z hlavného programu do obsluhy prerušenia. Do hlavného programu budeme signalizovať zmenu cez premennú signal. Kontrola premennej na začiatku prerušenia je tam len preto, aby sme v hlavnom programe stihli odoslať ladiace informácie do PC. Pokiaľ nie je odoslanie dokončené, radšej ignorujeme signály z enkodéra.

Nastavenie správnych bitov v registroch vidíte vo funkcii setup. A hlavný program vo funkcii loop iba odosiela ladiace informácie do PC.

#include <rotary.h>

Rotary r = Rotary(3, 4);
volatile int encoderPosCount = 0;
volatile char result;
volatile bool signal = false;

ISR(PCINT2_vect)
{
  if(signal)
    return;
  result = r.process();
  if (result) {
    if (result == DIR_CCW) encoderPosCount--;
    if (result == DIR_CW) encoderPosCount++;
    signal = true;
  }
}

void setup() {
  Serial.begin(9600);
  Serial.println("Rotary Encoder Interrupt");

  // pin change interrupt 3, 4
  PCMSK2 |= bit (PCINT19); // want pin 3
  PCMSK2 |= bit (PCINT20); // want pin 4
  PCIFR  |= bit (PCIF2);   // clear any outstanding interrupts
  PCICR  |= bit (PCIE2);   // enable pin change interrupts for D0 to D7
}

void loop() {
  if (signal)
  {
    Serial.print("Rotated: ");
    if (result == DIR_CW) {
      Serial.println("clockwise");
    } else {
      Serial.println("counterclockwise");
    }
    Serial.print("Encoder Position: ");
    Serial.println(encoderPosCount);
    signal = false;
  }
}


Download

20.12.2015


Menu