Avtorja: Vladimir Mitrović in Robert Sedak
Email: vmitrovic12@gmail.com
2020_290_
V tem nadaljevanju bomo najprej izvedeli, kako časovno omejiti izvajanje nekega dela programa, potem pa tudi to, kako lahko mikrokontroler “prebere”, ali je neko stikalo, oziroma tipka, sklenjeno ali ne.
3. programska naloga: Piskač mora izmenično ustvarjati tona frekvence 500 Hz in 1 kHz, vsakega po eno sekundo (piskač je priključen tako, kot prikazuje slika 14 v prejšnjem nadaljevanju te serije člankov).
Bascom-AVR rešitev (program Shield-A_3.bas)
Struktura vseh programov je zelo podobna: po uvodnih ukazih (s katerimi nastavimo delovanje mikrokontrolerja in določimo začetna stanja) sledi neskončna zanka (znotraj katere se programski ukazi vedno znova ponavljajo). Problem pri neskončni zanki pa je, da program iz nje ne more izstopiti, vsaj ne z našim dosedanjim znanjem programiranja. Zato podobna rešitev, kot je ta in ki bi se nam najbrž najprej porodila v mislih, ne bi delovala:
Do Portb.5 = Not Portb.5 Waitms 1 Loop Do Portb.5 = Not Portb.5 Waitus 500 Loop
Ta vsebina je samo za naročnike
Dim I As Word ... Do For I = 1 To 1000 Portb.5 = Not Portb.5 Waitms 1 Next For I = 1 To 2000 Portb.5 = Not Portb.5 Waitus 500 Next Loop
V obeh For-Next zankah smo uporabili spremenljivko I, prek katere program šteje, kolikokrat je zanka izvedena. Vrednost števca se ob vsakem izvajanju zanke poveča za 1. Zato se bo prva zanka izvedla 1000-krat, potem pa bo program izstopil iz nje in začel 2000-krat izvajati drugo zanko. Obe zanki sta umeščeni znotraj neskončne Do-Loop zanke, zato se bosta izmenično izvajali.
In zakaj sta morali biti ti dve vrednosti različni? Eno izvajanje prve zanke traja (približno) 1 ms, zato bo njenih 1000 izvajanj trajalo približno 1 s, ravno toliko, kot zahteva ta naloga. Druga zanka traja (približno) 500 µs in če želimo, da se izvaja 1 sekundo, jo je potrebno ponoviti 2000-krat.
Bascom-AVR sam po sebi ne more vedeti, kaj je to I. Zato smo mu na začetku programa z ukazom
Dim I As Word
Napake so lahko različnih vrst in včasih je iz opisa težko razbrati kaj je narobe. Zelo je važno, da po prevajanju programa pogledamo na dno okna in preverimo, če so tam kakšna sporočila o morebitnih napakah. Če ne razumemo smisla teh sporočil, je najbolje dvojno klikniti na eno od njih in potem nam bo Bascom-AVR z rdečim obarval vrstico, ki je zaradi nekega vzroka ni mogel uspešno prevesti. V primeru na sliki 15 se napaka nahaja v vrstici 10, kjer smo prvič uporabili spremenljivko I, to pa nas bo spomnilo, da preverimo, če smo jo morda pozabili definirati.
Arduino rešitev (program Shield-A_3.ino)
Struktura vseh programov je zelo podobna: za uvodnimi ukazi v funkciji setup() (s katerimi nekaj konfiguriramo in nastavljamo začetna stanja) sledi neskončna zanka, ki je definirana s funkcijo loop() (v kateri se programski ukazi vedno znova ponavljajo). Ker ne moremo imeti dveh funkcij z enakim imenom (na primer loop()), lahko logično zaključimo, da moramo uporabiti dve zanki znotraj neskončne zanke funkcije loop(). V ta namen bomo uporabili zanko for v kateri lahko poljubno nastavimo, kolikokrat naj se izvede določen niz ukazov:
for ( int i = 1; i <= 1000; i++) { digitalWrite(13, !digitalRead(13)); delay(1); } for ( int i = 1; i <= 2000; i++) { digitalWrite(13, !digitalRead(13)); delayMicroseconds(500); }
V prvi for zanki se ustvari ton frekvence 500 Hz, v drugi pa ton frekvence 1 kHz.
V obeh for zankah smo uporabili spremenljivko i, prek katere program šteje, kolikokrat je zanka izvedena. Vrednost števca se ob vsakem izvajanju zanke poveča za 1. Zaradi tega se bo prva zanka izvedla 1000-krat, potem pa se bo druga zanka izvedla 2000-krat. Ker eno izvajanje prve for zanke traja okrog 1 ms in eno izvajanje druge for zanke okrog 500 µs, se bosta obe zanki časovno izvajali približno eno sekundo, kot je bilo z nalogo določeno. Obe for zanki sta umeščeni znotraj neskončne zanke funkcije loop(), zato se bosta izmenično izvajali.
Opažamo, da je znotraj vsake for zanke spremenljivka i deklarirana kot celo število. V programskem jeziku C/C++ lahko spremenljivko definiramo v kateremkoli delu programa, vendar moramo paziti tudi na območje njenega delovanja (scope). Območje delovanja spremenljivke je lahko celoten program, funkcija ali zanka. Ena izmed smernic za programiranje predlaga definiranje spremenljivk v tistem delu programa, kjer jih bomo uporabljali. Spremenljivko lahko na primer definiramo na samem začetku programa, če gre za kratek program (recimo do 50 vrstic programske kode) in uporabljamo majhno število spremenljivk (na primer do 5). V tem primeru si bomo brez težav zapomnili, za kakšen namen uporabljamo katero od spremenljivk. Pri daljših programih to postaja vse težje, zato pogosteje uporabljamo dodatne tehnike, kot so način imenovanja spremenljivk in položaj v programu, kjer jih definiramo.
Če Arduino IDE definicije spremenljivke med prevajanjem ne najde, bo pri prevajanju javil napako (slika 16).
Vzroki za napake so lahko različni in včasih je pravi vzrok iz opisa težko ugotoviti. Zelo je važno, da po prevajanju programa pogledamo na dno okna in preverimo, če so v oknu kakšna sporočila o napakah. V primeru s slike 16 se napaka nahaja v vrstici, v kateri smo omenili spremenljivko i in nas tako opozarja, da v območju delovanja for zanke spremenljivke nismo definirali.
***
Mikrokontroler ATmega328P ima 20 vhodno-izhodnih priključkov: PD0-PD7, PB0-PB5 in PC0-PC5 (Arduino oznake teh priključkov so “0” – “19”). Če katerega od teh priključkov uporabimo kot izhod, ga bomo v programu lahko postavljali v logična stanja “0” ali “1”, kar bo povzročilo, da se na njem pojavi napetost 0 ali 5 V. Kot smo videli v prejšnjih primerih, lahko ta napetost potem vklopi LEDico ali piskač. Seveda zmore še veliko več od tega, vendar bomo o tem spregovorili v kakšni drugi priložnosti.
Če jih uporabljamo kot vhode, imamo dve možnosti:
- vsakega od teh 20 priključkov lahko nastavimo kot digitalni vhod in mikrokontroler lahko potem “prebere”, ali se nahaja v stanju logične ničle ali enice;
- 6 priključkov, PC0-PC5, lahko nastavimo tudi kot analogne vhode (Arduino oznake teh priključkov so “A0” – “A5”), prek katerih lahko mikrokontroler izmeri, kakšna napetost se na njih nahaja.
Za začetek se bomo pozabavali z digitalnimi vhodi. Na Shield-A ploščici se nahajata dve tipki, SW1 in SW2, ki sta z mikrokontrolerjem povezana tako, kot je prikazano na sliki 17.
Slika 17: Tipki SW1 in SW2 sta z mikrokontrolerjem ATmega328P takole povezani
S pomočjo iste slike bomo pojasnili, kako mikrokontroler bere stanja tipk. Za to je potrebna pomožna napetost, ki se prek uporov RC1 in RC2 dovede na “zgornji” priključek tipk. Ti upori se imenujejo pull-up in so vgrajeni v sam mikrokontroler. Po potrebi se lahko vklopijo ali izklopijo s stikali znotraj mikrokontrolerja, za naše potrebe pa morajo biti vklopljeni. Stikala ali tipke na priključke mikrokontrolerja vedno povezujemo proti masi (GND). Če je neka tipka, kot na primer SW1, odprta, bo vhodni priključek v stanju “1” (v našem primeru bo PC1 = 1), ker bo pull-up upor „držal“ vhodno napetost na 5 V. Če pa je neka tipka, recimo SW2, sklenjena, smo pravzaprav naredili kratek stik tega izhoda proti masi. Upornost Pull-up uporov je načrtovana tako, da v primeru kratkega stika prek priključka teče zelo majhen tok, ki ga ne more poškodovati. Obenem pa kratek stik vhodnega priključka pomeni, da se na njem nahaja napetost 0 V, kar bo mikrokontroler prebral kot logično ničlo (PC2 = 0).
Ta pravila bomo preverili in izkoristili v naslednji programski nalogi.
4. programska naloga: Dokler je pritisnjena tipka SW1, mora piskač piskati s frekvenco 500 Hz; dokler pa je pritisnjena tipka SW2, mora piskač piskati s frekvenco 1 kHz.
Rešitev z Bascom-AVR (program Shield-A_4.bas)
Program je podoben prejšnjemu, vendar bomo namesto For-Next zanke uporabili pogojno, While-Wend zanko. No, pred tem moramo na samem začetku programa določiti, da želimo priključka PC1 in PC2 uporabiti kot vhoda:
Config Portc.1 = Input Config Portc.2 = Input
Kot smo že v uvodu pojasnili, moramo vključiti tudi njuna notranja pull-up upora, da bomo lahko brali stanja na njunih priključkih. V našem primeru vključimo upora priključkov PC1 in PC2:
Portc.1 = 1 Portc.2 = 1
Z vključitvijo pull-up uporov bo napetost na teh dveh priključkih narasla na 5 V (če je tipka razklenjena) ali pa bo ostala na 0 V (če je tipka sklenjena). Mikrokontroler mora samo prebrati, ali se na vhodnem priključku nahaja logično stanje “1” ali “0”, za kar nam Bascom-AVRu ponuja nekaj ukazov. Nam bo za rešitev zahtevane naloge najbolj ustrezala While-Wend zanka:
Do While Pinc.1 = 0 Portb.5 = Not Portb.5 Waitms 1 Wend While Pinc.2 = 0 Portb.5 = Not Portb.5 Waitus 500 Wend Loop
Ukazi znotraj te zanke se izvajajo vse dotlej, dokler obstaja naveden pogoj. Konkretno, dokler držimo pritisnjeno tipko SW1, bo obstajal pogoj “Pinc.1 = 0” in program se bo vrtel znotraj prve zanke in pri tem ustvarjal na piskaču ton frekvence 500 Hz. Če držimo pritisnjeno tipko SW2, bo obstajal pogoj “Pinc.2 = 0” in program se bo vrtel znotraj druge zanke in pri tem ustvarjal ton frekvence 1 kHz. Če ni pritisnjena nobena tipka, ne bo obstajal noben od teh dveh pogojev, zato bo piskač utihnil.
Zdaj pa malo razmislimo, kaj se bo zgodilo, če hkrati pritisnemo obe tipki! Dokler ni pritisnjena nobena, se bo program vrtel znotraj Do-Loop zanke in neprestano preverjal, če smo morda medtem že pritisnili katero od tipk. Frekvenca na kateri deluje mikrokontroler je 16 MHz, zato bo cikel preverjanja trajal manj od ene mikrosekunde – z drugimi besedami, mikrokontroler bo vsako sekundo preveril tipke več kot milijon-krat.
Če gledamo s tega stališča, je zelo malo verjetno, da bi nam uspelo pritisniti obe tipki čisto v istem trenutku: program se bo “ujel” v tisti zanki, katere tipko smo pritisnili samo trenutek prej. Tudi če bi nam to po nekem čudežu uspelo, moramo vedeti, da se program izvaja v zaporedju “prvo preverjanje” – “drugo preverjanje” – “vrni se na prvo preverjanje”. Zato bi se program “ujel” v tisto zanko, katere preverjanje bi bilo v tistem trenutku na vrsti.
Arduino rešitev (program Shield-A_4.ino)
Program je zelo podoben prejšnjemu, vendar bomo namesto for zanke uporabili pogojno, while zanko. No, pred tem moramo na samem začetku programa v funkciji setup() definirati, da želimo priključka “15” (PC1) in “16” (PC2) uporabljati kot vhoda in istočasno vključiti njuna pull-up upora. Poleg tega bomo definirali priključek “13” kot izhodni priključek:
voidsetup() { pinMode(15, INPUT_PULLUP); pinMode(16, INPUT_PULLUP); pinMode(13, OUTPUT); }
Z vključitvijo pull-up uporov bo napetost vhodnih priključkov zrasla na 5 V (če je tipka odprta) ali bo ostala na 0 V (če je tipka sklenjena). Mikrokontroler mora samo prebrati, ali je na vhodnem priključku logično stanje “1” ali “0”, to pa bomo preverili z ukazom digitalRead().
voidloop() { while (digitalRead(15) == 0){ digitalWrite(13, !digitalRead(13)); delay(1); } while (digitalRead(16) == 0){ digitalWrite(13, !digitalRead(13)); delayMicroseconds(500); } }
Ukazi znotraj while() zanke se izvajajo, dokler obstaja navedeni pogoj. Konkretno, dokler držimo pritisnjeno tipko SW1, bo obstajal pogoj “digitalRead(15) == 0” in program se bo vrtel znotraj prve zanke, pri tem pa ustvarjal ton frekvence 500 Hz. Če držimo pritisnjeno tipko SW2, bo to ustrezalo pogoju “digitalRead(16) == 0” in program se bo vrtel znotraj druge zanke, pri tem pa ustvarjal ton frekvence 1 kHz. Če ni pritisnjena niti ena tipka, ne bo zadoščeno niti enemu od teh dveh pogojev, zato bo piskač utihnil.
Opazimo lahko, da moramo za razliko od Bascoma-AVR, kjer za preverjanje stanja priključka v While-Wend zanki uporabljamo le en enačaj (=),v programskem jeziku C ali C++ za preverjanje stanja priključka v while() zanki uporabiti dva enačaja (==).
Opomba: Programe Shield-A_3.bas, Shield-A_4.bas, Shield-A_3.ino in Shield-A_4.ino lahko brezplačno dobite v uredništvu revije Svet elektronike.
Shield-A_3