# Power Modes

RSL15 has three main power modes: Run Mode, Sleep Mode, and Standby Mode. Run Mode is the default functional mode. Sleep Mode and Standby Mode power down and/or reconfigure subsystems to achieve lower system-level power consumption; they are generally categorized as Low Power Modes.

An overview of the power modes and operating modes is shown in the "Power Modes" table. States and configurability of subsystems in each power mode are summarized in the "Power Mode Subsystems" table.

Table: Power Modes
Power Mode | Description
--- | ---
Run Mode | Processor, RF subsystem, and memory are powered as needed by the user application – clocks are active, all peripherals available. Idle mode, a subset of run mode, configures the system as though entering standby mode, where the Arm Cortex-M33 processor is held waiting for an interrupt or event instead of actually entering standby mode.
Sleep Mode | The lowest power mode. Processor and RF subsystem are powered down and not clocked. Only selected wakeup sources are powered. Memory retention (and amount of memory retained) is optional. Some peripherals are available in Sleep Mode. On wakeup, the ROM restores the system before program execution begins. Smart Sense Mode is a subset of Sleep Mode, highlighted by the use of the ultra low power data acquisition subsystem in a configuration that spans both Sleep and Run Modes.
Standby Mode | A low power mode with faster wakeup time than Sleep Mode. Processor and RF subsystem are powered with lower voltage and not clocked. Only selected wakeup sources are powered. Memory retention (and amount of memory retained) is configurable. Some peripherals are available in Standby Mode. On wakeup, the program execution is resumed (i.e., the instruction right after the LWFI statement is executed).

Table: Power Mode Subsystems
Sub-Blocks | Power Modes
--- | ---
|  | Run Mode | Sleep Mode | Standby Mode
Core | Arm Cortex-M33 Processor | Clock, normal voltage | No clock, low voltage or OFF | No clock, low voltage
Arm Cortex-M33 Processor FPU | Clock, normal voltage or OFF | No clock, low voltage or OFF | No clock, low voltage or OFF
Arm Cortex-M33 Processor Debug | Clock, normal voltage or OFF | No clock, low voltage or OFF | No clock, low voltage or OFF
Baseband | Clock, normal voltage or OFF | No clock, low voltage or OFF | No clock, low voltage or OFF
Baseband timer | Clock, normal voltage | Clock, low voltage or No clock, OFF | Clock, low voltage
Arm CryptoCell-312 (+ its memories) | Clock, normal voltage or OFF | No clock, low voltage or OFF | No clock, low voltage or OFF
Arm CryptoCell-312 Always On | Clock, normal voltage | No clock, low voltage or OFF | No clock, low voltage
Individual RAM instances | Clock, normal voltage or OFF | No clock, low voltage retention mode or OFF | No clock, low voltage retention mode
Flash instance | Clock, normal voltage or OFF | No clock, OFF | No clock, low voltage, OFF
ACS | Analog registers | Active | Active | Active
Wakeup GPIOs | SW programmable | SW programmable | SW programmable
RTC clock | SW programmable | SW programmable | SW programmable
Analog | Bandgap | ON | SW programmable | ON
VCC regulator | ON | SW programmable | ON
VDDC regulator | Normal voltage | OFF | Low voltage
VDDM regulator | Normal voltage | OFF | Low voltage
VDDC retention regulator | SW programmable | SW programmable | SW programmable
VDDM retention regulator | SW programmable | SW programmable | SW programmable
VDDCP charge pump | ON | SW programmable | ON
VDDFLASH regulator | ON | OFF | SW programmable
RF regulator | SW programmable | OFF | SW programmable
PA regulator | SW programmable | OFF | SW programmable
Temp sensor | SW programmable | OFF | OFF
Current source | SW programmable | OFF | OFF
LSAD | ON | OFF | OFF
Ultra Low Power (ULP) Data Acquisition Subsystem | SW programmable | SW programmable | SW programmable
SAR-ADC | SW programmable | SW programmable | SW programmable
RF front-end | SW programmable | OFF | SW programmable
SDAC | SW programmable | OFF | OFF
ACOMP | SW programmable | SW programmable | SW programmable
RF | RF front-end | SW programmable | OFF | SW programmable

NOTE: The power modes in descending order of power consumption are Run, Idle, Standby, Smart Sense, and Sleep.

## Keeping the Debugger Connected in Low Power Modes

If the Arm Cortex-M33-Debug power domain remains active, and a debug connection has been previously established to the device, the debug link will remain active across different power modes. This allows the device to be debugged while in Sleep or Standby Mode.

To support debugging in Sleep and Standby Modes, the following items are forced to remain enabled during these modes:

- VDDC and VDDM regulators

- Pads used by the debug port, by masking pad retention configuration for only the active debug port pads (in SW debug mode this includes only the SWCLK and SWDIO pads, in JTAG debug mode this includes JTCK, JTMS, JTDI, JTDO, and optionally JTRESET).

IMPORTANT: The device will consume more power when a debug connection is used than the device would consume if the debug connection is not used.

NOTE: To maintain the same behavior as when the debug port is disabled in Sleep or Standby Mode, VDDC will be reset if the ACS_VDDRET_CTRL_VDDCRET_ENABLE bit from the ACS_VDDRET_CTRL register is not set. As it is not possible to match the VDDM behavior between cases where debug is enabled and debug is not enabled in Sleep and Standby Modes, take extra care in applications to appropriately set the ACS_VDDRET_CTRL_VDDMRET_ENABLE bit from the ACS_VDDRET_CTRL register if the contents of any RAM instances are to be retained when the debug port is disabled.

## Run Mode

In Run Mode, all the circuitry is powered on.

### Idle Mode

Idle Mode is a subset of the Run Mode configurations, allowing for some power savings relative to Run Mode, with the fastest wakeup time of any power mode. In Idle Mode, the device is configured as though entering into Standby Mode, but rather than disabling the system and isolating memories, the Arm Cortex-M33 processor is held waiting for an interrupt or event instead of actually entering Standby Mode, to minimize the time needed to return to normal Run Mode execution.

