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.

Mennyire volt hasznos amit olvastál?

Kattints egy csillagra az értékeléshez!

Sajnálom, hogy amit olvastál nem volt hasznos számodra!

Szeretném ha elégedett lennél!

Írd le kérlek, hogy mi nem tetszett!?