RT LOOP - also for Arduino

links to code

list folder: nokrnl


Often you do not have any operation system and maybe do even not need it.

Still you might need to run precise periodic code - aka realtime.

You can

  • use interrupt and have an internal or external precise timer

  • use internal busy waiting on the right time

    • requires an internal timer - like millis in Arduino

Timers based on increment of a counter every xx millisecond or second will do a wrap around some time in the future.

unsigned char x=0;
 while (1)
   x++;

   x will be 0 1 2 3 4 ... 250 251 252 253 254 255 0 1 2
                                                   ^-- wrap around

Unsigned integer math has some - in this case - positive sideffects.

The below handles nicely wrap around without any problem :-)

First example is just pure C you can test on a std PC

Next is a Arduino implementation using millis

Leap milliseconds

In some systems leap milli seconds is added to time to compensate for that the timer interrupt differs from the wanted time.

In an AVR arduino beware that millis from time to time add lead milliseconds

from https://forum.arduino.cc/t/when-does-millis-increment/144482/5

The interrupt fires every 1.024 mS, so millis() increments that often. Because of the slight discrepancy (the 0.024 part) the interrupt will
increment the millis figure by 2 from time to time (think of it as a leap year).

Jens: Every 41,6 interrupt millis counter is incremented by 2 and not 1.

So approx every 42 msec is a leap millisecond added

In vrs 1.18.19 the file wiring.c holds the interupt code

<your install>hardwarearduinoavrcoresarduinowiring.c

millis code in timer interrupt ISR

Here is a fragment of Arduinos timer interrupt. You can see leap milliseconds is added from time to time.

from arduino source ..

MICROSECONDS_PER_TIMER0_OVERFLOW =  (64*256)/16 = 1024
So an interrupt every 1.024msec not every millisecond
So for every tick we are 24 micro sec more behind

We therefor need to add a millisec leap.

So every 1000 usec/24usec [tick] we must add this leap millis
every  1000/24 = 41,6666.. tick.

This is implemented in the Arduino timer ISR

But you must live we an extra millisecond every approx 42 "millissecond"


wiring.c:#define FRACT_INC ((MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3) == 24 >> 3 = 3
wiring.c:#define FRACT_MAX (1000 >> 3)  == 1000/ (2^3) = 125


Approx every 42(125/3) intr a leap millisecond is added

ISR(TIMxxOVF_vect)
m += MILLIS_INC;
f += FRACT_INC;
if (f >= FRACT_MAX) {
	f -= FRACT_MAX;
	m += 1;
}
timer0_fract = f;
timer0_millis = m;
timer0_overflow_count++;

Arduino leap test program

For chekking how often it occurs just mail a test program like this:

unsigned long tLast, tNow;
void setup()
{
  // last line
  tNow = tLast = millis();
  Serial.begin(115200);
}

void loop() {
  noInterrupts();
  tNow = millis();
  interrupts();
  if (2 <= tNow - tLast) {
    //lead millisecond

    Serial.println(millis());
  }
  tLast = tNow;
}

/*
output
...
...    delta
3243
3286   43
3328   42
3371   43
3414   43
3456   42
3499   43
3542   43
3584   42

41,66
*/

The wrap around problem reaching max UINT

Straight forward wrap around code example for testning.


// small test program
// use unsigned char instead of unsigned to reach wrap around with 256 steps
#include <stdio.h>
#include <math.h>
void main()
{
int c =0;
unsigned char x,x0,y,z;  // char -> int in real life
x0 = x = 12;
y = 3;
z = x;

  for (int i=0; i < 500; i++)
  {
     x0 = x;
     x++;
     c++;
     if (x < x0) { // wrap
       z+=y ;
       x+=y ;
     }

     if (x-z >= y) {
       z+=y;
       printf("ping %i\n",c);
       c = 0;
     }
     printf(" x%i y %i z %i\n",x,y,z);
  }
}


Arduino impl

Vanilla Arduino code where you can add your code at now its time …

You will have a leap millisecond approx 42 times a second

const unsigned long tPeriod = 100;  // 100 msec periode
unsigned long tLastRun;

void setup() {
  TLastRun = millis();
}

void loop() {

   //busy waiting
  if  (millis() - tLastRun >= tPeriod) {
    tLastRun += tPeriod;
    delay (tPeriod/2);  // emulate running critical code 50% of time
  }
}

No wrap problem solution presented here: millisrt.pdf

Thanks to Nick Gammon for this idea

code snippets

list folder: nokrnl