05.01.2017

Asynchronous Tasks by Interrupt

Asynchronous tasks are useful because they don't block your main loop and you needn't play with time to detect whether an action should occure. Your code is cleaner and less vulnerable for bugs.

Reusing timer 0

You can read about timers here or you can generate one by my timer generator

You can also use predefined timer 0 used for micros(). This timer is configured as overflow timer. You can use this timer for your ISR interrupt routine but it must be configured as compare timer. The prescaler is 64 and every 256 ticks the routine is called. For 16MHz processor it's every 0.001024s, for 8MHz every 0.002048s

void setup() {
    TIMSK0 |= (1 << OCIE0A);    // enable timer compare interrupt
}

ISR(TIMER0_COMPA_vect) {
    // action like AsyncWorker
}
...

When no action in timer you can disable it

    TIMSK0 &= ~(1 << OCIE0A); // disable timer compare interrupt

Async worker use

AsyncWorker is a class which counts cycles (e.g. interrupt cycles when used ISR) and after defined number of cycles it calls a Task class which does an action. It's designed not to spend too much processor clock time to decide whether launch Task. It is about 44 processors clock cycles. Of course, in case the action is triggered the time is longer and you must be careful not to do long operation.

There are 2 predefined Task implementations:

  • SimpleTask.h - sets pin HIGH/LOW after some time
  • BlinkerTask.h - allows led to blink. The HIGH and LOW periods can be different. Note: it uses AsyncWorker repeatelly because it returns next cycles to wait unless last blink finished.

Example of BlinkerTask:

BlinkerTask blinkerTask(ledPin6, 5, INTERRUPT_CYCLES_FOR_DEFAULT_TIMER0(0.15), INTERRUPT_CYCLES_FOR_DEFAULT_TIMER0(0.3);
AsyncWorker asyncWorkerForBlinker6(&blinkerTask);

AsyncWorker is launched by its method startAfterCycles(int cycles):

asyncWorkerForBlinker6.startAfterCycles(0);  // start immediatelly

Interrupt routine

Use the timer 0.

ISR(TIMER0_COMPA_vect) {
    asyncWorkerForBlinker6.check(); // encounter next cycle and decide whether to launch task
}

Math for cycles

We reuse timer 0 which is configured for frequency 1000Hz on 16MHz processor

#define INTERRUPT_FREQUENCE_ON_16MHZ 1000 // what interrupt frequency would be for 16MHz processor

Formula to compute number of interrupt cycles for given time period:

#define CYCLES (SECONDS * INTERRUPT_FREQUENCE_ON_16MHZ) * (F_CPU / 16000000.0)

If you use timer0 configured by default you can use simplier macro:

INTERRUPT_CYCLES_FOR_DEFAULT_TIMER0(1.0) // for 1 second

Complete Example

This routine waits for press of button on pin 6 and since this it turns on the led 13 and asynchronously after 1 second disables it.

#include "Arduino.h"
#include "AsyncBlinker.h"

const int ledPin = 13;

// Timer0 for millis() is configured with frequency 1000 HZ
#define INTERRUPT_FREQUENCY_ON_16MHZ 1000

AsyncBlinker incomingStateBlinker(ledPin, SECONDS_TO_INTERRUPT_CYCLES(0.5, INTERRUPT_FREQUENCY_ON_16MHZ), SECONDS_TO_INTERRUPT_CYCLES(0.05, INTERRUPT_FREQUENCY_ON_16MHZ));

void setup() {
    TIMSK0 |= (1 << OCIE0A);
    pinMode(13, OUTPUT);
}

ISR(TIMER0_COMPA_vect) {
    incomingStateBlinker.check();
}

void loop() {
    delay(3000); // note: it blinks also during delay
    incomingStateBlinker.blink(3);
}

Conclusion

It's very handy to use predefined timer0 interrupt and you can enable your own interrupt routine which performs asynchronous tasks. Main loop is not blocked and is clear.

Download