Fyzikální kabinet GymKT

Články FUNDUINO: Multi-function Shield Funduino - stopky, časovač...

Funduino jako stopky, časovač (minutka), hodiny a budík

Jestliže si osvojíme možnosti výukového multi-shieldu zvaného Funduino, otevírá se nám široká paleta jeho možného využití. Je jen na daném vývojáři, do jaké aplikace se v rámci možností shieldu pustí. Následující článek se zaměřuje na využití Funduina jako jednoduchých stopek, kuchyňské minutky, digitálních hodin a „trochu nepraktického“ budíku.

Dále uváděné kódy byly vyzkoušeny a byly (v rámci možností) funkční. Je třeba ale dodat, že jsou to kódy určené jako jednoduchý studijní materiál a tak díky své jednoduchosti některé věci prostě neřeší. Budu se snažit u nich na některá úskalí upozorňovat. Ale i tak rovnou říkám, že to nejsou jediné a nejlepší kódy, jaké lze pro dané aplikace napsat. Osobně doporučuji, aby se těmito kódy případní zájemci jen nechali inspirovat a raději se pustili se do vývoje aplikace s kódem svým, protože jedině tak je modul Adruino + Shield Funduino TO PRAVÉ DOBRODRUŽSTVÍ!

Ovládání segmetovek

Dříve než se pustíme do slíbených aplikací, zmíním zde několik částí kódu, které budou ve všech následujícího kódech společné. Zejména to bude následující procedura display() obsluhující LED zobrazovače (segmentovky):

void display(void)
{
  for(char i = 0; i <= 3; i++) // probehne segmenty a zobrazi hodnoty
  {
    digitalWrite(latchPin, LOW); // zapis do latch pameti
    shiftOut(dataPin, clockPin, MSBFIRST, Dis_table[disbuff[i]]);
    shiftOut(dataPin, clockPin, MSBFIRST, Dis_buf[i] ); // adr. zobrazovace
    digitalWrite(latchPin, HIGH); // zobraz zapsana data
    delay(2); // pred dalsim segmentem pockej 2 ms
  }
}

Pole proměnných Dis_table obsahuje hodnoty odpovídající rozsvícení jednotlivých segmentů LED zobrazovačů (segmentovek). Takže první hodnota (zapsána v hexadecimální podobě 0xC0) odpovídá stavu rozsvícení symbolu 0. Pojďme to ověřit! Hodnota čísla C0 v hexadecimální podobě odpovídá hodnotě 192 v podobě desítkové. Na segmentovce je celkem 8 segmentů (sedm pro symbol a jedna pro tečku). Každému segmentu odpovídá jeden bit. Má-li daný segment svítit je třeba tento bit nastavit na 0, naopak 1 vyvolá zhasnutí. Začneme na horním segmentu zobrazovače, ten odpovídá hodnotě 1, pokračujeme ve směru hodinových ručiček, tedy další je 2. Ten při zobrazení číslice 0 musí také svítit. Stejně tak další po obvodu jsou 4 (pravý dolní), 8 (dolní), 16 (levý dolní) a 32 (horní levý). Všechny tyto bity musí být nastaveny na hodnotu 0. Zbylé dva bity tedy odpovídající hodnotě 64 (prostřední segment) a 128 (tečka) jsou nastaveny naopak na hodnotu 1. Výsledná hodnota tedy musí být dána součtem „jedničkových“ bitů v daném byte, tedy 128+64, což je právě hodnota 192. Obdobně jsou připraveny hodnoty pro zobrazení dalších symbolů, tedy 1, 2, … 9.

// data pro zobrazeni cislic 0-9
unsigned char Dis_table[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0X80,0X90};
unsigned char Dis_buf[] = {0xF1,0xF2,0xF4,0xF8};  // adresy segmentovek
unsigned char disbuff[] = {0, 0, 0, 0}; // hodnoty pro zobrazeni

display(); // zobrazovani

Nás jako uživatele bude ještě zajímat pole proměnných disbuff[], do kterého budeme vkládat číslice, které budeme zobrazit na segmentovkách (index 0 odpovídá úplně levé, naopak index 3 té úplně vpravo). Samotné zobrazení již zařídí zavolání procedury display(). Je třeba jen zdůraznit, že vždy svítí jen jedna segmentovka. Procedura display() tedy musí být neustále volána v hlavní části programu, abychom „ošálili“ oko pozorovatele, které toto rychlé přeblikávání nepostřehne.

POZN:
Později uvidíme, že budeme chtít ještě proceduru display() trochu upravit, abychom mohli zobrazovat potřebnou desetinnou tečku.

Ošetření tlačítek

Další věc, kterou je dobré si uvědomit ještě před přistoupení k tvorbě kódy, je ošetření tlačítek. Zpravidla potřebujeme, aby při stisknutí tlačítka nastala nějaká akce. Budeme-li ale testovat pouze aktuální stav tlačítka, stane se, že tato akce proběhne hned několikrát. Představme si, že stiskneme nějaké tlačítko na desetinu vteřiny. Pro člověka je tato doba na úrovní reakční doby, ale kód v modulu Arduino za tu dobu proběhne několikrát, stejně tak je několikrát tedy i splněna zmíněná podmínka.

Takže pokud chceme startovat a zastavovat stopky jediným tlačítkem, musíme reagovat na změnu stavu tlačítka. Naopak tam, kde nám několikanásobné splnění podmínky nevadí (např. vynulování stopek) nám stačí testovat pouhý stav tlačítka. Pro testování změny stavu tlačítka budeme používat pomocnou proměnnou, kterou poznáme podle toho, že její název končí výrazem _old. Např. KEY_ST_old je proměnná pro uložení minulého stavu tlačítka START.

A teď už pojďme na to!


1. Stopky

Pro tuto aplikaci využijeme čtyři zobrazovače modulu Funduino a dvojici (později trojici) tlačítek, kterými budeme stopky ovládat.

Ovládání zvolíme následujícím způsobem:

