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

Ura realnega časa in zapis v pomnilnik flash

Besedilo vaje: Izdelajte program, ki bo na prikazovalniku LCD v prvi vrstici prikazoval trenuten čas (ure:minute:sekunde), v drugi vrstici pa čas zadnjega pritiska tipke T0. V primeru izklopa napajanja mikrokrmilniškega sistema naj se informacije ohranijo. To pomeni, da ob izklopu sistem ne izgubi podatka o zadnjem pritisku tipke T0, ter da trenuten čas medtem nemoteno teče dalje. Tipke T1, T2 in T3 uporabite za nastavljanje trenutnega časa.

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). Dodani sta datoteki rtc.c in rtc.h za delo z uro realnega časa, ter datoteki iap.c in iap.h za delo s pomnilnikom flash. V prvih dveh datotekah se nahaja koda za inicializacijo, branje in nastavitev ure realnega časa, v drugih dveh pa koda, ki omogoča pisanje v pomnilnik flash. 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).

Trenutni čas merimo s pomočjo ure realnega časa, ki je prirejena človeški predstavi. Ura realnega časa meri na sekundo natančno. To je pravzaprav poseben časovnik, ki je mikrokrmilniku dodan z namenom, da ura nemoteno teče tudi kadar je mikrokrmilniški sistem izklopljen. Zato sistem, medtem ko ni v uporabi, ne pozabi ure oziroma datuma. Da je temu res tako, moramo zagotoviti samostojno baterijsko napajanje, ki je ves čas prisotno, in napaja le eno periferno enoto, to je uro realnega časa.

Ura realnega časa začne teči, ko jo omogočimo s klicem inicializacijske funkcije rtc_init(), ki je že napisana v datoteki rtc.c. Funkcijo rtc_init() kličemo iz funkcije start_up(), kjer so zbrane vse inicializacije. Za njeno uporabo morate vključiti datoteko rtc.h. Deklaracija funkcije rtc_init() je naslednja:

  void rtc_init();

Funkcija rtc_init() je brez argumentov. Klic funkcije omogoči uro realnega časa, onemogoči prekinitve, ki bi jih zahtevala ta periferna enota, in preveri doslednost datuma. Slednje pomeni preverjanje števila dni v mesecu (primer: datum 31.4. ne obstaja), dneva v tednu (primer: 1.1.2000 je bila sobota) ipd. Če ura realnega časa ob klicu funkcije rtc_init() že teče, potem se preveri le doslednost datuma, ter onemogočijo prekinitve. To pomeni, da lahko funkcijo rtc_init() kličemo večkrat, tudi takrat, ko ura realnega časa že normalno teče. Zato ob vklopu mikrokrmilniškega sistema uro realnega časa vedno inicializiramo. Če ura še ne teče, bo začela. V nasprotnem primeru je ponovna inicializacija ne zmoti.

Za nastavitev ure realnega časa imamo na voljo funkciji set_time() in set_date(), ki se prav tako nahajata v datoteki rtc.c, za njuno uporabo pa moramo vključiti datoteko rtc.h. Deklaraciji funkcij sta naslednji:

  void set_time(int hour, int min, int sec);
  void set_date(int day, int month, int year);

Argumenti funkcije set_time() podajajo nastavitev časa:

   - hour ... ure (možne vrednosti med 0 in 23),
- min ... minute (možne vrednosti med 0 in 59) in
- sec ... sekunde (možne vrednosti med 0 in 59).

Argumenti funkcije set_date() pa podajajo nastavitev datuma:

   - day ... dan v mesecu (možne vrednosti med 1 in 28, 29, 30 oziroma 31, odvisno od meseca in leta),
- month ... mesec (možne vrednosti med 1 in 12) in
- year ... leto (možne vrednosti med 1901 in 2099).

Ker v naši vaji s tipkami T1, T2 in T3 nastavljamo le trenuten čas, ne pa tudi datuma, bomo potrebovali le funkcijo set_time().

Za odčitavanje ure realnega časa uporabimo funkcijo get_time_and_date(). Funkcija vrne vse podatke o času in datumu, ki so na voljo. Nahaja se v datoteki rtc.c, za njeno uporabo je potrebno vključiti datoteko rtc.h. Deklaracija funkcije get_time_and_date() je naslednja:

  void get_time_and_date(int *hour, int *min, int *sec, int *day_of_week, int *day_of_year,
          int *day, int *month, int *year);

Vsi argumenti funkcije get_time_and_date() so kazalci na cela števila, kamor funkcija zapiše informacije o trenutnem času in datumu. In sicer:

   - hour ... kazalec na ure,
- min ... kazalec na minute,
- sec ... kazalec na sekunde,
- day_of_week ... kazalec na dan v tednu (0 -> ponedeljek, 1 -> torek, ... 6 -> nedelja),
- day_of_year ... kazalec na dan v letu,
- day ... kazalec na dan v mesecu,
- month ... kazalec na mesec in
- year ... kazalec na leto.

Za odčitavanje časa in datuma je dana le ena funkcija. Razlog je v doslednosti odčitanih podatkov. Primer: če bi čas in datum odčitali vsakega posebej, in bi to počeli ob polnoči, tako da bi polnoč nastopila ravno med obema odčitkoma, potem čas nebi ustrezal datumu in obratno.

Ob vsakem pritisku tipke T0 si moramo zapomniti čas pritiska, ki je nato prikazan v drugi vrstici prikazovalnika LCD. Da informacije o času zadnjega pritiska tipke T0 ob izklopu mikrokrmilniškega sistema ne izgubimo, jo shranimo v pomnilnik flash. Pri tem moramo paziti, da ne prekrijemo programske kode, ki se prav tako nahaja tam in vedno začenja na naslovu 0x00000000. Zato je koristno vedeti do kam sega programska koda skupaj s konstantnimi podatki, oziroma od kje naprej lahko pomnilnik flash uporabljamo za shranjevanje svojih podatkov. V ta namen imamo na voljo funkcijo end_of_code(), ki se nahaja v datoteki iap.c. Za njeno uporabo moramo vključiti datoteko iap.h. Deklaracija funkcije end_of_code() je naslednja:

  int *end_of_code();

Funkcija nima argumentov in vrne naslov prvega prostega mesta v pomnilniku flash, ki ni zaseden s programsko kodo ali konstantnimi podatki. Od vključno tega naslova dalje, je pomnilnik flash nezaseden in zato na voljo za uporabo.

Pomnilnik flash je razdeljen na 27 sektorjev, od katerih je vsak velik 4kB ali 32kB. Prvi sektor z indeksom 0 se razteza od naslova 0x00000000 do 0x00000fff in je velik 4kB, drugi sektor z indeksom 1 se razteza od naslova 0x00001000 do 0x00001fff, prav tako 4kB itd. do sektorja z indeksom 26, ki se razteza od naslova 0x0007c000 do 0x0007cfff, 4kB. Indeks sektorja, kjer se nahaja celica z naslovom v pomnilniku flash, dobimo s klicem funkcije find_sector(). Funkcija se nahaja v datoteki iap.c, za njeno uporabo moramo vključiti datoteko iap.h. Deklaracija funkcije find_sector() je naslednja:

  int find_sector(int *address, int **sector, int *length);

Naslov celice, za katero želimo ugotoviti v katerem sektorju je, podamo kot prvi argument. Sledita kazalca kamor funkcija zapiše še informacijo o naslovu prve celice v tem sektorju in dolžini sektorja. Argumenti funkcije find_sector() so tako naslednji:

   - address ... naslov celice, za katero želimo ugotoviti v katerem sektorju se nahaja,
- sector ... kazalec na naslov prve celice v izbranem sektorju in
- length ... kazalec na dolžino izbranega sektorja.

Ob pisanju v pomnilnik flash lahko pišemo le po izbrisanih, to je postavljenih bitih. Celica v pomnilniku flash je izbrisana, kadar ima vrednost 0xffffffff. Posamezne bite ob pisanju postavljamo na nič, obratno ni mogoče. Če hočemo na nepostavljen bit zopet zapisati enico, je potrebno celotno celico najprej izbrisati, oziroma postaviti v začetno stanje z vrednostjo 0xffffffff. Zaradi tehnologije izdelave pomnilnika flash lahko brišemo le cel sektor, ali več sektorjev naenkrat. Posamezne celice ni mogoče izbrisati ločeno. Za brisanje pomnilnika flash imamo na voljo funkcijo erase_sectors(), ki se zopet nahaja v datoteki iap.c, za njeno uporabo pa moramo vključiti datoteko iap.h. Deklaracija funkcije erase_sectors() je naslednja:

  int erase_sectors(int first, int last);

Funkcija vrne kodo, ki pove ali je brisanje uspelo. Argumenta funkcije erase_sectors() podajata sektorje, ki jih želimo izbrisati:

   - first ... indeks prvega sektorja, ki ga želimo izbrisati in
- last ... indeks zadnjega sektorja, ki ga želimo izbrisati; če sta indeksa first in last enaka, izbrišemo le en sektor.

Preostane nam še pisanje v pomnilnik flash. V ta namen imamo v datoteki iap.c na voljo funkcijo write_flash(). Za njeno uporabo moramo seveda vključiti še datoteko iap.h. Funkcija podatke, ki jih pripravimo v pomnilniku RAM, zapiše v pomnilnik flash. Zaradi tehnologije izdelave pomnilnika flash lahko zapisujemo le po 256B, 512B, 1kB ali 4kB hkrati in sicer od na 256B poravnanega naslova dalje. Deklaracija funkcije write_flash() je naslednja:

  int write_flash(int *to, int *from, int length);

Funkcija vrne kodo, ki pove ali je pisanje uspelo. Argumenti funkcije write_flash() podajo ponor, izvor in količino podatkov za zapis:

   - to ... na 256B poravnan naslov v pomnilniku flash (zadnji bajt naslova je 0x00), kamor bomo podatke zapisali,
- from ... na 32 bitov poravnan naslov, kjer se nahajajo podatki za zapis in
- length ... količina podatkov, ki jih želimo zapisati (možne vrednosti so: 256, 512, 1024 in 4096).

Z uporabo zgoraj opisanih funkcij postane izvedba vaje razmeroma preprosta. V algoritmu vaje, ki ga prikazuje slika spodaj, posebno pozornost posvetimo temu, da vsak pritisk katerekoli tipke povzroči le en dogodek, ne glede na dolžino pritiska.

Po inicializaciji mikrokrmilnika in ure realnega časa ugotovimo dolžino programske kode, oziroma kateri sektor v pomnilniku flash je prvi nezaseden, kamor zapisujemo podatke o zadnjem pritisku tipke T0. Vsak nov pritisk katerekoli izmed tipk sproži ustrezno reakcijo. Stanje tipk odčitavamo s klicem funkcije get_keys(), ki smo jo spoznali v drugi vaji (inicializacija mikrokrmilnika). Za prikaz na prikazovalniku LCD uporabimo gonilnik lcd_driver_1(), ki smo ga spoznali že v prvi vaji (samostojni zagon). Gonilnik je priložen v datoteki main.c. Ob vsakem klicu na prikazovalnik LCD izpiše vsebino niza, na katerega kaže globalni kazalec lcd_string.


Poskusite poleg časa prikazovati, nastavljati in zapisovati tudi trenutni datum, oziroma datum zadnjega pritiska tipke T0. Zaradi omejenega prostora na prikazovalniku LCD, kakor tudi zaradi omejenega števila tipk, morate načrtovati primeren uporabniški vmesnik, ki bo dodatno funkcionalnost omogočal.