RTC (Real-Time Clock)

Martina Hejdová, UREL, VUT Brno
xhejdo00@stud.feec.vutbr.cz
Jan Hofman, UREL, VUT Brno
xhofma01@stud.feec.vutbr.cz

Obsah:

  1. Úvod
  2. Specifikace RTC
  3. Vlastní realizace
  4. Závěr
  5. Použitá literatura

Úvod

       Cílem tohoto projektu je ukázka práce s RTC - obvodem reálného času na vývojové desce ATmega16[1], která je k dispozici v laboratoři předmětu MMIA. Zejména potom implementace funkcí pro nastavování, čtení a zápis času i data. Dále vytvoření další obvodem podporovaných aplikací jako jsou například stopky.

Specifikace RTC

       Integrovaný obvod zdroje reálného času PCF8583[2] generuje roky, měsíce, dny, hodiny, minuty a sekundy s adresami 01 až 07. Podporuje 12i i 24i hodinový formát času. Obsahuje 2048 bitovou RAM paměť s 8 bitovou délkou slova a je určen pro aplikace s nízkým odběrem řádově do desítek µA. Řádná komunikace je podmíněna napájením v rozmezí od 2,5V do 6V. Adresa pro zápis je A0, pro čtení A1. Má zabudován vlastní oscilátor generující frekvenci 32,768 kHz. Jemnější doladění je umožněno kapacitním trimrem. Komunikaci s řídícím mikrokontrolérem zajišťuje dvouvodičová sběrnice I2C. Sériový přenos dat je v režimu Master & Slave, přičemž mikrokontrolér pracuje jako Master a RLC jako Slave. Maximální přenosová rychlost je 400kbit/s Kromě funkce zobrazování času umožňuje i kalendář a programovatelný alarm.

Vlastní realizace

       Pro realizaci je plně využíváno vývojové desky ATmega16[1] (obr. 1) s integrovaným obvodem reálného času PCF8583[2]. Pro zajištění vzájemné komunikace s řídícím mikrokontrolérem byly na obvod RTC (na desce označen J9)[3], LCD displej a klávesnici využity zkratovací propojky.
       Jak již bylo řečeno, přenost dat zajišťuje sběrnice I2C, kdy neaktivní úroveň na datovém vodiči je log. 0. Pro potřeby obvodu RTC je však třeba zajistit aktivní úroveň v log. 1. Pro tento účel byly připojeny dva pull-up rezistory o velikosti 4k7 ohm na piny PC0 a PC1[3].


Obr. 1: Pohled na desku ATmega16 s displejem zobrazující hlavní menu RTC hodin


       Jednotlivé časové úseky (sekundy, minuty...) mají v obvodu RTC vlastní datové registry s příslušnou adresou, kde jsou rozděleny na desítkovou a jednotkovou hodnotu. Některé sdílí příslušný datový registr společně, proto se k získání požadovaných bitů musí pronásobit určitými maskami (obr. 2). Z datových registrů jsou vyčítány v BCD kódu a pro další práci je nutné BCD formát převádět do binární podoby. Naopak pro zápis do RTC obvodu se využije zpětné operace. Zde je zobrazen převod z BCD kódu a naopak pro sekundy.
  sec_l = 0x0f & BCD;		 	// Převod jednotek sekund z BCD
  sec_h = (BCD>>4) & 0x0f;		// Převod desítek sekund z BCD
  BCD = (((set_sec_h <<4) & 0xF0) | (set_sec_l & 0x0F)); // Převedení z binárního vyjádření na BDC

Obr. 2: Registry obvodu RTC

       Čtení a zápis probíhá po sériové sběrnici I2C. Komunikace začíná startovací podmínkou, která sestává ze Start bitu, adresy RTC a bitu určujícího, zda se jedná o zápis či čtení obvodu RTC. Následně dochází ke zvolení příslušné datové adresy pro zápis, respektive čtení a k zapsání dat v BCD podobě do příslušného registru. Komunikace je ukončena Stop bitem. V případě čtení z RTC obvodu se startovací podmínka vysílá opakovaně. Samotná implementace funkcí pro čtení a zápis je realizovány pomocí knihovny určené pro I2C komunikaci.

unsigned char read(bunka)
{
  i2c_start_wait(RTC+I2C_WRITE);		// Vyslání Start bitu, adresy RTC a bitu pro zápis (čtení, zápis) (S ADR R/\W)
  i2c_write(bunka);				// Vyslání datové adresy, ze které se má číst
  i2c_rep_start(RTC+I2C_READ);   		// Vyslání znovu Start bitu, adresy RTC a bitu pro čtení (čtení, zápis) (S ADR R/\W)
  BCD = i2c_readNak();               	        // Přečtení celého bytu z vyslané adresy (sekund)
  i2c_stop();                         	        // Vyslání Stop bitu, ukončení komunikace
}

