Softvérová obsluha LED

Po prečítaní nadpisu sa zrejme pozastavíte nad tým, že v ňom chýba prívlastok „pre začiatočníkov“. Veď čo už môže byť zaujímavé na softvérovej obsluhe LED? Pripojíme cez rezistor na pin mikrokontroléra a privedením úrovne HIGH LED rozsvietime a úrovňou LOW diódu zhasneme. Prípadne budeme regulovať jej jas nastavením napäťovej úrovne na analógovom, alebo PWM výstupe.

Schválne si to skúste a pokúste sa zmenou napätia dosiahnuť efekt pulzujúcej, čiže plynulo sa rozsvecujúcej a stmievajúcej diódy. Zistíte, že s jednoduchým algoritmom inkrementácie a dekrementácie napäťovej úrovne nepochodíte, pretože LED dióda má exponenciálnu závislosť jasu od napätia.

Ale poďme pekne po poriadku. V konštrukciách s mikrokontrolérmi sa často používajú svietiace, alebo technokraticky presnejšie povedané svetlo emitujúce diódy, známe ako LED (Light-Emitting Diode). Dióda teda pri prechode prúdu emituje svetlo. Má dva vývody – anódu, ktorá sa pripája na kladný pól napájania a katódu, ktorá sa pripája na záporný pól. Na rozlíšenie je vývod anódy dlhší a katóda je označená zbrúsenou plôškou zboku na spodnej strane puzdra. Katóda má u väčšiny LED kratší vývod. Pri pozornejšom pohľade do vnútra uvidíte väčšiu a menšiu plôšku. Väčšia plôška je pripojená na katódu.

V praktických zapojeniach budete najčastejšie anódu cez rezistor obmedzujúci prúd pripájať na výstupný port mikrokontroléra a katódu na elektrickú zem. Prúd tečúci LED a taktiež prúd prechádzajúci výstupným pinom by nemal prekročiť 10 – 20 mA, preto sa do série s LED VŽDY pripája obmedzovací odpor. Pre 5 V odporúčame rezistor 330 Ohm, pre 3,3 V rezistor 220 Ohm.  Jedinou výnimkou sú LED, ktoré sú už konštruované na napájacie napätie 5 V.

V príklade pre populárnu dosku Arduino budeme blikať LED pripojenou na vhodný výstupný port. V zdrojovom kóde pre Arduino je LED pripojená na port 2. Parameter príkazu delay() je v milisekundách. My sme použili dosku Totemduino, ktorá je kompatibilná s Arduino UNO a prepojovacie pole na stojane zo stavebnice Totem.

#define LED1 2  //port na ktorom je pripojená LED

void setup()
{
  pinMode(LED1, OUTPUT);
}

void loop()
{
  digitalWrite(LED1, HIGH);  delay(500);
  digitalWrite(LED1, LOW);   delay(500);
}

Blikanie LED máme zdanlivo vyriešené. Prečo zdanlivo? Ste spokojní s tým, že počas pol sekundových periód vytvorených príkazom delay (500), keď LED dióda svieti, prípadne je zhasnutá mikrokontrolér nerobí NIČ?

V ďalšom príklade ukážeme ako blikať LED bez použitia funkcie delay(). Využijeme funkciu millis() ktorá vracia čas v milisekundách od spustenia programu. Výsledok je vrátený ako dátový typ unsigned long. Táto hodnota sa ukladá ako 4 bajty a maximálna hodnota je 4 294 967 295. Inak povedané k pretečeniu dôjde približne za 49 dní.

V nasledujúcom príklade teda pretečenie nebudeme riešiť.

#define LED1 2  //port na ktorom je pripojená LED

unsigned long naposledyMs = 0;     // kedy bola aktualizácia LED
unsigned long  intervalMs = 500;   // interval blikania v ms

void setup()
{
  pinMode(LED1, OUTPUT);
}

void loop()
{
  // tu bude kód, ktorý sa bude vykonávať
  //...

  // nastal čas na zmenu stavu LED?
  unsigned long aktualneMS = millis();
  if(aktualneMS - naposledyMs > intervalMs)
  {
      // aktualizácia času posledného bliknutia LED
      naposledyMs = aktualneMS;
      
      // zmena stavu LED
      digitalWrite(LED1, !digitalRead(LED1));
   }
}

Ako vyriešiť problém s pretečením po 49 dňoch nájdete v tomto článku.

Regulácia jasu pomocou PWM

