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ě.