V predhodnih nadaljevanjih smo spoznali dve važni tehniki programiranja: multipleksiranje in pulzno-širinsko modulacijo. Prva od njih nam omogoča, da na RGB diodah D8-D11 razvojnega sistema Shield-A sočasno (pravzaprav, navidezno sočasno) aktiviramo različne barve. Druga tehnika omogoča, da programsko krmilimo jakost vsake posamezne barve. V naši naslednji nalogi bomo prav tako uporabili vezavo s slik 45 in 46, pa tudi timerje bomo ponovno konfigurirali tako, da delajo v PWM načinu, glede na sliko 44 – tukaj ni nobenih sprememb. Da pa bi rešili nalogo, bomo morali kombinirati obe navedeni tehniki!
Avtorja: Vladimir Mitrović in Robert Sedak
E-pošta: vmitrovic12@gmail.com
2021-300-40
16. programska naloga: Na RGB diodi D8 postopno menjajte jakost rdeče barve od minimalne do maksimalne jakosti in nazaj; istočasno na isti način menjajte jakost zelene barve na diodi D9, modre na D10 in bele na D11.
Dodatno, na diodi D8 postopno menjajte barvo iz rdeče v zeleno in nazaj; istočasno na D9 iz zelene v modro in nazaj, na D10 pa iz modre v rdečo in nazaj; na D11 je treba menjati jakost bele barve, kot v osnovni nalogi.
Ta vsebina je samo za naročnike
Bascom-AVR rešitev
(program Shield-A_16a.bas)
Najprej bomo vse priključke mikrokontrolerja, na katere so vezane katode RGB diod, konfigurirati kot izhodne in jih postavili v stanje “1”, s čemer smo izključili vse štiri RGB diode:
Config Portb = &B00001111 Portb = &B00001111
Opazili bomo, da priključkov PD5, PD6 in PD7 nismo konfigurirali kot izhodne, ker bomo namesto njih uporabili PWM izhode timerja, OC0B, OC0A in OC2B. Za to bo poskrbel konfiguracijski ukaz za timerje, katerega konfiguriramo v skladu s postavljeno nalogo:
Config Timer0 = Pwm , Prescale = 64 , Compare A Pwm = Clear Up , Compare B Pwm = Clear Up Config Timer2 = Pwm , Prescale = 64 , Compare B Pwm = Clear Up
Oba timerja sta postavljena v PWM način dela s faktorjem deljenja 64, Compare A in Compare B sta Bascom-AVR naziva za OCR registra. Tem OCR registrom bomo dodali alternativna imena v skladu z barvami, s katerimi upravljajo in jim vpisali začetno vrednost 0, zato, da bi od začetka vse barve bile izključene.
Modra Alias Ocr0a Rdeča Alias Ocr0b Zelena Alias Ocr2b Modra = 0 Rdeča = 0 Zelena = 0
V programu bomo uporabili še pomožno spremenljivko Barva
Dim Barva As Byte
katere vrednost bomo menjali v For-Next zanki od 0 do 255
Do Waitms 200 For Barva = 0 To 255 Gosub D8_d11 Next
in nato nazaj, od 255 do 0:
For Barva = 255 To 0 Step -1 Gosub D8_d11 Next Loop
Kot tudi v predhodnih programih, vrednost 0 ustreza ugasnjeni diodi, 255 pa maksimalni intenzivnosti posamezne barve. Te vrednosti bomo prenašali v registre OCR0B (= Rdeča), OCR2B (= Zelena) in OCR0A (= Modra) v podprogramu D8_d11. Istoimenski podprogram smo že uporabljali v programih 14a in 14b, v katerih je rešeno multipleksiranje (izmenično vklapljanje RGB diod) kot del prekinitvenega mehanizma (interrupt) timerja Timer0. V tem programu ima enako funkcijo, vendar ga bomo uporabili kot “običajni” podprogram, ki ga iz Do-Loop zanke pogosto kličemo z ukazi Gosub D8_d11. In programska logika je malo drugačna (slika 47).
Posamezni postopki iz diagrama teka imajo naslednje pomene:
pripravi rdečo, zeleno ali modro pomeni, da je trenutno vrednost spremenljivke Barva potrebno prenesti v tisti OCR register, ki krmili jakost navedene barve;
vključi D8, D9, D10 ali D11 pomeni, da je ustrezni priključek mikrokontrolerja potrebno postaviti v stanje logične ničle;
generiraj impulz pomeni da mora mikrokontroler generirati en impulz na priključku ustrezne barve, katere trajanje je določeno z vrednostjo spremenljivke Barva;
izklopi D8, D9, D10 ali D11 pomeni da je ustrezen priključek mikrokontrolerja potrebno postaviti stanje logične enice;
Registrom smo dali ustrezna imena Rdeča, Zelena in Modra, pa tudi krmilne priključke mikrokontrolerja smo preimenovali glede na diode, ki jih krmilijo, Led8, Led9, Led10 in Led11, zato to ne bi bilo težko sprogramirati. Ker moramo pri izvrševanju podprograma proizvesti samo en impulz ustrezne barve za vsako od diod, je potrebno program uskladiti (sinhronizirati) z delom timerja. Vrednosti v registrih, katere določajo trajanje impulzov smemo menjati samo takrat, kadar je timer dosegel najvišjo vrednost – 255, in preden začne šteti nazaj (poglejte sliko 44 in pojasnitev v predhodnem nadaljevanju). To je hkrati tudi trenutek, kadar menjamo “aktivno” diodo. Zato bomo na začetku podprograma počakali, da vrednost Timerja0 naraste do 255:
D8_d11: While Timer0 < 255 Wend
Nato bomo, v skladu s postavljeno nalogo, prenesli trenutno vrednost spremenljivke Barva v register, ki krmili jakost rdeče barve in počakali trenutek, v katerem timer prične šteti nazaj: Rdeča = Barva While Timer0 = 255 Wend
To je pravi trenutek da vključimo diodo D8 in jo držimo vključeno, dokler se vrednost timerja ne vrne na 255:
Led8 = 0 While Timer0 < 255 Wend
Med tem časom je timer štel od 255 do 0 in nazaj do 255 in proizvedel en impulz, katerega trajanje je določila vrednost spremenljivke Barva, oziroma registra Rdeča. Preostane nam še to, da izključimo diodo D8 in vpišemo 0 v register Rdeča, kot smo do nadaljnjega preprečili vklop rdeče barve:
Led8 = 1
Rdeča = 0
V nadaljevanju bomo isto proceduro ponovili za zeleno barvo na diodi D9 in modro na diodi D10 ter še na diodi D11 za vse tri barve istočasno:
Rdeča = Barva Zelena = Barva Modra = Barva While Timer0 = 255 Wend Led11 = 0 While Timer0 < 255 Wend Led11 = 1 Rdeča = 0 Zelena = 0 Modra = 0 Return
Opomba: ker se podprogram D8_d11 izvaja ciklično, ena izvršitev za drugo, se je v praksi pokazalo, da se bo “sam od sebe” sinhroniziral s timerji po prvi izvršitvi, zato so v končni rešitvi ukazi za sinhronizacijo iz začetka podprograma izbrisani.
Bascom-AVR rešitev dodatne naloge
(program Shield-A_16b.bas)
Če želimo barve menjati v skladu s postavljeno nalogo, bomo morali v podprogramu D8_d11 sočasno menjati parametre dveh barv. Za diodo D8 bomo v registre za rdečo in zeleno barvo postavili takšne vrednosti:
D8_d11: Rdeča = 255 - Barva Zelena = Barva
Z rastjo vrednosti spremenljivke Barva, se bo vrednost v registru Rdeca zmanjševala, vrednosti v registru Zelena pa bo rastla prav tako, kot je v nalogi zahtevano. Podobno bomo napravili z zeleno in modro barvo na diodi D9 in z modro in rdečo na D10:
... Zelena = 255 - Barva Modra = Barva ... Modra = 255 - Barva Rdeča = Barva ...
Ostanek podprograma je zelo podoben tistemu iz predhodne naloge.
Poglejmo sedaj na kateri način je možno rešiti postavljene naloge v Arduino programskem jeziku!
Arduino rešitev
(program Shield-A_16a_SE.ino)
V začetnih postavkah Arduino postavlja timerje Timer0 in Timer2 v različne načine dela; za rešitev te naloge ju moramo uskladiti. Ker bomo uporabili ukaz delay(), ne smemo menjati postavke Timerja0, zato bomo prilagoditi način dela Timerja2. To pa ni povsem enostavno: Arduino IDE podpira več različnih razvojnih plošč, ki ne uporabljajo istih mikrokontrolerjev. Zato je funkcija analogWrite() napisana tako, da ustreza različnim situacijam, za kar je potrebno daleč več procesorskega časa, kot ga uporabljamo z direktnim upravljanjem registrov, kot v Bascom-AVR-ju. V kolikor uporabljamo funkcijo analogWrite(), se zelo lahko zgodi, da se izgubi sinhronizacija in diode pričnejo svetiti drugače, kot je zahtevano v programu.
To je razlog zakaj je ne bomo uporabili, ampak bomo direktno upravljali z vsebino registrov mikrokontrolerja, ki določajo način dela timerja. Za boljše razumevanje predlagamo, da pogledate tudi uradno dokumentacijo mikrokontrolerja ATMega328P (tj. njegov data sheet); tam je zelo precizno pojasnjeno, kakšna je funkcija posameznih bitov krmilnih registrov. Direktno upravljanje z registri je precej zahtevno za tiste, ki se do sedaj niso srečali s to metodo; zato bomo najprej definirali funkcije, ki bodo imele samo-pojasnjujoča imena. Uporabili bomo metodo v kateri prevajalnik ne kliče funkcije, ampak zamenjuje kodo in s tem pohitri izvrševanje strojne kode (makro inštrukcije).
Timerja imata po dva kontrolna registra, katerih biti imajo točno določeno funkcijo. To sta registra TCCR0A in TCCR0B za Timer0, oziroma TCCR2A in TCCR2B za Timer2. Način dela timerja definiramo s postavljanjem posameznih bitov ali kombinacij bitov v določena stanja.
Najprej bomo definirali funkcije s pomočjo katerih bomo krmilili s stanji PWM priključkov in preko njih krmilili RGB diode. Funkcijo, s katero aktiviramo uporabo PWM izhoda timerja za rdečo LED na priključku PD5, smo poimenovali red_PWM_enable(). Z njeno pomočjo postavljamo COM0B1 bit krmilnega registra TCCR0A v stanje “1”:
#define red_PWM_enable() (TCCR0A |= (1 << COM0B1))
Na ta način ne vplivamo na stanja ostalih bitov. Zakaj postavljanje prav tega bita povzroči potrebno aktivnost lahko ugotovite s preučevanjem data sheeta mikrokontrolerja ATmega238P.
Funkcijo, s katero deaktiviramo uporabo PWM izhoda timerja za rdečo LED na priključku PD5, smo poimenovali red_PWM_disable(), v njej isti bit postavljamo v stanje “0”:
#define red_PWM_disable()(TCCR0A &= ~(1 << COM0B1))
Na podoben način definiramo funkcije, katerim aktiviramo in deaktiviramo PWM izhode timerja za zeleno in modro LEDico:
#define green_PWM_enable() (TCCR2A |= (1 << COM2B1)) #define green_PWM_disable()(TCCR2A &= ~(1 << COM2B1)) #define blue_PWM_enable() (TCCR0A |= (1 << COM0A1)) #define blue_PWM_disable()(TCCR0A &= ~(1 << COM0A1))
Sedaj bomo definirati funkcije, s katerimi postavljamo priključke PD5, PD3 in PD6 v stanje “0”
#define red_off() (PORTD &= ~(1 << PD5)) #define green_off() (PORTD &= ~(1 << PD3)) #define blue_off() (PORTD &= ~(1 << PD6))
in nato tudi funkcije, s katerimi bomo vključevali in izključevali RGB diode D8-D11:
#define led8_on() (PORTB &= ~(1 << PB0)) #define led8_off() (PORTB |= (1 << PB0)) #define led9_on() (PORTB &= ~(1 << PB1)) #define led9_off() (PORTB |= (1 << PB1)) #define led10_on() (PORTB &= ~(1 << PB2)) #define led10_off() (PORTB |= (1 << PB2)) #define led11_on() (PORTB &= ~(1 << PB3)) #define led11_off() (PORTB |= (1 << PB3))
Tudi tukaj so imena izbrana tako, da je iz samega imena jasno, kaj katera funkcija dela. Zaradi enostavnejšega “branja” programske kode bomo uporabili pointer spremenljivke s prikladni imenom. Njihova vrednosti bo naslov registra OCR0B (= red_intensity), OCR2B (= green_intensity) in OCR0A (= blue_intensity):
volatile uint8_t *red_intensity = &OCR0B; volatile uint8_t *blue_intensity = &OCR0A; volatile uint8_t *green_intensity = &OCR2B; volatile uint8_t *timer0 = &TCNT0;
Sedaj smo naredili potrebne priprave in pripravljeni smo za pisanje programa! V funkciji setup() bomo vse priključke mikrokontrolerja, na katere so vezani priključki RGB diod, konfigurirali kot izhodne, in še bomo priključke, na katere so vezane katode RGB diod, postavili v stanje logične enice, da bi z eno potezo izključili vse RGB diode:
void setup() { DDRB = B00001111; DDRD = B01101000; PORTB = B00001111;
Opazili boste, da je priključek 7 (PD7) definiran kot vhodni (vrednost nula). Preden delamo kakršne koli izmenjave v registrih, ki so povezani s prekinitvami, moramo zaustaviti izvrševanje prekinitve:
noInterrupts();
Sedaj lahko postavimo bit WGM21 v registru TCCR2A v stanje logične enice, da bi način dela Timera2 izenačili z načinom dela Timera0, in po tem bomo ponovno omogočili prekinitve.
TCCR2A |= (1 << WGM21); interrupts(); //omogoći prekinitve }
V funkciji loop() bomo s pomočjo for zanke menjali vrednost spremenljivke barva od 0 do 255 in nazaj, od 255 do 0, in vsako dobljeno vrednost takoj pošljemo funkciji D8_D11():
void loop() { delay(200); for ( int barva = 0; barva <=255; barva++){ D8_D11(barva); } for ( int barva = 255; barva >= 0; barva--){ D8_D11(barva); } }
V funkciji D8_D11() bomo uporabili isti algoritem, kot v Bascom_AVR rešitvi, s tem da bomo za branje vrednosti timerja uporabili pointer spremenljivko timer0. Preden vpišemo vrednost spremenljivke intensity v OCR, moramo aktivirati uporabo PWM izhoda timerja, preden pa izključimo rdečo barvo, ga moramo deaktivirati:
void D8_D11(int intensity) { red_PWM_enable(); *red_intensity = intensity; led8_on(); while ( *timer0 < 255 ) {} led8_off(); red_PWM_disable(); red_off();
V kolikor tega ne naredimo, se bo dogodilo to, da RGB diode svetijo s slabo intenzivnostjo in to takrat, ko tega ne pričakujemo. Isti postopek bomo ponovili za zeleno in modro barvo in za vse tri barve istočasno na diodi D11:
green_PWM_enable(); *green_intensity = intensity; while ( *timer0 == 255){} led9_on(); while ( *timer0 < 255){} led9_off(); green_PWM_disable(); green_off(); blue_PWM_enable(); *blue_intensity = intensity; while ( *timer0 == 255){} led10_on(); while ( *timer0 < 255){} led10_off(); blue_PWM_disable(); blue_off(); red_PWM_enable(); *red_intensity = intensity; green_PWM_enable(); *green_intensity = intensity; blue_PWM_enable(); *blue_intensity = intensity; while ( *timer0 == 255){} led11_on(); while ( *timer0 < 255){} led11_off(); red_PWM_disable(); red_off(); green_PWM_disable(); green_off(); blue_PWM_disable(); blue_off(); }
Arduino rešitev dodatne naloge
(program Shield-A_16b_SE.ino)
Tukaj bomo sočasno menjali jakost dveh barv v vsaki RGB diodi, programska koda za D11 pa bo ostala enaka, kot predhodni rešitvi:
void D8_D11(int intensity) { red_PWM_enable(); green_PWM_enable(); *red_intensity = 255 - intensity; *green_intensity = intensity; ... *green_intensity = 255 - intensity; *blue_intensity = intensity; ... *blue_intensity = 255 - intensity; *red_intensity = intensity; ... }
Samo zaradi primerjave si lahko pogledate tudi “klasične” programske rešitve s pomočjo ukazov analogWrite(), in opazili boste njihove pomanjkljivosti (programi Shield-A_16a.ino in Shield-A_16b.ino). Teh programov tukaj ne bomo analizirali.
Opomba: programe Shield-A_16a.bas, Shield-A_16a.ino, Shield-A_16a_SE.ino, Shield-A_16b.bas, Shield-A_16b.ino in Shield-A_16b_SE.ino lahko brezplačno dobite v uredništvu revije Svet elektronike.
Shield-A (13)