Close

Retrográdní hodiny

Retro Arduino hodiny

Ing. Jaroslav Kavalír nám poslal popis velice zajímavého projektu – “Retrográdní hodiny” – tedy hodin, které k vizualizaci času používají analogové voltmetry. Ty byly vyrobeny někdy kolem roku 1950 a pocházejí ze sovětského servisního přístroje.

Voltmetry

Voltmetry ukazují popořadě dny v týdnu, hodiny, minuty, vteřiny a teplotu. Čas se dá nastavit přes USB z PC. Pro běžnou korekci je určeno tlačítko, které vynuluje sekundy. Letní čas je nastavován automaticky softwarově. Poslední voltmeter ukazuje teplotu.

Retro Arduino hodiny - Detail ciferníků

Detail ciferníků

RTC DS3231

Aktuální čas zajišťuje modul reálného DS3231.

Srdcem hodin je modul hodin reálného času DS3231. Výrobce uvádí přesnost ±2ppm od 0°C do +40°C což je asi 5 vteřin za měsíc. Čas z modulu je čten mikroprocesorem Arduino Uno přes sběrnici I2C. Časové údaje jsou převedeny pomocí PWM regulátorů na napětí a to je přes odpory a trimry přivedeno na voltmetry. Trimry je možné při oživování nastavit přesný rozsah měřidla. To by se dalo udělat i softwarově, ale takhle je to jednodušší. Velikost předřadného odporu se vypočte podle použitého voltmetru. V mém případě byl potřeba na plnou výchylku proud 1 mA. Takže při 5 V je to 5 kOhm. Použil jsem odpory 4k7 v sérii s 1k trimrem. Běžně bývá u voltmetru na plnou výchylku 100 uA, takže hodnoty budou 10x vyšší.

Arduino retro hodiny - Pohled dovnitř

Pohled dovnitř

Program

Vlastní program je složen z toho co jsem posbíral na WEBu bez toho, že bych kód nějak optimalizoval.

Ke stažení naleznete program zde.

