Hodiny RTC s kalendárom pomocou PCF8583: Rozdiel medzi revíziami
Zo stránky SensorWiki
Bez shrnutí editace |
Bez shrnutí editace |
||
(63 medziľahlých úprav od rovnakého používateľa nie je zobrazených.) | |||
Riadok 5: | Riadok 5: | ||
|} | |} | ||
== Zadanie == | == '''Zadanie''' == | ||
* Popíšte ako funguje samotný senzor, ako sa pripojí k mikropočítaču. | * Popíšte ako funguje samotný senzor, ako sa pripojí k mikropočítaču. | ||
Riadok 14: | Riadok 14: | ||
[[Obrázok:cipPCF8583.jpg|300px|center]] | [[Obrázok:cipPCF8583.jpg|300px|center]] | ||
== '''Literatúra:''' == | |||
* | * Zoznam použitej literatúry, vrátane katalógových údajov (datasheet), internetových odkazov a pod.: | ||
=== Odkazy produktu: === | |||
* [http://www.nxp.com/products/interface_and_connectivity/i2c/i2c_real_time_clocks_rtc/series/PCF8583.html Product page] | |||
* [http://ap.urpi.fei.stuba.sk/mmp/doc/prog_i2c.pdf Programming the i2c interface] | |||
=== Ďalšie užitočné odkazy: === | |||
* [http://www.nxp.com/documents/data_sheet/PCF8583.pdf Datasheet] | |||
* [http://ap.urpi.fei.stuba.sk/sensorwiki/index.php/Acrob Stránka vývojovej dosky Acrob] | |||
* [http://ap.urpi.fei.stuba.sk/sensorwiki/index.php/LCD_displej Použitý LCD display] | |||
__TOC__ | __TOC__ | ||
== Analýza == | == '''Analýza''' == | ||
Ako už bolo spomenuté, našou úlohou je vytvoriť hodiny reálneho času pomocou čipu PCF 8583. PCF 8583 je hodinovo-kalendárový čip. Adresy a dáta sú prenášané sériovo po zbernici i2c. Adresa zabudovaných registrov je inkrementovaná automaticky po každom zápise alebo čítaní jedného bajtu. Adresný pin A0 slúži na naprogramovanie adresy čipu, a dovoľuje nám zapojiť dva rovnaké čipy na tú istú zbernicu bez pridania ďalšieho hardware-u. Ako mikroprocesor sme použili Atmegu 328P, ktorá je nasadená na vývojovej doske Acrob. | |||
[[Obrázok:AcrobBoard.png|600px|center]] | |||
=== Kľúčové vlastnosti čipu PCF8583: === | |||
* napájacie napätie i2c zbernice 2,5 V až 6 V | |||
* 240 x 8 bitová nízko-napäťová pamäť | |||
* operačný prúd (pri f(scl)=0 Hz) maximálne 50 uA | |||
* funkcia hodín so štvorročným kalendárom | |||
* časovač s alarmom, indikácia pretečenia | |||
* 12 alebo 24 hodinový formát času | |||
* potreba 32,768 kHz kryštálu | |||
* dvojvodičová zbernica i2c | |||
* automatická inkrementácia adresy po zápise/čítaní | |||
* programovateľný alarm, časovač a prerušovacie funkcie | |||
* adresa slave-u A1h alebo A3h pre čítanie, A0h alebo A2h pre zápis (podľa stavu pinu A0)+ | |||
=== Rozloženie pinov === | |||
[[Obrázok:pinypcf8583.jpg|300px|center]] | |||
<table frame="border" rules = "all"> | |||
=== Popis pinov PCF 8583 === | |||
<tr><th>PIN</th><th>Skratka-označenie</th><th>Krátky popis</th><th>I/O</th></tr> | |||
<tr><td>1</td><td>OSCI</td><td>Vstupný pin pre kryštál</td><td>Vstup</td></tr> | |||
<tr><td>2</td><td>OSCO</td><td>Výstupný pin oscilátora</td><td>Výstup</td></tr> | |||
<tr><td>3</td><td>A0</td><td>Pin pre voľbu adresy</td><td>Vstup</td></tr> | |||
<tr><td>4</td><td>Vss</td><td>Napájacie napätie</td><td>Napájanie</td></tr> | |||
<tr><td>5</td><td>SDA</td><td>Linka serial data</td><td>Vstup/výstup</td></tr> | |||
<tr><td>6</td><td>SCL</td><td>Linka serial clock</td><td>Vstup</td></tr> | |||
<tr><td>7</td><td>INT</td><td>Open-drain výstup prerušenia (aktívne LOW)</td><td>Výstup</td></tr> | |||
<tr><td>8</td><td>Vdd</td><td>Napájacie napätie</td><td>Napájanie</td></tr> | |||
</table> | |||
=== Zbernica i2c === | |||
I2c zbernica je skratka ktorá vznikla z názvu IIC zbernica, teda Internal-Integrated-Circuit Bus. Z názvu je hneď jasné, že sa jedná o internú dátovú zbernicu, ktorá slúži na komunikáciu a prenos dát medzi jednotlivými integrovanými obvodmi, väčšinou v rámci jedného zariadenia. Túto zbernicu vyvinula spoločnosť Philips približne pred 30 rokmi a od tej doby prešla niekoľkými vylepšeniami. V dnešnej dobe tento spôsob komunikácie podporuje celá rada integrovaných obvodov nielen firmy Philips. V prvom rade sa jedná o mikropočítače, sériové pamäte, inteligentné LCD obrazovky, a/d a d/a prevodníky, atď. Hlavnou výhodou je, že obojstranná komunikácia prebieha len po dvoch vodičoch SDA data a SCL hodiny, a tým pádom sa zjednoduší výsledné zapojenie celého obvodu. Na jednu zbernicu môžeme pripojiť viac integrovaných obvodov. | |||
'''Spôsob adresovania:''' | |||
* 7 bitové adresovanie - 128 čipov na tej istej zbernici | |||
* 10 bitové adresovanie - 1024 čipov na tej istej zbernici | |||
* na pevno (určené výrobcom čipu) | |||
Prenosová rýchlosť je pre väčšinu aplikácií dostatočná už v základnej verzií, avšak zbernica prešla určitými vylepšeniami a tým pádom máme dnes rôzne prenosové rýchlosti. Rýchlosť prenosu však musí byť prispôsobená najpomalšiemu čipu na zbernici. Oba vodiče (SDA a SCL) musia byť v logickej jednotke, a to je zaistené pomocou pull-up odporov. čím je vyššia komunikačná rýchlosť, tým musia byť pull-up odpory menšie. Pre základnú rýchlosť 100 kHz postačujú odpory 4k7. | |||
'''Rýchlosť zbernice i2c:''' | |||
* základná - 100 kHz | |||
* vylepšená - 400 kHz | |||
* najrýchlejšia - až 1 MHz | |||
'''Príklad zapojenia viacerých čipov na rovnakú zbernicu:''' | |||
[[Obrázok:i2cpriklad.jpg|500px|center]] | |||
=== Princíp prenosu prostredníctvom i2c === | |||
Mikroprocesor sa považuje za MASTER a všetky ostatné obvody sú SLAVE. MASTER pri akomkoľvek prenosu generuje hodinový signál na vodiči SCL. Keď jeden čip vysiela, prijímajú všetky ostatné čipy na zbernici ale údaje spracuje len čip, ktorému boli dáta určené. Čip, ktorý chce dáta vyslať alebo prijať musí vždy najprv zadefinovať adresu čipu s ktorým chce nadviazať komunikáciu, a musí tiež určiť, či chce vysielať alebo čítať. To určuje bit R/W, ktorý je súčasťou adresy. | |||
'''Prenos prebieha kombináciou nasledujúcich celkov:''' | |||
* '''stav kludu''' - Tento stav je zaistený logickými jednotkami na oboch vodičoch, tj. MASTER negeneruje žiadny hodinový signál a neprebieha žiadny prenos. Logické jednotky sú zabezpečené pomocou pull-up odporov. Pull-up odpor je odpor medzi vodičom a napájacím napätím. | |||
* '''start bit''' - Zahajuje prenos, alebo jeho ďalšiu časť. Je vygenerovaný tak, že sa zmení úroveň SDA z 1 na 0, zatiaľčo SCL = 1 | |||
* '''stop bit''' - Ukončuje prenos. Je generovaný podobne ako start bit. Úroveň SDA sa zmení z 0 na 1, zatiaľčo SCL = 1 | |||
* '''prenos dát''' - Všetky dáta sú prenášané po 1 bajt, teda 8 po sebe idúcich bitov od najvyššieho po najnižší. Pri prenose dát sa môže logická úroveň na SDA meniť len keď SCL = 0. | |||
* '''ACK''' - tento bit slúži k potvrdeniu správneho prijatia dát. Odosiela sa rovnakým spôsobom, ako keby sa odosielal deviaty bit dát, ale s tým rozdielom, že ho '''generuje čip ktorý prijímal dáta a nie ten ktorý ho odosielal'''. Pokial prenos prebehol v poriadku, tak odošle logickú 0. Pokiaľ prenos zlyhal odošle sa logická 1. Pokiaľ má dojsť k ukončeniu prenosu, tak sa neodošle ACK. | |||
[[Obrázok:i2cprenos.jpg|500px|center]] | |||
== '''Popis riešenia''' == | |||
Použitý RTC čip je štandartne pripojený k mikroprocesoru Atmega 328P pomocou dvoch vodičov SDA a SCL. V našom prípade však využívame aj dodatočné funkcie čipu PCF8583, a práve preto pripájame k mikroprocesoru aj tretí vodič. Spomenutou funkciou je zabudovaný alarm, ktorý v určitých časových intervaloch (aké si nastaví užívateľ), spustí alarmový stav, tj. niečo sa udeje, napríklad sa spustí siréna, spustí sa určité svetelné výstražné značenie alebo zapne sa motor, atď. Všetky tri vodiče musia byť pripojené na napájacie napätie (+5 V) pomocou pull-up odporov. Tie nám zabezpečia, že v kľudovom stave bude na všetkých troch zapojených pinoch logický stav 1. Taktiež sme využili 20 pinový konektor X1 vývojovej dosky [http://ap.urpi.fei.stuba.sk/sensorwiki/images/2/20/AcrobSchematic18.png Acrob] na pripojenie [http://ap.urpi.fei.stuba.sk/sensorwiki/index.php/LCD_displej LCD displeja]. | |||
=== Schéma zapojenia snímača PCF8583 k vývojovej doske Acrob === | |||
[[Obrázok:zapojpcf8583.jpg|500px|center]] | |||
== '''Algoritmus a program''' == | |||
Algoritmus je napísaný v programovacom prostredí AVR studio 4. Jednotlivé časti kódu budú vysvetlené nižšie. Pre jednoduchšie pochopenie programu nám poslúži vývojový diagram, v ktorom sú veľmi stručne opísané jednotlivé deje, ktoré sa udejú pri chodu programu. Aktuálny čas máme možnosť zobraziť na LCD displeji, alebo použijeme sériovú komunikáciu USART, a dáta vypíšeme napríklad pomocou programu [https://sites.google.com/site/terminalbpp Terminal] | |||
=== Vývojový diagram === | |||
[[Obrázok:vyvojkopcf8583.jpg|500px|center]] | |||
=== Knižnice === | |||
Prvý krokom je zadefinovanie všetkých použitých knižníc. Knižnice slúžia na správnu funkciu a ovládanie všetkých použitých periférnych častí procesora. Niektoré sa použijú pri sériovej komunikácií cez USART, niektoré pri i2c komunikácií alebo pri operáciách s LCD displejom. V našom prípade sme použili 11 knižníc, a sú nasledovné: | |||
<source lang="c"> | |||
#include <inttypes.h> | |||
#include <compat/twi.h> | |||
#include "serial.h" | |||
#include "i2cmaster.h" | |||
#include <avr/interrupt.h> | |||
#include <stdlib.h> | |||
#include <avr/io.h> | |||
#include <util/delay.h> | |||
#include <stdio.h> | |||
#include <string.h> | |||
#include "lcd.h" | |||
</source> | |||
V | === Rýchlosť procesora a SCL === | ||
Ďalším krokom bolo definovať rýchlosť nielen procesora ale aj rýchlosť komunikácie prostredníctvom i2c zbernice. V našom prípade procesor je taktovaný na frekvenciu 16 Mhz, rýchlosť komunikácie zbernice i2c je 100 kHz. Pre danú operáciu táto rýchlosť plne postačuje. | |||
<source lang="c"> | |||
#ifndef F_CPU | |||
#define F_CPU 16000000UL | |||
#endif | |||
* | /* I2C clock in Hz */ | ||
* | #define SCL_CLOCK 100000L | ||
</source> | |||
=== Porty, prerušenie, LCD init === | |||
V main funkcií najprv prebieha inicializácia pripojeného LCD displeja, nastavenie jednotlivých portov a povolenie prerušenia (v prípade, že chceme túto možnosť využiť) | |||
= | <source lang="c"> | ||
lcdInit4(); // inicializácia v 4-bitovom režime | |||
lcdControlWrite(1<<LCD_CLR); // zmazanie displeja | |||
lcdControlWrite(0x40); // nastavenie pozície 0,0, tj. ľavý horný roh | |||
DDRB &= ~(1<<PCINT0); // nastavenie PB0 ako vstup | |||
PORTB |= (1<<PCINT0); // aktivácia pull-up rezistorov | |||
PCMSK0 |= (1 << PCINT0); // aktivácia PCINT0 | |||
PCICR |= (1 << PCIE0); // aktivácia prerušenia na aktivovanom PCINT0 | |||
sei (); // povolí prerušenie | |||
DDRB=0x20; // PB5 - výstup na žltú LED diódu | |||
PORTB = 0x00; // logický stav PORTB = 0, tj. LED nesvieti | |||
</source> | |||
[[ | === Zápis času a dátumu do čipu === | ||
Prvá dôležitá operácia pri práci s čipom PCF8583 je zapísať čas a dátum do čipu a spustiť počítanie (spustiť hodiny). K tomu budeme potrebovať adresu pre čítanie a pre zápis do čipu. Táto adresa však závisí od zapojenia pinu A0. V našom prípade je adresa: | |||
<source lang="c"> | |||
#define adr_w 0xA2 | |||
#define adr_r 0xA3 | |||
</source> | |||
Ďalej budeme potrebovať lokáciu jednotlivých riadiacich registrov v pamäti RAM. Nato nám poslúži nasledujúci obrázok: | |||
[[Obrázok:lokacieram.jpg|200px|center]] | |||
Najdôležitejší register (Ovládací a stavový register) sa nachádza na prvej adrese 00h. Tu nastavujeme hlavné funkcie, ktoré chceme pri našom procese aktívne využívať, tj. či chceme použiť časovač, alarm, aký kryštál je zapojený, tak isto tu nastavujeme zastavenie a spustenie hodín. Najdôležitejšie bity, ktoré sme použili sú opísané na obrázku: | |||
[[Obrázok:00h.jpg|400px|center]] | |||
Najprv použijeme nastavenie 0x80, takto docielime aby hodiny boli zastavené. Pri zápise času a dátumu do čipu musia byť hodiny vždy zastavené. Po zápise času a dátumu do čipu hodiny pustíme prepisom registra 00h z 0x80 na 0x04, čím zapneme počítanie a taktiež zapneme funkciu alarmu. | |||
Kedže čip disponuje funkciou automatickej inkrementácie adresy, po zápise do registra 00h (stavový), ďalší bajt sa zapíše do registra 01h a tak ďalej. Od adresy 01h je uložený čas nasledovne: stotiny - 01h, sekundy - 02h, minúty - 03h, hodiny - 04h, rok - dátum - 05h, deň - mesiac - 06h. To znamená, že keď zapíšeme do registra 00h hodnotu 0x80 (zastavíme hodiny), automaticky prejdeme do registra 01h a môžme nastaviť čas a dátum bez toho aby sme stále zadávali aj adresu registra do ktorého chceme zapisovať. V našom prípade zápis času vyzerá nasledovne: | |||
<source lang="c"> | |||
... | |||
write_time_to_chip(0x23,0x59,0x57,0x68,0x02); //hodiny (23); minúty (59); sekundy (57); rok-dátum (horné dva bity rok, 5-4 bit: deň-desiatky, 3-0 bit: deň do 9) - teda 0x68 = rok 1, datum 28; | |||
//deň-mesiac (horné 3 bity deň, 4 bit: mesiac-desiatky, 3-0 bit: mesiac do 9) - teda 0x02 = deň 0 - pondelok , mesiac 2 - február | |||
void write_time_to_chip(unsigned char hh,unsigned char mm,unsigned char ss,unsigned char yd,unsigned char wm) | |||
{ | |||
//write to rtc | |||
i2c_start_wait(adr_w); //pošle adresu zariadenia | |||
i2c_write(0x00); //pošle lokáciu registra kam chceme zapisovať | |||
i2c_write(0x80); //zastaví hodiny | |||
i2c_write(0x00); //nastavíme 0 na stotiny | |||
i2c_write(ss); //sekundy | |||
i2c_write(mm); //minúty | |||
i2c_write(hh); //hodiny | |||
i2c_write(yd); //rok, dátum | |||
i2c_write(wm); //deň, mesiac | |||
i2c_write(0x00); //časovač - my nepoužívame | |||
i2c_stop(); | |||
_delay_ms(5); | |||
//start counting again: | |||
i2c_start_wait(adr_w); //repeat start pošle adresu zariadenia | |||
i2c_write(0x00); //pošle lokáciu registra kam chceme zapisovať | |||
i2c_write(0x04); //spustí hodiny, alarm zapnutý | |||
i2c_stop(); //i2c stop | |||
} | |||
... | |||
</source> | |||
Nato aby sme správne zapisovali hodnoty do registra kde je hodinový záznam, záznam rok-dátum a deň-mesiac, musíme presne vedieť akú majú funkciu jednotlivé bity daných registrov. Vysvetlenie nájdeme v následujúcom obrázku: | |||
[[Obrázok:hodrokmes.jpg|500px|center]] | |||
=== Nastavenie alarmu === | |||
Alarm sa nastavuje rovnakým spôsobom ako čas a dátum, len s tým rozdielom že pri čase, stavový register sa nachádzal na adrese 00h, kým stavový register alarmu sa nachádza na adrese 08h. Ako presne nastaviť register 08h nájdeme na nasledujúcom obrázku: | |||
[[Obrázok:alarmsetting.jpg|500px|center]] | |||
V registry 08h nastavíme jednotlivé bity na 0x90. Dolné 4 bity sú nulové, keďže pri našom zadaní nepoužívame funkciu časovača, horné 4 bity majú logickú hodnotu 1001. Logická jednotka na 7. bite nastavuje aby bolo prerušenie aktívne pri alarmoch, 6. bit vypína alarm časovača, logické úrovne bitov 5,4 (v našom prípade 01) nastavujú daily alarm - teda denný alarm. Denný alarm pracuje tak, že každý deň v tom istom čase dôjde k prerušeniu. | |||
<source lang="c"> | <source lang="c"> | ||
/ | i2c_start_wait(adr_w); //i2c štart, pošle adresu zariadenia | ||
i2c_write(0x08); //nastaví lokáciu registra kam chceme zapisovať | |||
i2c_write(0x90); //do 08h zapíšeme 0x90, prečo práve 0x90 je vysvetlené vyššie | |||
i2c_write(0x00); //prebieha autoinkrementácia adresy registrov, 0x00 zapisujeme už do registra pre stotiny | |||
i2c_write(0x05); //Alarm sekundy | |||
i2c_write(0x00); //Alarm minúty | |||
i2c_write(0x00); //Alarm hodiny | |||
i2c_write(0x00); //Alarm rok, dátum nemá žiadny efekt keď používame denný alarm | |||
i2c_write(0x00); //Alarm deň, mesiac nemá žiadny efekt keď používame denný alarm | |||
i2c_stop(); //koniec komunikácie i2c | |||
</source> | |||
=== Vyčítanie aktuálneho času a dátumu z čipu === | |||
Vyčítanie prebieha rovnakým spôsobom ako zápis. To znamená, že čítame dáta z rovnakých registrov, ako do ktorých sme na začiatku zapisovali (pravdaže medzitým sa čas zmenil, takže vyčítame iné hodnoty a nie na začiatku nami zadané). Vyčítanie vyzerá nasledovne: | |||
<source lang="c"> | |||
... | |||
//read from rtc | |||
i2c_start_wait(adr_w); //i2c start, pošle adresu zariadenia | |||
i2c_write(0x01); //zapíšeme od ktorého registra chceme čítať, stavový register 00h nie je potrebné pre nás vyčítať | |||
i2c_rep_start(adr_r); //repeat štart,zo zápisu prepneme na čítanie | |||
stotina=i2c_readAck(); //vyčítame stotiny a uložíme do premennej (ďalšiu adresu neurčíme kedže aj tu funguje automatická inkrementácia) | |||
sec=i2c_readAck(); //vyčítame sekundy a uložíme do premennej | |||
min=i2c_readAck(); //vyčítame minúty a uložíme do premennej | |||
hour=i2c_readAck(); //vyčítame hodiny a uložíme do premennej | |||
Year_Date=i2c_readAck(); //vyčítame rok a dátum a uložíme do premennej | |||
Weekday_Month=i2c_readNak(); //vyčítame deň a mesiac a uložíme do premennej | |||
i2c_stop(); //ukončíme komunikáciu | |||
... | |||
</source> | |||
== '''Spracovanie vyčítaných dát''' == | |||
=== Maskovanie === | |||
V premenných Year_Date a Weekday_Month sa nachádzajú viaceré potrebné dáta. Práve preto musíme tieto premenné maskovať, tj. oddeliť od seba rok a dátum a tak isto oddeliť od seba deň a mesiac. Musíme získať z dvoch premenných ako keby štyri premenné. Spravíme to tak, že vynásobíme bity reprezentujúce rok a bity reprezentujúce deň nulou. Tým získame premenné, v ktorých bude len dátum a mesiac. | |||
<source lang="c"> | |||
//maskovanie: | |||
mesiac=Weekday_Month & 0x1F; //vynásob horné tri bity premennej Weekday_Month nulou a výsledok ulož do premennej mesiac | |||
denmes=Year_Date & 0x3F; //vynásob horné dva bity premennej Year_Date nulou a výsledok ulož do premennej denmes | |||
</source> | |||
=== Konvertovanie z BCD na ASCII === | |||
Všetky vyčítané dáta sú uložené ako číselná hodnota v BCD formáte. Tento formát pravdaže musíme konvertovať na ASCII kód. Ako prekonvertovať BCD na ASCII nájdeme napríklad [http://www.avr-asm-tutorial.net/avr_en/beginner/CALC.html TU] v sekcií Numbers in ASCII-format. Z popisu je jasné, že treba pripočítať číslo 48 k BCD číslu. Ďalej si treba uvedomiť, že pri výpise na LCD nevieme posielať reťazec naraz ako cez Terminál, tj. treba čísla rozdeliť na desatinnú a jednotkovú časť. Keď máme napríklad 14 hodín zvlášť pošleme 1 a zvlášť 4 na displej. Ako uložiť jednotlivé čísla zvlášť do premenných a ako ich prekonvertovať na ASCII vidíme nižšie: | |||
<source lang="c"> | |||
//BCD to ASCII: | |||
hodina1=(hour >> 4)+48; //posun 4 horné bity do prava o 4 a pridaj 48, ulož do premennej hodina1, ak v hour bolo napríklad 23 v BCD, v hodina1 bude 2 v ASCII | |||
hodina2=(hour & 0x0F)+48; //vynásob horné 4 bity nulou a pridaj 48, ulož do premennej hodina2, ak v hour bolo napríklad 23 v BCD, v hodina2 bude 3 v ASCII | |||
minuta1=(min >> 4)+48; | |||
minuta2=(min & 0x0F)+48; | |||
sekunda1=(sec >> 4)+48; | |||
sekunda2=(sec & 0x0F)+48; | |||
mesiac1=(mesiac >> 4)+48; | |||
mesiac2=(mesiac & 0x0F)+48; | |||
denmes1=(denmes >> 4)+48; | |||
denmes2=(denmes & 0x0F)+48; | |||
</source> | |||
=== Výpis cez Terminál === | |||
<source lang="c"> | |||
printf(" %c%c:%c%c:%c%c %s/%c%c/%c%c %s ",hodina1,hodina2,minuta1,minuta2,sekunda1,sekunda2,rok[b],mesiac1,mesiac2,denmes1,denmes2,dentyz[a]); | |||
</source> | |||
=== Výpis cez LCD === | |||
<source lang="c"> | |||
lcdDataWrite(hodina1); //vypíše na 0,0 hodnotu premennej hodina1 | |||
lcdDataWrite(hodina2); //vypíše na 0,1 hodnotu premennej hodina2 | |||
if(sec%2==0) lcdDataWrite(':'); //ak je sec párne vypíše dvojbodku na 0,2 (tým sa zabezpečí blikanie dvojbodky každú sekundu) | |||
else lcdDataWrite(32); //space //ak je sec nepárne vypíše medzeru na 0,2 (tým sa zabezpečí blikanie dvojbodky každú sekundu) | |||
lcdDataWrite(minuta1); //vypíše na 0,3 hodnotu premennej minuta1 | |||
lcdDataWrite(minuta2); //vypíše na 0,4 hodnotu premennej minuta2 | |||
if(sec%2==0) lcdDataWrite(':'); //ak je sec párne vypíše dvojbodku na 0,5 (tým sa zabezpečí blikanie dvojbodky každú sekundu) | |||
else lcdDataWrite(32); //space //ak je sec nepárne vypíše medzeru na 0,5 (tým sa zabezpečí blikanie dvojbodky každú sekundu) | |||
lcdDataWrite(sekunda1); //vypíše na 0,6 hodnotu premennej sekunda1 | |||
lcdDataWrite(sekunda2); //vypíše na 0,7 hodnotu premennej sekunda2 | |||
lcdControlWrite(0x40+0x80); //pozicia 1,0 - druhý riadok | |||
lcdDataWrite(rokk1); //vypíše na 1,0 hodnotu premennej rokk1 | |||
lcdDataWrite(rokk2); //vypíše na 1,1 hodnotu premennej rokk2 | |||
lcdDataWrite('/'); //vypíše na 1,2 '/' | |||
lcdDataWrite(mesiac1); //vypíše na 1,3 hodnotu premennej mesiac1 | |||
lcdDataWrite(mesiac2); //vypíše na 1,4 hodnotu premennej mesiac2 | |||
lcdDataWrite('/'); //vypíše na 1,5 '/' | |||
lcdDataWrite(denmes1); //vypíše na 1,6 hodnotu premennej denmes1 | |||
lcdDataWrite(denmes2); //vypíše na 1,7 hodnotu premennej denmes2 | |||
</source> | |||
=== Prerušenie === | |||
Pri dosiahnutí času, ktorý je nastavený v alarme od adresy 09h, dochádza k prerušeniu programu a zapne sa žltá LED dióda na vývojovej doske Acrob. Aby efekt trval dlhšie, program je napísaný tak, že po ukončení prerušenia LED ostane svietiť ešte ďalších 30 cyklov programu. | |||
<source lang="c"> | |||
ISR (PCINT0_vect) //funkcia prerušenia PCINT0 | |||
{ | |||
c=0; | |||
PORTB=PORTB | 0x20; //zapne sa LED dióda | |||
i2c_start_wait(adr_w); //vymaže sa príznak prerušenia, aby program mohol bežať ďalej | |||
i2c_write(0x00);//adress byte | |||
i2c_write(0x04);//control register | |||
i2c_stop(); | |||
} | } | ||
</source> | </source> | ||
== '''Celý kód''' == | |||
<source lang="c"> | |||
#include <inttypes.h> | |||
#include <compat/twi.h> | |||
#include "serial.h" | |||
#include "i2cmaster.h" | |||
#include <avr/interrupt.h> | |||
#include <stdlib.h> | |||
#include <avr/io.h> | |||
#include <util/delay.h> | |||
#include <stdio.h> | |||
#include <string.h> | |||
#include "lcd.h" | |||
//FILE mystdout = FDEV_SETUP_STREAM(sendchar, NULL, _FDEV_SETUP_WRITE); | |||
/* define CPU frequency in Mhz here if not defined in Makefile */ | |||
#ifndef F_CPU | |||
#define F_CPU 16000000UL | |||
#endif | |||
Zdrojový kód: [[Médiá: | /* I2C clock in Hz */ | ||
#define SCL_CLOCK 100000L | |||
#define adr_w 0xA2 | |||
#define adr_r 0xA3 | |||
#define comread 0x0B | |||
void write_time_to_chip(unsigned char hh,unsigned char mm,unsigned char ss,unsigned char yd,unsigned char wm) | |||
{ | |||
//write to rtc | |||
i2c_start_wait(adr_w); //pošle adresu zariadenia | |||
i2c_write(0x00); //pošle lokáciu registra kam chceme zapisovať | |||
i2c_write(0x80); //zastaví hodiny | |||
i2c_write(0x00); //nastavíme 0 na stotiny | |||
i2c_write(ss); //sekundy | |||
i2c_write(mm); //minúty | |||
i2c_write(hh); //hodiny | |||
i2c_write(yd); //rok, dátum | |||
i2c_write(wm); //deň, mesiac | |||
i2c_write(0x00); //časovač - my nepoužívame | |||
i2c_stop(); | |||
_delay_ms(5); | |||
//start counting again: | |||
i2c_start_wait(adr_w); //repeat start pošle adresu zariadenia | |||
i2c_write(0x00); //pošle lokáciu registra kam chceme zapisovať | |||
i2c_write(0x04); //spustí hodiny, alarm zapnutý | |||
i2c_stop(); //i2c stop | |||
} | |||
int c=50; | |||
int main(void) | |||
{ | |||
lcdInit4(); // inicializácia v 4-bitovom režime | |||
lcdControlWrite(1<<LCD_CLR); // zmazanie displeja | |||
lcdControlWrite(0x40); // nastavenie pozície 0,0, tj. ľavý horný roh | |||
DDRB &= ~(1<<PCINT0); // nastavenie PB0 ako vstup | |||
PORTB |= (1<<PCINT0); // aktivácia pull-up rezistorov | |||
PCMSK0 |= (1 << PCINT0); // aktivácia PCINT0 | |||
PCICR |= (1 << PCIE0); // aktivácia prerušenia na aktivovanom PCINT0 | |||
sei (); // povolí prerušenie | |||
DDRB=0x20; // PB5 - výstup na žltú LED diódu | |||
PORTB = 0x00; // logický stav PORTB = 0, tj. LED nesvieti | |||
//unsigned char stat_reg; | |||
unsigned char stotina; | |||
unsigned char sec; | |||
unsigned char min; | |||
unsigned char hour; | |||
unsigned char Year_Date; | |||
unsigned char Weekday_Month; | |||
unsigned char mesiac; | |||
unsigned char denmes; | |||
//char *dentyz[] = {"Po","Ut","Str","Stv","Pi","So","Ne"}; //pre terminal | |||
//int a=0; | |||
//char *rok[] = {"2014","2015","2016","2017"}; //pre terminal | |||
//int b=0; | |||
unsigned char rokk1; | |||
unsigned char rokk2; | |||
unsigned char hodina1; | |||
unsigned char hodina2; | |||
unsigned char minuta1; | |||
unsigned char minuta2; | |||
unsigned char sekunda1; | |||
unsigned char sekunda2; | |||
unsigned char mesiac1; | |||
unsigned char mesiac2; | |||
unsigned char denmes1; | |||
unsigned char denmes2; | |||
//inituart(); | |||
// stdout = &mystdout; | |||
i2c_init(); | |||
write_time_to_chip(0x23,0x59,0x57,0x68,0x02); //hh,mm,ss,yd - (7.6.bit year, 5.-0.bit day in bcd format) | |||
//wm - (7.-5. bit weekdays, 4.-0. bit month in bcd format) | |||
//ALARM SET: | |||
i2c_start_wait(adr_w); //i2c štart, pošle adresu zariadenia | |||
i2c_write(0x08); //nastaví lokáciu registra kam chceme zapisovať | |||
i2c_write(0x90); //do 08h zapíšeme 0x90, prečo práve 0x90 je vysvetlené vyššie | |||
i2c_write(0x00); //prebieha autoinkrementácia adresy registrov, 0x00 zapisujeme už do registra pre stotiny | |||
i2c_write(0x05); //Alarm sekundy | |||
i2c_write(0x00); //Alarm minúty | |||
i2c_write(0x00); //Alarm hodiny | |||
i2c_write(0x00); //Alarm rok, dátum nemá žiadny efekt keď používame denný alarm | |||
i2c_write(0x00); //Alarm deň, mesiac nemá žiadny efekt keď používame denný alarm | |||
i2c_stop(); //koniec komunikácie i2c | |||
while(1) | |||
{ | |||
//read from rtc | |||
i2c_start_wait(adr_w); //i2c start, pošle adresu zariadenia | |||
i2c_write(0x01); //zapíšeme od ktorého registra chceme čítať, stavový register 00h nie je potrebné pre nás vyčítať | |||
i2c_rep_start(adr_r); //repeat štart,zo zápisu prepneme na čítanie | |||
//stat_reg=i2c_readAck(); | |||
stotina=i2c_readAck(); //vyčítame stotiny a uložíme do premennej (ďalšiu adresu neurčíme kedže aj tu funguje automatická inkrementácia) | |||
sec=i2c_readAck(); //vyčítame sekundy a uložíme do premennej | |||
min=i2c_readAck(); //vyčítame minúty a uložíme do premennej | |||
hour=i2c_readAck(); //vyčítame hodiny a uložíme do premennej | |||
Year_Date=i2c_readAck(); //vyčítame rok a dátum a uložíme do premennej | |||
Weekday_Month=i2c_readNak(); //vyčítame deň a mesiac a uložíme do premennej | |||
i2c_stop(); //ukončíme komunikáciu | |||
//maskovanie: | |||
mesiac=Weekday_Month & 0x1F; //vynásob horné tri bity premennej Weekday_Month nulou a výsledok ulož do premennej mesiac | |||
denmes=Year_Date & 0x3F; //vynásob horné dva bity premennej Year_Date nulou a výsledok ulož do premennej denmes | |||
//CONVERSIONS FROM BCD TO ASCII: | |||
/*switch((Weekday_Month & 0xE0)>>5 ) //maskovanie, potom posun do prava o 5 | |||
{ | |||
case 0x00: | |||
a=0;break; | |||
case 0x01: | |||
a=1;break; | |||
case 0x02: | |||
a=2;break; | |||
case 0x03: // vypis cez terminal | |||
a=3;break; | |||
case 0x04: | |||
a=4;break; | |||
case 0x05: | |||
a=5;break; | |||
case 0x06: | |||
a=6;break; | |||
} | |||
switch((Year_Date & 0xC0)>>6 ) //maskovanie, potom posun do prava o 6 | |||
{ | |||
case 0x00: | |||
b=0;break; | |||
case 0x01: | |||
b=1;break; // vypis cez terminal | |||
case 0x02: | |||
b=2;break; | |||
case 0x03: | |||
b=3;break; | |||
} | |||
*/ | |||
switch((Year_Date & 0xC0)>>6 ) //maskovanie, potom posun do prava o 6 | |||
{ | |||
case 0x00: | |||
rokk1=1+48;rokk2=4+48;break; //rok bcd na ascii | |||
case 0x01: | |||
rokk1=1+48;rokk2=5+48;break; | |||
case 0x02: | |||
rokk1=1+48;rokk2=6+48;break; | |||
case 0x03: | |||
rokk1=1+48;rokk2=7+48;break; | |||
} | |||
//BCD to ASCII: | |||
hodina1=(hour >> 4)+48; //posun 4 horné bity do prava o 4 a pridaj 48, ulož do premennej hodina1, ak v hour bolo napríklad 23 v BCD, v hodina1 bude 2 v ASCII | |||
hodina2=(hour & 0x0F)+48; //vynásob horné 4 bity nulou a pridaj 48, ulož do premennej hodina2, ak v hour bolo napríklad 23 v BCD, v hodina2 bude 3 v ASCII | |||
minuta1=(min >> 4)+48; | |||
minuta2=(min & 0x0F)+48; | |||
sekunda1=(sec >> 4)+48; | |||
sekunda2=(sec & 0x0F)+48; | |||
mesiac1=(mesiac >> 4)+48; | |||
mesiac2=(mesiac & 0x0F)+48; | |||
denmes1=(denmes >> 4)+48; | |||
denmes2=(denmes & 0x0F)+48; | |||
//printf(" %c%c:%c%c:%c%c %s/%c%c/%c%c %s ",hodina1,hodina2,minuta1,minuta2, | |||
//sekunda1,sekunda2,rok[b],mesiac1,mesiac2,denmes1,denmes2,dentyz[a]); | |||
lcdDataWrite(hodina1); //vypíše na 0,0 hodnotu premennej hodina1 | |||
lcdDataWrite(hodina2); //vypíše na 0,1 hodnotu premennej hodina2 | |||
if(sec%2==0) lcdDataWrite(':'); //ak je sec párne vypíše dvojbodku na 0,2 (tým sa zabezpečí blikanie dvojbodky každú sekundu) | |||
else lcdDataWrite(32); //space //ak je sec nepárne vypíše medzeru na 0,2 (tým sa zabezpečí blikanie dvojbodky každú sekundu) | |||
lcdDataWrite(minuta1); //vypíše na 0,3 hodnotu premennej minuta1 | |||
lcdDataWrite(minuta2); //vypíše na 0,4 hodnotu premennej minuta2 | |||
if(sec%2==0) lcdDataWrite(':'); //ak je sec párne vypíše dvojbodku na 0,5 (tým sa zabezpečí blikanie dvojbodky každú sekundu) | |||
else lcdDataWrite(32); //space //ak je sec nepárne vypíše medzeru na 0,5 (tým sa zabezpečí blikanie dvojbodky každú sekundu) | |||
lcdDataWrite(sekunda1); //vypíše na 0,6 hodnotu premennej sekunda1 | |||
lcdDataWrite(sekunda2); //vypíše na 0,7 hodnotu premennej sekunda2 | |||
lcdControlWrite(0x40+0x80); //pozicia 1,0 - druhý riadok | |||
lcdDataWrite(rokk1); //vypíše na 1,0 hodnotu premennej rokk1 | |||
lcdDataWrite(rokk2); //vypíše na 1,1 hodnotu premennej rokk2 | |||
lcdDataWrite('/'); //vypíše na 1,2 '/' | |||
lcdDataWrite(mesiac1); //vypíše na 1,3 hodnotu premennej mesiac1 | |||
lcdDataWrite(mesiac2); //vypíše na 1,4 hodnotu premennej mesiac2 | |||
lcdDataWrite('/'); //vypíše na 1,5 '/' | |||
lcdDataWrite(denmes1); //vypíše na 1,6 hodnotu premennej denmes1 | |||
lcdDataWrite(denmes2); //vypíše na 1,7 hodnotu premennej denmes2 | |||
_delay_ms(100); | |||
lcdControlWrite(1<<LCD_CLR); | |||
lcdControlWrite(0x40); // pozicia 0,0 | |||
c++; | |||
if(c==30) PORTB=PORTB & 0x00; //Alarm OFF | |||
} | |||
} | |||
ISR (PCINT0_vect) //funkcia prerušenia PCINT0 | |||
{ | |||
c=0; | |||
PORTB=PORTB | 0x20; //zapne sa LED dióda | |||
i2c_start_wait(adr_w); //vymaže sa príznak prerušenia, aby program mohol bežať ďalej | |||
i2c_write(0x00);//adress byte | |||
i2c_write(0x04);//control register | |||
i2c_stop(); | |||
} | |||
</source> | |||
Zdrojový kód: [[Médiá:i2cmaster.h|i2cmaster.h]] a [[Médiá:twimaster.c|twimaster.c]] | |||
[[Médiá: | [[Médiá:cfinalpcf.c|PCF8583.c]] | ||
== | == '''Overenie''' == | ||
Program funguje veľmi jednoducho. Po zapojení obvodu čip začne okamžite pracovať. To, že od akého času má počítať je nastavené priamo v zdrojovom kóde ako aj alarm. V základnom stave je program nastavený tak, že čas vypisuje na LCD displej. Po malej úprave sa to dá jednoducho zmeniť, aby čas vypisoval cez USART na PC. Výsledok nášho zadania cez LCD ako aj cez Terminál vyzerá nasledovne: | |||
[[Obrázok:finalterminal.jpg|270px|center]] | |||
[[Obrázok:finallcd.jpg|960px|center]] | |||
[[Category:AVR]] [[Category:DVPS]] | [[Category:AVR]] [[Category:DVPS]] |
Aktuálna revízia z 21:37, 12. január 2015
Autori: | Győző Katona, Robert Nehánszki | |
Študijný odbor: | Aplikovaná mechatronika | 2. Ing. (2014/2015) |
Zadanie
- Popíšte ako funguje samotný senzor, ako sa pripojí k mikropočítaču.
- Zobrazte na LCD displeji a cez terminál reálny čas, deň a rok z obvodu PCF8583.
- Vyriešte naprogramovanie alarmu (denný, dňový alebo mesačný), alarmový stav zobrazte napríklad pomocou LED diódy.
Literatúra:
- Zoznam použitej literatúry, vrátane katalógových údajov (datasheet), internetových odkazov a pod.:
Odkazy produktu:
Ďalšie užitočné odkazy:
Analýza
Ako už bolo spomenuté, našou úlohou je vytvoriť hodiny reálneho času pomocou čipu PCF 8583. PCF 8583 je hodinovo-kalendárový čip. Adresy a dáta sú prenášané sériovo po zbernici i2c. Adresa zabudovaných registrov je inkrementovaná automaticky po každom zápise alebo čítaní jedného bajtu. Adresný pin A0 slúži na naprogramovanie adresy čipu, a dovoľuje nám zapojiť dva rovnaké čipy na tú istú zbernicu bez pridania ďalšieho hardware-u. Ako mikroprocesor sme použili Atmegu 328P, ktorá je nasadená na vývojovej doske Acrob.
Kľúčové vlastnosti čipu PCF8583:
- napájacie napätie i2c zbernice 2,5 V až 6 V
- 240 x 8 bitová nízko-napäťová pamäť
- operačný prúd (pri f(scl)=0 Hz) maximálne 50 uA
- funkcia hodín so štvorročným kalendárom
- časovač s alarmom, indikácia pretečenia
- 12 alebo 24 hodinový formát času
- potreba 32,768 kHz kryštálu
- dvojvodičová zbernica i2c
- automatická inkrementácia adresy po zápise/čítaní
- programovateľný alarm, časovač a prerušovacie funkcie
- adresa slave-u A1h alebo A3h pre čítanie, A0h alebo A2h pre zápis (podľa stavu pinu A0)+
Rozloženie pinov
Popis pinov PCF 8583
PIN | Skratka-označenie | Krátky popis | I/O |
---|---|---|---|
1 | OSCI | Vstupný pin pre kryštál | Vstup |
2 | OSCO | Výstupný pin oscilátora | Výstup |
3 | A0 | Pin pre voľbu adresy | Vstup |
4 | Vss | Napájacie napätie | Napájanie |
5 | SDA | Linka serial data | Vstup/výstup |
6 | SCL | Linka serial clock | Vstup |
7 | INT | Open-drain výstup prerušenia (aktívne LOW) | Výstup |
8 | Vdd | Napájacie napätie | Napájanie |
Zbernica i2c
I2c zbernica je skratka ktorá vznikla z názvu IIC zbernica, teda Internal-Integrated-Circuit Bus. Z názvu je hneď jasné, že sa jedná o internú dátovú zbernicu, ktorá slúži na komunikáciu a prenos dát medzi jednotlivými integrovanými obvodmi, väčšinou v rámci jedného zariadenia. Túto zbernicu vyvinula spoločnosť Philips približne pred 30 rokmi a od tej doby prešla niekoľkými vylepšeniami. V dnešnej dobe tento spôsob komunikácie podporuje celá rada integrovaných obvodov nielen firmy Philips. V prvom rade sa jedná o mikropočítače, sériové pamäte, inteligentné LCD obrazovky, a/d a d/a prevodníky, atď. Hlavnou výhodou je, že obojstranná komunikácia prebieha len po dvoch vodičoch SDA data a SCL hodiny, a tým pádom sa zjednoduší výsledné zapojenie celého obvodu. Na jednu zbernicu môžeme pripojiť viac integrovaných obvodov.
Spôsob adresovania:
- 7 bitové adresovanie - 128 čipov na tej istej zbernici
- 10 bitové adresovanie - 1024 čipov na tej istej zbernici
- na pevno (určené výrobcom čipu)
Prenosová rýchlosť je pre väčšinu aplikácií dostatočná už v základnej verzií, avšak zbernica prešla určitými vylepšeniami a tým pádom máme dnes rôzne prenosové rýchlosti. Rýchlosť prenosu však musí byť prispôsobená najpomalšiemu čipu na zbernici. Oba vodiče (SDA a SCL) musia byť v logickej jednotke, a to je zaistené pomocou pull-up odporov. čím je vyššia komunikačná rýchlosť, tým musia byť pull-up odpory menšie. Pre základnú rýchlosť 100 kHz postačujú odpory 4k7.
Rýchlosť zbernice i2c:
- základná - 100 kHz
- vylepšená - 400 kHz
- najrýchlejšia - až 1 MHz
Príklad zapojenia viacerých čipov na rovnakú zbernicu:
Princíp prenosu prostredníctvom i2c
Mikroprocesor sa považuje za MASTER a všetky ostatné obvody sú SLAVE. MASTER pri akomkoľvek prenosu generuje hodinový signál na vodiči SCL. Keď jeden čip vysiela, prijímajú všetky ostatné čipy na zbernici ale údaje spracuje len čip, ktorému boli dáta určené. Čip, ktorý chce dáta vyslať alebo prijať musí vždy najprv zadefinovať adresu čipu s ktorým chce nadviazať komunikáciu, a musí tiež určiť, či chce vysielať alebo čítať. To určuje bit R/W, ktorý je súčasťou adresy.
Prenos prebieha kombináciou nasledujúcich celkov:
- stav kludu - Tento stav je zaistený logickými jednotkami na oboch vodičoch, tj. MASTER negeneruje žiadny hodinový signál a neprebieha žiadny prenos. Logické jednotky sú zabezpečené pomocou pull-up odporov. Pull-up odpor je odpor medzi vodičom a napájacím napätím.
- start bit - Zahajuje prenos, alebo jeho ďalšiu časť. Je vygenerovaný tak, že sa zmení úroveň SDA z 1 na 0, zatiaľčo SCL = 1
- stop bit - Ukončuje prenos. Je generovaný podobne ako start bit. Úroveň SDA sa zmení z 0 na 1, zatiaľčo SCL = 1
- prenos dát - Všetky dáta sú prenášané po 1 bajt, teda 8 po sebe idúcich bitov od najvyššieho po najnižší. Pri prenose dát sa môže logická úroveň na SDA meniť len keď SCL = 0.
- ACK - tento bit slúži k potvrdeniu správneho prijatia dát. Odosiela sa rovnakým spôsobom, ako keby sa odosielal deviaty bit dát, ale s tým rozdielom, že ho generuje čip ktorý prijímal dáta a nie ten ktorý ho odosielal. Pokial prenos prebehol v poriadku, tak odošle logickú 0. Pokiaľ prenos zlyhal odošle sa logická 1. Pokiaľ má dojsť k ukončeniu prenosu, tak sa neodošle ACK.
Popis riešenia
Použitý RTC čip je štandartne pripojený k mikroprocesoru Atmega 328P pomocou dvoch vodičov SDA a SCL. V našom prípade však využívame aj dodatočné funkcie čipu PCF8583, a práve preto pripájame k mikroprocesoru aj tretí vodič. Spomenutou funkciou je zabudovaný alarm, ktorý v určitých časových intervaloch (aké si nastaví užívateľ), spustí alarmový stav, tj. niečo sa udeje, napríklad sa spustí siréna, spustí sa určité svetelné výstražné značenie alebo zapne sa motor, atď. Všetky tri vodiče musia byť pripojené na napájacie napätie (+5 V) pomocou pull-up odporov. Tie nám zabezpečia, že v kľudovom stave bude na všetkých troch zapojených pinoch logický stav 1. Taktiež sme využili 20 pinový konektor X1 vývojovej dosky Acrob na pripojenie LCD displeja.
Schéma zapojenia snímača PCF8583 k vývojovej doske Acrob
Algoritmus a program
Algoritmus je napísaný v programovacom prostredí AVR studio 4. Jednotlivé časti kódu budú vysvetlené nižšie. Pre jednoduchšie pochopenie programu nám poslúži vývojový diagram, v ktorom sú veľmi stručne opísané jednotlivé deje, ktoré sa udejú pri chodu programu. Aktuálny čas máme možnosť zobraziť na LCD displeji, alebo použijeme sériovú komunikáciu USART, a dáta vypíšeme napríklad pomocou programu Terminal
Vývojový diagram
Knižnice
Prvý krokom je zadefinovanie všetkých použitých knižníc. Knižnice slúžia na správnu funkciu a ovládanie všetkých použitých periférnych častí procesora. Niektoré sa použijú pri sériovej komunikácií cez USART, niektoré pri i2c komunikácií alebo pri operáciách s LCD displejom. V našom prípade sme použili 11 knižníc, a sú nasledovné:
#include <inttypes.h>
#include <compat/twi.h>
#include "serial.h"
#include "i2cmaster.h"
#include <avr/interrupt.h>
#include <stdlib.h>
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include <string.h>
#include "lcd.h"
Rýchlosť procesora a SCL
Ďalším krokom bolo definovať rýchlosť nielen procesora ale aj rýchlosť komunikácie prostredníctvom i2c zbernice. V našom prípade procesor je taktovaný na frekvenciu 16 Mhz, rýchlosť komunikácie zbernice i2c je 100 kHz. Pre danú operáciu táto rýchlosť plne postačuje.
#ifndef F_CPU
#define F_CPU 16000000UL
#endif
/* I2C clock in Hz */
#define SCL_CLOCK 100000L
Porty, prerušenie, LCD init
V main funkcií najprv prebieha inicializácia pripojeného LCD displeja, nastavenie jednotlivých portov a povolenie prerušenia (v prípade, že chceme túto možnosť využiť)
lcdInit4(); // inicializácia v 4-bitovom režime
lcdControlWrite(1<<LCD_CLR); // zmazanie displeja
lcdControlWrite(0x40); // nastavenie pozície 0,0, tj. ľavý horný roh
DDRB &= ~(1<<PCINT0); // nastavenie PB0 ako vstup
PORTB |= (1<<PCINT0); // aktivácia pull-up rezistorov
PCMSK0 |= (1 << PCINT0); // aktivácia PCINT0
PCICR |= (1 << PCIE0); // aktivácia prerušenia na aktivovanom PCINT0
sei (); // povolí prerušenie
DDRB=0x20; // PB5 - výstup na žltú LED diódu
PORTB = 0x00; // logický stav PORTB = 0, tj. LED nesvieti
Zápis času a dátumu do čipu
Prvá dôležitá operácia pri práci s čipom PCF8583 je zapísať čas a dátum do čipu a spustiť počítanie (spustiť hodiny). K tomu budeme potrebovať adresu pre čítanie a pre zápis do čipu. Táto adresa však závisí od zapojenia pinu A0. V našom prípade je adresa:
#define adr_w 0xA2
#define adr_r 0xA3
Ďalej budeme potrebovať lokáciu jednotlivých riadiacich registrov v pamäti RAM. Nato nám poslúži nasledujúci obrázok:
Najdôležitejší register (Ovládací a stavový register) sa nachádza na prvej adrese 00h. Tu nastavujeme hlavné funkcie, ktoré chceme pri našom procese aktívne využívať, tj. či chceme použiť časovač, alarm, aký kryštál je zapojený, tak isto tu nastavujeme zastavenie a spustenie hodín. Najdôležitejšie bity, ktoré sme použili sú opísané na obrázku:
Najprv použijeme nastavenie 0x80, takto docielime aby hodiny boli zastavené. Pri zápise času a dátumu do čipu musia byť hodiny vždy zastavené. Po zápise času a dátumu do čipu hodiny pustíme prepisom registra 00h z 0x80 na 0x04, čím zapneme počítanie a taktiež zapneme funkciu alarmu. Kedže čip disponuje funkciou automatickej inkrementácie adresy, po zápise do registra 00h (stavový), ďalší bajt sa zapíše do registra 01h a tak ďalej. Od adresy 01h je uložený čas nasledovne: stotiny - 01h, sekundy - 02h, minúty - 03h, hodiny - 04h, rok - dátum - 05h, deň - mesiac - 06h. To znamená, že keď zapíšeme do registra 00h hodnotu 0x80 (zastavíme hodiny), automaticky prejdeme do registra 01h a môžme nastaviť čas a dátum bez toho aby sme stále zadávali aj adresu registra do ktorého chceme zapisovať. V našom prípade zápis času vyzerá nasledovne:
...
write_time_to_chip(0x23,0x59,0x57,0x68,0x02); //hodiny (23); minúty (59); sekundy (57); rok-dátum (horné dva bity rok, 5-4 bit: deň-desiatky, 3-0 bit: deň do 9) - teda 0x68 = rok 1, datum 28;
//deň-mesiac (horné 3 bity deň, 4 bit: mesiac-desiatky, 3-0 bit: mesiac do 9) - teda 0x02 = deň 0 - pondelok , mesiac 2 - február
void write_time_to_chip(unsigned char hh,unsigned char mm,unsigned char ss,unsigned char yd,unsigned char wm)
{
//write to rtc
i2c_start_wait(adr_w); //pošle adresu zariadenia
i2c_write(0x00); //pošle lokáciu registra kam chceme zapisovať
i2c_write(0x80); //zastaví hodiny
i2c_write(0x00); //nastavíme 0 na stotiny
i2c_write(ss); //sekundy
i2c_write(mm); //minúty
i2c_write(hh); //hodiny
i2c_write(yd); //rok, dátum
i2c_write(wm); //deň, mesiac
i2c_write(0x00); //časovač - my nepoužívame
i2c_stop();
_delay_ms(5);
//start counting again:
i2c_start_wait(adr_w); //repeat start pošle adresu zariadenia
i2c_write(0x00); //pošle lokáciu registra kam chceme zapisovať
i2c_write(0x04); //spustí hodiny, alarm zapnutý
i2c_stop(); //i2c stop
}
...
Nato aby sme správne zapisovali hodnoty do registra kde je hodinový záznam, záznam rok-dátum a deň-mesiac, musíme presne vedieť akú majú funkciu jednotlivé bity daných registrov. Vysvetlenie nájdeme v následujúcom obrázku:
Nastavenie alarmu
Alarm sa nastavuje rovnakým spôsobom ako čas a dátum, len s tým rozdielom že pri čase, stavový register sa nachádzal na adrese 00h, kým stavový register alarmu sa nachádza na adrese 08h. Ako presne nastaviť register 08h nájdeme na nasledujúcom obrázku:
V registry 08h nastavíme jednotlivé bity na 0x90. Dolné 4 bity sú nulové, keďže pri našom zadaní nepoužívame funkciu časovača, horné 4 bity majú logickú hodnotu 1001. Logická jednotka na 7. bite nastavuje aby bolo prerušenie aktívne pri alarmoch, 6. bit vypína alarm časovača, logické úrovne bitov 5,4 (v našom prípade 01) nastavujú daily alarm - teda denný alarm. Denný alarm pracuje tak, že každý deň v tom istom čase dôjde k prerušeniu.
i2c_start_wait(adr_w); //i2c štart, pošle adresu zariadenia
i2c_write(0x08); //nastaví lokáciu registra kam chceme zapisovať
i2c_write(0x90); //do 08h zapíšeme 0x90, prečo práve 0x90 je vysvetlené vyššie
i2c_write(0x00); //prebieha autoinkrementácia adresy registrov, 0x00 zapisujeme už do registra pre stotiny
i2c_write(0x05); //Alarm sekundy
i2c_write(0x00); //Alarm minúty
i2c_write(0x00); //Alarm hodiny
i2c_write(0x00); //Alarm rok, dátum nemá žiadny efekt keď používame denný alarm
i2c_write(0x00); //Alarm deň, mesiac nemá žiadny efekt keď používame denný alarm
i2c_stop(); //koniec komunikácie i2c
Vyčítanie aktuálneho času a dátumu z čipu
Vyčítanie prebieha rovnakým spôsobom ako zápis. To znamená, že čítame dáta z rovnakých registrov, ako do ktorých sme na začiatku zapisovali (pravdaže medzitým sa čas zmenil, takže vyčítame iné hodnoty a nie na začiatku nami zadané). Vyčítanie vyzerá nasledovne:
...
//read from rtc
i2c_start_wait(adr_w); //i2c start, pošle adresu zariadenia
i2c_write(0x01); //zapíšeme od ktorého registra chceme čítať, stavový register 00h nie je potrebné pre nás vyčítať
i2c_rep_start(adr_r); //repeat štart,zo zápisu prepneme na čítanie
stotina=i2c_readAck(); //vyčítame stotiny a uložíme do premennej (ďalšiu adresu neurčíme kedže aj tu funguje automatická inkrementácia)
sec=i2c_readAck(); //vyčítame sekundy a uložíme do premennej
min=i2c_readAck(); //vyčítame minúty a uložíme do premennej
hour=i2c_readAck(); //vyčítame hodiny a uložíme do premennej
Year_Date=i2c_readAck(); //vyčítame rok a dátum a uložíme do premennej
Weekday_Month=i2c_readNak(); //vyčítame deň a mesiac a uložíme do premennej
i2c_stop(); //ukončíme komunikáciu
...
Spracovanie vyčítaných dát
Maskovanie
V premenných Year_Date a Weekday_Month sa nachádzajú viaceré potrebné dáta. Práve preto musíme tieto premenné maskovať, tj. oddeliť od seba rok a dátum a tak isto oddeliť od seba deň a mesiac. Musíme získať z dvoch premenných ako keby štyri premenné. Spravíme to tak, že vynásobíme bity reprezentujúce rok a bity reprezentujúce deň nulou. Tým získame premenné, v ktorých bude len dátum a mesiac.
//maskovanie:
mesiac=Weekday_Month & 0x1F; //vynásob horné tri bity premennej Weekday_Month nulou a výsledok ulož do premennej mesiac
denmes=Year_Date & 0x3F; //vynásob horné dva bity premennej Year_Date nulou a výsledok ulož do premennej denmes
Konvertovanie z BCD na ASCII
Všetky vyčítané dáta sú uložené ako číselná hodnota v BCD formáte. Tento formát pravdaže musíme konvertovať na ASCII kód. Ako prekonvertovať BCD na ASCII nájdeme napríklad TU v sekcií Numbers in ASCII-format. Z popisu je jasné, že treba pripočítať číslo 48 k BCD číslu. Ďalej si treba uvedomiť, že pri výpise na LCD nevieme posielať reťazec naraz ako cez Terminál, tj. treba čísla rozdeliť na desatinnú a jednotkovú časť. Keď máme napríklad 14 hodín zvlášť pošleme 1 a zvlášť 4 na displej. Ako uložiť jednotlivé čísla zvlášť do premenných a ako ich prekonvertovať na ASCII vidíme nižšie:
//BCD to ASCII:
hodina1=(hour >> 4)+48; //posun 4 horné bity do prava o 4 a pridaj 48, ulož do premennej hodina1, ak v hour bolo napríklad 23 v BCD, v hodina1 bude 2 v ASCII
hodina2=(hour & 0x0F)+48; //vynásob horné 4 bity nulou a pridaj 48, ulož do premennej hodina2, ak v hour bolo napríklad 23 v BCD, v hodina2 bude 3 v ASCII
minuta1=(min >> 4)+48;
minuta2=(min & 0x0F)+48;
sekunda1=(sec >> 4)+48;
sekunda2=(sec & 0x0F)+48;
mesiac1=(mesiac >> 4)+48;
mesiac2=(mesiac & 0x0F)+48;
denmes1=(denmes >> 4)+48;
denmes2=(denmes & 0x0F)+48;
Výpis cez Terminál
printf(" %c%c:%c%c:%c%c %s/%c%c/%c%c %s ",hodina1,hodina2,minuta1,minuta2,sekunda1,sekunda2,rok[b],mesiac1,mesiac2,denmes1,denmes2,dentyz[a]);
Výpis cez LCD
lcdDataWrite(hodina1); //vypíše na 0,0 hodnotu premennej hodina1
lcdDataWrite(hodina2); //vypíše na 0,1 hodnotu premennej hodina2
if(sec%2==0) lcdDataWrite(':'); //ak je sec párne vypíše dvojbodku na 0,2 (tým sa zabezpečí blikanie dvojbodky každú sekundu)
else lcdDataWrite(32); //space //ak je sec nepárne vypíše medzeru na 0,2 (tým sa zabezpečí blikanie dvojbodky každú sekundu)
lcdDataWrite(minuta1); //vypíše na 0,3 hodnotu premennej minuta1
lcdDataWrite(minuta2); //vypíše na 0,4 hodnotu premennej minuta2
if(sec%2==0) lcdDataWrite(':'); //ak je sec párne vypíše dvojbodku na 0,5 (tým sa zabezpečí blikanie dvojbodky každú sekundu)
else lcdDataWrite(32); //space //ak je sec nepárne vypíše medzeru na 0,5 (tým sa zabezpečí blikanie dvojbodky každú sekundu)
lcdDataWrite(sekunda1); //vypíše na 0,6 hodnotu premennej sekunda1
lcdDataWrite(sekunda2); //vypíše na 0,7 hodnotu premennej sekunda2
lcdControlWrite(0x40+0x80); //pozicia 1,0 - druhý riadok
lcdDataWrite(rokk1); //vypíše na 1,0 hodnotu premennej rokk1
lcdDataWrite(rokk2); //vypíše na 1,1 hodnotu premennej rokk2
lcdDataWrite('/'); //vypíše na 1,2 '/'
lcdDataWrite(mesiac1); //vypíše na 1,3 hodnotu premennej mesiac1
lcdDataWrite(mesiac2); //vypíše na 1,4 hodnotu premennej mesiac2
lcdDataWrite('/'); //vypíše na 1,5 '/'
lcdDataWrite(denmes1); //vypíše na 1,6 hodnotu premennej denmes1
lcdDataWrite(denmes2); //vypíše na 1,7 hodnotu premennej denmes2
Prerušenie
Pri dosiahnutí času, ktorý je nastavený v alarme od adresy 09h, dochádza k prerušeniu programu a zapne sa žltá LED dióda na vývojovej doske Acrob. Aby efekt trval dlhšie, program je napísaný tak, že po ukončení prerušenia LED ostane svietiť ešte ďalších 30 cyklov programu.
ISR (PCINT0_vect) //funkcia prerušenia PCINT0
{
c=0;
PORTB=PORTB | 0x20; //zapne sa LED dióda
i2c_start_wait(adr_w); //vymaže sa príznak prerušenia, aby program mohol bežať ďalej
i2c_write(0x00);//adress byte
i2c_write(0x04);//control register
i2c_stop();
}
Celý kód
#include <inttypes.h>
#include <compat/twi.h>
#include "serial.h"
#include "i2cmaster.h"
#include <avr/interrupt.h>
#include <stdlib.h>
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include <string.h>
#include "lcd.h"
//FILE mystdout = FDEV_SETUP_STREAM(sendchar, NULL, _FDEV_SETUP_WRITE);
/* define CPU frequency in Mhz here if not defined in Makefile */
#ifndef F_CPU
#define F_CPU 16000000UL
#endif
/* I2C clock in Hz */
#define SCL_CLOCK 100000L
#define adr_w 0xA2
#define adr_r 0xA3
#define comread 0x0B
void write_time_to_chip(unsigned char hh,unsigned char mm,unsigned char ss,unsigned char yd,unsigned char wm)
{
//write to rtc
i2c_start_wait(adr_w); //pošle adresu zariadenia
i2c_write(0x00); //pošle lokáciu registra kam chceme zapisovať
i2c_write(0x80); //zastaví hodiny
i2c_write(0x00); //nastavíme 0 na stotiny
i2c_write(ss); //sekundy
i2c_write(mm); //minúty
i2c_write(hh); //hodiny
i2c_write(yd); //rok, dátum
i2c_write(wm); //deň, mesiac
i2c_write(0x00); //časovač - my nepoužívame
i2c_stop();
_delay_ms(5);
//start counting again:
i2c_start_wait(adr_w); //repeat start pošle adresu zariadenia
i2c_write(0x00); //pošle lokáciu registra kam chceme zapisovať
i2c_write(0x04); //spustí hodiny, alarm zapnutý
i2c_stop(); //i2c stop
}
int c=50;
int main(void)
{
lcdInit4(); // inicializácia v 4-bitovom režime
lcdControlWrite(1<<LCD_CLR); // zmazanie displeja
lcdControlWrite(0x40); // nastavenie pozície 0,0, tj. ľavý horný roh
DDRB &= ~(1<<PCINT0); // nastavenie PB0 ako vstup
PORTB |= (1<<PCINT0); // aktivácia pull-up rezistorov
PCMSK0 |= (1 << PCINT0); // aktivácia PCINT0
PCICR |= (1 << PCIE0); // aktivácia prerušenia na aktivovanom PCINT0
sei (); // povolí prerušenie
DDRB=0x20; // PB5 - výstup na žltú LED diódu
PORTB = 0x00; // logický stav PORTB = 0, tj. LED nesvieti
//unsigned char stat_reg;
unsigned char stotina;
unsigned char sec;
unsigned char min;
unsigned char hour;
unsigned char Year_Date;
unsigned char Weekday_Month;
unsigned char mesiac;
unsigned char denmes;
//char *dentyz[] = {"Po","Ut","Str","Stv","Pi","So","Ne"}; //pre terminal
//int a=0;
//char *rok[] = {"2014","2015","2016","2017"}; //pre terminal
//int b=0;
unsigned char rokk1;
unsigned char rokk2;
unsigned char hodina1;
unsigned char hodina2;
unsigned char minuta1;
unsigned char minuta2;
unsigned char sekunda1;
unsigned char sekunda2;
unsigned char mesiac1;
unsigned char mesiac2;
unsigned char denmes1;
unsigned char denmes2;
//inituart();
// stdout = &mystdout;
i2c_init();
write_time_to_chip(0x23,0x59,0x57,0x68,0x02); //hh,mm,ss,yd - (7.6.bit year, 5.-0.bit day in bcd format)
//wm - (7.-5. bit weekdays, 4.-0. bit month in bcd format)
//ALARM SET:
i2c_start_wait(adr_w); //i2c štart, pošle adresu zariadenia
i2c_write(0x08); //nastaví lokáciu registra kam chceme zapisovať
i2c_write(0x90); //do 08h zapíšeme 0x90, prečo práve 0x90 je vysvetlené vyššie
i2c_write(0x00); //prebieha autoinkrementácia adresy registrov, 0x00 zapisujeme už do registra pre stotiny
i2c_write(0x05); //Alarm sekundy
i2c_write(0x00); //Alarm minúty
i2c_write(0x00); //Alarm hodiny
i2c_write(0x00); //Alarm rok, dátum nemá žiadny efekt keď používame denný alarm
i2c_write(0x00); //Alarm deň, mesiac nemá žiadny efekt keď používame denný alarm
i2c_stop(); //koniec komunikácie i2c
while(1)
{
//read from rtc
i2c_start_wait(adr_w); //i2c start, pošle adresu zariadenia
i2c_write(0x01); //zapíšeme od ktorého registra chceme čítať, stavový register 00h nie je potrebné pre nás vyčítať
i2c_rep_start(adr_r); //repeat štart,zo zápisu prepneme na čítanie
//stat_reg=i2c_readAck();
stotina=i2c_readAck(); //vyčítame stotiny a uložíme do premennej (ďalšiu adresu neurčíme kedže aj tu funguje automatická inkrementácia)
sec=i2c_readAck(); //vyčítame sekundy a uložíme do premennej
min=i2c_readAck(); //vyčítame minúty a uložíme do premennej
hour=i2c_readAck(); //vyčítame hodiny a uložíme do premennej
Year_Date=i2c_readAck(); //vyčítame rok a dátum a uložíme do premennej
Weekday_Month=i2c_readNak(); //vyčítame deň a mesiac a uložíme do premennej
i2c_stop(); //ukončíme komunikáciu
//maskovanie:
mesiac=Weekday_Month & 0x1F; //vynásob horné tri bity premennej Weekday_Month nulou a výsledok ulož do premennej mesiac
denmes=Year_Date & 0x3F; //vynásob horné dva bity premennej Year_Date nulou a výsledok ulož do premennej denmes
//CONVERSIONS FROM BCD TO ASCII:
/*switch((Weekday_Month & 0xE0)>>5 ) //maskovanie, potom posun do prava o 5
{
case 0x00:
a=0;break;
case 0x01:
a=1;break;
case 0x02:
a=2;break;
case 0x03: // vypis cez terminal
a=3;break;
case 0x04:
a=4;break;
case 0x05:
a=5;break;
case 0x06:
a=6;break;
}
switch((Year_Date & 0xC0)>>6 ) //maskovanie, potom posun do prava o 6
{
case 0x00:
b=0;break;
case 0x01:
b=1;break; // vypis cez terminal
case 0x02:
b=2;break;
case 0x03:
b=3;break;
}
*/
switch((Year_Date & 0xC0)>>6 ) //maskovanie, potom posun do prava o 6
{
case 0x00:
rokk1=1+48;rokk2=4+48;break; //rok bcd na ascii
case 0x01:
rokk1=1+48;rokk2=5+48;break;
case 0x02:
rokk1=1+48;rokk2=6+48;break;
case 0x03:
rokk1=1+48;rokk2=7+48;break;
}
//BCD to ASCII:
hodina1=(hour >> 4)+48; //posun 4 horné bity do prava o 4 a pridaj 48, ulož do premennej hodina1, ak v hour bolo napríklad 23 v BCD, v hodina1 bude 2 v ASCII
hodina2=(hour & 0x0F)+48; //vynásob horné 4 bity nulou a pridaj 48, ulož do premennej hodina2, ak v hour bolo napríklad 23 v BCD, v hodina2 bude 3 v ASCII
minuta1=(min >> 4)+48;
minuta2=(min & 0x0F)+48;
sekunda1=(sec >> 4)+48;
sekunda2=(sec & 0x0F)+48;
mesiac1=(mesiac >> 4)+48;
mesiac2=(mesiac & 0x0F)+48;
denmes1=(denmes >> 4)+48;
denmes2=(denmes & 0x0F)+48;
//printf(" %c%c:%c%c:%c%c %s/%c%c/%c%c %s ",hodina1,hodina2,minuta1,minuta2,
//sekunda1,sekunda2,rok[b],mesiac1,mesiac2,denmes1,denmes2,dentyz[a]);
lcdDataWrite(hodina1); //vypíše na 0,0 hodnotu premennej hodina1
lcdDataWrite(hodina2); //vypíše na 0,1 hodnotu premennej hodina2
if(sec%2==0) lcdDataWrite(':'); //ak je sec párne vypíše dvojbodku na 0,2 (tým sa zabezpečí blikanie dvojbodky každú sekundu)
else lcdDataWrite(32); //space //ak je sec nepárne vypíše medzeru na 0,2 (tým sa zabezpečí blikanie dvojbodky každú sekundu)
lcdDataWrite(minuta1); //vypíše na 0,3 hodnotu premennej minuta1
lcdDataWrite(minuta2); //vypíše na 0,4 hodnotu premennej minuta2
if(sec%2==0) lcdDataWrite(':'); //ak je sec párne vypíše dvojbodku na 0,5 (tým sa zabezpečí blikanie dvojbodky každú sekundu)
else lcdDataWrite(32); //space //ak je sec nepárne vypíše medzeru na 0,5 (tým sa zabezpečí blikanie dvojbodky každú sekundu)
lcdDataWrite(sekunda1); //vypíše na 0,6 hodnotu premennej sekunda1
lcdDataWrite(sekunda2); //vypíše na 0,7 hodnotu premennej sekunda2
lcdControlWrite(0x40+0x80); //pozicia 1,0 - druhý riadok
lcdDataWrite(rokk1); //vypíše na 1,0 hodnotu premennej rokk1
lcdDataWrite(rokk2); //vypíše na 1,1 hodnotu premennej rokk2
lcdDataWrite('/'); //vypíše na 1,2 '/'
lcdDataWrite(mesiac1); //vypíše na 1,3 hodnotu premennej mesiac1
lcdDataWrite(mesiac2); //vypíše na 1,4 hodnotu premennej mesiac2
lcdDataWrite('/'); //vypíše na 1,5 '/'
lcdDataWrite(denmes1); //vypíše na 1,6 hodnotu premennej denmes1
lcdDataWrite(denmes2); //vypíše na 1,7 hodnotu premennej denmes2
_delay_ms(100);
lcdControlWrite(1<<LCD_CLR);
lcdControlWrite(0x40); // pozicia 0,0
c++;
if(c==30) PORTB=PORTB & 0x00; //Alarm OFF
}
}
ISR (PCINT0_vect) //funkcia prerušenia PCINT0
{
c=0;
PORTB=PORTB | 0x20; //zapne sa LED dióda
i2c_start_wait(adr_w); //vymaže sa príznak prerušenia, aby program mohol bežať ďalej
i2c_write(0x00);//adress byte
i2c_write(0x04);//control register
i2c_stop();
}
Zdrojový kód: i2cmaster.h a twimaster.c
Overenie
Program funguje veľmi jednoducho. Po zapojení obvodu čip začne okamžite pracovať. To, že od akého času má počítať je nastavené priamo v zdrojovom kóde ako aj alarm. V základnom stave je program nastavený tak, že čas vypisuje na LCD displej. Po malej úprave sa to dá jednoducho zmeniť, aby čas vypisoval cez USART na PC. Výsledok nášho zadania cez LCD ako aj cez Terminál vyzerá nasledovne: