Pages: [1] 2 3 ... 8   Go Down
Author Topic: realtime clock, microseconds, etc.  (Read 8631 times)
0 Members and 1 Guest are viewing this topic.
0
Offline Offline
God Member
*****
Karma: 1
Posts: 513
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

This is a work in progress, but I wanted to look at re-arranging the timer0 code a tad to better facilitate built in real time clock support and getting access to sub millisecond timing.

With this, timer0 will keep track of seconds and of the number of calls to timer0, and use a point where it can translate the number of timer0 calls to seconds without error (1 call = 1.024 seconds), or 128/125 in lowest common denominator.  So every 125000 calls, it ads 128 seconds to the timer0_seconds accumulator and subtracts 125k from its own counter.  This means that we can keep accurate track of time down to the second for 130 YEARS and build on it to get an accurate microseconds stamp.

so step one (more steps to follow eventually), in wiring.c  (couldn't test on 8mhz but works flawlessly on 16mhz):
Code:
#include "wiring_private.h"

volatile unsigned long timer0_count = 0;
volatile unsigned long timer0_seconds = 0;

SIGNAL(SIG_OVERFLOW0)
{
    timer0_count++;
    while(timer0_count>=125000){
      timer0_count-=125000;
      timer0_seconds+= 8* clockCyclesPerMicrosecond();
    }
}

unsigned long millis(){
      unsigned long m, s;
      uint8_t oldSREG = SREG;
      cli();
      m = timer0_count;
      s = timer0_seconds;
      SREG = oldSREG;
      m=(m*( 8* clockCyclesPerMicrosecond())/125)+(s*1000);                          return m;
}
...
Logged

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

Hi DCB, just in case you were not aware of it, there is a DateTime library in the playground that keeps track of time in seconds that doesn't require modifying wiring.c

It's a software real time clock that can be set to the actual time, or if set to zero at startup will return the number of seconds since the sketch started.

The playground article is here: http://www.arduino.cc/playground/Code/DateTime

Logged

0
Offline Offline
God Member
*****
Karma: 1
Posts: 513
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Sure, I remember that, was glad to see it, but without interrupt support for the time model, you had to be sure to call it every so often or else it could miss a millis() turnover, IIRC.  With this, the timer0 interrupt keeps track of actual run time internally for a ridiculously long amount of time.  
Logged

0
Offline Offline
God Member
*****
Karma: 1
Posts: 513
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Ok, maybe it makes more sense if I fill in the pieces a little more.  With the previous changes to wiring.c, it becomes possible to track both seconds and microseconds.  

Here is a sample sketch, though these functions might belong in a library (or also in wiring) eventually:

Code:
extern volatile unsigned long timer0_count;
extern volatile unsigned long timer0_seconds;

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

unsigned long mil, t0sec, t0us ;
void loop(){
 cli();
 t0us=microSeconds();
 mil=millis();
 t0sec=seconds();
 sei();
 Serial.print(t0sec);
 Serial.print(" ");
 Serial.print(mil);
 Serial.print(" ");
 Serial.print(t0us);
 Serial.println(" ");
 delay(500);
}

//keep track of seconds over a period of 136 years
unsigned long seconds(){
  unsigned long m, s;
  uint8_t oldSREG = SREG;
  cli();
  m = timer0_count;
  s = timer0_seconds;
  SREG = oldSREG;
  m=(m*( 8 * clockCyclesPerMicrosecond())/125000)+(s);                  
  return m;
}

//microseconds timestamp recycles every 128 seconds
unsigned long microSeconds(){
  unsigned long m, t;
  uint8_t oldSREG = SREG;
  cli();
  t = TCNT0;
  m = timer0_count;
  SREG = oldSREG;
  return ((m << 8) + t) * 4;
}

and here is some output:
0 0 88
0 507 507960
1 1021 1022020
1 1537 1537108
2 2052 2052184
2 2567 2567228
3 3082 3082308
3 3597 3597384
...
126 126051 126051396
126 126572 126572632
127 127094 127094840
127 127616 127616088
128 128137 137272
128 128655 655428
129 129173 1173572
129 129692 1692748
130 130212 2212924
130 130732 2732124

So you can see that at 128 seconds, the microseconds timestamp rolls over and represents the last three digits of the milliseconds column.  You can use microSeconds() to time events up to 128 seconds long (do the rollover trick to see if you need to subtract from 128 seconds), and you can do elapsed seconds based calculations like nothing.  You'll be long gone before that rolls over smiley

« Last Edit: November 09, 2008, 07:06:29 pm by dcb » Logged

0
Offline Offline
God Member
*****
Karma: 1
Posts: 513
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

And one more bit of core functionality to consider.  A user defined callback, which would allow for building an event scheduler (will post that later):

here is a possible wiring.c implementation:

Code:
...
void (*userDefinedTimer0Handler)(void) = NULL;

SIGNAL(SIG_OVERFLOW0)
{
    timer0_count++;
    while(timer0_count>=125000){
      timer0_count-=125000;
      timer0_seconds+= 8* clockCyclesPerMicrosecond();
    }
    if(userDefinedTimer0Handler)
      userDefinedTimer0Handler();
}
...

and a rough cut script that makes use of it
Code:
extern void  (*userDefinedTimer0Handler)(void);

volatile unsigned long  t0ovfX3;
void myTimer0Callback(){
  t0ovfX3 += 3;
}

void setup(){
  userDefinedTimer0Handler=myTimer0Callback;
  Serial.begin(9600);
}

void loop(){
 Serial.println(t0ovfX3);
 delay(500);
}
Logged

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

Quote
without interrupt support for the time model, you had to be sure to call it every so often or else it could miss a millis() turnover

The DateTime code will not miss a turnover unless the sketch waits over four years between each call to get the time. Can you think of any application where a sketch would wait longer than this between intervals to check the time?
« Last Edit: November 09, 2008, 09:23:19 pm by mem » Logged

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

Quote
And one more bit of core functionality to consider.  A user defined callback, which would allow for building an event scheduler

There is code to do date and time scheduling using callbacks posted here:
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1217881285

this code uses the DateTime library so does not need changes to wiring.c. As above, no events get lost as long as the interval between successive scheduled events occurs at least every  four years

FWIW there was code posted here http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1210889307/8#8 that updated DateTime by changing the interrupt handler in wiring.c (that code was for 0011) but I didn't think the four year interval between updates would be a problem, do you?
« Last Edit: November 09, 2008, 09:36:30 pm by mem » Logged

0
Offline Offline
God Member
*****
Karma: 1
Posts: 513
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

no, 4 years isn't a problem.  I do have use for a milliseconds resolution callback function (interrupt debouncing and ???) though, as well as getting a handle on microseconds.    

There is nothing wrong with leveraging wiring.c either as far as I know.  The core files are not in their final form, and subject to change based on community input.  And this isn't much more complicated than the current functionality.

I'm sure if you had a seconds function available that was good for 136 years you would have used that as a base for the datetime library, no?

There's a number of folks wanting microsecond timestamps as well, would like to see that supported in the core.  If timer0 can handle both micro timing and long term timing needs then why not?

It appears on the surface that the main difference in scheduling approaches is that with this version it is interrupt driven.  At about 1ms resolution you can schedule the re-enabling of an input pin, or the playing of the next note in a background pwm music generator, without having to poll millis().  And being interrupt driven (need wiring.c change for that on timer0) it could also serve as the basis for a larger scale scheduler (i.e. time to wake up, or go to the dentist).  

It looks like you must always call delay/wait in dtalarms to keep the scheduler active.  Which is a workable solution in many cases, but would be more ideally handled in the background in many cases as well.  

The real caveat with the interrupt approach is keeping the callback function small, i.e. just re-enable the pin in question or perform the pwm frequency change and re-schedule the next change.  But wiring.c is fair game to that end, IMHO.
Logged

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

I certainly agree that your approach is valid, and I would not want to discourage your developments. But I do have a different perspective on the relative merits of the alternatives so perhaps it could be useful for me to clarify.

I had considered modifying wiring.c (mellis had offered to make the change mentioned in the link above) that would have added a seconds counter to the interrupt handler. I decided not to do this because I though there was no practical advantage in doing so. The code as is will not roll over for 138 years from the time the seconds counter is zeroed.  And calling a timekeeping function at least once every four years does not seem like an impediment to any Arduino user.
My background in software engineering has taught me that every line of code added can bring significant baggage in future maintenance and I think solutions with the lighter touch are more appropriate to the Arduino environment.  YMMV

Anyway, I will watch your work on real time scheduling with interest.

Good luck!
« Last Edit: November 10, 2008, 12:01:35 am by mem » Logged

Austin, TX USA
Offline Offline
God Member
*****
Karma: 4
Posts: 997
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I'm still digesting all this, but I thought I'd point out that the code for the microSeconds seems to assume (and rely on) a 16MHz clock.  If I understand this correctly (and I may not), with a prescale value of 64, the value of TCNT0 increments every (1/16) * 64 = 4us, hence the "* 4" in the expression.  However, this will not be valid at other frequencies, will it?  Perhaps the "4" should be replaced with "(prescale/clockCyclesPerMicrosecond())"?



« Last Edit: November 10, 2008, 01:55:11 am by mikalhart » Logged

0
Offline Offline
God Member
*****
Karma: 1
Posts: 513
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Maybe, it looks like the same prescaler for 8 or 16mhz in the existing code.   Can't currently test 8mhz though (don't have an 8mhz *duino handy) otherwise I would have tested it.  Sicne we are dealing with microseconds though, it should probably be another #define based on F_CPU if it needs fixing.

