Asistent

Osnove
mikroprocesorskih
sistemov
  Laboratorijske vaje
    Samostojni zagon
Inicializacija
mikrokrmilnika

Časovnik
Zunanje prekinitve
Prekinitve
Ura realnega časa in
zapis v pomnilnik flash

A/D in D/A pretvorba
Asinhrona zaporedna komunikacija
Vodilo I2C

Računalniško
načrtovanje vezij II


Mikroprocesorji
v elektroniki

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:

   #define P0_0 0x00000001
#define P0_1 0x00000002
          ...
#define P0_30 0x40000000

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:

   - prescale ... največja vrednost števca T1PC, ki predstavlja delilnik impulzov urinega signala; števec časovnika timer1 T1TC se poveča vsakič, ko je T1PC = prescale,
- match ... kazalec na polje štirih vrednosti tipa integer; vrednosti predstavljajo konstante, ki se primerjajo z vrednostjo števca T1TC; ko je T1TC enak kateri izmed štirih vrednosti, se izvrši dejanje podano z argumentom control,
- control ... poljubna kombinacija zastavic, ki podajajo dejanja, ko števec časovnika doseže katero izmed podanih vrednosti
   (možne zastavice, pri čemer je j = 0, 1, 2, ali 3, so:
   mrji ... dogodek T1TC = match[j] postavi zahtevo po prekinitvi,
mrjr ... dogodek T1TC = match[j] resetira števec T1TC in
mrjs ... dogodek T1TC = match[j] ustavi štetje)
in
- count ... podaja način štetja časovnika (možne vrednosti:
   timer ... šteje impulze urinega signala perifernega vodila VPB,
counter_rising ... šteje naraščajoče fronte izbranega vhodnega signala,
counter_falling ... šteje padajoče fronte izbranega vhodnega signala in
counter_both ... šteje naraščajoče in padajoče fronte izbranega vhodnega signala) in
- pin ... maska s konfiguracijo pina z izbranim vhodnim signalom (možne vrednosti:
   P0_10_TIMER1_CAP0 ... vhodni signal na pinu p0.10 (zajemni vhod CAP1.0),
P0_11_TIMER1_CAP1 ... vhodni signal na pinu p0.11 (zajemni vhod CAP1.1),
P0_17_TIMER1_CAP2 ... vhodni signal na pinu p0.17 (zajemni vhod CAP1.2),
P0_18_TIMER1_CAP3 ... vhodni signal na pinu p0.18 (zajemni vhod CAP1.3),
P0_19_TIMER1_CAP2 ... vhodni signal na pinu p0.19 (zajemni vhod CAP1.2) in
P0_21_TIMER1_CAP3 ... vhodni signal na pinu p0.21 (zajemni vhod CAP1.3)

Č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
nlow = (1 - 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 void function_name() ).

  typedef void (* voidfuncptr)();

Argumenti funkcije vic_init() podajajo nastavitve vektorskega nadzornika opravil:

   - fiq ... poljubna kombinacija prekinitev, ki so definirane kot prednostne FIQ prekinitve; navadno nimamo več kot ene FIQ prekinitve (možne prekinitve: wdt, arm_core0, arm_core1, timer0, timer1, uart0, uart1, pwm0, i2c0, spi0, spi1_ssp, pll, rtc, eint0, eint1, eint2, eint3, ad0, i2c1, bod in ad1),
   - irq ... poljubna kombinacija prekinitev, ki so definirane kot navadne IRQ prekinitve (možne prekinitve so enake kot pri argumentu fiq),
   - function ... polje 16-ih kazalcev na funkcije po prioriteti razvrščenih vektorskih prekinitev IRQ,
   - interrupt ... polje 16-ih po prioriteti razvrščenih vektorskih prekinitev IRQ (možne prekinitve so enake kot pri argumentu fiq) in
   - def ... kazalec na funkcijo po prioriteti nerazvrščene prekinitve IRQ.

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 T1TC = match[0]). Za naslednjo prekinitev je potrebno še ponastaviti nadzornik prekinitev, kar storimo z vpisom poljubne vrednosti v register VICVectAddr.

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.