unsigned char write(BCD,bunka)
{	
  i2c_start_wait(RTC+I2C_WRITE);   	        // Vyslání Start bitu, adresy RTC a bitu pro zápis (čtení, zápis) (S ADR R/\W)								   	
  i2c_write(bunka);			        // Výběr adresy(hodiny, minuty atd.)											   
  i2c_write(BCD);				// Zápis BCD na vybranou adresu							   
  i2c_stop(); 				        // Vyslání Stop bitu, ukončení komunikace
}

       Nastavování číselných údajů času i data a pohyb v menu se provádí pomocí tlačítek na klávesnici. Pro tento účel je vytvořen následující ovladač, který je založen na spínání příslušného sloupce a řádku. V hlavní funkci main je nastaven řídící port B na hodnotu 0x0f, aby sepnuté tlačítko detekovalo log. 0 (z toho důvodu jsou právě využity pull-up rezistory). Tlačítka s písmeny A až D slouží hlavně pro pohyb mezi jednotlivými výběry z nabídky (mod 1 až 4). Dále pro potvrzení výběru, mazání a návrat do předchozího menu.

int klavesnice(void)                      	// Funkce pro čtení z klávesnice
{
  int i=0;
  int j=0;

  for(i=0;i<4;i++)						
  {
  	if(i==0)
	  PORTB = 0b11110111;			// Sepnutí prvního sloupce tlačítek
	if(i==1)
	  PORTB = 0b11111011;			// Sepnutí druhého sloupce tlačítek
	if(i==2)
	  PORTB = 0b11111101;		        // Sepnutí třetího sloupce tlačítek
	if(i==3)
	  PORTB = 0b11111110;			// Sepnutí čtvrtého sloupce tlačítek

	for(j=0;j<4;j++)
  	{
	  if((bit_is_clear(PINB,PINB7)) && (i==0))		// Čtení stavu tlačítka 1
	    tl=1;
	  if((bit_is_clear(PINB,PINB6)) && (i==0))		// Čtení stavu tlačítka 2
	    tl=2;
	  if((bit_is_clear(PINB,PINB5)) && (i==0))		// Čtení stavu tlačítka 3
	    tl=3;
	  if((bit_is_clear(PINB,PINB4)) && (i==0))		// Čtení stavu tlačítka A
	    tl=10;

	  if((bit_is_clear(PINB,PINB7)) && (i==1))		// Čtení stavu tlačítka 4
	    tl=4;
	  if((bit_is_clear(PINB,PINB6)) && (i==1))		// Čtení stavu tlačítka 5
	    tl=5;
	  if((bit_is_clear(PINB,PINB5)) && (i==1))		// Čtení stavu tlačítka 6
	    tl=6;
	  if((bit_is_clear(PINB,PINB4)) && (i==1))		// Čtení stavu tlačítka B
	    tl=11;

	  if((bit_is_clear(PINB,PINB7)) && (i==2))		// Čtení stavu tlačítka 7
	    tl=7;
	  if((bit_is_clear(PINB,PINB6)) && (i==2))		// Čtení stavu tlačítka 8
	    tl=8;
	  if((bit_is_clear(PINB,PINB5)) && (i==2))		// Čtení stavu tlačítka 9
	    tl=9;
	  if((bit_is_clear(PINB,PINB4)) && (i==2))		// Čtení stavu tlačítka C
	    tl=12;

	  if((bit_is_clear(PINB,PINB7)) && (i==3))		// Čtení stavu tlačítka *
	    tl=14;
	  if((bit_is_clear(PINB,PINB6)) && (i==3))		// Čtení stavu tlačítka 0
	    tl=0;
	  if((bit_is_clear(PINB,PINB5)) && (i==3))		// Čtení stavu tlačítka #
	    tl=15;
	  if((bit_is_clear(PINB,PINB4)) && (i==3))		// Čtení stavu tlačítka D
	    tl=13;
	}	
  }
}

       Manipulace s časem a datem je prováděna pomocí funkce pro nastavení času, respektive data. Příslušný výběr se volí v hlavním menu pro nastavení. Obě funkce mají dále vlastní menu (obr. 3), kde je přehledně zobrazen posun na dílčí časové pozice při samotném nastavování. Obě funkce jsou založeny na stejném principu, proto je zde pro ukázku zobrazena pouze funkce pro nastavení času.

