Enginursday: Doing Away with Delay();

An approach to timing Arduino sketches.

Classical Arduino Programming

The mentality of Arduino is really to be able to throw some code in there and make it work. Coming from the nitty-gritty world of industrial design, I found the Arduino IDE was a fun toy to play with on weekends. I didn't have to care about being professional and could shoehorn my ideas into the loop, delay and repeat. I thought the Arduino IDE was unprofessional and there's no point to trying to write good code with it, because really, why would you?

however...

After I started working at SparkFun I came up with a challenge for myself. I saw the forty seven effects midi library, looked inside and said, "It's not the Arduino IDE that is wrong. It's my way of thinking that's wrong!" I challenged myself to use the constraints of the IDE to create code that works and is maintainable.

The delay(); statement

The deal breaker from the beginning was the use of the delay statement. One of the first lessons in microcontrollers is that software-driven delays are not a good idea.

A few problems with delays:

  • They tie up the program preventing it from doing anything else
  • They can't make well-regulated timing loops - user code is unknown in length
  • They promote linear thinking

An example using delay

The LED blink application states:

  • Turn on
  • Wait a second
  • Turn off
  • Wait another second
  • Repeat

Well what if I want another LED to blink every half second?

  • Turn on the LED A
  • Wait a half a second
  • Turn off LED A
  • Wait a half a second
  • Turn on LED B
  • Wait a half a second
  • Turn on LED A
  • Wait a half a second
  • Turn off LED B
  • Turn off LED A
  • Repeat

It's do-able. But what if I want to flash one LED at one second and the other at 34/92 of a second? Well then I'm in trouble. Or if I want to do any number of services, all at different rates? It becomes a nightmare.

Getting away from delay();

The timer interrupt is a perfect example of something else you can use. As soon as I realize a sketch needs consistent time tracking, a free interrupt gets used. I consulted the Hardware Resource Map from the Google code archive, found a timer I could take hold of, and made the interrupt increment some counter somewhere. Then, I can look at this counter and compare what time it is to some time I've previously recorded. Loop() can race along as fast as possible while the counter regularly increments, and I can do things on intervals.

Using a timer interrupt to unload work from the processor isn't exactly a new concept. I noticed when I code that I always make it the same way, so it was time to pull it out and make it a module. TimerModule is born.

TimerModule: periodic code execution

I want a thing that keeps time without drifting, and operates user code chunks at arbitrary intervals. That's the spec. I made, tested, and incremented until I was happy with it. More importantly, until I could trust it. Here's how it works.

Sketch-level operation

From the highest level, the sketch, the system is drawn like this:

alt text

Every time the loop executes, it first updates the current time to all the timers that are in use, then checks if any of the timers have exceeded the defined period. If so, each timer has a place the user code can be executed from. All the while, the interrupt increments the time counter.

Here's how the loop is coded. Objects msTimerA, B, etc. are all TimerModule classes, with msTimerx as the name for copies of the class.

void loop()
{
    //Update the timers, but only once per interrupt
    if( msTicksLocked == 0 )
    {
        msTimerA.update(msTicks);
        msTimerB.update(msTicks);
        msTimerC.update(msTicks);
        //Done?  Lock it back up
        msTicksLocked = 1;
    }  //The ISR will unlock.


    if(msTimerA.flagStatus() == PENDING)
    {
        digitalWrite( LEDPIN, digitalRead( LEDPIN ) ^ 0x01 );
    }
    if(msTimerB.flagStatus() == PENDING)
    {
        digitalWrite( LEDPIN, digitalRead( LEDPIN ) ^ 0x01 );
    }
    if(msTimerC.flagStatus() == PENDING)
    {
        digitalWrite( LEDPIN, digitalRead( LEDPIN ) ^ 0x01 );
    }
    delay(1);
}

There are a few assumptions for this to work.

  • The time counter has an infinite size, and will count forever.
  • The user code doesn't block operation (aka, delay(5000) in one task prevents the others from working for five seconds).
  • The loop() runs faster, slower, or equal to the timer interrupt. Order can't matter.

If these assumptions can be accounted for in code, the goal will have been met.

The first hurdle is that the size of the variable that counts how many times the interrupt has to be triggered. It can't be infinite and must rollover somehow.

alt text

The simplest solution is to use a known data type and just let it roll over. If I do that though, I have no way of knowing (ok, we're not looking at low level cpu register bits here). So I add more code. I check during each interrupt if it's above some limit, and reset it to zero.

Graphing the value of the time counter as a function of real time, the rolling action can be seen.

alt text

The tick counter (msTicks) counts until some limit is reached and then is reset. The padding below is to handle overflow cases of timer operation and is described later.

The ISR is written like this:

void serviceMS(void)
{
    uint32_t returnVar = 0;
    if( msTicks >= ( MAXTIMER + MAXINTERVAL ) )
    {
        returnVar = msTicks - MAXTIMER;

    }
    else
    {
        returnVar = msTicks + 1;
    }
    msTicks = returnVar;
    msTicksLocked = 0;  //unlock
}

Again, there's a subtle extra part that has been added. Rather than resetting to zero, a known quantity is subtracted from the counter and it's given an overhead. This is actually a product of how the arbitrary timers operate.

To create a sketch with any number of timers (to the extent of the memory available):

  • create a new TimerModule in the global scope - TimerModule myNewTimer
  • Add it to the list of protected updates - myTimer.update(msTicks);
  • Check it in the main loop with if(msTimerA.flagStatus() == PENDING){do something} - code goes within!

That's all there is to use it. If you want to try it out, drop this GitHub library in your 'libraries' directory, restart Arduino, and select the example "timerModuleExample." This will run on the Teensy 3.1 or Uno/Mega, and will xor the LED at three different rates, giving a pseudo-random like effect.

If you want to see what goes on under the hood, keep reading!

The TimerModule

The TimerModule is a small state machine that is controlled by the call to update, and the check of the flag. Here's the state diagram of the messaging action (Need a refresher? State Machines Enginursday Post):

alt text

The state is controlled by updating the time and by checking the state. If the module is updated and has passed a threshold, the state increments from waiting to pending, unless it was already pending. If it was, two thresholds have passed without anybody checking the state. Time begins slipping. When the state is checked, it becomes serviced and goes back to waiting.

The last piece of the puzzle is to determine how the timer module handles rollover. Also, what's with the counter overhead in the ISR?

Updating the TimerModule's counters

Take a step back and look at the loop. The loop begins by sending the current time (in msTicks) to each timerModule. Then, it checks to see if any of the updates caused a timer to expire.

Imagine three irregular but closely spaced updates of the timerModule. The loop takes different amounts of time, depending on which timers were executed, so they are not spaced evenly.

alt text

Notice how the internal value of time within the timerModule is a step function. Although the tick counter is updated every time the ISR hits, the timerModule doen't know about it unless the loop vends the information.

Each time they are updated, they check if the current threshold (or next service time) is reached. If so, they activate the state machine and calculate a new threshold for comparison.

Handling rollover

The basic operation is defined, but the case of rollover is still not understood. To investigate, look at what happens as the timer counters get close to the rollover. As Neal Stephenson said in Snow Crash, "Interesting things happen along borders."

The ISR automaticaly rolls the msTicks timer over. If the system presents a timerModule with a new time that is less than the previous time, it knows the ISR must have rolled the msTicks counter. Then, the threshold and the previous threshold time get reduced by MAXTIMER. Now, the counter is at the bottom of the cycle and the threshold is still the same distance away. One of the thresholds is negative though, and the "greater than" check doesn't work.

alt text

The module needs the next and the last thresholds to determine expiration. Without the padding, one of the thresholds can become negative.

A solution is to add a padding of the longest used timer interval to the bottom of the ticks coming in from the system, or, let the ISR do it automatically.

alt text

The offset makes sure that all rollovers result in positive thresholds.

Now, the ISR and the time keeping qualities of the TimerModule all ratchet the target numbers automatically. The logic of the TimerModule's state machine is then executed without any knowledge that rollover ever occured. Take a look at the update code of the TimerModule.

void TimerClass::update( uint16_t msTicksInput )
{
    //Check if overflow has occurred
    if( msTicksInput < lastService )  //overflow has occurred
    {
        //Adjust as if nothing ever happened
        lastService = lastService - MAXTIMER;
    }

    //Now process knowing an overflow has been dealt with if present
    if( msTicksInput >= (lastService + interval) )
    {
        //Timer has expired
        //Save the last service time
        lastService = lastService + interval;
        //Ready the flag
        if( flag == WAITING )
        {
            flag = PENDING;

        }
        else
        {
            flag = UNSERVICED;

        }
    }
}


Download the library

The library is compatible with Arduino. Put the library in your libraries folder and try running the example "timerModuleExample" on an Uno/Mega or Teensy 3.1.

Direct link: https://github.com/marshalltaylorSFE/uCModules

This post talks about just one module I commonly use. If you download the library and try it out, you'll find sketches for a few others I didn't want to talk about in the post (check /extras for more pictures!)

Also in the library you'll find:

  • TimeKeeper: the stopwatch class
  • FlagMessanger: communicate between nested classes
  • 32-bit versions of both the TimeKeeper and TimerModule for use on faster processors. They operate with microsecond ticks rather than millisecond ticks.
  • A seven-segment display wrapper for our 7-Segment Display
  • A collection of panel parts for implementing hardware - still in progress

And as development continues, these modules will be added:

  • Dynamically assigned linked list of objects
  • Circular buffer with averaging
  • RGBA color mixing for Arduino

Let us know what you think below!

I wanted to present how this engineer thinks, but I also wanted to give out something that may be useful to someone. If you gave it a whirl, let me know what your reaction was.

Keep on thinkin'!