Utilizing low-power modes is a standard practice in embedded applications that rely on a battery as their primary power source. Most microcontrollers offer various low-power modes that gradually disable on-chip features and sometimes lower the CPU clock frequency or disable cores, in multi-core CPUs. All these measures can help reduce an embedded design’s power requirements and extend battery life, which can be crucial in projects that are difficult to service. Many hobbysists might not be familiar with the Arduino’s low-power modes, and this article investigates the power-saving options on AVR and SAMD-based Arduinos. We’ll explore the different power-saving modes, which features they disable, and how to activate them.

Projects That Benefit From Power-Saving Modes

As mentioned, any project that primarily runs on battery power can benefit from reduced power consumption. However, the difference is especially striking in projects that operate remotely, for example, outdoor weather stations or other projects that periodically collect and transmit sensor data. These projects usually idle for most of the time the device is powered on, and they only sporadically get active and request values from sensors. Instead of waiting, these devices could power down entirely or go into a deep-sleep mode to save energy instead of waiting for time to pass between sensor reading cycles.

In some cases, an external low-power timer, such as the TPL5110, can help conserve even more energy. When using such a timer, the Arduino can be shut down entirely so that it doesn’t use any power at all. The timer board only draws a few micro-amps, and it can be programmed to periodically power up the Arduino so that it can perform whatever task it was programmed to do, before shutting down again. Using this setup is especially beneficial if the project requires the entire Arduino board, instead of using only the microcontroller chip, as the Arduino’s other components, such as the power LED, require power as well.

Available Low-Power Modes

SAMD-Powered Arduino Boards

The easiest way to reduce power consumption on an Arduino is by putting the processor to sleep. Deep Sleep mode turns off most of the internal modules, which saves the most power. You can also set a timer so the device automatically wakes up after a certain period. Light Sleep mode, on the other hand, keeps some modules running, which allows the board to maintain certain tasks or settings while still reducing power. Typically, Deep Sleep is used when maximum power savings are needed, while Light Sleep is useful if the device needs to track external inputs or maintain state.

Within these sleep modes, there are different levels of power reduction called Stand-By and Idle modes. These let you control which parts of the microcontroller remain active. Stand-By mode is typically the lowest power option. It stops most clock sources and puts voltage regulators into a low-power state. Normally, oscillators can be configured to stop completely, run as needed, or run only when requested by a peripheral. The processor remains in Deep Sleep until it receives an interrupt or a signal from the Watchdog Timer (WDT), which wakes it up.

Idle mode works similarly to Stand-By, but it keeps peripherals running. Since the processor isn’t in Deep Sleep, power consumption is higher than in Stand-By mode. Programmers can choose which clocks stay on. Like Stand-By, the device wakes from Idle mode when an interrupt or the WDT triggers it.

The Low-Power Arduino Library

Which low-power features are available usually depends on the MCU architecture. Some devices offer ultra-deep sleep features that disable everything except for the watchdog timer, while others only allow turning off certain features, such as the ADC or some of the CPU cores. However, hobbyists generally don’t need to know the details about which features are available on the Arduino board they want to use, unless they want to utilize very specific features. For all other instances, makers can make use of the standard Arduino Low Power library.

This library hides the nitty-gritty details behind a convenient abstraction layer, allowing programmers to write code that makes use of low-power modes and remains compatible with all official Arduino boards. It is available for several platforms, such as SAMD and Portenta H7.

AVR-Based Arduino Boards

The most effective way to reduce power consumption on an AVR-based Arduino, such as the Uno, is to put the processor into one of its built-in sleep modes. The ATmega328P supports six modes, each offering a different level of power savings. Idle mode stops the CPU while keeping peripherals such as timers, the ADC, and the USART running. ADC Noise Reduction mode halts the CPU and most other modules but keeps the ADC active for more precise analog readings. Power-down mode shuts off nearly all internal functions, leaving only asynchronous interrupts and the watchdog timer able to wake the device, making it the lowest-power option. Power-save mode is similar to Power-down but allows a running timer to continue counting, useful for long-term timing tasks. Standby mode keeps the oscillator running so the MCU can wake more quickly, and Extended Standby combines the features of Standby and Power-save for low-power operation with ongoing timers.

The device remains in the selected sleep mode until it is awakened by an external interrupt, a pin change, or the watchdog timer. Choosing the appropriate sleep mode lets you balance energy savings with which peripherals need to remain active, ensuring the MCU only consumes as much power as the application requires.

Low-Power Library for AVR

Unfortunately, there’s no offical equivalent of the Arduino Low-Power library for AVR-based Arduino boards.

Wake Up From Deep Sleep

As discussed, there are generally two approaches to waking an MCU from its low-power state: periodic and event-based wake up.

Periodic (timer-based) wake up

This one is useful for devices that sit on their own and repeatedly perform a task, such as taking a photograph for a time-lapse or reading sensor values once every hour. In the simplest form, the on-board watchdog timer (WDT) is commonly used to periodically reset the CPU or trigger an ISR. However, the catch is that some MCUs, such as the one on the Arduino Uno, only support a limited range of configurable periods. On the Uno, for example, the maximum sleep time is approximately eight seconds when using the WDT to reset the CPU. However, programmers can work around this limitation by configuring the WDT to call an ISR, then count the number of wake events, and only trigger an action once the device woke up enough times:

#include <avr/wdt.h>
#include <avr/sleep.h>

volatile int wdtCount = 0;

ISR(WDT_vect) {
  wdtCount++; // increment counter every WDT wakeup
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  // Configure WDT for 2-second interrupt
  MCUSR &= ~(1<<WDRF); // Clear reset flag
  WDTCSR |= (1<<WDCE) | (1<<WDE); // Enable WDT config changes
  WDTCSR = (1<<WDIE) | (1<<WDP2) | (1<<WDP1); // 2s interrupt mode
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Configure lowest power mode
}

void loop() {
  while (wdtCount < 5) {
    sleep_enable();
    sleep_cpu(); // go to sleep, will wake on WDT
    sleep_disable();
  }
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  wdtCount = 0;
}

This snippet may seem rather complicated for what it achieves. The setup function initializes the WDT to call ISR every two seconds. It does this by setting certain bits in the MCUSR and WDTCSR control registers. Finally, it configures the CPU sleep mode, which it sets to SLEEP_MODE_PWR_DOWN in this example, which is the lowest possible sleep state.

The ISR increments a wake-up counter that counts how many two-second sleep phases the CPU finished. This counter is checked in the loop function, which toggles the LED if the CPU slept for ten seconds (five two-second sleep/wake-up cycles). If not, it sends the CPU back to sleep.

Note that the previous snippet is specific for AVR Arduinos, like the Uno. However, using the Arduino Low-Power library, mentioned above, programmers can greatly simplify their code while simultaneously ensuring that it works on all supported Arduino boards. All they have to do is call a single convenience function with an arbitrary time delay in milliseconds, for example:

#include "ArduinoLowPower.h"

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  LowPower.deepSleep(5000); // Ultra Low-Power Mode
  // LowPower.sleep(5000);  // Low-Power Mode
}

The low-power library code works on SAMD-based boards, and it generates a single wake-up event, thus not wasting any power on waking up repeatedly when the WDT wraps over.

External events (interrupts)

While the WDT approach is very simple and doesn’t require additional external devices, the MCU may still need to wake up more often than wanted, which can reduce the battery runtime. Therefore, it’s better to use an external low-power timer when sleep times beyond the chip’s built-in capabilities are required. The timer can then trigger a pin-interrupt that instructs the device to wake up and perform an action.

Similarly, using an interrupt to wake a device allows it to react to its environment, for example, by reacting to certain sensor values or other events, such as another device requesting the MCU to wake up and process incoming data. Other peripherals, such as the UART interface or the ADC, can also prompt the MCU to wake up.

Similar to before, using the Low-Power Arduino library, the code to wake the CPU from its sleep state is straightforward:

#include "ArduinoLowPower.h"

#define INTERRUPT_PIN 2

volatile bool outputState = false;

void wakeUp() {
  outputState = !outputState;
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(INTERRUPT_PIN, INPUT);
  LowPower.attachInterruptWakeup(INTERRUPT_PIN, wakeUp, CHANGE);
}

void loop() {
  digitalWrite(LED_BUILTIN, outputState);
  LowPower.deepSleep(20000);
}

The following snippet achieves the same goal on AVR-based Arduino boards:

#include <avr/sleep.h>
#include <avr/interrupt.h>

#define INTERRUPT_PIN 2

volatile bool ledState = false;

void wakeUp() {
  ledState = !ledState;
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(INTERRUPT_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), wakeUp, CHANGE);
}

void loop() {
  digitalWrite(LED_BUILTIN, ledState);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sleep_cpu(); // go to sleep, will wake on INT0
  sleep_disable();
}

Advanced Power-Saving Considerations

Of course, it’s always possible to set or clear certain flags in the MCU’s control registers to enable or disable hardware features, provided that the MCU supports those operations. However, they are very specific to certain models and platforms, and I’ll not discuss them in any more detail here. If you’re interested in going further, you should refer to the datasheet of your specific MCU, where you’ll find details on configuring sleep modes, adjusting clock speed, changing voltage levels, or selectively turning off peripherals or cores. The Arduino library provides a convenient abstraction for writing short, understandable code, but in doing so it hides some of these nuances and limits the amount of fine-grained control you have.

Summary

In conclusion, low-power modes are not just an optional feature but often a necessity for battery-powered designs. They can extend the lifetime of a project from a few days to several months on a single charge. The ATmega328P provides a range of options, from light sleep to deep sleep, with the ability to wake periodically through the watchdog timer or in response to external events. Projects that spend long periods idle, such as weather stations, data loggers, and remote sensor nodes, benefit the most from these techniques since they can remain inactive until needed. The Arduino Low Power library offers a simple way to use these features across different boards, while the datasheet remains the key reference for those who want to fine-tune clocks, voltage levels, or peripherals for maximum efficiency.