Prvním stisknutím levého tlačítka (S1) dojde ke spuštění měření času. Druhým stiskem téhož tlačítka se měření času zastaví. Pochopitelně budeme chtít, aby po zastavení zůstala na zobrazovačích časová informace. Zastavené stopky budeme moci pomocí druhého tlačítka (S2) vynulovat. Dále chceme, aby pokud stopky běží, nešly vynulovat. A pokud je zastavíme a nevynulujeme, aby se novým stisknutím tlačítka START (S1) opět nerozběhly. V obou případech tím chceme zabránit náhodnému dvojkliku nebo přehmátnutí na ovládacích prvcích.

Z hlediska zobrazované informace je třeba zvolit formát, který nám ukáže měřený čas v nějaké „rozumné“ podobě – musíme uvažovat, že máme k dispozici je čtyři segmentovky. Aby se naše stopky co nejvíce podobaly skutečným stopkám, zvolili jsme následující řešení: První segmentovka bude ukazovat počet minut, další dvě segmentovky budou zobrazovat počet vteřin a poslední segmentovka nám zbyla na počet desetin vteřiny. Zvolené řešení má tu nevýhodu, že i když jsme v našem programu schopni modulem Arduino měřit čas s přesností 1 ms, zobrazovat budeme pouze na desetiny vteřiny. Druhý problém nastane při překročení časového intervalu 9 minut 59 sekund. Místo požadovaného času 10 minut 00 sekund se zobrazí hodnota 0:00:00 a stopky vlastně měří od začátku. Ve skutečnosti stopky měří dále, ale čtyři zobrazovače to nejsou schopny zobrazit.

Jako možné další rozšíření by mohla být varianta, kdy stopky po časovém intervalu 10 min by přestaly zobrazovat desetiny vteřiny a zobrazovaná informace by se tak posunula „vpravo“, aby se uvolnilo místo pro počet desítek minut. Tím bychom problém „vynulování“ odsunuli na čas 99 minut nebo 59 minut (podle toho jako bychom chtěli počet minut zobrazovat) Toto však dále prezentovaný program nedělá! Nechám to jako otevřenou otázku pro další vývoj (a vývojáře!)

Kód:

// Nastaveni dle Multi-Shieldu navaneho Funduino
int latchPin = 4; // piny potrebne pro zobrazovace
int clockPin =7;
int dataPin = 8;
unsigned char Dis_table[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0X80,0X90}; // data pro zobrazeni cislic 0-9
unsigned char Dis_buf[] = {0xF1,0xF2,0xF4,0xF8};  // adresy zobrazovacu
unsigned char disbuff[] = {0, 0, 0, 0};

int KEY_START = A1;   // tlacita pro ovladani
int KEY_NUL = A2;
int KEY_MEZI = A3;
int KEY_ST_old;  // minuly stav tlacitka S1

int minuta; // promenne pro praci s casem
int sekunda;
int desetina;

long int start;
bool CASbezi;
bool ByloNUL;
//--------------------------------------

void setup(void)
{
  pinMode(latchPin, OUTPUT);   // nastaveni pro zobrazovace
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);

  KEY_ST_old = HIGH;  // (false = nestisknuto)
  CASbezi = false; // urcuje je poprve stiknuto tlacitko START
  ByloNUL = true;

  minuta = 0; // pocatecni nastaveni
  sekunda = 0;
  desetina = 0;
}

void display(void)
{
  for(char i = 0; i <= 3; i++) // probehne segmenty a zobrazi hodnoty
  {
    digitalWrite(latchPin, LOW); // zapis do latch pameti
    if ((i != 2)&&(i != 0)) shiftOut(dataPin, clockPin, MSBFIRST, Dis_table[disbuff[i]]); // data pro zobrazovac bez tecky
    else shiftOut(dataPin, clockPin, MSBFIRST, Dis_table[disbuff[i]] & 127); // data pro zobrazovac s teckou
    shiftOut(dataPin, clockPin, MSBFIRST, Dis_buf[i] ); // adresa zobrazovace
    digitalWrite(latchPin, HIGH); // zobraz zapsana data
    delay(2); // pred dalsim segmentem pockej 2 ms
  }
}

//--------------------------------------

void loop(void)
{
// PRVNI HLAVNI PODMINKA
  if ((digitalRead(KEY_START) == LOW) && (KEY_ST_old == HIGH)) { // je stisknuto poprve
    if (CASbezi) {  // cas jiz bezi je treba zastavit
      CASbezi = false;
    } else {
      if (ByloNUL) {
        start = millis();
        CASbezi = true;
        ByloNUL = false;
      }
    }
  }

  KEY_ST_old = digitalRead(KEY_START); // zapamatuj si stav START tlacitka

// DRUHA HLAVNI PODMINKA
  if ((digitalRead(KEY_NUL) == LOW)&&(!CASbezi)) { // je zasatveno a zaroven se stisklo tl. NULUJ
    sekunda = 0;
    minuta = 0;
    desetina = 0;
    ByloNUL = true;
  }

// TRETI HLAVNI PODMINKA
  if ((CASbezi) && (digitalRead(KEY_MEZI) == HIGH)) {
    desetina = (millis() - start)/100;   // urceni casoveho rozdilu
    sekunda = desetina/10;   // vypocet potrebnych casovych udaju
    minuta = sekunda/60;     // vsechna deleni jsou celociselna
    sekunda = sekunda%60;    // zbytek po deleni 60
  }

  disbuff[0]= minuta%10;   // pocet minut
  disbuff[1]= sekunda/10;  // pocet desitek sekund
  disbuff[2]= sekunda%10;  // pocet jednotek sekund
  disbuff[3]= desetina%10; // pocet desetin vteriny
  display(); // zobrazeni
}

Popis kódu

První podmínka v proceduře loop() testuje změnu tlačítka S1 – tedy start a stop stopek. Testuje okamžitý stav tlačítka KEY_START (S1) a zároveň testuje proměnnou KEY_ST_old, ve které je uchován stav tlačítka při minulém průběhu smyčky loop(). Podmínka tedy říká: „Pokud je stisknuté tlačítko S1 a zároveň předtím nebylo, pak NĚCO dělej“. To „NĚCO“ je další podmínka. Ta se dotazuje, zda již běží čas (proměnná CASbezi). Pokud ano, znamená to, že tlačítko má být vyhodnoceno jako STOP, tedy logická proměnná CASbezi je nastavena na logickou hodnotu false. Pokud čas neběží, tedy proměnná CASbezi je false, dojde naopak ke startu stopek. Start stopek je ještě podmíněn tím, zda již byly stopky vynulovány. To je právě ta podmínka, kdy nechceme spustit znovu stopky, dokud je nevynulujeme. (viz naše zadání)

