Asistent

Osnove
mikroprocesorskih
sistemov


Računalniško
načrtovanje vezij II
  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

Mikroprocesorji
v elektroniki

A/D in D/A pretvorba

Besedilo vaje: Izdelajte program, ki na analognem izhodu D/A pretvornika generira nizkofrekvenčni signal pravokotne, trikotne ali sinusne oblike. Frekvenca signala naj bo spremenljiva od 1Hz do 10Hz. Nastavljamo jo z velikostjo analogne napetosti na analognem vhodu A/D pretvornika, ki jo spreminjamo s pomočjo potenciometra. Med posameznimi oblikami signalov preklapljamo s pritiski na tipke T0, T1 in T2. Trenutna oblika signala in njegova frekvenca naj bo izpisana na prikazovalniku LCD. 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), datoteka vic.c pa je opisana v tretji vaji (časovnik). Dodani sta datoteki adc_dac.c in adc_dac.h, kjer je se nahaja koda za inicializacijo analogno digitalnega in digitalno analognega pretvornika. 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).

Analogen signal generiramo z zaporednimi pretvorbami digitalnih vzorcev. Digitalno analogne pretvorbe se dogajajo v enakomernih časovnih intervalih. Posledica tega je stopničasta oblika generiranega signala, kar pomeni, da bosta trikotni in sinusni signal pravzaprav stopničasta približka obeh oblik.

Digitalne vzorce signalov pripravimo v naprej v treh tabelah. Zaradi simetrične oblike bi bilo dovolj, če bi tabelirali le 1/4 periode vsakega izmed signalov. Vendar zaradi kasnejše lažje implementacije branja vzorcev tabeliramo polovico periode, in sicer od T/4 do 3*T/4. Če so vzorci tabelirani na tak način, jih lahko preprosto beremo od prvega do zadnjega in nato zopet nazaj do prvega ... Če imamo signale tabelirane z N vzorci, potem je i-ti vzorec enak:

recti = max * (sign(2 * i - N + 1) + 1) / 2
triangi = max * (N - i - 1) / (N - 1)
sinei = max * (sin((2 * i + N - 1) * pi / (2 * (N - 1))) + 1) / 2

pri čemer je i = {0, 1 ... N-1}. Število max predstavlja največjo digitalno vrednost, ki se po digitalno analogni pretvorbi preslika v maksimalno napetost. V našem primeru imamo na voljo 10-bitni pretvornik, zato je max = 0x3ff, oziroma 1023.

Ker bomo vzorce brali od prvega (indeks = 0) do zadnjega (indeks = N-1) in nazaj, se v eni periodi zvrsti 2*(N-1) vzorcev. Zaporedna vzorca si sledita v časovnem intervalu

t = 1 / (2 * (N - 1) * f) ,

pri čemer f predstavlja frekvenco generiranega signala. V tem časovnem intervalu se zvrsti n impulzov urinega signala na perifernem vodilu VPB, ki teče s frekvenco fVPB. Velja:

n = t * fVPB = fVPB / (2 * (N - 1) * f)

Čas bomo merili s časovnikom, ki šteje impulze urinega signala na perifernem vodilu VPB. Vsakič, ko časovnik prešteje do n, digitalno analogni pretvornik pretvori naslednji vzorec. Frekvenco f generiranega signala spreminjamo z analogno napetostjo, kar pomeni, da se s tem spreminja tudi število n.

Analogno digitalni in digitalno analogni pretvornik moramo pred uporabo seveda inicializirati, kar naredimo s klicem funkcij adc0_init() in dac_init(), ki sta že napisani v datoteki adc_dac.c. Funkciji adc0_init() in dac_init() kličemo iz funkcije start_up(), kjer so zbrane vse inicializacije. Za njuno uporabo morate vključiti datoteko adc_dac.h. Deklaraciji funkcij adc0_init() in dac_init() sta naslednji:

  void adc0_init(int pins0, int pins1, int mode);
  void dac_init();

Funkcija dac_init() je brez argumentov. Klic funkcije povzroči le nastavitev pina p0.25 vrat port0 kot izhoda digitalno analognega pretvornika, s čimer je pretvornik že pripravljen za uporabo. Argumenti funkcije adc0_init() podajajo nastavitve analogno digitalnega pretvornika adc0:

   - pins0 ... analogni vhodni pini med p0.0 do p0.15 vrat port0 (možna je poljubna kombinacija vrednosti: adc0_6 in adc0_7), če noben izmed pinov p0.0 do p0.15 vrat port0 ni uporabljen kot vhod analogno digitalnega pretvornika, potem je pins0 = 0x00000000,
- pins1 ... analogni vhodni pini med p0.16 do p0.31 vrat port0 (možna je poljubna kombinacija vrednosti: adc0_0, adc0_1, adc0_2, adc0_3, adc0_4, adc0_5), če noben izmed pinov p0.16 do p0.31 vrat port0 ni uporabljen kot vhod analogno digitalnega pretvornika, potem je pins1 = 0x00000000 in
- mode ... način pretvorbe; če je mode = 0, se pretvorba izvrši le, kadar je zahtevana; v tem primeru je dovoljen le en analogni vhodni pin (argumenta pins0 in pins1); kadar mode ni enak nič, se pretvorbe dogajajo nepretrgoma ena za drugo zaporedoma na pinih, ki so definirani kot vhodni analogni pini; število mode določa število ciklov in s tem natančnost posamezne pretvorbe (možne vrednosti so: 0, 4, 5, 6, 7, 8, 9, 10 in 11).

Zahtevo po izvršitvi ene analogno digitalne pretvorbe podamo s postavitvijo bita start_now v registru AD0CR. Pretvorjena 10-bitna digitalna vrednost se nahaja od 6-ega do 15-ega bita registra AD0DR. Vrednost je veljavna, ko se pretvorba konča, kar označuje postavljen bit done.
Digitalno analogna pretvorba se izvaja neprestano. 10-bitna vrednost od 6-ega do 15-ega bita registra DACR se ves čas preslikava v analogno napetost.

Da bomo vzorec zamenjali z novim v pravem trenutku, moramo meriti čas, kar seveda storimo s časovnikom. Zopet imamo na voljo dva načina. Časovnik lahko prosto teče, medtem program z dovolj pogostim odčitavanjem stanja časovnika sam preverja, ali že čas za zamenjavo vzorca. Po drugem načinu časovnik s prekinitvijo opozori, da je trenutek za zamenjavo vzorca pravi.

Naredimo vajo najprej na prvi način. Časovnik timer1 moramo inicializirati, kar naredimo s klicem funkcije timer1_init(), ki je opisana v tretji vaji (časovnik). Nastavimo ga tako, da prosto teče. Algoritem ponazarja spodnja slika. Vse inicializacije (mikrokrmilnik, A/D in D/A pretvornik, časovnik) so izpuščene.

Najprej napolnimo tabele z vzorci signalov po zgornjih enačbah. Vzpostavimo začetno stanje. Kazalec table vedno kaže na tabelo vzorcev trenutno izbrane oblike signala. Iz izbrane začetne frekvence izračunamo konstanto n ter sprožimo časovnik. Časovnik po inicializaciji namreč še ne teče. Po klicu funkcije timer1_init() časovnik prične s štetjem šele, 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. Vsakič, ko pride trenutek zamenjave vzorca, le tega zamenjamo z naslednjim in določimo nov indeks za prihodnjo iteracijo. Naredimo še vse spremljajoče aktivnosti (branje tipk (glej drugo vajo - inicializacija mikrokrmilnika), A/D pretvorba, izračun prihodnjega trenutka zamenjave vzorca ...). Za izpis na prikazovalnik LCD uporabimo gonilnik lcd_driver_1(), katerega izvorna koda je priložena v datoteki main.c. Gonilnik ob vsakem klicu na prikazovalnik LCD izpiše vsebino niza, na katerega kaže globalni kazalec lcd_string (glej prvo vajo - samostojni zagon).


Vajo naredimo še na drug način z uporabo prekinitev. Časovnik z zahtevo po prekinitvi sam opozori, da je prišel trenutek za zamenjavo vzorca. Mikrokrmilnik med čakanjem 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] = n 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. Ker v konstanto match[0] zapišemo vrednost n, bo časovnik timer1 zahteval prekinitev na vsakih n impulzov urinega signala na perifernem vodilu VPB, torej vsakič, ko je potrebno zamenjati vzorec.

Č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. Opis funkcije najdete v razlagi tretje vaje (časovnik). Zahteva časovnika timer1 po prekinitvi naj le to dejansko sproži. Prekinitev timer1 definiramo kot vektorsko prekinitev IRQ. Vse ostale prekinitve onemogočimo. Podrobnejšo razlago vektorskega nadzornika prekinitev najdete v skripti. Po vseh inicializacijah (mikrokrmilnik, A/D in D/A pretvornik, časovnik in vektorski nadzornik prekinitev) in vzpostavitvi začetnega stanja (napolnitev tabel z vzorci, postavitev začetnih vrednosti ...) lahko časovnik prične s štetjem. 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 zamenjave vzorcev 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 zamenjamo vzorec in postorimo še vse potrebno (trenutne nastavitve, osveževanje prikazovalnika LCD, priprava za naslednjo prekinitev ...). Postavimo 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 naslednja zamenjava vzorca (prekinitev) zgoditi zelo kmalu (zelo natančno vzorčenje, ali visoka frekvenca), 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.


Premislite kako bi vajo naredili še na tretji način brez uporabe prekinitev, ter brez uporabe tabeliranih vzorcev signalov.