Pages: 1 ... 6 7 [8]   Go Down
Author Topic: realtime clock, microseconds, etc.  (Read 6972 times)
0 Members and 1 Guest are viewing this topic.
0
Offline Offline
Newbie
*
Karma: 0
Posts: 6
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Well, let's see...

If I understand correctly, what that technique will get me is a correction factor to apply to my millis() reading, is that right?

If so, I'm already doing that (I'm just doing an initial calibration by running each board for a long period, with periodic outputs of both millis() and the digitalClockDisplay() based on the now() time that gets adjusted by NTP, and observing the drift over time that way). I'm already assuming the solution for my application will have to be just assuming the clock drift is a constant amount, which I can use to compensate my millis() value.

I'm also not sure if I'm missing something, but... when you suggest remembering the times when the NTP request is sent and received, isn't that subject to the same basic problem (e.g. I can't read the current clock time's millisecond value)? The best I'm going to get is time values with second resolution, which won't really give me a useful clock error (it'll be either 0 or 1)...
Logged

Brunsbüttel, SH, F.Rep.GERM
Offline Offline
God Member
*****
Karma: 4
Posts: 596
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

hm [i had to edit my last post the hard way (by removing it)...]

1.
the error varies most likely over time... even if it is the same arduino... e. g. when the temperature changes...

2.
i would keep an offset, that is updated with every myMillis() call...
e. g. offset += (current_millis-last_millis)*current_error_estimation
"current_error_estimation" would be in ppm (nano seconds per milli second)...
"offset" should be a 64bit integer...

3.
the NTP messages would be time stamped with myMillis()...

4.
i dont know if that is a good idea in real life applications (it would use at least 28 bytes of RAM (4 last_millis + 4 current_error_estimation + 8 offset + 8 ntp time stamp + 4 myMillis@ntp time stamp))...

-arne
Logged

-Arne

Ross-on-Wye (UK)
Offline Offline
Newbie
*
Karma: 0
Posts: 29
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi, as others have said, sorry for being here late, but here goes...

I have been working on a battery powered binary clock. This code introduces several techniques, and they are NOT all interdependent. So, portions may be used separately. This project is not yet complete (are they ever??) It also includes main loop activation via. interupts (ISRs) from button presses and a modified delay2() function which calls sleep_cpu().

Of real interest to this discussion is my use of the "compare" interrupt instead of "overflow".

Secondly, I believe all current use of timer0 for time/date/clock purposes should be switched over to timer2.
In the default interrupt priority ordering, timer2 is the highest, it was designed as a real-clock-timer within the CPU
based in part on:
1. its ability to interface with external watch crystals (32.768 kHz)
2. use the prescalar with an external asynchronous clock source (signals on TOSC1 pin).
3. Wake CPU from sleep modes.
(see ATmegaxx8 datasheet for details on all of the above).

There is still some debug code/comments, and I have not created a schematic/diagram of my hardware yet. But, if anyone wants to see the whole project I will post it in exhibition.

Code:
// begin new wall-clock-time routines

#include <avr/io.h>
#include <WProgram.h>
#include "clock_timer2.h"
#include "interrupt_mgmt.h"
/*
 * NOTE: Updates to init() required -- change timer2 to fast-PWM mode!!!
 *                                     and load OCR2A with 249
 *                                     set CTC mode for matchA
 *                                     enable timer2 compare match A interrupt
 */

// XXX volatile unsigned long timer2_matchA_count = 0;
volatile unsigned long timer2_millis = 0;

// The timer2_millis_update_count is a "guard" variable for timer2_millis.
// Its update is atomic. It is only updated in the timer2-compare ISR below.
// Since this ISR is uninterruptable, if another process/task detects
// an increment in timer2_millis_update_count, the ISR below must have started
// and run to completion. This means a "get" of timer2_millis was invalid.
// Simple solution, go get it again. And since this ISR is very small and
// quick compared to the timer2 count interval, any re-gets will be very-very
// infrequent. Also the elapsed time to perform the re-get is very quick.
volatile unsigned char timer2_millis_update_count = 0; // 8bit cyclical

         // SIGNAL() is depricated, using ISR(). See avr/interrupt.h