Druhá hlavní podmínka v proceduře loop() testuje stisknutí nulovacího tlačítka KEY_NUL (S2). Opět jde o složenou podmínku – kromě stisknutí (a zde nepotřebujeme testovat změnu stavu, ať při stisknutí klidně nastává několikrát) testujeme i to, zda jsou zároveň stopky zastaveny. Opět jde o podmínku, kterou jsme si stanovili na počátku – tedy, aby nebylo možné je vynulovat za běhu.

Třetí hlavní podmínka obstarává výpočet stopovaného času. Ten probíhá pouze, jsou-li stopky spuštěné (proměnná CASbezi je true). Pozorný čtenář si ale všimne, že v jejím výrazu je i testováno třetí tlačítko, o kterém v zadání nebyla ani zmínka! To je takový drobný BONUS našeho řešení. Když shield Funduino má tlačítka tři, proč tedy to poslední také nevyužít? Třetí tlačítko slouží jako zobrazení mezičasu – když děláme stopky, tak pořádné! 😀 Pro vysvětlení třetího tlačítka se ale nejdříve podíváme na to hlavní, tedy vysvětlíme si princip, jak tyto stopky vlastně fungují.

Základem celého měření času je funkce modulu Arduino, která se jmenuje millis(). Pomocí funkce millis() se dá zjistit hodnota uložená ve vnitřním časovači procesoru, kde je uchována informace o délce běhu programu od jeho spuštění. Tato funkce tedy nepotřebuje žádný parametr a vrací počet milisekund od začátku programu. Je třeba zdůraznit, že tento počet není nekonečný. Maximální vrácená hodnota je 4 294 967 295. Po překročení této hodnoty dojde k přetečení časovače, který poté znovu začne počítat od nuly. K přetečení časovače dojde přibližně jednou za 50 dní. (4 294 967 295 ms = 4 294 967 s = 71 582 min = 1 193 h = 49,7 dní)

Stopky tedy fungují následující způsobem: Při startu stopek se uloží hodnota funkce millis() do proměnné start (viz první hlavní podmínka). Průběžně se počítá časový úsek, který uběhl – tedy rozdíl mezi aktuální hodnotou funkce millis() a původní hodnotou start. Z toho je určen počet minut, sekund a desetin – viz třetí hlavní podmínka. Pokud stiskneme třetí tlačítko, nedochází k tomuto výpočtu, tedy zůstává na segmentovkách zobrazena poslední hodnota. Čas však běží dál – nikdo funkci millis() nezastavil (a ani nezastaví!), takže stopky počítají čas dále, i když ho zrovna nezobrazují. Je tedy zobrazen mezičas, ale po uvolnění třetího tlačítka stopky dále ukazují čas od jejich skutečného startu.

Naopak slabším místem tohoto programu je, jak už bylo zmíněno, neřešení situace po 10 minutách a ještě jedna zrada. Ta souvisí právě s funkcí millis() a jejím přetečením. Tento stav vůbec není ošetřen, takže pokud by při měření času stopek došlo přetečení, bude údaj na stopkách nesmyslný. Oba dva problémy jsou ale řešitelné a můžeme je pojmout jako „domácí úkol“ pro zájemce.

A ještě jednu věc nám zbývá k uvedenému kódu doplnit. Pokud si jej projdete opravdu pečlivě, jistíte, že procedura display() použitá v programu se liší oproti proceduře popsané v úvodu článku. Přibyla nám tam jedna podmínka, která testuje pořadí segmentovky. Není-li to první (i != 0) a třetí (i != 2) segmentovka, je vše, jak má být. Naopak pro tyto dvě segmentovky se odešle se hodnota „ošizená“ o nejvyšší bit (provedeno logickou konjunkcí s hodnotou 127). Proč tomu tak je? Tak se podívejte na údaje na zobrazovačích! Počet minut a vteřin je oddělen tečkou, stejně tak počet vteřin a jejich desetin. To jsou právě první a třetí segmentovka, které potřebují mít nastavený bit pro tečku (nejvyšší bit odpovídá hodnotě 128) na hodnotu 0. Pokud se Vám nelíbí logická operace pomocí konjunkce 127, můžete ji zkusit nahradit „neprogramátorským“ odčtením hodnoty 128.
(Spíše si ale takovou zoufalost nezvykejme!)


2. Kuchyňská minutka (časovač)

Pokud jsme pochopili předchozí příklad, bude následující změna stopek na kuchyňskou minutku poměrně rychlá a jednoduchá. Princip reakce tlačítek na změnu stavu bude zcela obdobný. Ovládání segmentovek téměř také. Jediná změna bude ta, že budeme chtít rozsvítit desetinnou tečku na druhém zobrazovači, neboť první dva budou udávat počet zbývajících minut a další dva zbývající sekundy. Tato změna se projeví v proceduře display(), konkrétně v podmínce testující proměnnou i. Chceme-li zobrazit desetinnou tečku na druhém zobrazovači, upravíme podmínku na následující tvar:

if (i != 1) shiftOut(dataPin, clockPin, MSBFIRST, Dis_table[disbuff[i]]);    // data pro zobrazovac bez tecky
else shiftOut(dataPin, clockPin, MSBFIRST, Dis_table[disbuff[i]] & 127);    // data pro zobrazovac s teckou