PWM znamená Pulse Width Modulation, čiže pulzne – šírková modulácia, ktorá umožňuje meniť výstupné napätie na pine nie analógovo, ale prostredníctvom premenlivej šírky pulzov.

Na výstupe sú impulzy s amplitúdou 5 V s premenlivým pomerom medzi časom zapnutia a vypnutia. Ak na PWM výstup pripojíte LED, maximálny jas sa dosiahne ak je na výstupe trvalo hodnota HIGH. Ak má byť hodnota napätia polovičná, pomer impulzov zapnuté/vypnuté je 50: 50. Ak potrebujete štvrtinový jas, pomer zapnuté/vypnute je 25:75 Pri tomto princípe regulácie, ktorá sa mimochodom využíva aj na reguláciu jasu podsvietenia lacnejších monitorov LED nesvietia kontinuálne, ale blikajú mnohokrát za sekundu a čím je doba ich zopnutia kratšia, tým je nižšia intenzita podsvietenia.

PWM na Arduine podporujú len niektoré digitálne výstupy, preto použijeme napríklad výstup D11. Piny podporujúce PWM sú označené vlnovkou

Ukážeme príklad s lineárnym prírastkom, či úbytkom jasu.

int led = 3;           // PWM pin na ktorom je LED
int jas = 0;
int prirastokJasu = 5;

void setup()
{
  pinMode(led, OUTPUT);
}

void loop()
{
  analogWrite(led, jas);
  jas = jas + prirastokJasu;
  if (jas <= 0 || jas <= 255) {prirastokJasu = -prirastokJasu;}
  delay(30);
};

Takéto „lineárne“ stmievanie však vizuálne nefunguje príliš dobre. Spočiatku to vyzerá ako rýchle exponenciálne rozjasnenie – veľký skok jasu na začiatku, potom dosť dlho žiadna poznateľná zmena. Závislosť jasu na intenzite LED diódy nie je lineárna, ale exponenciálna, takže vyššie úrovne jasu od seba nerozoznáte. Preto funkcia nastavovania jasu by mala mať logaritmický priebeh.

Napríklad:

int led = 3;
int pwmInterval = 100;
float H;

void setup()
{
  pinMode(led, OUTPUT);
  H = (pwmInterval * log10(2))/(log10(255));
}

void loop()
{
  int jas = 0;

for (int interval = 0; interval <= pwmInterval; interval++)
{
    jas = pow (2, (interval / H)) - 1;
    analogWrite(led, jas); delay(5);
  }
for (int interval = 0; interval <= pwmInterval; interval++)
{
    jas = pow (2, (interval / H)) - 1;
    analogWrite(led, 255 -jas); delay(5);
  }
}

Pre bežné účely stačí použiť funkciu sinus

int led = 3;           // PWM pin na ktorom je LED

void setup()
{
  pinMode(led, OUTPUT);
}

void loop()
{
  for (int i = 0; i < 360; i++)
  {
    analogWrite(led, (sin(i * 0.0174533) + 1) * 127);
    delay(5);
  }
}

A znovu tu máme avizovaný problém. Teraz je oneskorenie medzi jednotlivými krokmi stmievania len 5 milisekúnd, takže zvyšok kódu, ktorý by teraz nebol v hlavnej slučke ale vo vnútri cyklu for sa dostane k slovu oveľa častejšie, ale aj tak 5 milisekúnd je pre mikroprocesor dlhá doba, kde by mohol robiť niečo užitočné, prípadne u platforiem, ktoré to umožňujú hlboko spať, aby nespotrebovával energiu. Aj v tomto prípade upravíme program tak aby na určenie času, kedy je potrebné zmeniť hodnotu napätia na LED pomocou PWM využíval funkciu millis().

#define LED1 3  //port na ktorom je pripojená LED

unsigned long naposledyMs = 0;     // kedy bola aktualizácia LED
unsigned long  intervalMs = 5;     // interval zmeny v ms
int i = 0;

void setup()
{
  pinMode(LED1, OUTPUT);
}

void loop()
{
  // tu bude kód, ktorý sa bude vykonávať
  //...

  // nastal čas na zmenu stavu LED?
  unsigned long aktualneMS = millis();

if(aktualneMS - naposledyMs > intervalMs)
{
    // aktualizácia času posledného bliknutia LED
    naposledyMs = aktualneMS;
    
    // zmena stavu LED
      if(i<360)
      {
        i++;
        analogWrite(LED1, (sin(i * 0.0174533) + 1) * 127);
      }
      else i=0;
  }
}

Blikanie LED v prerušení od časovača

Ešte elegantnejšie je blikať s LED, alebo riadiť jej stmievanie v obsluhe prerušenia od časovača. Takéto riešenie  je však závislé na platforme. Ukážeme komplet ako naprogramovať prerušenie od časovača. Mikrokontrolér ATmega328P v Arduine má tri časovače. Na náš účel potrebujeme časovač, ktorý v pravidelných intervaloch vyvolá prerušenie. Využijeme režim časovača CTC (Clear Timer on Compare), ktorý po dosiahnutí hodnoty v registri OCRA vyvolá prerušenie. Využijeme 16 bitový časovač 1, takže register bude mať označenie OCR1A. Budeme ešte pracovať aj s registrami TCCR1A, TCCR1B a TCNT1. Pred nastavením časovača, ktorý vyvolá prerušenie je potrebné zakázať prerušenia a po ukončení nastavenia ich zasa povoliť.

Pri nastavovaní časovača je potrebné najskôr vynulovať registre TCCR1A, TCCR1B a TCNT1. Časovač je riadený frekvenciou a tá je v prípade Arduina UNO 16 MHz. Na naše účely blikania LED v sekundových intervaloch je to však vysoká frekvencia, takže musíme použiť deličky. K dispozícii máme deličky deličky 8, 64, 256 a 1024

Frekvencia mikrokontroléra ATmega328P je 16 MHz. To môže byť pre naše časovanie príliš vysoká hodnota. Preto máme k dispozícii deličky frekvencie. Pre každý časovač sú tieto deličky mierne iné, ale dá sa povedať, že obvykle sú k dispozícii deličky 8, 64, 256 a 1024. Napríklad delička 8 nám zníži základnú frekvenciu zo 16 na 2 MHz.

Vzorec na výpočet frekvencie je OCRA = ftakt / (N x (1 + OCRA)), kde ftakt je taktovacia frekvencia procesora a N je hodnota deličky. Ak chceme dosiahnuť frekvencie 1 Hz tak dosadíme vypočítame rovnicu

16000000 Hz / (1024 x (1 + OCRA)) = 1 Hz

OCRA = 15624

Tu je zdrojový kód. V hlavne slučke môže nerušene bežať váš program

#define LED1 3

void setup()
{
  pinMode(LED1, OUTPUT);

  // inicializácia časovača CTC
  noInterrupts();    //zakážeme prerušenia
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  OCR1A = 15624;  // hodnota na porovnanie 16 MHz/1024 pre 1 Hz
  TCCR1B |= (1 << WGM12);   // CTC mode
  TCCR1B |= (1 << CS12) | (0 < CS11) | (1 < CS10); // 1024 delička
  TIMSK1 |= (1 << OCIE1A);  // povolenie prerušenia od časovača
  interrupts();             // povolenie prerušení
}

ISR(TIMER1_COMPA_vect)          // obsluha prerušenia od časovača
{
  digitalWrite(LED1, !digitalRead(LED1));
}

void loop()
{
  // váš program...
}

Kód nemusíte písať sami. Na této adrese nájdete kalkulačku, ktorá vám na základe zadanej frekvencie procesora a frekvencie, ktorú chcete dosiahnuť vygeneruje rovno kód pre Arduino.

Pre naše hodnoty 16 MHz a 1 Hz bude vygenerovaný kód:

// AVR Timer CTC Interrupts Calculator
// v. 8
// http://www.arduinoslovakia.eu/application/timer-calculator
// Microcontroller: ATmega328P
// Created: 2020-04-21T18:04:55.708Z

#define ledPin 13

void setupTimer1()
{
  noInterrupts();
  // Clear registers
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;

  // 1 Hz (16000000/((15624+1)*1024))
  OCR1A = 15624;
  // CTC
  TCCR1B |= (1 << WGM12);
  // Prescaler 1024
  TCCR1B |= (1 << CS12) | (1 << CS10);
  // Output Compare Match A Interrupt Enable
  TIMSK1 |= (1 << OCIE1A);
  interrupts();
}

void setup()
{
  pinMode(ledPin, OUTPUT);
  setupTimer1();
}

void loop()
{
}

ISR(TIMER1_COMPA_vect)
{
  digitalWrite(ledPin, digitalRead(ledPin) ^ 1);
}

Převzato z webu Nextech se souhlasem autora.