SD-Logger II. - Mi is az a Valós Idejű Óra (RTC - Real Time Clock)?

A címbeli magyar rövidítés nem néz ki olyan jól, mint az angol. Így magyarul marad: óra, angolul RTC.
Amikor adatgyűjtésre adjuk a processzormagunkat, akkor nem elég a gyűjtött adat. Szükségünk van az adat keletkezésének időpotjára is. Ezt hívják tudományosan időpecsétnek. Ez az időpecsét körülbelül a postai dátumbélyegzőnek felel meg, ahol még az óra-perc-másodpercet is megadjuk. Ennél finomabb időérték nagyon ritkán kerül mikrokontrolleres környezetben felhasználásra.

Az Arduino illetve a Bascom-AVR tartalmaz belső órát. Ezzel többször találkoztunk már: millis() néven ketyeg. Ezzel akár napos tartományt is vizsgálhatunk, hiszen kb. 70 óra alatt fordul körbe. Így felmerülhet a kérdés: minek nekünk egy külső órachip? Bascom-AVR alatt a Config Clock = Soft / User a kulcsszó a belső órához, melyet a Time$ és a  Date$ egészít ki.

A processzorok belső órájénak a legnagyobb problémája, hogy csak a bekapcsolás óta eltelt időt képes mérni, hiszen újraindításkor mindig nullázódik a számláló. Az Arduino és a Bascom nem tudja, hogy ma kedd, 2013 március havának 10. napja van. Csak azt, hogy bekapcsolás óta eltelt 14.321 msec.

Ok. De hogyan állíthatunk be időt a kontrolleren? Soha nem tudhatjuk, hogy bekapcsoláskor mennyi az idő. Olyanok mint a klasszikus világítós órák: elmegy az áram és az idő villogó 12:00 lesz.

Gyakorlati RTC

A rövid bevezető után láthatjuk, hogy az időmérő szerkezet nem is olyan haszontalan jószág. Jónéhány adatgyűjtő projekt esetén elengedhetetlen, hogy az mikrokontroller újraprogramozása vagy újraindulása esetén legyen egy olyan eszközünk, amihez idő kérdésében fordulhatunk:
Ez maga a valós idejű óra!

De mitől valós? Van valótlan is?

Az órákkal mindig van egy nagyon nagy probléma. Amígy egy van belőle, akkor tudjuk, hogy mennyi az idő. Kettő esetén azonban sohasem :). A valós időnek az általunk ismert időképet nevezzük és minden esetben az időszámításunk kezdetét tekintjük nullpontnak. Ezt a legtöbbször abszolút időnek is hívjuk. Egy tetszőleges időponthoz vagy "relatív nullidőhöz" képest a relatív időt mérhetjük. Ilyen ebben a megközelítésben a processzor induása. Arduino alatt a millis() a relatív időt mutatja. Ha az időmérő eszközök érdekelnek, akkor ezekről bővebben az Időmérés - Mennyi is az idő? cikkben olvashatsz. De a kitérő után maradjunk csak az RTC chipünknél.

Az órachip

Az óra nem más, mint egy speciális áramkör, ami a pontos időt számolja. Ezt úgy teszi, hogy figyeli a szökőévet, a hónap napjainak számát - azonban nem kezeli a téli/nyári óraállítást, mert országonként/időzónánként eltérő lehet.
A régi PC alaplapokon ez külön egység volt - még el sem lehetett téveszteni. DS1387 névre hallgatott és a komplexitására jellemző, hogy egy tokon belül az elemet, kvarcot, minden egyéb sallangot tartalmazott. Ez elég nagy lenne az áramköreinkhez, így az Arduino körökben a DS1307 jelű chip DIP tokos kivitelére esett a választás Ennek oka a könnyű kezelhetőség, az egyszerű programkód és a logikus felépítés. A kikapcsolás után, hogy ne felejtse el az időt - valamilyen kis elemre vagy akkura is szükségünk van a használatához. Az órachip az elemről jár, így ha valami oknál fogva nem tud működni, akkor gyanakodni kezdhetük. Az órachipek egy része a rendszertápfeszültségről is képes működni - ám a kimerült elem esetén alaphelyzetbe áll és minden bekapcsolásnál innen is folytatja  a működést.

Az RTC megszólítása

A legtöbb RTC I2C buszra csatlakoztatható, ami Arduino alatt Wire és Bascom-AVR alatt TWI névre hallgat. Ez fixen az Arduino Diecimila/Uno hardware (azaz ATMegax8 chip) esetén az A4/A5 kivezetéseket foglalja el. A tervezésnél és a használatnál figyelni kell rá, hogy más kivezetésre ezek nem helyezhetőek át, mivel belső hardware-hez kötöttek. A használat következménye hogy, így két analóg kivezetést be kellett áldoznunk.
Az órachipet közvetlenül is manipulálhatjuk, azonban jobban járunk, ha ehhez a kész függvénykönyvtárat használjuk. Erre a legegyszerűbb az RTClib használata, amit letöltés után a függvénykönyvtárak közé elég kibontani.

A függvények megkönnyítik a chip használatát, mivel az egyes funkciókra külön-külön eljárások lettek már létrehozva bennük. Az RTClib függvényrendszer nem csak a DS1307 órachipet, hanem számos, más gyártó, más eszközét is támogatja (DS1307, DS1388, PCF8563, BQ32000).  A háttérben lehet, hogy ezeket teljesen eltérően kell kezelni - azonban a függvényhívásokon keresztül ezek azonosan szólíthatóak meg. Bascom-AVR alatt a közvetlen chipkezelésnek van inkább hagyománya - viszonylag ritka a külön függvénykönyvtár (de a DS1307 esetén mind a közvetlen chipelérést, mind a járulékos függvénykezelést megtalálhatjuk a minták közt). 

A kezdet

A legelső minta az RTC chip kezelésére nem az amit várnánk. Nem funkcionális teszt: hanem, hogy egyáltalán létezik a buszon. Igaz erre a programra lehet azt is mondani, hogy egyszerű hibakeresés.... A chip helyét az I2C/Wire buszon az ismert i2cdetect programmal végezhetjük el. Ennek számos vállfaja kering Arduino körökben. A legegyszerűbb az Arduino-1.x rendszerhez készült változat. Kissé feljavítva a kód így néz ki:

/*
  TWI/I2Cscanner
  (c) TavIR http://www.tavir.hu
 
 This sketch tests the standard 7-bit addresses
 from 0 to 127. Devices with higher bit address
 might not be seen properly. Work only at hardware I2C/TWI!

 Tested via Arduino 1.0.2
 
  Founded at arduino.cc forum
  Modified 12 March 2013
  by TavIR / Robert Cseh
 
  This example code is in the public domain.
 */

#include <Wire.h>

void setup()
{ Wire.begin();
  Serial.begin(9600);
  Serial.println("\\nI2C Scanner"); }

void loop()
{ byte error, address;
  int nDevices;

  Serial.println("Scanning...");

  nDevices = 0;
  for(address = 0; address <= 127; address++ )
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0)
    { Serial.print("I2C device found at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.println(" !");
      nDevices++;
    }
    else if (error==4)
    {
      Serial.print("Unknow error at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\\n");
  else
    Serial.println("done\\n");

  delay(8000);
}

A futás eredménye nem más, mint az I2C térkép:

Ha nem látszik a chip a buszrendszeren, annak számos oka lehet. Ezek hardware alapúak, azaz egyesével kell átnézni a lehetséges hibákat. Ezek tipikusan a következők:

  • valamelyik vagy mindkét felhúzóellenállás szakadt vagy zárlatos,
  • az ellenállások értéke nem 2k2...4k7,
  • az i2cbusz GND fele zárlatos,
  • szakadt wire-busz vezeték.

Kevésbé típushiba, ugyanakkor a tünetek azonosak:

  • 180 fokkal elforgatva beültetett órachip,
  • kontakthibás, zárlatos órakvarc vagy vezetéke,
  • akkufeszültség tartományon kívüli, akkor is ha nincs közvetlenül telepítve.

Fontos! A DS1307 esetén, ha Vbatt-ra (3. láb) a tápfeszültséget kapcsoljuk, akkor sem indul el a chip!
Ha a chip gombakkuról jár, és "megfagyott", akkor az Arduino rendszert áramtalanítsuk és a gomelemet is vegyük ki 10-15 másodpercre. Ha nagykapacitású kondenzátorról jár, akkor áramtalanítás után a kondenzátort zárjuk rövidre!

És kicsit részletesebb tesztprogram Bascom-AVR alatt is elérhető:

''*********************************************
''* About:      I2C full scanner              *
''* Title:      IICdetect                     *
''* Filename:   i2cdetect.bas                 *
''* Compiler:   Bascom-AVR 2.0.7.5            *
''*                                           *
''* Author:     Robert Cseh                   *
''* E-mail:     avr /kukac/ tavir /pont/ hu   *
''* Homepage:   http://www.tavir.hu           *
''*********************************************

'' This program demonstrate the I2Ccommuncication and Err variable

   $crystal = 16000000                                      ''Sebesseg
   $regfile = "M328pdef.dat"                                ''Chip
   $baud = 9600
   $swstack = 128
   $hwstack = 128
   $framesize = 128
   Dim Temp1 As Byte
   Dim Temp2 As Byte
     ''I2C konfigurálása
   Config I2cdelay = 10
     ''lassú mód, alapesetben:10
   Config Sda = Portc.4
   Config Scl = Portc.5

   Config Portc.4 = Output
   Config Portc.5 = Output
   Portc.4 = 1
   Portc.5 = 1
   Print "      Out  In"
   Print "SDA(H):" ; Portc.4 ; "   " ; Pinc.4
   Print "SCL(H):" ; Portc.5 ; "   " ; Pinc.5
   Portc.4 = 0
   Portc.5 = 0
   Print "SDA(L):" ; Portc.4 ; "   " ; Pinc.4
   Print "SCL(L):" ; Portc.5 ; "   " ; Pinc.5
   Wait 1
   I2cinit
     ''I2C inicializalas
   Print "I2c-locator 1=no_device 0=device"
   Print "     0    2    4    6    8    A    C    E"
   Print " 00 ";
   For Temp1 = 0 To 254 Step 2
      Temp2 = Temp1 Mod 16
      If Temp2 = 0 And Temp1 > 0 Then
         Print
         Print " " ; Hex(temp1) ; " ";
      End If
      I2cstart
        '' Startjel
      I2cwbyte Temp1
      Print " " ; Err ; "   ";
        '' 1-nincs, 0-van eszkoz
      I2cstop
   Next Temp1
End

A futtatás eredménye, ha mindent jól csináltunk:

Az első siker után a gyakorlat

A mintaáramkörünkön ezután az RTC chipet gyorsan birtokba is vehetjük. Ehhez elég az RTClib mintaalkalmazásból a DS1307 nevű -t feltölteni az adatgyűjtőre:

/*
  DS1307 RTC example
  (c) TavIR http://www.tavir.hu
 
  Date and time functions using a DS1307 RTC
  connected via I2C and Wire lib.
  Work only at hardware I2C/TWI!

  Tested via Arduino 1.0.2
 
  Modified 12 March 2013
  by TavIR / Robert Cseh
 */

#include <Wire.h>
#include "RTClib.h"

RTC_DS1307 RTC;

void setup () {
    Serial.begin(57600);
    Wire.begin();
    RTC.begin();

  if (! RTC.isrunning()) {
    Serial.println("RTC is NOT running!");
    // sets the RTC to the date & time this sketch was compiled
    RTC.adjust(DateTime(__DATE__, __TIME__));
  }
}

void loop () {
    DateTime now = RTC.now();
    
    Serial.print(now.year(), DEC);
    Serial.print(''/'');
    Serial.print(now.month(), DEC);
    Serial.print(''/'');
    Serial.print(now.day(), DEC);
    Serial.print('' '');
    Serial.print(now.hour(), DEC);
    Serial.print('':'');
    Serial.print(now.minute(), DEC);
    Serial.print('':'');
    Serial.print(now.second(), DEC);
    Serial.println();
    
    Serial.print(" since midnight 1/1/1970 = ");
    Serial.print(now.unixtime());
    Serial.print("s = ");
    Serial.print(now.unixtime() / 86400L);
    Serial.println("d");
    
    // calculate a date which is 7 days and 30 seconds into the future
    DateTime future (now.unixtime() + 7 * 86400L + 30);
    
    Serial.print(" now + 7d + 30s: ");
    Serial.print(future.year(), DEC);
    Serial.print(''/'');
    Serial.print(future.month(), DEC);
    Serial.print(''/'');
    Serial.print(future.day(), DEC);
    Serial.print('' '');
    Serial.print(future.hour(), DEC);
    Serial.print('':'');
    Serial.print(future.minute(), DEC);
    Serial.print('':'');
    Serial.print(future.second(), DEC);
    Serial.println();
    
    Serial.println();
    delay(3000);
}

A program feltöltése után az Arduino terminál-alkalmazása 57600 bps sebességgel kommunikál a lapkával. Ha mindent jól csináltunk, akkor valami ilyet láthatunk:


A kép jobb alsó sarkában állíthatjuk át a sebességet.

Ha áramtalanítás után az RTC chip a 2165/165/165 165:165:85 időt adja vissza, akkor a chip szünetmentes táplálása szenvedett csorbát. Ekkor az elem kontakthibájára illetve kimerültségére gondolhatunk. Nagyon ritka esetben felmerülhet még a szünetmentes kör zárlata, forrasztási hibája illetve a nyomtatott áramkör szakadása.
De egyszerűen az is lehet, hogy még soha nem állíottuk be a chipet! Ha már van pontos időnk, akkor amíg az elem benn van (és nem merül ki), addig a múló idő rendelkezésünkre áll.

Idő beálltás

Az idő beállítása az RTC.adjust sorral történhet:

  // following line sets the RTC to the date & time this sketch was compiled
  RTC.adjust(DateTime(__DATE__, __TIME__));

Az utasítássor nagyon beszédes: a __DATE__ és a __TIME__ helyére a fordításkor a PC pontos ideje kerül. Így minden egyes induláskor ez a kiindulási időpont. Persze, ha hibásan van beállítva a számítógép órája, akkor a programunk is hibás időpontot érzékel a kezdés időpontjaként. A program lefordítása után azonnal fel kell tölteni és futtatni. A fordítás és a futtatás közt eltelt idővel az óránk "csúszni" fog. A soros terminalban láthatjuk a beállított időt. Fontos! Az időbeállítás után a programkódot írjuk felül pl. a blink programmal. Ha bennemarad a chipben, minden elemcsere utáni indításkor újra és újra megtörténik az időállítás. 

A beállítás után, ha új gombelemet használunk - akkor 5 évig eljár róla az óra. Ha nagykapacitású kondenzátort, akkor áramtalanítás után 8-10 hétig működik. És újbóli áram alá helyezés után 10 msec alatt újra teljesen feltöltődik a kondenzátor!

Idő kiolvasása

Most, hogy végre van egy működő óránk, itt az idő, hogy ezt kicsavarjuk belőle. Egy kakukkosórát elég felhúzni, beállítani majd rápillantani. Félvezető alapon ez kicsit bonyolultabb....

void loop () {
    DateTime now = RTC.now();

    Serial.print(now.year(), DEC);
    Serial.print(''/'');
    Serial.print(now.month(), DEC);
    Serial.print(''/'');
    Serial.print(now.day(), DEC);
    Serial.print('' '');
    Serial.print(now.hour(), DEC);
    Serial.print('':'');
    Serial.print(now.minute(), DEC);
    Serial.print('':'');
    Serial.print(now.second(), DEC);
    Serial.println();

Ez egy egyszerű módja az idő kiolvasásának. Az RTClib eljáráskönyvtár now() függvényét használjuk fel. Ez a megoldás adja vissza a datetime változótípusként az idő egyes részeit: az órát, percet, másodpercet, napot, hónapot és az évet.
Az RTClib számos belső függvényt tartalmaz, melyek az adatok megjelenítéséért felelősek. Ilyen például az RTC.month(), ami az időadat hónapértékét adja vissza. De minden időegységre van beépített függvény.
Az RTClib függvénynek van egy óriási hibája: tegyük fel, hogy lekérdezzük az időt. Ez a program indulásakor: 16:45:59.99. A lekérdezés visszaadja az évet, honapot, napot; majd az óra, perc, másodpercet. Itt meglepődve látjuk, hogy az idő 16:45:00. Azaz egy percet késtünk. A következő beolvasás pedig 16:46:00. Akkor most hol a hiba?

Az óra váltásánál. Mert mit csinál a függvényünk?
Minden egyes kiíráskor lekérdezi a chipből az időt - és megkapjuk abból a nekünk kellő részt. És, ha kiírás közben az órachip pont léptet, akkor hamis óraadatot kapunk. Néhány chip ad vissza adatkonzisztencia jelzést (azaz egy "kiolvasás alatt" történt-e léptetés). Ezzel - sajnos - nincs megoldva a problémánk. A függvénykönyvtár kódjába belelesve (ez az RTClib.h és RTClib.cpp névre hallgat) - egy kis meglepetés ér. Igen, itt már "csúnyább" a program, hisz az Arduino egyszerű nyelvéhez vagyunk szokva. Ami látszik a függvényből: minden egyes kiíráskor (óra, nap, stb.), újra kiolvassuk az idő megfelelő részét a chipből.  Itt van az eb elhantolva. De mit tehetünk?
A legegyszerűbb megoldás: kiolvassuk egyszerre az idő összes részét, majd utána kezdjük darabolni... Ám ezt magunknak kell megírni, az RTClib nem tartalmazza.

Ha eltelt időre van szükségünk két időpont közt, akkor erre használhatjuk a beépített millis() függvényt. De ha valódi időpecsétre van szükségünk, akkor az ún. Unixtime használható e célra. Ez nem más, mint az 1970. január 1. szilvester éjjel 00:00:00 óta eltelt idő - másodpercben.

    Serial.print(" since 2000 = ");
    Serial.print(now.unixtime());
    Serial.print("s = ");
    Serial.print(now.unixtime() / 86400L);
    Serial.println("d");

Ha pedig nem abszolút időt, hanem időkülönbséget akarunk mérni, akkor a régi és a jelen idő különbséget kell megvizsgálni...''

Bascom órakezelés

Az órakezelés Bascom alatt sokkal egyszerűbben megoldható, mint az Arduino alatt. Ám ehhez jó, hogyha kéznél van a DS1307-es chip adatlapja (kattints ide a Maxim-Dallas oldalához).

''*********************************************
''* Title:      DS1307 RTC                    *
''* About:      shows how to use the ds1307   *
''*             clock on Arduino & SDlogger   *
''              shows the CONFIG CLOCK=USER   *
''* Filename:   ds1307.bas                    *
''* Compiler:   Bascom-AVR 2.0.7.5            *
''*                                           *
''* Author:     Robert Cseh                   *
''* E-mail:     avr /kukac/ tavir /pont/ hu   *
''* Homepage:   http://www.tavir.hu           *
''*********************************************

   $crystal = 16000000
     ''Sebesseg
   $regfile = "M328pdef.dat"
     ''Chip
   $baud = 9600
   $framesize = 16
   $hwstack = 24
   $swstack = 16

   $lib "mcsbyte.lbx"
     '' kisebb kód
   $lib "ds1307clock.lib"
     '' datetime kibővítés

  ''I2C konfigurálása
   Config I2cdelay = 10
     ''lassú mód, alapesetben:10
   Config Sda = Portc.4
   Config Scl = Portc.5

   ''DS1307 címe
   Const Ds1307w = &HD0
   Const Ds1307r = &HD1
   Config Clock = User
     ''hiányzó óraváltozó
   Dim _weekday As Byte
   Config Date = Ymd , Separator = Minus
     ''Dátumforma

   Print "DS1307"
   Waitms 100
     ''Startidőpont
   Time$ = "23:58:59"
   Date$ = "13-03-14"
     '' 2013. marc. 14
   Do
     Print "Date Time : " ; Date$ ; " " ; Time$
     Waitms 500
   Loop

   End


''ds1307clock.lib által meghívott külső rutin
Getdatetime:
  I2cstart                                                  '' Generate start code
  I2cwbyte Ds1307w                                          '' send address
  I2cwbyte 0                                                '' start address in 1307

  I2cstart                                                  '' Generate start code
  I2cwbyte Ds1307r                                          '' send address
  I2crbyte _sec , Ack
  I2crbyte _min , Ack                                       '' MINUTES
  I2crbyte _hour , Ack                                      '' Hours
  I2crbyte _weekday , Ack                                   '' Day of Week
  I2crbyte _day , Ack                                       '' Day of Month
  I2crbyte _month , Ack                                     '' Month of Year
  I2crbyte _year , Nack                                     '' Year
  I2cstop
  _sec = Makedec(_sec) : _min = Makedec(_min) : _hour = Makedec(_hour)
  _day = Makedec(_day) : _month = Makedec(_month) : _year = Makedec(_year)
Return

Setdate:
  _day = Makebcd(_day) : _month = Makebcd(_month) : _year = Makebcd(_year)
  I2cstart                                                  '' Generate start code
  I2cwbyte Ds1307w                                          '' send address
  I2cwbyte 4                                                '' starting address in 1307
  I2cwbyte _day                                             '' Send Data to SECONDS
  I2cwbyte _month                                           '' MINUTES
  I2cwbyte _year                                            '' Hours
  I2cstop
Return

Settime:
  _sec = Makebcd(_sec) : _min = Makebcd(_min) : _hour = Makebcd(_hour)
  I2cstart                                                  '' Generate start code
  I2cwbyte Ds1307w                                          '' send address
  I2cwbyte 0                                                '' starting address in 1307
  I2cwbyte _sec                                             '' Send Data to SECONDS
  I2cwbyte _min                                             '' MINUTES
  I2cwbyte _hour                                            '' Hours
  I2cstop
Return

Bascom-AVR alatt a beépített Date$ és Time$ függvényeken keresztül a belső eljárások kerülnek meghívásra. Felhasználóként mind számként, mind szövegként rendelkezésre állnak az időjellemzők. Míg a Bascom-AVR alatt az órabeállítás és kezelés azonnal működik,. Nem egyszerűbb? Sőt míg az Arduino kód ~4 kbyte, addig a Bascom alatt ez ~1.2 kbyte

Kapcsolódó leírások:

Kapcsolódó áramkörök:

Források:

 

TavIR-Facebook