24 hours x 365, 55 day millis rollover

Hey guys

I am currently on a project requiring the arduino to be able to keep the time (hours and minutes) for about a year. As millis still stop working after about 55 days i was wondering if there was a way how to manually reset the millis, for example every 24 hours? (my programm will do the same thing every day and can just reset in the evening)

If thats not possible, is it possible to restart the arduino with a command? (loosing a couple of seconds per day wouldnt be a big problem)

Cheers

You could use the DateTime library here: Arduino Playground - DateTime

below is an example sketch that prints the minute and hour. The date rolls over in 2038 but the time will remain correct for as long as the system is powered

#include <DateTime.h>

void setup(){
  Serial.begin(19200);
  DateTime.sync(0);  // sets the time to midnight Jan 1 1970
                     // you can set a real time by using a Unix type time value
}

void  loop(){
  unsigned long  prevtime;

  if(DateTime.available()) { // update clock if time has been synced
    prevtime = DateTime.now();
    while( prevtime == DateTime.now() )  // wait for the second to rollover
        ;
    DateTime.available(); //refresh the Date and time properties
    digitalClockDisplay( );   // update digital clock
  }
  delay(100); 
}

void digitalClockDisplay(){
  // digital clock display of current date and time
  Serial.print(DateTime.Hour,DEC);
  Serial.print(":");
  Serial.println(DateTime.Minute,DEC);
}

The playground has some info on how to sync the time to an external source if you need to do that.

Note that the (new) rollover after 55 days is MUCH cleaner than the old 9-hour rollover. In particular, a blink-like program that checks millis() against previous_millis + interval will work fine with the new code, even after more than 55 days, because both calculations wrap at the same time. With the old code, millis() would wrap long before millis()+interval, which was what caused sketches to stop working (mostly.)

o.k thanks about , was thinking I may have problem with my coding.

if ( millis() >= knight_time )
{
knight_time = millis() + 40, knight() ; // play knight if ?? millis passed since the last go
}

If you just want to do something every 40 milliseconds, do this:

unsigned long last_event = 0;
void loop()
{
  if (millis() - last_event >= 40)
  {
    last_event = millis();
    ... do your thing here
  }
  ...
}

This will work correctly even when millis() overflows.

Mikal

thanks guys!

Mikal - are you sure that would keep working?

At rollover, wouldn't that equate something like:

if ( 20 - 4752000000 >= 40 ) {
...

Which would return false for the remainder of the operation of the program.

Generally speaking, just extending the rollover period does not change the way in which you deal with rollover, just how often you do... =)

Here's a quick and dirty way:

  // rollover! our cunt of running ms is less than
  // our previous count!
if( millis() < last_event ) 
  last_event = 0;

  // now you can do your check

if( millis() - last_event >= 40 ) {

  last_event = millis();
  // etc...
}

This would cause one period that may be shorter than 40ms every 55 days, if you want to make sure this one is correct, subtract the last_event value from the absolute maximum (before rollover) value and add that to the amount of time passed.

!c

Mikal - are you sure that would keep working?

Yup! :slight_smile:

This works because of the beauty of unsigned arithmetic. Try this little sketch:

void setup()
{
  unsigned long max_ul = 0xFFFFFFFFul; // The largest possible 32-bit unsigned long
  Serial.begin(9600);
  Serial.println(20 - max_ul);
}
void loop(){}

This prints 21. The expression "if (millis() - last_event >= 40)" returns true whenever 40ms have elapsed... forever, whether a rollover occurs or not.

Just about as "quick and dirty" as you can get. :slight_smile:

Mikal

Ahh, sweet. I hadn't considered that bit of magic-ness in there. I completely spaced on that point.

(And yeah, I realize it's not "magic" grin)

!c

I think it IS magic. :slight_smile:

Maybe I'm not understanding something, but Mikal's rollover sketch doesn't work if max_ul is less than the maximum value. How would I rollover my unsigned long counters when millis() rollsover?

void setup()
{
  unsigned long max_ul = 0xFFFFFFFFul; // The largest possible 32-bit unsigned long
  Serial.begin(9600);
  Serial.println(20 - max_ul);
}
void loop(){}

Currently I'm using something like the following to detect when it's time to run my update code

void setup()
{
  unsigned long updatecounter = millis();
  unsigned long updateperiod = 86400000;  //about one day
}
void loop()
{
  if (millis() - updatecounter > updateperiod){
    update();
    updatecounter = millis();
  }
  //do other stuff here
}
void update()
{
  //updating stuff here
}

Is it possible to manually reset the millis() counter?

thanks,
Matt

Matt, my code in #7 was designed merely to demonstrate "rollover subtraction" of unsigned values. It does work for any value of max_ul (greater than 20).

Your code should (almost) work fine for doing something once a day, although you have a scoping problem with your update* variables. Try this:

unsigned long updatecounter = 0;
unsigned long updateperiod = 86400000;  //about one day

void setup()
{
}

void loop()
{
  if (millis() - updatecounter > updateperiod){
    update();
    updatecounter = millis();
  }
  //do other stuff here
}
void update()
{
  //updating stuff here
}

Mikal

My bad, your code is working properly. If I lower max_ul by 1 (0xFFFFFFFEul) the printed value goes up by 1 and so on. So the only limitation then is if my update period is greater than 50 days it will never trigger?

Can you please explain or direct me to an explanation of the "scoping problem" you mentioned?

much appreciative,
Matt

Quite right. If your period was greater than 50 days, you couldn't directly use this technique because you can't fit that number of milliseconds into a 32-bit unsigned long. But you could work around this difficulty by, say, triggering once a day, and incrementing a "day counter" each time. Then you could perform the update only when the counter is equal to 60 days or 1000 days or whatever.

By "scoping problem" I mean that your declarations of updatecounter and updateperiod were inside setup() ("inside the scope" of the function). That means they were not visible from (were "outside the scope of") loop(). The compiler would have given you an "undefined identifier" error, but this is easily fixed by simply making them global.

Mikal

I should've seen that right aways.... A little too much 'Arduino' at one time. :-[