http://svn.berlios.de/viewcvs/arduino/trunk/hardware/cores/arduino/wiring.c?view=markup
« Last Edit: November 10, 2008, 06:01:50 am by dcb » Logged

Forum Administrator
Cambridge, MA
Offline Offline
Faraday Member
*****
Karma: 11
Posts: 3538
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I'd love to have a micros() function that returns the number of microseconds since the sketch started.  It's also important that the core timing functions work reliably at 16 and 8 MHz, and accurately (though not necessarily precisely) at other speeds.  It could also be nice to have built-in second(), minute(), hour(), day(), month() etc. functions that return the current date and time.  (I imagine that they would assume the sketch started at, say, midnight on Jan 1, 2000, but that there would be a setTime() that would take the number of milliseconds since the epoch, for example.)  I'm not so worried about how many years it is between overflows of the millis() counter.

Any thoughts on the best way to achieve these goals?
Logged

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

Returning the elapsed microseconds can be done in a number of ways, here are three approaches that come to mind:

The first two optimize for applications that will call this function many thousands of times a second. Using hardware timer1 (or timer3) would be a good way of supporting this but would cause the loss of other important functionality so I would not like to see this in the core.  But perhaps it could be a useful library add-on for specialized applications that need precision micro timing.

Accumulating the count in an interrupt could  be a way to go but this adds processing overhead to every sketch whether the functionality is needed or not.

