Battery Powered Arduino Applications through FreeRTOS © GPL3+

Use FreeRTOS (simple, easy, robust, & optimised for the Arduino IDE) to reduce the power required for battery or low power applications.

  • 9,215 views
  • 2 comments
  • 18 respects

Components and supplies

Apps and online services

About this project


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.

Background

Most 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 functionsvTaskDelayUntil() or vTaskDelay(), or using FreeRTOS semaphores or queues to block your Tasks until an event happens, or data is available to be processed.

Let's Get Started

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_DOWNThe 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 Steps

Now 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 Hardware

Of 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).


Code

Arduino loop() as FreeRTOS Idle TaskArduino
This is the only code that needs to go into the loop() function to enable sleep mode between Scheduler Ticks.
#include <avr/sleep.h>  // include the Arduino (AVR) sleep functions.
 
loop() // Remember that loop() is simply the FreeRTOS idle task. Something to do, when there's nothing else to do.
{
// There are several macros provided in the  header file to actually put
// the device into sleep mode.
// See ATmega328p Datasheet for more detailed descriptions.
 
// 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 );
 
portENTER_CRITICAL();

sleep_enable();
 
// Only if there is support to disable the brown-out detection.
// If the brown-out is not set, it doesn't cost much to check.
#if defined(BODS) && defined(BODSE)
sleep_bod_disable();
#endif
 
portEXIT_CRITICAL();

sleep_cpu(); // Good night.
 
// Ugh. Yawn... I've been woken up. Better disable sleep mode.
// Reset the sleep_mode() faster than sleep_disable();
sleep_reset();
}
Arduino setup() Power Saving CodeArduino
These sections of code can be added to setup() to reduce power consumption, by turning off unused features.
// Include the AVR LibC functions for power reduction.
#include <avr/power.h>


// the setup function runs once when you press reset or power the board
void setup() {
  
// Digital Input Disable on Analogue Pins
// When this bit is written logic one, the digital input buffer on the corresponding ADC pin is disabled.
// The corresponding PIN Register bit will always read as zero when this bit is set. When an
// analogue signal is applied to the ADC7..0 pin and the digital input from this pin is not needed, this
// bit should be written logic one to reduce power consumption in the digital input buffer.
 
#if defined(__AVR_ATmega640__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega1281__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega2561__) // Mega with 2560
DIDR0 = 0xFF;
DIDR2 = 0xFF;
#elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284PA__) // Goldilocks with 1284p
DIDR0 = 0xFF;
#elif defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega8__) // assume we're using an Arduino with 328p
DIDR0 = 0x3F;
#elif defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__) // assume we're using an Arduino Leonardo with 32u4
DIDR0 = 0xF3;
DIDR2 = 0x3F;
#endif


// Analogue Comparator Disable
// When the ACD bit is written logic one, the power to the Analogue Comparator is switched off.
// This bit can be set at any time to turn off the Analogue Comparator.
// This will reduce power consumption in Active and Idle mode.
// When changing the ACD bit, the Analogue Comparator Interrupt must be disabled by clearing the ACIE bit in ACSR.
// Otherwise an interrupt can occur when the ACD bit is changed.
ACSR &= ~_BV(ACIE);
ACSR |= _BV(ACD);


// CHOOSE ANY OF THESE <avr/power.h> MACROS THAT YOU NEED.
// Any *_disable() macro can be reversed by the corresponding *_enable() macro.

// 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.
power_timer1_disable();

// Disable the Timer 2 module. Used for RTC in Goldilocks 1284p devices.
power_timer2_disable();


// Now continue to initialise Tasks, and configure the Interfaces (that are not disabled).
// And do any other setup that you need to do.

}

Comments

Similar projects you might like

Smart Access [Home/Office Automation]

Project in progress by Ajmal Muhammad P

  • 1,512 views
  • 5 comments
  • 6 respects

SPCPM (Solar Powered City Pollution Monitor)

Project tutorial by 5 developers

  • 3,354 views
  • 0 comments
  • 18 respects

Internal Timers of Arduino

Project tutorial by Marcazzan_M

  • 5,012 views
  • 10 comments
  • 35 respects

Mini CNC Laser Wood Engraver and Laser Paper Cutter

Project tutorial by Maggie Shah

  • 4,262 views
  • 0 comments
  • 27 respects

Smart Talking Humanoid Robot Just with Arduino!

Project tutorial by ashraf_minhaj

  • 724 views
  • 2 comments
  • 9 respects

Sigfox kWh Meter

Project tutorial by jassak

  • 4,801 views
  • 3 comments
  • 12 respects
Add projectSign up / Login