Zápisník experimentátora
Hierarchy: DS1307 Hodiny reálneho času
V dnešom článku si vysvetlíme základy používania integrovaného obvodu DS1307. Naučíme sa nastaviť čas a naučíme sa aj čítať čas z obvodu. Vysvetlíme si, ako využívať celú RAM, ktorú nám integrovaný obvod poskytuje. Súčasťou vysvetlenia bude aj drobný výlet do hlbín protokolu I2C.
Použil som tieto súčiastky:
Vytvoril som tri vzorové príklady:
Pred samotnou prácou s obvodom je vhodné, ak otestujete, či je viditeľný na zbernici I2C. Môžete mať omylom prehodené pripojovacie vodiče, alebo ste urobili nejakú inú chybu. Na otestovanie je vhodný program i2c_tester
, pomocou ktorého si zobrazíte všetky I2C slave zariadenia na zbernici. Ak ste si poskladali zapojenie na breadboarde pomocou môjho návodu, uvidíte iba jedno zariadenie. Ak použijete hotový modul, uvidíte dve zariadenia, pretože na module sa obvykle nachádza aj externá EEPROM.
Prvú činnosť, ktorú musíte po zakúpení integrovaného obvodu urobiť je nastavenie času. Najjednoduchšie je nastaviť si rovnaký čas, ako máte v počítači. Mohli by ste to urobiť aj priamo zadaním hodnôt pre hodiny, minúty a sekundy, ale existuje pohodlnejší spôsob. Využijú sa makrá __TIME__
a __DATE__
. Tie obsahujú textový reťazec s aktuálnym časom v okamihu kompilácie programu. Pretože kompilácia a nahratie programu do Arduina sa deje tesne po sebe, bude takto nastavený čas v integrovanom obvode iba o pár sekund rozdielny voči času, ktorý máte v počítači.
V programe sa o prevod času z textovej podoby starajú dve funkcie. Funkcia getDate
získa dátum a funkcia getTime
získa čas. Využívajú na to funkciu sscanf
. Textové reťazce majú v každom kompilátore rovnakú formu a preto sa nemusíte obávať, že by program nefungoval preto, lebo máte inú jazykovú mutáciu v operačnom počítači, než je angličtina.
Pri takomto nastavení času si treba uvedomiť, aký čas zadáte do integrovaného obvodu. Je to takzvaný lokálny čas. Čiže je to čas, ktorý podlieha pravidelným zmenám v lete, kedy sa posúva o jednu hodinu. Ale na vyskúšanie nastavenia času nám to bude stačiť. Neskôr si ukážeme, ako sa s letným časom vysporiadať.
#include <Wire.h>
#include <TimeLib.h>
#include <DS1307RTC.h>
const char *monthName[12] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
tmElements_t tm;
bool parse=false;
bool config=false;
void setup() {
Serial.begin(9600);
while (!Serial) ; // wait for Arduino Serial Monitor
delay(200);
Serial.print("Sketch compile time __TIME__='");
Serial.print(__TIME__);
Serial.print("', __DATE__='");
Serial.print(__DATE__);
Serial.println("'");
// get the date and time the compiler was run
if (getDate(__DATE__) && getTime(__TIME__)) {
parse = true;
// and configure the RTC with this info
if (RTC.write(tm)) {
config = true;
}
}
if (parse && config) {
Serial.println("Parse and write OK.");
} else if (parse) {
Serial.println("DS1307 Communication Error");
Serial.println("Please check your circuitry");
} else {
Serial.println("Could not parse info from the compiler");
}
}
void loop() {
if(parse && config) {
time_t t=RTC.get();
Serial.print("RTC.get()=");
Serial.println(t);
}
delay(1000);
}
bool getTime(const char *str)
{
int Hour, Min, Sec;
if (sscanf(str, "%d:%d:%d", &Hour, &Min, &Sec) != 3) return false;
tm.Hour = Hour;
tm.Minute = Min;
tm.Second = Sec;
return true;
}
bool getDate(const char *str)
{
char Month[12];
int Day, Year;
uint8_t monthIndex;
if (sscanf(str, "%s %d %d", Month, &Day, &Year) != 3) return false;
for (monthIndex = 0; monthIndex < 12; monthIndex++) {
if (strcmp(Month, monthName[monthIndex]) == 0) break;
}
if (monthIndex >= 12) return false;
tm.Day = Day;
tm.Month = monthIndex + 1;
tm.Year = CalendarYrToTm(Year);
return true;
}
Keď máme nastavený čas, môžeme ho z integrovaného obvodu čítať. Máme dve možnosti, v akej podobe čas získame. V tomto príklade je získaný čas rozparsovaný do štruktúry tmElements_t
. Na to slúži funkcia read
. Čas ale môže získať aj ako time_t
. Na to slúži funkcia get
. Všetko závisí od ďalšieho spracovania času v programe, akú formu na to zvolíme. V tomto prípade sa nám viac hodila rozparsovaná forma do tmElements_t
. Do príkladu som doplnil aj prevod medzi oboma formami pomocou funkcie makeTime
, aby ste videli, že zmena je možná kedykoľvek.
#include <Wire.h>
#include <TimeLib.h>
#include <DS1307RTC.h>
void setup() {
Serial.begin(9600);
while (!Serial) ; // wait for serial
delay(200);
Serial.println("DS1307RTC Get Time");
Serial.println("-------------------");
}
void loop() {
tmElements_t tm;
time_t t;
if (RTC.read(tm)) {
t=makeTime(tm);
Serial.print("Ok, Time=");
print2digits(tm.Hour);
Serial.write(':');
print2digits(tm.Minute);
Serial.write(':');
print2digits(tm.Second);
Serial.print(", Date(D/M/Y)=");
Serial.print(tm.Day);
Serial.write('/');
Serial.print(tm.Month);
Serial.write('/');
Serial.print(tmYearToCalendar(tm.Year));
Serial.print(", time_t=");
Serial.print(t);
Serial.println();
} else {
if (RTC.chipPresent()) {
Serial.println("The DS1307 is stopped. Please run the set_time");
Serial.println("sketch to initialize the time and begin running.");
Serial.println();
} else {
Serial.println("DS1307 read error! Please check the circuitry.");
Serial.println();
}
delay(9000);
}
delay(1000);
}
void print2digits(int number) {
if (number >= 0 && number < 10) {
Serial.write('0');
}
Serial.print(number);
}
V poslednom príklade budeme priamo čítať obsah RAM v integrovanom obvode. Pretože použitá knižnica na to neposkytuje žiadne funkcie, musíme si ich doplniť. Aby sme sa trochu precvičili v objektovom programovaní, odvodil som od triedy DS1307RTC
novú triedu DS1307RTCex
. Do nej som doplnil premennú ram
, aby sme mali kam uložiť obsah celej RAM v integrovanom obvode. Doplnil som aj tri funkcie, ktoré urobia celú prácu.
*ptr=ram
- Do premennej ptr priradím začiatok poľa ram. V c++ je aj ptr aj ram pointer a preto to môžem zapísať takto. Pomocná premenná ptr mi slúži na to, aby som sa postupne presúval cez jednotlivé prvky poľa a zapisoval do neho prečítané hodnoty.*ptr++=Wire.read()
- Tento zápis znamená to, že uloží do miesta, kam aktuálne ukazuje pointer (*ptr
) a potom posunie pointer o jedno miesto nahor (ptr++
). C++ nám umožňuje robiť aj takéto operácie v jednom kroku.
#include <Wire.h>
#include <TimeLib.h>
#include <DS1307RTC.h>
#define DS1307_CTRL_ID 0x68
class DS1307RTCex : public DS1307RTC {
uint8_t ram[64];
public:
DS1307RTCex() : DS1307RTC() {}
bool readRam() {
uint8_t *ptr = ram;
for (uint8_t x = 0; x <= 4; x++) {
Wire.beginTransmission(DS1307_CTRL_ID);
Wire.write((uint8_t)x * 16);
if (Wire.endTransmission() != 0)
return false;
uint8_t sz = 16;
// block
Wire.requestFrom(DS1307_CTRL_ID, 16);
if (Wire.available() < 16) return false;
while (sz) {
*ptr++ = Wire.read();
sz--;
}
}
return true;
}
void dump() {
uint8_t nRows = 4;
char asciiDump[17];
int value;
for (uint8_t r = 0; r < nRows; r++) {
uint8_t a = 16 * r;
Serial.print(F("0x"));
if ( a < 16 * 16 * 16 ) Serial.print(F("0"));
if ( a < 16 * 16 ) Serial.print(F("0"));
if ( a < 16 ) Serial.print(F("0"));
Serial.print(a, HEX); Serial.print(F(" "));
for (int c = 0; c < 16; c++) {
value = ram[a + c];
if (value < 16)
Serial.print(F("0"));
Serial.print(value, HEX);
Serial.print(c == 7 ? " " : " ");
if ((value >= 0x20) && (value < 0x7f))
asciiDump[c] = value;
else
asciiDump[c] = '.';
}
asciiDump[16] = 0;
Serial.println(asciiDump);
}
}
void demoRam() {
uint8_t data = 0;
for (uint8_t i = 8; i < 64; i++) {
Wire.beginTransmission(DS1307_CTRL_ID);
Wire.write(i);
Wire.write(data++);
Wire.endTransmission();
}
}
};
DS1307RTCex ex;
void setup() {
Serial.begin(9600);
while (!Serial) ; // wait for serial
delay(200);
Serial.println("DS1307RTC Get RAM");
Serial.println("-------------------");
//ex.demoRam();
if (ex.readRam()) {
ex.dump();
}
else
Serial.println("DS1307RTC Communication error");
}
void loop() {
delay(1000);
}
Zdrojový kód sa nachádza na serveri GitHub.
25.09.2017