V 3. delu te serije člankov smo pokazali, kako lahko mikrokontroler ugotovi, ali je neka tipka ali stikalo sklenjeno ali razklenjeno in kako lahko s pritiskom na tipko usmerjamo program, da nekaj počne na en ali drug način. Včasih moramo v programu šteti, kolikokrat je tipka pritisnjena, tu pa potem lahko nastanejo nepričakovane težave. To bomo ilustrirali s primerom, v katerem bomo potrebovali celoten niz svetlečih diod, od D0 do D7. Zato je najbolje, da si najprej ogledamo, kako na enostaven način upravljati z večjim številom svetlečih diod.
Kako so LED-ice D0-D7 in tipki SW1 in SW2 povezani z mikrokontrolerjem ATmega328P na Arduino Uno ploščici, smo lahko videli v drugem nadaljevanju te serije člankov. Isto shemo lahko spet vidimo na sliki 18, saj jo bomo potrebovali za razumevanje naloge tudi v tem nadaljevanju.
Ta vsebina je samo za naročnike
Na kratko obnovimo, kaj smo v naši prvi programski nalogi morali storiti, da smo prižgali LED-ico D7:
- Priključek PB4 smo konfigurirali kot izhod in ga postavili v stanje “1”, s čimer smo sklenili tranzistorsko stikalo T1 in ustvarimo predpogoj, da lahko katerakoli od LED-ice D0-D7 sploh sveti,
- Priključek PD7 smo konfigurirali kot izhod, ki ga bomo zatem lahko postavljali v stanja “1” in “0” in s tem prižigali in ugašali LED-ico D7.
Če bi želeli upravljati z delovanjem večjega števila svetlečih diod, bi bilo potrebno za vsako od njih pripadajoči priključek na mikrokontrolerju konfigurirati kot izhodni, nato pa bi jih lahko neodvisno prižigali in ugašali. Tega seveda ni težko sprogramirati, vendar bi morali v tem primeru napisati veliko ukazov. Pa si oglejmo, kako lahko to naredimo veliko bolj enostavno!
Predpogoj je, da so vse LED-ice priključene na ista vrata (port) – v našem primeru so to vrata D (port D). V tem primeru lahko vse njegove priključke, od PD0 do PD7 konfiguriramo kot izhodne z enim samim ukazom. Prav tako lahko sedaj z enim samim ukazom prižgemo določene LED-ice, druge pa pustimo ugasnjene.
Kako to uporabiti v praksi, bomo ilustrirali z naslednjo programsko nalogo.
- programska naloga (bežeča luč): Prižgati LED-ico D0, vse ostale pa ugasniti; dokler je pritisnjena tipka SW1, se morajo po vrsti prižigati LED-ica D1, potem D2 in tako vse do D7, zatem pa se znova prižge D0; dokler je pritisnjena tipka SW2, mora luč “potovati” v nasprotni smeri, od D7 proti D0.
Bascom-AVR rešitev (program Shield-A_5.bas)
Najprej bomo celotna vrata mikrokontrolerja port D konfigurirali kot izhodna, potem pa prižgali le njegovo skrajno desno LED-ico, D0:
ConfigPortd = Output
Portd = &B00000001
V tem primeru binarni niz &B00000001 ustreza priključkom od PD7 do PD0 po vrsti, zato bodo priključki od PD7 do PD1 postavljeni v stanje “0”, PD0 pa v stanje “1”. Z enim samim ukazom smo tako nastavili izhodna stanja vseh priključkov vrat port D – zelo enostavno, kaj pravite!?
No, da bi LED-ica D0 dejansko zasvetila, je treba vključiti še tranzistorsko stikalo T1, zato moramo ustrezno konfigurirati in nato postaviti v stanje “1” še priključek PB4, ki vklaplja to stikalo:
Config Portb.4 = Output
Portb.4 = 1
V programu bomo uporabili tipki SW1 in SW2, ki sta povezani na priključka PC1 in PC2, zato ta dva priključka nastavimo kot vhodna in vključimo še njuna pull-up upora:
Config Portc.1 = Input
Portc.1 = 1
Config Portc.2 = Input
Portc.2 = 1
Zdaj lahko napišemo neskončno zanko, v kateri bo program z While ukazi preverjal, če je katera od tipk pritisnjena (za razlago delovanja While zanke si poglejte prejšnje nadaljevanje te serije):
Do
While Pinc.1 = 0
RotatePortd , Left
Waitms 200
Wend
While Pinc.2 = 0
RotatePortd , Right
Waitms 200
Wend
Loop
Če ni pritisnjena nobena od tipk, to ne bo ustrezalo nobenemu pogoju While ukazov in program se bo “vrtel v prazno”: svetila bo sicer LED-ica D0, vendar ne bo nobenih drugih znakov, da program karkoli počne. Če pritisnemo tipko SW1, bo priključek PINC.1 postal “0” in program se bo začel vrteti v prvi While-Wend zanki, vse dokler tipko držimo pritisnjeno. V zanki je ključen ukaz RotatePortd, Left. Kaj in kako ta ukaz deluje, je ilustrirano na sliki 19.
Slika 19 prikazuje povezavo med Bascom-AVR ukazom (modro), vsebino registra PORTD in končnega efekta, ko se ta vsebina preslika na LED-ice D0-D7. Rdeča enka v registru ustreza rdeči LED-ici, pri čemer rdeča barva simbolizira, da ta LED-ica sveti. Prvi ukaz,
Portd = &B00000001
vpiše binarno enko v PD0, zato bo zasvetila samo LED-ica D0. Vsak ukaz
RotatePortd , Left
premakne vsebino registra za eno mesto v levo, zato bo binarna enka, z njo pa tudi svetloba v nizu LED-ice, potovala ali “bežala” proti levi. Osmi Rotate prestavi binarno enko s PD7 znova v PD0 in celoten proces se začne od začetka.
V drugi While-Wend zanki, ki se izvaja, dokler je pritisnjena tipka SW2, se nahaja ukaz
RotatePortd , Right
To daje slutiti, da je njegov učinek enak, le vsebina registra PORTD se bo premikala v desno, zato bo tudi svetloba po nizu LED-ice “bežala” proti desni strani. V obeh zankah nam je ukaz Waitms 200 nekoliko upočasnil celoten proces, da sploh lahko zaznavamo prižiganje LED-ice in potovanje svetlobe. Brez tega ukaza bi se diode prižigale tako hitro, da bi imeli občutek, kot da vse svetijo istočasno (poskusite)!
Arduino rešitev (program Shield-A_5.ino)
V Arduino IDE ukaz Rotate ne obstaja, zato si bomo za rešitev naloge morali izmisliti način, kako ta ukaz simulirati. Izkoristili bomo dejstvo, da so LED-ice označene s številkami od 0 do 7 in izkoristili te njihove oznake. Spremenljivko z imenom ukljucenaLED bomo uporabili za shranjevanje informacije, katera od LED-ice je trenutno vključena.
Za začetek bomo definirali spremenljivko, katere doseg je celotni program (in ne samo funkcija ali zanka) in definirati tudi to, da je vključena LED-ica D0:
intukljucenaLED = 0;
Zatem bomo v funkciji setup() definirali priključke LED-ice kot izhodne z uporabo for zanke in prižgali LED-ico, katere številka je zapisana v spremenljivki ukljucenaLED (v tem trenutku je to 0). Potem vklopimo tranzistorsko stikalo T1 tako, da njegov priključek “12” definiramo kot izhodni in ga postavimo v stanje “1” . Ostane nam še definiranje priključkov “A1” (PC1) in “A2” (PC2) na katera sta povezani tipki SW1 in SW2; ta dvs priključka definiramo kot vhodna in hkrati vključimo še njuna pull-up upora:
voidsetup() {
for ( int i = 0; i <= 7; i++){
pinMode(i, OUTPUT);
}
digitalWrite(ukljucenaLED, HIGH);
pinMode(12, OUTPUT);
digitalWrite(12, HIGH);
pinMode(A1, INPUT_PULLUP);
pinMode(A2, INPUT_PULLUP);
}
Zdaj lahko v neskončni zanki funkcije loop() z ukazi while preverjamo, če je katera od tipka pritisnjena.
voidloop() {
while (digitalRead(A1) == 0){
digitalWrite(ukljucenaLED, LOW);
if ( ukljucenaLED == 7 ){
ukljucenaLED = 0;
} else {
ukljucenaLED++;
}
digitalWrite(ukljucenaLED, HIGH);
delay(200);
}
while (digitalRead(A2) == 0){
digitalWrite(ukljucenaLED, LOW);
if ( ukljucenaLED == 0 ){
ukljucenaLED = 7;
} else {
ukljucenaLED–;
}
digitalWrite(ukljucenaLED, HIGH);
delay(200);
}
}
Če ni pritisnjena nobena tipka, ne bo izpolnjen nobeden izmed pogojev za vstop v katero od while zank in program se bo “vrtel v prazno”: svetila bo le LED-ica D0, kakšnega drugega znaka o kakršnemkoli delovanju programa pa ne bomo zaznali.
Če pa pritisnemo tipko SW1, bo vrednost priključka A1 postala “0” in program se bo začel vrteti v prvi while zanki, dokler bomo tipko držali pritisnjeno. V zanki se nahaja tudi naša rešitev, ki simulira delovanje Bascomovega ukaza Rotate. Najprej ugasnemo aktivno LED-ico:
digitalWrite(ukljucenaLED, LOW);
Nato preverimo, če je aktivna LED-ica hkrati tudi skrajna leva LED-ica (D7). Če je, definiramo, da bo naslednja aktivna LED-ica D0. Če aktivna LED-ica ni (else) D7, potem povečamo vrednost spremenljivke ukljucenaLED za eno, s čimer definiramo, da je naslednja LED-ica, ki jo želimo prižgati, prva na levi od prej ugasnjene LED-ice:
ukljucenaLED++;
Končno, na priključek vpišemo novo vrednost, ki je zapisana v spremenljivki ukljucenaLED:
digitalWrite(ukljucenaLED, HIGH);
In s pomočjo ukaza delay(200) upočasnimo delovanje programa, da bi lahko sledili spremembam pri prižiganju in ugašanju LED-ice.
Ugašanje aktivne LED-ice, definiranje naslednje, ki naj bi bila prižgana in njen vklop je tako hitro, da naše oko tega ne more zaznati, zato imamo občutek, da se vse zgodi istočasno.
V drugi while zanki preverjamo, če je pritisnjena tipka SW2, oziroma ali je njen priključek A2 v stanju “0”. Če je pritisnjena, se bo program vrtel v zanki vse dotlej, dokler držimo tipko pritisnjeno. Najprej izklopimo aktivno LED-ico:
digitalWrite(ukljucenaLED, LOW);
Nato preverimo, če je aktivna LED-ica hkrati tudi skrajna desna LED-ica (D0). Če je, moramo definirati, da bo naslednja aktivna dioda D7. Če aktivna LED-ica ni (else) D0, potem zmanjšamo vrednost spremenljivke ukljucenaLED za eno, s čimer definiramo, naj bo naslednja prižgana LED-ica prva na desni strani od prej ugasnjene LED-ice:
ukljucenaLED–;
Končno lahko na priključek pošljemo vrednost, ki je zapisana v spremenljivki ukljucenaLED:
digitalWrite(ukljucenaLED, HIGH);
in s pomočjo ukaza delay(200) upočasnimo delovanje programa, da lahko spremljamo spremembe ob prižiganju in ugašanju LED-ice.
***
Mehanska stikala niso popolna: vzmet lahko zaniha, zato zapiranje in odpiranje njihovih kontaktov ne poteka vedno idealno in večkrat se zgodi še kakšno dodatno zapiranje ali odpiranje, ki ni zaželeno. Če štejemo, kolikokrat je neka tipka pritisnjena, lahko v takšnih primerih nastanejo težave! V naslednjem programskem primeru bomo dokazali, da opisane težave v praksi dejansko obstajajo, potem pa poiskali ustrezno rešitev.
Uporabili bomo LED-ice D7-D0 in tipki SW1 in SW2 v vezavi, ki jo vidimo na sliki 18, vendar bomo programsko nalogo nekoliko spremenili: namesto, da luč “beži” dokler je pritisnjena katera od tipk, bomo sedaj zahtevali, da se z vsakim pritiskom premakne le za eno mesto.
- programska naloga: Prižgati LED-ico D0, vse ostale pa ugasniti; s pritiski na tipko SW1 se morajo po vrsti prižigati LED-ica D1, za njo D2 in tako vse do D7 popravilu “en pritisk, en premik”; s pritiski na tipko SW2 pa mora svetloba po istem pravilu “potovati” v nasprotni smeri, od D7 proti D0.
Prva Bascom-AVR rešitev (program Shield-A_6a.bas)
Programska naloga je zelo podobna prejšnji, zato je treba tudi vhodne in izhodne priključke konfigurirati na enak način. Če je tipka pritisnjena ali ne, se bo preverjalo z ukazom If, program znotraj neskončne zanke pa bo videti takole:
Do
If Pinc.1 = 0 Then
RotatePortd , Left
While Pinc.1 = 0
Wend
EndIf
If Pinc.2 = 0 Then
RotatePortd , Right
While Pinc.2 = 0
Wend
EndIfLoop
Kadar je pritisnjena tipka SW1, bo to ustrezalo pogoju iz prvega If ukaza, zato se bo izvedel v njem ugnezden ukaz RotatePortd, Left in luč se bo premaknila za eno mesto proti levi. Kadar pa je pritisnjena tipka SW2, bo to ustrezalo pogoju iz drugega If ukaza, zato se bo izvedel v njem ugnezden ukaz RotatePortd, Right in luč se bo premaknila za eno mesto proti desni.
Ker želimo zagotoviti, da se z vsakim pritiskom na tipko izvede samo en premik, smo za Rotate ukazom dodali prazni While-Wend zanki. Zanki uporabljata isti pogoj kot If ukaza, znotraj katerih se nahajata, zato bosta zadržali izvajanje programa, dokler bo ta pogoj obstajal, oziroma dokler tipke ne spustimo.
Prva Arduino rešitev (program Shield-A_6a.ino)
Vhodne in izhodne priključke moramo konfigurirati na enak način kot v prejšnji nalogi in definirati spremenljivko ukljucenaLED, preverjanje, ali je tipka pritisnjena ali ne, pa bomo zaupali ukazu If. Funkcija loop() je zdaj videti takole:
voidloop() {
if (digitalRead(A1) == 0) {
digitalWrite(ukljucenaLED, LOW);
if ( ukljucenaLED == 7 ){
ukljucenaLED = 0;
} else {
ukljucenaLED++;
}
digitalWrite(ukljucenaLED, HIGH);
while (digitalRead(A1) == 0){
}
}
if (digitalRead(A2) == 0){
digitalWrite(ukljucenaLED, LOW);
if ( ukljucenaLED == 0 ){
ukljucenaLED = 7;
} else {
ukljucenaLED–;
}
digitalWrite(ukljucenaLED, HIGH);
while (digitalRead(A2) == 0){
}
}
}
Če je pritisnjena tipka SW1, bo to ustrezalo pogoju v prvem If ukazu, zato se bo izvedel v njem ugnezden set ukazov, ki simulirajo Bascomov ukaz RotatePortd, Left in svetloba se bo premaknila za eno mesto v levo. Če je pritisnjena tipka SW2, bo to ustrezalo pogoju v drugem If ukazu, izvedel se bo v njem ugnezden set ukazov, ki simulirajo Bascomov ukaz RotatePortd, Right in svetloba se bo premaknila za eno mesto v desno.
Ker želimo zagotoviti, da se z vsakim pritiskom na tipko izvede samo en premik, smo za ukazi, ki izvajajo pomik, dodali dve prazni while zanki. Zanki uporabljata isti pogoj kot if ukaza, v katerih se nahajata, zato bosta zadržali izvajanje programa, dokler bo ta pogoj veljaven, oziroma dokler ne spustimo tipke.
Zaključek
Če smo programe dobro zasnovali, bodo izvajali točno tisto, kar zahteva naloga: z vsakim pritiskom na katero od tipk bo zasvetila sosednja LED-ica, nato pa se ne bo čisto nič dogajalo, vse dokler tipke ne spustimo in eno od tipk ponovno pritisnemo. In res, večina pritiskov na tipki povzroči samo en premik svetlobe v levo ali desno. Če boste pritiskali tipke dovolj dolgo in posebno še takrat, ko tipkate hitro, pa boste lahko opazili, da lahko svetloba včasih tudi “preskoči”, za eno ali več mest. Razlog za to je prej opisano neidealno obnašanje mehanskih kontaktov: mikrokontroler zazna mehansko nihanje kontakta kot dva ali več zaporednih pritiskov in naredi natanko tisto, kar “misli”, da želimo.
Gre za zelo resen problem, ki se pojavi v vseh vezjih, kjer so na mikrokontroler povezana mehanska stikala, tipke, kontakti. Problem je mogoče reševati na nivoju strojne in programske opreme, mi pa bomo v naslednjem nadaljevanju ponudili ustrezno programsko rešitev.
Opomba: Programe Shield-A_5.bas, Shield-A_6a.bas, Shield-A_5.ino in Shield-A_6a.ino lahko brezplačno dobite v uredništvu revije Svet elektronike.
Avtorja: Vladimir Mitrović in Robert Sedak
email: vmitrovic12@gmail.com
2020_292_40
Mitrovic Shield-A (4)