Hardware | Programování | Pojďme programovat elektroniku

Pojďme programovat elektroniku: RTC hodiny, aneb když Arduino ví, kolik je opravdu hodin

  • Přesný čas považujeme na počítačích za samozřejmost
  • Jenže ona není
  • Každá mašina má speciální obvod RTC hodin

Když se vás zeptám, kolik je hodin, kouknete na zápěstí, na displej telefonu, anebo instinktivně zpravidla do pravého dolního rohu monitoru, kde září aktuální systémový čas.

Real-Time Clock

Tomuto času se v počítačovém žargonu říká RTC (Real-Time Clock) a počítač jej může získat dvěma způsoby. Buď si o něj po každém spuštění řekne z internetu a bude jej udržovat, dokud jej zase nevypnete, anebo bude vybavený nezávislým obvodem RTC hodin s vlastní malou baterií, čili mašina bude dobře vědět, kolik je hodin, i když ji třeba na týden odpojíte od elektrického proudu.

767313628
Základní deska a v jejím středu baterie RTC hodin

Asi netřeba příliš rozvádět, která z těchto technik je rozšířenější. Prakticky každá elektronika, která potřebuje pracovat s reálným časem, je dnes samozřejmě vybavená speciálním časovým čipem počínaje mobilními telefony, GPS navigacemi a konče notebooky a velkou pracovní stanicí pod stolem, kde je RTC čip obvyklou součástí základní desky.

Váš systémový RTC čas v praxi:

Oproti dřevním dobám osobních počítačů se vlastně změnila jen jedna věc. Zatímco před lety jsme museli tyto hodiny čas od času seřídit, dnešní počítače průběžně korigují vlastní RTC hodiny pomocí speciálních internetových synchronizačních serverů.

Bezčasé Arduino

Naprosto klíčovou úlohu hardwarových RTC hodin si uvědomí každý bastlíř v okamžiku, kdy bude potřebovat pomocí Arduina spouštět různé úlohy v přesný čas. Dejme tomu, že budete mít zahradu a vždy v šest večer budete chtít spouštět automatické zavlažování.

Byl by to vlastně docela primitivní obvod i kód programu. Stačí mít naprosto základní Arduino za pár korun a stejně laciný obvod s magnetickým relé, které zapne, nebo vypne napájení 230V čerpadla. Má to jen jednu slabinu. Arduino nemá páru, kdy nastane čas 18:00:00, poněvadž jednoduché AVR procesory ATmega nejsou vybavené RTC hodinami.

Laciné mikropočítače mají zpravidla jen základní procesorový čítač – počítají jen čas od svého vlastního spuštění. Pokud bychom tedy chtěli vědět, kolik je opravdu hodin, museli bychom reálný čas nastavit při každém startu a ručně jej čas od času aktualizovat: Uplynulo už od spuštění mikropočítače 900 000 milisekund? Fajn, to je 15 minut a já zapnul mikropočítač ve 14:43:32, tak těch 15 minut k tomuto výchozímu RTC času prostě připočtu.

Hardwarové hodiny na Arduinu

Hrůza! Takhle by to přeci nešlo. Ano, nešlo, proto lze samozřejmě i Arduino a další mikropočítače rozšířit o modul RTC hodin, který zpravidla komunikuje na sběrnici I2C, má vlastní baterii a ty lepší i speciální pin, který může v nastavený čas vyslat do počítače krátký pulz, tzv. přerušení, které mikropočítač zaregistruje a zpracuje vaše příkazy.

529490392
Populární RTC čip DS3231

Pokud bych takový budík nastavil třeba na spouštění každou celou minutu, budu vědět, že se mé příkazy zpracují vždy v reálný čas xx:xx:00, což by bylo bez RTC hodin poměrně složité. Jak už jsem totiž napsal výše, mikropočítač sice ví, jak dlouhá je jedna minuta, takže samozřejmě mohu napsat kód, který bude periodicky vykonávat sadu příkazů každých 60 sekund, nicméně pokud mikropočítač spustím v čase 14:30:15, pak k druhému průběhu dojde v čase 14:31:15, pak 14:32:15, 14:33:15 a tak dále. Mikropočítač prostě nezná vztah mezi svým přesným čítačem a aktuálním reálným časem.

Dost ale bylo teoretické omáčky, pojďme si takové hodiny vyzkoušet v praxi. Na eBay seženete třeba populární RTC čip DS3231 v mnoha provedeních od holého švába po modul AT24C32 s vlastní 3,6V baterií a hlavně sběrnicí I2C pro snadné připojení třeba k Arduinu a dalším mikropočítačům. Cena za kus? Od dvaceti korun výše.

874599204 607429252 336469591
Modul reálného času AT24C32 s baterií, sběrnicí I2C a RTC čipem DS3231

Práci s hodinami usnadní některá z mnoha dostupných knihoven pro konkrétní model hodin. Nutno ale podotknout, že ne každá podporuje všechny funkce. Seeeduino DS321 je sice poněkud staršího data, nicméně funguje a má dostatek funkčních příkladů jak pro čtení a nastavování času, tak práci s budíky.

956304384 140394151 762708714
Modul RTC hodin používá sběrnici I2C, takže stačí propojit s mikropočítačem piny SCL, SDA a připojit napájení VCC (+) a GND (-)

Jednoduchý kód, který vypíše aktuální datum a čas do sériové linky, by mohl vypadat takto:

// Nativni knihovna pro praci se sbernici I2C
#include <Wire.h>
// Rucne doinstalovana knihovna pro hodiny DS3231 z GitHubu
#include "DS3231.h"

// Objekt nasich RTC hodin
DS3231 hodiny;

// Text s datem a casem ve formatu dd.mm. yyyy hh:mm:ss
char txtDatumCas[21];

// Ceske popisky dnu v tydnu (zacina se nedeli)
char dny[][7] = {"Nedele", "Pondeli", "utery", "Streda", "Ctvrtek", "Patek", "Sobota"};

/*
Pri prvnim spusteni je treba nastavit cas. Pro vyssi presnost bychom jej mohli nastavit treba rucnim zadanim pres seriovou linku az za behu. Ja naopak natvrdo v kodu vytvorim objekt s casem a ten se pak pri spusteni nahraje do pameti RTC cipu.

Postupne: Rok, mesic, den, hodina, minuta, sekunda, den v tydnu (nedele = 0)
*/ 
DateTime cas(2016, 10, 22, 15, 0, 0, 6);

// Funkce setup() se zpracuje jen jednou pri startu mikropocitace
void setup() 
{
 // Nastartuj sbernici I2C
 Wire.begin();
 // Nastartuj RTC hodiny
 hodiny.begin();
 // Nastav hodiny podle naseho casu vyse
 hodiny.adjust(cas); 
 // Nastartuj seriovou linku na rychlosti 9600 bps
 Serial.begin(9600);
}

// Kod uvnitr funkce loop() se opakuje stale dokola
void loop() 
{
 // Ziskej aktualni cas z RTC hodin
 cas = hodiny.now();
 // Uloz do txtDatumCas datum a cas ve formatu dd.mm. yyyy hh:mm:ss
 snprintf(txtDatumCas, sizeof(txtDatumCas), "%02d.%02d. %d %02d:%02d:%02d", cas.date(), cas.month(), cas.year(), cas.hour(), cas.minute(), cas.second());
 // Vypis do seriove linky aktualni den
 Serial.print("\nDnes je ");
 Serial.println(dny[cas.dayOfWeek()]);
 // Vypis datum a cas
 Serial.print("Datum a cas: ");
 Serial.println(txtDatumCas);
 delay(1000);
}

A takto to bude vypadat, jakmile program přeložíte a podíváte se do výstupu sériové linky. Každou sekundu se vypíše aktuální čas RTC hodin, který jsme na začátku programu ručně nastavili.

202422805
I něco tak zdánlivě primitivního jako výpis aktuálního data a času je věda, která pro správný chod vyžaduje vlastní kus hardwaru

Nyní bych mohl Arduino vypnout a modul RTC hodin klidně odpojit a schovat do krabice. Když jej za týden zase zapojím, bude ukazovat správný čas, poněvadž o jeho napájení v odpojeném stavu se starala baterie.

Hodiny na OLED displeji

Výpis data a času do sériové linky ale není vůbec hezký. Jelikož se mi v krabici povaluje několik 0,96” OLED displejů s rozlišením 128×64 pixelů a opět sběrnicí I2C, bude se tentokrát čas vypisovat na jeden z nich.

686776409 747913908 240602041
Různé 0,96" OLED displeje: černobílý a dvoubarevný

Na eBay podobné maličké displeje seženete za cenu okolo stokoruny a pixely buď do jednoho svítí stejným odstínem, nebo má displej v horní části pruh zpravidla se žlutými pixely, čehož lze využít pro vizuální oddělení třeba nějaké informační lišty jako na mobilech.

rtchodiny.gif

Ať už koupíte jakýkoliv, máte skoro jistotu, že jej ovládá obvod SSD1306, se kterým opět počítá knihovna, která se postará o to, abyste nemuseli řešit nic složitého, ale rovnou kreslit/rozsvěcovat pixely podle osy X a Y, nebo psát text o několika velikostech. S dodatečnými knihovnami pak můžete použít i vlastní rastrové fonty.

98573567 205290436
I2C sběrnice v praxi: Na piny SDA a SCL na mikropočítači zapojíte hromadu periférií (v tomto případě RTC hodiny a OLED displej). Procesor pak komunikaci rozliší pomocí speciální adresy každé z nich.

Jednoduchý kód, který vypíše aktuální čas na OLED displej

#include <Wire.h>
// Nova knihovna od Adafruit, kterou jsme stahli z GitHubu
#include <Adafruit_SSD1306.h>
#include <DS3231.h>

DS3231 hodiny; 
// Objekt OLED displeje
Adafruit_SSD1306 displej;
DateTime presnycas;
// Text s hodinami
char txtHodiny[9];
// Text s datem
char txtDatum[12];
// Den v tydnu jako text
char txtDen[][7] = { "Nedele", "Pondeli", "Utery", "Streda", "Ctvrtek", "Patek", "Sobota" };
// Blikani dvojtecky mezi hodinovym a minutovym ukazatelem
bool tick = true;

void setup(){ 
 Serial.begin(9600);
 Wire.begin(); 
 hodiny.begin(); 
 // Nastartovani OLED displeje s I2C adresou 0x3C
 displej.begin(SSD1306_SWITCHCAPVCC, 0x3C);
}

void loop(){
 // Ziskani casu z RTC hodin
 presnycas = hodiny.now();
 
 // Vygenerovani textu s casem ve formatu hh:mm.
 // Jednou s dvojteckou a podruhe bez = efekt blikani kazdou sekundu
 if(tick == true){
 snprintf(txtHodiny, sizeof(txtHodiny), "%02d:%02d", presnycas.hour(), presnycas.minute());
 tick = false;
 }
 else{
 snprintf(txtHodiny, sizeof(txtHodiny), "%02d %02d", presnycas.hour(), presnycas.minute());
 tick = true;
 }

 // Vygenerovani textu s datem ve formatu dd.mm. yyyy
 snprintf(txtDatum, sizeof(txtDatum), "%02d.%02d. %d", presnycas.date(), presnycas.month(), presnycas.year());

 // Vymaz displej
 displej.clearDisplay();
 // Nastav kurzor na X=5 a Y=0
 displej.setCursor(5,0);
 // Nastav barvu na bilou (ve skutecnosti na tovarni barvu displeje, coz muze byt i modra aj.)
 displej.setTextColor(WHITE);
 // Nastav velikost pisma na 1
 displej.setTextSize(1);
 // Vypis aktualni den
 displej.print(txtDen[presnycas.dayOfWeek()]);
 displej.print(" - ");
 // Vypis text s datem
 displej.print(txtDatum);
 // Nastav velikost pisma na 3
 displej.setTextSize(3);
 // Nastav kurzor na X=20 a Y=20
 displej.setCursor(20,20);
 // Vypis text s hodinami
 displej.print(txtHodiny);
 // Ve skutecnosti jsme az do teto chvile jen upravovali pole bajtu = buffer se sviticimi a zhasnutymi pixely, ktery zabere vetsinu pameti, a nyni jej poslednim prikazem posleme na displej a vse se zobrazi. 
 displej.display();
 // Ted sekundu pockame a vse se zopakuje
 delay(1000);
}

RTC hodiny budou probouzet Arduino pomocí interruptu

Na závěr si ukážeme ještě kód speciálního periodického budíku. Nebude to ale budík, který bude probouzet přímo nás, ale samotné Arduino, které po zpracování sady úloh usne co možná nejhlubším spánkem a probudí jej až impulz, který RTC hodiny vyšlou skrze svůj pin SQW na speciální pin Arduina jménem interrupt, tedy přerušení. Arduino Uno má pro tyto účely vyhrazené digitální piny 2 (INT0) a 3 (INT1).

111559510
Nový bílý vodič spojuje pin SQW na RTC hodinách s D2 na Arduinu, který má funkci INT0

Podobný impulz pak přinutí Arduino k tomu, aby všeho nechalo a zpracovalo přednostně instrukce iniciované přerušením. V případě spánku mikročipu se tedy zase probudí a to díky RTC hodinám v konkrétní reálný čas.

Začněme ale oním spánkem. Co to vlastně je? Pokud bych napsal kód:

Serial.println("Ahoj");
delay(60000);
Serial.println("Ahoj");

Arduino do sériové linky napíše „Ahoj,“ pak 60 sekund počká a znovu napíše „Ahoj.“ Jenže co se děje během té minuty, kdy se čeká? Rozhodně se nespí! Mikrokontroler je sám o sobě pod plným výkonem.

Aby nám Arduino usínalo a my třeba při provozu na baterii ušetřili drahocenné miliampéry, stáhneme z GitHubu další knihovnu LowPower pro procesory ATmega a pak už můžeme použít třeba příkaz:

LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);

Který uspí procesor na osm sekund (SLEEP_8S) a na daný čas odpojí i energeticky náročný A/D převodník (ADC_OFF) a detektor výkyvů napětí (BOD_OFF). Ještě bychom mohli vypnout rozhraní SPI, TWI/I2C, některé časovače a UART.

Osm sekund není mnoho, pro delší spánek je tedy třeba příkaz opakovat ve smyčce. Arduino by se jednoduše po každých osmi sekundách probudilo a okamžitě zase usnulo.

Nabízí se ale ještě jedna možnost – trvalý spánek:

LowPower.powerDown(SLEEP_FOREWER, ADC_OFF, BOD_OFF);

Toho „FOREWER“ se ale nebojte, jde prostě o to, že se čip neprobudí sám, ale probudí jej nějaký externí signál. V našem případě RTC hodiny.

110855922
Poslední ukázka kódu níže demonstruje probouzení uspaného Arduina pomocí RTC obvodu a speciálního pulzu

Pojďme si konečně projít jednoduchý kód usínání a probouzení pomocí RTC. V bdělém stavu napíše Arduino zprávu s aktuálním časem do sériové linky, poté usne a přesně po minutě jej RTC hodiny opět probudí. Mohlo by to ale samozřejmě být třeba až za dvě hodiny, anebo v nějaký konkrétní čas bez dalšího opakování.

Jednoduchý kód, který využije schopnosti RTC hodin probudit uspané Arduino

#include <Wire.h> 
// Nova knihovna LowPower z GitHubu
#include <LowPower.h>
#include <DS3231.h>

DS3231 hodiny; 
// Promenna s casem, kdy se ma Arduino probudit 
DateTime casProbuzeni;
char txtHodiny[9];

void setup() {
 Serial.begin(9600);
 Wire.begin();
 hodiny.begin();
 // Priprava digitalniho pinu 2 pro signal od RTC hodin
 pinMode(2, INPUT_PULLUP);
 // Cas prvniho probuzeni nastane presne za minutu od tohoto okamziku
 casProbuzeni = DateTime(hodiny.now().get() + 60); 
}

void loop(){
 // Vypisu do seriove linky, kolik je hodin ve formatu hh:mm
 Serial.print("Jsem vzhuru! Prave je ");
 snprintf(txtHodiny, sizeof(txtHodiny), "%02d:%02d", hodiny.now().hour(), hodiny.now().minute());
 Serial.println(txtHodiny);
 
 // Pripravim RTC pin SQW na vyslani pulzu pro probuzeni
 hodiny.clearINTStatus(); 
 
 /*
 Bud nastavim periodicke probouzeni kazdou sekundu, minutu a hodinu pomoci konstant EverySecond, EveryMinute a EveryHour (priklad: hodiny.enableInterrupts(EveryMinute)), anebo nastavim probuzeni na konkretni cas v ramci dne.
 */
 hodiny.enableInterrupts(casProbuzeni.hour(), casProbuzeni.minute(), casProbuzeni.second());

 // Registrace interruptu na pinu D2 (D2 na Arduino UNO = INT0, proto 0)
 attachInterrupt(0, probuzeni, LOW);

 // Vypisu do seriove linky, ze usinam, vyprazdnim seriovou linku a usnu navzdy...
 Serial.println("Usinam!");
 Serial.flush();
 LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);

 // Od RTC hodin dorazil impulz na INT0, Arduino se probudilo a dokonci se kod smycky

 // Njeprve ukoncim interrupt
 detachInterrupt(0);
 // Cas dalsiho probuzeni nastavim presne minutu po poslednim probuzeni
 casProbuzeni = DateTime(casProbuzeni.get() + 60);
 
 // A jeste dam cipu trosku casu, aby se dal do formy, nez vse zacne znovu
 delay(10);
}

// Pomocna prazdna funkce. Neobsahuje zadny kod, ale vrati nas zpet do smycky loop
void probuzeni(){}

Jak vidno, obvod reálného času nemusí sloužit jen k vypsání, kolik je právě hodin, ale třeba i k přesnému periodickému probouzení Arduina, které jsme převedli do úsporného režimu, ve kterém může spotřeba samotného čipu výrazně klesnout, což se hodí při provozu na baterii.

Diskuze (22) Další článek: Video: Co najdete v novém Computeru

Témata článku: , , , , , , , , , , , , , , , , , , , , , , , ,