Opět si nejdříve stanovíme, co má naše aplikace dělat. Prvním tlačítkem (S1) budeme nastavovat počet minut, druhým tlačítkem (S2) počet vteřin, které budeme odečítat. Na začátku je výchozí čas nastaven na 00:00. Každé zmáčknutí tlačítka vždy zvýší nastavenou hodnotu (vteřiny, minuty) o jedničku. Snížení hodnoty neřešíme, pokud budete chtít nastavit hodnotu nižší, budeme muset hodnoty „protočit“ přes maximální hodnotu 59. Pochopitelně zde se nám otevírá první nápad na možné vylepšení kódu. Časovač spustíme pomocí třetího tlačítka (S3). Hodnota na displeji se začne zmenšovat, dosáhne-li hodnoty 00:00, spustí se bzučák. Aby byl vzniklý časovač použitelný jako kuchyňská minutka a ne jen k odpalování časovaných pum (vtip), bylo by dobré jej ještě doplnit o následující funkce: Možnost předčasného zastavení a pochopitelně vypnutí závěrečného pískání. K předčasnému vypnutí časování použijeme opět třetí tlačítko – vlastně použijeme stejný postup, jako byl použit u stopek (první stisknutí start, druhé stop). Nastane-li konec, tedy zvuková signalizace, vypneme ji prvním tlačítkem, kterým se nastavují minuty. Určitým vedlejším efektem tohoto způsobu vypnutí zvukového signálu, ale bude nastavení času na 01:00. Vynulovat tuto hodnotu pak můžeme dvojklikem třetího tlačítka. První stisknutí spustí odečet, ale ten je nastaven na 1 minutu, takže než tento čas uběhne, přijde druhé stisknutí dvojkliku a to zastaví a vynuluje odečet. Je třeba zmínit, že je zde ještě jeden efekt dále zmíněného řešení – když je čítač nastaven na 00:00 (buď na začátku, nebo po vynulování) a stiskneme třetí tlačítko (START) ozve se okamžitě zvukový signál. Pokud se výše zmíněné chování někomu nelíbí, musí si kód opět upravit.

Kód:

// Nastaveni dle Multi-Shieldu navaneho Funduino
int latchPin = 4; // piny potrebne pro zobrazovace
int clockPin =7;
int dataPin = 8;
unsigned char Dis_table[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0X80,0X90}; // data pro zobrazeni cislic 0-9
unsigned char Dis_buf[] = {0xF1,0xF2,0xF4,0xF8};  // adresy zobrazovacu
unsigned char disbuff[] = {0, 0, 0, 0};

int KEY_MIN = A1;   // tlacita pro ovladani
int KEY_SEC = A2;
int KEY_START = A3;
int KEY_MIN_old;  // minuly stav tlacitka S1
int KEY_SEC_old;  // minuly stav tlacitka S2
int KEY_ST_old;  // minuly stav tlacitka S3

int minuta; // promenne pro praci s casem
int sekunda;
long int zbyva;

int buzzer = 3; // adresa bzucaku

long int stop;
bool CASbezi;

//---------------------------------------------------------------------------

void setup(void)
{
  pinMode(latchPin, OUTPUT);   // nastaveni pro zobrazovace
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);

  KEY_MIN_old = HIGH;  // (HIGH = nestisknuto)
  KEY_SEC_old = HIGH;
  KEY_ST_old = HIGH;

  CASbezi = false; // urcuje je poprve stiknuto tlacitko START

  minuta = 0; // pocatecni nastaveni
  sekunda = 0;
}

void display(void)
{
  for(char i = 0; i <= 3; i++) // probehne segmenty a zobrazi hodnoty
  {
    digitalWrite(latchPin, LOW); // zapis do latch pameti
    if (i != 1) shiftOut(dataPin, clockPin, MSBFIRST, Dis_table[disbuff[i]]); // data pro zobrazovac bez tecky
    else shiftOut(dataPin, clockPin, MSBFIRST, Dis_table[disbuff[i]] & 127); // data pro zobrazovac s teckou
    shiftOut(dataPin, clockPin, MSBFIRST, Dis_buf[i] ); // adresa zobrazovace
    digitalWrite(latchPin, HIGH); // zobraz zapsana data
    delay(2); // pred dalsim segmentem pockej 2 ms
  }
}

//---------------------------------------------------------------------------

void loop(void)
{
  if ((digitalRead(KEY_START) == LOW) && (KEY_ST_old == HIGH)) { // je stisknuto START poprve
    if (CASbezi) {  // cas jiz bezi je treba zastavit
      CASbezi = false;
      minuta = 0;
      sekunda = 0;
    } else {  // cas nebezi je straba start
        stop = millis()+sekunda*1000+minuta*60000;
        CASbezi = true;
    }
  }

  if ((digitalRead(KEY_MIN) == LOW) && (KEY_MIN_old == HIGH) && (!CASbezi)) { // je stisknuto MIN poprve a nebezi casovani
    minuta = (minuta +1)%60;
    noTone(buzzer);
  }

  if ((digitalRead(KEY_SEC) == LOW) && (KEY_SEC_old == HIGH) && (!CASbezi)) { // je stisknuto SEC poprve a nebezi casovani
    sekunda = (sekunda +1)%60;
  }

  KEY_MIN_old = digitalRead(KEY_MIN); // zapamatuj si stav MIN tlacitka
  KEY_SEC_old = digitalRead(KEY_SEC); // zapamatuj si stav MIN tlacitka
  KEY_ST_old = digitalRead(KEY_START); // zapamatuj si stav START tlacitka

  if (CASbezi) {
    zbyva = stop - millis();
    if (zbyva <= 0) {
      CASbezi = false;
      tone(buzzer, 440);
    } else {
      sekunda = (stop - millis())/1000;
      minuta = sekunda/60;
      sekunda = sekunda%60;
    }
  }

disbuff[0]= minuta/10;  // pocet desitek minut
disbuff[1]= minuta%10;  // pocet stovek sekund
disbuff[2]= sekunda/10;  // pocet desitek sekund
disbuff[3]= sekunda%10;  // pocet jednotek
display(); // zobrazovani
}

Popis kódu

Základem celého programu je opět práce s funkcí millis(), která vrací počet milisekund, které proběhly od spuštění modulu Arduino. Opět si postupně projdeme hlavní část programu, která je obsažená v cyklicky se opakující proceduře loop().

