Před nějakou dobou jsme si popsali obvod reálného času DS1307, který dokáže udržovat aktuální datum a čas a je napájen z baterie. V tomto článku si ukážeme jak RTC obvod použít s Arduinem. Samotné zapojení je velmi jednoduché, a proto se budeme více věnovat programu pro Arduino.
Základní zapojení obvodu
Na obrázku níže je schéma zapojení samotného obvodu DS1307, v dolní části pak naznačeno, ke kterým pinům Arduina je nutné obvod připojit (DS1307 komunikuje po standardní sběrnici I²C). Pokud máte DS1307 již na nějaké hotové desce (například já jsem zakoupil hotový obvod od SparkFun.com), stačí jen připojit napájení a dva I²C vodiče k arduinu.

Čtení obsahu paměti
Jakmile máme vše zapojeno, můžeme přečíst obsah paměti DS1307. V datasheetu nebo v minulém příspěvku se můžete dočíst, že obvod obsahuje celkem 63 bajtů paměti RAM. Spodních 8 bajtů je určeno pro datum a čas, zbytek může uživatel volně použít (to může být užitečné například pro uložení informace o letním/zimním čase, časové zóně, či nějaké důležité události).

Pojďme si pomocí Arduina přečíst obsah prvních 8 bajtů. Každý bajt je většinou rozdělen na dvě části. Spodní 4 bity určují jednotky a horní 4 (nebo méně) bitů určují desítky.
To znamená, že například hodnota 01001000 na adrese 0x01 není 72 minut, ale 48 minut (0100 — 4 a 1000 — 8). Obdobně je tomu i u ostatních hodnot.
Další důležité upozornění — aby obvod fungoval, je nutné vynulovat (nastaven na nulu) bit CH na adrese 0x00. V opačném případě obvod neběží a tím pádem neudržuje aktuální čas.

Teď už bez dalšího zdržování ukážeme kód pro Arduino, který přečte prvních 8 bajtů paměti a pošle je přes UART do PC. Kód jsem napsal co nejvíce přímočaře, takže snad nepotřebuje žádné další komentáře.
#include <Wire.h>
// Adresa obvodu je napevno nastavená
// na hodnotu 0x68.
#define DS1307_ADDRESS 0x68
void setup() {
Serial.begin(9600);
Wire.begin();
// Sem budeme ukládat příchozí data.
uint8_t data[8];
Wire.beginTransmission(DS1307_ADDRESS);
Wire.write((uint8_t)0x00); // Adresy musí být typu uint8.
Wire.endTransmission();
Wire.requestFrom(DS1307_ADDRESS, 8);
for(int i = 0; i < 8; i++)
data[i] = Wire.read();
// Pokud je bit CH vynulován, RTC běží.
Serial.print("RTC running: ");
Serial.println((data[0] >> 7) ? "No" : "Yes");
// Spodní polovina bajtu jsou jednotky,
// horní pak desítky.
Serial.print("Seconds: ");
Serial.print((data[0] & 0x70) >> 4, DEC);
Serial.println(data[0] & 0x0F, DEC);
Serial.print("Minutes: ");
Serial.print(data[1] >> 4, DEC);
Serial.println(data[1] & 0x0F, DEC);
// Hodiny mohou být v 12 i 24 hodinovém formátu.
// Lze mezi nimi volit nastavením šestého bitu na
// adrese 0x02.
if(data[2] >> 6) {
// 12-hodinový formát
Serial.print("Hours: ");
Serial.print((data[2] & 0x1F) >> 4, DEC);
Serial.print(data[2] & 0x0F, DEC);
// Pátý bit určuje dopoledne/odpoledne.
Serial.println(((data[2] & 0x20) >> 5) ? "PM" : "AM");
} else {
// 24-hodinový formát
Serial.print("Hours: ");
Serial.print((data[2] & 0x3F) >> 4, DEC);
Serial.println(data[2] & 0x0F, DEC);
}
Serial.print("Day: ");
Serial.println(data[3], DEC);
Serial.print("Date: ");
Serial.print(data[4] >> 4, DEC);
Serial.println(data[4] & 0x0F, DEC);
Serial.print("Month: ");
Serial.print(data[5] >> 4, DEC);
Serial.println(data[5] & 0x0F, DEC);
Serial.print("Year: ");
Serial.print(data[6] >> 4, DEC);
Serial.println(data[6] & 0x0F, DEC);
// Řídící registr. Určuje výstup pinu SQW/OUT.
// Může být nastaven třeba na 0x10 a pak se na
// zmíněném pinu objeví obdelníkový signál s
// frekvencí 1Hz.
Serial.print("Control: ");
Serial.println(data[7], BIN);
}
void loop() { }
Pokud jste obvod ještě nespouštěli, může být obsah paměti libovolný, a proto je nejdříve nutné nastavit aktuální datum a čas. Ukažme si jednoduchou funkci, která nastaví obsah libovolného registru v paměti RTC:
/**
* Funkce uloží hodnotu 'value' na adresu 'address'.
*
* Použití může být například:
* ds1307_set_register(0x06, 0x12);
* Nastaví rok na 2012.
*/
void ds1307_set_register(uint8_t address, uint8_t value) {
Wire.beginTransmission(DS1307_ADDRESS);
Wire.write(address);
Wire.write(value);
Wire.endTransmission();
}
Tím jsme si ukázali nízkoúrovňovou komunikaci s obvodem. Tento obvod je ale hodně oblíbený, a tak existuje několik již hotových knihoven, které usnadňují použití. Jednou z nich je například RTClib z webu ladyada.net (odkaz na samotnou knihovnu). S touto knihovnou je pak nastavení aktuálního data a času velmi jednoduché. Například:
#include <Wire.h>
#include <RTClib.h>
RTC_DS1307 RTC;
void setup () {
Serial.begin(9600);
Wire.begin();
RTC.begin();
// Datum a čas nastavíme, pouze pokud obvod neběží.
if (! RTC.isrunning()) {
Serial.println("RTC is NOT running!");
// __DATE__ a __TIME__ jsou konstanty překladače, které
// jsou nastaveny během překladu programu.
RTC.adjust(DateTime(__DATE__, __TIME__));
}
}
void loop() {
// A budeme stále dokola tisknout aktuální čas.
DateTime now = RTC.now();
Serial.print(now.hour(), DEC);
Serial.print(':');
Serial.print(now.minute(), DEC);
Serial.print(':');
Serial.println(now.second(), DEC);
delay(500);
}
Samotné Arduino poskytuje také knihovnu Time.h. Arduino samo o sobě sice nemá RTC, ale díky 16bitovému časovači je schopné udržovat si aktuální datum a čas (pouze dokud je připojené k napájení, po odpojení napájení se aktuální čas ztrácí). Je tedy možné po každém spuštění Arduina synchronizovat čas s externím RTC a Arduino si pak bude udržovat aktuální čas samo.
To lze provést například pomocí zmíněné knihovny Time. Pokud si ji stáhnete do adresáře s ostatními Arduino knihovnami, můžete ji použít následovně (předpokládám, že DS1307 je již připojen a nastaven):
#include <Time.h>
#include <Wire.h>
#include <DS1307RTC.h>
void setup() {
Serial.begin(9600);
// Načteme datum a čas z externího RTC a nastavíme Arduino.
setSyncProvider(RTC.get);
if(timeStatus()!= timeSet)
Serial.println("Unable to sync with the RTC");
else
Serial.println("RTC has set the system time");
}
void loop() { }
Pak už lze RTC od Arduina odpojit a používat funkce jako hour(), minute(), second(), day(), apod.
Přesnost
Přesnost obvodu do velké míry závisí na přesnosti použitého krystalu, teplotě okolí a napětí baterie. Experimentálně jsem ověřil, že se obvod za necelé tři měsíce opozdil o 286 vteřin. Desku jsem nechal ve skříni, napájenou pouze z malé baterie v cca 20°C.
Závěr

Ukázali jsme si, jak používat obvod reálného času DS1307 s Arduinem. Nejdříve jsme si popsali nízkoúrovňovou komunikaci přímo po I²C sběrnici, pak jsme využili existujících knihoven. V závěru jsme si ukázali i možnost udržovat čas pomocí vnitřního časovače Arduina a externí RTC použít jen pro jednorázovou synchronizaci.
Zde bych ještě upozornil na jednu nepříjemnost, která nemusí být na první pohled zřejmá. Arduino si aktuální datum a čas udržuje pomocí funkce millis(), která vrací počet milisekund od spuštění. Tato hodnota se aktualizuje při každém přetečení 16bitového časovače a je typu unsigned long, takže jednoduchým výpočtem zjistíme, že přeteče za přibližně 50 dní. Co se stane s aktuálním časem Arduina pak, netuším. Nevsázel bych ale na jeho přesnost po této době.

