Arduino v příkladech - XIV. díl - Displej a hodiny
K čemu je dobré měřit čas. To je na dlouhou debatu. U svých projektů ho hlavně využívám k časování spínání a ukládání logů. O tom toho bylo už napsáno dost, ale o nastavení času? To jsem nenašel.
Podáváme se nejen na to, jak zprovoznit modul času, ale i jak ho za provozu nastavovat za pomoci displeje. A nakonec, když už máme ten displej, který 99% času nepoužíváme, tak si na něm zobrazíme hodiny, ať nesvítí zbytečně. Také je tu důležitá informace ohledně jedné drobnosti, na kterou kolegové zapomínají.
Modul času
K dispozici máme dva. DS1307 a DS3231. Tyto hodinové moduly jsou zálohovány baterii CR2032, na nichž vydrží běžet roky. Mají vlastní EEPROM paměť, budík, nastavení formátu času a spoustu vychytávek. Jaký je mezi nimi rozdíl? Pokud pominu, dvacet korun, tak hlavní rozdíl je v přesnosti. Modul DS3231 je podstatně přesnější – odchylka je ve vteřinách za rok. Vyndal jsem jeden ze šuplíku, kde ležel dva roky. Odchylka 23 s, paráda. U modulu DS1307 podle umístění, může odchylka dosahovat až několika desítek minut ročně. Osobně používám DS3231, ale pokud máte ten druhý při pokojové teplotě, dá se s ním bez problémů také pracovat.
My se podíváme na práci s modulem DS3231.
Na co si dát pozor
Moduly se prodávají ve dvou verzích. Jedna s integrovaným nabíjením baterie a druhá bez tohoto nabíjení. Přestože Vám budou tvrdit, že je to zrovna ta verze bez nabíjení, vždy přišla ta s nabíjením. Pokud tedy nedáte do modulu nabíjecí baterii, nebo budete Arduino používat v bateriovém provozu, bude nezbytné udělat malou úpravu. Je potřeba na desce přerušit přívod z napájení k baterii, nebo vyjmout odpor 201 Ω, viz foto. Pro úsporu energie ještě můžete odstranit svítící diodu.
Zapojení
Pro naší ukázku budeme potřebovat Arduino UNO, SPI displej a modul času. Arduino máme, displej známe, modul má I2C zapojení (A4 na SDA a A5 na SCL) a 5V logiku. Adresa hodinového modulu je 0x68, adresa EEPROM je 0x57. U modulu DS3231 se dá adresa hodin změnit propojkami A0, A1 a A2.
Zobrazujeme čas
Nejdříve si ukážeme zprovoznění modulu bez displeje. Vystačíme si s instalovanými knihovnami. Program na úvod zjistí I2C adresu modulu, to aby jsme se s tím nemuseli mořit a začne vypisovat čas na serial monitor.
Nastavení času při prvním zprovoznění modulu je zapotřebí provést nastavením adjustment_time = true; a vyplněním hodnot do funkce setDS3231time. Čas už zůstane v modulu nastaven, pak vypněte funkci nahrání času změnou hodnoty adjustment_time = false;
//------------------------------------------------------------------
// inicializace zakladnich knihoven
#include <Wire.h> // inport knihovny I2C
//------------------------------------------------------------------
// promenne pro vyhledani adres hodinoveho modulu
byte error; // prommena pro zobrazeni chyby
int nDevices = 0; // promenna pro vyhledavani poctu zarizeni
//------------------------------------------------------------------
// promenne pro zobrazeni casu
#define DS3231_I2C_ADDRESS 0x68
byte second;
byte minute;
byte hour;
byte dayOfWeek;
byte dayOfMonth;
byte month;
byte year;
//------------------------------------------------------------------
// povoleni nahrani aktualniho casu
boolean adjustment_time = false;
//------------------------------------------------------------------
// spusteni programu
void setup() {
Wire.begin(); // inicializace knihovny I2C
Serial.begin(9600); // nastaveni rychlosti seriove komunikace
//------------------------------------------------------------------
// vyhledani adresy hodinoveho modulu
Serial.println("Skenuji...");
for (byte address = 0; address < 127; address++ ) { // prochazeni povolenych adres
Wire.beginTransmission(address); // volani adresy
error = Wire.endTransmission(); // ukonceni volani
if (error == 0) { // kdyz je vracena hodnota false, zobrazi nalezenou adresu
Serial.print("I2C zarizeni nalezeno na adrese 0x");
if (address < 16) Serial.print("0");
Serial.print(address, HEX);
Serial.println(" !");
nDevices++;
}
else if (error == 4) { // kdyz je vracena hodnota true, zobrazi chybu hledani
Serial.print("Neznama chyba na adrese 0x");
if (address < 16) Serial.print("0");
Serial.println(address, HEX);
}
}
// zobrazeni poctu nalezenych zarizeni
if (nDevices == 0) Serial.println("Zadne zarizeni nenalezeno");
else {
Serial.print("Nalezeno ");
Serial.print(nDevices);
Serial.println(" I2C zarizeni.");
}
//------------------------------------------------------------------
// nastaveni casu pri prvnim spusteni
// DS3231 seconds, minutes, hours, day, date, month, year
if (adjustment_time) setDS3231time(30, 42, 21, 4, 26, 11, 14);
}
//------------------------------------------------------------------
// hlavni smycka
void loop() {
readDS3231time(); // precti cas a uloz promenne
displayTime(); // zobraz aktualni cas,
delay(1000); // pockej vterinu
}
//------------------------------------------------------------------
// ulozeni casu
void setDS3231time(byte second, byte minute, byte hour, byte dayOfWeek, byte dayOfMonth, byte month, byte year) { // uloz data do DS3231
Wire.beginTransmission(DS3231_I2C_ADDRESS); // otevri komunikaci
Wire.write(0); // nastav prvni hodnotu registru pred zapisem
Wire.write(decToBcd(second)); // uloz vteriny
Wire.write(decToBcd(minute)); // uloz minuty
Wire.write(decToBcd(hour)); // uloz hodiny
Wire.write(decToBcd(dayOfWeek)); // uloz den tydne (1=Nedele, 7=Sobota)
Wire.write(decToBcd(dayOfMonth)); // uloz den mesice (1-31)
Wire.write(decToBcd(month)); // uloz mesic
Wire.write(decToBcd(year)); // uloz rok (0 to 99)
Wire.endTransmission(); // ukonci komunikaci
}
//------------------------------------------------------------------
// cteni casu
void readDS3231time() {
Wire.beginTransmission(DS3231_I2C_ADDRESS); // otevri komunikaci
Wire.write(0); // nastav prvni hodnotu registru pred ctenim
Wire.endTransmission(); // ukonci komunikaci
Wire.requestFrom(DS3231_I2C_ADDRESS, 7); // precti 7 bytu
// dotaz na data z DS3231 zaciname v registru 00h
second = bcdToDec(Wire.read() & 0x7f);
minute = bcdToDec(Wire.read());
hour = bcdToDec(Wire.read() & 0x3f);
dayOfWeek = bcdToDec(Wire.read());
dayOfMonth = bcdToDec(Wire.read());
month = bcdToDec(Wire.read());
year = bcdToDec(Wire.read());
}
//------------------------------------------------------------------
// zobraz data z hodinoveho modulu
void displayTime() {
Serial.print(hour, DEC);
Serial.print(":");
if (minute < 10) {
Serial.print("0");
}
Serial.print(minute, DEC);
Serial.print(":");
if (second < 10) {
Serial.print("0");
}
Serial.print(second, DEC);
Serial.print(" ");
Serial.print(dayOfMonth, DEC);
Serial.print("/");
Serial.print(month, DEC);
Serial.print("/");
Serial.print("20");
Serial.print(year, DEC);
Serial.print(" Den tydne: ");
switch (dayOfWeek) {
case 1:
Serial.println("Nedele");
break;
case 2:
Serial.println("Pondeli");
break;
case 3:
Serial.println("Utery");
break;
case 4:
Serial.println("Streda");
break;
case 5:
Serial.println("Ctvrtek");
break;
case 6:
Serial.println("Patek");
break;
case 7:
Serial.println("Sobota");
break;
}
}
//------------------------------------------------------------------
// prevod jednotek pro ulozeni dat do hodinoveho modulu
byte decToBcd(byte val) {
return ((val / 10 * 16) + (val % 10));
}
//------------------------------------------------------------------
// prevod jednotek pro cteni dat z hodinoveho modulu
byte bcdToDec(byte val) {
return ((val / 16 * 10) + (val % 16));
}
Jednoduché a funkční s jednou malou nevýhodou, pokud nebudete mít po ruce počítač, čas nezměníte.
Nastavujeme čas
Teď tam přidáme ten displej. Po spuštění programu uvidíte běžící hodiny. Mě osobně se to moc líbí. Po stisku displeje se Vám zobrazí nastavení času. Moc jsem tomu graficky nedal, ale funguje to.
Protože je program poněkud větší, najdete ho ke ztažení zde.
Teď už je program podstatně složitější. Využili jsme většinu toho, co jsme se od počátku naučili. Čas nejen zobrazujeme, ale v interakci s displejem i nastavujeme. Třeba změna letní na zimní. Dá se řešit i programově, ale to zabírá zase místo. Jsme na jednočipu, místa není nikdy dost.
Program je pečlivě popsaný a kdo to s modulem myslí vážně, vytáhne si co potřebuje. Co ale zmíním, že mimo práci s modulem času jsme si ukázali ještě jednu důležitou věc. A to je výpočet úhlů. Úžasná věc pro grafické zobrazení nastavení klapek, sklonu atd. V budoucnu se k tomu vrátíme.
Závěr
Program nám zabírá ¾ paměti Arduino Uno a to jsem původně chtěl šipky kreslit. Nevešlo se to tam. Prostě použité Arduino Uno není pro větší projekty s displejem moc vhodné. Řešení jsou dvě. Buď použijeme větší čip, anebo to co zabírá místo přesuneme přímo do displeje.
My se podíváme na druhou variantu a proto tímto článkem uzavřeme displeje na SPI a příště se podrobněji podíváme na displej Nextion, jak ho načal kolega.
JB
Přidat komentář
Přehled komentářů
-
Zálohování reálného času
(Tomáš H., 27. 12. 2016 17:23:49)
Odpovědět
| Zobrazit odpovědi
Dobrý den, zakoupil jsem zde před časem modul RTC DS1307 s 32K flash. Pochopil jsem, že při vložené baterii a bez externího napájení by čas interně v DS1307 měl normálně běžet. Mi čas běží jen při připojeném externím napájení. Po odpojení a opětovném připojení napájení je čas na té hodnotě, na které byl před odpojením. Tedy obvod si pamatuje poslední hodnotu času, ale neposouvá jej. Je to správně a já tuto funkci špatně pochopil nebo dělám něco špatně?
-
Re: Zálohování reálného času
(Kádlík, 26. 12. 2020 14:32:18)
Odpovědět
Zřejmě jsi na to už přišel. Ale přesto, změř napětí baterie při odpojeném napájení arduina -nebo s vyjmutým modulem. Mělo by bejt kolem 3V
-
Wire a Serial
(DiDi, 17. 1. 2021 21:32:31)
Odpovědět
Ahoj. Mám takovou potíž zařízení na SAMD21 byl nespolehlivý Wire tak jsem opravil kód.
Bez USB naběhne až do Wire kde čeká na připojení USB. Po odpojení USB zase čeká a po připojení jede tam kde skončil a nemůžu přijít na to jak to vyřešit. Protože potřebuji jet pes aktivního USB spojení...