PCF8563 RTC óra

Kezdetben a DS3231 RTC óra chip-et illetve modult kezdtem el használni. Minden webáruházban ebbe akad bele először, aki óra IC-t keres megának. Nincs is vele semmi baj, nagyon pontos, hőmérsékelt kompenzált, két időpontot is lehet riasztásra használni stb. Egy aprócska problémám adódott. Az IC INT kimenete, amit egy ATmega chip felélesztésére szerettem volna használni, csak bekapcsolt tápfesz esetén működik. Ha elemről működik az óra IC, akkor az INT kimenete nem küld megszakítást. Viszont, ha tápfesszel látom el folyamatosan a modult (mert én nem csupasz IC-t használtam), már elég tekintélyes áramfelvétel adódott, ami elemes áramköröknél nagy hátrány. Hát szétnéztem, hogy mi van még a piacon. Találtam is egy őskövületet az 1995 környékéről. Ez a PCF8563 IC-re épülő modul. Áramfelvétele ha nem használom az I2C kommunikációt, akkor szinte elenyésző, adatlap szerint 250-300nA. Az INT kimenete mindig működik, mert ez az IC annyira korszerűtlen, hogy nincs külön elemes táplálásra szolgáló tápfesz bemenete, nem is tud arról, hogy elemről működik. Szóval az én céljaimra tökéletes. Bónusz tulajdonság, hogy van beépített timer, ami beállítható időnként ad megszakítás jelet az INT kimeneten, vagyis nem kell azzal vacakolnom, hogy egy megszakítást követően kiszámolgassam a következő riasztási időpontot és beállítsam a riasztási idő regiszterekben. Hátrány, hogy ez a timer nem egy rugalmasan beállítható darab, de esetemben néhány perces, esetleg néhány órás időintervallumokat szerettem volna beállítani, így számomra ez is tökéletes.

Lássunk néhány műszaki adatot:

  • tápfeszültség:1,8V – 5,5V
  • áramfelvétel ha nem használom az I2C kommunikációt: 250nA
  • timer intervallumok: ~2mikrosec-től 4 óra 15 percig. (hosszú időzítéseknél 1 perces felbontással)

Az óra IC beállítása nagyon hasonló a DS3231-nél használatos megoldásokhoz. Van egy csomó regisztere, amiket lehet írni és olvasni. Az I2C buszon az IC a Hex 0x51 címen (decimális 81) érhető el. A regisztereinek összefoglaló táblázata:

A 0-val jelölt biteket mindig 0-val kell írni, olvasáskor a kiolvasott érték 0 és 1 is lehet.
Az „x”-el jelölt bitek nincsenek használva. Regiszter beállításakor tetszőleges értéket lehet írni az adott bit pozícióba, amikor ezeket visszaolvassák, a visszaolvasott értékek eltérhetnek beírt értéktől.

Néhány tulajdonság azoknak, akik használni szeretnék:

  • Az idő beállításához az I2C buszon keresztül az 0x02-től az 0x08 regiszterig kell beírni az adatokat nagyrészt BCD kódolással. Kiolvasni ugyanezeket a regisztereket kell.
  • A riasztási időpont beállítása a 0x09-tól a 0x0C regiszterekkel lehetséges. Csak az óra és perceket állíthatjuk be, azonban lehetőség van óránként azonos percben, naponta, hónap adott napján, vagy hét adott napján történő ismétlésre. Ezt úgy lehet elérni, hogy a regiszterek balról első bitjébe 1-et kell írni annál a paraméterben, aminek az egyezőségét nem szeretnénk figyelni. Pl. ha csak a perc regiszterben adunk meg ezen a biten 0 értéket, a többi raisztás regiszterben itt 1-et írunk, akkor minden órában ugyanabban a percben fog riasztani. Ha pl. beállítjuk a percet, órát és a hét egy adott napját, akkor csak a hétnek az adott napján fog a megadott időben risztani. A példa programban csináltam egy setAlarm() nevű függvényt négy bemenő paraméterrel (sorrendben: perc, óra, hónap napja, hét napja). Néhány példa ezzel a függvénnyel:
    – riasztás minden óra minden adott (15.) percében -> setAlarm(15,99,99,99)
    – riasztás minden nap 15 óra 14 perckor -> setAlarm(14,15,99,99)
    – riasztás minden hónap 5. napján 15 óra 14 perckor -> setAlarm(14,15,5,99)
    – riasztás minden hétfőn (1 nap) 15 óra 14 perckor -> setAlarm(14,15,99,1)
  • Létezik egy időintervallum beállítási lehetőség, aminek a segítségével ciklikusan a beállított időintervallumban keletkezik riasztás. Az intervallum megadáshoz egy byte áll rendelkezésre, tehát 1-255 közötti értéket állíthatunk be. Erről az értékről mindig visszaszámol 0-ra, és ekkor ad megszakítás jelet az INT kimeneten. Persze a visszaszámlálás sebessége beállítható. Az órajel ami ezt végzi 4096Hz, 64Hz, 1Hz, és 1/60Hz lehet. Ezzel gyakorlatilag kb. 2miliSec és 4 óra 15 perc között állíthatunk be riasztási (időzítési) időintervallumot. Azonban a beállítható idő felbontása is elég korlátozott, pl. a legnagyobb időzítésnél már csak 1 perc. De ugyan kit érdekel 4 órás időzítésnél, hogy nem adhatja meg másodperc pontosan az időintervallumot!
    További hátrány, hogy a visszaszámlálás órajelét a folyamatosan működő órajel osztóból kapja a visszaszámoló regiszter, így az első számlálási időzítés meglehetősen pontatlan is lehet. Amikor elindítjuk a visszaszámlálást, szerencsétlen esetben szinte azonnal megkapjuk ez első visszaszámoló impulzust, miközben a következőre már fix és pontos időt kell várni. Pl. ha az időalap 1/60Hz, és 2-et állítunk be a timer regiszterbe, akkor lehet hogy az első időzítésünk 1 perc lesz, míg ettől kezdve a többi már stabilan és pontosan az elvárt 2 perc. Ezzel meg kell békélni! Ez kőkorszaki masina, lehet, hogy még ma is kőbaltával faragják a szilicium lapkát.
  • Az időpont és a visszaszámláló timer riasztás szabadon keverhető, akár mindkettő működhet egyszerre. Azt, hogy az INT kimenet melyik riasztásra reagáljon 0 szinttel (folyamatosan magas a kimenet, de riasztáskor lehúzza az IC 0-ra), az a STATUS2 regiszterben beállítható. Jobbról a második AIE (alarm interrupt enable) bittel az időpont riasztás, míg jobbról az első TIE (timer interrupt enable) bittel a timer riasztás megszakítást engedélyezhetjük. Ha bármelyik 1, akkor az INT kimenet 0 lesz riasztáskor. A riasztás bekövetkeztét pedig jobbról a 4. AF (alarm flag) és jobbról a 3. TF (timer flag) bitek jelzik. Ezek 1-re billennek be, ha elérte az óra a beállított időpontot, vagy a timer leszámolt 0-ra. Azt nagyon fontos tudni, hogy a riasztás bebillenti a megfelelő bitet (AF vagy TF), de semmi nem billenti vissza 0-ba. Márpedig az INT kimenet mindaddig 0, amíg ezen bitek valamelyike 1. Tehát ha egy riasztás megtörtént, akkor szoftverből gondoskodni kell ezen bitek törléséről. Ha a megfelelő bitet töröltük, az INT azonnal újra 1 lesz, és várhatjuk is a következő riasztás.
  • A timer riasztás használatában van egy picike könnyítés. Ha a STATUS2 regiszter jobbról 5. bitjét a TI/TP bitet 1-re állítjuk, akkor nem kell a TF bittel vacakolni. Minden egyes visszaszámlálás okozta riasztás után az INT kimenet vissza áll 1-re. A visszaállási idő késleltetése a visszaszámlálási frekvenciától függ, de maximális értéke is 1/64 másodperc. Végül is ez közömbös, hiszen itt a lefutó élet fogjuk figyelni.  

Ehhez az RTC óra chip-hez nem találtam számomra megfelelő könyvtárat. Van könyvtár hozzá, de túl sok függvényt valósítottak meg benn, külön függvény van a dátum beállításra, az idő beállításra, külön lehet lekérdezni az órát, percet stb. Írtam hát egy saját függvény gyűjteményt, amit a végletekig egyszerűsítettem, vagyis minimalizáltam a meghívható függvények számát. Ha valaki hasznát veszi, annak alább a minta program, és részletes kommentekkel az összes függvény. Sok sikert a használatához!

#include <Wire.h>
int PCF8563_I2C_cim = 81;

// globális változók az időpont tárolásához. A getDateTime() függvény 
// ezen változók érétkét tölti az aktuális dátummal és időponttal
byte ora;
byte perc;
byte sec;
byte nap;
byte het_napja;
byte honap;
int ev;
// globális változók a riasztási időpontok és flag-ek tárolásához. A getAlarm() függvény
// ezen változók értékét tölti fel az aktuális riasztási időpont adatokkal
byte alarm_ora;
byte alarm_perc;
byte alarm_nap;
byte alarm_het_napja;
bool alarm_flag; //a getAlarm() geletölti az STATUS2 regiszter AF flag állapotát (0 esetén még nem volt riasztás)
bool timer_flag; //a getAlarm() geletölti az STATUS2 regiszter TF flag állapotát (0 esetén még nem volt riasztás)
bool int_alarm; //a getAlarm() geletölti az STATUS2 regiszter AIE flag állapotát (0 esetén INT tiltva, 1 engedélyezve)
bool int_timer; //a getAlarm() geletölti az STATUS2 regiszter TIE flag állapotát (0 esetén INT tiltva, 1 engedélyezve)
byte timer; //a getAlarm() geletölti az timer számláló regiszter (0x0F) tertalmát, ha 0-ra ér, akkor riaszt (TF=1)

//byte status2;
long ido_tmp=millis();  //segéd változó a másodpercenkénti idő kiíráshoz

void setup()
{
  pinMode(13, OUTPUT);           // A beépített LED fogj jelezni a PCF8563 INT kivezetésének állapotát
  pinMode(3, INPUT);           // Ezen a bemeneten fogjuk figyelni PCF8563 INT kivezetésének állapotát
  digitalWrite(3, HIGH);       // felhúzó ellenállás bekapcsolása a 3-as bemeneten
  //megvillogtatjuk a beépített ledet, hogy lássuk az alaplapon melyik az
  delay(500);
  digitalWrite(13,HIGH);delay(100);digitalWrite(13,LOW);delay(100);digitalWrite(13,HIGH);delay(100);digitalWrite(13,LOW);delay(100);
  digitalWrite(13,HIGH);delay(100);digitalWrite(13,LOW);delay(100);digitalWrite(13,HIGH);delay(100);digitalWrite(13,LOW);
  delay(500);
  Wire.begin();
  Serial.begin(9600);
  
  //Egyszer be kell állítani a dátumot és az órát
  //Paraméterek sorrendje: év, hó, nap, hét napja, óra, perc
  //setDateTime(2020,9,8,2,7,43);

  //a riasztási üzemmódok beállítása
  // Paraméterek sorrendje: időpont riasztás, timer riasztás
  // Nem szükséges kiadni, mert setAlarm() és a setTimer() is beállítja magának
  // a szükséges biteket a STATUS2 regiszterben. Azonban flag-ek törlésére és 
  // a megszakítás (INT) kimenet tiltására ezt lehet felhasználni
  // Az időpont és a timer riasztás szabadon keverhető, egyszerre mindkettőt
  // be lehet kapcsolni
  setAlarmTimerMode(0,0);
  
  // Ha az időpont riasztást teszteljük, akkor setAlarm() fügvénnyel állíthatjuk be az időpontot
  // Paraméterek sorrendje: perc, óra, nap, hét napja
  //   Ha periódikus riasztást akarsz, akkor 99-et kell megadni annál a paraméternél, 
  //   amit nem kell vizsgálnia a chip-nek
  //   pl. minden óra minden adott (15.) percében -> 15,99,99,99
  //   pl. minden nap 15 óra 14 perckor -> 14,15,99,99
  //   pl. minden hónap 5. napján 15 óra 14 perckor -> 14,15,5,99
  //   pl. minden hétfőn (1 nap) 15 óra 14 perckor -> 14,15,99,1
  setAlarm(46,99,99,99);
  
  //Riasztás beállított időintervallumonként (ismétlődő).
  //Paraméterek sorrendje: időzítési idő (másodperc)
  //Az időzítési idő csak 1-tól 15300 másodpercig (aza 255 perc, ami 4óra 15 perc) terjedhet
  //255 másodperc után már csak perc pontosságú azaz, egész percre kerekíti a beállított értéket (lefele kerekít egészre)
  setTimer(12); //10 másodperc


  getAlarm();  //lekérdezi a riasztással kapcsolatos paramétereket és biteket
  Serial.println("Ido riasztás status:"); 
  Serial.println("===================="); 
  Serial.print("           AF flag:"); Serial.println(alarm_flag);
  Serial.print(" Megszakítás (INT):"); Serial.println(int_alarm);
  Serial.print("              Perc:"); if (alarm_perc==99) {Serial.println("-");} else {Serial.println(alarm_perc);} 
  Serial.print("               Ora:"); if (alarm_ora==99) {Serial.println("-");} else {Serial.println(alarm_ora);} 
  Serial.print("       Honap napja:"); if (alarm_nap==99) {Serial.println("-");} else {Serial.println(alarm_nap);}
  Serial.print("         Het napja:"); if (alarm_het_napja==99) {Serial.println("-");} else {Serial.println(alarm_het_napja);} 
  Serial.println("Timer riasztás status:"); 
  Serial.println("======================"); 
  Serial.print("           TF flag:"); Serial.println(alarm_flag);
  Serial.print(" Megszakítás (INT):"); Serial.println(int_timer);
  Serial.print("          Szamlalo:"); Serial.println(timer);
}

void loop()
{
  if (ido_tmp<millis()){      //másodpercenként kiirjuk az időt a soros portra
    getDateTime();
    Serial.print("Datum:"); Serial.print(ev); Serial.print(":"); Serial.print(honap); Serial.print(":"); Serial.print(nap);
    Serial.print("  Het napja:"); Serial.print(het_napja);
    Serial.print("  Ido:"); Serial.print(ora); Serial.print(":"); Serial.print(perc); Serial.print(":"); Serial.println(sec);
    ido_tmp=millis()+1000;
  }
  if (digitalRead(3))          //AZ INT kimenet állapotát beolvassuk 
    {digitalWrite(13,HIGH);}   //INT kimenet HIGH, tehát nincs megszakítás
  else {
    digitalWrite(13,LOW);      //INT kimenet LOW, tehát megszakítás érkezett, törölni kell a TF bitet
    Serial.println("Riasztas");
    delay(200);                //ha ezt nem tesszük be, nem látható, hogy a led egy pillanatra elalszik amikor riasztás jött. 
    clearAlarm();              //Törli az AF és a TF bitet a status2 regiszterben, így jöhet egy újabb riasztás ami majd bebillenti ezek valamelyikét
  }
}

void clearAlarm()
/********************************************************************************************************
 * STATUS2 (0x01) regiszter AF (BIT3) és TF (BIT2) törlése                                              *
 * Amikor az időpont riasztás megtörténik az AF flag bebillen 1-re. Ha a timer riasztást történik meg   *
 * a TF flag billen be 1-re. Egy újabb riasztást csak akkor lehet észre venni, ha ezen biteket 0-ra     *
 * billentjük vissza. Mivel az INT kimenet akkor generál megszakítást, ha az AF vagy a TF 1, ezért      *
 * Ezen bitek törlése az INT kimeneten is megszünteti a LOW szintet. Az INT LOW szintű megszakításához  *
 * még engedélyezni kell a megszakítás láb (INT) működését is a AIE illetve a TIE bitekkel.             *
 ********************************************************************************************************/
{
  Wire.beginTransmission(PCF8563_I2C_cim);  //megszólítjuk a slave egységet a címével
  Wire.write(0x01);                         //megadjuk a STATUS2 regiszter címét
  Wire.endTransmission();                   //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.requestFrom(PCF8563_I2C_cim, 1);     //1 byte-ot kérünk az I2C buszon a slave-től
  byte status2 = Wire.read();                    //beolvassuk a STATUS2 regiszter tartalmát
  status2=status2 & 0b11110011;
  Wire.beginTransmission(PCF8563_I2C_cim);  //megszólítjuk a slave egységet a címével
  Wire.write(0x01);                         //megadjuk a STATUS2 regiszter címét
  Wire.write(status2);                      //Írjuk a STATUS2 regisztert az új értékével
  Wire.endTransmission();                   //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
}

void setTimer(int masodperc)
/***************************************************************************************************************
 * Beállítja a timert, ami ciklikusan megszakítást generál az INT kimeneten, amikor a számláló 0-ra számol     *
 * vissza a beállított értékről (max 255). Ha a STATUS2 regiszterben a TI_TP bit értéke 0, akkor a TI bitet    *
 * a STATUS2 regiszterben minden megszakítás után törölni kell, különben INT folyamatosan alacsony marad       *
 * az első generált timer riasztás után. Ha TI_TP bit értéke 1, akkor az INT kimenet egy órajel ciklus után    *
 * automatikusan HIGH lesz, vagyis nem kell törölni a TF bitet. Ez a függvény nem használja ki ezt a           *
 * lehetőséget, mert így egységesen ugyanúgy kell lekezelni a timer riasztást, mint ha időpont riasztás        *
 * történt volna.                                                                                              *
 * Bemenő paramétere a visszaszámlálási ciklus időtartama másodpercben megadva. Ennek lehetséges intervalluma  *
 * 1-15300 sec. Amíg a bemenő paraméter értéke 1-255, addig a timer számláló visszaszámoló időalapja           *
 * 1hz-ra állítódik be, ha az érték 255-nél nagyobb, akkor 1/60Hz-re. Utóbbi esetben már csak egy perces       *
 * felbontásban lehet a riasztást beállítani 1-255 perc között. Ha ennél pontosabb beállítás szükséges         *
 * az időpont riasztást lehet felhasználni a következő riasztási időpont kiszámolásával és beállításával       *
 * Az első timer riasztás időintervalluma nem pontos. Az 1/60Hz előállításakor az órajel az óra egész          *
 * perceikor keletkezik, így szerencsétlen esetben akár 1 perccel kevesebb is lehet az eltelt idő.             *
 * Pl 4. perc beállításakor (bemenő paraméter 256), és ezt pont az adott perc 59. másodpercében állítjuk       *
 * be, a következő másodpercben már számol is egyet vissza a timer, így 3 perc múlva jön a riasztás. A további *
 * időintervallumok már pontosak. Ez a működés minden bizonnyal igaz a többi visszaszámolási frekvenciára      *
 * is, tehát pl. az 1Hz frekvencia is egész másodperc értékeknél keletkezik.                                   *
 * Időalap lehetséges értékei:                                                                                 *
 *  1 - 0,000244140625 sec (0,244 milisec, 0,24 msec - 62,2 msec) ==> BIT1=0,BIT0=0                            *
 *  2 - 0,015625 sec (15,6 milisec 15 msec - 3,98 sec)==> BIT1=0,BIT0=1                                        *
 *  3 - 1 sec (1 sec - 4,25 perc)==> BIT1=1,BIT0=0                                                             *
 *  4 - 1/60 sec (60 sec 1 perc - 4,25 óra)==> BIT1=1,BIT0=1                                                   *
 *   (ezekből mi csak a 3-as és 4-es paramétert fogjuk használni)                                              *
 ***************************************************************************************************************/
{
  byte ciklus;                              //ebben a változóban adjuk meg a "timer" regiszter értékét 
  byte timercontrol;                        //ebben a változóban képezzük a "timer control" regiszter értékét
  if (masodperc == 0) {ciklus = 1;}         //a ciklus minimuma 1
  if (masodperc<256){timercontrol = 0b10000010;ciklus=masodperc;} //BIT7=1 engedélyezi a timer működését és 1Hz-es visszaszámolást állít be
  else {timercontrol = 0b10000011;ciklus=masodperc/60;} //BIT7=1 engedélyezi a timer működését és 1/60Hz-es visszaszámolást állít be
  Wire.beginTransmission(PCF8563_I2C_cim);  //megszólítjuk a slave egységet a címével
  Wire.write(0x01);                         //megadjuk a STATUS2 regiszter címét
  Wire.endTransmission();                   //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.requestFrom(PCF8563_I2C_cim, 1);     //1 byte-ot kérünk az I2C buszon a slave-től
  byte status2 = Wire.read();                    //beolvassuk a STATUS2 regiszter tartalmát
  status2 = status2 & 0b11101111;           //A BIT4 TI/TP=0, TI-t mindig törölni kell riasztás után
  status2 = status2 & 0b11111011;           //A BIT2 TF=0, a következő timer riasztás bebillentheti 1-re
  status2 = status2 | 0b00000001;           //A BIT0 TIE=1, engedélyezett az interrupt kimenet (INT) amikor TF bebillen 1-re
  Wire.beginTransmission(PCF8563_I2C_cim);  // megszólítjuk a slave egységet a címével
  Wire.write(0x01);                         //megadjuk a STATUS2 regiszter címét
  Wire.write(status2);                      //írjuk a STATUS2 regisztert az új értékével
  Wire.endTransmission();                   //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.beginTransmission(PCF8563_I2C_cim);  // megszólítjuk a slave egységet a címével
  Wire.write(0x0E);                         //"timer control" regiszter címzése
  Wire.write(timercontrol);                 //írjuk a "timer control" regisztert az új értékével
  Wire.write(ciklus);                       //visszaszámlálási ciklusszám beállítása
  Wire.endTransmission();                   //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
}