#if defined(__AVR_ATmega8__)
ISR(TIMER2_COMP_vect)
#else
ISR(TIMER2_COMPA_vect)
#endif
{
      // (volatile variables _ARE_ read from memory on every access)

      // Using Output Compare Register "A" set to 249 (+1 = ticks).
        // milliseconds increment := (1/F_CPU) * prescaler * ticks * millis-per-second
        //                        := (1/16000000) * 64 * 250 * 1000
        //                        := 1
        //  for different clock frequencies the timer2-prescaler may be
        //  ajusted to also call this interrupt on 1 millisecond
        //  boundary...
        //  clock(MHz) : t2-prescaler
        //      16     :    64
        //       8     :    32     (only available on timer2)
        //  for longer cycles (allowing sleep modes to extend battery
        //  life) the following works:
        //  clock(MHz) : prescaler : increment
        //      16     :   1024    :    16
        // ( this ONLY synchronizes with whole seconds on EVEN second boundries).
        //  but it is accurate.
//#define MILLIS_INCREMENT 1UL // prescalar is 64
#define MILLIS_INCREMENT 16UL // prescalar is 1024
        
        // reducing system clock with its prescaler can save more
        // but is more complex.
        
        // this is a candidate for being ISR_NAKED and coding
        // the following in assembler
        
        timer2_millis_update_count++;
      timer2_millis += MILLIS_INCREMENT;

// XXX      timer2_matchA_count++;
}

volatile unsigned long millis2_spin_wait_count = 0; // debug test
volatile unsigned long millis2_count = 0; // debug test
unsigned long millis2()
{
      unsigned long m;
      unsigned char bc;

      {
        bc = timer2_millis_update_count; // get the before count
        m = timer2_millis;
        // if ISR changed 8bit counter --> must re-fetch multi-byte value
        //    else its OK just return it.
        if (bc == timer2_millis_update_count) return m;
        // XXX should  repeat ONE time only, may need better check
        // millis2_spin_wait_count++;
      } while (1);
}

unsigned long get_spin_wait() // debug test
{
      unsigned long w;
      w = millis2_spin_wait_count;
      return w;
}
unsigned long get_m2() // debug test
{
      unsigned long w;
      w = millis2_count;
      return w;
}

volatile unsigned long micros2_spin_wait_count = 0; // debug test
volatile unsigned long micros2_count = 0; // debug test
unsigned long micros2() {
      unsigned long m, t;
      unsigned char bc;

      // see comments in "millis2()"
      micros2_count++; // debug test
      {
        bc = timer2_millis_update_count; // get the before count
        t = TCNT2;
        m = timer2_millis;
        // if ISR changed 8bit counter --> must re-fetch multi-byte value
        //    else its OK just return it.
        m = timer2_millis * 1000UL * MILLIS_INCREMENT;
      
      
        if (bc == timer2_millis_update_count)
        {
          // Note: MILLIS_INCREMENT * 64 / c-c-perMicroS should
            //       be optimized to a single constant by the compiler.
          return (m  + (t * MILLIS_INCREMENT * 64 / clockCyclesPerMicrosecond()));
        }
        // XXX should  repeat ONE time only, may need better check
        // micros2_spin_wait_count;
      } while (1);
}

/*
void trueSleep2(unsigned long ms)
*/
void delay2(unsigned long ms)
{
      unsigned long start = millis2();
      
      // for details see:
      // http://www.nongnu.org/avr-libc/user-manual/group__avr__sleep.html
#if defined(__AVR_ATmega8__)
        // set sleep mode Power Save
      set_sleep_mode(SLEEP_MODE_PWR_SAVE);
#else
        // set sleep mode External Standby
      set_sleep_mode(SLEEP_MODE_EXT_STANDBY);
#endif
      // set Sleep Enable Bit
      cli();
      sleep_enable();
      
      while (millis2() - start <= ms && get_pcintx() == 0)
        {
            sei(); // always perform IMMEDIATELY prior...
            sleep_cpu();
      }
      // clear Sleep Enable Bit
      sleep_disable();
        sei(); //to be safe
//        clear_pcintx();
}

unsigned long get_micros2_spin_wait() // debug test
{
      unsigned long w;
      w = micros2_spin_wait_count;
      return w;
}
unsigned long get_micros2() // debug test
{
      unsigned long w;
      w = micros2_count;
      return w;
}

void clock_timer2_init()
{

#if defined(_T2_PRESCALE_64_)
      // set timer 2 prescale factor to 64
#if defined(__AVR_ATmega8__)
      sbi(TCCR2, CS22);
#else
      sbi(TCCR2B, CS22);
#endif
#else
      // set timer 2 prescale factor to 1024
#if defined(__AVR_ATmega8__)
      sbi(TCCR2, CS22);
      sbi(TCCR2, CS21);
      sbi(TCCR2, CS20);
#else
      sbi(TCCR2B, CS22);
      sbi(TCCR2B, CS21);
      sbi(TCCR2B, CS20);
#endif
#endif

        // load match value for timer2
        // 0::249  => 250 ticks
#if defined(__AVR_ATmega8__)
        OCR2 = 249;
#else
        OCR2A = 249;
#endif
        // set CTC mode (clear timer on compare).
#if defined(__AVR_ATmega8__)
        // set CTC mode only
      sbi(TCCR2, WGM21);
#else
      // configure timer 2 for fast hardware pwm
        // _AND_ CTC
      sbi(TCCR2A, WGM21);
      sbi(TCCR2A, WGM20);
      sbi(TCCR2B, WGM22);
#endif

      // enable timer2 compare  interrupt
#if defined(__AVR_ATmega8__)
      sbi(TIMSK, OCIE2);
#else
      sbi(TIMSK2, OCIE2A);
#endif


}

void clock_timer2_dis_timer0()
{
      // disable timer 0 overflow interrupt
#if defined(__AVR_ATmega8__)
      cbi(TIMSK, TOIE0);
#else
      cbi(TIMSK0, TOIE0);
#endif
}

// end new wall-clock-time routines
Logged

London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Using timer2  as another way to do millis loses the Tone output that was added in the latest Arduino release.

Also, what happens to to the  two analog output pins that are driven from timer 2?

I would be interested in seeing the results of some real world tests that comapred the accuracy of a clock using timer2 instead of millis on timer0
« Last Edit: April 19, 2010, 05:10:31 pm by mem » Logged

Ross-on-Wye (UK)
Offline Offline
Newbie
*
Karma: 0
Posts: 29
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi, in reply to mem's comments.

All existing functionality of timer2 may swapped with timer0 (Tone library, PWM pins, everything). Yes, I understand this would necessitate updating all library code using timer2 and timer0 (and everyones applications likewise). But,  as I stated, timer2 was designed by the chip designer for driving real-time clock. Using timer0 has drawbacks which will be evermore evident with the growth of Arduino. And, I would like to emphasize again, there are several feature/techniques used in this code, they may be used separately. i.e. just change timer overflow to compare-match. Switch timer0 with timer2. Use a guard variable instead of global disable interrupts.

Regarding accuracy, timer2 itself is identical to timer0. Its the use of the compare-match feature (available in both timer2 and timer0) which both simplifies the counting and improves accuracy. Using the COMPare interrupt the timer returns to zero following 249 instead of 255. Millis is incremented exactly every 1/1000 th of a second instead of every 1/1024 th. This eliminates the correction code. It eliminates clock "jitter". (no inaccuracy over short intervals, precision and long interval accuracy remain the same). It simplifies the code in the ISR, eliminating the need for all that FRACT_INC FRACT_MAX stuff.
Logged

Ross-on-Wye (UK)
Offline Offline
Newbie
*
Karma: 0
Posts: 29
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Sorry, math flub. The current scheme updates millis every  1024/1000000 th of a second, which is 1/976.5625 th of a second.
(that cannot be right mixing fractions and decimals?? -- another reason to change...  smiley-wink
Logged

London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi rfrankla, what happens to analog output (PWM) on the timer when you use OC ?
Logged

Ross-on-Wye (UK)
Offline Offline
Newbie
*
Karma: 0
Posts: 29
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi mem, as I understand it only from reading the datasheet for the ATmegaxx8, PWM will continue to work with the difference being the interval being 250 timer ticks (exactly 1/1000 th of a second) so the math of PWM will change slightly... and this will affect applications and libraries, some more than others.

I would not consider this a quick change by any means. It should be carefully considered and planned. But, I do believe it is the correct thing to do long term. I have lots of software experience, but I have only played with a micro-controller since receiving my Christmas present. I'm still working up to motion sensors and motor control. (And saving my pennies).
Logged

Pages: 1 ... 6 7 [8]   Go Up
Jump to: