Asistent

Osnove
mikroprocesorskih
sistemov


Računalniško
načrtovanje vezij II


Mikroprocesorji
v elektroniki
  Laboratorijske vaje
    Programiranje v zbirniku
Zbirniška funkcija
Zapis s plavajočo vejico
Digitalna ura
Generator vlaka
impulzov

Gonilnik
prikazovalnika LCD

Gonilnik asinhronih
zaporednih vrat

Uporaba operacijskega
sistema v realnem času

Generiranje vlaka impulzov pri pulznem izbiranju telefonske številke

Besedilo vaje: Sprogramirajte pulzno izbiranje telefonske številke s tipkovnico. Številke, ki še čakajo na generiranje ustreznega vlaka impulzov, naj bodo prikazane na prikazovalniku LCD. Generirani impulzi naj vklapljajo in izklapljajo eno izmed diod LED. Pri programiranju uporabite podan preprost operacijski sistem, ki teče v realnem času.

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. V njem se poleg nastavitev projekta nahajajo tudi datoteke s spremljajočo izvorno kodo.

Po končanem zagonu pristanemo v prazni funkciji start_up(), ki se nahaja v datoteki startup.c. Tu boste mikrokrmilnik inicializirali, kar naredite s klicem funkcije init(). Za njeno uporabo morate vključiti datoteko init.h. Nato boste s klicem funkcije sch_on() dvignili operacijski sistem. Dolžino časovne rezine bomo določili kasneje. Razlago delovanja in opis izvorne kode operacijskega sistema najdete v skripti.

Ker imamo na učnem razvojnem sistemu Šarm na voljo le štiri tipke, bomo v tej vaji realizirali le štiri številke. In sicer naj tipka T0 pomeni številko ena, tipka T1 številko dve ..., do tipke T3, ki naj predstavlja številko štiri. Za pravo telefonsko številčnico bi sicer potrebovali še šest dodatnih tipk, kar pa nima bistvenega vpliva na zgradbo programske kode.

Številko ena predstavlja en impulz, številko dve dva impulza ..., do številke devet, ki jo podaja devet impulzov in ničle z desetimi impulzi. Hitrost vlaka impulzov, ki predstavljajo določeno številko, ni točno določena. Vendar se največkrat priporoča hitrost desetih impulzov na sekundo. Prav tako ni strogo definirano trajanje posameznega impulza in presledka med dvema impulzoma. Za potrebe naše vaje bomo upoštevali priporočeno razmerje med sklenjeno in razklenjeno linijo 40% proti 60%. Presledek med dvema vlakoma impulzov pa naj bo najmanj eno sekundo. Iz vsega povedanega sledi, da moramo generirati vlake 40ms impulzov z 60ms pavzami med njimi, kakor je prikazano v spodnji sliki.

Vajo bomo realizirali s pomočjo opravil, ki jih bo ob vnaprej določenih trenutkih klical mehanizem operacijskega sistema. Nalogo razdelimo na manjše dele. Pri naši vaji moramo odčitavati pritisnjene tipke, ter generirati ustrezne vlake impulzov. Za prikaz moramo še sproti pripravljati primeren niz, ki ga izpisujemo na prikazovalniku LCD. Glede na povedano lahko nalogo naredimo s štirimi opravili, ki jih poimenujmo:

   - key_driver() ... gonilnik za tipkovnico, ki odtipkano vpisuje v ciklični medpomnilnik,
- string() ... opravilo, ki vsebino medpomnilnika preoblikuje v niz za prikaz,
- lcd_driver_1() ... opravilo, ki pripravljen niz prikaže na prikazovalniku LCD, in
- dial() ... opravilo, ki iz medpomnilnika jemlje odtipkane vrednosti in generira ustrezen vlak impulzov.

Opravilo key_driver() preverja tipke in pritisnjene zapisuje v ciklični medpomnilnik tipa FIFO (angl. First In First Out), vkolikor je v njem še prostor. Več o cikličnih medpomnilnikih najdete v skripti. Z branjem iz medpomnilnika lahko katerokoli drugo opravilo, ali glavni program pogleda, ali je bila kakšna tipka pritisnjena, oziroma katera. Pri tem ostali deli programske opreme ne potrebujejo informacij o zgradbi tipkovnice, na katere pine je priključena itd. V primeru da tipkovnico zamenjamo, moramo na novo napisati le gonilnik, torej opravilo key_driver(). Gonilnik key_driver() mora biti klican dovolj pogosto, da prestreže vse pritiske tipk. Ob preredkem klicanju lahko pritisk kakšne tipke povsem zgrešimo in uporabnik dobi vtis, da je tipkovnica na trenutke mrtva. Algoritem opravila prikazuje slika spodaj.

Vsakič ko je opravilo na vrsti s funkcijo get_keys() preverimo stanje tipk. Posebno pozornost je potrebno posvetiti temu, da se vsak pritisk tipke vpiše v medpomnilnik le enkrat, neglede na to, koliko časa je tipka pritisnjena. Za to poskrbi spremenljivka ready, ki je enaka ena le v primeru, ko je tipka pritisnjena na novo. Tipko vpišemo v medpomnilnik key_buf na prvo prosto mesto, na katerega kaže indeks key_buf_end. Vkolikor medpomnilnik ni poln pomaknemo indeks key_buf_end ciklično za eno mesto naprej. Medpomnilnik tako pripravimo na sprejem naslednje tipke.

Naslednje opravilo string() je zelo podobno opravilu string() iz prejšnje vaje. Prebere čakajoča števila v cikličnem medpomnilniku in to pretvori v niz. Z dodajanjem presledkov pripravi 32-znakovni niz za prikaz na prikazovalniku LCD, na katerega kaže globalni kazalec lcd_string. Algoritem opravila string() je enostaven in ga prikazuje slika spodaj. Številka je pretvorjena v znak s prištevanjem znaka '0'.

Tretje opravilo je gonilnik prikazovalnika LCD. Skrbi za izpis niza, na katerega kaže globalni kazalec lcd_string, in ga pripravi opravilo string(). Uporabimo funkcijo lcd_driver_1(), ki jo poznamo že od prej.

Zadnje opravilo dial() skrbi za generiranje vlakov impulzov. Da so impulzi pravilno dolgi, je pri tem opravilu odločilna frekvenca klicanja, medtem ko je za ostala opravila veljalo le, da ne smejo biti klicana preredko. Zato se najprej posvetimo dolžini časovne rezine operacijskega sistema, ki jo določimo ob klicu funkcije sch_on(). Potrebujemo 40ms dolge impulze s 60ms presledki. Da točno zadenemo trenutke preklopov, je lahko časovna rezina dolga le 20ms / m, pri čemer je m naravno število. Vendar potrebuje gonilnik lcd_driver_1() za osvežitev celotnega prikazovalnika LCD nekaj manj kot 10ms. To pomeni, da mora biti časovna rezina dolga vsaj 10ms, oziroma raje malo daljša. Ob upoštevanju obeh pogojev je časovna rezina lahko dolga le 20ms. Ker imamo štiri opravila, bi bilo vsako klicano enkrat na 80ms, kar je za opravilo dial() preredko, saj bi zamudili trenutke preklopov. Zato mora biti poleg trenutnega opravila opravilo dial() na vrsti v vsaki časovni rezini. Oziroma opravilo dial() je privilegirano opravilo. Imamo torej tri normalna opravila key_driver(), string() in lcd_driver_1(), ter privilegirano opravilo dial(). V datoteki rtos_tasks.c normalna opravila naštejemo v seznamu opravil sch_tab[], na privilegirano opravilo pa kaže kazalec priv_task. Pri tem seveda ne smemo pozabiti na deklaracije extern v datoteki rtos_tasks.h.

Opravilo dial() iz cikličnega medpomnilnika prebere število n, nakar prične z generiranjem ustreznega števila impulzov. Vsak impulz skupaj s presledkom traja 100ms, oziroma pet časovnih rezin. Če generiramo vlak n-ih impulzov in prištejemo še eno-sekundno pavzo med dvema vlakoma, potem vse skupaj traja 100ms * n + 1s, oziroma 5 * n + 50 časovnih rezin. Ker je opravilo na vrsti v vsaki časovni rezini, mora biti napisano tako, da si zapomni trenutek v katerem se trenutno nahaja. V našem primeru to najlažje dosežemo s štetjem časovnih rezin, ali z drugimi besedami opravilo samo meri čas. Na ta način pridobimo informacijo o tem, kaj je potrebno v določeni časovni rezini narediti. Algoritem opravila dial() podaja slika spodaj.

Spremenljivka slice_no podaja število časovnih rezin, ki so, skupaj s presledkom do naslednjega vlaka, še preostale do konca trenutnega vlaka impulzov. Ob vsakem klicu opravila se slice_no zmanjša za ena. Zadnjih 50 časovnih rezin predstavlja eno-sekundno pavzo med dvema vlakoma impulzov. Zato se preklopi izvajajo le, dokler je slice_no > 50. Ko je slice_no = 0, je vlak s presledkom vred zaključen. Iz medpomnilnika preberemo naslednje število.

Izvorno kodo opravil napišite v datoteki main.c.

Ker smo vse postorili s pomočjo opisanih opravil, je glavni program le prazna neskončna zanka.