Stiskněte "Enter" pro přeskočení obsahu

Kusy kódu k Arduinu

0

Síla Arduina tkví v nesčetném množství knihoven, pomocí kterých i absolutní neprogramátor dokáže poskládat funkční program – tzv. lepič kódu 🙂 Knihovny jsou však vytvářeny univerzálně, aby bylo jejich použití co nejjednodušší. Použití knihovny je většinou vyváženo jejich větší velikostí. Takže i jednoduchý program může být pomalejší a zbytečně zabírat mnoho paměti – to může být problém např. i Arduina Nano, Micro apod. Samozřejmě toto pravidlo neplatí vždy a některé knihovny jsou velice kvalitní.

Tento článek bude pojat podobně jako článek Reléové obvody. Je to tedy pro úplné začátečníky. Postupně bych rád přidával části kódu, které si myslím, že jsou zajímavé.

Arduino

Kusy kódu

Čekání bez příkazu delay(); Multitasking na Arduinu

V začátcích s Arduinem se často používá příkaz delay();. Tento příkaz má jednu obrovskou nevýhodu. Kromě generování PWM, sériové komunikace a přerušení komplet zastaví provádění programu. Jeho použité nemusí být nesprávné, pokud není potřeba např. hlídat stavy tlačítek, vykreslovat na displej atd.

Mnohem elegantnější je zjistit si aktuální čas pomocí příkazu millis() přičíst si k času požadované zpoždění a pomocí příkazu IF kontrolovat, jestli již nastal správný čas. Program tedy bude provádět ostatní příkazy a pokud nastane čas, kdy se má příkaz provést zavolá se funkce která příkaz provede. Tímto programátorským stylem se částečně vyhnete vytváření špagetového kódu protože budete nuceni kód rozdělit do jednotlivých funkcí. Každou funkci můžeme samostatně ladit, aniž bychom zasáhli do ostatních funkcí v programu.

Tímto kódem lze vytvořit takový jakoby „multitasking“. Máme více funkcí (např. hlídání tlačítek, vykreslování na displej, čtení času, měření teploty) a každé této funkci můžeme přidělit čas, po kterém se má obnovovat. Nejdůležitější jsou tlačítka – funkci kontroly stisknutého tlačítka můžeme spouštět každých např. 50 ms. Čtení času nám může stačit jednou za půl minuty a čtení teploty ze snímače jednou za minutu. Displej vykreslíme pouze pokud nastane nějaká změna teploty, nebo času. Program tedy nečeká neustále na příkazu delay(), ale aktivně hlídá tlačítka a jednou za čas zkontroluje čas a teplotu. O řízení časování pomocí millis() jsem se rozepsal v člunku Přesnost časování pomocí Arduina.

Je třeba upozornit, že pokud budeme chtít spouštět funkci např. jednou za 100 ms a ostatní části programu se vykonávají déle, bude funkce vykonána v nejbližší možný čas – až na ní přijde řada. Proto je nutné porovnávat pomocí znaménka „>=“. Pokud by zde bylo „==“ a v přesném čase by program nebyl na řádku s podmínkou, tak by se podmínka nevykonala. Kód tedy nefunguje jako přerušení, které pozastaví ostatní části programu a spustí konkrétní funkci.

Díky použité konstrukci nemá přetečení časovače millis() vliv na funkci programu. Po přetečení začne počítat od nuly. Rozdíl časů je však kvůli odečítání stále stejný, takže k zaseknutí časovače nedojde.

unsigned long lastTime1; //proměnná s uloženým časem posledního vyvolání funkce podprogram1
unsigned long lastTime2; //proměnná s uloženým časem posledního vyvolání funkce podprogram2
unsigned long presentTime; //proměnná pro aktuální čas
void setup() {
//pokud zde nebude přiřazení hodnoty do proměnné, tak se obě funkce pustí ihned po spuštění MCU a pak dále po nastaveném čase
//pokud zde přiřazení proměnné bude, spustí se funkce až po nastaveném čase
lastTime1 = millis(); //ulož čas provedení podprogramu 1
lastTime2 = millis(); //ulož čas provedení podprogramu 2
}
void loop() {
presentTime = millis(); //uložení aktuálního času do proměnné
if (presentTime - lastTime1 >= 1000) {
//pokud je čas od poslední změny větší, nebo roven 1000 ms, proveď příkazy
lastTime1 = presentTime; //ulož čas provedení podprogramu 1
Podprogram1(); //spusť funkci Podprogram1
}
if (presentTime - lastTime2 >= 5000) {
//pokud je čas od poslední změny větší, nebo roven 5000 ms, proveď příkazy
lastTime2 = presentTime; //ulož čas provedení podprogramu 2
Podprogram2(); //spusť funkci Podprogram2
}
//další části programu, které běží i když se čeká na spuštění funkcí
}
void Podprogram1() {
//tato funkce se spustí jednou za 1000 ms
}
void Podprogram2() {
//tato funkce se spustí jednou za 5000 ms
}

Hlídání sepnutí/vypnutí (náběžné/sestupné hrany) vstupu/proměnné (varianta 1)

Pod tímto krkolomným názvem se skrývá úplně primitivní funkce, která nám zajistí, že např. po stisknutí tlačítka bude provedena určitá funkce pouze jednou. Tato část kódu jednoduše hlídá změnu bool proměnné z 0 na 1, nebo naopak z 1 na 0. V případě změny se spustí definovaná funkce.
Protože se kód v programu cyklicky spouští budeme hlídat změnu proměnné mezi minulým a aktuálním cyklem. Řekněme, že hlídáme změnu proměnné mezi prvním a druhým cyklem. V prvním cyklu se program spustí. Načte se proměnná na vstupu 11 (LOW) např. do proměnné presentInput11. Zkontroluje se jestli je oproti minulému cyklu změna (není, protože program byl právě spuštěn) a proměnná se zkopíruje do proměnné lastInput11. V druhém cyklu došlo ke změně. Na vstupu 11 je HIGH. Tato hodnota se opět uloží do proměnné presentInput11. Znovu se zkontroluje, jestli nastala proti minulému cyklu změna – porovnají se proměnné presentInput11 a lastInput11. Pokud nejsou stejné, tak nastala změna oproti minulému cyklu. Zkontroluje se hodnota presentInput11. Pokud je HIGH, tak je jasné, že nastala náběžná hrana (předtím bylo LOW, teď je HIGH), pokud LOW, je detekována sestupná hrana. Spustí se příslušná funkce – třeba rozsvícení LEDky na výstupu 13.

V příkladu níže jsou detekovány náběžné hrany na vstupech 11 a 12. Pokud je detekována náběžná hrana (stisk tlačítka) na vstupu 11 je výstup 13 (LEDka) aktivován, pokud je detekována náběžná hrana (stisk tlačítka) na vstupu 12 je výstup 13 deaktivován. Kvůli možnosti zákmitů při stisku tlačítka je dobré tuto funkci (vše v loop()) nevolat v každém cyklu, ale např. jednou za 50 ms. To lze elegantně provést pomocí příkladu výše.

