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

Napsat komentář