void set_cas(void)			        // Funkce pro nastavení času
{
  int set_sec_h=0;
  int set_sec_l=0;
  int set_min_h=0;
  int set_min_l=0;
  int set_hod_h=0;
  int set_hod_l=0;
  int poz=0;
  tl=17;
  mod=3;				        // Mód pro nastavení času
  _delay_ms(zpozdeni);				// Zpoždění po stisku tlačítka
  lcd_clrscr();					// Mazání displeje

  lcd_gotoxy(0,0);			        // Zápis na displej
  lcd_puts("nastav cas");
  lcd_gotoxy(0,1);
  sprintf(buff,"poz:%d  %d%d:%d%d:%d%d",(poz+1),set_hod_h,set_hod_l,set_min_h,set_min_l,set_sec_h,set_sec_l);		
  lcd_puts(buff);
  lcd_gotoxy(0,2);				
  lcd_puts("A-potvr");
  lcd_gotoxy(0,3);				
  lcd_puts("D-jdi pryc");

  while(mod==3)
  {
  	klavesnice();			        // Čtení z klávesnice
	_delay_ms(zpozdeni);			// Zpoždení po stisku tlačítka

	if(tl<10)				// Pokud je stisknuto tlačítko 0 až 9, dojde k nastavení hodnoty
	{
	  if(poz==0)
		set_hod_h=tl;			// Nastavení desítek hodin
	  if(poz==1)
		set_hod_l=tl;			// Nastavení jednotek hodin
	  if(poz==2)
		set_min_h=tl;			// Nastavení desítek minut
	  if(poz==3)
		set_min_l=tl;			// Nastavení jednotek minut
	  if(poz==4)
		set_sec_h=tl;			// Nastavení desítek sekund
	  if(poz==5)
		set_sec_l=tl;			// Nastavení jednotek sekund
	  tl=17;				// Signalizuje, že není stisknuto žádné tlačítko
	  poz=poz+1;				// Inkrementace pozice pro zápis
	}

	lcd_gotoxy(0,0);			// Výpis na displej
  	lcd_puts("nastav cas");
	lcd_gotoxy(0,1);
	sprintf(buff,"poz:%d  %d%d:%d%d:%d%d",(poz+1),set_hod_h,set_hod_l,set_min_h,set_min_l,set_sec_h,set_sec_l);	
	lcd_puts(buff);

	if(tl==10)				// Pokud je stisknuto tlačítko A, dojde k potvrzení času a odeslání do obvodu RTC
    {
	  BCD = (((set_hod_h <<4) & 0xF0) | (set_hod_l & 0x0F));		// Převedení z binárního vyjádření na BDC
	  write(BCD,hodiny);							// Zápis hodin do obvodu RTC
	  BCD = (((set_min_h <<4) & 0xF0) | (set_min_l & 0x0F));		// Převedení z binárního vyjádření na BDC
	  write(BCD,minuty);							// Zápis minut do obvodu RTC
	  BCD = (((set_sec_h <<4) & 0xF0) | (set_sec_l & 0x0F));		// Převedení z binárního vyjádření na BDC
	  write(BCD,sekundy);							// Zápis sekund do obvodu RTC
	  mod=1;				// Nastavení výchozího módu
	  tl=17;				// Signalizuje, že není stisknuto žádné tlačítko
	  _delay_ms(zpozdeni);			// Zpoždění po stisku tlačítka

	  lcd_clrscr();
	  lcd_gotoxy(0,0);				
	  lcd_puts("A-nastaveni casu");
	  lcd_gotoxy(0,1);				
	  lcd_puts("B-nast. datumu");
	  lcd_gotoxy(0,2);				
 	  lcd_puts("D-zpet");
	}
    
	if(tl==13)				// Při stisku tlačítka D, dojde k nastavení výchozího módu
	{
	  mod=1;
	  _delay_ms(zpozdeni);
	  tl=17;
	 
	  lcd_clrscr();			        // Výpis na displej
	  lcd_gotoxy(0,0);				
	  lcd_puts("A-nastaveni casu");
	  lcd_gotoxy(0,1);				
	  lcd_puts("B-nast. datumu");
	  lcd_gotoxy(0,2);				
 	  lcd_puts("D-zpet");    
	}
  }            
}

Obr. 3: Ukázka menu pro nastavení času

       Funkce pro stopky je založena na čtení setin sekundy z RTC, následné inkrementace a přetečení jednotlivých časových úseků. Na displeji se zobrazuje postupné čítání času po sekundách až do času 59:59:9, poté začne probíhat čítání znova. Zároveň je možné zobrazovat mezičasy, přesněji řečeno tři mezičasy, které neustále rotují (obr. 4). Kromě spuštění stopek a funkce mezičasů je na výběr i zastavení a vynulování stopujícího času, včetně mezičasů.

void stopky(void)				// Funkce stopky
{
  int m;
  int ss_2=1;
  int ss=1;
  int ss_h=0;
  int ss_l=0;
  int s_h=1;
  int s_l=1;
  int m_h=1;
  int m_l=1;
  int poc=0;
  int i=0;  

  int stopky_mod=0;
  
  char mezicas_1[16];				// Pole pro uložení mezičasů 
  char mezicas_2[16];
  char mezicas_3[16];
  
  mod=2;					// Nastavení módu pro stopky
  tl=17;
  _delay_ms(zpozdeni);				// Zpoždění
  lcd_clrscr();					// Mazání displeje

  lcd_gotoxy(0,0);				// Výpis na displej
  sprintf(buff,"cas:  %d%d:%d%d:%d",m_h-1,m_l-1,s_h-1,s_l-1,ss_2-1);		
  lcd_puts(buff);

  lcd_gotoxy(0,1);
  sprintf(mezicas_1,"mez:  %d%d:%d%d:%d",m_h-1,m_l-1,s_h-1,s_l-1,ss_2-1);
  lcd_puts(mezicas_1);

  lcd_gotoxy(0,2);
  sprintf(mezicas_2,"mez:  %d%d:%d%d:%d",m_h-1,m_l-1,s_h-1,s_l-1,ss_2-1);
  lcd_puts(mezicas_2);
	  
  lcd_gotoxy(0,3);
  sprintf(mezicas_3,"mez:  %d%d:%d%d:%d",m_h-1,m_l-1,s_h-1,s_l-1,ss_2-1);
  lcd_puts(mezicas_3);

  while(mod==2)
  {
	read(setiny);				// Čtení setin z RTC
 	ss_l = 0x0f & BCD;			// Získaní jednotek setin z BCD
	ss_h = (BCD>>4) & 0x0f;			// Získaní desítek setin z BCD
	klavesnice();	

	if(tl==10)				// Při stisku tlačítka A jsou spuštěny stopky
	  stopky_mod=1;
	if(tl==11)				// Při stisku tlačítka B jsou stopky zastaveny
	  stopky_mod=2;
	if(tl==12)				// Při stisku tlačítka C jsou stopky vynulovány
	  stopky_mod=3;
	if(tl==13)				// Při stisku tlačítka D dojde k návratu do výchozího módu
	{
	  lcd_clrscr();				// Mazání displeje
	  mod=0;
	}
	if(tl==15)				// Při stisku tlačítka * je zaznamenán mezičas
	{
	  stopky_mod=4;
	  m=s_l;
	}

	if(i==0)				// Při spuštění musí být zaznamenána aktuální hodnota desetin
	{
	  ss=ss_h;
	  i=1;
	}

	if(stopky_mod==1 || stopky_mod==4)	// Pokud je aktivní režim pro stopky, je inkrementován čas
	{
		if(ss!=ss_h)			// Pokud hodnota desetin z předchozího kroku není rovna aktuální hodnotě, jsou inkrementovány desetiny
		  {ss=ss_h;			// Uložení aktuální hodnoty desetin do proměnné pro desetiny z předchozího kroku
		   ss_2=ss_2+1;}	        // Inkrementace desetin sekundy

		if(ss_2>10)			// Pokud dojde k přetečení desetin sekundy, jsou inkrementovány sekundy
  		{   ss_2=1;			// Vynulování desetin
			s_l=s_l+1;}		// Inkrementace sekund

		if(s_l>10)			// Pokud dojde k přetečení sekund, jsou inkrementovány desítky sekundy
  		{   s_l=1;			// Vynulování sekund
			s_h=s_h+1;}		// Inkrementace desítek sekund

		if(s_h>6)			// Pokud dojde k přetečení desítek sekund, jsou inkrementovány minuty
  		{   s_h=1;			// Vynulování desítek sekund
			m_l=m_l+1;}		// Inkrementace minut

		if(m_l>10)			// Pokud dojde k přetečení minut, jsou inkrementovány desítky minut
  		{   m_l=1;			// Vynulování minut
			m_h=m_h+1;}		// Inkrementace desítek minut

		if(m_h>6)			// Při přetečení čítače desítek minut dojde k čítání od času 00:00:0
  			{   
			m_h=1;
			m_l=1;
			s_h=1;
			s_l=1;
			ss_2=1;
			}  
 	 	lcd_gotoxy(0,0);	        // Výpis na displej
 	 	sprintf(buff,"cas:  %d%d:%d%d:%d",m_h-1,m_l-1,s_h-1,s_l-1,ss_2-1);
 	 	lcd_puts(buff);
	}

	if(stopky_mod==4)		        // Uložení mezičasů
	{
	  tl=17;

	  strcpy(mezicas_3,mezicas_2);	  	// Rotování mezičasů
	  strcpy(mezicas_2,mezicas_1);

	  lcd_gotoxy(0,1);			// Výpis na displej
	  sprintf(mezicas_1,"mez:  %d%d:%d%d:%d",m_h-1,m_l-1,s_h-1,s_l-1,ss_2-1);
	  lcd_puts(mezicas_1);

	  lcd_gotoxy(0,2);
	  lcd_puts(mezicas_2);
	  
	  lcd_gotoxy(0,3);
	  lcd_puts(mezicas_3);	  
	  
	  stopky_mod=1;  			// Nastavení módu pro běh stopek
	}

	if(stopky_mod==3)			// Nulování stopek
	{
		m_h=1;
		m_l=1;
		s_h=1;
		s_l=1;
		ss_2=1;

		i=0;		

		lcd_gotoxy(0,0);		// Výpis na displej
  		sprintf(buff,"cas:  %d%d:%d%d:%d",m_h-1,m_l-1,s_h-1,s_l-1,ss_2-1);
  		lcd_puts(buff);

  	    lcd_gotoxy(0,1);
		sprintf(mezicas_1,"mez:  %d%d:%d%d:%d",m_h-1,m_l-1,s_h-1,s_l-1,ss_2-1);
  		lcd_puts(mezicas_1);

  		lcd_gotoxy(0,2);
  		sprintf(mezicas_2,"mez:  %d%d:%d%d:%d",m_h-1,m_l-1,s_h-1,s_l-1,ss_2-1);
  		lcd_puts(mezicas_2);
	  
  		lcd_gotoxy(0,3);
  		sprintf(mezicas_3,"mez:  %d%d:%d%d:%d",m_h-1,m_l-1,s_h-1,s_l-1,ss_2-1);
  		lcd_puts(mezicas_3);
	} 
  }
  lcd_clrscr();					// Mazání displeje
}
}