bool lastInput11; //proměnná s minulým stavem vstupu 11 
bool lastInput12; //proměnná s minulým stavem vstupu 12 
bool presentInput11; //proměnná s aktuálním stavem vstupu 11 
bool presentInput12; //proměnná s aktuálním stavem vstupu 12
void setup() {
pinMode(11, INPUT); //vstup 1 (tlačítko ON)
pinMode(12, INPUT); //vstup 2 (tlačítko OFF)
pinMode(13, OUTPUT); //výstup (signalizační LED)
}
void loop() {
//načtení vstupů do proměnných
presentInput11 = digitalRead(11);
presentInput12 = digitalRead(12);
if (presentInput11 != lastInput11) {
//aktuální a minulý stav tlačítka není stejný
//bylo stisknuto, nebo povoleno tlačítko na pinu 11
if (presentInput11 == HIGH) {
//aktuální stav je HIGH, tlačítko je stisknuto
//je detekována náběžná hrana
digitalWrite(13, HIGH); //zapnout LED na pinu 13
}
else {
//aktuální stav je LOW, tlačítko je povoleno
//je detekována sestupná hrana
}
}

if (presentInput12 != lastInput12) {
//aktální a minulý stav tlačítka není stejný
//bylo stisknuto, nebo povoleno tlačítko na pinu 12
if (presentInput12 == HIGH) {
//aktuální stav je HIGH, tlačítko je stisknuto
//je detekována náběžná hrana
digitalWrite(13, LOW); //vypnout LED na pinu 13
}
}
lastInput11 = presentInput11; //uložení aktuálního stavu tlačítka do minulého stavu
lastInput12 = presentInput12; //uložení aktuálního stavu tlačítka do minulého stavu
}

Hlídání náběžné/sestupné hrany vstupu/proměnné (varianta 2)

Podobný příklad jako výše, tento však elegantně využívá logickou funkciXOR. Nejprve je do proměnné input načten stav vstupu. Poté je pomocí funkce XOR zjištěno, jestli je minulý stav (lastInput) a aktuální stav (input) rozdílný. Pokud ano, je první část podmínky splněna. Poté je pomocí logického součinu (AND) aktivován výstup pouze v případě, že je proměnná input true – to platí pro detekci náběžné hrany signálu. Negováním aktuálního stavu signálu input v podmínce lze detekovat sestupnou hranu signálu. Nakonec je uložen stav proměnné input do proměnné lastInput pro příští cyklus.

Proměnná edgeInput je true vždy pouze jeden cyklus loop po detekci náběžné/sestupné hrany signálu.

bool input1, input2, input3; //vstupy 
bool lastInput1, lastInput2, lastInput3; //minulé stavy vstupů 
bool edgeInput1, edgeInput2, edgeInput3; //proměnná je true pouze jeden cyklus po náběžné hraně
void setup() {
//nastavení I/O
pinMode(11, INPUT);
pinMode(12, INPUT);
pinMode(13, INPUT);
}
void loop() {
//načtení vstupů do proměnných
input1 = digitalRead(11);
input2 = digitalRead(12);
input3 = digitalRead(13);
//detekce hrany signálu
edgeInput1 = (input1 ^ lastInput1) & input1;
edgeInput2 = (input2 ^ lastInput2) & input2;
edgeInput3 = (input3 ^ lastInput3) & !input3; //zde se hlídá sestupná hrana signálu
//uložení minulého stavu vstupů
lastInput1 = input1;
lastInput2 = input2;
lastInput3 = input3;
}

Jednoduché uložení a načtení času z modulu reálného času DS3231 

Na internetu lze najít spoustu příkladů k modulu DS3231. Většina z nich (zejména českých) používá při načítání a ukládání času do modulu reálného času pointery. To může být pro začátečníka velice matoucí a nakonec degraduje pouze k jednoduchému překopírování kódu. Přitom je jejich použitá v tomto případě zcela zbytečné.

RTC modul DS3231

Na začátku programu se definuje adresa modulu a několik proměnných do kterých se budou ukládat jednotlivá data. Protože jsou proměnné definovány hned na začátku programu jsou globální – lze je použít v celém programu.

Následuje funkce SetRtc, které se předají jako parametr jednotlivé hodnoty. Ty jsou pomocnou funkcí převedeny na číslo ve formátu BCD.
Další funkce GetRtc (bez návratové hodnoty) načte z modulu reálných hodin čas, převede ho pomocí pomocné funkce do dekadického formátu a uloží do jednotlivých proměnných.

