Obsluha zatlačenia tlačidla a obsluha prerušení

predchádzajúcej časti sme avizovali, že ako zhrnutie témy o meraní hodnôt analógových veličín a spínaní zariadení pomocou digitálnych výstupov ukážeme príklad automatizácie nejakého jednoduchého procesu, napríklad stále populárneho zväčšovania čiernobielych fotografií. Fungovalo by to jednoducho – po zatlačení tlačidla sa odmeria intenzita svetla na vybranom mieste fotografie, podľa toho sa vypočíta expozičný čas a po zatlačení ďalšieho tlačidla sa na tento čas rozsvieti žiarovka zväčšováka. Je to jednoduchý a zrozumiteľný príklad. Všetko potrebné na realizáciu tohto príkladu sme už prebrali – okrem jednej maličkosti. V zadaní úlohy sa dvakrát vyskytuje fráza: „po zatlačení tlačidla…“.

Fungovanie (a niekedy aj nefungovanie) príkladov z tejto časti je ukázané vo videu.

Na prvý pohľad je to jednoduché – tlačidlo pripojíte na niektorý digitálny pin naprogramovaný ako vstup. V zapojení podľa obrázka je tlačidlo pripojené na digitálny vstup cez 10 kΩ odpor proti zemi a po zopnutí sa na pin pripojí napájacie napätie 5 V. Takže ak je tlačidlo rozpojené, na vstupnom pine je úroveň LOW čiže logická 0. Po zopnutí tlačidla bude na vstupe úroveň HIGH, teda logická 1.

Zapojenie na prepojovacom poli

Najjednoduchší kód na načítanie stavu tlačidla na pine D2 a jeho indikáciu pomocou LED na pine D13. V hlavnej sľučke sa periodicky sníma stav tlačidla a podľa toho sa nastavuje digitálny pin nakonfigurovaný ako výstupný, na ktorom je pripojená LED dióda L1, umiestnená štandardne na väčšine dosiek z rodiny Arduino.

int nTlacidlo = 0;   //stav tlačidla

void setup() {
  pinMode(2, INPUT);
  pinMode(13, OUTPUT);
}

void loop() {
  nTlacidlo = digitalRead(2);
  digitalWrite(13, nTlacidlo);
}

Tento kód funguje, takže sa zdá, že obsluha tlačidla je triviálna záležitosť. Opak je pravda. Skúste nepatrne zložitejší kód, ktorý využíva tlačidlo ako prepínač stavov. Ak bola LED dióda predtým zhasnutá, po zatlačení tlačidla sa rozsvieti a opačne.

int nTlacidlo = 0;   //stav tlačidla
int nLed = LOW;        //stav LED
 
void setup() {
 pinMode(2, INPUT);
 pinMode(13, OUTPUT);
}
 
void loop() {
 nTlacidlo = digitalRead(2);
  if (nTlacidlo == HIGH) 
  {
    if (nLed == LOW) nLed = HIGH;
   else             nLed = LOW;
  }
  
 digitalWrite(13, nLed);
}

Pri testovaní tohto kódu zistíte, že funguje, ale pomerne nespoľahlivo. Nie vždy sa po zatlačení a uvoľnení tlačidla zmení stav LED diódy. Dôvodom sú zákmity v trvaní niekoľko málo milisekúnd na kontaktoch mechanického tlačidla pri zopnutí alebo rozopnutí, ktoré sú dostatočne dlhé na to, aby ich Arduino zachytilo a vyhodnotilo.

Falošný impulz pri rozopnutí tlačidla, zachytený osciloskopom

Preto je potrebné ošetrenie zákmitov kontaktov mechanického tlačidla. Ošetrenie môže byť hardvérové alebo softvérové. Kód na ošetrenie zákmitov nájdete v príkladoch, ktoré sú dostupné cez menu vývojového prostredia Arduino IDE Súbor -> Príklady -> 02. Digital -> Debounce. Tento kód využíva časové oneskorenie 50 ms od posledného snímania stavu, počas ktorého sa predpokladá, že sa prechodové javy na kontaktoch ustália. V prípade potreby môžete pri nekvalitných tlačidlách tento čas predĺžiť. Na meranie časových intervalov sa využíva funkcia millis(), ktorá do premennej typu long  zapíše čas v milisekundách, ktorý uplynul od spustenia kódu v mikrokontroléri.

// konštanty priradenia pinov:
const int buttonPin = 2;    
const int ledPin = 13;      
 
// premenné:
int ledState = HIGH;         // aktuálny stav výstupného pinu
int buttonState;             // aktuálny stav načítaný zo vstupného pinu
int lastButtonState = LOW;   // predchádzajúci stav načítaný zo vstupného pinu
 
unsigned long lastDebounceTime = 0;  // čas poslednej zmeny výstupného pinu
unsigned long debounceDelay = 50;    // oneskorenie na ošetrenie zákmitov
 
void setup() {
 pinMode(buttonPin, INPUT);
 pinMode(ledPin, OUTPUT);
 digitalWrite(ledPin, ledState);
}
 
void loop() {
  int reading = digitalRead(buttonPin); //lokálna premenná
 
  // ak sa hodnota vstupu zmenila
  if (reading != lastButtonState) {
    // nastav čas
    lastDebounceTime = millis();
  }
 
  //ak sa udrží stav dlhšie ako nastavené oneskorenie je to aktuálna hodnota
  if ((millis() - lastDebounceTime) > debounceDelay) {
 
    // ak sa stav zmeni:
    if (reading != buttonState) {
     buttonState = reading;
 
      // nastav stav LED
      if (buttonState == HIGH) {
       ledState = !ledState;
      }
    }
  }
 
  // nastav stav LED na výstupný pin:
 digitalWrite(ledPin, ledState);
 
  // zapamätaj naposledy načítaný stav:
 lastButtonState = reading;
}

Obsluha prerušenia

V doterajších príkladoch sme zisťovali stav tlačidla v hlavnej slučke programu a fungovalo to, pretože kód v tých príkladoch nerobil nič iné, len skenoval tlačidlo a rozsvecoval LED diódu. Keby sa v hlavnej slučke realizoval zložitejší výpočet alebo postupnosť úkonov trvajúca niekoľko sekúnd, skenovanie stavu tlačidla povedzme každých 5 sekúnd by nemuselo zatlačenie tlačidla zachytiť. Preto sa na snímanie udalostí tohto typu, ktoré môžu nastať kedykoľvek, využíva mechanizmus nazývaný prerušenie. Takto môže mikrokontrolér operatívne reagovať na podnety od rôznych hardvérových komponentov, zatiaľ čo aplikácia v hlavnej slučke vykonáva naprogramovanú činnosť. Prerušenie môže byť aj interné, napríklad periodicky od časovača alebo od komunikačných obvodov, takže dokáže operatívne reagovať na údaje prijaté cez sériovú komunikáciu, I2C, SPI a podobne. Externé prerušenie nastáva pri zmene stavu na definovanom pine.

Prerušeniam mikrokontroléra ATmega328P, ktorý je na doske Arduino, sa budeme venovať podrobnejšie v ďalších častiach seriálu, takže uvedieme len nevyhnutné základy potrebné na obsluhu tlačidla v prerušení. Arduino IDE má na prácu s prerušeniami funkcie attachInterrupt() a detachInterrupt(). Význam parametrov: attachInterrupts(číslo_prerušenia, funkcia_obsluhy, mód);

Upozornenie: Číslo prerušenia sa v týchto funkciách nezhoduje s číslom digitálneho pinu. Pre Arduino UNO sa prerušenie 0 generuje na pine D2 a prerušenie 1 na pine D3. Je to preto, lebo piny D0 a D1 sa štandardne využívajú na sériovú komunikáciu pre signály RXD a TXD.

Piny štandardne použiteľné na externé prerušenia pre jednotlivé typy Arduino dosiek sú v tabuľke. Pre zaujímavosť, na prerušenia sa dá použiť aj väčšina ostatných pinov, ale to je už trochu zložitejšie programovanie.

Uno, Nano, Mini, s procesorom ATmega328 2, 3
Uno WiFi Rev.2 všetky digitálne piny
Mega, Mega2560, MegaADK 2, 3, 18, 19, 20, 21
Micro, Leonardo, other 32u4-based 0, 1, 2, 3, 7
Zero všetky digitálne piny okrem 4
Due všetky digitálne piny
101 všetky digitálne piny (iba piny 2, 5, 7, 8, 10, 11, 12, 13 fungujú s CHANGE)

V parametri mód môžete nastaviť preddefinované hodnoty:

  • LOW – prerušenie nastane, pokiaľ je na pine logická nula.
  • CHANGE – prerušenie nastane pri zmene logickej úrovne.
  • RISING – prerušenie nastane pri zmene z LOW na HIGH.
  • FALLING – prerušenie nastane pri zmene z HIGH na LOW.
  • HIGH – prerušenie nastane pri logickej jednotke na pine. Iba pre Arduino Due, Zero a MKR1000.

V hlavnej slučke alebo vo funkciách volaných z tejto sľučky môže byť kód, ktorý z rôznych dôvodov nie je vhodné prerušovať. Preto sú k dispozícii príkazy na zakázanie a povolenie prerušení.

void loop()
{
 noInterrupts(); //zakázanie prerušení
  // časovo kritický kód 
 interrupts();  //povolenie prerušení 
  // kód, ktorý je možné prerušovať
}  

Najskôr ukážem najjednoduchší príklad na obsluhu prerušenia 0 pomocou tlačidla pripojeného na pin D2. Parameter RISING definuje, že prerušenie nastane pri zmene stavu z LOW na HIGH. V príklade neriešime ošetrenie zákmitov tlačidla. V metóde na obsluhu prerušenia sa mení stav LED diódy, ak bola zhasnutá, rozsvieti sa a opačne. Všimnite si, že v hlavnej sľučke nie je žiadny kód, takže aplikácia okrem rozsvecovania a zhášania LED diódy v obsluhe prerušenia nerobí nič, iba realizuje nekonečný cyklus. V niektorom z ďalších pokračovaní ukážeme, ako sa dá Arduino prepnúť do režimu nízkej spotreby, čo oceníte pri napájaní zariadenia z batérie.

void setup()
{    
 pinMode(13, OUTPUT);
 attachInterrupt(0,ObsluhaPrerusenia, RISING);
}
 
void loop()
{
 
}
 
void ObsluhaPrerusenia()
{
  if (digitalRead(13) == LOW) digitalWrite(13, HIGH);
  else digitalWrite(13, LOW);
} 

A na záver ešte jeden zaujímavý príklad, keď jedna doska Arduino generuje prerušenie pre inú dosku. V reálnej konštrukcii tak môže jedno Arduino aktivovať druhé, aj keď na tento účel sa lepšie hodia niektoré typy komunikácie. V našom príklade na jednom Arduine beží kód aplikácie Blinky, ktorá bliká LED diódou na pine D13, táto LED dióda je umiestnená priamo na doske. Pin D3 tejto dosky je pripojený na pin D2 druhého Arduina. Kód programu na tejto doske je identický ako pri obsluhe tlačidla v prerušení.

void setup() {
 pinMode(13, OUTPUT); //nastav pin 13 ako výstupný
}
 
void loop() {
 digitalWrite(13, HIGH); //pin 13 napatie 5V
 delay(1000);            //pauza 10000 ms
 digitalWrite(13,LOW);   //pin 13 napatie 0V
 delay(1000); 
}

Jedna doska Arduino (modrá) generuje externé prerušenie pre druhú dosku (oranžovú)

Všimnite si, že LED dióda na doske, kde sa obsluhuje prerušenie, bliká dvakrát pomalšie. Je to logické, každá nábehová hrana pri rozsvietení LED diódy na prvej doske prepne stav diódy na druhej doske.

Na záver upútavka na budúcu časť. Programový kód mikrokontroléra sa môže z rôznych príčin zaseknúť alebo nežiaduco zacykliť. Keďže sa mikrokontroléry používajú na riadenie rôznych zariadení alebo na zber údajov, znamenalo by takéto zaseknutie veľký problém. Preto je k dispozícii takzvaný WatchDog, teda mechanizmus, ktorý mikroprocesor v prípade akéhokoľvek „zaseknutia“ programu vyvedie z tohto nežiaduceho stavu resetom. Témou budúceho pokračovania budú aj časovače.


Převzato z webu Nextech se souhlasem autora.