The Arduino IDE and environment has many drivers and libraries available within an arms reach, but the Arduino environment is limited to just setup()
and loop()
and doesn't support multi-tasking effectively. It also doesn't provide direct support for low power modes required if the application needs to be battery powered, or run from a power budget (like solar cells).
This article describes the use of the low power modes available for Arduino and compatible AVR ATmega devices within an easy to use and robust Arduino_FreeRTOS implementation that is available in the Arduino IDE as a Library, and using the AVR LibC functions and macros. Using FreeRTOS within Arduino allows the use of the best parts of both environments and makes it simple to effectively reduce the power consumed by any application.
BackgroundMost operating systems appear to allow multiple programs or threads to execute at the same time. This is called multi-tasking. In reality, each processor core can only be running a single program at any given point in time. A part of the operating system called the scheduler is responsible for deciding which program to run when, and provides the illusion of simultaneous execution by rapidly switching between each program.
Traditional real time schedulers, such as the scheduler used in FreeRTOS, achieve determinism by allowing the user to assign a priority to each thread of execution. The scheduler then uses the priority to know which thread of execution to run next. In FreeRTOS, a thread of execution is called a Task.
By using a combination of the FreeRTOS Idle Task and the AVR LibC Library <avr/sleep.h>
functions, we can easily reduce the instantaneous power consumption of the Arduino Uno MCU by orders of magnitude.
Specifically, from the ATmega328p datasheet the MCU consumes about 10mA when operated in Active mode at 5V supply and at 16MHz. When in the Power-down mode SLEEP_MODE_PWR_DOWN
the MCU consumes between 4μA and 8μA. That is potentially a 1000x reduction in power consumption.
Clearly, we can't have the Arduino MCU in Power-down mode constantly, as in that mode it is doing nothing but deeply sleeping deeply; a bit like Snow White. In Power-down mode, only the external Interrupts, the I2C interrupt, and the Watchdog Timer are enabled. Everything else is turned off to reduce the power consumption.
The trick is to wake up the Arduino MCU to do whatever Tasks are required and, as soon as these tasks are finished, put it back in the deepest sleep mode possible. That way, on average, we are sipping the smallest possible amount of power.
As the Arduino FreeRTOS Library uses the Watchdog Timer to manage its scheduling of Tasks, we can use any of the lowest power modes available, and still ensure that the Arduino MCU wakes up when it should. Because we use the Watchdog Timer, there is no need to use the external interrupts to wake the Arduino MCU, as most descriptions advise.
So, how do we know when the Arduino MCU has finished its duties, and no further Tasks need to run? Fortunately, FreeRTOS does this for us automatically, through its use of an Idle Task.
In FreeRTOS the Idle Task is started by the Scheduler when no other Task is available to be run. That is, all other Tasks are Blocked, awaiting some signal to awaken them. Often, Tasks are waiting for a specific time period to elapse before they need to be run again. In this situation, we can use the Idle Task to run the code we need to put the Arduino MCU to sleep, as we know that as long as the Idle Task has started the Scheduler has no other Task that it needs to run.
In the Arduino FreeRTOS library, the Idle Task only calls the Arduino loop()
function, which makes it very easy to add the sleep code into sketches.
Therefore, saving power becomes as simple as:
- Turn off unnecessary MCU features in
setup()
using<avr/power.h>
macros. - Enable the deepest sleep mode we can in
loop()
using<avr/sleep.h>
functions. - Choose the slowest FreeRTOS Scheduler Tick that we can support, to maximise the period of low power consumption, before the MCU is awoken and the Scheduler run to prioritise and unblock Tasks where appropriate. This will depend on your application.
- Ensure that all Tasks are blocked using FreeRTOS delay functions
vTaskDelayUntil()
orvTaskDelay()
, or using FreeRTOS semaphores or queues to block your Tasks until an event happens, or data is available to be processed.
In a previous ProTip we have described how to install the FreeRTOS Library for the Arduino IDE, and to test that it is working properly. If you haven't already done this step, please do so now.
Now copy and paste the setup()
and loop()
code provided below into a copy of Blink_AnalogRead.ino
, which you should rename and save as you choose.
Switch Off Unused Features
Firstly, we are going to turn off unused or unnecessary MCU features in setup()
using <avr/power.h>
macros.
Usually, we are able to switch off the digital input buffers on our Analog Pins. For analog input pins, the digital input buffer should always be disabled. An analog signal level close to half the Vcc supply voltage on any Digital or Analog Pin can cause significant current even in active mode. Usually this only happens when we are inputting an analog signal though. Digital input buffers can be disabled by writing to the Digital Input Disable Registers (DIDR0 and DIDR2), as shown in the example code.
We can also usually switch off the Analog Comparator circuitry. When entering Idle sleep mode, the Analog Comparator should be disabled if not used. When entering ADC Noise Reduction sleep mode, the Analog Comparator should also be disabled. In all other sleep modes, the Analog Comparator is automatically disabled. The Analog Comparator can be switched off using the example code provided.
Now we come to disabling any remaining unused features using the <avr/power.h>
macros. These macros affect control bits contained in the Power Reduction Register, and by setting or clearing the relevant bit in the register each feature is controlled.
- Disable the Analog to Digital Converter module.
power_adc_disable();
- Disable the Serial Peripheral Interface module.
power_spi_disable();
- Disable the Two Wire Interface or I2C module.
power_twi_disable();
- Disable the Timer 0 module. millis() will stop working.
power_timer0_disable();
- Disable the Timer 1 module. analogWrite() will stop working.
power_timer1_disable();
- Disable the Timer 2 module. tone() will stop working. Used for Real Time Clock in the Goldilocks 1284p devices.
power_timer2_disable();
Choose Deepest Sleep Mode
Now we have disabled as many features as are possible to reduce the active power consumption of the Arduino, we can consider which sleep mode is best suited for our specific application. The 6 available options for the set_sleep_mode()
function from <avr/sleep.h>
are listed below. In active mode the ATmega328p in an Arduino Uno will consume about 10mA.
SLEEP_MODE_IDLE
The Idle mode stops the CPU while allowing the SRAM, Timer/Counters, USART, 2-wire Serial Interface, SPI port, and interrupt system to continue functioning. Halting the CPU drops the power consumption to about 2.5mA.SLEEP_MODE_ADC
The ADC Noise Reduction mode stops the CPU and all I/O modules except asynchronous timer and ADC, to minimize switching noise during ADC conversions. This sleep mode is only relevant for increasing the accuracy of ADC conversions.SLEEP_MODE_PWR_DOWN
The Power-down mode saves the register contents but freezes the Oscillator, disabling all other chip functions until the next Interrupt or Reset. When waking up from Power-down mode, there is a delay from when the wake-up condition occurs until the wake-up becomes effective. This allows the Oscillator to restart and become stable after having been stopped. The wake-up period is defined by the CKSEL Fuses (about 1ms). In Power-down mode (with Watchdog Timer running) we are consuming only 6μA.SLEEP_MODE_PWR_SAVE
In Power-save mode the asynchronous (external or real-time) Timer 2 also continues to run, allowing the user to maintain a timer base while the rest of the device is sleeping. When waking up from Power-save mode, there is also a delay from when the wake-up condition occurs until the wake-up becomes effective defined by the CSEL Fuses (about 1ms). Enabling the asynchronous Timer 2 increases the total consumption to about 6.5μA.SLEEP_MODE_STANDBY
In Standby mode the crystal/resonator Oscillator is running, while the rest of the device is sleeping. This allows very fast start-up (6 CPU cycles) combined with low power consumption. In Standby mode the power consumption is less than 0.2mA.SLEEP_MODE_EXT_STANDBY
In Extended Standby mode the crystal/resonator Oscillator is running, and the asynchronous (external or real-time) Timer 2 also continues to run, while the rest of the device is sleeping. This also allows very fast start-up (6 CPU cycles) combined with low power consumption. In Extended Standby mode the power consumption is also less than 0.2mA.
So we can see that by using the simplest Idle mode, we have reduced the power consumption to 25% of Active consumption with no compromises, and by using Extended Standby mode we can achieve about 2% of Active consumption, with only a 6 CPU cycle start-up delay.
Choose Slowest Scheduler Tick
We can adjust the length of time that the Arduino MCU will sleep before being awoken by the Watchdog Timer, by configuring the setting in the FreeRTOSVariant.h
file contained within the Arduino_FreeRTOS library in your sketch folder.
// Using the Watchdog Timer for the System Tick (Scheduler Timer).
// Choose the rate at which Scheduler interrupts will occur.
/* Watchdog period options: WDTO_15MS
WDTO_30MS
WDTO_60MS
WDTO_120MS
WDTO_250MS
WDTO_500MS
*/
#define portUSE_WDTO WDTO_15MS
By changing the definition of portUSE_WDTO
we can adjust the sleep period from 15ms through to 500ms. But note, during the sleep period the MCU will truly be dead asleep, and won't awake (unless you actually do implement one of the External Interrupts, as otherwise advised, or an I2C Interrupt occurs). So it is a balance between length of sleep (less total power consumed) and the responsiveness of your applications.
Note also that delays are calculated with integer math. This means that the minimum delay will be the remaining time until the next system Tick (equivalent to vTaskDelay(0);
), and it is not possible to delay a specific fraction of a period or Tick.
Using a shorter sleep period is actually not so bad, because when the Scheduler is awoken to find no Tasks available to run it will immediately call the Idle Task containing the Arduino loop()
function, and we will go straight back to deep sleep.
As a reminder, when using the Arduino_FreeRTOS Library the Arduino loop()
function should never Block, or busy wait using delay()
, or have any kind of delay function included in it, as it is called by the FreeRTOS Idle Task (which also must never block).
As an example, assume we have an application that takes 1ms to process (capturing a sample, or reading a pin), and that it needs to be processed 4 times each second. By choosing WDTO_250MS we can wake up our Arduino in 1ms and do our Application processing in Active mode consuming 10mA for 1ms, and then put the Arduino back in Power Save mode for the next 248ms consuming only 6.5μA. That takes our average consumption to only 87μA.
Next StepsNow that you've created a sketch with multiple Tasks sleeping, try changing the set_sleep_mode();
configuration to see what effect that each option has on power consumption.
// set_sleep_mode() options available:
// SLEEP_MODE_IDLE
// SLEEP_MODE_ADC
// SLEEP_MODE_PWR_DOWN
// SLEEP_MODE_PWR_SAVE
// SLEEP_MODE_STANDBY
// SLEEP_MODE_EXT_STANDBY
set_sleep_mode( SLEEP_MODE_PWR_DOWN );
Read the ATmega328p datasheet for a more detailed description for each of the different power reduction modes. As there are 6 different power reduction modes available, and it is important to understand where each mode should be used to best effect in your own application.
The Right HardwareOf course, to make this effort in reducing the power consumption of the AVR MCU worthwhile the overall system power budget for your application needs to be considered. Standard Arduino devices are built with linear power supplies which typically have a leakage current in excess of several milli-Amperes. Trying to reduce the system power draw to micro-Amperes is not going to work if the power supply is already consuming orders of magnitude more than the MCU.
There are several options to avoid this problem.
- Use an Arduino compatible board that can completely disconnect the board power supply (with jumpers) and power it from your own regulated battery supply, like the Goldilocks Analogue or Freetronics EtherMega (although both these examples have ancillary hardware which consumes power, and may be redundant in your application).
- Use an Arduino compatible board with an efficient switched mode power supply (SMPS) design, with low leakage current, like the FTDI Nero.
- Use a device designed from the outset for low power consumption and battery power, like the Whisper Node (on Kickstarter now).
Comments
Please log in or sign up to comment.