void setAlarm(byte perc, byte ora, byte nap, byte het_napja)
/*******************************************************************************************************************
 * Ez a függvény beállítja a riasztási időpontot. A lehetséges parméterek a perc, óra nap és hét napja             *
 * A beállított időpont ciklikusan ismétlődő riztást fog eredményezni amikor a beállított paraméter megegyezik     *
 * az aktuális időponttal. Pl. ha csak a percet adjuk meg, akkor minden órában időpont riasztás fog generálódni.   *
 * Ha egy paramétert nem akarunk megadni, akkor annak értéke legyen 99. Ekkor az adott paramétert Pl. hét napját   *
 * Nem fogja figyelni, és csak a többi paraméter egyezőségét figyeli.                                              *
 * Pl.  riasztás minden óra minden adott (15.) percében -> setAlarm(15,99,99,99)                                   *
 *      riasztás minden nap 15 óra 14 perckor -> setAlarm(14,15,99,99)                                             *
 *      riasztás minden hónap 5. napján 15 óra 14 perckor -> setAlarm(14,15,5,99)                                  *
 *      riasztás minden hétfőn (1 nap) 15 óra 14 perckor -> setAlarm(14,15,99,1)                                   *
 * A STATUS2 regiszterben az AF bitet minden egyes riasztás után törölni kell, különben a következő ciklikus       *
 * riasztás nem jut érvényre hiszen már 1 az AF értéke. Ez a függvény a riasztási időpont beállításakor            *
 * beállítja megának a STATUS2 regiszterben az AF bitet 0-ra, valamint beállítja 1-re az AIE bitet, ami            *
 * engedélyezi az INT kimenet működését. INT kimenet LOW szinttel jelzi a riasztás bekövetkeztét. INT akkor        *
 * lesz újra HIGH, ha az AF bitet töröljük, egyébként folyamatosan LOW marad a riasztás bekövetkezte után.         *
 *******************************************************************************************************************/
{
  Wire.beginTransmission(PCF8563_I2C_cim);  // megszólítjuk a slave egységet a címével
  Wire.write(0x01);                         //megadjuk a STATUS2 regiszter címét
  Wire.endTransmission();                   //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.requestFrom(PCF8563_I2C_cim, 1);     //1 byte-ot kérünk az I2C buszon a slave-től
  byte status2 = Wire.read();                    //beolvassuk a status regiszter tartalmát
  if (perc < 99) {                          //Ha a perc nem 99, akkor értékét figyelni fogja a chip riasztáskor
    perc=constrain(perc, 0, 59);
    perc=decToBcd(perc);
    perc=perc & 0b01111111;                 //ha 0x09 regiszter 7. bitje 0, ekkor a regiszter értékét is figyeli riasztáskor
  } 
  else {perc = 0b10000000;}                 //ha 0x09 regiszter 7. bitje 1, ekkor a regiszter értékét nem figyeli riasztáskor
  if (ora < 99) {                           //Ha az óra nem 99, akkor értékét figyelni fogja a chip riasztáskor
    ora=constrain(ora, 0, 23);
    ora=decToBcd(ora);
    ora=ora & 0b01111111;                   //ha 0x0A regiszter 7. bitje 0, ekkor a regiszter értékét is figyeli riasztáskor
  } 
  else {ora = 0b10000000;}                  //ha 0x0A regiszter 7. bitje 1, ekkor a regiszter értékét nem figyeli riasztáskor
  if (nap < 99) {                           //Ha a nap nem 99, akkor értékét figyelni fogja a chip riasztáskor
    nap=constrain(nap, 1, 31);
    nap=decToBcd(nap);
    nap=nap & 0b01111111;                   //ha 0x0B regiszter 7. bitje 0, ekkor a regiszter értékét is figyeli riasztáskor
  } 
  else {nap = 0b10000000;}                  //ha 0x0B regiszter 7. bitje 1, ekkor a regiszter értékét nem figyeli riasztáskor
  if (het_napja < 99) {                     //Ha a hét napja nem 99, akkor értékét figyelni fogja a chip riasztáskor
    het_napja=constrain(het_napja, 0, 6);
    het_napja=decToBcd(het_napja);
    het_napja=het_napja & 0b01111111;       //ha 0x0C regiszter 7. bitje 0, ekkor a regiszter értékét is figyeli riasztáskor
  } 
  else {het_napja = 0b10000000;}            //ha 0x0C regiszter 7. bitje 1, ekkor a regiszter értékét nem figyeli riasztáskor
  status2 = status2 & 0b11110111;           //A BIT3 AF=0, a következő riasztás bebillentheti 1-re
  status2 = status2 | 0b00000010;           //A BIT1 AIE=1, engedélyezett az interrupt kimenet
  Wire.beginTransmission(PCF8563_I2C_cim);  // megszólítjuk a slave egységet a címével
  Wire.write(0x01);                         //STATUS2 regiszter címét állítjuk be a slave-en
  Wire.write(status2);                      //kiírjuk az I2C buszon a STATUS2 regiszter új tartalmát a slave-re
  Wire.endTransmission();                   //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.beginTransmission(PCF8563_I2C_cim);  //megszóllítjuk a I2C buszon a slave-et
  Wire.write(0x09);                         //alarm perc regiszter címét állítjuk be
  Wire.write(perc);                         //beírjuk a riasztás perc értékét a 0x09 regiszterbe
  Wire.write(ora);                          //beírjuk a a riasztás óra értékét a 0X0A regiszterbe (soron következő cím a slave-en)
  Wire.write(nap);                          //beírjuk a a riasztás nap értékét a 0X0B regiszterbe (soron következő cím a slave-en)
  Wire.write(het_napja);                    //beírjuk a a riasztás hét napja értékét a 0X0C regiszterbe (soron következő cím a slave-en)
  Wire.endTransmission();                   //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
}

void setAlarmTimerMode(bool alarm, bool timer) 
/******************************************************************************************************************
 * Ez a függvény beállítja vagy törli az időpont illetve timer riasztás megfelelő bitjeit a STATUS2 regiszterben  *
 * Az AF és TF riasztás flegeket törli, a következő riasztás bebillentheti ezeket újra.                           *
 * bemenő paraméterek:                                                                                            *
 * alarm=1 AIE=1 időpont megszakítás engedélyezve az INT kimeneten                                                *
 * alarm=0 AIE=0 időpont megszakítás tiltva az INT kimeneten                                                      *
 * timer=1 TIE=1 timer megszakítás engedélyezve az INT kimeneten                                                  *
 * timer=0 TIE=0 timer megszakítás tiltva az INT kimeneten                                                        *
 ******************************************************************************************************************/
{
  Wire.beginTransmission(PCF8563_I2C_cim);   // megszólítjuk a slave egységet a címével
  Wire.write(0x01);                          //STATUS2 regiszter címét állítjuk be a slave-en
  Wire.endTransmission();                    //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.requestFrom(PCF8563_I2C_cim, 1);      //1 byte-ot kérünk az I2C buszon a slave-től
  byte status2=Wire.read();                  //beolvassuk a STATUS2 regiszter tartalmát
  status2=status2 & 0b11110011;              //A BIT3 AF, és BIT2 TF 0, a következő riasztás bebillentheti 1-re
  if (alarm) {status2=status2 | 0b00000010;} //A BIT1 AIE=1, engedélyezett az interrupt kimenet időpont riasztás esetén
  else {status2 = status2 & 0b11111101;}     //nem kell időpont riasztás, ezért AIE=0 (INT kimenet tiltva)
  if (timer) {status2=status2 | 0b00000001;} //A BIT0 TIE=1, engedélyezett az interrupt kimenet timer riasztás esetén
  else {status2 = status2 & 0b11111110;}     //nem kell timer riasztás, ezért TIE=0 (INT kimenet tiltva)
  Wire.beginTransmission(PCF8563_I2C_cim);   //vége az I2C tranzakciónak
  Wire.write(0x01);                          //STATUS2 regiszter címét állítjuk be a slave-en
  Wire.write(status2);                       //kiírjuk az I2C buszon a STATUS2 regiszter új tartalmát a slave-re
  Wire.endTransmission();                    //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
}

void setDateTime(int ev, byte honap, byte nap, byte het_napja, byte ora, byte perc)
/*******************************************************************************************************************
 * Beállítja az óra dátumát és időpontját. A másodperceket nem lehet állítani, az alapértelmezetten 0 beállításkor *
 *******************************************************************************************************************/
{
  Wire.beginTransmission(PCF8563_I2C_cim);               // megszólítjuk a slave egységet a címével
  Wire.write(0x02);                                      //megmondjuk a "second" regiszter címét
  Wire.write(decToBcd(0));                               //kiírjuk az I2C-ra a slave nek a 0x02 regiszterbe a másodperc értékét (0 a beállításkor)
  Wire.write(decToBcd(perc));                            ///kiírjuk az I2C-ra a slave nek a 0x03 regiszterbe a perc értékét
  Wire.write(decToBcd(ora));                             //kiírjuk az I2C-ra a slave nek a 0x04 regiszterbe az óra értékét
  Wire.write(decToBcd(nap));                             //kiírjuk az I2C-ra a slave nek a 0x05 regiszterbe a nap értékét
  Wire.write(decToBcd(het_napja));                       //kiírjuk az I2C-ra a slave nek a 0x06 regiszterbe a hét napjának értékét
  if (ev > 1999) {honap=decToBcd(honap);ev=ev-2000;}     //Ha az év 2000, akkor a 0x07 regiszter balról első bitje 0, vagyis a hónap értékét 
                                                         //változatlanul tároljuk, az év értékéből 2000-et kivonunk tárolás előtt 
  else {honap=decToBcd(honap) | 0b10000000;ev=ev-1900;}  //Ha az év 2000 előtti, akkor a hónap regiszter balról első bitje 1, és az 
                                                         //év értékéből 1900-at kell kivonni tárolás előtt
  Wire.write(honap);                                     //kiírjuk az I2C-ra a slave nek a 0x07 regiszterbe a hónap értékét
  Wire.write(decToBcd((byte)ev));                        //kiírjuk az I2C-ra a slave nek a 0x08 regiszterbe az év értékét
  Wire.endTransmission();                                //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
}

void getDateTime()
/*****************************************************************************************
 * Ez a függvény beolvassa a dátum és időpont értékeket a megfelelő globális változókba  *
 *****************************************************************************************/
{
  Wire.beginTransmission(PCF8563_I2C_cim);                  // megszólítjuk a slave egységet a címével
  Wire.write(0x02);                                         //megadjuk a second regiszter címét
  Wire.endTransmission();                                   //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.requestFrom(PCF8563_I2C_cim, 7);                     //megadjuk a slave-nak, hogy küldjön 7 byte-ot, a dátum és idő registereket
  sec=bcdToDec(Wire.read() & 0b01111111);                   //másodperc kiolvasás 0x02 "second" regiszterből, az elso bit nem kell a regiszterből, 
                                                            //mert az az óra integritásról tartalmaz információt
  perc=bcdToDec(Wire.read() & 0b01111111);                  //perc kiolvasás 0x03 "minute" regiszterből, az első bit nem kell a regiszterből
  ora=bcdToDec(Wire.read() & 0b00111111);                   //őra kiolvasás 0x04 "hour" regiszterből, az első bit nem kell a regiszterből
  nap=bcdToDec(Wire.read() & 0b00111111);                   //őra kiolvasás 0x05 "day" regiszterből, az első bit nem kell a regiszterből
  het_napja=bcdToDec(Wire.read() & 0b01111111);             //őra kiolvasás 0x06 "weekday" regiszterből, az első bit nem kell a regiszterből
  honap=Wire.read();                                        //őra kiolvasás 0x07 "mount" regiszterből, az első bit nem kell a regiszterből
  if (honap & 0b10000000) {ev=1900+bcdToDec(Wire.read());}  //a 0x07 "mount" regiszter balról első bitje 1, év kiolvasás 0x08 "year" regiszterből év=+1900 
  else {ev=2000+bcdToDec(Wire.read());}                     //a 0x07 "mount" regiszter balról első bitje 0, év kiolvasás 0x08 "year" regiszterből év=+2000 
  honap=honap & 0b00011111;honap=bcdToDec(honap);           //Az első három bit nem kell a hónaphoz
}


void getAlarm()
/******************************************************************************************************
 * Ez a függvény kiolvassa az időpont riasztási értékeket. Az egyes riasztási értékek byte-jainak     *
 * balról első bitje mindig azt adja meg, hogy figyeli-e a chip a rsiztás létrehozásakor az adott     *
 * értéket. Ha ez a bit 0, akkor az adotot figyeli, ha 1 akkor nem figyeli. Utóbbi esetben a függvény *
 * riasztási adatnak 99-et ad vissza, ezzel jelzi, hogy az adott adat nem vesz részt a riasztás       *
 * létrehozásában                                                                                     *
 ******************************************************************************************************/
{
  Wire.beginTransmission(PCF8563_I2C_cim);                 //megszólítjuk a slave egységet a címével
  Wire.write(0x09);                                        //megadjuk a "minute alarm" regiszter címét
  Wire.endTransmission();                                  //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.requestFrom(PCF8563_I2C_cim, 4);                    //megadjuk a slave-nak, hogy küldjön 4 byte-ot, a riasztási időpont registereket
  alarm_perc=Wire.read();                                  //riasztás perc kiolvasás 0x09 "minute alarm" regiszterből
  if(0b10000000 & alarm_perc){alarm_perc=99;}              //ha 0x09 regiszter balról első bitje 1, akkor a perc nem vesz részt a riasztásban (visszadott érték 99)
  else {alarm_perc=bcdToDec(alarm_perc);}                  //0x09 regiszter balról első bitje 0, visszaadjuk a beállított perc értéket
  alarm_ora=Wire.read();                                   //riasztás óra kiolvasás 0x0A "hour alarm" regiszterből
  if(0b10000000 & alarm_ora){alarm_ora=99;}                //ha 0x0A regiszter balról első bitje 1, akkor az óra nem vesz részt a riasztásban (visszadott érték 99)
  else {alarm_ora=bcdToDec(alarm_ora);}                    //0x0A regiszter balról első bitje 0, visszaadjuk a beállított óra értéket
  alarm_nap=Wire.read();                                   //riasztás perc kiolvasás 0x0B "day alarm" regiszterből
  if(0b10000000 & alarm_nap){alarm_nap=99;}                //ha 0x0B regiszter balról első bitje 1, akkor a hónap napja nem vesz részt a riasztásban (visszadott érték 99)
  else {alarm_nap=bcdToDec(alarm_nap);}                    //0x0B regiszter balról első bitje 0, visszaadjuk a beállított hónap napja értéket
  alarm_het_napja=Wire.read();                             //riasztás perc kiolvasás 0x0C "weekday alarm" regiszterből
  if(0b10000000 & alarm_het_napja){alarm_het_napja=99;}    //ha 0x0C regiszter balról első bitje 1, akkor a hét napja nem vesz részt a riasztásban (visszadott érték 99) 
  else {alarm_het_napja=bcdToDec(alarm_het_napja);}        //0x0C regiszter balról első bitje 0, visszaadjuk a beállított hét napja értéket
  Wire.beginTransmission(PCF8563_I2C_cim);                 //megszólítjuk a slave egységet a címével
  Wire.write(0x01);                                        //STATUS2 regiszter címét állítjuk be a slave-en
  Wire.endTransmission();                                  //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.requestFrom(PCF8563_I2C_cim, 1);                    //megadjuk a slave-nak, hogy küldjön 1 byte-ot, a STATUS2 registert
  byte status2=Wire.read();                                     //beolvassuk a STATUS2 regiszter tartalmát
  if(0b00001000 & status2){alarm_flag=1;}                  //kimaszkoljuk az AF bitet
  else {alarm_flag=0;}
  if(0b00000100 & status2){timer_flag=1;}                  //kimaszkoljuk a TF bitet
  else {timer_flag=0;}
  if(0b00000010 & status2){int_alarm=1;}                   //kimaszkoljuk a AIE bitet
  else {int_alarm=0;}
  if(0b00000001 & status2){int_timer=1;}                   //kimaszkoljuk a TIE bitet
  else {int_timer=0;}
  Wire.beginTransmission(PCF8563_I2C_cim);                 //megszólítjuk a slave egységet a címével
  Wire.write(0x0F);                                        //megadjuk a "timer" regiszter címét
  Wire.endTransmission();                                  //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.requestFrom(PCF8563_I2C_cim, 1);                    //megadjuk a slave-nak, hogy küldjön 1 byte-ot, a timer registert
  timer=Wire.read();                                       //timer regiszter értékének kiolvasása (pillanatnyi érték, közben lehet, hogy épp visszaszámol)
}


