Simulating a timer wheel

Note to moderator: This really isn't a question, but a helpful methodology I came across. This might be in the category of "everybody knows that", but it sure confused me for a while, so I thought I'd share. Here is the text of my proposed tip...

My project required the simulation of a mechanical timer wheel, which could reliably operate endlessly with no rollover or overflow issues. Typically a timer wheel makes a full revolution in a day, week, or another common time period, and has mechanical "pins" to trip electrical switches to an "on" or "off" state at selected times. Sometimes multiple "pins" are provided. So first the easy part... using the system millis() timer, a "wheel" representing any amount of time short of 49 days can be simulated. First, some helpful defines...

#define SECOND 1000
#define MINUTE (60*SECOND)
#define HOUR   (60*MINUTE)
#define DAY    (24*HOUR)

Next, I create an ongoing time reference, which counts up to 1 day of milliseconds, then resets without loss. The variables are global, so they will stay valid through the life of the program.

static uint32_t dayTime;
static uint32_t currentRunTime = 0;
static uin32_t timePassed;

loop()
{
uint32_t  tmp = millis();
timePassed =  tmp - currentRunTime;
currentRunTime = tmp;

// now to update our 24 hour dayTime "timing wheel"...

dayTime += timePassed;
if (dayTime >= DAY) dayTime -= DAY;  // when dayTime meets or exceeds a day,subtract a day.

// other code ...

}

So "dayTime" is now our basic "timer wheel" reference. It will count 24 hours of milliseconds, forever. If the "timePassed" happenes to push "dayTime" slightly beyond a full day, 1 day is subtracted without losing left over milliseconds.

Now lets create a pair of simulated on/off "pins" for the timer wheel. I'll call them "startTime" and "stopTime". In this case I'll make the "start" 7:00AM, and the "stop" 6:30PM. Keep in mind that despite the convenience for humans, we need to deal with daytime hours as 0-23, so...

uint32_t startTime =  (HOUR * 7);        // 7AM
uint32_t stopTime =  (HOUR * 18  +  MINUTE * 30);  // 18:30, or 6:30PM

Now here's the part that had me stumped for a while, and the reason for this post. I must have tried over a dozen combinations of comparisons just to decide whether my simulated circuit (in my case a motor) should be on or off at given time of the day (dayTime). If I was starting with the motor OFF, then it would seem that when dayTime > startTime we should turn it on, and turn it off later when dayTime > stopTime. But that won't work! When dayTime rolls over to '0' at midnight, then dayTime < stopTime. Should we turn it on? No because at midnight its also true that dayTime < startTime! Clearly a method was needed that took both times into account.

I thought it was making a big deal out of a simple problem, but poking around online I'd found what some fairly complex solutions. One attempted to add a "day Count" variable that would increment whenever midnight was passed, and take this into account when testing the time. Another method involved creating an array of time slots, from 0 time through 24 hours, and a looping process would simulate a rotation of the wheel. This seemed insane to me, because the array would have to contain 1440 entries for 1 minute resolution (60 minutes x 24 hours).

So after pulling out lots of hair in frustration, I came up with this method. Its based on the way a logical exclusive or (or exclusive nor) would work, if C/C++ had such an operator. Putting it simply...

if ((dayTime  < startTime ) != (dayTime >= stopTime ))  // we are in a STOP condition
if ((dayTime  < startTime ) == (dayTime >= stopTime ))  // we are in a START or RUN condition

