|
Vodilo I2C Besedilo vaje: Izdelajte program, ki bo omogočal pošiljanje sporočil med večimi mikrokrmilniškimi sistemi preko vodila I2C. Znaki sprejeti preko asinhronih zaporednih vrat uart1 naj bodo prikazani v prvi vrstici prikazovalnika LCD. V drugi vrstici naj se izpišejo znaki sprejeti preko vodila I2C. Pritisk tipke T0 naj sproži oddajo prve vrstice preko vodila I2C izbranemu mikrokrmilniškemu sistemu, pritisk tipke T2 pa sprejem vrstice od izbranega sistema. S tipko T1 izberemo mikrokrmilniški sistem kateremu oddajamo, s tipko T3 pa sistem od katerega zahtevamo njegovo vrstico. Hkrati mora biti naš sistem ves čas pripravljen na podobne zahteve ostalih sistemov na vodilu I2C. Torej na sprejem vrstice drugega sistema oziroma oddajo svoje vrstice. 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 je opisana v tretji vaji (časovnik), datoteki uart.c in uart.h pa v osmi vaji (asinhrona zaporedna komunikacija). Dodani sta datoteki i2c.c in i2c.h, kjer je se nahaja koda za delo z obema vmesnikoma do vodila I2C. 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). Dva ali več mikrokrmilniških sistemov bomo povezali preko vodila I2C, kot prikazuje slika spodaj. Vsak izmed sistemov je preko asinhronih zaporednih vrat uart1 povezan s terminalskim programom, ki teče na osebnem računalniku. Sprejete znake bo mikrokrmilniški sistem prikazoval v prvi vrstici prikazovalnika LCD. Vmesnik mikrokrmilnika i2c0 priključimo na povezavi sda in scl vodila I2C. Obe povezavi potrebujeta tako imenovana 'pull-up' upora do napajalne napetosti. Vsem sistemom je potrebno zagotoviti tudi skupno maso.
Mikrokrmilnik ves čas sprejema podatke, ki prispejo preko asinhronih zaporednih vrat uart1, in jih prikaže v prvi vrstici prikazovalnika LCD. Sprejem naredimo s prekinitvijo, ki jo ob vsakem prispelem znaku zahteva uart1. Pred uporabo asinhrona zaporedna vrata uart1 inicializiramo s klicem funkcije uart1_init(), zaradi česar moramo vključiti datoteko uart.h (glej osmo vajo - asinhrona zaporedna vrata). Po inicializaciji ne pozabite omogočiti oddaje. Funkcijo uart1_init() kličemo iz funkcije start_up(), kjer so zbrane vse inicializacije. Algoritem prekinitve uart1 je zelo podoben tistemu iz osme vaje (asinhrona zaporedna vrata) in je prikazan na sliki spodaj.
Delovanje programa ponazarja naslednja slika. Na prvem terminalu uporabnik odtipka tekst 'xxx', ki se prikaže v prvi vrstici prikazovalnika LCD prvega sistema. Podobno je na drugem terminalu odtipkan tekst 'yyy' in na tretjem 'zzz'. Številka na prvem mestu v prvi vrstici pove, kateremu sistemu bo prikazan tekst ob pritisku na tipko T0 poslan. Spreminjamo jo s pritiski na tipko T1. Številka na prvem mestu druge vrstice pa pove od katerega sistema bo tekst ob pritisku tipke T2 sprejet. Spreminjamo jo s pritiski na tipko T3.
V tretjem koraku je ob pritisku na tipko T0 sistema 1 tekst 'xxx' poslan sistemu 3. Sistem 1 je v tem primeru zahteval oddajo podatkov sistemu 3. Pri prenosu deluje sistem 1 v načinu master transmit, sistem 3 pa v načinu slave receive. V četrtem koraku je situacija spremenjena. Pritisk tipke T2 sistema 2 povzroči prenos teksta 'zzz' s sistema 3. Sistem 2 zahteva sprejem podatkov s sistema 3. Zato sedaj sistem 2 deluje v načinu master receive, sistem 3 pa v načinu slave transmit. Kot je bilo nakazano, lahko naprava na vodilu I2C deluje na štiri različne načine, in sicer: master transmit, master receive, slave transmit in slave receive. Vsi štirje načini delovanja so s kodo podprti v datoteki i2c.c. Preden začnete vmesnik i2c0 uporabljati, ga je potrebno inicializirati. To naredite s funkcijo i2c0_init(), ki se prav tako nahaja v datoteki i2c.c. Funkcijo i2c0_init() kličemo iz funkcije start_up(), kjer so zbrane vse inicializacije. Za njeno uporabo morate vključiti datoteko i2c.h. Deklaracija funkcije i2c0_init() je naslednja: void i2c0_init(int address, int general, int duty, char *tx_buf); Argumenti funkcije i2c0_init() podajajo nastavitve vmesnika i2c0 do vodila I2C:
Po inicializaciji bo vmesnik i2c0 pripravljen na delovanje, ko ga omogočimo. To storimo s postavitvijo bita i2en v registru I2C0CONSET. Vmesnik uporablja prekinitev i2c0. Ob prekinitvah i2c0 naj se izvrši funkcija handle_i2c0_state(), ki je že napisana v datoteki i2c.c. Ostala programska oprema komunicira z vmesnikom i2c0 preko globalnih spremenljivk. Odvisno od načina delovanja se uporabljajo različne globalne spremenljivke, kar podaja tabela spodaj.
Glavni program te vaje nadzira dogodke na tipkah. S tipko T1 nastavljamo naslov sistema, kateremu bomo sporočilce poslali, s tipko T3 pa naslov sistema, od katerega bomo sporočilce sprejeli. Tipki T0 in T2 sprožita oddajo, oziroma sprejem. V prvem primeru deluje vmesnik i2c0 v master transmit načinu, drugem primeru v master receive načinu. Algoritem glavnega programa podaja spodnja slika. Inicializacije (mikrokrmilnik, asinhrona zaporedna vrata uart1, vmesnik i2c0 do vodila I2C ...) so izpuščene.
Glavni program v neskončni zanki preverja stanje tipk, ter ustrezno reagira. Vsak pritisk tipke povzroči le en dogodek, ne glede na to, kako dolgo je tipka pritisnjena. Takšno obnašanje zagotavlja zgradba algoritma, ki po vsakem pritisku v zanki čaka na konec pritiska, preden prične z novim preverjanjem tipk. Po tej plati je algoritem le malo spremenjena različica algoritma iz druge vaje (inicializacija mikrokrmilnika), ki si je pomagal s spremenljivko ready. Pri izpisu znakov na prikazovalnik LCD si pomagamo z gonilnikom 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). Vendar v našem primeru nikdar ne spreminjamo celotnega napisa na prikazovalniku LCD. Spreminjamo le prvi znak v prvi ali drugi vrstici, ali spreminjamo napis v le eni izmed vrstic ... V nizu lcd_string zamenjamo le nekaj znakov. Da postane sprememba vidna na prikazovalniku LCD, moramo poklicati gonilnik lcd_driver_1(), ki bi ga potemtakem klicali po vsaki zamenjavi znakov v nizu lcd_string. Gonilnik lcd_driver_1() bi bil zato klican tako iz glavnega programa, kot iz prekinitve uart1. Prekinitev uart1 se seveda lahko zgodi tudi med osveževanjem prikazovalnika LCD iz glavnega programa. Sledi osveževanje prikazovalnika LCD znotraj prekinitve uart1, česar gonilnik lcd_driver_1() ne dopušča (funkcija lcd_driver_1() ni napisana kot funkcija z dovoljenim ponovnim vstopom - angl. reentrant). To pomeni, da se prekinitev uart1 med osveževanjem prikazovalnika LCD iz glavnega programa ne bi smela zgoditi. To pa je težje zagotoviti, zato se odločimo za drugačno rešitev. Gonilnika lcd_driver_1() naj neposredno ne kliče niti glavni program, niti prekinitev uart1. Oba naj spreminjata le niz lcd_string. Sam gonilnik kličemo s pomočjo prekinitve timer1, in sicer dovolj pogosto, da so vse spremembe vidne dovolj hitro. Prekinitev timer1 tako skrbi za redno in časovno enakomerno osveževanje vsebine prikazovalnika LCD. Da je temu tako poskrbimo s primerno inicializacijo časovnika timer1. Uporabimo funkcijo timer1_init() (glej tretjo vajo - časovnik). Po inicializaciji ne pozabite pričeti s štetjem. Poleg osveževanja prikazovalnika LCD naj ima prekinitev timer1 še dodatno vlogo. Ko kateri izmed preostalih sistemov našemu sistemu odda svoje sporočilo, se to sporočilo sprejme v medpomnilnik i2c0_rxslv, katerega je seveda potrebno sproti prazniti, sprejeto vsebino pa prikazati v drugi vrstici prikazovalnika LCD. Algoritem prekinitve timer1 je dokaj preprost in je prikazan na sliki spodaj. Prekinitev timer1 vsakič prebere morebitne sprejete znake in osveži prikazovalnik LCD. Naj ob tej priliki omenimo še, da sistem na zahtevo drugega sistema po sprejemu odda niz, na katerega kaže kazalec i2c0_txslv. Oddati moramo prvo vrstico prikazovalnika LCD. Zato niz i2c0_txslv že ob inicializaciji vmesnika i2c0 postavimo tako, da kaže na pravi del celotnega niza lcd_string.
Iz vsega povedanega sledi, da imamo tri vrste prekinitev, in sicer uart1, i2c0 in timer1. Za prvo in zadnjo moramo napisati svoji funkciji, za kateri smo v razlagi te vaje že podali algoritma. Za prekinitev i2c0 poskrbi funkcija handle_i2c0_state(), ki se nahaja v datoteki i2c.c. Vsemu povedanemu primerno moramo inicializirati vektorski nadzornik prekinitev, kar naredimo s klicem funkcije vic_init() (glej tretjo vajo - časovnik). Za njeno uporabo moramo vključiti datoteko vic.h. | ||||||||||||||||||||||||||||||||||||||||