byte decToBcd(byte val)  //decimálisból BCD-be alakít
{return ((val/10*16)+(val%10));}

byte bcdToDec(byte val)  //BCD-ből decimálisba alakít
{return ((val/16*10)+(val%16));}



DS3231 RTC óra

Mivel az Arduino Uno R3-ban nincs beépített óra, a pontos idő kijelzéséhez valamilyen külső modulra lesz szükségünk. Elvben megírható lenne programként is egy óra funkció, hiszen a 16Mhz-es kvarcz kristály elég pontos órajelet állít elő egy átlagos időméréshez, de a program elég bonyolult lenne. Egyszerűbbnek tűnik egy külső óra modul beszerzése. Elsőre a DS3231 RTC modult választottam. Van azonban több beszerezhető modul is, itt megismerhetsz egy másikat is, ha ez nem fog tetszeni.

A DS3231 modul ára kb. 1000Ft. Nagy előnye még ennek a modulnak, hogy nem csak óra van benne, hőmérsékletet is képes mérni. A hőmérséklet mérés azért került a chip-be, mert a kvarc kristály frekvenciáját a hőmérséklet függvényében automatikusan korrigálja a chip. Így tudnak elérni évi egy perc körüli óra pontosságot. Egy 4Kbyte-os EEPROM-ot is tartalmaz a modul, amit a gyári adatlap szerint legalább 1 milliószor lehet újraírni, így korlátozottan adatgyűjtésre is felhasználható. A modul I2C buszon keresztül használható, így nem fog újabb kivezetéseket lefoglalni. Simán párhuzamosan köthetjük az LCD kijelzővel, és kész is a hőmérős óra!
Így néz ki a modul:

…és így kell összekötni az Arduino-val:

A kivezetéseiből a 32K jelű kivezetésen 32.768Hz négyszög jelet kapunk, ha ezt a lehetőséget szeretnénk és bekapcsoljuk. A SQW kivezetés lehet egy kimenet, ami két beállítható riasztási időpont elérkeztét jelzi, de lehetséges négyszög jelet is kapni rajta, ami 1Hz, 1024Hz, 4048Hz vagy 8096Hz frekvenciára állítható. Én ezeket még nem igazán tudtam semmire használni, de vannak! Már érik a gondolat bennem egy elemes adatgyűjtő elkészítésére, aminek a működésében szerepe lehet a beépített két riasztási időpontnak. Az elképzelés szerit az odagyűjtő program beállítja a következő mérés időpontját, és elmegy aludni. Amikor az időpont elérkezik, az óra modul felébreszti alvásból az ATmega328P chip-et. Azért nem az Arduino-t, mert annak nagyon nagy az alap fogyasztása az elemes tápláláshoz még akkor is, ha alszik a ráépített vezérlő. A chip elvégzi a szükséges méréseket, tárolja az adatokat az eeprom-ban, és újra aludni megy. Alvási üzemmódban rendkívül kicsi a fogyasztása, évekig működhet elemről. A DS3231 szintén működik elemről, sőt ezt már rá is építették, az én első példányom 4 éve megy, megy és egész pontos.

A modulnak az I2C buszon konkrét címe van, amit meg tudunk változtatni az A0,A1,A2 átkötések felhasználásával. Erre akkor lehet szükség, ha több hasonló modult is az I2C buszra szeretnénk kötni, és ütközne a címük.

Természetesen ehhez az óramodulhoz is rengeteg előre elkészített programkönyvtárat találunk. Az LCD kijelzőnél leírt módon nyissuk meg a “könyvtárak kezelése” menüpontot az Arduino IDE programban, és a keresőbe írjuk be a DS3231 szót. Már csak választani kell. Én azt szoktam használni az utóbbi időben, melynek neve pontosan “DS3231”. Érdemes tanulmányozni a példa programokat.

Most azonban megragadnám az alkalmat, és szeretném megmutatni, hogy mit is tartalmaznak ezek a programkönyvtárak belül a színfala mögött. Alább található egy olyan példaprogram, ami csak a “Wire.h”-t használja fel, ami az I2C kommunikációhoz szükséges. Nem vagyok annyira ügyes, hogy magamtól írtam az alábbi programot, az egyik DS3231 programcsomag forrást emeltem ki, és addig alakítgattam, amíg működni nem kezdett. Ezek a forráskódok a programkönyvtárak “cpp” kiterjesztésű állományaiban lelhetők fel. Ezeket az állományokat az AtmelStudió programmal sikerült megnyitnom, ez is ingyenesen letölthető, és C-ben lehet vele fejleszteni, de első körben nekem nagyon bonyolult. Arra azonban jó volt, hogy megnyissam a példa programot. Azóta rájöttem, hogy a Github.com weboldalon rengeteg forráskód megtalálható, és ott közvetlenül bele lehet olvasni ezekbe az állományokba. Itt egy példa a DS3231-hez: https://github.com/rodan/ds3231
A kódok szinte egy-az egyben átemelhetők. A fordító jelezni fog hibákat, egy-egy változó hiányzik stb., de még az én szerény tudásommal is sikerült átemelni programomba. Így maradt a végén a következő forrásprogram, amit kommentekkel láttam el. Talán hasznos lehet másnak is, hogy megismerje egy-egy modul alacsonyabb szintű használatát. A program megértéséhez feltétlenül szükséges még a DS3231 IC belső regisztereinek a tanulmányozása. A forráskód után megtaláljátok azt a táblázatot, amit az IC adatlapjából emeltem ki.

Itt a forráskód:

#define CLOCK_ADDRESS 0x68  //a modulon található óra IC címe az I2C buszon. 
                                                               //A cím vezetékek átforrasztásával változhat
#include <Wire.h>

int masodperc,perc,ora,nap,honap,ev,het_napja;
bool h12;             //ha true, akkor 12 órás üzemmód, false esetén 24 órás üzzemmód
bool PM;              //12 órás üzemmód esetén a délelőtt délután jelzése fals=AM, true=PM
float temperature;

void setup() {
Wire.begin();                                               // I2C busz használat indítása
enableOscillator(true, true, 2);               //Paraméterek jelentése:oscillátor eng. ha nincs Vcc,
                                                                       // battery mód, frekvencia
                     //Az oszcillátor mindíg megy, ha van Vcc. Ha első paraméter true, akkor 
                     //elemes táplálásnál is megy az oszcillátor.
                     //Ha második paraméter true, akkor elemes táplásálásnál is van oszzcillátor.
                     //Nekem ezek nem működnek, valamit biztosan félreértek. 
                    //Azonban a frekvenciát tudtam állítani.
enable32kHz(false); //a 32K kimeneti lábon engedélyezi true-val az oszcillátort. 
                                      //False kikapcsolja.
//  setClockMode(false);  //true értékkel 12 órás, false értékkel 24 órás üzemmód, 
                                              //ha átállítod az üzemmódot ujra be kell állítani az órát
                                              // ( setTime() ), mert fals eredményt ad vissza.  
              // a kövekező két sorból kell kivenni a kommentet és beállítani a változókat, 
              //lefordítani a programot, egyszer futtatni
              // majd újra betenni a kommenteket. Primitív órabeállítási mód!
// masodperc=0;perc=14;ora=20;het_napja=1;nap=1;honap=8;ev=16; 
// setTime();   //óra beállítása
 Serial.begin(115200);
}

void loop() {
  getTime();            //kiolvassuk az időt a DS3132-ből a "masodperc, perc, ora, 
                                  //het_napja, nap, honap, ev" változókba
  Serial.print("20");
  Serial.print(ev,DEC);
  Serial.print('-');
  Serial.print(honap,DEC);
  Serial.print('-');
  Serial.print(nap,DEC);
  Serial.print(' ');
  Serial.print(ora,DEC);
  Serial.print(':');
  Serial.print(perc,DEC);
  Serial.print(':');
  Serial.print(masodperc,DEC);
  if (h12) {                //ha 12órás mód, akkor kiírjuk az idő mögé a PM vagy AM-et
    if (PM) {Serial.print(" PM ");}
    else {Serial.print(" AM ");}
  }
  Serial.print("  ");
  Serial.print(het_napja);
  Serial.print('\n');
  temperature=getTemperature();
  Serial.print("Temperature=");
  Serial.println(temperature);
  if (oscillatorCheck()) {Serial.println("Az ora valoszinuleg pontos");}
                  else {Serial.println("Az ora valoszinuleg NEM pontos");}
  delay(5000);
}

void setClockMode(bool h12) {
  // beállítja a 12 órás vagy 24 órás üzemmódot
  // true 12 órás üzemmód
  // false24 órás üzemmód
  byte temp_buffer;
  // kiolvassa a 0x02 regisztert (Hour)
  Wire.beginTransmission(CLOCK_ADDRESS);
  Wire.write(uint8_t(0x02));
  Wire.endTransmission();
  Wire.requestFrom(CLOCK_ADDRESS, 1);
  temp_buffer = Wire.read();
  // beállítja bit6-ot (12/24 vezérlő bit)
  if (h12) {
    temp_buffer = temp_buffer | 0b01000000;
  } else {
    temp_buffer = temp_buffer & 0b10111111;
  }
  // visszaírja a 0x02 registerbe az új értéket
  Wire.beginTransmission(CLOCK_ADDRESS);
  Wire.write(uint8_t(0x02));
  Wire.write(temp_buffer);
  Wire.endTransmission();
}

void setTime() {
  // Beállítja az órát és torli az OSF regisztert
  // This function also resets the Oscillator Stop Flag, which is set
  // whenever power is interrupted.
  Wire.beginTransmission(CLOCK_ADDRESS);
  Wire.write(uint8_t(0x02));
  Wire.endTransmission();
  Wire.requestFrom(CLOCK_ADDRESS, 1);
  h12 = (Wire.read() & 0b01000000);                     //kiolvassa a 12 órás üzemmód jelzőbitjét
  if (h12) {                                                               //ha true, akkor 24 órás üzemmódban vagyunk
    if (ora > 12) {                                                    //ha a megadott óra nagyobb mint 12, akkor délután van
      ora = dec_bcd(ora-12) | 0b01100000;           //ki kell vonni 12 órár
    } else {
      ora = dec_bcd(ora) & 0b11011111;
    }
  } else {
    // 24 hour
    ora = dec_bcd(ora) & 0b10111111;
  }
  Wire.beginTransmission(CLOCK_ADDRESS);
  Wire.write(uint8_t(0x00));
  Wire.write(dec_bcd(masodperc));
  Wire.write(dec_bcd(perc));
  Wire.write(ora);
  Wire.write(dec_bcd(het_napja)); 
  Wire.write(dec_bcd(nap));
  Wire.write(dec_bcd(honap));
  Wire.write(dec_bcd(ev)); 
  Wire.endTransmission();
                // OSF flag törlése a 0x0F control registerben
                // OSF true-val jelzi, ha az óra valószínűleg nem pontos (leállt az oszcillátor stb.)
  byte temp_buffer = readControlByte(1);
  writeControlByte((temp_buffer & 0b01111111), 1);
}

void getTime() {
  //kiolvassa a dátumot és az időpontot 
  byte tempBuffer;
  bool PM;
  bool h12;
  Wire.beginTransmission(CLOCK_ADDRESS);
  Wire.write(uint8_t(0x00));
  Wire.endTransmission();
  Wire.requestFrom(CLOCK_ADDRESS, 7);
  masodperc = bcd_Dec(Wire.read());
  perc = bcd_Dec(Wire.read());
  tempBuffer = bcd_Dec(Wire.read());
  h12 = tempBuffer & 0b01000000;
  if (h12) {
    PM = tempBuffer & 0b00100000;
    ora = bcd_Dec(tempBuffer & 0b00011111);
  } else {
    ora = bcd_Dec(tempBuffer & 0b00111111);
  }
  het_napja = bcd_Dec(Wire.read());
  nap = bcd_Dec(Wire.read());
  honap = bcd_Dec(Wire.read() & 0b01111111);
  ev = bcd_Dec(Wire.read());
}

float getTemperature() {
                // Kiolvassa a hőmérséklet értékét a 0x11h és 0x12h regiszterekből
  byte temp;
  Wire.beginTransmission(CLOCK_ADDRESS);
  Wire.write(uint8_t(0x11));
  Wire.endTransmission();
  Wire.requestFrom(CLOCK_ADDRESS, 2);
  temp = Wire.read(); // MSB temp tegiszter
  return float(temp) + 0.25*(Wire.read()>>6);
}
byte bcd_Dec(byte val) {
// Ckonvertál bcd számból decimalisba
  return ( (val/16*10) + (val%16) );
}
byte dec_bcd(byte val) {
// Convertál decimalis számból bcd-be
  return ( (val/10*16) + (val%10) );
}

byte readControlByte(bool which) {
                // Read selected control byte: (0); reads 0x0e, (1) reads 0x0f
                // Read selected control byte
                // első byte (0) is 0x0e, második (1) is 0x0f
  Wire.beginTransmission(CLOCK_ADDRESS);
  if (which) {
                // 0x0f  control byte
    Wire.write(uint8_t(0x0f));
  } else {
                // 0x0e control byte
    Wire.write(uint8_t(0x0e));
  }
  Wire.endTransmission();
  Wire.requestFrom(CLOCK_ADDRESS, 1);
  return Wire.read();
}

void writeControlByte(byte control, bool which) {
                // Write the selected control byte.
                // which=false -> 0x0e, true->0x0f.
  Wire.beginTransmission(CLOCK_ADDRESS);
  if (which) {
    Wire.write(uint8_t(0x0f));
  } else {
    Wire.write(uint8_t(0x0e));
  }
  Wire.write(control);
  Wire.endTransmission();
}

void enableOscillator(bool TF, bool battery, byte frequency) {
 // bekapcsolja az oszcillátort. True - on, false - off.
  // if battery is true, turns on even for battery-only operation,
  // otherwise turns off if Vcc is off.
  // frequency must be 0, 1, 2, or 3.
  // 0 = 1 Hz
  // 1 = 1.024 kHz
  // 2 = 4.096 kHz
  // 3 = 8.192 kHz (Default if frequency byte is out of range)
  if (frequency > 3) frequency = 3;
                // read control byte in, but zero out current state of RS2 and RS1.
  byte temp_buffer = readControlByte(0) & 0b11100111;
  if (battery) {
                // turn on BBSQW flag
    temp_buffer = temp_buffer | 0b01000000;
  } else {
                // turn off BBSQW flag
    temp_buffer = temp_buffer & 0b10111111;
  }
  if (TF) {
                // set ~EOSC to 0 and INTCN to zero.
    temp_buffer = temp_buffer & 0b01111011;
  } else {
                // set ~EOSC to 1, leave INTCN as is.
    temp_buffer = temp_buffer | 0b10000000;
  }
                // shift frequency into bits 3 and 4 and set.
  frequency = frequency << 3;
  temp_buffer = temp_buffer | frequency;
                // And write the control bits
  writeControlByte(temp_buffer, 0);
}

void enable32kHz(bool TF) {
  // A 32Khz kimenetet kapcsolja ki vagy be
  // on (true);
  // off (false).
  byte temp_buffer = readControlByte(1);
  if (TF) {
                // turn on 32kHz pin
    temp_buffer = temp_buffer | 0b00001000;
  }
  else {
                // turn off 32kHz pin
    temp_buffer = temp_buffer & 0b11110111;
  }
  writeControlByte(temp_buffer, 1);
}

bool oscillatorCheck() {
  // ellenőrzi az OSF (oszcillátor stop Flag) állapotát.
  // Ha false értékkel tér vissza, akkor az óra valószínűleg nem pontos.
  // Az OSF értékét egy óra beállítással lehet törölni ( setTime() )
  byte temp_buffer = readControlByte(1);
  bool result = true;
  if (temp_buffer & 0b10000000) {
    // Ha az OSF true, akkor az függvény fals értékkel tér vissza
    result = false;
  }
  return result;
}


Ha szeretnénk a program működését részletesen megismerni és megérteni, akkor a DS3132 IC adatlapjából a következő táblázatot ajánlom tanulmányozni:

A táblázat az I2C buszon keresztül elérhető regisztereket tartalmazza. Ezek címe 00H-tól 12H-ig terjed. Ezeket a regisztereket tudjuk olvasni és írni. Az egyes regiszterekből olvassuk ki az időt, dátumot. A program megértéséhez néhány adalék:
Az óra IC 12 illetve 24 órás üzemmódban tud működni. Ezt a 02H regiszter BIT6 bitjével tudjuk beállítani. Ha ez a bit 0, akkor 24 órás üzemmód, ha 1, akkor 12 órás üzemmód. Nyilván az ugyanebben a regiszterben található BIT3-BIT0 bitekben található számot kell megfelelően értelmeznünk a BIT6 segítségével. Ha 12 órás üzemmódban vagyunk, akkor a BIT3-BIT0 biteket kell BCD kódból visszaalakítanunk decimálisba, és egy 0-9-ig terjedő számot kell kapnunk. A BIT4-nek ekkor a 10 óra kijelzés a feladata, értéke 0-1 lehet.  Ekkor a BIT5 jelzi, hogy a kiolvasott idő délután, vagy délelőtt. Ha tartalma 1, akkor azt jelzi, hogy délután van (PM), illetve 0 esetén délelőtt (AM). Ha 24 órás üzemmódban vagyunk, akkor a BIT5-BIT4 adja meg a 10 óra értékét, ami értelem szerűen 0-2 tartományban lehet.

Én a programban nem használtam (állítgattam) a 05H regiszterben található BIT7-ben kódolt “Century” értéket. Ezzel ugyanis az évszázadot lehet jelezni. Ha értéke 0, akkor a kiolvasott évet, ami a 06H regiszter BIT3-BIT4 illetve BIT7-BIT4 bitekben található, 2000-es év éveiként kell értelmezni. Ha értéke 1, akkor 2100-es év éveiként. Ezzel azért nem foglalkoztam, mert én már biztosan nem érem meg, hogy ez problémát okozzon, a gyönyör a jövő ifjúságának problémája marad.

Lényegesen részletesebb leírást találhatsz még a leírások menüben a DS3231 óra megszakítások használata című jegyzetemben. Abban a leírásban már a riasztási időpontok használatával is foglalkoztam, és összekombináltam a dolgot az Arduino megszakítás kezelésével. Ha még nem tudod mi a megszakítás kezelés, akkor ezt olvasd el előtte!

Az előbb említett DS3231 megszakítás használat leírásomnál megállapítottam, hogy elemes táplálás esetén nem működik az SQW kimenet, vagyis ez a modul nem alkalmas arra, hogy nagyon kis fogyasztású áramkörökben használjuk, hiszen nem tudja így felébreszteni az eszközünket egy adott időpontban. Az áramkör adatlapja szerint a fogyasztása 100 mikroA körüli, így még bőven felhasználható elemes kapcsolásokban is. Azonban a fenti modulon van egy EEPROM és LED is, ami jelentősen megnöveli az áramfelvételét. Ezért kerestem a net-en egy olyan modult, ami csak a DS3231 IC-t tartalmazza. Erre sajnos ráfaragtam, mert nincs neki kivezetve az SQW kimenete. Bár a modul áramfelvétele valóban elég alacsony kb. 110 mikroA, ébresztésre nem tudom felhasználni. Csak a tanulság kedvéért írtam le az esetet, nehogy más is pórul járjon. Itt egy fotó az érintett modulról:

Itt találtam meg az ali-n: https://www.aliexpress.com/item/32833136577.html?spm=a2g0s.9042311.0.0.4f754c4d60f7zM

Azonban még nem adtam fel, mert látható, hogy van egy nem használt kivezetés, erre esetleg egy kis ügyeskedéssel ráforraszthatom a chip SQW lábát egy darab dróttal. Ez azonban ilyen méretekben már nem tuti.

Időközben megérkezett a rendelésem, és megismerkedtem egy PCF8563 chip-et tartalmazó modullal. Ez kevésbé pontos órát tartalmaz, de visszaszámláló időzítője is van. Nagyon kicsi az áramfelvétele, és elemes működés közben is ad az INT kimenete megszakító jelet. Itt ismerkedhetsz meg a használatával.

DS3231 óra riasztás használata

Nem gondoltam, hogy valaha szükségem lesz a DS3231 óra alapos megismerésére, azonban mindennek eljön egyszer az ideje. Szeretnék egy beállítható idővel működő mérés adatgyűjtőt készíteni, ami adatokat rögzít SD kártyára. Ráadásul szeretném, ha akkumulátorról működne, amihez az Arduino-t alvás állapotában kell tartani két mérés között. Ehhez a DS3231 óra megszakítás funkciójára lesz szükség. Be kell programozni egy időpontot, és amikor ez elérkezik az óra felébreszti az Arduino-t, illetve ez biztosan egy ATmega328P chip lesz, mert az Arduino nano, vagy uno nagyon sokat fogyaszt. A DS3231 működésének megismerése bonyolultabb volt, mint gondoltam.

Lássuk először a részletes működést. Elővettem az adatlapját, és lefordítottam magamnak a szükséges részeket. Sajnos a fordítás során több mindent félreértettem, ezeket a hibákat javítottam, ahol kiderült. Bízom benne, hogy már jó a szöveganyag, és lehet vele dolgozni. Talán másnak is hasznos!

A DS3231 címezhető regiszterei:

Az alábbi ábra a DS3231 regisztereit mutatja. Több bájtos hozzáférés során, amikor a cím mutatója eléri a regiszter terület végét (12h), a következő beolvasott regiszter újra a 00h lesz. Az I2C START esetén, vagy ha több bájt olvasásakor a cím újra 00h címre kerül, az aktuális idő átkerül egy második regiszterkészletbe. Az időinformációt ezekből a másodlagos regiszterekből olvassa ki, miközben az óra továbbra is futhat. Ez kiküszöböli azt a problémát, hogy a regiszterek olvasása közben egyes bitek megváltozhatnak, ami miatt a kiolvasott érték nem lesz következetes (pl. 59 másodperc kiolvasása után az eltelt rövid időben a perc kiolvasásakor a perc már 1-el megnövekedhet, míg a másodperc 00-ra változik, ezért a kiolvasott érték 1 perccel későbbi időpontot mutatna).

Idő is dátum regiszterek (00h-06h):

Az idő és a naptár adatait a megfelelő regiszter bájtok kiolvasásával kapjuk meg. Az idő és a dátum adatokat a megfelelő regiszter bájtok írásával lehet beállítani. Az idő- és naptár regiszterek tartalma bináris kódolású decimális (BCD) formátumban kerül tárolásra.Magyarázatok a regiszterekben található jelző bitetekhez (12/24, AM/PM, Century), és a regiszter értékek kezeléséhez:

  • A DS3231 12 vagy 24 órás üzemmódban is működtethető. Az aktuális idő óráját mutató regiszter (02h) 6. bitje határozza meg, hogy 12 vagy 24 órás üzemmódban működik az óra. Ha ez 1, akkor a 12 órás üzemmód érvényes. 12 órás üzemmódban az 5. bit az AM/PM bit határozza meg, hogy a mutatott idő óra értéke délelőttöt vagy délutánt jelent. Amikor értéke 1, az délutánt (PM) jelent. 24 órás módban az 5. bit a második 10 órás bit, mert ekkor az óra tízes számjegye már nem csak 0 és 1 (0-12 óráig), hanem 2 is lehet (20–23 óráig), amihez két bitre van szükség.
  • Az évek kijelzésekor csak az év utolsó két számjegye kerül tárolásra. Az itt található értéket alapértelmezetten 2000-hez kell hozzáadni. Az évszázad bitjét (a 05h hónap regiszter 7. bitjét) akkor kell használni, amikor az éveket mutató regiszter (06h) értékét majd a jövőben 2100-hoz kell hozzáadni, illetve amikor majd 2099 év után az óra átfordul 2100-ra, akkor ez automatikusan bebillen 1-re.
  • A hét napjának változtatása éjfélkor történik. A hét napjának megfelelő értékeket a felhasználó határozza meg, de szekvenciának kell lennie (azaz ha 1 egyenlő vasárnap, akkor 2 egyenlő hétfővel és így tovább).
  • A nem logikus idő, dátumbevitel és hét napjának beírása meghatározatlan működést eredményez.
  • Az idő- és dátumregiszterek olvasásakor vagy írásakor a DS3231 másodlagos (felhasználói) puffereket használ a hibák elkerülésére a belső regiszterek frissítésekor. A visszaszámláló láncot a másodperc regiszter írása törli. Az átfutási problémák elkerülése érdekében az óra, perc és dátum regisztereket 1 másodpercen belül be kell állítani. Az 1 Hz-es négyszöghullámú kimenet, ha engedélyezve van, a másodperc érték beírását követő 500 ms után vált, feltéve, hogy az oszcillátor már fut.

Riasztási regiszterek (07h-0Dh):

Kétféle módon jelzi a DS3231 ha riasztás történt. Egyrészt lekérdezhető a status regiszter (0Fh) A1F és A2F bitjeiből, melyek 1-el jelzik, ha volt riasztás, másrészt ha az INT/SQW kimenet nem a négyszögjel kimenetnek van állítva, akkor alacsony szinttel jelzi a riasztási eseményt. Az INT/SQW névben az INT-et aláhúzassal kellene jelölni, mert ez jelöli, hogy az alacsony szint az aktív. Azonban az aláhúzás a weblapon nem jelent meg, nem küzdöttem vele. A DS3231 két dátum/idő riasztást tartalmaz. A riasztások bekapcsolhatók a riasztás engedélyezésével (AI1E és AI2E a kontroll regiszterben) és a kontrollregiszter INTCN bitjével. Az INT/SQW kimenet aktiválásához egyeznie kell a beállított biteknek az aktuális idő megfelelő bitjeivel (00h-02h, 03h vagy 04h). Az 1. riasztást úgy lehet beállítani, hogy a 07h-tól 0Ah-ig terjedő regiszterek bitjeit állítjuk be:

Az egyes dátum/idő riasztási regiszterek 7. bitjei maszkbitek. Ha az összes riasztáshoz tartozó maszk bit 0, akkor minden beállított értéket figyel az óra. Akkor lesz ilyen esetben riasztás, ha az aktuális idő megfelelő regisztereinek bitjei megegyeznek a dátum/idő riasztás regiszterekben tárolt bitekkel. Ha a maszk biteket 1-re állítjuk, akkor az adott időpont adatot nem nem vesz részt a riasztásban. például, ha az A1M1 maszk bit 1, akkor a hét vagy hónap napjának egyezőségét nem figyeli a rendszer, azaz bármilyen napon bekövetkezik a riasztás, ha az óra, perc és másodperc értékek megegyeznek. A lehetséges beálltásokat az alábbi táblázat tartalmazza.

A fenti táblázat a lehetséges beállításokat mutatja. A táblázatban nem felsorolt ​​konfigurációk logikátlan működést eredményeznek. A DY/DT bit (a hét napja vagy hónap napja regiszter 6. bitje) beállítja, hogy a regiszter 0–5 bitjeiben tárolt riasztási érték a hét napját vagy a hónap dátumát jelenti. Ha a DY/DT = 0, akkor a riasztás a hónap napjával való egyeztetéskor következik be. Ha a DY/DT = 1, akkor a riasztás a hét napjával való egyezéskor következik be.
Ha az aktuális idő regisztereinek bitjei megegyeznek a riasztási regiszter bitjeivel, akkor a megfelelő A1F vagy A2F riasztási jelzőbit 1-re billen. Ha a bit értéke 1, az aktiválja INT/SQW jelet. Az egyezést másodpercenként teszteli a DS3231. Ha azt szeretnénk, hogy a riasztást követően az INT/SQW kimenet újra magas legyen, akkor törölni kell a riasztást az A1F és az A2F bitekben is. Ha nem töröljük, akkor egy következő ciklikus riasztást nem tud jelezni a DS3231.

A 2. riasztást úgy lehet beállítani, hogy a 0Bh – 0Dh regiszterek bitjeit állítjuk be:

Ez a riasztási regiszter abban különbözik az 1. riasztási regisztertől, hogy a másodperc egyezését nem figyeli. A lehetséges riasztási eseteket a következő táblázat tartalmazza:

Control Register (0Eh)

7. bit: Oszcillátor (EOSC) engedélyezése. Ha 0-ra állítjuk, az oszcillátor működik. Ha 1-re állítjuk és a VBAT kivezetésről kap feszültséget a DS3231, akkor az oszcillátor leáll. Ez a bit 0, amikor az IC tápfeszültségét bekapcsoljuk. Ha a DS3231 IC-t a VCC kivezetés táplálja, az oszcillátor mindig be van kapcsolva, az EOSC bit állapotától függetlenül. Ha az EOSC le van tiltva (értéke 1), és a VBAT-ról működik a chip, az összes regiszter tartalma statikus az óra nem jár, de az utolsó érvényes időt őrzik a regiszterek, amíg az elem teljesen le nem merül.

6. bit: Négyszöghullám kimenet engedélyezése (BBSQW) akkumulátoros táplálás esetén. Ha az 1-re van állítva, és a DS3231 a VBAT kivezetésen keresztül kap feszültséget, az INT/SQW kimeneten megjelenik a négyszögjel, akkor is ha nincs feszültség a VCC kivezetésen. Az adatlap eredeti szövegének következő mondata ennek a bitnek a magyarázatához: “When BBSQW is logic 0, the INT/SQW pin goes high impedance when VCC falls below the power-fail trip point.”
Én ezt így fordítottam (értelmeztem): Amikor a BBSQW  0, az INT/SQW kivezetés nagy impedanciát mutat, amikor a VCC a minimális feszültségszint alá esik (kikapcsoljuk a tápfeszt és elemről tápláljuk az IC-t). Arra számítottam, hogy ez utóbbi mondat akkor is igaz, ha nem négyszögjel jön ki a vezetéken, hanem a megszakítás jelet szeretném felhasználni. A kísérleteim alapján azonban ez nem igaz. A mikor lekapcsolom a feszültséget a Vcc lábról, és az IC a Vbat bemeneten kap feszültséget elemről, az INT/SQW kimenet azonnal földre húzza magát. Nem hinném, hogy valamit elrontottam, mert ez egy nagyon egyszerű kapcsolás volt. Az INT/SQW kimenetet egy Arduino lábra kötöttem, ami bemenet. Ezt a bemenetet folyamatosan kérdezgettem, és néztem az állapotát. A DS3231 modulon van beépített felhúzó ellenállás, de gondoltam, hátha rossz, ezért kívülről raktam rá egyet. Ekkor is lemegy földre. Kipróbáltam úgy is, hogy ez a bit 1, de a ugyanaz az eredmény. Ha vissza adom a tápfeszt, azonnal magasra megy, és a risztás is bekövetkezik, tehát az IC működése rendben van attól, hogy egy időre nem volt tápfesz csak elemről. Ez elkeserítő számomra, mert ezek szerint elemről táplált állapotban nem képes megszakítás lábbal riasztani. Bár a leírás csak a négyszögjel kibocsátásról szól, de reménykedtem, hogy ettől még a megszakítás üzemmódban is működik a láb. Úgy tűnik nem. Egyébként a leírás szerint ez a bit 0, amikor a tápfeszültséget bekapcsoljuk.

5. bit: Hőmérséklet (CONV) konvertálása. Ennek a bitnek az 1-re állítása elindítja a hőmérséklet mérést, és a hőmérséklet regiszter (11h és 12h) értékének frissítését, és végrehajtja a TCXO algoritmust, hogy frissítse a kapacitási tömböt az oszcillátorban (ez nem tudom, hogy pontosan mit jelent, de feltehetőleg az óra pontosságának javításához van köze). Ez csak akkor történhet meg, ha az átalakítás még nem zajlik. A felhasználónak ellenőriznie kell a BSY állapotbitet, mielőtt a vezérlőt új TCXO végrehajtás elindítására kényszeríti. A felhasználó által kezdeményezett hőmérséklet-átalakítás nem befolyásolja a belső 64 másodperces frissítési ciklust. A felhasználó által kezdeményezett hőmérséklet-átalakítás kb. 2ms-ig nem befolyásolja a BSY-bitet. A CONV bit az íródástól az átalakítás befejezéséig 1-en marad, ez után CONV és  BSY 0-ra megy. A CONV bitet kell használni a felhasználó által kezdeményezett konverzió állapotának figyelésére.

Bit 4. és 3.: Rate Select (RS2 és RS1). Ezek a bitek szabályozzák a négyszöghullám frekvenciáját, amikor a négyszöghullám engedélyezve van. Az alábbi táblázat bemutatja az RS bittel választható négyszöghullámú frekvenciákat. Mindkét bit alapértelmezetten 1 (8,192 kHz), amikor a tápfeszültséget bekapcsoljuk.

Bit 2: Megszakító vezérlés (INTCN). Ez a bit vezérli az INT/SQW kivezetést. Ha az INTCN bit értéke 0, négyszög jelet ad ki az INT/SQW kivezetés. Ha az INTCN bit értéke 1, akkor az aktuális idő regiszterek és a riasztási regiszterek közötti egyeztetés 0-ra állítja az INT/SQW kimenetet (ha a riasztás szintén engedélyezve van). A megfelelő riasztási jelzőt mindig az INTCN bit állapotától függetlenül állítják be. Az INTCN bit értéke alapértelmezetten 1, amikor a tápfeszültséget bekapcsoljuk.

Bit 1: 2. riasztás megszakítás engedélyezése (A2IE). Ha az 1-re van állítva ez a bit, az lehetővé teszi a 2. riasztás jelző (A2F) számára, hogy INT/SQW kimenetet 0-ra állítsa megszakításkor (ha INTCN = 1). Ha az A2IE bit értéke 0 vagy az INTCN értéke 0, akkor az A2F bit nem kezdeményez megszakítási jelet az INT/SQW kivezetésen. Az A2IE bit alapértelmezetten le van tiltva (0), amikor a tápfeszültséget bekapcsoljuk.

Bit 0: 2. riasztás megszakítás engedélyezése (A1IE). Ha az 1-re van állítva ez a bit, az lehetővé teszi a 1. riasztás jelző (A1F) számára, hogy INT/SQW kimenetet 0-ra állítsa megszakításkor (ha INTCN = 1). Ha az A1IE bit értéke 0 vagy az INTCN értéke 0, akkor az A2F bit nem kezdeményez megszakítási jelet az INT/SQW kivezetésen. Az A1IE bit alapértelmezetten le van tiltva (0), amikor a tápfeszültséget bekapcsoljuk.

Status Register (0Fh)

7. bit: Oszcillátor Stop Flag (OSF). Ebben a bitben az 1 azt jelzi, hogy az oszcillátor vagy leállt, vagy egy ideig leállt. Ez felhasználható az az óra pontosságának megítélésére. Az alábbiakban esetekben válthat az OSF 1-re:

  1. A tápellátás most kapcsoltuk be.
  2. A VCC-n és a VBAT-n egyike sem elegendő az oszcillátor működéséhez.
  3. Az EOSC bit kikapcsolt akkumulátorral támogatott módban volt miközben VBAT-ról történt a táplálás.
  4. A kristályra gyakorolt külső hatások (azaz zaj, szivárgás stb.) miatt leállt az osszcillátor.

Ez a bit az 1-en marad, amíg nem töröljük.

3. bit: Engedélyezi a 32 kHz-es kimenetet (EN32 kHz). Ez a bit vezérli a 32 kHz-es kivezetés állapotát. Ha az 1-re van állítva, a 32 kHz-es kivezetés engedélyezve van, és 32,768 kHz négyszöghullámú jelet ad ki. Ha 0-ra állítjuk, a 32 kHz-es kivezetés nagy impedanciájú állapotba kerül. Ennek a bitnek a kezdeti bekapcsolási állapota 1, és egy 32,768 kHz négyszöghullámú jel jelenik meg a 32 kHz-es kivezetésnél a DS3231 bekapcsolása után (ha az oszcillátor működik).

2. bit: Foglalt (BSY). Ez a bit azt jelzi, hogy az eszköz elfoglalt a TCXO funkciók végrehajtásában. 1-re állítódik, amikor a hőmérséklet-érzékelőre történő átalakítási folyamatban van, majd törlődik, amikor az eszköz 1 perces szünetet tart a következő mérésig.

1. bit: 2. riasztás jelző (A2F). Azt jelzi, hogy a 2. riasztáshoz tartozó idő megegyezett az óra aktuális idejével. Ha az A2IE bit 1, és az INTCN bit 1, akkor az INT/SQW kivezetés megszakítást jelez ennek a bitnek a hatására. Az A2F bitet törölni kell egy riasztást követően. Ezt a bitet csak a 0-ra lehet írni. Ha megpróbáljuk 1-re írni, az érték változatlan marad.

0. bit: 1. riasztás jelző (A1F). Azt jelzi, hogy a 2. riasztáshoz tartozó idő megegyezett az óra aktuális idejével. Ha az A1IE bit 1, és az INTCN bit 1, akkor az INT/SQW kivezetés megszakítást jelez ennek a bitnek a hatására. Az A1F bitet törölni kell egy riasztást követően. Ezt a bitet csak a 0-ra lehet írni. Ha megpróbáljuk 1-re írni, az érték változatlan marad.

Példa program az INT/SQW kimenet megszakításokkal történő használatára:

A programot bőven elláttam kommentekkel. Sok magyarázatot nem is fűznék hozzá. Ez a program még nem küldi alvásba az ATmega328P chip-et, és még a DS3231 elemes működése sincs benne megoldva. Annyit érdemes tudni, hogy elemes táplálás esetén alapértelmezetten az INT/SQW kimenet magas impedanciás állapotba kerül, azaz nincs rajta megszakítás jel. Ezt külön be kell kapcsolni, hogy működjön, és az általam használt DS3231 könyvtár ezt nem tartalmazza, tehát meg kell írni. Erre még nem volt időm, bár egyáltalán nem látszik bonyolultnak. Még érdemes megnézni a DS3231 könyvtár forráskódját is Github-on. Link a forrás első sorában. Ez volt az első amit letöltöttem magamnak. A leírások és példa programok elég szerények, de használható. Rengeteg DS3231 kezelő könyvtár létezik, de egyikben sem találtam állítási lehetőséget az elemes táplálásban történő INT/SQW kimenet beállítására, így maradtam ennél a könyvtárnál. Ha készen lesz a függvény, még annyit fog változni a program, hogy az ATmega chip egyik lábáról fogja a tápfeszt kapni a DS3231, hogy az I2C kommunikáció idejére be lehessen kapcsolni, amikor a következő riasztási időpontot beállítom majd.

#include <DS3231.h> // https://github.com/NorthernWidget/DS3231 beépített függvéynek forrása a linken megnézhető
#include <Wire.h>
DS3231 Clock;

byte Year,Month,Date,Hour,Minute,Second,DoW; //óra beállításkor illetve óra kiolvasás segéd változói, Dow a hét napja (1-7)
bool Century=false; //évszázad jelzése, ha 0 akkor 2000-2099, ha 1 akkor 2100-2199 
bool h12; //24 órás időpontot használunk
bool PM; //ha 12 órást használnánk, ez jelezné a délutánt (1-el)
byte ADay; //riasztásban a hét napja (1-7), vagy a hónap napja (1-31)
byte AHour, AMinute, ASecond, ABits;  //risztási időpont változói
bool ADy; //risztásban ez jelzi, hogy hét napja (1) vagy hónap napja (0) lesz figyelve
bool A12h, Apm; //12 órás üzemmód esetén A12h true, jelen programban 24 órás üzemmódot használunk


void setup() {
  Wire.begin();
  Serial.begin(9600);

  pinMode(13, OUTPUT); //a 13-as kivezetésen van a LED
  digitalWrite(13, LOW); //led nem világít

 /******************************************************************************************* 
  * óra beállítás, csak egyszer kell beállítani aztán az elem őrzi az időt a DS3231-en   
  * miután egyszer lefordítottuk és le töltöttük a programot, az alábbi rész kikommentezhető
  *******************************************************************************************/
  /* Clock.setClockMode(false); //óra üzemmód 24 órás
    Clock.setSecond(0);
    Clock.setMinute(14);
    Clock.setHour(7);
    Clock.setDate(5);
    Clock.setMonth(7);
    Clock.setYear(20);
    Clock.setDoW(7); 
  */
    
  // kiolvassuk az időt és kiírjuk a sorosportra
  Serial.println("Ora időpontja");
  Serial.println("-------------");
  //kiolvassuk az óra idopontját
  Year=Clock.getYear();
  Month=Clock.getMonth(Century);
  Date=Clock.getDate();
  Hour =Clock.getHour(h12, PM);
  Minute =Clock.getMinute();
  Second =Clock.getSecond();
  DoW=Clock.getDoW();
  //kiírjuk a sorosportra, hogy lássuk is
  Serial.print("Het napja:");Serial.print(DoW, DEC);Serial.print(" datum:");
  Serial.print("2");if (Century) {Serial.print("1");} else {Serial.print("0");}
  Serial.print(Year, DEC);Serial.print("-");Serial.print(Month, DEC);Serial.print("-");Serial.print(Date, DEC);
  Serial.print(" ido:");
  Serial.print(Hour, DEC);Serial.print(':');Serial.print(Minute, DEC);Serial.print(':');Serial.print(Second, DEC);
  if (h12) {if (PM) {Serial.print(" PM ");} else {Serial.print(" AM ");}} else {Serial.println(" 24h ");}
  Serial.println();
  
  /******************************************************************************************************
  * risztás beállító függvény paraméterezéséhez segítség:
  * setA1Time(nap/hét napja, óra, merc, másodperc, alarm mód, map/hét napja flag, H12 flag, PM flag)
  * nap/hét napja = 0-30 vagy 0-7
  * óra = 0-12 vagy 0-24 (H12=1 essetén 12 órás)
  * perc=0-59
  * másodperc=0-59
  * alarm mód:
  *  0b1111 - minden másodpercben riaszt
  *  0b1110 - mikor a másodperc megeggyezik riaszt
  *  0b1100 - mikor a perc és a másodperc megeggyezik riaszt
  *  0b1000 - mikor az óra, perc és a másodperc megeggyezik riaszt
  *  0b0000 - mikor a hét napja vagy a hónap napja,az óra, perc és a másodperc megeggyezik riaszt 
  * nap/hét napja flag:
  *  0 - hónap napján riaszt (1-31)
  *  1 - hét napján riaszt (1-7)
  * H12 falg:
  *  0 - 12 órás idő megadás
  *  1 - 24 órás idő megadás
  * PM flag (csak akkor, ha H12=1):
  *  0 - délelőtt
  *  1 - délután
  **********************************************************************************************************/
  //Riasztási funkciók beállítása
  
  // beállítási példa: a hét ugyanazon napán (beállítás napja), a pillanatnyi idő + 1 perc időpontban lesz risztás, 24 órás üzemmód
  // Vigyázni, mert ha pont óra 59. percében adjuk ki, akkor a risztásben a perc 60 lesz, ami hüjeség
  //Clock.setA1Time(DoW, Hour, Minute+1, Second, 0b0000, true, false, false);
  
  //beállítási példa: a hét napja vagy a hónap napja nem számít, óra nem számít, perc nem számí, ha másodperc megeggyezik, akkor lesz risztás, 24 órás üzemmód
  // Vigyázni, mert ha pont óra 59. percében adjuk ki, akkor a risztásben a perc 60 lesz, ami hüjeség
  Clock.setA1Time(DoW, Hour, Minute+1, Second, 0b1110, true, false, false);
  
  Clock.turnOnAlarm(1);  //engedélyezzük a 1. risztást 
  Clock.turnOffAlarm(2); //a 2. riasztást most nem használjuk
    
  //lekérdezzük a risztási időpontot és kiírjuk a sorosportra
  Serial.println("Riasztási idopont");
  Serial.println("-----------------");
  Clock.getA1Time(ADay, AHour, AMinute, ASecond, ABits, ADy, A12h, Apm); //lekérdezzük a risztási adatokat
  if (ADy) {Serial.print("Het napja:");} else {Serial.print("Honap napja:");} Serial.print(ADay, DEC);
  Serial.print(" Idopont:");Serial.print(AHour, DEC);Serial.print(':');Serial.print(AMinute, DEC);Serial.print(':');Serial.print(ASecond, DEC);Serial.print(' ');
  if (A12h==LOW) {Serial.print("24h ");} else {if (Apm) {Serial.print("PM ");} else {Serial.print("AM ");}}
  if (Clock.checkAlarmEnabled(1)) {Serial.print("Risztas engedelyezve");} else {Serial.print("Riasztas tiltva");}
  Serial.print(" Alarm bits:");Serial.println(ABits,BIN);Serial.println();



 /*****************************************************************************************
  * Megszakítási bemenetek előkészítése
  * belső felhúzó ellenállás bekapcsolva, mert a DS3231 INT/SQW kimenete
  * magas impedanciás állapotban van ha nincs risztás, és a felhúzó ellenállás húzza fel 1-re,
  * risztáskor földre húzza az INT/SQW kimenetet 
  *****************************************************************************************/
  delay(500);
  pinMode(2,INPUT); //megszakítás 0 bemenet, erre kötjük a DS3231 INT/SQW kimenetét
  digitalWrite(2,HIGH);
  pinMode(3,INPUT); //megszakítás 1 bemenet, ezen nyomógomb lesz a risztásjelzés törléséhez
  digitalWrite(3,HIGH);

  /*****************************************************************************************************************
   megszakítások beállításának paraméterezéséhez segítség:
   attachInterrupt(kivezetés száma, meghívott függvény, esemény mód)
   esemény módok:
     LOW: az interrupt bemenet alacsony állapotban van
     CHANGE: ha a bemenet értéke változik, magasról alacsonyra vagy alacsonyról magasra
     RISING: ha a bemeneti jel alacsonyról magasra változik
     FALLING: ha a bemeneti jel magasról alacsonyra vált.
   A megszakítás közben a késleltetés [delay()] nem működik, és a millis() által visszaadott érték nem növekszik.
   A megszakítás végrehajtása közben a soros portra érkező adatok elveszhetnek.
   Valamennyi változót, amelyet módosítani szeretne a megszakításban, volatile-ként kell definiálnia.
   ****************************************************************************************************************/
  attachInterrupt(0, int_0, FALLING); //a kettes bemeneten lefutó élre (1->0) meghívódik a int_0 függvény (INT/SQW risztást ad)
  attachInterrupt(1, int_1, FALLING); //a hármas bemeneten lefutó élre (1->0) meghívódik a int_1 függvény (megnyomtuk a nyomógombot)

  /*
   * nem tudok rájönni, hogy miért, de a program elindulásakor egyszer meghívódik az int_0() függvény, és kigyúllad a led
   * Ezért kénytelen vagyok kikapcsolni a led-et azzal, hogy meghívom az int_1() függvényt. Lehetne úgy is, hogy simán 
   * kikapcsolom a led-et a kimenet LOW-ra állításával. Kinek hogyan teszik jobban. Ettől kezdve a megszakítás jól működik
   */
  //int_1();
  digitalWrite(13,LOW);    
}

void loop() {
  //lekérdezzük a D2 állapotát, vajon risztás után magas, vagy alacson (alacsony)
  if (digitalRead(2)) {Serial.println("2 high");} else {Serial.println("2 low");}
  
  // lekérdezi a risztás jelző állapotát, és egyben törli is a risztás jelző bitet IDS3231 status regizter (0Fh) A1F
  // ha nem töröljük a risztas jelző bitet, akkor az INT/SQW kivezetés magas marad, és nem történik következő risztás
  // ezt az int_0() függvénybe kelett volna rakni, de akkor nem tudtam volna lekérdezni a D2 bemenet állapotát
  // így látható, hogy riztás után mindaddig magas az INT/SQW, amíg nem töröljük
  Clock.checkIfAlarm(1); 
   
  delay(1000);
}

void int_0() {
  // a 2-es bemeneten érkezett egy 0 jelszint, beállítjuk a led_allpot wáltozót 1-re
  Serial.println("Megszakitas 0, led be");
  digitalWrite(13, HIGH);
}

void int_1() {
  // a 3-es bemeneten érkezett egy 0 jelszint, beállítjuk a led_allpot wáltozót 0-ra
  Serial.println("Megszakitas 1, led ki");
  digitalWrite(13, LOW);
}

Mint azt a kontrol regiszter 6. bitjénél (BBSQW) leírtam, nekem nem sikerült a DS3231-et elemmel táplált üzemmódban arra rábírnom, hogy az INT/SQW lábon megszakítás jelet küldjön. Vagyis a modult csak bekapcsolt Vcc állapotban lehet használni. Ez egyébként önmagában még nem baj, mert az áramfelvétele még így is igen kicsi a DS3231 chip-nek. A leírás szerint ha nem kommunikálok vele I2C buszon keresztül, 200 mikroA körüli áramot fogyaszt. Már ez is elegendően kicsi érték lenne. De nézzük, mit tud a DS3231 modul. Gyanítható, hogy a rajta lévő led, ami bekapcsolt Vcc esetén világít, már eleve használhatatlanná teszi a modult. És igen, 8 mA az áramfelvétel, de nem túl pontos árammérőm van, ezért ez csak nagyságrend. Ha kiforrasztom a led-et, akkor ez kisebb lesz pár milliA-el. De biztosan nem megy le 200 mikroA környékére. Ezért nézegetni kezdtem a modul kapcsolási rajzát, ami szerencsére fellelhető a neten:

Az R2 ellenállás értéke 1 Kohm, tehát itt 1mA alatti áramokra lehet számítani. Semmi más nincs ami fogyasszon. Illetve ott van még az AT24C32 eeprom. Azonban az adatlapja alapján ha nem írom és nem olvasom, akkor 35mikroA az áramfelvétele. Itt elakadtam. Vagy szétbarmolom a modult, és tönkre teszem a led-et, vagy keresek egy másik modult, amin csak óra van, nincs rajta led. Ezt tettem. Most várom, hogy megérkezzen aliexpresz-en. Addig pihi van!

Ha azonban nem lényeges az áramfelvétel, a fenti kapcsolás tökéletesen működik!

Hőmérséklet adatgyűjtő

Ebben a “tanuló programban” egy Arduino Uno R3, egy LCD kijelző, DS3231 RTL óra és egy SD kártya illesztő illetve egy SD kártya, volt a főszereplő.
A program az SD kártyára folyamatosan rögzíti a hőmérsékletet 30 percenként. Egy futási eredmény így néz ki. (PC-vel kiolvasva a kártyát):

Datum;Ido;Homerseklet;
2016.07.21;06:04;22.75;
2016.07.21;06:34;23.00;
2016.07.21;07:04;23.00;
2016.07.21;07:34;23.00;
2016.07.21;08:04;23.00;
2016.07.21;08:34;23.25;
2016.07.21;09:04;23.25;

A file dátuma alapértelmezetten 2000.01.01 0:00, a rendszer nem kezeli. Az állomány kiterjesztése CSV, így közvetlenül Excel-el megnyitható és feldolgozható.

Sokat küzdöttem azzal, hogy működés közben ki lehessen venni a kártyát, és az adatok rögzítése folytatódjon, ha visszadugom a helyére. Nem gondoltam ugyanis arra, hogy ez nem probléma. Tudni kell, hogy az SD kártyák példa programjaiban mindenütt úgy használják az SD kártyát, hogy a tápfeszültséget a kártyának az Arduino egyik kivezetés adja. Kimenetnek programozzák, HIGH szintet állítanak be, és az SD kártya innen kapja a tápfeszt. Olyan kicsi az SD kártya áramfelvétele, hogy ez így elméletben jó. Nekem azonban ez a megoldás nem működött. Rengeteget vacakoltam vele, míg rájöttem, hogy direktben a tápra kell kötni az SD modult, különben nem lehet a kártyát használni. Az okot azóta sem sikerült kiderítenem. Több SD modult is kipróbáltam, sehogyan sem működött hibátlanul. A kártya adatait le lehetett kérdezni, de íráskor már hibát dobott a függvény.

Úgy gondoltam, ha a tápfeszt nem lehet lekapcsolni a kártyáról, akkor biztosan nem lehet kivenni. Az első elképzelésem az volt, hogy a kártyát minden egyes írás előtt megnyitom, és ezzel biztosítom, hogy a visszahelyezett kártyát is kezelje program, ha esetleg kivettem, és közben nem volt a helyén, amikor a program írni akart rá. Azonban ez a módszer egyáltalán nem működött, amit nem igazán értettem. Arra jutottam, hogy a kártya kezelő programja hibás ebben a tekintetben. A kártyát egyszer lehet megnyitni egy programon belül és kész. Valamelyik idegen nyelvű fórumon olvastam, hogy ebbe a problémába mások is beleütköztek. Tovább kísérleteztem, és próbálkoztam azzal, hogy csak az SD.begin() függvényt hívtam meg újra és újra, de ez sem működött. Megdöbbentő módon egyedül az a megoldás működött a gyakorlatban, hogy a setup részben SD.begin()-el megnyitom a kártyát és levizsgálom létezik-e a file. Ha nem akkor létrehozom. A loop-ban már csak minden különösebb előkészítés nélkül írok a kártyára amikor kell. Esetemben fél óránként került erre sor. Azonban kísérletezgetés közben a gyakoriságot 5 másodperce állítottam, és kb. 20-30 alkalommal hosszabb rövidebb időre kivettem a kártyát. És láss csodát, minden hiba nélkül működött a kártyára a file írása. Nyilván előfordult, hogy egy vagy több file írást nem tudott a program végrehajtani, hiszen nem volt a kártya a helyén. Azt, hogy belül mi történt, nem tudom, de hiba nélkül folytatta a file írását a program. Szóval szerintem ez működik, lehet használni. Ebből az a tanulság, ha nincs probléma, akkor nem kell megoldani.
Annak aki SD kártya használatra adja a fejét, gondolnia kell a memória igényre. Az SD kártya kezelésére található könyvtárak mindegyike memóriazabáló. Legalább 10kbyte-ot elvesz a rendelkezésre álló flash memóriából. Ez ebben a programban nem okozott problémát, de sikerült olyan adatgyűjtő programot írnom, ahol sajna nem fértem bele a 32Kbyte-ba. Erre megoldásként javasolni tudom a külön datalogger modult, ami gyakorlatilag ugyanannyiba kerül, mint egy SD kártya modul. Ez azonban tartalmaz egy ATmega mikrovezérlőt, és önmagában tartalmazza a leírása szerint a szükséges programokat, és használatához már csak I2C kapcsolat szükséges. Már megérkezett aliexpressz-ről, de nem volt még időm kipróbálni. Így néz ki:

forrás: https://www.aliexpress.com/item/32959138610.html?spm=a2g0s.9042311.0.0.27424c4d81zskG

A program többi része a DS3231 RTC óramodul leírt programmal azonos. Nem is másoltam a forrásba, ha valaki ki akarja próbálni a programot, akkor onnan ide kell másolgatni a hiányzó függvényeket. A fordító jelezni fogja, hogy mi hiányzik. A forrás sorainak számát nagyon megnöveli, hogy az RTC óra lekérdezését egy függvénykönyvtár alapján megírtam magamnak. Ha ez valakit zavar, a számtalan előre megírt függvénykönyvtár valamelyikét érdemes használni, és sokkal rövidebb lesz a kód. Sőt, ha az RTC óra modul leírásánál használt könyvtárat telepíti, akkor valószínűleg változtatás nélkül futni fog az itt található kód, csak a program elején “#include”-olni be kell a függvénykönyvtárat.

A kód:

#define CLOCK_ADDRESS 0x68  //a modulon található óra IC címe az I2C buszon. A cím vezetékek átforrasztásával
                            //változthat
#include <Wire.h>
#include <LiquidCrystal_I2C.h>  //I2C LCD kezelő könyvtár
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  //LCD paraméterek megadása
#include <SD.h>
#define SD_POWER 8

File temp_adat;

int masodperc,perc,ora,nap,honap,ev,het_napja;
bool h12=true;             //ha true, akkor 12 órás üzemmód, false esetén 24 órás üztemmód
bool PM;              //12 órás üzemmód esetén a délelőtt délután jelzése fals=AM, true=PM
float temperature;
long lepes=millis();
long rogz_ido=millis();
long light_ido=millis();
int elozoperc=100;
String datum;
String ido;
String filenev=String("TEMP_1.CSV");
byte letrehozas=0;
String error=String("");

void setup() {
  pinMode(2, INPUT);

  Wire.begin();  // I2C busz használat indítása
  /*  setClockMode(false); //true értékkel 12 órás, false értékkel 24 órás üzemmód, ha átállítod az üzemmódot
                      //ujra be kell állítani az órát ( setTime() ), mert fals eredményt ad vissza.  
  masodperc=0;perc=34;ora=19;het_napja=7;nap=18;honap=7;ev=16; //óra beállításhoz az értékek
  setTime();   //óra beállítása */

  getTime();
  temperature=getTemperature();
  date_format();
  time_format();
  error="SD OK!";
  if (SD.begin(10)) {
    if (!SD.exists(filenev)) {
      temp_adat = SD.open(filenev, FILE_WRITE);
      temp_adat.println("Datum;Ido;Homerseklet;");
      error="File create OK!";
    }
  }
  else {error="No SD";}
  lcd.begin(16,2); //LCD inicializálása
  lcd.backlight();      //háttérvilágítás bekapcsolása
  lcd.print(error);
  delay(3000);
  lcd.noBacklight();
}

void loop() {
  if (digitalRead(2)==LOW) {lcd.backlight();light_ido=millis();}
  //30 percenként sd kártyára írunk
  if (millis()>rogz_ido+1800000){
    getTime();
    temperature=getTemperature();
    date_format();
    time_format();
    temp_adat.print(datum+";"+ido+";");
    temp_adat.print(temperature);
    temp_adat.println(";");
    temp_adat.flush();
    lcd.backlight();
    lcd.setCursor(14,1);
    lcd.print("+");
    rogz_ido=millis();
    light_ido=millis();
  }
  //5 másodperc után kikapcsoljuk az LCD háttétvilágítását
  if (millis()>light_ido+5000){
    lcd.setCursor(14,1);
    lcd.print(" ");
    lcd.noBacklight();
  }
  //másodpercenként frissítjük az LCD-n az időt
  if (millis()>lepes+1000) {
    getTime();            //kiolvassuk az időt a DS3132-ből a "masodperc, perc, ora, het_napja, nap, honap, ev" változókba
    if (elozoperc!=perc) {
      date_format();
      time_format();
      lcd.setCursor(0,0);
      lcd.print(datum);
      lcd.print(" ");
      lcd.print(ido);
      temperature=getTemperature();
      lcd.setCursor(0,1);  
      lcd.print(temperature);
      lcd.print(" C");
      elozoperc=perc;
    }
    lepes=millis();
  }
}

void date_format() {
  datum="";
  datum="20"+String(ev)+".";
    if (honap<10) {
      datum=datum+"0";
    }
    datum=datum+String(honap)+".";
    if (nap<10) {
      datum=datum+"0";
    }
    datum=datum+String(nap);
}

void time_format() {
  ido="";
  if (ora<10) {
    ido="0";
  }
  ido=ido+String(ora)+":";
  if (perc<10) {
    ido=ido+"0";
  }
  ido=ido+String(perc);
}

byte bcd_Dec(byte val) {
// Ckonvertál bcd számból decimalisba
  return ( (val/16*10) + (val%16) );
}

byte dec_bcd(byte val) {
// Convertál decimalis számból bcd-be
  return ( (val/10*16) + (val%10) );
}