## Sleep Mode

When operating in Sleep Mode, RSL15 shows very low current consumption. Only the wakeup logic (through pad, RTC, or sensor interface) is kept powered. The regulators, RF block, etc., are disabled. The bandgap, digital core, and memories can be optionally powered, at low voltage.

If the digital core is not powered (i.e., the core retention regulator is not enabled), a reset is generated when entering Sleep Mode, and all registers in the digital core are reset to their default values. Registers in the Analog Control Sub-system (ACS) always keep their values over power mode cycles.

The ACS_BOOT_CFG_PADS_RETENTION_EN bit in the ACS_BOOT_CFG register needs to be set prior to entering Sleep Mode, in cases where VDDC and VDDM are not powered during this mode. This ensures that the pads keep their configuration (direction, state, etc.) during sleep time. Upon wakeup, the boot PROM code is executed. The initial pad configuration (the one used before entering the Sleep Mode) needs to be restored by the software before resetting the PADS_RETENTION_EN bit, to avoid toggling the pads.

NOTE: Setting the ACS_BOOT_CFG_PADS_RETENTION_EN bit in the ACS_BOOT_CFG register disables all RSL15 input pads. Disabling an input pad causes its logic level state to go low internally. All digital interfaces with at least one input signal must be disabled before enabling pad retention in order to prevent a false interrupt from aborting Sleep Mode.

The RF block (the analog section of the RF block) can be disconnected from its supply through the ACS_VDDRF_CTRL register. The RF (the digital section of the RF block) and Baseband, CryptoCell, CM33-FPU, and CM33-Debug power domains can be powered down through the SYSCTRL_RF_POWER_CFG, SYSCTRL_CRYPTOCELL_PWR_CFG, SYSCTRL_FPU_PWR_CFG, and SYSCTRL_DBG_PWR_CFG registers, respectively.

The following wakeup sources are typically selected using the configuration registers of the ACS:

1. Wakeup through an external event on the GPIO[3:0] pads
2. Wakeup through an ACOMP event
3. Wakeup through the baseband timer
4. Wakeup through the RTC, clocked either by the internal 32 kHz RC oscillator or the 32 kHz XTAL oscillator. The sleep time can be programmed using the RTC configuration registers of the ACS (see Real Time Clock (RTC)).
5. Wakeup through a Sensor interface threshold event or FIFO full event.

Entering Sleep Mode starts the following sequence:

1. The system clock is stopped.
2. Reset is asserted unless the VDDC retention regulator is enabled.
3. All memories (flash, PROM and RAM) are isolated from the core (AND gates).
4. Memories are powered off. However, if the VDDM retention regulator is enabled, RAM instances enabled in the memory enable retention latches are put into retention mode.
5. The logic is disconnected from its supply unless the core retention regulator is enabled.
6. The baseband timer is disconnected from its supply unless the VDDT power switch is enabled; to do this, short VDDT to VDDC when VDDCRET is enabled, or to VDDACS when VDDCRET is disabled.
7. The RF block is disconnected from its supplies. Note that the RF block needs to be isolated manually if the VDDC retention regulator is enabled.

8. The charge pump, VDDC, VDDM, and VDDRF regulators are disabled.

NOTE: To keep the charge pump enabled during Sleep Mode, set the ACS_SLEEP_MODE_CFG_VDDCP_ENABLE bit of the ACS_SLEEP_MODE_CFG register.

9. The VCC regulator converter is disabled.

NOTE: To keep the bandgap enabled during Sleep Mode, set the ACS_SLEEP_MODE_CFG_BG_ENABLE bit of the ACS_SLEEP_MODE_CFG register.

10. The bandgap is disabled.

NOTE: To keep the bandgap enabled during Sleep Mode, set the ACS_SLEEP_MODE_CFG_BG_ENABLE bit of the ACS_SLEEP_MODE_CFG register.

At wakeup, the following sequence restarts the system:

1. Eight LSBs of the RTC counter are captured in the ACS_WAKEUP_STATE_RTC_VALUE bit field of the ACS_WAKEUP_STATE register, to record the wakeup time.
2. The bandgap is enabled.
3. The other regulators are set according to the configuration registers when VDDC and VDDM are ready.
4. Memories are powered back on when VDDM is ready.
5. The wakeup delay is applied.
6. Memory isolation is removed.
7. The system clock is enabled.
8. The digital reset is released, enabling boot PROM execution, unless the core retention regulator has been enabled.

### Smart Sense Mode

Smart Sense Mode is a low power features that can work as a subset of the Sleep Mode configurations. Smart Sense takes advantage of the low power capability of Sleep Mode, while keeping some digital and analog peripherals, including the ultra low power (ULP) data acquisition subsystem, active with minimal processor intervention. Smart Sense allows an RSL15 device not only to remain responsive to external events, but also to monitor and acquire data from external sensors with very low system-level power consumption. Typical wakeup sources used with Smart Sense Mode include the analog comparitor (ACOMP) (see Analog Comparator) and the FIFO (FIFO_FULL) and threshold (THRESHOLD) sources from the ultra low power data acquisition subsystem (see Ultra-Low Power Data Acquisition Subsystem). If you are using Smart Sense Mode, we recommend configuring the ULP data acquisition subsystem to be clocked from STANDBYCLK for consistent data collection in both Run Mode and Smart Sense Mode.

## Standby Mode

Standby Mode can be used to reduce the average power consumption for short intervals of inactivity. In this mode, the logic and memories are not clocked, and are powered at a reduced voltage to minimize the leakage current. The ACS, bandgap, and digital regulator are active in this mode.

Most of the blocks can be powered down individually using memory-mapped registers.

The RF block (the analog section of the RF block) can be disconnected from its supply through the ACS_VDDRF_CTRL register. The RF (the digital section of the RF block) and Baseband, CryptoCell, CM33-FPU, and CM33-Debug power domains can be powered down through the SYSCTRL_RF_POWER_CFG, SYSCTRL_CRYPTOCELL_PWR_CFG, SYSCTRL_FPU_PWR_CFG, and SYSCTRL_DBG_PWR_CFG registers, respectively.

The reduced voltage level can be programmed in the STANDBY_VTRIM field of the ACS_VDDC_CTRL and ACS_VDDM_CTRL registers. (See Power Supply Registers.

Entering Standby Mode starts the following sequence:

1. The system clock is stopped.
2. All memories (flash, PROM and RAM) are isolated from the core (AND gates).
3. ROM and flash are powered off, and the relevant RAM instances are placed in retention mode.
4. The VDDC and VDDM regulators’ output voltages are set to their standby voltages.

At wakeup, the following sequence restarts the system:

1. Eight LSBs of the RTC counter are captured in the ACS_WAKEUP_STATE_RTC_VALUE field of the ACS_WAKEUP_STATE register, to record the wakeup time.
2. The VDDC and VDDM regulator output voltages are set to the normal voltage.
3. Memories are powered back on.
4. The wakeup delay is applied.
5. Memory isolation is removed.
6. Clock is enabled and system execution is resumed.

## Entering a Low Power Mode

NOTE: Wakeup event flags must be cleared before entering any Low Power Mode. If the application does not do this, and if a wakeup event flag is still set, the system wakes up immediately. These flags are not reset by the core watchdog reset. (See Power Modes.)

Entering any of the Low Power Modes is performed with these steps:

1. For Sleep Mode, if custom boot is selected in the ACS_BOOT_CFG_BOOT_SELECT field of the ACS_BOOT_CFG register, store the desired memory access configuration in the ACS_BOOT_GP_DATA register.
2. Enable the wakeup source if needed. For example, the GPIO[3:0] wakeup source can be enabled by setting the relevant ACS_WAKEUP_CFG_GPIO*_EN and ACS_WAKEUP_CFG_GPIO*_POL bits in the ACS_WAKEUP_CFG register. RTC alarm wakeup source can be enabled by configuring and enabling it through the ACS_RTC_CFG and ACS_RTC_CTRL registers (Real Time Clock (RTC)). Using the baseband timer as a wakeup source requires configuring the baseband controller registers.
3. Set an appropriate value of wakeup delay in the ACS_WAKEUP_CFG_DELAY field of the ACS_WAKEUP_CFG register.
4. Clear all sticky wakeup event flags in the ACS_WAKEUP_CTRL register. If this is not done, and if a wakeup event flag is still set, the system wakes up immediately.
5. For Sleep Mode, if the logic content is to be kept, set the ACS_VDDRET_CTRL_VDDCRET_ENABLE bit in the ACS_VDDRET_CTRL register (see Power Supply Registers).
6. For Sleep Mode, if RAM content is to be kept, set the ACS_VDDRET_CTRL_VDDMRET_ENABLE bit in the ACS_VDDRET_CTRL register, and configure the RAM instances to be kept in retention mode in the SYSCTRL_MEM_POWER_STARTUP and SYSCTRL_MEM_POWER_ENABLE registers (see Memory Registers).
7. For Sleep Mode, put the pads in retention mode by setting the ACS_BOOT_CFG_PADS_RETENTION_EN bit in the ACS_BOOT_CFG register.
8. For Sleep Mode, if the baseband low power timer is kept powered, enable the ACS_VDDRET_CTRL_VDDTRET_EN bit in the ACS_VDDRET_CTRL register (see Power Supply Registers).
9. For Sleep Mode, if the Arm CryptoCell-312 Always On is kept powered, enable the ACS_PWR_CTRL_CCAO_PWR_EN bit in the ACS_PWR_CTRL register.
10. For Sleep Mode, if the ULP data acquisition subsystem is kept powered, enable the ACS_PWR_CTRL_SENSOR_PWR_EN bit in the ACS_PWR_CTRL register.
11. Switch the SYSCLK to the RC oscillator by setting the ACS_RCOSC_CTRL_RC_FSEL field of the ACS_RCOSC_CTRL register to RC_OSC_3MHZ. If the RC oscillator is calibrated for a frequency different from 3 MHz, clear the ACS_RCOSC_CTRL_RC_FTRIM_FLAG field of the ACS_RCOSC_CTRL register, as it must be correct for the boot ROM.
12. For Standby Mode, including Smart Sense Mode, if the RFCLK needs to stay active (typically when the standby period is very short), leave the oscillator enabled and set the RFCLK clock divider in the RF block to Disabled (i.e., no clock output). If the RFCLK does not need to stay active (typically when the standby period is longer), disable it; it restarts after coming out of Standby Mode.
13. When entering Sleep Mode with the digital logic in retention (VDDCRET enabled), ensure that the RF is isolated (access bit disabled).
14. Write the relevant 32-bit power-down key in the ACS_PWR_MODES_CTRL register.
15. Put the CPU in power down mode with the WFI statement. This is required for entering all Low Power Modes.

IMPORTANT: The ACS waits for the Arm Cortex-M33 processor to enter into a WFI state before starting to enter into a low power mode. This has two potential impacts: If an interrupt is pending when the processor reaches the WFI instruction, the processor does not enter a WFI state and does not enter a low power mode. Execution continues with the instruction following the WFI statement. If no interrupt is pending when the processor reaches the WFI instruction, the processor enters a WFI state. The ACS then takes 4.5 system clock cycles to enter into a Low Power Mode. Due to this delay, a race condition results if an interrupt occurs during the first three cycles of this process, while the digital logic and memories are being placed into retention. If an interrupt occurs during this process, the Arm Cortex-M33 processor resumes, and if this interrupt occurred in the first three cycles there is a danger that the processor might access a powered down memory prior to going to a Low Power Mode. If the interrupt occurs in the fourth cycle, the processor does not have sufficient time to execute any instructions before transitioning to the low power mode. If this race condition is triggered it can cause either a bus or memory fault, or an access to a partially powered down memory, which would cause a corrupted memory access. To avoid this potential fault condition, follow the WFI instruction with at least three cycles of dummy operations that can be safely accessed while memories remain in retention, and which do not count on any data read for proper operation (as shown in the supplied firmware and sample code).

IMPORTANT: In the firmware sample code, the values defined by TWOSC (in the app.h file) is used to configure the time required for analog wake-up, 48 MHz XTAL oscillator stabilization, and execution of software wakeup functions. Those values (in microseconds) are based on use of the RSL15 EVBs, SDK sample code, and room temperature. You might need to tune the TWOSC value based on your system capacitance, characteristics of the 48 MHz XTAL oscillator, wakeup function execution time, sleep duration, and operating temperatures.

## Waking Up from a Low Power Mode

If an event occurs on any selected wakeup source while the system is entering a Low Power Mode, the system wakes up immediately.

After wakeup, by reading the ACS_WAKEUP_CTRL register, the Arm Cortex-M33 processor knows which event source has wakened the device up. (See Wakeup Sources for a list of wakeup sources.) In case another event occurs during the wakeup sequence, multiple wakeup source bits can be set when the ACS_WAKEUP_CTRL register is read. The read-only ACS_WAKEUP_STATE_WAKEUP_SRC field from the ACS_WAKEUP_STATE register indicates the first wakeup source that actually wakes up the system.

The ACS_BOOT_CFG register contains the ACS_BOOT_CFG_BOOT_SELECT field which, when configured to BOOT_FLASH_XTAL_DEFAULT_TRIM or BOOT_FLASH_XTAL_CUSTOM_TRIM by the software, tells the system (handled by the system booting software) to enable the RF block and start the 48 MHz XTAL oscillator directly after boot. This can reduce the boot time considerably, as the 48 MHz XTAL oscillator can have a long start-up time.

The ACS_BOOT_CFG_BOOT_SELECT field of the ACS_BOOT_CFG register, when configured to BOOT_CUSTOM by the software, tells the system (handled by the system booting software) to execute the code from the flash or RAM using information stored in a RAM instance that has been kept in retention. The VDDM retention regulator must be enabled to keep content of the RAM instance valid on wakeup. For details on how the wakeup from retention memory works, see Memory Instances.

The remaining ACS_BOOT_CFG_BOOT_SELECT configuration is BOOT_FLASH_XTAL_DISABLE, which does not configure the 48 MHz XTAL oscillator, and boots the system like the first boot.

The ACS_BOOT_CFG register contains the ACS_BOOT_CFG_BOOT_ROT_BYPASS bit. If this bit is set, the boot ROM skips the Root of Trust procedure when coming out of a Low Power Mode. This bit then needs to be reset by writing 0 to it.

Finally, the ACS_BOOT_CFG register contains the ACS_BOOT_CFG_BOOT_PWR_CAL_BYPASS bit. If this bit is set, the boot ROM skips the calibration of the analog part when coming out of a Low Power Mode. The bit then needs to be reset by writing 0 to it.

The ACS_WAKEUP_CFG_DELAY field in the ACS_WAKEUP_CFG register defines a delay from the point when VDDC and VDDM are ready to the point when the digital reset is released; this delay ensures that VDDC and VDDM have sufficient time to settle into a safe operating voltage before full wakeup.

IMPORTANT: For proper operation, the ACS_WAKEUP_CFG_DELAY field in the ACS_WAKEUP_CFG register must be set to a minimum delay of WAKEUP_DELAY_2.

## Wakeup Sources

The following are the wakeup sources that can wake RSL15 from a Low Power Mode:

- GPIO[3:0] pad
- Baseband timer
- RTC timer alarm, RTC clock tick, and RTC overflow errors
- DC-DC overload
- Analog comparator
- ULP data acquisition subsystem FIFO full
- ULP data acquisition subsystem threshold

## Adding a Low-Power Mode to an Existing Application

The Hardware Abstraction Layer provides a library for the Power Modes and a library for the Low-Power Clocks. These libraries can be used to conveniently add a Low Power Mode to an existing application. The following steps describe how this can be done with the ble_peripheral_server sample application. Along the way, some details are provided to give a deeper understanding of how the Power Modes work, and what needs to be taken into account with them for different types of applications (e.g., applications that use Bluetooth vs those that do not use Bluetooth).

NOTE: All steps after Setting Up the Application can be performed in any order you chose. This guide presents the steps in a top-down approach, to provide a better understanding of how the changes are integrated into the existing application and what is required from the functions that are added, to ensure that the Low Power Mode functions correctly.

### Setting Up the Application

Before making changes to the application code, we need to set up our project with the libraries and files that we need:

1. While in the CMSIS-Pack Manager perspective of the onsemi IDE, copy the ble_peripheral_server sample application into your workspace, as shown in the "Power Modes" figure.

   Figure: Copying the Project into the Workspace

2. Open the ble_peripheral_server.rteconfig file using the RTE Configuration Wizard and verify that the HAL libraries are selected, as shown in the "Power Modes" figure. This ensures that you can use the libraries for the Power Modes and the Low-Power Clock. Save the file if any changes have been made.

3. Create a new source and header file called app_lowpower.c and app_lowpower.h, respectively. These new files will hold all the application-level functions and settings that we'll need to add for the low-power state to work.

### Verifying the Linker Script

When a Low Power Mode with memory retention is used, the Power Modes library places the wakeup function into a specific memory section so that the library knows to call the function during the wakeup routine. For this process to work, the section name in the linker script must match the section name used by the Power Modes library. This can be a problem when you are adding a Low Power Mode to an application from an older SDK version, because the section name might have changed due to an SDK update. To verify that the section names match, perform the following steps:

1. Open the Power Modes source file, power_modes.c, and locate the prototype of the _Sys_PowerModes_WakeupFromRAM() function, as shown in the code example below. This function has a section attribute with a given name (e.g., .program_wakeup_subsection).

   static void _Sys_PowerModes_WakeupFromRAM(void) __attribute__((used)) __attribute__ ((section (".program_wakeup_subsection")));

2. Next, open the linker script file, sections.ld, and in the section definitions look for a section called .data, as shown in the code example below. Within the .data section, locate a subsection that matches the section name stated in the Power Modes library (e.g. .program_wakeup_subsection). If the subsection name in the linker script does not match the section name in the Power Modes library, rename one to match the other. Otherwise, the Power Modes library cannot function correctly.

   SECTIONS { ... .data : AT (__data_init__) { . = ALIGN(4); __data_start__ = .; *(.data_begin .data_begin.*) *(.program_wakeup_subsection) *(.data .data.*) *(.data_end .data_end.*) . = ALIGN(4); __data_end__ = .; } >DRAM ... }

   NOTE: When using the onsemi IDE, the power_modes.c and sections.ld files can be found in the RTE/Device/RSL15/ directory of the project.

   NOTE: When using the IAR IDE, the power_modes.c file can be found in the CMSIS-Pack/Device.Libraries.HAL.source/ directory, and the sections.icf file can be found in the CMSIS-Pack/Device.Startup.Startup.source/ directory of the project.

   NOTE: When using the Keil IDE, the power_modes.c and sections.sct files can be found in the RSL15/Device/ directory of the project.

### Updating the Application's Control Flow

The additions made for the Low Power Mode are mostly contained in the new source file we created. However, integration with the existing application needs to be done in the main application file, app.c. The integration involves initializing the required components and updating the flow of the main spin loop to attempt to enter a low-power state whenever possible. Open app.c and make the following changes:

1. Include the new header file we just created. This will allow you to access the functions that is created later.

   #include "app_lowpower.h"

2. In the main function, add a function call to App_LowPower_Init(). This function call needs to be placed between the function call for the GPIO initialization, App_GPIOInit(), and re-enabling the interrupts and exceptions with the call to App_EnableInterrupts().

   int main(void)

   {

   ...

   /* Configure the application's power mode and wakeup sources */

   App_LowPower_Init(

   &App_MainLoop,

   &App_LowPower_SavePeripheralStates,

   &App_LowPower_RestorePeripheralStates

   );

   ...

   }

3. In the function call to App_LowPower_Init(), add the three function pointers &App_MainLoop, &App_LowPower_SavePeripheralStates, and &App_LowPower_RestorePeripheralStates. The first pointer is for the main spin loop and the other two are defined by us later.

4. In the main spin loop, App_MainLoop(), add a conditional to the Bluetooth kernel processing to make sure that the Bluetooth baseband is awake and ready to perform the processing. Add a function call to App_LowPower_SleepCheck() (to be defined later); this function attempts to place the system into a low-power state once the kernel processing is complete. Lastly, if the baseband is not awake we can place the system into Idle Mode.

   static void App_MainLoop(void)

   {

   while (1)

   {

   /* Refresh the watchdog timer */

   SYS_WATCHDOG_REFRESH();

   if (BLE_Baseband_Is_Awake())

   {

   /* Perform pending Bluetooth processes */

   BLE_Kernel_Process();

   /* Check if the system is ready to go to sleep, do so if possible */

   App_LowPower_SleepCheck();

   }

   else

   {

   /* Go into Idle Mode and wait for an interrupt */

   __WFI();

   }

   }

   }

   NOTE: When adding a Low Power Mode to an application that does not feature Bluetooth, you can skip the check to see if the baseband is awake and the call to perform pending Bluetooth processes. The application can proceed directly with the App_LowPower_SleepCheck() function call.

### Configuring the Low-Power Module

For the Low Power Mode to work with the application, we first need to configure these things:

- The low-power clock
- The wakeup sources
- The general low-power configuration structure

In this example, we keep everything as basic as possible and use default configurations as much as we can.

Add the following to the app_lowpower.c module and its header file:

1. Include the libraries and files needed, as follows: The module's header, app_lowpower.h, stores the function prototypes. The app_init.h header is required for the definition of the clock frequencies that are used by the application. The lowpower_clock.h library allows us to initialize the low-power clock, and the power_modes.h will give us access to the default low-power configuration structure that we'll modify for our application. It also gives us access to the Sys_PowerModes_SetWakeupConfig() function needed to set the wakeup sources that we want to use.

   /* Device and library headers */

   #include <ble_abstraction.h>

   #include <lowpower_clock.h>

   #include <power_modes.h>

   /* Application headers */

   #include "app_init,h"

   #include "app_lowpower.h"

   NOTE: For applications that do not use Bluetooth, the Bluetooth stack and libraries are not needed nor selected. The ble_abstraction.h header can be omitted for those applications.

2. Define the initialization function that we added to app.c and add a prototype for the function to the app_lowpower.h header file.

   void App_LowPower_Init(AppResumeAddress_t p_app_addr, AppPeripheralFunc_t p_save_

   peripherals, AppPeripheralFunc_t p_restore_peripherals)

   { ...

   }

3. In the new function, add a call to Sys_LPClock_Init(). This initializes the XTAL32K low-power clock that is used by the system in a Low Power Mode. If desired, you could change the clock configuration and source, but we recommend that you use the default settings when getting started with the EVB.

   void App_LowPower_Init(AppResumeAddress_t p_app_addr, AppPeripheralFunc_t p_save_

   peripherals, AppPeripheralFunc_t p_restore_peripherals)

   {

   /* Configure and initialize the system low-power clock to default values */

   Sys_LPClock_Init();

   ...

4. Next we need to configure the wakeup sources we want to use. Since this application is Bluetooth enabled, any processes that use the kernel timer are automatically considered a wakeup source and do not require further configuration. For our example, we add GPIO0 as a wakeup source, which means it is not used for any other purpose. Reset GPIO0 to the default settings and disable its interrupt. The wakeup events triggered by GPIO0 are also captured while the system is in Run Mode, so you do not need to use the interrupt for the pin.

   /* Disable and reset the GPIO back to its default settings */

   SYS_GPIO_CONFIG(GPIO0, (GPIO_MODE_DISABLE | GPIO_LPF_DISABLE | GPIO_WEAK_PULL_

   UP | GPIO_6X_DRIVE));

   /* Disable the NVIC interrupt for GPIO0. Since GPIO0 is used as a wakeup

   source, it will be captured

   * by the WAKEUP_IRQn interrupt which works during the low-power mode and the

   regular Run Mode. */

   NVIC_DisableIRQ(GPIO0_IRQn);

   ...

   NOTE: If the application enables the GPIO interrupts somewhere else in the code (e.g. during the initialization of the rest of the system), make sure to remove those statements so that the interrupt is not re-enabled after the low-power initialization is complete.

5. Now we customize the low-power configuration structure to suit our application. We are using Sleep Mode with Memory Retention and have GPIO0 as our only wakeup source (besides the baseband). The configuration structure is initialized by the Power Modes library with commonly used settings for this type of mode, so our modifications will be minimal. See the Power Modes library for more details about the default configuration and the different settings.

   1. Set the three function pointers that are passed as parameters into the function. The first pointer, to App_MainLoop, is set to app_lowpower_mode_cfg.p_app_resume. When the system wakes from a low-power state, it first restores crucial components that are required for Run Mode, including initializing the peripherals that we are using (see the next step in this walk-through). With crucial components running, it then services interrupts that have been triggered, including the WAKEUP_IRQn that is triggered by wakeup sources during a low-power mode. Once the interrupts have been serviced, the system resumes running the application from the address we have provided to app_lowpower_mode_cfg.p_app_resume. In this case, we want it to continue running through the application's main spin loop.

      /* Application will resume from this address after wakeup from BOOT_CUSTOM

      with memory retention */

      app_lowpower_mode_cfg.p_app_resume = p_app_addr;

      ...

   2. The second and third pointers, to the App_LowPower_SavePeripheralStates and App_LowPower_RestorePeripheralState functions (not yet defined), are set to app_lowpower_mode_cfg.p_save_peripherals and app_lowpower_mode_cfg.p_restore_peripherals, respectively. These functions are called by the Power Modes library before it places the system into a low-power state and after waking up from a low-power state. The functions are intended to save and restore the states of any peripherals that are used by the application and that are reset when the system enters the low-power state. For example, if the system enters Sleep Mode with Memory Retention, the GPIO configuration is reset, so we need to save the values of the GPIO configuration registers to a set of variables and then restore those values in our save and restore functions. If we are using Standby Mode, the GPIO registers are not reset during the low-power state, so we do not need to save and restore the register values.

      /* Set the peripheral configuration functions that will be called before

      sleep and after wakeup */

      app_lowpower_mode_cfg.p_save_peripherals = p_save_peripherals;

      app_lowpower_mode_cfg.p_restore_peripherals = p_restore_peripherals;

      ...

   3. Set the boot configuration to BOOT_CUSTOM, which indicates to the Power Modes library that we want our application to resume running from the function we provided to app_lowpower_mode_cfg.p_app_resume (i.e. App_MainLoop).

      /* Set the boot configuration */

      app_lowpower_mode_cfg.boot_cfg = BOOT_CUSTOM;

      ...

   4. Set the clock configurations that are used to reconfigure the system clocks after waking up. These clock settings must match what our application was using before, so we are setting them using the values defined in app_init.h.

      /* Set the clock configurations for use during Run Mode */

      app_lowpower_mode_cfg.clock_cfg.sensorclk_freq = SENSOR_CLK_HZ;

      app_lowpower_mode_cfg.clock_cfg.systemclk_freq = SYSTEM_CLK_HZ;

      app_lowpower_mode_cfg.clock_cfg.uartclk_freq = UART_CLK_HZ;

      app_lowpower_mode_cfg.clock_cfg.userclk_freq = USER_CLK_HZ;

      ...

   NOTE: For applications that do not use Bluetooth, we also need to set the flag indicating that Bluetooth is not being used, i.e., app_lowpower_mode_cfg.ble_present_flag = POWER_MODES_BLE_NOT_PRESENT;

   NOTE: If the baseband is not utilized in the application (e.g., Bluetooth is not used and the kernel timer is not used), VDDT retention can be disabled to reduce power consumption by setting app_lowpower_mode_cfg.vddret_ctrl.vddt_ret = VDDTRETENTION_DISABLE;

6. Initialize the wakeup sources that have been set (in our case the default sources) by calling Sys_PowerModes_SetWakeupConfig(). This function will need to be called whenever our wakeup sources change in the application. In our example, the wakeup sources remain static.

   /* Initialize the wakeup configuration */

   Sys_PowerModes_SetWakeupConfig(app_lowpower_mode_cfg.wakeup_cfg);

   ...

7. Clear the WAKEUP_IRQn interrupt, in case the interrupt is pending, and enable the interrupt.

   /* Clear any pending WAKEUP interrupt and enable the interrupt */

   NVIC_ClearPendingIRQ(WAKEUP_IRQn);

   NVIC_EnableIRQ(WAKEUP_IRQn);

   }

### Attempting to Enter a Low-Power State

Before the system can enter a low-power state, checks must be peformed to make sure that the system is ready for the transition.

1. Check if there is a swmTrace transmission in progress, and verify that the low-power clock is ready for use.

   The swmTrace check ensures that log messages are not abruptly cut off when the system goes to sleep. If we are not concerned about the log messages and just want the system to enter a low-power state as quickly as possible, then we can remove the check for the swmTrace (and possibly disable the swmTrace altogether).

   The low-power clock check is crucial if we are using the RC32K as our low-power clock source. In such a case, we need to periodically measure the clock frequency, and the system must remain in Run Mode until this measurement is complete and the low-power clock is ready again. If the XTAL32K is used as the low-power clock source (the default setting, which we are using in this example), then the clock remains in the ready state after it is first initialized, and the check is a bit superfluous (though still recommended).

   void App_LowPower_SleepCheck(void)

   {

   /* If not in the middle of a swmTrace UART transfer, allow the application /*

   /* to enter into a low-power mode */

   if (!swmTrace_txInProgress() && LPClock_state == LPCLOCK_READY)

   {

   ...

   }

   else

   {

   __WFI();

   }

   }

2. If the checks for swmTrace and the low-power clock fail, we can still place the system into Idle Mode, which conserves some power but keeps the system in a ready state.

3. If the checks for swmTrace and the low-power clock pass, we can check if the baseband is ready to be placed into a low-power state. To do so we first initialize a structure with our request for entering a low-power state, the minimum sleep duration, and the maximum sleep duration for the low-power state. The minimum sleep duration is measured in microseconds and the value must exceed the time required for the system to wake up from the low-power state. The maximum sleep duration is measured in multiples of 312.5 microseconds.

   /* If not in the middle of a swmTrace UART transfer, allow the application /*

   /* to enter into a low-power mode */

   if (!swmTrace_txInProgress() && LPClock_state == LPCLOCK_READY)

   {

   /* Define the parameters used for putting the device to sleep */

   struct ble_sleep_api_param_tag ble_sleep_api_param =

   {

   .app_sleep_request = 1, /* request to go to sleep */

   .max_sleep_duration = 96000, /* 30s = (96000 * 312.5us) */

   .min_sleep_duration = 3500, /* 3500us */

   }

   ...

   }

   NOTE: For non-Bluetooth applications, the baseband check does not need to be performed, and as such this structure does not need to be defined.

4. Disable interrupts globally. This needs to be done before we can check the baseband. After the baseband check, enable the interrupts again.

   /* Checks for sleep have to be done with interrupts disabled */

   GLOBAL_INT_DISABLE();

   ...

   /* Checks for sleep have to be done with interrupts disabled */

   GLOBABL_INT_RESTORE();

5. Check if the baseband can be placed into a low-power state. Do this by calling the BLE_Baseband_Sleep() function and passing the structure that is defined with the minimum and maximum sleep durations. The function checks when the next baseband process is scheduled to occur (this includes any processes that are set to use the kernel timer and all Bluetooth processes). The function then cross-references the baseband process timing with the desired sleep interval. If the next process occurs before the minimum sleep duration, the baseband avoids entering the low-power state. If the next process occurs after the maximum sleep duration, the baseband sets the sleep time to the maximum duration that has been set. The function calculates the number of low-power clock cycles that it can go to sleep for, and assigns this number to ble_sleep_api_param.calculated_sleep_duration.

   /* Check if processor clock can be gated */

   switch (BLE_Baseband_Sleep(&ble_sleep_api_param))

   {

   ...

   }

   NOTE: For non-Bluetooth applications, this step and the next step can be skipped.

6. The BLE_Baseband_Sleep() function returns one of three states that indicates if the system can enter a low-power state.

   If RWIP_ACTIVE is returned, it means that there is an active baseband process and the system cannot enter a low-power state.

   If RWIP_CPU_SLEEP is returned, it indicates that the system can be placed into Idle Mode but must not be placed into a lower-power state. This is likely to occur if the next baseband process is scheduled to take place within the minimum sleep duration that we set, but there is no baseband process currently active.

   If RWIP_DEEP_SLEEP is returned, it mean that there are no active or pending baseband processes and the baseband is ready to enter a low-power state.

   case RWIP_DEEP_SLEEP:

   {

   /* The baseband is ready to be placed into a low-power mode. */

   ...

   break;

   }

   case RWIP_CPU_SLEEP:

   {

   /* The baseband cannot be placed into a low-power mode. Instead,

   let the CPU wait in Idle Mode

   * until the next task is read (i.e. wait for interrupt).

   __WFI();

   break;

   }

   case RWIP_ACTIVE:

   {

   /* Some activity is being process or pending; the system /*

   /* cannot enter a low-power mode. */

   break;

   }

7. Since our application is Bluetooth enabled and entering Sleep Mode, we need to save some RF registers before we enter the low-power state. This is not required when using Standby Mode.

   /* Save the RF registers before entering low-power mode. These */

   /* states need to be restored after wakeup */

   App_LowPower_RFSaveStates();

   ...

   NOTE: For non-Bluetooth applications, the RF is not used so this step can be skipped.

8. Call the Power Modes function and pass our modified configuration structure to enter the low-power state. The Power Modes library takes care of the remaining processing required to place the system into the chosen low-power state.

   /* Enter the low-power mode, as configured */

   Sys_PowerModes_EnterPowerMode(&app_lowpower_mode_cfg);

   NOTE: For non-Bluetooth applications, if the GPIO is the only wakeup source, the RTC clock and the system clock are not present, which can cause a reset generated by the clock detector. To avoid this, the clock detector can be disabled by setting the ACS_CLK_DET_CTRL_RESET_IGNORE bit from the ACS_CLK_DET_CTRL register, and resetting the ACS_CLK_DET_CTRL_ENABLE bit from the same regiersr, before entering the low-power mode.

### Handling Wakeup Sources

Besides the baseband processes (e.g. the Bluetooth processes and the kernel timer processes), the application can also be configured to wake up earlier based on several available wakeup sources. The most commonly used wakeup sources include receiving an input signal on a GPIO, the FIFO becoming full, an RTC timer event, and a sensor input threshold event. For our application, we want to be able to handle GPIO wakeup events, and we need to define the WAKEUP_IRQHandler() to do so. Since the wakeup interrupt handler is called first, before the system returns to the App_MainLoop(), we need to do some basic re-initialization to ensure the system functions as we wish. This interrupt handler is also called whenever a baseband event occurs, so we can use this to re-initialize anything needed for baseband processes as well.

1. Refresh the system watchdog to avoid a timeout, re-initialize the swmTrace to re-enable logging messages, and restore the RF registers saved before entering the low-power state.

   void WAKEUP_IRQHandler(void)

   {

   SYS_WATCHDOG_REFRESH();

   /* Re-initialize the swmTrace on wakeup */

   App_SWMTraceInit();

   /* Restore the VDDPA settings to keep the TX power consistent */

   App_LowPower_RFRestoreStates();

   ...

   NOTE: For non-Bluetooth applications, the RF is not used, so the registers do not need to be restored.

2. Check if the wakeup event has occurred from one of our configured sources. Since the same interrupt occurs for all of the wakeup sources, we need to check each one individually and perform the corresponding actions. It is also possible for multiple wakeup events to be set (e.g. a GPIO input is provided just as the FIFO becomes full), so make sure to check each in a separate if block.

   ...

   /* Check for a GPIO0 wakeup event */

   if (ACS->WAKEUP_CTRL & WAKEUP_GPIO0_EVENT_SET)

   {

   /* Clear the flag for the GPIO wakeup event */

   WAKEUP_GPIO0_FLAG_CLEAR();

   /* Perform the associated processing */

   Sys_GPIO_Toggle(USER_LED_GPIO);

   }

   ...

3. If the wakeup event has occurred for our wakeup source, we need to clear the wakeup flag and process the event. In our case, the GPIO wakeup event toggles the EVB's green LED on GPIO8.

4. At the end of the interrupt handler we can place a safety check that resets the interrupt flag, so that the interrupt handler is called again if there is a pending wakeup event. This is a good check to add because it ensures that no events are missed, especially if they are set during the processing of another event.

   ...

   /* Check if any wakeup events are still pending and reset the IRQ handler */ if (ACS->WAKEUP_CTRL && (!NVIC_GetPendingIRQ(WAKEUP_IRQn)))

   {

   NVIC_SetPendingIRQ(WAKEUP_IRQn);

   }

   }

   NOTE: If you forget to reset the wakeup flags for the processed events or forget to processes events altogether, then the interrupt is continuously reset by the statement above, and the system becomes stuck, unable to continue other tasks or re-enter the low-power state.

### Saving and Restoring RF Register States

The function calls to save and to restore the RF registers before entering a low-power state and after waking up, respectively, have already been added in the previous steps. Now we need to define those functions and the variables to hold the values while the system is in the low-power state.

NOTE: For non-Bluetooth applications, the RF is not used, so these steps can be skipped.

1. Define a set of global variables to store the register values.

   /* Storage variables for the RF registers */

   static uint32_t vddpa_ctrl = 0;

   static uint32_t vddrf_ctrl = 0;

   static uint8_t vddpa_power = 0;

   static SYSCTRL_VDDPA_CFG0_Type vddpa_cfg0 = {0};

2. Define the RF save function.

   /* Save the RF registers */

   static void App_LowPower_RFSaveStates(void)

   {

   vddrf_ctrl = ACS->VDDRF_CTRL;

   vddpa_ctrl = ACS->VDDPA_CTRL;

   vddpa_power = RF0_REG1A->PA_PWR_PA_PWR_BYTE;

   vddpa_cfg0 = (*(SYSCTRL_VDDPA_CFG0));

   }

3. Define the RF restore function.

   /* Restore the RF settings */

   static void App_LowPower_RFRestoreStates(void)

   {

   if (vddrf_ctrl != 0)

   {

   /* Restore VDDRF supply without changing trimming settings */

   Sys_ACS_WriteRegister(&ACS->VDDRF_CTRL, vddrf_ctrl);

   /* Wait until VDDRF supply has powered up */

   while (!(ACS->VDDRF_CTRL & VDDRF_READY))

   {

   /* Waiting */

   }

   /* Restore VDDPA */

   Sys_ACS_WriteRegister(&ACS->VDDPA_CTRL, vddpa_ctrl);

   RF0_REG1A->PA_PWR_PA_PWR_BYTE = vddpa_power;

   (*(SYSCTRL_VDDPA_CFG0)) = vddpa_cfg0;

   }

   }

### Saving and Restoring Peripheral States

In the previous steps we have made several mentions of saving and restoring peripheral states. Similar to the save and restoring for the RF registers, we need to define functions that save and restore the peripheral registers that are used by our application. However, unlike the RF registers, the peripheral state functions are handled by the Power Modes library, which is why we provide pointers to these functions in the configuration structure.

These save and restore functions need to be customized heavily to match the application that they are created for and the low-power state that the system is trying to enter. For example, if the application uses LSAD and enters Sleep Mode with Memory Retention, then we need to save and restore the LSAD registers. However, if the application uses LSAD and enters Sleep Mode with Core Retention, then the LSAD registers are retained and we do not need to save and restore the registers. In our example, the application uses GPIO and Sleep Mode with Memory Retention, so we need to save and restore the GPIO registers.

1. Define a set of global variables to store the GPIO register values, and define a save function that assigns the register values to those variables.

   /* Storage variables for the GPIO registers */

   static uint32_t gpio_cfg[GPIO_PAD_COUNT] = {0};

   static uint32_t gpio_output = 0;

   static uint32_t gpio_jtag_sw_pad_cfg = 0;

   /* Save the GPIO registers */

   void App_LowPower_SavePeripherals(void)

   {

   for (uint8_t i = 0; i < GPIO_PAD_COUNT; i++)

   {

   gpio_cfg[i] = GPIO->CFG[i];

   }

   gpio_output = GPIO->OUTPUT_DATA;

   gpio_jtag_sw_pad_cfg = GPIO->JTAG_SW_PAD_CFG;

   }

2. Define a restore function that reads the global variable values and re-assigns them back to the GPIO registers.

   /* Restore the GPIO registers */

   void App_LowPower_RestorePeripherals(void)

   {

   for (uint8_t i = 0; i < GPIO_PAD_COUNT; i++)

   {

   GPIO->CFG[i] = gpio_cfg[i];

   }

   GPIO->OUTPUT_DATA = gpio_output;

   GPIO->JTAG_SW_PAD_CFG = gpio_jtag_sw_pad_cfg;

   }

The modifications to the ble_peripheral_server sample application are now complete, and the application is set to enter a low-power state. If we run the application on an EVB, we can measure the current consumption and confirm that it is entering the low-power state, and we can confirm that GPIO0 causes a wakeup event and toggles the green LED whenever pressed.