Ages ago I promised an update to how I run fire simulation in the turntable bridge hut.
Nothing wrong with that except that the list of objects that needed to be updated on every loop iteration was getting long. Because each object decides when it needs to do something on an update call, the total time required for an update cycle to complete can be modestly variable.
To get really predictable timing for the fire simulation, I needed another approach. Since the update method is very simple, this object was a good candidate for handling via a timed interrupt.
Interrupts are (literally) a way of interrupting and taking over program flow to perform some high-priority task. Interrupts can be triggered by a hardware event, the passage of time or a signal from a running program. Interrupts are part of processor design and extensively employed in large scale computing. When an interrupt is triggered, program flow is handed over to a “handler” which is expected to perform a simple, time critical task then return control to the interrupted process.
The Atmel processors used in Arduino boards support hardware interrupts on specific pins so that the board can respond to momentary events, such as pulses coming from a rotary encoder.
Atmel processors also support timed interrupts. To access that capability and trigger interrupts based on the passage of time, we can use a special software utility – TimerOne. The code can be downloaded from the google code archive.
After you have installed the library, include it at the top of your sketch:
A timer object called Timer1 is automatically created by the TimerOne library.
In setup(), you initialize the timer with two lines:
Timer1.initialize(10000); // timer interval 10000 microseconds (or 0.01 sec; 100Hz)
Timer1.attachInterrupt( timerIsr ); // attach a service routine
The service routine is a function in your sketch that runs whenever the timed interrupt is called. My service routine gets the current time in milliseconds, then runs the update routine for the stove fire object.
unsigned long currentMillis = millis();
An ISR takes no parameters. If you want to share a global variable between an ISR and another part of your sketch, the variable declaration has to include the volatile modifer so the compiler knows it is subject to change by an ISR:
volatile int state;
Note that the fire object maintains its own state without having to declare any variables volatile. That is because I am not accessing the state variable outside the object. Accordingly, the compiler keeps track of the variable correctly without having to treat it as volatile.
The fire update method still decides when to run by comparing the time value passed by the ISR to the stored time of last update. In this case the fire object is set to update every 30 milliseconds ( a parameter when the object is instantiated ). Since the timer is going off every 10 milliseconds, the fire is updated every third interrupt.
You can find the code for the fire object in the original post.
If interrupts are a new concept, you might be tempted to think you can use them for all your multitasking needs, avoiding time slicing in the main loop.
In general, interrupts are not a universal substitute for time slicing in the main loop. Interrupts literally grab control without permission. As you ask interrupts to do more everything else slows down. Sequencing of actions can become a problem when timed interrupts are controlling everything.
Even worse, what happens if you are in the middle of the interrupt service routine and the timer goes off again? It can happen both because the routine is too long OR another interrupt takes over before the first ISR is finished and can exit.
Total smoking crash. Don’t say I didn’t warn you.
Here’s the fire in action:
So, use TimerOne strategically, such as to maintain simple, repetitive animations. Use it sparingly, but use it to get a simple, routine calculation and pin task done and out of the way of your main multitasking loop. Watch your timing: the interrupt should go off more often than needed, but not too much more. 2 to 1 or 3 to 1 ratios (interrupt frequency / update frequency) should work in most cases.