I favor an approach that adds little or no overhead for the vast majority of users that don't need an elapsed microsecond count. This could be done by calculating the count only when the application makes a call to get the value. The function would be something like:

Code:
#define TIMER0_TO_MICROSECONDS (insert code here to convert each timer0 count to microseconds);

unsigned long micros()
{
   return (millis() * 1000) + TIMER0_TO_MICROSECONDS;
}

As to date and time functions, I would be happy to adapt the playground DateTime library for use in the core. Certainly the change to start at Jan 1 2000 (or any specific date) would be simple to do.
« Last Edit: November 10, 2008, 06:45:28 am by mem » Logged

0
Offline Offline
God Member
*****
Karma: 1
Posts: 513
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

FYI, my proposal adds zero interrupt processing overhead to what is currently implemented until you bring in the scheduler library.  And you don't have to look far for people wanting better than millisecond timing.  It doesn't exist so maybe that is why it isn't more in demand.

I don't understand what functionality you are talking about losing mem?  I'm trying to add functionality.

But I do think additional microseconds and days/months/years (which I have never had a need for) functionality should be only brought in on demand.  So with timer0 keeping track of seconds + cycles and some pre-arranged hooks for the library,  the additional code can be done on demand.

So if I had my druthers, the top of wiring.c would look like below, and a library would get brought in for micros, elapsedMicros, seconds.  And the datetime library could be based on the seconds function to eliminate any possibility of a missed rollover and add its own userDefinedTimer0Handler for scheduling small tasks.  Though this level of scheduling may be better suited for debouncing, background music, ??? and not (hey time to send all the log data now).  It may be that mems use case is a better fit for a scheduler that requires polling, where I want to fire and forget the next note to play and the re-enabling of a button, etc.

Code:
...

#include "wiring_private.h"

volatile unsigned long timer0_count = 0;
volatile unsigned long timer0_seconds = 0;
void (*userDefinedTimer0Handler)(void) = NULL;

SIGNAL(SIG_OVERFLOW0)
{
    timer0_count++;
    while(timer0_count>=125000){
      timer0_count-=125000;
      timer0_seconds+= 8* clockCyclesPerMicrosecond();
    }
    if(userDefinedTimer0Handler)
      userDefinedTimer0Handler();

}

unsigned long millis(){
      unsigned long m, s;
      uint8_t oldSREG = SREG;
      cli();
      m = timer0_count;
      s = timer0_seconds;
      SREG = oldSREG;
      m=(m*( 8* clockCyclesPerMicrosecond())/125)+(s*1000);                          
        return m;
}
...

« Last Edit: November 10, 2008, 07:11:42 am by dcb » Logged

Forum Administrator
Cambridge, MA
Offline Offline
Faraday Member
*****
Karma: 11
Posts: 3538
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Anyone want to take a shot at code for calculating microseconds from timer0 overflow counts and TCNT0?  Again, it should work at any CPU speed, although it only need to be precise for 8 MHz and 16 MHz.
Logged

Pages: [1] 2 3 ... 8   Go Up
Jump to: