Das Blinkenlights - Arduino w/sleep version

Jul 27, 2011 00:31


Let's blink an LED. But we'll do it with an Arduino. And just to be difficult, we won't use delay and we certainly don't want to spin around in a loop watching a number go up slowly until it matches our expected value. The person who wrote that example should be shot.

And even if you are interested in programming an Arduino, LJ will bork the fomatting of the following code so badly that you'll wish you were blind.

#include #include #define LED 13 int count; volatile boolean wdt_tripped=1; void system_sleep() { delay(2); // Wait for serial traffic _SFR_BYTE(ADCSRA) &= ~_BV(ADEN); // Switch ADC off set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); sleep_mode(); // System sleeps here sleep_disable(); _SFR_BYTE(ADCSRA) |= _BV(ADEN); // Switch ADC on } void wdt_interrupt_mode() { wdt_reset(); WDTCSR |= _BV(WDIE); // Restore WDT interrupt mode } ISR(WDT_vect) { wdt_tripped=1; // set global volatile variable } void setup() { wdt_disable(); wdt_reset(); wdt_enable(WDTO_1S); Serial.begin(9600); pinMode(LED, OUTPUT); count = 0; } void loop() { wdt_interrupt_mode(); if (wdt_tripped) { count++; wdt_tripped = 0; Serial.println(count); if (count == 9) { digitalWrite(LED, 1); } if (count > 9) { digitalWrite(LED, 0); count = 0; } } //delay(2000); // uncomment to simulate program crash/infinite loop system_sleep(); // it's knock off time! }
What a lot of code to simply blink an LED! I like it like that: why use one line of code when you can use a hundred?

“But what does it all mean?” you ask. Okay let's go through it.

First up, note that the global variable wdt_tripped is defined as volatile. This is a warning to the compiler that this variable can change at any time, thus use of the variable should not be "optimised" (such as being stored in a register during a function call or loop) otherwise the code could end up using an incorrect value of this variable.

The system_sleep function is responsible for putting the system to sleep in a sane manner, then waking it up in a sane manner. The delay is there to allow time for serial ports to finish sending characters, then the ADC is switched off, the AVR is told to use the maximum power saving sleep mode, then the system is put to sleep. When the system is woken (using any interrupt), the ADC is turned on again.

The wdt_interrupt_mode function is just a nice English-language wrapper around the instruction to turn on "Interrupt Mode" for handling the Watchdog Timer. When the WDIE bit of the WatchDog Timer Control Register is set, the Watchdog Timer will trigger an interrupt, rather than restarting the system. Once the interrupt has been triggered, the AVR will clear the WDIE bit. Thus if we go to sleep again without setting WDIE, the outcome will be one wake-from-sleep followed by a system restart. Thus one of the first things the loop function does is set interrupt mode again.

The WDT interrupt handler presents an important concept in writing interrupt code: do the absolute least work possible in the interrupt handler. You want to let the processor get back to its normal program execution as soon as possible. Mark the interrupt as having been invoked, then do what ever else is required in the main program. Don't copy data from I/O buffers in the interrupt handler: simply set a flag to indicate that buffers need to be emptied, and do the actual emptying in the main loop.

The setup function sets up the watchdog timer. In this example we're going to trigger the watchdog timer after about 1 second (the time is only approximate - it's a watchdog timer, not a real time clock). Then there is the usual serial setup, digital pin setup and variable initialisation.

The loop function does a few housekeeping things: first there's the attention it has to pay to the watchdog timer, then the logic to blink the LED, and finally the system is put to sleep. Note that the work of incrementing the counter is done in the portion of the main loop reserved for doing the work flagged by the WDT interrupt.

There's no code here to check whether it is time to turn the LED on again - if it's important to blink the LED to a schedule, there would be a RTC clock attached to this project and the loop code would be sure to keep track of when the next on/off transitions are supposed to occur. The millis function can not be used, since millis are not incremented when the system is asleep.

Another point to note is that to turn the LED on I check for equality to 9. I shouldn't do that. There is no guarantee that the code will get to this point before another interrupt arrives and increments count again. One alternative would be to have a second variable representing the "state" of the LED blinking machine. When the LED is off, wait for count to become greater than 9, at which point you turn the LED on and reset the counter. When the LED is on, wait for the count to become greater than 0, at which point you turn the LED off and reset the counter. See how that works?

To simulate an infinite loop or other software fault, uncomment the delay(2000) line. Watch the output of the Arduino on your serial console, and you'll see that it is endlessly emitting "1" because it's being restarted by the watchdog timer, which has given up waiting.

Finally, the processing loop is finished. Put the system to sleep and it will be woken up by the next interrupt, with processing starting from the top of the loop.

There's my quick-and-dirty walkthrough of a trivial sleep-and-interrupt driven LED blinking sketch. I hope this helps you in some way! There is plenty of refactoring that could be done with this code as it is: move the contents of the if (wdt_tripped) statement to a function, change the name of the variable from wdt_tripped to something else, who knows?

PS: I originally followed this line of programming because I was interested in conserving as much power as possible in my temperature logging project. It turns out that I can reduce the power consumption of the Mega326P from about 15mW to about 0.7mW - pretty impressive, yeah? But then the Arduino Uno has that linear power regulator which is constantly drawing 15mW on its own, even when everything on the board is switched off. Bugger. I'll verify those claims at some point in the future, when I can get over the disappointment of all this apparently wasted effort ;)

howto, arduino, tutorial, engineers-log, geek, programming

Previous post Next post
Up