Each line tests two possible conditions. When both conditions yield the same result (either 'true' or 'false') we are within "start" or "run" time. If the two conditions are different (one 'true', one 'false), then we are in a 'stop' or 'off' state. Eureka??

Well not quite! A reader on this forum (alto777) showed me that the results were only as expected when the startTime was earlier in the day than StopTime. If startTime is later than stopTime, the results are reversed (for example, to turn something on at night and off the next day). But all was not lost if we simply take both situations into account, and reverse the output as needed.

Here is a test you can do to prove to yourself how reliably this works with actual hour counts of milliseconds, without waiting a full day for results. First, I'm defining some "timer wheel" functions, to make the sketch code easier to write and read.

uint8_t isStart( uint32_t tNow, uint32_t tStart, uint32_t tStop) // true for started state
{
  uint8_t x= ((tNow < tStart) == (tNow >= tStop)); 
  return  (tStart < tStop) ? x : !x;
}

uint8_t isStop( uint32_t tNow, uint32_t tStart, uint32_t tStop) // true for stopped state
{
  uint8_t x= ((tNow < tStart) != (tNow >= tStop));
  return  (tStart < tStop) ? x : !x;
}

// define an hour worth of milliseconds
#define HOUR 3600000

setup()
{
 Serial.begin(9600);

 uint32_t startTime =  (HOUR * 7);        // 7AM
 uint32_t stopTime =  (HOUR * 18);  // 6PM

 for (dayTime = 0; dayTime < (HOUR * 24); dayTime += HOUR)
   {
    Serial.println(String("") + "Hour = " + (dayTime / HOUR) + "  Start = " +
           isStart(dayTime, startTime ,stopTime ) + "  Stop = " +
             isStop(dayTime, startTime ,stopTime )  );
   }
}

loop()  { }

This sketch will print a table like the below. The "1" or "0" mean "Yes" or "No" for the start or stop states. Try plugging different numbers of hours into the startTime and stopTime to satisfy yourself it always works. It will work reliably as long as you've created the 24 hour "timing wheel" "dayTime" explained earlier.

Hour = 0  Start = 0  Stop = 1
Hour = 1  Start = 0  Stop = 1
Hour = 2  Start = 0  Stop = 1
Hour = 3  Start = 0  Stop = 1
Hour = 4  Start = 0  Stop = 1
Hour = 5  Start = 0  Stop = 1
Hour = 6  Start = 0  Stop = 1
Hour = 7  Start = 1  Stop = 0
Hour = 8  Start = 1  Stop = 0
Hour = 9  Start = 1  Stop = 0
Hour = 10  Start = 1  Stop = 0
Hour = 11  Start = 1  Stop = 0
Hour = 12  Start = 1  Stop = 0
Hour = 13  Start = 1  Stop = 0
Hour = 14  Start = 1  Stop = 0
Hour = 15  Start = 1  Stop = 0
Hour = 16  Start = 1  Stop = 0
Hour = 17  Start = 1  Stop = 0
Hour = 18  Start = 0  Stop = 1
Hour = 19  Start = 0  Stop = 1
Hour = 20  Start = 0  Stop = 1
Hour = 21  Start = 0  Stop = 1
Hour = 22  Start = 0  Stop = 1
Hour = 23  Start = 0  Stop = 1

At any point within your program, you can use this tool to turn timed devices or program states on or off accordingly. Since your program is probably running quickly in a fast loop(), you'll want to create state variables to avoid repeating the turn on/off of a process. For example, create a variable to represent the state of a relay controlling a motor...

static uint8_t motorOn = 0;   

if  (  isStop(dayTime, startTime ,stopTime ))    // within stop time
  {
   if (motorOn )  turnOffMotor();         // if device is on, turn it off
   motorOn  = 0;
  }

if  (  isStart(dayTime, startTime ,stopTime ))   // within start / run time,
  {
  if ( !motorOn ) turnOnMotor();         //   start your device
  motorOn  = 1;
  }

Also, you can run this simulated timer wheel with multiple pairs of "pins" for start and stop times, and you can arrange for longer simulation periods than a single day! Maybe you want things turned on and off differently, depending on the day of the week. Your start and stop variables should include the day of the week in their calculated values. The below assumes Sunday is day '0'

define WEEK (DAYS * 7)

static uint32_t weekTime;  instead of "dayTime,
static uint32_t currentRunTime = 0;
static uint32_t timePassed;

uint32_t startTimeMonday =  (DAY * 1 + HOUR * 7);        // 7AM Monday
uint32_t stopTimeMonday =  (DAY * 1 + HOUR * 18  +  MINUTE * 30);  6:30PM Monday

uint32_t startTimeTuesday =  (DAY * 2 + HOUR * 6);      // 6 AM Tuesday
uint32_t stopTimeTuesday =  (DAY * 2 + HOUR * 20 );     // 8:00PM

loop()
{
uint32_t  tmp = myMillis();    
timePassed =  tmp - currentRunTime;
currentRunTime = tmp;

// update our 7 day "timing wheel"...

weekTime += timePassed;
if (weekTime> WEEK) weekTime -= WEEK;

// ...
}
[/code]

These could then be used as arguments in the functions we created.

isStop(weekTime , startTimeMonday, stopTimeMonday)
isStart(weekTime .lastTime, sunStates.startTime, sunStates.stopTime)[/code]

So I hope you find this useful. Certainly, if what you need is something that mimics the simple operation of a mechanical "wheel" timer, this method might be just what the doctor ordered.

So "dayTime" is now our basic "timer wheel" reference. It will count 24 hours worth of milliseconds and properly reset whenever midnight comes around

Without a real time clock, how will the Arduino know that it is midnight? It really does not appear that that snippet resets at midnight, unless you started the Arduino at precisely midnight. It resets dayTime after accumulating 86,400,000 milliseconds.

resets dayTime after accumulating 86,400,000 milliseconds.

  • plus/minus the inherent Arduino crystal drift.

OP may be better off using $20 ethernet shield for NTP, or a $40 GSM shield to harvest accurate ‘absolute’ time. At least a $2 RTC chip.

Here is one approach I've used to keep track of time:
https://forum.arduino.cc/index.php?topic=493264.msg3365520#msg3365520
It uses separate variables for days, hours, minutes, and seconds.

Here is another approach I've used:
https://forum.arduino.cc/index.php?topic=408565.0
It's a bit more complicated to set up, but I find it easier to work with.
It stores the time in a variant of binary-coded decimal.

PaulS:
Without a real time clock, how will the Arduino know that it is midnight? It really does not appear that that snippet resets at midnight, unless you started the Arduino at precisely midnight. It resets dayTime after accumulating 86,400,000 milliseconds.

It doesn't exactly "reset" it in the sense of zeroing, as no residual time (fraction of a day) is lost during the reset. It just creates a timer that repeats exactly on a daily (or other period) basis, which is often easier to work with than a 4 byte millisecond counter, with a 49 + fractional day rollover. It was intended to solve a particular problem I had with daily (or other period recurrent) determination, of exactly when to control events or devices. It was never intended to eliminate the need for setting the time. I've solutions for that too, but this just covered one thing.

lastchancename:

  • plus/minus the inherent Arduino crystal drift.

OP may be better off using $20 ethernet shield for NTP, or a $40 GSM shield to harvest accurate ‘absolute’ time. At least a $2 RTC chip.

As it turns out I have a separate solution to keeping the reference project in sync with actual day time, once its set, which actually cost me exactly 40¢ in added parts. That, however is a separate subject. Here I was just offering a tip for anyone having the particular issue I had (where I mentioned my "real reason" for the post.).

odometer:
Here is one approach I've used to keep track of time:
System Uptime: millis to DD:HH:MM code optimization? (esp8266) - #13 by odometer - Programming Questions - Arduino Forum
It uses separate variables for days, hours, minutes, and seconds.

Here is another approach I've used:
Two-button clock - Exhibition / Gallery - Arduino Forum
It's a bit more complicated to set up, but I find it easier to work with.
It stores the time in a variant of binary-coded decimal.

I may have done something similar. Having needed a way to display both current time and various event times on my LCD display (mainly for user setting, but sometimes also for eye candy), I needed to be able to convert my day's milliseconds into those various elements (hours, etc), in a structure. Mine has a millisecond member element too, and a day counter as well. Maybe I'll post it at some point. One weird thing I found when creating that functionality, is that the unlikely method of using subtraction loops to get your hours, minutes, etc., was faster than the usual approaches I've seen, which use quite a bit of division and modulus division. I can only guess that was because the ATmega328 lacks a hardware division instruction, so that division must be done with library code. Plus, when you do a subtraction loop, you already have the remainder available for calculating the next element, using a similar loop.

simulation of a mechanical timer wheel

I'm thinking that this "timer wheel" concept may be a better way to explain "Blink without delay" and "combine multiple programs" than what we have been doing... Nice idea, and I'll have to look more carefully.

A nit:

#define SECOND 1000

#define MINUTE 60000
#define HOUR 3600000
#define DAY 86400000

Would be more understandable written as:

#define SECOND 1000
#define MINUTE (60*SECOND)
#define HOUR   (60*MINUTE)
#define DAY    (24*HOUR)

All those multiplications happen at compile time, and don't affect the performance of the sketch binary.

westfw:
I'm thinking that this "timer wheel" concept may be a better way to explain "Blink without delay" and "combine multiple programs" than what we have been doing... Nice idea, and I'll have to look more carefully.

A nit:

Would be more understandable written as:

#define SECOND 1000

#define MINUTE (60SECOND)
#define HOUR  (60
MINUTE)
#define DAY    (24*HOUR)




All those multiplications happen at compile time, and don't affect the performance of the sketch binary.

Thanks. Yeah... I am aware of that handy compile time nature of constants and defines (note my use within some of the timeStart/timeStart example code). I'll try to edit the post. Its kind of close to the edge of the stupid 9000 character limit, so I have to be careful about edits. :frowning:

Probably useful enough to put up someplace like github...

PeterPan321:
As it turns out I have a separate solution to keeping the reference project in sync with actual day time, once its set, which actually cost me exactly 40¢ in added parts.

What parts would those be? It would be useful to know for my next clock project.

PeterPan321:
I may have done something similar. Having needed a way to display both current time and various event times on my LCD display (mainly for user setting, but sometimes also for eye candy), I needed to be able to convert my day's milliseconds into those various elements (hours, etc), in a structure. Mine has a millisecond member element too, and a day counter as well. Maybe I'll post it at some point. One weird thing I found when creating that functionality, is that the unlikely method of using subtraction loops to get your hours, minutes, etc., was faster than the usual approaches I've seen, which use quite a bit of division and modulus division. I can only guess that was because the ATmega328 lacks a hardware division instruction, so that division must be done with library code. Plus, when you do a subtraction loop, you already have the remainder available for calculating the next element, using a similar loop.

Maybe something like this?

//            01234567890123456 so 17 chars including null terminator
char buf[] = "XXd XX:XX:XX.XXX";



//            01234567890123456
strcpy(buf,  "00d 00:00:00.000");

uint32_t r = ms_to_convert;

while (r >= 864000000UL) { r -= 864000000UL;  buf[0]++; } //  10 days
while (r >=  86400000UL) { r -=  86400000UL;  buf[1]++; } //   1 day

while (r >=  36000000UL) { r -=  36000000UL;  buf[4]++; } //  10 hours
while (r >=   3600000UL) { r -=   3600000UL;  buf[5]++; } //   1 hour

while (r >=    600000UL) { r -=    600000UL;  buf[7]++; } //  10 minutes
while (r >=     60000UL) { r -=     60000UL;  buf[8]++; } //   1 minute

while (r >=     10000UL) { r -=     10000UL; buf[10]++; } //  10 seconds
while (r >=      1000UL) { r -=      1000UL; buf[11]++; } //   1 second

while (r >=       100UL) { r -=       100UL; buf[13]++; } // 100 milliseconds
while (r >=        10UL) { r -=        10UL; buf[14]++; } //  10 milliseconds

buf[15] += r; // finish up milliseconds

westfw:

#define SECOND 1000

#define MINUTE (60SECOND)
#define HOUR  (60
MINUTE)
#define DAY    (24*HOUR)




All those multiplications happen at compile time, and don't affect the performance of the sketch binary.

You forgot to force long multiplication.

You forgot to force long multiplication.

I did :frowning:

#define SECOND (1000UL)

odometer:
What parts would those be? It would be useful to know for my next clock project.

I'm basically running off line AC line frequency. The extra parts needed to make 120hZ pulses available from my power supply are an extra diode for isolation, and a pair of resistors to divide the voltage down to something appropriate for a digital input. I have ISR code written to keep a counter updated, based on the premise that 3 pulses from a 120 hz source equal 25 mS, which is what I add to my counter ever 3 pulses. Then I have a separate version of millis (myMillis() Which properly retrieves the count, to a resolution of 25 mS. As a bonus, I can quickly tell when power is lost, but using the regular system millis() call to let me know when my ISR hasn't fired for about 1/4 second, at which point I can move to save critical data (including the current time count) to EEprom. That works because my filter cap is big enough to keep the project running for well over a second. If this interest you, I'll post it along with schematic and code in a separate topic. However, having discussed this scheme a few times amid much backlash, I don't want to re-hash the arguments against using power grid time. My device needs to run for months at a time, and while grid power does drift, it is always corrected. I don't need a RTC, as I'm mainly just wanting to guard against short power drop outs that usually last less than a minute. For longer power outages, its already a given that most digital clocks within my environment will need to be reset.

odometer:
Maybe something like this?

//            01234567890123456 so 17 chars including null terminator

char buf[] = "XXd XX:XX:XX.XXX";

//            01234567890123456
strcpy(buf,  "00d 00:00:00.000");

uint32_t r = ms_to_convert;

while (r >= 864000000UL) { r -= 864000000UL;  buf[0]++; } //  10 days
while (r >=  86400000UL) { r -=  86400000UL;  buf[1]++; } //  1 day

while (r >=  36000000UL) { r -=  36000000UL;  buf[4]++; } //  10 hours
while (r >=  3600000UL) { r -=  3600000UL;  buf[5]++; } //  1 hour

while (r >=    600000UL) { r -=    600000UL;  buf[7]++; } //  10 minutes
while (r >=    60000UL) { r -=    60000UL;  buf[8]++; } //  1 minute

while (r >=    10000UL) { r -=    10000UL; buf[10]++; } //  10 seconds
while (r >=      1000UL) { r -=      1000UL; buf[11]++; } //  1 second

while (r >=      100UL) { r -=      100UL; buf[13]++; } // 100 milliseconds
while (r >=        10UL) { r -=        10UL; buf[14]++; } //  10 milliseconds

buf[15] += r; // finish up milliseconds

Similar idea, yes. Maybe not the best place for me to post it all though. Another thread maybe?

westfw:
I did :frowning:

#define SECOND (1000UL)

Correct... true enough. Fortunately (or maybe not) I've not seen a situation where the compiler complained or got it wrong. Perhaps the smartness of today's compilers are making me lazy too. :frowning:

westfw:
I did :frowning:

#define SECOND (1000UL)

PeterPan321:
Correct... true enough. Fortunately (or maybe not) I've not seen a situation where the compiler complained or got it wrong. Perhaps the smartness of today's compilers are making me lazy too. :frowning:

@PeterPan321:
What compiler were you using? I ask because, from reading these forums, it seems that a common cause of problems is multiplying ints (variables and/or literals) and expecting a long result.

As for myself, I try to make it a habit to at least do rough mental arithmetic to make sure everything will fit in the desired datatype.

(regarding conversion of milliseconds to human-readable time)

PeterPan321:
Similar idea, yes. Maybe not the best place for me to post it all though. Another thread maybe?

Please start another thread, where you describe this project in more detail.

I would also like to know: what is your procedure for setting the time, such as after a power outage? I find that with the approach I used for my two-button clock, I can reset the time (and date!) surprisingly quickly.

odometer:
(regarding conversion of milliseconds to human-readable time)

Please start another thread, where you describe this project in more detail.

I would also like to know: what is your procedure for setting the time, such as after a power outage? I find that with the approach I used for my two-button clock, I can reset the time (and date!) surprisingly quickly.

That thread will have to wait a little. No surprise I'm spending way too much time on that bigger project already, and am still working on solving some unrelated problems (finding a good temperature sensor, an learing to use them for one).

But in summary, I created a "digitalClock()" call that works with time structure, having all the typical elements. One of the possible commands it accepts is to set its current time based on a passed variable containing the number of milliseconds in the current day. It also will return a string containing the time in 12 or 24 hour format, with whatever elemenets I require (sometimes just hrs and minutes with an AM/PM tag, other times with seconds, etc. Since all the times in my project are based on a day's milliseconds, setting the clock is doon by calling that function to display the time on an LCD, while allowing buttons (I have a 4 button membrane ) to cause it to raise the time or lower it, by taking its current mS, and adding seconds. I also have my own button "debounce" function, which among other things, allows for the current button press to repeat more rapidly if held, so I can set the time quickly. Also and I mentioned, I can detect momentary loss of power and quickly save my current time to EEprom. So when the system comes back, I'll only be behind by the duration of the power loss, which is better than reverting to 12:00 midnight the way most digital clocks do.

But again... a thread describing the whole project will require a span of time set aside. :frowning: