mylms.cz

... web o elektronice


Kusy kódu k Arduinu

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í.

arduino-ide-9

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-ide-2

Arduino





cbbegin2-1-300x180

CENA BASTLÍŘŮ 2018

Od 17. 9. 2018 do 1. 11. 2018 probíhá nominování do druhého ročníku Cenu bastlířů.
Pokud jste na této stránce našli něco zajímavého, nebo užitečného, budu rád pokud nominujete a dále budete hlasovat pro tento web. I samotná nominace by pro mě byla super věc a impuls k dalšímu tvoření těchto stránek...

Nominovat tento web můžete ZDE (do 1. 11. 2018)

Hlasovat pro tento web můžete ZDE (od 1. 11. 2018)

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.

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;
lastInput2 = input2;
}

 

 

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é.

kusykodukarduinu-1

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);
}


Napsal Petan před jedním rokem v kategorii Elektronika Programy. Připojeno 0 komentářů.
Přečteno 2345x.

Na programy zde poskytované není žádná záruka na funkčnost. Jednotlivé články, stejně jako celý obsah stránek není návodem a slouží pouze k studijním účelům. Zapojení výše mají pouze informativní charakter! Vždy se řiďte originálním návodem k použití! Na elektrickém (vyhrazeném) zařízení smí pracovat pouze osoba s příslušnou kvalifikací dle vyhlášky 50/78 Sb! Vše tedy děláte na vlastní nebezpečí! Autor stránek nebere žádnou zodpovědnost za případné újmy na zdraví, životě, majetku a jiné!

I když jsou články psány s co největší pečlivostí, mohou obsahovat chyby. Vše tedy bez záruky!

Jakékoliv části webu je zakázáno bez svolení autora a uvedení zdroje publikovat! Některé části článků mohou obsahovat texty, případně obrázky ze stránek Wikipedia a Wikimedia Commons. Tyto části jsou dostupné pod původní licencí Creative Commons.



Doposud nebyl připojen žádný komentář. Buďte první!

Připojte váš komentář!

Hvězdičkou jsou označena povinná pole.
E-mailovou adresu k příspěvku přidávat nemusíte. Autor stránek odpovídá vždy do komentáře, ne na přiložený e-mail!
Kvůli zachování kompletnosti komentáře budou vložené odkazy na obrázky vloženy do komentáře přímo jako obrázek. Původní soubor bude zkopírován na tuto stránku.