Arduino Mega + Chronodot for accurate timing ???

Hello everyone, I currently own an Arduino Duemilanove and have enjoyed using it. However, I have had one issue with it and that is getting accurate timing from it. I’ve visited the IRC channel and asked about this but never got much response so I thought I’d try and ask here.

Basically, I need to fire off a commands within 16.6667ms intervals (1/60th of a second). I found using various timing techniques with the Arduino (using loops with millis(), micros(), delay(), delayMicroseconds(), and even using hardware interrupts) and none ever give me precise timing. The Arduino will usually end up being too fast or too slow by 1ms and sometimes right on the money. I need something more accurate and consistent.

So, that leads me to my questions, I recently ordered an Arduino Mega (I really love these things so I wanted to give this a shot as well) and a Chronodot RTC module. I plan to make a loop that checks the time on the Chronodot and to fire off the commands when 1/60th of a second has passed.

I’ve seen examples of the Chronodot on their website:

#include <Wire.h>

void setup()
{
  Wire.begin();
  Serial.begin(9600);
}

void loop()
{
  // send request to receive data starting at register 0
  Wire.beginTransmission(104); // 104 is DS3231 device address
  Wire.send(0); // start at register 0
  Wire.endTransmission();
  Wire.requestFrom(104, 3); // request three bytes (seconds, minutes, hours)

  while(Wire.available())
  { 
    int seconds = Wire.receive(); // get seconds
    int minutes = Wire.receive(); // get minutes
    int hours = Wire.receive();   // get hours
        
    seconds = (((seconds & 0b11110000)>>4)*10 + (seconds & 0b00001111)); // convert BCD to decimal
    minutes = (((minutes & 0b11110000)>>4)*10 + (minutes & 0b00001111)); // convert BCD to decimal
    hours = (((hours & 0b00110000)>>4)*10 + (hours & 0b00001111)); // convert BCD to decimal (assume 24 hour mode)
    
    Serial.print(hours); Serial.print(":"); Serial.print(minutes); Serial.print(":"); Serial.println(seconds);
  }

  delay(1000);
}

So my questions are:

  • Why is the calculation to get the seconds the exact same calculation as getting the minutes?
  • How can I convert the data received from the Chronodot into even smaller increments than seconds, for instance, milliseconds, microseconds, nanoseconds, ETC…
  • Will using a Chronodot as my timing mechanism give me the consistent results that I am looking for?

Thank you for any help you can provide!

Why is the calculation to get the seconds the exact same calculation as getting the minutes

Because there are 60 seconds in a minute, and 60 minutes in an hour, and both are represented in BCD?

How can I convert the data received from the Chronodot into even smaller increments than seconds, for instance, milliseconds, microseconds, nanoseconds, ETC....

You can't - it is accurate to the second.

Will using a Chronodot as my timing mechanism give me the consistent results that I am looking for?

Probably not - even if you divided down the 32768Hz output.

Have you thought of using 60Hz mains frequency (I assume you're in a 60Hz region) to generate your "ticks"?

I’ve tried using this:

boolean is_setup;  // flag whether prescale & timer has been initialized
boolean is_waiting;  // set to false by interrupt once a frame has passed
 
// interrupt handler
ISR(TIMER1_COMPA_vect) 
{
  is_waiting = false;
} 

// setup interrupt timer
void setup_timer() 
{
  // disable interrupts
  cli();

  // Set CTC mode (Clear Timer on Compare Match) (p.133)
  // Have to set OCR1A *after*, otherwise it gets reset to 0!
  TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
  TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));

  // 8 prescaler (p.134)
  TCCR1B = (TCCR1B & ~( _BV(CS12) | _BV(CS10))) | _BV(CS11);

  // = 16666 microseconds (each count is .5 us)  
  OCR1A = 33333;  

  // Enable interrupt when TCNT1 == OCR1A (p.136)
  TIMSK1 |= _BV(OCIE1A);

  is_waiting = true;

  // enable interrupts 
  sei();

  is_setup = true;
}

// wait for 1 frame, e.g. 1/60th of second. = 16.6_ milliseconds
void frame()
{
  // kinda silly to check this every time, but avoids breaking old sketches
  // by making users setup the timer manually
  if ( ! is_setup ) {
    setup_timer();
  }

  // dunno if spinning until the flag is set by interrupt is the best way to do this . . .
  while( is_waiting ) {
    delayMicroseconds(10);
  }
  is_waiting = true;
  sei();
}

void frames( int skip )
{
  for ( int i=0; i < skip; ++i ) {
    frame();
  }
}

This is still of by .5ms-1ms at times.