Obr. 4: Ukázka stopek

Závěr

       V projektu byly realizovány RTC hodiny. Funkce pro nastavování a zobrazování jednotlivých časových úseků jako jsou sekundy, minuty atd. na LCD displeji, stejně jako funkce pro datum. Na displeji nejsou zobrazovány roky, protože čítač pro ně určený je pouze 2bitový, a proto přiřazení k jednotlivým rokům by vyžadovalo zdlouhavou softwarovou realizaci. Taktéž nebyl ošetřen počet dní v jednotlivých měsících, tudíž se spoléhá na uživatelovu dostatečnou inteligenci, že si je vědom faktu, že například Únor má pouze 28 dní. V neposlední řadě byly realizovány stopky. Původně bylo zamýšleno na displeji zobrazovat čítání od setin sekundy výše, bohužel jednotlivá zpoždění (například při stisknutí tlačítka) v řádech milisekund tento záměr nedovolovala realizovat. Jejich čítání bylo až příliš pomalé. Současné zobrazování od desetin sekundy výše však dostatečně plní svou funkci. V plánu bylo implementovat i další aplikace typu budík a odpočítávání času, nicméně překážkou se stala nedostatečná programová paměť, která byla zaplněna téměř z 98%. Celkově tento projekt byl zaměřen více na ukázku práce s obvodem RTC, než na vytvoření zařízení zobrazující dokonale přesný čas. Proto ani takto nedostatečná paměť nebyla nikterak velkou překážkou.

Celý projekt v soubouru zip je možné stáhnout zde.

Použitá literatura

[1] ATMEL. ATmega16 datasheet [online]. Rev. 12/03 [cit. 20. května 2011]. 349 s. Dostupné na WWW: http://www.gme.cz/_dokumentace/dokumenty/958/958-099/dsh.958-099.1.pdf
[2] Philips Semiconductors. PCF8583 Clock/calendar Product specification [online]. Rev. 07/15/1997 [cit. 20. května 2011]. 28 s. Dostupné na WWW: http://www.datasheetcatalog.org/datasheet/philips/PCF8583_5.pdf
[3] FRÝZA,T., FEDRA Z., ŠEBESTA. J.: Mikroprocesorová technika - Laboratorní cvičení. Vývojová deska ATmega16. str. 45. Dostupné na WWW: https://krel.feec.vutbr.cz/VYUKA/M_EST/MMIA/Texty/bmpt_laboratore.pdf