Simple software timer alternative to millis()

While I am still busy with writing code and project generating scripts, I want to share one of these scripts here.

Beginners are taught to use the millis() function to handle software timed events. And it is not the most efficient way. You need to declare 4 bytes of memory for every timer. You need to perform a function call and fetch a 4 byte arguement, subtract these 4 bytes from 4 other bytes and compare the result to another 4 bytes And lastly you need to copy the 4 bytes of the current value in the previous value. And for longer running arduino's, you are a few weeks removed from a possible buggy condition when millis overflows.

I have developed and worked with more fancier SW timer code with which I could simply do:

start(&foo, someInterval);            // repetitive
trigger(&bar, someOtherInterval); // one shot timer
// and
stop(foo);

usage was easy

But this also came with too much overhead and inefficiency. Therefor I wanted something more efficient and more simplistic.

The usage of a single SW timer in my code can look as follows:

#include "timers.h"

#define someInterval 50

void loop() {
    if(someTimer == 0) { // or "if(!someTimer) {"
        someTimer = someInterval;

        // .. code to run ever 50 ms/ds/hs/s
    }

If you want to use one SW timer, you can do so as shown in the example. You wait for a timer to reach 0, than set the timer at a desired interval and perform your code. I use this method alot in combination with state machines. It has proven to be usefull for simple time-outs of states, in-state delays and inter state delays.

My new software timers have the following properties:

  • All timers are decremented in interrupt untill the timers reach 0

  • All timers are of an 8-bit type

  • both timer .ccp and .h files are generated for you by a python script

  • Timers can have different interval bases of 1ms, 10ms, 100ms or 1000ms, which can let you run tasks up to every 255 seconds. This ofcourse can easily be expanded to more or other intervals

  • If you want to add, modify or remove a timer, you modify the file timers.tab and run the script

  • There is no need for some 'update' function in the main loop

  • There won't be any overflows

The works.

To make/modify/remove timers, you must make and edit "timers.tab" (just a plain txt file) and run the attached python script.

task1T 100
task2T 1000
task3T 10
task4T 1
helloT 10
worldT 100

The capital T is just a syntax thing of mine, for me this indicates that the used variable is a SW timer. Feel free to use whatever name you want.

The format in timers.tab must be maintained. "name", "1 tab", "interval base (1, 10, 100 or 100)" and a \n character

The purpose of this script is to reduce the workload for you. If you would manually add a SW timer, it would have to be done in 3 separate places. Now it is just one place.

The generated cpp and h files with some example timers look as follows:

#include <Arduino.h>
#include "timers.h"

extern void initTimers() {
 TCCR2B = 0;// same for TCCR2B
 TCNT2  = 0;//initialize counter value to 0
 // set compare match register for 8khz increments
 OCR2A = 249;// = (16*10^6) / (1000*64) - 1 (must be <256)
 // turn on CTC mode
 TCCR2A |= (1 << WGM21);
 // Set CS21and and CS20 bit for 64 prescaler
 TCCR2B |= (1 << CS20);
 TCCR2B |= (1 << CS21); 
 TCCR2B &= ~(1 << CS22); 
 // enable timer compare interrupt
 TIMSK2 |= (1 << OCIE2A); }

volatile unsigned char task1T;
volatile unsigned char task2T;
volatile unsigned char task3T;
volatile unsigned char task4T;
volatile unsigned char helloT;
volatile unsigned char worldT;

// Don't complain about the indentations. This code is generated for you and you shouldn't be looking at this part.
ISR(TIMER2_OVF_vect) {
static unsigned char _1ms, _10ms, _100ms;

// 1ms timers
_1ms += 1;

 if(task4T) task4T--;



// 10ms timers
if(!(_1ms % 10)) { _1ms = 0; _10ms += 1;

 if(task3T) task3T--;
 if(helloT) helloT--;



// 100ms timers
if(!(_10ms % 10)) { _10ms = 0; _100ms += 1;

 if(task1T) task1T--;
 if(worldT) worldT--;



//1000ms timers
if(!(_100ms % 10)) { _100ms = 0;

 if(task2T) task2T--;



}
}
}
}

You can see that the indentation is not standard. For this particular use-case I found this to be more logical. If you do not agree, I really do not want to know :smiley: Besides, you don't have to modify these files yourself.

And the .h

extern void initTimers();

extern volatile unsigned char task1T;
extern volatile unsigned char task2T;
extern volatile unsigned char task3T;
extern volatile unsigned char task4T;
extern volatile unsigned char helloT;
extern volatile unsigned char worldT;

Perhaps I do slightly more in an ISR than most programmers do prefer, but still I don't do sooo much that other parts of my programs are affected in a negative way. And I don't have a roundRobin time consuming update function. Only the timers you are currently working with, are polled.

I deliberately did not use any for loops in the ISR to make the ISR as fast as possible. I came to believe that this SW comes with minimal overhead and it is easy to use (afteral it differs little with the millis() method)

Let me know what you think about this method.

Bas.

P.S. forum doesn't allow me to upload a .py so I made it a .txt instead.
Also, pythonese is not my native programming language and I have not much experience in python so I am sorry if you find the script is poorly written.

updateTimers.txt (2.49 KB)

Wow !

Did you go out of your way to make things complicated ?

Despite what you say, non blocking timing using millis() does not cause a problem when millis() overflows to zero unless you try to use a timing period of over 49 days in which case an RTC would be more appropriate anyway

Writing timing code using millis() does take memory and you do have to make comparisons in the code but it is all very transparent and sensible variable names make it easy to see and understand what is going on and the variables can be printed for debugging if required

What you do not have to do is to add a new tab to your project and run a Python script, which does not seem to be attached to your topic

Whilst the method you are espousing may suit you I could not recommend it to a beginner because they would not understand it, nor to a more experienced user because they don't need it but it is, of course, good to explore alternatives to the accepted ways of doing things.

but it is all very transparent and sensible variable names make it easy to see and understand

No arguement there, I hardly use abbreviations at all. I always use clear names for everything. For the same purpose I added the 'T'.

I also agree that using millis() is not that bad, I am just pointing out it is not that fast and consumes memory which is scarce in an atmega328P. For beginners it may or may not be also be interesting to see what an ISR can do for you.

nor to a more experienced user because they don't need it but it is,

Why wouldn't an 'experienced' programmer not be needing this? I can tell you from my own experience how painfully effective this is in combination with state machines. That, it's performance and is simplicity is what makes it even useful for 'experienced' programmers.

Like I said before, we used dynamic SW timers in combination with function pointers before, but we disliked it's slower performance. It was nice that we could just do start(&foo, 500); and function foo() would be called every 500ms. But it wasn't worth the overhead.

I could not recommend it to a beginner because they would not understand it

I beg to differ about this one because:

Essentially I do the same thing. Only millis() increments and I decrement.

if(millis() - prevMillis >= interval) {
    prevMillis = millis();

    // ... code
}

// opposed to:

if(timerT == 0) {
   timerT  = interval;

   // ... code
}

It is really not that different, is it?

What you do not have to do is to add a new tab to your project and run a Python script, which does not seem to be attached to your topic

I do not precisely understand what you mean? I did explain that it is needed to create the .tab file as well as that it is needed to run the script.

This also demonstrates the basic usage of splitting code in separate source files. I personally think that these .ccp and .h files should be located in a sub-folder alongside other files which don't need any alteration. My IO.cpp and IO.h also lie in this folder alongside debounce code. But this is an other debate.

it is, of course, good to explore alternatives to the accepted ways of doing things

This is true. 'the accepted ways of doing things' is imo followed too litteral by most programmers. All the 'default phrases' I have heared: "never use macros", "using macros makes code harder to read because you use non-standard code", "never use global variables", "using const int is better than #define" and my favorite: "you always must use a programming language like how the developers meant it to be used." :sob:

The 'generally accepted' indentation styles also suck. I came across this code today about SW pwm, it is well written according to the 'rules'....

  if (myPWMpins[index].pinState == ON) {
    if (myPWMpins[index].pwmTickCount >= myPWMpins[index].pwmValue) {
      myPWMpins[index].pinState = OFF;
    }
  } else {
    // if it isn't on, it is off
    if (myPWMpins[index].pwmTickCount >= pwmMax) {
      myPWMpins[index].pinState = ON;
      myPWMpins[index].pwmTickCount = 0;
    }
  }

If you look how the if-elses are nested and how the {} are used.. afwul..l truly. I could not see in an eyeblink whether the else belonged to the first or the second if, because some people once voted that it was totally okay to put an 'else' behind a '}' for some reason. And not to mention the jokers who don't use {} for a single line...

if (myPWMpins[index].pwmTickCount >= myPWMpins[index].pwmValue)
    myPWMpins[index].pinState = OFF;

off-topic.

This timer SW is just a small part of my entire project-assembly work. With my current hobby projects I still get bothered by the fast increasing size of .ino file. Therefor all my .ino files are also generated and incredibly short. My main loops() only perform a call to 1 or 2 state machines and the roundRobinTasks.

The generated state machines (using a state diagram in yEd) only need some filling in to do, and the roundRobinTasks are partially filled in already, depening on which modules you included with the project. The timers.tab is also already filled in partially. Every state machine get's it's own timer assigned and some modules also get a timer assigned.

Within the states of the state machine I have all IO available to me out of the box complete with debouncing code, built in.

The generated project is compilable, it is simple and fast, it is modulair and the best part: it does not contain bugs because the computer made it for us. It allows for an incredible fast project assembly and the structure helps preventing bugs. State machines have assigned timers which you can use and buttons have built-in debouncing, what more does one want? I used about 5 very pritty macros to make the code readable. And I am still in doubt to create even more macros :)D

I am afraid that we will have to agree to differ over how useful your technique is, and it may well have its place for some projects, but one thing is for sure, I will not be recommending it to beginners

I have one project with more than 30 different millis() timers running and being restarted/reset at different times all over the place.

To be honest, I started reading this thread and asked the same question..
“Why is it so complicated?”

The easiest timers are those that appear in code when you need them.
To centrally ‘manage’ a collection of timers seems like unnecessary effort to an experienced developer.

lastchancename:
The easiest timers are those that appear in code when you need them.

It really differs little with millis() when it comes to the usage. Like I demonstrated before:

if(millis() - prevMillis >= interval) {
    prevMillis = millis();

    // ... code
}

// opposed to:

if(timerT == 0) {
   timerT  = interval;

   // ... code
}

The usage and place of usage is practically the same.

The primary difference is the location of where the timer variables are declared + mine are global. And the execution time is shorter.

You have a list of 30 unsigned long type variables somewhere above your setup or as static local variables spread all over the program.

I have a list of 30 unsigned char variables in a different file in a single place.

You use atleast 120 bytes storage
I use 30 + 3 = 33 bytes for storage.

What you call "unnecessary effort".
I call "an extra tool".

You let your arduino perform a bucketload of instructions.
I let mine do less than 1/3rd of the effort.