Nastavit a přečíst čas pak lze jednoduše zavoláním funkce. Tento kód používám v mých hodinách s maticovým displejem.

Kód je podobný jako u všech ostatních příkladů. Ale všimněte si, že se u názvu proměnných nepoužívají symboly * a &, které značí práci s pointery.

#include <Wire.h> //knihovna Wire pro práci s I2C
#define DS3231_I2C_ADDRESS 0x68 //adresa modulu DS3231
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year; //globální proměnné
void setup(){
Wire.begin(); //start komunikace I2C
}
//Nastavení hodin reálného času
void SetRtc(byte second, byte minute, byte hour, byte dayOfWeek, byte dayOfMonth, byte month, byte year) {
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0); //nastvit do prvního registru nulu
Wire.write(decToBcd(second)); //nastavit sekundy
Wire.write(decToBcd(minute)); //nastavit minuty
Wire.write(decToBcd(hour)); //nastavit hodiny
Wire.write(decToBcd(dayOfWeek)); //nastavit den v týdnu (1=neděle, 2=pondělí)
Wire.write(decToBcd(dayOfMonth)); //nastavit den v měsíci
Wire.write(decToBcd(month)); //nastavit měsíc
Wire.write(decToBcd(year)); //nastavit rok
Wire.endTransmission();
}
//Přečtení hodin reálného času
void GetRtc() {
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0); //zapsat nulu
Wire.endTransmission();
Wire.requestFrom(DS3231_I2C_ADDRESS, 7); //požadavek na 7 bajtů od modulu RTC
second = bcdToDec(Wire.read() & 0x7f);
minute = bcdToDec(Wire.read());
hour = bcdToDec(Wire.read() & 0x3f);
dayOfWeek = bcdToDec(Wire.read());
dayOfMonth = bcdToDec(Wire.read());
month = bcdToDec(Wire.read());
year = bcdToDec(Wire.read());
}
// Konverze Dec na BCD
byte decToBcd(byte val) {
return((val / 10 * 16) + (val % 10));
}
// Konverze BCD na Dec
byte bcdToDec(byte val) {
return((val / 16 * 10) + (val % 16));
}
//POUŽITÍ JEDNOTLIVÝCH FUNKCÍ
//Nastavení času
SetRtc(0, 30, 12, 1, 5, 11, 17); //nastavení hodin na 5.11.2017 (neděle) na 12:30:00
//Načtení hodin (funkce nevrací hodnotu, ale nastavuje přímo proměnné)
GetRtc();

Zvýšení rozlišení analogového výstupu

Pomocí 10bitového analogového vstupu Arduina lze rozlišit 1024 hodnot. Analogový výstup (PWM modulace) je pouze 8mi bitový, dokáže rozlišit pouze 256 hodnot. Toto rozlišení může být pro některé aplikace moc hrubé. Naštěstí lze pomocí krátkého kusu kódu nastavit několik výstupů na rozlišení 10 bit. Lze tedy na výstup zapsat 1024 různých hodnot. Nebo místo Arduina můžete použít desku založenou na procesoru STM32. Ta má možnost „analogového výstupu“ s rozlišením 65536 stupňů.

void PwmHighResolution() { 
 //zvětší rozsah PWM na 10 bitů 
 //platí pouze pro D9 a D10 
 // Set pwm clock divider 
 TCCR1B &= ~(1 << CS12); 
 TCCR1B |= (1 << CS11); 
 TCCR1B &= ~(1 << CS10);
// Set pwm resolution to mode 7 (10 bit)
TCCR1B &= ~(1 << WGM13); // Timer B clear bit 4
TCCR1B |= (1 << WGM12); // set bit 3
TCCR1A |= (1 << WGM11); // Timer A set bit 1
TCCR1A |= (1 << WGM10); // set bit 0
}
void loop(void){
//Použití analogového výstupu
analogWrite(10, 512);
}

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *