Komunikacija preko USB kabla je dvosmerna: ne samo da mikrokontroler lahko pošlje sporočilo osebnemu računalniku, pač pa lahko tudi osebni računalnik pošlje sporočilo mikrokontrolerju. S primeri iz preteklega nadaljevanja smo ilustrirali pošiljanje sporočila, tokrat pa bomo pokazali kako lahko mikrokontroler sprejme in obdela sporočilo, ki mu je bila poslana iz terminalskega emulatorja. Predpostavili bomo, da sta Arduino Uno in osebni računalnik še vedno povezana kot prikazano na sliki 23 in da je na Arduino Uno postavljen razvojni sistem Shield-A. Izkoristili bomo svetleče diode D0-D7 z razvojnega sistema ki so, kot smo to že prej spoznali, s priključki Arduino Uno ploščice in z mikrokontrolerjem povezane kot je prikazano na sliki 27.
Komunikacija med mikrokontrolerjem in PC-jem
-
Slika 27: Svetleče diode D7-D0 so tako povezane s priključki Arduino Uno ploščice in priključki mikrokontrolerja programska naloga: Program sprejema znake iz terminalskega emulatorja in vsak sprejeti znak vrača nazaj v nespremenjeni obliki, razen če gre za majhno črko; v tem primeru jo bo program pretvoril v veliko črko in jo šele nato poslal nazaj. Nekatere znake bo program razumel kot ukaze:
- če je sprejeta številka med 0 in 7, je potrebno vklopiti “istoimensko” LED-ico, D0-D7;
- če je sprejeta številka 8, je potrebno zamenjati stanje vseh LED-ic;
- če je sprejeta številka 9, je potrebno ugasniti vse LED-ice.
Ta vsebina je samo za naročnike
Bascom-AVR rešitev (program Shield-A_9.bas)
Najprej bomo definirali spremenljivko Znak in konfigurirali priključke ki krmilijo LED-ice na že poznati način:
Dim Znak As Byte Config Portb.4 = Output Portb.4 = 1 Config Portd = Output Portd = &B00000000
V spremenljivko Znak bomo v brezkončni zanki sprejemali znake, ki prihajajo iz terminalskega emulatorja:
Do Znak = Inkey()
Funkcija Inkey() vrača binarno vrednost sprejetega znaka (njegovo 8-bitno ASCII kodo) ali binarno ničlo, če od predhodne do te izvršitve nič ni bilo sprejeto. Takšna “prazna” sporočila moramo ignorirati, zato bomo podprogram Vrati_znak izvršili samo v primeru, če je terminalski emulator res nekaj poslal:
If Znak <> 0 Then Gosub Vrati_znak End If
V podprogramu preverjamo, ali je sprejeti znak mala črka, in v tem primeru zmanjšujemo njegovo ASCII kodo za 32 (tako smo ga pretvorili v veliko črko) in nato sprejeti ali “popravljeni” znak pošljemo nazaj v terminalski emulator z ukazom Print:
Vrati_znak: If Znak >= "a" And Znak <= "z" Then Znak = Znak - 32 End If Print Chr(znak) Return
Po vrnitvi v glavno zanko mora program še preveriti, ali sprejeti znak predstavlja nek ukaz definiran z nalogo. Če gre za številko med 0 in 7, program pretvori njegovo ASCII kodo v binarno vrednost 0 do 7 in nato postavlja en od bitov PORTD.0 do PORTD.7, in tako vklopi eno od LED-ic:
If Znak >= "0" And Znak <= "7" Then Znak = Znak - "0" Portd.znak = 1
Če so sprejete številke 8 ali 9, bo program spremenil stanje vseh priključkov ali pa jih vse postavil v stanje 0, prav tako, kot je z nalogo predvideno:
Elseif Znak = "8" Then Portd = Not Portd Elseif Znak = "9" Then Portd = &B00000000 End If Loop
Preverimo, če vse dela kakor smo si zamislili! Za to je potrebno postaviti Shield-A na Arduino Uno, ju povezati z osebnim računalnikom po shemi s slike 23, sprogramirati mikrokontroler in zagnati terminalski emulator.
Če pritisnemo na tipkovnici neko tipko, se bo v oknu terminalskega emulatorja izpisal ustrezen znak. To, česar ne vidimo je, da je ta znak “potoval” preko USB kabla do mikrokontrolerja, da ga je program sprejel in se je nato preko istega kabla vrnil nazaj v terminalski emulator, da bi ga končno lahko prikazal na zaslonu. Da je temu res tako, bomo dokazali tako, da na tipkovnici pritisnemo eno od tipk s črkami: male črke se bodo izpisale kot velike, kot da je na tipkovnici aktivirana tipka “caps lock” (opomba: to velja samo za črke angleške abecede, za specifične črke naše abecede bi morali dodatno dodelati program).
In sedaj preverimo del programa, ki bi moral vklapljati in izklapljati svetleče diode! Če pritisnete tipko s številko 7, se bo vklopila svetleča dioda D7, 6 bo vklopila D6 in tako po vrsti vse do tipke s številko 2, prav tako kot pričakujemo. Tipki s številkama 8 in 9 delata po pričakovanjih vendar opazimo, da LED-ici D0 in D1 stalno svetita in ju ni mogoče kontrolirati iz terminalskega emulatorja. Zaradi česa sta te dve LED-ici drugačni od ostalih?
Priključka mikrokontrolerja PD0 in PD1, na katera sta spojeni, sta večnamenska in se tako uporabljata tudi za serijsko komunikacijo. Če v Bascom-AVR programu uporabimo ukaz kot je Print ali Inkey(), bomo izgubili kontrolo nad njima – neodvisno od tega, ali smo ju konfigurirali kot izhodna ali če jima menjamo logična stanja, te naše aktivnosti se bodo dogodile, kot smo jih želeli znotraj mikrokontrolerja, vendar se to ne bo odrazilo na zunanjih priključkih PD0 in PD1.
Obstajajo tudi druge situacije v katerih izgubimo kontrolo nad tema priključkoma, obstajajo pa tudi situacije, v katerih ju normalno uporabljamo (npr., normalno smo ju uporabljali v programih Shield-A_5.bas do Shield-A_7b.bas). Zato bi se generalno priporočilo lahko glasilo tako: če uporabljate Arduino v nekem vezju, se izogibajte uporabi priključkov PD0 in PD1, lahko se zgodi, da ne bo vse delalo tako, kot ste si zamislili. To velja kar se tiče Bascom-AVRa; poglejmo, če obstaja tudi pri Arduino programskem jeziku isti problem!
Arduino rešitev (program Shield-A_9.ino)
Najprej bomo konfigurirati priključke, ki krmilijo LED-ice in serijsko komunikacijo z računalnikom na od prej poznani način:
void setup() { for ( int i = 0; i <= 7; i++){ pinMode(i, OUTPUT); digitalWrite(i, LOW); } pinMode(12, OUTPUT); digitalWrite(12, HIGH); pinMode(A1, INPUT_PULLUP); pinMode(A2, INPUT_PULLUP); Serial.begin(9600); }
Za prepoznavanje, če je poslan znak preko serijske komunikacije, bomo uporabili funkcijo Serial.available(). Ta funkcija nam sporoči če in koliko bajtov je dostopnih za branje s serijskega porta, kateri so že sprejeti in spravljeni v priročen vhodni spomin serijskega porta. V kolikor funkcija vrača številko večjo od ničle to pomeni, da so podatki pripravljeni v priročnem spominu in jih lahko preberemo.
Na sliki 28 vidite z rdečim pravokotnikom okvirjeno polje, v katero se vpisujejo podatki. Za pošiljanje v Arduino Uno pa moramo bodisi pritisniti tipko Enter ali klikniti na virtualno tipko “Send”.
Za branje enega znaka bomo uporabili funkcijo Serial.read(). Funkcija vrača vrsto podatkov int. Da bi si olajšali programersko življenje pri kasnejši primerjavi prebranega znaka s pričakovanimi znaki, bomo prebrani znak shranili v spremenljivko znak vrste char. Prevajalnik bo med prevajanjem dodal funkcije za pretvarjanje int v char.
Opazili bomo isti algoritem kot v Bascom-AVR primeru:
void loop() { if (Serial.available() > 0) { char znak = Serial.read(); if (znak >= 'a' and znak <= 'z' ) { znak = znak - 32; Serial.println(znak); } if (znak >= '0' and znak <= '7' ) { znak = znak -'0'; digitalWrite(znak, HIGH); } else if (znak == '8') { for ( int i = 0; i <= 7; i++){ digitalWrite(i, !digitalRead(i)); } } else if (znak == '9') { for ( int i = 0; i <= 7; i++){ digitalWrite(i, LOW); } } } }
Opazili boste, da pri primerjavi vsebine spremenljivke znakuporabljamo enojne narekovaje in ne dvojnih. Ko uporabljamo dvojne narekovaje takrat prevajalnik ve, da želimo primerjati vrsto podatkov string, ko pa uporabljamo enojne narekovaje takrat prevajalnik ve, da želimo primerjati podatke vrste char. V programskih jezikih C in C++ morata biti oba podatka, ki se primerjata, iste vrste. V nasprotnem primeru se lahko dogodi to, da prevajalnik ne javi napake, program pa ne bo delal to, kar pričakujemo.
Ena od karakteristik programskega jezika Bascom-AVR je izjemno enostavno krmiljenje s priključki mikrokontrolerja s pomočjo pridruženih registrov. Da bi lahko isto naredili v programskih jezikih C ali C++, bi morali uporabljati Booleovo algebro in pomikanje podatkov v registru. Za podrobno pojasnitev takega načina krmiljenja bi potrebovali nov članek; zato uporabljamo že poznana ukaza digitalRead() in digitalWrite().
Ko vpišemo znak 8, takrat program v For zanki “prehaja” preko priključkov PD0 (D0) do PD7 (D7), bere stanje posameznega priključka, spremeni (invertira) stanje in zapiše novo stanje priključka (za invertiranje se uporablja znak “!”).
Pri izvrševanju programa se pojavi že prej opisani problem s priključkoma PD0 in PD1.
Vprašanje za radovedne: Kaj se bo zgodilo, če s pomočjo monitorja serijske komunikacije pošljete niz 9753?
***
Drugi, še bolj eleganten način prikazovanja sporočil iz mikrokontrolerja je uporaba alfanumeričnega displeja (LCD). Razvojni sistem Shield-A ima 16-pinski priključek za alfanumerični displej tipa 16×2 (dve vrstici s po 16 znaki v svaki vrstici). Ko se displej postavi na Shield-A, bo prekril svetleče diode (slika 29), vendar to niti ni več važno: informacija ki jo lahko prikažemo na displeju je precej bolj kvalitetna od informacije, ki jo lahko prenesemo s pomočjo LED-ic.
Opombe: standardno se proizvajajo alfanumerični displeji z eno, dvema ali štirimi vrsticami, s 16 ali 20 znaki v vsaki vrstici. Večina displejev na tržišču ima identičen razpored priključkov in se, teoretično, vsi te displeji lahko priključijo na Shield-A. Ker pa so dimenzije displejev odvisne od skupnega števila znakov, bo na Shield-A najbolje “sedel” displej tipa 16×2. Razpored priključkov na tiskani ploščici razvojnega sistema Shield-A ustreza AN displejem, pri katerih je priključek 16 katoda, priključek 15 pa anoda osvetlitve ozadja zaslona. Na tržišču obstajajo tudi displeji pri katerih sta priključka anode in katode osvetlitve ozadja zaslona “zamenjana”; takšni displeji se lahko postavijo na Shield-A,vendar jim brez ustreznega popravka prevezave ne bomo mogli vklopiti osvetlitev ozadja.
Shema na sliki 30 prikazuje kako se alfanumerični displej preko Shielda-A povezuje s priključki Arduino Uno ploščice, oziroma, s priključki mikrokontrolerja. Za komunikacijo z displejem je potrebnih šest linij: dve kontrolni (RS, ENA) in štiri podatkovne (D7-D4). Opazili boste, da smo izkoristili iste priključke Arduino ploščice oziroma mikrokontrolerja, katere smo do sedaj uporabljali za krmiljenje LED-ic. To ima smisel zato, ker so LED-ice tako prekrite, pa tudi ne bodo predstavljale problem niti v tehničnem smislu, dokler je tranzistorsko stikalo T1 (slike 18, 21, 27) izključeno.
Prikaz na displeju se nastavlja z napetostjo na priključku VEE, s pomočjo trimerja RV2 na razvojni plošči Shield-A. Če je slika preveč bleda ali pretemna ali se sporočilo sploh ne vidi, je potrebno vrteti trimer, dokler se ne doseže najboljša vidljivost. Optimalen položaj drsnika je največkrat blizu priključka, ki je spojen na maso. Osvetlitev ozadja alfanumeričnega displeja se vklopi s tranzistorjem T2.
V naslednjih primerih bomo ilustrirali kako se uporablja alfanumerični displej iz programskih jezikov Bascom-AVR in Arduino.
- 10. programska naloga: Program naj izpiše pozdravno sporočilo, nato naj izmeri napetost na drsniku potenciometra RV1 in prikaže izmerjeno vrednost v drugi vrstici displeja.
Bascom-AVR rešitev (program Shield-A_10.bas)
V programu moramo najprej definirati kateri tip AN displeja uporabljamo
Config Lcdbus = 4 Config Lcd = 16 * 2
in nato opisati, kako so priključki displeja povezani s priključki mikrokontrolerja:
Config Lcdpin = Pin , Db7 = Pind.7 , Db6 = Pind.6 , Db5 = Pind.5 , Db4 = Pind.4 , E = Pind.3 , Rs = Pind.2
Ta opis mora ustrezati dejanskemu stanju (primerjajte s sliko 30)! Konfiguracijski del običajno zaključimo z dvema izvršnima ukazoma:
Cursor Off Cls
Prvi bo preprečil prikaz kurzorja (črtice ki prikazuje aktualen naslov oziroma lokacijo, na kateri se bo izpisal naslednji znak), drugi ukaz bo zbrisal vsebino displeja in kot aktualen naslov postavi gornji levi vogal displeja. Če ima displej osvetlitev ozadja, ga bomo vključili s pomočjo tranzistorskega stikala T2:
Config Portc.3 = Output Portc.3 = 1
Sedaj lahko pišemo kar želimo! Uporabili bomo ukaz Lcd, ki je zelo podoben ukazu Print iz predhodnih primerov:
Lcd "*** Shield-A ***" Home L Lcd "Svet elektronike"
Rezultat vidimo na sliki 31 zgoraj: prvo sporočilo je izpisano v gornji vrstici displeja, medtem, ko ukaz Home Lpovzročil, da se je drugo sporočilo izpisalo v začetku spodnje vrstice.
Pozdravno sporočilo bomo zadržali nekaj sekund, in nato izbrisali spodnjo vrstico, da bi jo osvobodili za prikaz merjenja:
Wait 5 Home L Lcd Spc(16)
Funkcija Spc(16) generira 16 belin, ki bodo prekrile predhodno vsebino spodnje vrstice displeja. Konfiguracija A-D pretvornika, postopek merjenja in preračun pridobljene vrednosti v napetost so identični postopkom prikazanim v programih Shield-A_8a in Shield-A_8b. Edina razlika je v tem, da smo ukaz Print zamenjali z ukazom Lcd:
Config Adc = Single , Prescaler = Auto , Reference = Avcc Do Napon = Getadc(0) Napon = Napon * 5000 Napon = Napon / 1023 Home L Lcd "U = " ; Napon ; " mV " Waitms 100 Loop
Arduino rešitev (program Shield-A_10.ino)
V Arduino programih za delo z AN displejem uporabljamo ustrezno knjižnico funkcij. Ko knjižnico vključimo v naš program, bomo definirali objekt z imenom lcd in sočasno definirali komunikacijske priključke:
#include <LiquidCrystal.h> LiquidCrystal lcd(2, 3, 4, 5, 6, 7);
V funkciji setup() bomo vključili osvetlitev ozadja s pomočjo tranzistorskega stikala T2. Vrsto AN displeja definiramo s pomočjo funkcije lcd.begin(16,2) s katero določimo, da uporabljamo displej z 2 vrsticama in s 16 znaki v vsaki vrstici. Po definiciji displeja se izpis avtomatsko prične od prvega mesta v gornji vrstici. To lahko spremenimo s funkcijo lcd.setCursor(), s katero določimo stolpec in vrstico v kateri želimo izpis. Potrebno je vedeti, da se prva vrstica označuje z ničlo. Isto velja tudi za stolpce. Zatolcd.setCursor(0,1) definira začetek izpisa v prvem stolpcu in drugi vrstici. Izpisovanje teksta in spremenljivk je omogočeno s funkcijo lcd.print(); po njeni izvršitvi kurzor ostaja v isti vrstici in stolpcu, ki sledi po zadnjem izpisanem znaku. Zato če želimo nastaviti izpis v isti vrstici, nam ni potrebno ponovno definirati položaja kurzorja. Če želimo zbrisati vsebino neke vrstice, jo bomo prepisali s sporočilom, ki vsebuje 16 razmikov (belina). Seveda moramo pred tem postaviti kurzor na začetek želene vrstice.
void setup() { pinMode(17, OUTPUT); digitalWrite(17, HIGH); lcd.begin(16, 2); lcd.print("*** Shield-A ***"); lcd.setCursor(0, 1); lcd.print("Svet elektronike"); delay(5000); lcd.setCursor(0, 1); lcd.print(""); }
Funkcija loop()uporablja identični algoritem kot v primerih Shield-A_8a in Shield-A_8b, samo sedaj namesto Serial.print() ni Serial.println() samo lcd.print() in obvezno moramo postaviti kurzor na začetek druge vrstice.
void loop() { int vrijednostPotenciometra = analogRead(A0); int napon = map(vrijednostPotenciometra, 0, 1023, 0 , 5000); lcd.setCursor(0, 1); lcd.print("U = "); lcd.print(napon); lcd.print(" mV "); delay(100); }
Opomba: Programi Shield-A_9.bas, Shield-A_10.bas, Shield-A_9.ino in Shield-A_10.ino se lahko brezplačno dobijo v uredništvu revije Svet elektronike.
Avtorja: Vladimir Mitrović in Robert Sedak
email: vmitrovic12@gmail.com
2021-294
Shield-A_9