První hlavní podmínka testuje stisknutí třetího tlačítka (S3). Všimněme si, že je jak testován okamžitý stav, tak jeho minulá podoba. To je proto, aby byla podmínka splněna právě jednou jen při stisknutí (změně stavu). V této hlavní podmínce následuje podmínka, která rozděluje funkci tlačítka na dvě části. Pokud již časování běží (proměnné CASbezi je true) dojde k zastavení časování a počet minut a sekund se vynuluje. V opačném případě, tedy pokud časování neběží, dojde ke startu časování a nastaví do proměnné stop počet milisekund, pro které skončí časování. Jen zde připomínám na problém s možným přetečením funkce millis(). To je opět námět k úpravě kódu a ošetření tohoto možného nežádoucího stavu.

Druhá a třetí hlavní podmínka je testováním stisknutí prvního (S1) a druhého (S2) tlačítka. Dojde-li ke změně stavu tlačítka, zvýší se potřebná hodnota v dané proměnné (minuta, sekunda), tím i na displeji. Všimněme si jen dvou věcí. Zaprvé kromě testu tlačítek, je zde test na to, zda neběží časování, aby tlačítka již neměnily hodnotu běžícího časovače. A zadruhé v případě stisknutí prvního tlačítka dojde ještě k vypnutí akustického signálu. (V případě, že zrovna „pískání“ neběží, tento příkaz běhu programu neuškodí)

Poslední hlavní podmínka obsluhuje běžící časování, konkrétně testuje, zda již nastal konec intervalu – to sleduje proměnná zbyva, která je dána rozdílem aktuální hodnoty funkce millis() a proměnné stop nastavené při startu časovače. Pokud čas ještě neskončil, jen se snižuje hodnota zobrazených zbývajících minut a vteřin, pokud je rozdíl nula (nebo i menší), spustí se zvukový signál.


3. Hodiny

V následujících aplikacích se zkusíme posunout o kousek dále. Práce s funkcí millis() je sice hezká, ale pro lepší celkový komfort tvorby kódu zkusíme využít připravenou knihovnu. V následujících programech tedy použijeme knihovnu pro práci s časem Time (v kódu volanou jako TimeLib).

K tomu jen malá poznámka:
V řadě aplikací uváděných na internetu se v hlavičce programů připojují dvě knihovny – Time a TimeLib. Není to nic proti ničemu, ale podíváme-li se na knihovnu TimeLib a Time blíže, zjistíme, že knihovna Time ve skutečnosti volá knihovnu TimeLib. Tato knihovna zase obsahuje ve své inicializaci kontrolu, zda již není knihovna Time připojena. Jedná se tedy o jednu a tu samou knihovnu. Asi to vzniklo tak, že kdysi existovaly dvě knihovny, které se sjednotily. Teoreticky tedy stačí do programu připojit jen knihovnu TimeLib – viz příkaz #include <TimeLib.h>
Druhá poznámka:
Druhá poznámka se týká připojení knihovny k programu: Pro instalaci knihoven do svého projektu máme tyto možnosti:
  1. Instalace ze ZIP souboru

    Tento způsob především využijeme, pokud budeme mít knihovnu v ZIP souboru – např. staženou ze stránek vývojáře knihovny, který nám ji poskytl. V případě knihovny TimeLib to je adresa: https://github.com/PaulStoffregen/Time)

    V prostředí Arduino IDE zvolíme v hlavní menu volbu Projekt→ Přidat knihovnu → Přidat ZIP Knihovnu… Poté v následném dialogu zvolíme umístění ZIP souboru, ve kterém je knihovna umístěna.

  2. Instalace on-line

    Pokud chceme využít nějaké standardní knihovny, které se již obecně využívají, zvolíme právě tuto možnost.

    V prostředí Arduino IDE v hlavním menu zvolíme: Projekt → Přidat knihovnu → Spravovat knihovny… Otevře se tzv. Manažér Knihoven, kterým se prostředí Arduino IDE připojí k internetu, odkud se načte seznam dostupných knihoven. V seznamu knihoven si pak vybereme požadovanou (viz obr. 1). Zvolenou knihovnu tak lze stáhnout a nainstalovat (popř. jen aktualizovat) přímo z prostředí Arduino IDE.

    instalace knihovny Time
    Obr. 1 – Instalace knihovny Time do prostředí Arduino IDE

    Ať již zvolíme kterýkoliv způsob instalace, oba by měly skončit stejně. To znamená, že se po úspěšném nainstalování knihovny vytvoří v pracovní složce (obvykle: Dokumenty aktuálního uživatele – podsložka Arduino) složka „libraries“ a v ní podsložky dané knihovny – v našem případě: Time.

    Tím došlo k propojení stažené knihovny a našeho prostředí Arduino IDE. Zatím to znamená jen to, že až budeme knihovny potřebovat, dokáže ji prostředí Arduino IDE najít, a tedy neskončí překlad kvůli tomu chybou. Použití kterékoliv z knihoven samozřejmě musíme též zapsat do zdrojového kódu programu. To uvidíme v dále uvedeném kódu.


Vraťme nyní zpět k našemu zadání úkolu. Chceme vytvořit z modulu Arduino a multifunkčního shieldu Funduino funkční hodiny. Chceme, aby se zobrazoval na čtyřech LED zobrazovačích aktuální čas. Asi zvolíme 24-hodinový režim (ale to je koneckonců na volbě autora). Počet hodin a minut oddělíme desetinou tečkou (nic lepšího nám nezbývá). Aby bylo vidět, že hodiny běží, zařídíme, aby tečka s vteřinovými intervaly poblikávala. Pro získání počtu hodin, minut, a sekund budeme využívat funkce knihovny Time. Mimochodem, tato knihovna umí pracovat nejen s dalšími údaji jako den, měsíc, rok, ale třeba i den v týdnu. Hodí se tedy i pro různé kalendáře a plánovače.

Hodiny bude třeba také nějak nastavovat, protože modul Arduino nemá čas zálohovaný. Po každém zapnutí nebo resetu začíná modul s časem 00:00. K tomu využijeme tlačítka S1–S3, která (podobně jako na skutečných hodinách) budou sloužit k nastavení času. Stejně tak je můžeme využít při korekci času (nedělejte si iluze o přesnosti vnitřních hodin modulu!), nebo při změně letního a zimního času (otázka je jak dlouho ještě). První dvě tlačítka, budou-li stisknuta samostatně, nebudou dělat nic. Jakmile jedno z nich stiskneme v kombinaci s třetím tlačítkem, budeme zvyšovat hodnotu hodin, popř. minut. Samotné tlačítko S3 také nebude dělat nic. Ještě nastavíme, že pokud změníme hodnotu minut, automaticky se nastaví i hodnota sekund na hodnotu 0 – jako, že daná minuta právě začala. To vše by měl zvládnout následující kód:

Kód:

// Pro praci s vnitrnim casem Arduina vyuzijeme tyto funkce knihovnu Time
// hour()  - pocet hodin (0-23)
// minute() - pocet minut (0-59)
// second() - pocet sekund (0-59)
// day()  - den (1-31)
// month() - mesic (1-12)
// year() - rok (2020, 2021...)
// setTime(hr,min,sec,day,mnth,yr) - nastavime vnitrni cas Arduina

#include <TimeLib.h>       //pridani knihovny Time

// Nastaveni dle Multi-Shieldu nzvaneho Funduino
int latchPin = 4; // piny potrebne pro zobrazovace
int clockPin =7;
int dataPin = 8;
unsigned char Dis_table[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0X80,0X90}; // data pro zobrazeni cisel
unsigned char Dis_buf[] = {0xF1,0xF2,0xF4,0xF8};
unsigned char disbuff[] = {0, 0, 0, 0};

int KEY_HOD = A1;   // tlacita pro ovladani
int KEY_MIN = A2;
int KEY_SET = A3;
int KEY_SET_old;  // minuly stav tlacitka S3

int hodina;   // promenne pro praci s casem Arduina
int minuta;
int sekunda;
int den;
int mesic;
int rok;

//---------------------------------------------------------------------------

void setup(void)
{
  pinMode(latchPin, OUTPUT);   // nastaveni pro zobrazovace
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);

// prvotni nastaveni nastaveni hodin - pri resetu
  setTime(9, 30, 00, 29, 03, 2020);  // hod, min, sec, den, mesic, rok

  KEY_SET_old = HIGH;
}

void display(void)
{
  for(char i = 0; i <= 3; i++) // probehne segmenty a zobrazi hodnoty
  {
    digitalWrite(latchPin, LOW); // zapis na latch
    if ((i != 1)||(sekunda%2 == 0)) shiftOut(dataPin, clockPin, MSBFIRST, Dis_table[disbuff[i]]); // data pro zobrazovac bez tecky
    else shiftOut(dataPin, clockPin, MSBFIRST, Dis_table[disbuff[i]] + 128); // data pro zobrazovac s teckou
    shiftOut(dataPin, clockPin, MSBFIRST, Dis_buf[i] ); // adresa zobrazovace
    digitalWrite(latchPin, HIGH); // zobraz zapsana data
    delay(2); // cekej 2 ms
  }
}

//---------------------------------------------------------------------------

void loop(void)
{
  hodina = hour();  // nacteni aktualniho casu
  minuta = minute();
  sekunda = second();

// A jdeme na testovani tlacitel
  if ((digitalRead(KEY_SET) == LOW) && (KEY_SET_old == HIGH))  // pokud neni S3 poprve sktisknute nic se nastavovat nebude
    {
      KEY_SET_old = LOW;
      den = day();
      mesic = month();
      rok = year();
      if(digitalRead(KEY_HOD) == LOW) // pokud je k tomu stisnute i tlacitko HOD pridej hodinu
        {
         hodina = (hodina+1)%24;
         setTime(hodina, minuta, sekunda, den, mesic, rok);
        } else
           if(digitalRead(KEY_MIN) == LOW) // pokud je k tomu stisnute i tlacitko HOD pridej hodinu
           {
            minuta = (minuta+1)%60;
            setTime(hodina, minuta, 0, den, mesic, rok);
           } // else { jen stisknute SET - zatim nic nedelej }
    }
KEY_SET_old = digitalRead(KEY_SET);

  disbuff[0]= hodina/10;  // pocet desitek hodin
  disbuff[1]= hodina%10;  // zbytek po delelni deseti = pocet jednotek hodin
  disbuff[2]= minuta/10;  // totez pro minuty
  disbuff[3]= minuta%10;

  display(); // zobrazovani

}

Popis kódu

Základem celého kódu je práce s vnitřním časem modulu Arduino, ke kterému přistupujeme pomocí knihovny Time. Přidána ke kódu je příkazem #include <TimeLib.h>. Tím získáváme následující procedury a funkce, které budeme používat:

hour() – funkce vrací počet hodin vnitřního času modulu Arduino (0–23)
minute() – funkce vrací počet minut vnitřního času (0–59)
second() – funkce vrací počet vteřin vnitřního času (0–59)
day() – funkce vrací den z data v modulu Arduino (1–31). To budeme potřebovat pro proceduru setTime() (viz dále)
month() – funkce vrací měsíc z data v modulu Arduino (1–12)
year() – funkce vrací rok z data v modulu Arduino (2020, 2021…)
setTime(hr, min, sec, day, mnth, yr) – nastavení vnitřního času a data modulu Arduino. Pozor, procedura vyžaduje zadat všechny vstupní proměnné (a to i při změně jediného parametru). Z tohoto důvodu musíme využívat funkce day(), month() a year().

Procedura display() opět obsluhuje zobrazování na segmentovky. Za zmínku jen stojí podmínka obsluhující zobrazení a blikání tečky na druhém zobrazovači. Testuje se tu jak pořadí zrovna obsluhované segmentovky, tak i podmínka „sudosti“ počtu vteřin. Pokud je zbytek po celočíselném dělení nula (je sudý počet vteřin) tečka se nezobrazuje. Naopak pokud je počet lichý, tečka se na druhém zobrazovači objeví. Tím docílíme pravidelného vteřinového blikání tečky.

Samotné hodiny fungují tak, že načteme počet hodin, minut a vteřin a zobrazíme na zobrazovačích. Zde je možné vylepšení v podobě nezobrazování počáteční nuly u počtu hodin, aby třeba místo 09:30 se zobrazila hodnota 9:30. Případnou úpravu procedury display() již „zvídavý“čtenář jistě zvládne sám.

Nejsložitější strukturou celého programu je testování tlačítek – hlavní podmínka ve středové části programu. Opět testujeme změnu stavu tlačítka, zde konkrétně S3. Při stisku tlačítka S3 otestujeme i stav tlačítka S1 nebo S2. Pokud je některé z nich stisknuté zvýší se hodnota hodin nebo minut a zpětně se podle toho nastaví vnitřní čas modulu Arduino (procedura setTime()). Pro potřeby této procedury je načten i rok, měsíc a den, který procedura při zadávání vyžaduje. (Jen dodávám, že den, měsíc a rok vůbec nemusí být aktuální.)

Podmínka je připravena i na akci, kdy bude stisknuté samotné tlačítko S3, ale zatím tato část není využita (tato část je uschována v komentáři). Mohlo by se třeba jednat o přepínání hodin mezi 24 a 12-hodinovým režimem – opět možné rozšíření dle přání programátora.

Na závěr celé aplikace je třeba upozornit na specifické chování tlačítek při nastavování hodin a minut. Zmáčknete-li nejdříve tlačítko S3 (nastavení) a pak teprve např. tlačítko S1 (nastavení hodin), nic se nestane. Ale uděláte-li to obráceně, tedy nejdříve stisknete tlačítko nastavení hodin (S1) a při jeho stálém držení stiskněte tlačítko S3 (nastavení). Hodnota počtu hodin se zvýší. Proč tomu tak je, poznáte, pochopíte-li strukturu hlavní složené podmínky.


4. Budík

Poslední aplikací je rozšíření předchozích hodin o možnost budíku. Opět budeme využívat knihovnu Time, jen musíme vyřešit nastavení a testování času buzení, které pak zařídí bzučák modulu Funduino.

Pro logičtější ovládání provedeme změnu v reakci na stisknutá tlačítka. Hodiny a minuty na hodinách se budou nastavovat stisknutím tlačítka S1 a S2 (samotných). Nastavení hodin a minut budíku se bude provádět se současným stisknutím tlačítka S3 – stejně jako se nastavovaly hodiny v předchozí aplikaci. Po dobu stisknutí tlačítka S3 se též bude zobrazovat čas budíku a ne aktuální čas hodin. To je proto, abychom viděli, jaký čas nastavujeme na budíku. Jakmile nastane čas buzení, spustí se zvuk bzučáku. Ten zní jednu minutu (dokud se neliší čas hodin a budíku) nebo lze budík „zamáčknout“ tlačítkem S3. Výše zmíněné nastavení je ale poněkud nepraktické. Pokud se při své rozespalosti netrefíte na tlačítko S3, ale stisknete tlačítko S1 nebo S2, rozštelujete si nastavení hodiny. Naopak dobrou zprávou je to, že i v tomto případě budík umlkne 😀. Pokud budík zamáčkneme, chtěli bychom, aby další den opět budil ve stejný čas. I to v programu ošetřeno pomocí proměnné, která sleduje stav hodin a „zamáčknutí“.

Kód:

// Pro praci s vnitrnim casem Arduina vyuzijeme tyto funkce knihovnu TimeLib
// hour()  - pocet hodin (0-23)
// minute() - pocet minut (0-59)
// second() - pocet sekund (0-59)
// day()  - den (1-31)
// month() - mesic (1-12)
// year() - rok (2020, 2021...)
// setTime(hr,min,sec,day,mnth,yr) - nastavime vnitrni cas Arduina
#include <TimeLib.h>

// Nastaveni dle Multi-Shieldu nzvaneho Funduino
int latchPin = 4; // piny potrebne pro zobrazovace
int clockPin =7;
int dataPin = 8;
unsigned char Dis_table[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0X80,0X90}; // data pro zobrazeni cisel
unsigned char Dis_buf[] = {0xF1,0xF2,0xF4,0xF8};
unsigned char disbuff[] = {0, 0, 0, 0};

int KEY_HOD = A1;   // tlacita pro ovladani
int KEY_MIN = A2;
int KEY_SET = A3;
int KEY_SET_old;  // minuly stav tlacitka S3
int KEY_HOD_old;  // minuly stav tlacitka S1
int KEY_MIN_old;  // minuly stav tlacitka S3

int hodina;   // promenne pro praci s casem Arduina
int minuta;
int sekunda;
int den;
int mesic;
int rok;

int ALARM_hod = 0;     // promenne pro praci budikem
int ALARM_min = 0;
bool vstavej = true;
bool zvoni = false;
int buzzer = 3; // adresa bzucaku

//---------------------------------------------------------------------------

void setup(void)
{
  pinMode(latchPin, OUTPUT);   // nastaveni pro zobrazovace
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);

  pinMode(buzzer, OUTPUT);

// prvotni nastaveni nastaveni hodin - pri resetu
  setTime(16, 40, 00, 22, 03, 2020);  // hod, min, sec, den, mesic, rok
}

void display(void)
{
  for(char i = 0; i <= 3; i++) // probehne segmenty a zobrazi hodnoty
  {
    digitalWrite(latchPin, LOW); // zapis na latch
    if ((i != 1)||(sekunda%2 == 0)) {
          if ((i == 0) && (disbuff[i] == 0)) { // je to prvni segmentovka a je na ni 0
            shiftOut(dataPin, clockPin, MSBFIRST, 255); // posli zhasle vsechny zobrabovace
          }
          else shiftOut(dataPin, clockPin, MSBFIRST, Dis_table[disbuff[i]]); // data pro zobrazovac bez tecky
    }
    else shiftOut(dataPin, clockPin, MSBFIRST, Dis_table[disbuff[i]] + 128); // data pro zobrazovac s teckou
    shiftOut(dataPin, clockPin, MSBFIRST, Dis_buf[i] ); // adresa zobrazovace
    digitalWrite(latchPin, HIGH); // zobraz zapsana data
    delay(2); // cekej 2 ms
  }
}

//---------------------------------------------------------------------------

void loop(void)
{
  hodina = hour();  // nacteni aktualniho casu
  minuta = minute();
  sekunda = second();
  den = day();
  mesic = month();
  rok = year();

  if ((hodina == ALARM_hod)&&(minuta == ALARM_min)) // nastal cas buzeni
    {
    if (vstavej) // je ale buzeni dovolene
     {
     tone(buzzer, 440);
     zvoni = true;
     vstavej = false;
     }
    }
    else {
      noTone(buzzer);
      zvoni = false;
      vstavej = true;
    } // cas uz nesedi, tak mlc

// A jdeme na testovani tlacitel

  // NASTAVENI BUDIKU
  if (digitalRead(KEY_SET) == LOW)  // pokud neni treti sktisknute nic se nastavovat nebude
  { // nastavovani budiku
    disbuff[0]= ALARM_hod/10;  // pocet desitek hodin
    disbuff[1]= ALARM_hod%10;  // zbytek po delelni deseti = pocet jednotek hodin
    disbuff[2]= ALARM_min/10;  // totez pro minuty
    disbuff[3]= ALARM_min%10;
    display(); // zobrazovani
    if (zvoni) {  // zamacknuti
        noTone(buzzer);
        zvoni = false;
    }

    if ((digitalRead(KEY_HOD) == LOW)&&(KEY_HOD_old == HIGH)) { // stisknute S1
       ALARM_hod = (ALARM_hod+1)%24;
    } else
        if ((digitalRead(KEY_MIN) == LOW)&&(KEY_MIN_old == HIGH)) { // pokud je k tomu stisnute i tlacitko MIN
           ALARM_min = (ALARM_min+1)%60;
        }
    //----------
  } else { // neni stiskle tlacitko S3 tak to bezi jako hodiny
    disbuff[0]= hodina/10;  // pocet desitek hodin
    disbuff[1]= hodina%10;  // zbytek po delelni deseti = pocet jednotek hodin
    disbuff[2]= minuta/10;  // totez pro minuty
    disbuff[3]= minuta%10;
    display(); // zobrazovani

  // NASTAVENI HODIN
  if ((digitalRead(KEY_HOD) == LOW)&&(KEY_HOD_old == HIGH)) {
       hodina = (hodina+1)%24;
       setTime(hodina, minuta, sekunda, den, mesic, rok);
  } else
      if((digitalRead(KEY_MIN) == LOW)&&(KEY_MIN_old == HIGH)) { // pokud je k tomu stisnute i tlacitko HOD pridej hodinu
         minuta = (minuta+1)%60;
         setTime(hodina, minuta, 00, den, mesic, rok);
      }
  }

KEY_SET_old = digitalRead(KEY_SET);
KEY_HOD_old = digitalRead(KEY_HOD);
KEY_MIN_old = digitalRead(KEY_MIN);
}

Popis kódu

Pokud jsme pochopili všechny předchozí programy, již nemá cenu zde moc rozebírat celý program řádek po řádku. Především se jedná o testování stisknutých tlačítek – série hlavních podmínek. Vysvětlíme si tuto část jen zběžně a zaměříme se na část algoritmu řešící budík.

Celý program se dělí na dvě části (viz druhá hlavní podmínka) podle toho, zda je stisknuté tlačítko S3. Pokud není, běží program stejně jako hodiny (viz předešlý program). Pokud je naopak tlačítko S3 stisknuté, řeší se nastavení budíku a zobrazení hodnot ALARM_hod a ALARM_min, což jsou proměnné s hodnotami budíku. V obou částech programu se dále řeší stisknutí tlačítek S1 a S2, kdy v jedné části se nastavují hodiny (nestisknuté S3) a v druhé části nastavení budíku (stisknuté S3).

První hlavní podmínka řeší samotný budík. Testuje se zde rovnost nastaveného času budíku a aktuálního času. Pokud dojde ke shodě, začne se budit. To poběží minutu (než se čas hodin začne lišit). Jakmile tato podmínka není splněna, je bzučák umlčen. Je zde ještě třeba vyřešit problém zamáčknutí. To řeší dvě logické proměnné. První nazvaná zvoni má logickou hodnotu true, pokud právě probíhá buzení, jinak má hodnotu false. Je-li stisknuté tlačítko S3, testuje se stav proměnné zvoni a pokud je true (budík právě zvoní) dojde k umlčení bzučáku a je blokováno splnění podmínky rovnosti času a alarmu pomocí druhé proměnné vstavej. Pokud by k tomu blokování nedošlo, budík by se po uvolnění tlačítka S3 opět rozezněl a nadále budil – dokud by se nelišily časy alarmu a hodin (viz dříve). Blokování buzení probíhá jen po minutu samotného buzení, což je uděláno právě zmíněnou proměnnou vstavej, která při buzení nastavena na false („už znova nebudit“) a hodnotu true („ano, lze znova budit“) nabyde až při nerovnosti času hodin a buzení. Nastavování této proměnné by v propracovanějších verzích tohoto programu měla obsluhovat kupříkladu podmínka kontrolující, zda je pondělí až pátek, což tento program nedělá, ale lze o tuto funkci rozšířit. To by již ale chtělo lepší uživatelské rozhraní, např. nějaký displej. čtyři zobrazovače modulu Funduino k tomu asi nejsou zcela nejvhodnější.

Závěr

Ukázali jsme si čtyři možné základní využití modulu Arduino s použitým shieldem Funduino ve spolupráci se systémovým časem. Snahou bylo ukázat tento shield jako základ nějakých „praktických“ aplikací. Samozřejmě šlo o základní (a co možná nejjednodušší) ukázkové programy. Cílem článku nebylo předložit jen hotové (dokonalé) kódy, ale ukázat především jejich princip, aby byly inspirací dalším vývojářům pro vývoj jejich vlastních kódů řešících jejich konkrétní požadavky.

Zároveň výše uvedené příklady se snaží ukázat další možné využití výukového shieldu Funduino. Jako další možné aplikace mě namátkové napadají aplikace spíše herního charakteru, kupříkladu kdysi oblíbená hra VIDEOSTOP.

Ale o tom třeba zase jindy…

Autor článku: RNDr. Miroslav Panoš, Ph.D.