|
Uporaba časovnika Besedilo vaje: Izdelajte program, ki bo na enem izmed pinov vzporednih vrat port0 generiral pravokotni signal. Razmerje med trajanjem visokega in nizkega nivoja (duty cycle), ter frekvenca signala naj bosta podana na enem mestu, tako da ju je možno nastavljati z minimalno spremembo izvorne kode. Delovanje programa preverite z osciloskopom. Razlaga: Program napišete v razvojnem okolju, ki teče na osebnem računalniku. Pripravljen delovni prostor (angl. workspace) z vsemi pripadajočimi datotekami najdete tukaj. Opis datotek, ki se nahajajo v paketu, najdete v uvodu razlage prve vaje (samostojni zagon). Dodana je datoteka vic.c, kjer je napisana inicializacija vektorskega nadzornika prekinitev (VIC). Lastno kodo, ki izvaja to vajo, dodate v prazno definicijo funkcije main() v datoteki main.c. Najprej morate mikrokrmilnik inicializirati, kar naredite s klicem funkcije init() v funkciji start_up() v datoteki startup.c. Za njeno uporabo morate vključiti datoteko init.h. Opis funkcije init() prav tako najdete v razlagi prve vaje (samostojni zagon). Enega izmed pinov vzporednih vrat port0 moramo definirati kot izhodni pin, da bomo na njem generirali pravokotni signal. Lahko uporabimo kateregakoli, razen p0.24 in p0.31, ki ju ni mogoče uporabljati kot splošna vhodno/izhodna pina, ter pinov p0.9 in p0.11 (oziroma p0.1 in p0.9 na starejših Šarmih z zaporedno številko manjšo od 50000), kamor sta priključena izhoda prilagodilnika nivojev zaporednih vrat MAX232. Prilagodilnik nivojev na pinih vsiljuje svoje stanje, zato morata biti p0.9 in p0.11 vedno konfigurirana kot vhodna. Pri kodiranju so nam na voljo v naprej definirane maske posameznih pinov:
Ker pri vaji ne uporabljamo diod LED, kakor tudi ne prikazovalnika LCD, lahko za generiranje pravokotnega signala brez omejitev uporabimo katerikoli pin, kamor so diode LED, oziroma prikazovalnik LCD priključeni. Paziti je potrebno le pri pinih, kamor so na učnem razvojnem sistemu Šarm priključene tipke in nastavljiva analogna napetost. To so pini od p0.12 do p0.15 in pin p0.27. Tipke na pine od p0.12 do p0.15 vsiljujejo napetosti, ki odražajo njihova stanja. Prav tako je na p0.27 vsiljena analogna napetost. Da ne pride do kratkega stika, morajo biti ti pini vhodni. Vseeno pa jih je mogoče uporabiti tudi kot izhodne, če vezje s tipkami, oziroma napetostni delilnik odklopimo. Za odklop vezja s tipkami imamo na voljo stikalo (angl. jumper) J16, za odklop napetostnega delilnika pa stikalo J20. Da bo pravokotni signal pravšnji, se morajo preklopi iz visokega v nizko stanje in obratno zgoditi ob točno določenih časovnih trenutkih. Z drugimi besedami, meriti moramo čas, za kar uporabimo časovnik. Čas lahko merimo na dva načina. Časovnik prosto teče, medtem program z dovolj pogostim odčitavanjem stanja časovnika sam preverja koliko časa je preteklo. Po drugem načinu časovnik s prekinitvijo opozori, da je določen časovni interval minil. Naredimo vajo najprej na prvi način. Časovnik timer1 moramo inicializirati, kar naredimo s klicem funkcije timer1_init(), ki je že napisana v datoteki timer.c. Funkcijo timer1_init() kličemo iz funkcije start_up(), kjer so zbrane vse inicializacije. Deklaracija funkcije timer1_init() je naslednja: void timer1_init(int prescale, int *match, int control, int count, int pin); Argumenti funkcije podajajo nastavitve časovnika timer1:
Časovnik timer1 inicializiramo tako, da prosto teče. Šteje naj impulze nedeljenega urinega signala perifernega vodila VPB. Ob dogodkih T1TC = match[j] naj se ne zgodi nič. Po klicu funkcije timer1_init() časovnik prične s štetjem, ko v register T1TCR vpišemo v naprej definirano konstanto counter_enable. Z branjem števca T1TC dobimo podatek o pretečenem času, oziroma število pretečenih period urinega signala perifernega vodila VPB, kar je osnova za delovanje našega programa. Glede na to določimo trajanje visokega in nizkega stanja generiranega pravokotnega signala.
Iz slike razberemo, da velja
nhigh =
duty *
fVPB /
f
pri čemer je nhigh število period urinega signala perifernega vodila VPB, ko je generiran signal frekvence f v visokem stanju, in nlow število period v nizkem stanju. Ob tako razdeljenem trajanju dosežemo zahtevano razmerje visokega in nizkega stanja (duty cycle). Algoritem vaje izvedene na prvi način prikazuje spodnja slika. Inicializacija mikrokrmilnika je v algoritmu izpuščena. Po pridobivanju osnovnih podatkov (fVPB, nhigh in nlow) in inicializaciji časovnika ves čas preverjamo, ali je že napočil trenutek preklopa. Ob preklopu se vsakič izračuna naslednji preklop, ki ga podaja spremenljivka next. Vajo naredimo še na drug, bolj pravilen način. Časovnik z zahtevo po prekinitvi sam opozori, da je predviden časovni interval potekel. Mikrokrmilnik med čakanjem na dogodek ni polno zaposlen s preverjanjem števca časovnika, kot je to primer zgoraj. V tem času lahko opravlja druga opravila. Časovnik timer1 sedaj inicializiramo tako, da ob dogodku T1TC = match[0] postavi zahtevo po prekinitvi, števec T1TC pa se postavi na nič. Ob ostalih dogodkih T1TC = match[j] se ne zgodi nič. Še vedno štejemo impulze nedeljenega urinega signala perifernega vodila VPB. Časovnik tako zahteva prekinitve. Da do prekinitev res pride mora biti vektorski nadzornik prekinitev pravilno nastavljen, kar naredimo s klicem funkcije vic_init(), ki je že napisana v datoteki vic.c. Funkcijo vic_init() kličemo iz funkcije start_up(), kjer so zbrane vse inicializacije. Za njeno uporabo morate vključiti datoteko vic.h. Deklaracija funkcije vic_init() je naslednja: void vic_init(int fiq, int irq, voidfuncptr *function, int *interrupt, voidfuncptr def); Pri tem
voidfuncptr pomeni kazalec na funkcijo brez argumentov, ki ničesar ne vrača (funkcija tipa
typedef void (* voidfuncptr)(); Argumenti funkcije vic_init() podajajo nastavitve vektorskega nadzornika opravil:
Vektorski nadzornik prekinitev nastavimo tako, da zahteva časovnika timer1 po prekinitvi le to dejansko sproži. Prekinitev timer1 definiramo na primer kot vektorsko prekinitev IRQ. Vse ostale prekinitve onemogočimo. Podrobnejšo razlago vektorskega nadzornika prekinitev najdete v skripti. Po inicializaciji časovnika (funkcija timer1_init()) in postavitvi vektorskega nadzornika prekinitev (funkcija vic_init()) lahko časovnik prične s štetjem. To se zgodi, ko v register T1TCR vpišemo v naprej definirano konstanto counter_enable. Z zagonom časovnika se glavni program konča. Zaključimo ga z neskončno zanko. Za preklope iz visokega v nizko stanje in obratno sedaj skrbi funkcija tipa voidfuncptr, ki se izvrši ob timer1 prekinitvi. Pokliče jo priložena funkcija irq(), katere razlago najdete v skripti. Algoritem timer1 prekinitvene funkcije prikazuje slika spodaj. Ob prekinitvi najprej pridobimo osnovne podatke
(fVPB,
nhigh in
nlow). Nato glede na trenutno stanje izvršimo preklop iz nizkega v visoko stanje ali obratno. Ob tem nastavimo novo vrednost primerjalnega registra
T1MR0 (=
match[0]), s čimer zagotovimo pravi trenutek naslednje prekinitve. Ker je trenutna zahteva po prekinitvi s tem obdelana, jo umaknemo. V našem primeru to storimo z vpisom v naprej definirane konstante
mr0_interrupt v register
T1IR (umaknitev prekinitvene zahteve tipa
Ob prekinitvi se števec časovnika postavi na nič, ter začne s štetjem že med izvajanjem prekinitvene funkcije. Če se mora naslednji preklop (prekinitev) zgoditi zelo kmalu, bo nova vrednost match[0] primerjalnega registra T1MR0 zelo nizka. Tako se lahko zgodi, da ima števec T1TC ob nastavitvi registra T1MR0 že višjo vrednost in do prekinitve bi prišlo šele po preletu celotnega obsega 32-bitnih števil. Zato takrat postavimo T1TC = T1MR0 - 1, s čimer ob zaključku trenutne prekinitve takoj izzovemo novo. Pravi trenutek prekinitve je zaradi končne hitrosti mikrokrmilnika tako ali tako že zamujen. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||