And you type much more characters than I do :smiley:

lastchancename:
To centrally ‘manage’ a collection of timers seems like unnecessary effort to an experienced developer.

This is like perfectly normal to do. I house all my IO in separate files as well. My round robin tasks have their own files, every state machine has it's own files. @work we have files for our databases which contains numerous structs and arrays. All files have their own static variables and some have extern variables as well.

Like you I use something when I need it. Do the round robin tasks need timers? I #include "timers.h", create a timer or 2 in the .tab file and I double click the script (or enter a terminal command).

Bas

What brought me up short when I read your original post was

To make/modify/remove timers, you must make and edit "timers.tab" (just a plain txt file) and run the attached python script.

That reminds me of a recipe for hare pie that starts "first catch your hare"

You let your arduino perform a bucketload of instructions.
I let mine do less than 1/3rd of the effort.

I suspect you are 'simplifying' something that doesn't to be simplified.
The compiled code will be very consistent, yet -to me- easier to understand.

If we had a more substantial chip architecture, my initial choice would be to use the Ticker class or similar. Almost zero work for the programmer.

UKHeliBob:
What brought me up short when I read your original post was
That reminds me of a recipe for hare pie that starts "first catch your hare"

"
many boards later, the Calculus professor announced, "so, obviously, B=x^2"

A befuddled student raised his hand and blurted out, "Professor, that's not obvious at all!".

Enraged, the professor filled three more boards and triumphantly announced, "I told you it was obvious!"

Although it uses the "memory inefficient" way of doing software timers, this library is MUCH more intuitive and easy to use. It's also installable through the Arduino IDE (search "FireTimer").

Example Sketch:

#include "FireTimer.h"

FireTimer msTimer;
FireTimer usTimer;

void setup()
{
  Serial.begin(115200);

  msTimer.begin(1000);
  usTimer.begin(1000000, true);
}

void loop()
{
  if(msTimer.fire())
    Serial.println("ms");

  if(usTimer.fire())
    Serial.println("us");
}

Power_Broker:
... is MUCH more intuitive and easy to use....

Though I agree the syntax in usage is better, it is slightly better and not MUCH, that is a tad exaggerated. Especially for a non-repetive action the difference is slim.

if(timerT == 0) { // instead of ! I used == 0 for a change
    // code
..
if(msTimer.fire()) {
    // code

I must say I like this library but it can be better. At first I thought that this 'true'

usTimer.begin(1000000, true);

Was ment to make it a repetitive action instead of a one shot event. But is is ment that is used micros instead of millis(). (#define MICRO_SECONDS true // perhaps nice idea?)

It is nice to use a function to 'automatically' reload last set interval and check if the time has expired. I once tried to macro'lize my 2 code lines but I was unhappy with the result.

expired(someTimerT, interval) {
    // code

I like macro's and I am holy convinced that they can increase readability of certain code, but this one was not of them. Perhaps that changing 'expired' in 'reloadTimer' would do the trick :stuck_out_tongue:

That library is OOP which comes with different syntax. I would add another fire function just for some more dynamica. To keep the interval value (being constant or variable) closer to the function.

if(usTimer.fire(interval)) {
    // always use accolades  :stuck_out_tongue:

Off-topic
On a coincidence, today I was able to set up a compile-able project folder using script work. The IO part is finished for 95% as well.

One fills in io.tab just like timers.tab

1 directionPin OUTPUT
2 pwmPin1 OUTPUT
8 endSensorLeft INPUT
12 endSensorRight INPUT
13 startSwitch INPUT_PULLUP

double click updateIO.py and a new io.cpp and io.h are made for you.

the source

#include <Arduino.h>
#include "io.h"
extern void initIO(void) {
 pinMode(directionPin, OUTPUT);
 pinMode(pwmPin1, OUTPUT);
 pinMode(endSensorLeft, INPUT);
 pinMode(endSensorRight, INPUT);
 pinMode(startSwitch, INPUT_PULLUP);
}

and the header

#define directionPin 1
#define pwmPin1 2
#define endSensorLeft 8
#define endSensorRight 12
#define startSwitch 13

extern void initIO(void);

The init function is called from out void setup. This is generated and cannot be forgotten.
You may think it is difficult but really it is not so complicated and comes with advantages.

  • You cannot ever forgot a pinMode instruction.
  • All IO and their triStates lie in one central file
  • the main .ino files is incredibly short

I have also added support for mcp23017 IO extender.
One can merely fill in io.tab with mcp IO looking like.

1	directionPin	OUTPUT

MCP1
1	mcpPin1	OUTPUT
2	mcpPin2	INPUT
3	mcpPin3	INPUT_PULLUP

MCP2
1	OthermcpPin1	OUTPUT
etc etc

The scripts enumerates and #define's numbers for all these MCP pins. From 0 to 127. Initialisation is done from within the same init function.
Currently I have 2 functions:

mcpWrite(pin, state);
mcpRead(pin);

They work like digitalWrite and read. The best part is that you don't have to remember which IO is on which mcp and on which port. The function calculates correct slave address, port and pin number for you. tri-state registers are also handled for us.

I am currently still working to implement a debounce feature for the standard IO pins. The library itself is finished, but I need to incorporate it within the program.

The idea is that instead of INPUT or INPUT_PULLUP you can type DEBOUNCED instead. That action should make a button object (or input object) which you can read out with:

state = buttonObject.readButton();

This function may return PRESSED, RELEASED, RISING or FALLING. The states rising & falling can only be returned once during a flank change. Reading a falling or released state forces the state to pressed or released. A rising or falling state is true for max 20ms.