[time-nuts] latest version of arduino solar clock

Jim Lux jimlux at earthlink.net
Mon Jan 20 11:19:06 EST 2014


here's the latest version..
I'm not sure the sign is right on the rate adjustment.

It will allow a "time set" in two different ways:
Unix time as U<unixtime>
or
conventional time as Tyyyymmddhhmmss

I haven't tested it with the mechanical clock yet, but the basic code to 
control the clock is in tick() and tock().  It puts out the right pulses 
(you can see them with a LED and resistor).  I'll head out to the store 
to get a clock a bit later to try the real thing.


One aspect that needs work is the actual timing.  The Timer1 library 
uses a 16 bit counter, so the resolution is poor. (16 usec/tick)  There 
have been multiple messages suggesting ways to do the accurate timing at 
a low level.

What I would like to do is figure out a way to use "vanilla" Arduino 
libraries to do this.

Either one could calculate the error after setting, and let it 
accumulate, and then adjust the rate to compensate..

Or, provide a finer resolution for the timer libraries.

I suspect someone has already done this, so I'll do some googling.
-------------- next part --------------
#include <TimerOne.h>

#include <Time.h>


// Solar clock to drive mechanical mechanism
// Jim Lux, 19 Jan 2014

// assumes a mechanical clock is connected (with appropriate current limiting resistors)
// to pins 6 and 7 (see clkpin1, clkpin2, below)
// puts out a short pulse to those pins, with alternating sign, each second
// the interval between pulses is adjusted according to the current date and equation 
// of time.

// the pulse interval is set using the Timer1.setPeriod( period in microseconds) call
// Since the underlying timer might be 16 bits and the clock is 16 MHz, it is likely
// that the period is some integer multiple of some multilple of microseconds
// depending on the prescaler value.  On the Arduino Uno, the prescaler will
// be 256, because that's what works for 16 MHz and 1 second, so the ticks are
// actually 16 microseconds. 

// this will probably nee dto be fixed in the future to keep the accuracy sufficient
// for solar time.  Or a fancier timer scheme.. maybe counting interrupts at a higher rate
// (e.g. 20 Hz, 50 ms, underlying clock rate of 2 MHz)
//




#include <math.h>
#define UNIX_MSG_LEN  11   // time sync to PC is HEADER followed by Unix time_t as ten ASCII digits
#define UNIX_HEADER  'U'   // Header tag for serial time sync message
#define TIME_REQUEST  7    // ASCII bell character requests a time sync message 
// U1262347200  - sample sync message using unix time
#define TIME_MSG_LEN 15
#define TIME_HEADER   'T'
// Tyyyymmddhhmmss - alternate form
// T20140120080500

const double refclk=31376.6;  //16 MHz/510?
const int clkpin1=6;          // pins going to external clock
const int clkpin2= 7;

int dd, hh;      //current day and hour
boolean UpdateClockFlag; // tells loop() that an interrupt has occurred
boolean pulsepol;

void setup(){
  
  pinMode(clkpin1,INPUT);      // set pins as inputs High Z for now.
  pinMode(clkpin2,INPUT);
  Timer1.initialize(1000000);  // one second
  Timer1.attachInterrupt(Tick);
  Serial.begin(9600);
  delay(1000);
  UpdateClockFlag = false;
  pulsepol = false;
}


void loop(){
  int DOY;
  double e1,e2;
  double secsperday,ratedelta;
  time_t t;

   t = now();      // get the time
  if(Serial.available() ) 
  {
    processSyncMessage();
  }
 // delay(1000);                // hack, til we get ISR timer running
 // UpdateClockFlag = true;    // hack
  
  if (UpdateClockFlag) {
    if(timeStatus() == timeNotSet) 
      Serial.println("waiting for sync message");
    else  {   
 
      DOY = DayWeekNumber(year(),month(),day(),weekday());
      hh = hour();
      e1 = eot(DOY,hh);        // EOT in minutes
      e2 = eot(DOY,hh+1);
      secsperday = (e2-e1)*1440;
      ratedelta = secsperday*1.E6/86400;     //ppm for now,
                                           // but we'll change to divisor later
      Serial.print(ratedelta); Serial.print(" ");
      digitalClockDisplay();  
      Tock();
      // code in here to update interrupt divisor, etc.
      long newper = 1000000+ratedelta;
      Timer1.setPeriod(newper);
    };
    UpdateClockFlag = false;   
  } 
}

// tick tock, drive clock
// tick is an ISR gets called on the interrupt and enables the outputs (with alternating
// polarity). tick sets a flag so that the loop() code can finish the job.
// tock finishes the pulse.

void Tick(){
  UpdateClockFlag = true;        // tell the outside world
   if (pulsepol) {               // set up the polarity appropriately
    digitalWrite(clkpin1,HIGH);
    digitalWrite(clkpin2,LOW);
  } else {
    digitalWrite(clkpin1,LOW);
    digitalWrite(clkpin2,HIGH);
  }    
  pinMode(clkpin1, OUTPUT);      // enable the outputs
  pinMode(clkpin2, OUTPUT);
  digitalWrite(13,digitalRead(13) ^ 1);  // toggle LED

}

void Tock() {
  pinMode(clkpin1, INPUT);        //disable the outputs
  pinMode(clkpin2, INPUT);
  pulsepol = !pulsepol;            // do the next one with reverse pol
}
  
// equation of time code from Tom Van Baak
//http://www.leapsecond.com/tools/eot1.c
double eot(int day,int hour){
    double Pi = 4 * atan(1);

        double y = (2 * Pi / 365.0) * (day - 1 + (hour - 12) / 24.0);
        double eqtime = 229.18 *
                ( 0.000075
                + 0.001868 * cos(y)
                - 0.032077 * sin(y)
                - 0.014615 * cos(2*y)
                - 0.040849 * sin(2*y)
                );
        return(eqtime);
}
// process any message received on serial port
// checks first character to see if it's a Unix Time sync
// or a yyyymmddhhmmss time, and parses accordingly.

void processSyncMessage() {
  int i;  //loop counter
  char c; //character
  // if time sync available from serial port, update time and return true
  while(Serial.available() >=  TIME_MSG_LEN ){  // time message consists of header & 10 ASCII digits
    c = Serial.read() ; 
    Serial.print(c);  
    if( c == UNIX_HEADER ) {       
      time_t pctime = 0;
      for(i=0; i < UNIX_MSG_LEN -1; i++){   
        c = Serial.read();          
        if( c >= '0' && c <= '9'){   
          pctime = (10 * pctime) + (c - '0') ; // convert digits to a number    
        }
      }   
      
      setTime(pctime);   // Sync Arduino clock to the time received on the serial port
    }
    if (c  == TIME_HEADER ){
      int yr=readnumber(4);
      int mo=readnumber(2);
      int dd=readnumber(2);
      int hh=readnumber(2);
      int mm=readnumber(2);
      int ss=readnumber(2);
      setTime(hh,mm,ss,dd,mo,yr);
    }
    
  }
}
// helper routine to read a positive decimal number in ASCII
// of specified length.  returns an int, so don't 
// make ndigits too big
// doesn't deal with signs, ignores characters that are not 0-9
int readnumber(int ndigits){
  int i,temp;
  char c;
  temp = 0;
  for (i=0;i<ndigits;i++){
   c = Serial.read();
   if (c >= '0' && c<='9') {
     temp = (temp * 10 ) + (c -'0');
   }
   }
   return(temp);
}
void digitalClockDisplay(){
  // digital clock display of the time
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.print(" ");
  Serial.print(day());
  Serial.print(" ");
  Serial.print(month());
  Serial.print(" ");
  Serial.print(year()); 
  Serial.println(); 
}
void printDigits(int digits){
  // utility function for digital clock display: prints preceding colon and leading 0
  Serial.print(":");
  if(digits < 10)
    Serial.print('0');
  Serial.print(digits);
}

int  DayWeekNumber(unsigned int y, unsigned int m, unsigned int d, unsigned int w){
  int days[]={0,31,59,90,120,151,181,212,243,273,304,334};    // Number of days at the beginning of the month in a not leap year.
//Start to calculate the number of day
 int DOY;
  if (m==1 || m==2){
    DOY = days[(m-1)]+d;                     //for any type of year, it calculate the number of days for January or february
  }                        // Now, try to calculate for the other months
  else if ((y % 4 == 0 && y % 100 != 0) ||  y % 400 == 0){  //those are the conditions to have a leap year
    DOY = days[(m-1)]+d+1;     // if leap year, calculate in the same way but increasing one day
  }
  else {                                //if not a leap year, calculate in the normal way, such as January or February
    DOY = days[(m-1)]+d;
  }
  return DOY;
// Now start to calculate Week number
//  if (w==0){
 //   WN = (DOY-7+10)/7;             //if it is sunday (time library returns 0)
 // }
//  else{
//    WN = (DOY-w+10)/7;        // for the other days of week
//  }
}


More information about the time-nuts mailing list