/* Zobrazuje čas a teplotu na 5-ti voltmetrech. 
 *  DS3231 je propojen na Arduino-Uno; SDA-A4; SLC-A5; +5V; GND
 *  Korekce pomocí tlačítka. 
Pro 0 - 30 se sekundy vynulují a pro 30 - 59 se nastaví na 59.
Je nastavován i letní čas. Ispirace Arduino.cz a WEB */
#include "Wire.h"
#define DS3231_I2C_ADDRESS          0x68
// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val){
   return( (val/10*16) + (val%10) );
}
// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val){
   return( (val/16*10) + (val%16) );
}
void setup(){
   Wire.begin();
   pinMode(3, OUTPUT);    pinMode(5, OUTPUT);
   pinMode(6, OUTPUT);    pinMode(10, OUTPUT);
   pinMode(11, OUTPUT);   pinMode(8, INPUT);  
   Serial.begin(9600);
   // set the initial time here:
   // DS3231 seconds, minutes, hours, day, date, month, year
 // setDS3231time(50,27,11,04,1,12,0); //****tady se nastavi poprve hodiny a pak REM //*** //
}
void setDS3231time(byte second, byte minute, byte hour, byte dayOfWeek, byte dayOfMonth, byte month, byte year){
   // sets time and date data to DS3231    
   Wire.beginTransmission(DS3231_I2C_ADDRESS);
   Wire.write(0); // set next input to start at the seconds register
   Wire.write(decToBcd(second)); // set seconds
   Wire.write(decToBcd(minute)); // set minutes
   Wire.write(decToBcd(hour)); // set hours
   Wire.write(decToBcd(dayOfWeek)); // set day of week (1=Sunday, 7=Saturday)
   Wire.write(decToBcd(dayOfMonth)); // set date (1 to 31)
   Wire.write(decToBcd(month)); // set month
   Wire.write(decToBcd(year)); // set year (0 to 99)
   Wire.endTransmission();  
}
void readDS3231time(byte *second, byte *minute, byte *hour, byte *dayOfWeek, byte *dayOfMonth, byte *month, byte *year){
   Wire.beginTransmission(DS3231_I2C_ADDRESS);
   Wire.write(0); // set DS3231 register pointer to 00h
   Wire.endTransmission();
   Wire.requestFrom(DS3231_I2C_ADDRESS, 7);
   // request seven bytes of data from DS3231 starting from register 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());
}
void displayTime(){
   pinMode(8,INPUT_PULLUP);
   int tlac = 8;    
   byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
   // retrieve data from DS3231
   readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
   tlac = !digitalRead(8);   //čtení tlačítka korekce a negace
   byte se = second;         //převod na pomocné proměnné
   byte mi = minute;
   byte ho = hour;
   byte we = dayOfWeek;
   byte dm = dayOfMonth;
   byte mo = month;
   byte ye = year;
   
 //korekce sekund zpět              
if (tlac>0 && se<30) { setDS3231time(0,mi,ho,we,dm,mo,ye);  }
   //korekce sekund vpřed               
if (tlac>0 && se>30) { setDS3231time(59,mi,ho,we,dm,mo,ye); } 
   //změna času na letní - poslední neděle v březnu ve 2 hodiny
if ((dayOfWeek == 7)&&(dayOfMonth >= 25)&&(month == 3)&&(hour == 2)) {
setDS3231time(se,mi,3,we,dm,mo,ye); }   // nastavení hodin na 3 hodinu 
   //změna času na zimní  
if ((dayOfWeek == 7)&&(dayOfMonth >= 25)&&(month == 10)&&(hour == 1)&&(year != 1)) {
setDS3231time(se,mi,ho,we,dm,mo,1); }  //rok použit jako indikace, že bylo léto
   
if ((dayOfWeek == 7)&&(dayOfMonth >= 25)&&(month == 10)&&(hour == 3)&&(year == 1)) {
setDS3231time(se,mi,2,we,dm,mo,0);  } // nastavení hodin na 2 hodinu a příznak na 0
   // příznak je tam, aby se ve 3 hodiny znovu nenastavovalo na 2
   //------------------------------------------------------------------
   // send it to the serial monitor
     Serial.print(hour, DEC);
     
   // convert the byte variable to a decimal number when displayed
     Serial.print(":");
   if (minute<10){
     Serial.print("0");
   }
     Serial.print(minute, DEC);
   
     Serial.print(":");
   if (second<10){
     Serial.print("0");
   }
     Serial.print(second, DEC); 
     
   
   // send it to the Voltmetrs  ----------------------------------------  
   second = second * 4.25;         // 60 sec na 255 dílků
   analogWrite(3,second);          // nastaveni plneni PWM
   
   minute = (minute * 4.25) + (second/60); // 60 min na 255 dílků plus vteřiny
   analogWrite(6,minute);         // nastaveni plneni PWM
   if (hour > 12 ){hour = hour - 12;  }
   else { hour = hour; }
   hour = hour + 6;             //posunuti stupnice, 12 uprostred
   hour = (hour * 21.25) + (minute/12);  //12 hodin na 255 dílků plus minuty
   analogWrite(10,hour);        // nastaveni plneni PWM
   
dayOfWeek = (dayOfWeek * 36.4) - 16;
     analogWrite(11,dayOfWeek);
   //--------------------------------------------
     Serial.print(" ");
     Serial.print(dayOfMonth, DEC);
     Serial.print("/");
     Serial.print(month, DEC);
     Serial.print("/");
     Serial.print(year, DEC);
     Serial.print(" ");
     Serial.print(we, DEC);
     Serial.print(" den ");
     Serial.print(" ");
          
    switch(we){
       case 1:
             Serial.print("pondeli ");                
           break;
       case 2:
             Serial.print("utery ");           
           break;
       case 3:
             Serial.print("streda ");           
           break;
       case 4:
             Serial.print("ctvrtek ");          
           break;
       case 5:
             Serial.print("patek ");           
           break;
       case 6:
             Serial.print("sobota ");            
           break;
       case 7:
            Serial.print("nedele ");          
           break;      
   }
  }
void loop(){
   displayTime(); // display the real-time clock data on the Serial Monitor,    
   delay(200);   // every 0,2 second
#define DS3231_I2C_ADDR             0x68
#define DS3231_TEMPERATURE_ADDR     0x11
  int tempC = DS3231_get_treg();  // Reads the temperature as an int, to save memory
  // float tempC = DS3231_get_treg();
   byte teplota;
     Serial.print(tempC);
     Serial.println("C ");
   teplota = (tempC-11.5)*6.125 ; //stupnice nastavena na rozsah5 až 250
                                 //korigováno na zvýšenou teplotu IO
   analogWrite(5, teplota);      //teplota je v PWM
}
float DS3231_get_treg()
{
   int rv;  // Reads the temperature as an int, to save memory 
   uint8_t temp_msb, temp_lsb;
   int8_t nint;
   Wire.beginTransmission(DS3231_I2C_ADDR);
   Wire.write(DS3231_TEMPERATURE_ADDR);
   Wire.endTransmission();
   Wire.requestFrom(DS3231_I2C_ADDR, 2);
   temp_msb = Wire.read();
   temp_lsb = Wire.read() >> 6;
   if ((temp_msb & 0x80) != 0)
       nint = temp_msb | ~((1 << 8) - 1);      // if negative get two's complement
   else
       nint = temp_msb;
   rv = 0.25 * temp_lsb + nint;
   return rv;
}

Význam kódu

Řádek Popis
11 Tady se při nastavování nastaví zda je právě letní nebo zimní čas
30 Tady se při nastavování zapíše aktuální čas, program se nahraje do Arduina a pak se tento řádek zaREMuje.
33-60 Komunikace s modulem DS3231, převzato z WEBu tak jak je
70 Hodiny mají jediné korekční tlačítko na Pin 8
80-87 Při stisknutí korekčního tlačítka v intervalu 0 < 30 sekund se sekundy vynulují a při sec > 30
se sekundy nastaví na 59. Tím se nemusí řešit zda se minuty změní nebo ne. Korekce nebude nutná často, neboť DS3231 je velmi přesný a odchylka je za měsíc u mého kusu menší než 2 sec
111-120 Řeší se změna na letní/zimní čas. Je tu použit label = leto aby se zamezilo nesprávnostem při přechodu na zimní čas. Když se posune čas o hodinu zpět, tak by se za hodinu posunoval znova
170-199 Je měření teploty – převzato z WEBu. Je nutná drobná korekce teploty, protože uvnitř hodin je tepleji. Teploměr jsem tam přidal protože bylo k dispozici pět voltmetrů z ruského přístroje z roku 1950.

Kód by se dal jistě ještě vylepšit. Převzaté části, když chodily, jsem nezkoumal. Sériová komunikace by se také dala vypustit, slouží jen pro oživení. Dalo by se doplnit ještě mnoho funkcí. Například tikání, odbíjení hodin, zvonkohra a podobně. Chce to jen čas. 🙂

Děkujeme za sdílení velice povedeného projektu! Další fotky si můžete prohlédnout zde.

Zbyšek Voda

Zbyšek Voda

Už nějaký čas se zajímám o věci kolem Internetu věcí a otevřeného hardware a software. Tak jsem se také v roce 2010 dostal k Arduinu, pro které dodnes programuji a taky píšu články o práci s ním. Baví mě vymýšlet, jak staré věci používat novým způsobem.
Zbyšek Voda

Latest posts by Zbyšek Voda (see all)

Napsat komentář