Power Manager Usage Scenarios#

Scenario 1: Asynchronous Sensor Reading with Power Management#

This scenario demonstrates fundamental Power Manager usage in a bare-metal application that implements asynchronous sensor reading to optimize power consumption. The key principle is that EM1 requirements are only active while the sensor hardware is actually busy.

Application Requirements:#

  • Periodic sensor readings every 5 seconds triggered by timer

  • Asynchronous sensor operation with non-blocking data acquisition

  • EM1 requirement only during actual sensor hardware operation (~10ms)

  • Optimal power management allowing deep sleep when sensor is idle

  • State-driven power requirements based on sensor activity

Implementation Strategy:#

  1. Initialization: Power Manager automatically initialized via sl_main framework

  2. Timer-Based Operations: Use sleep timer for periodic wake-ups

  3. Requirement Management: Add EM1 requirement during sensor operations

  4. Optimal Sleep: Allow deep sleep (EM2/EM3) between operations

  5. Non-Blocking Design: Main loop remains responsive for other system tasks during sensor operation

Expected Power Behavior:#

  • Deep Sleep (EM2/EM3): 99% of the time between sensor readings

  • Sleep (EM1): Only during the ~10ms sensor reading operation

  • Wake-up Events: Sleep timer every 5 seconds

  • Power Optimization: Automatic selection of lowest possible energy mode

Complete Implementation:#

#include "sl_main_init.h"
#include "sl_main_process_action.h"
#include "sl_power_manager.h"
#include "sl_sleeptimer.h"
#include <stdio.h>

sl_sleeptimer_timer_handle_t periodic_timer;
sl_sleeptimer_timer_handle_t sensor_timer;
bool sensor_reading_pending;
uint32_t reading_count;
uint32_t reading;

// Simulated sensor reading function represents actual hardware sensor access
uint32_t read_temperature_sensor(void)
{
  return 2350 + (reading_count % 100);  // 23.50°C + variation
}

// Sensor completion callback runs in ISR context
void sensor_callback(sl_sleeptimer_timer_handle_t *timer, void *data)
{
  (void)timer;
  (void)data;

  // Sensor hardware has completed acquisition. Read the result
  reading = read_temperature_sensor();
  reading_count++;
  sensor_reading_pending = true;

  // Remove EM1 requirement as soon as sensor hardware is idle
  sl_power_manager_remove_em_requirement(SL_POWER_MANAGER_EM1);
}

// Simulated sensor start function
void start_temperature_sensor(void)
{
  // Add EM1 requirement to maintain system responsiveness during sensor acquisition
  // This ensures stable clocks and fast interrupt response while sensor is active
  sl_power_manager_add_em_requirement(SL_POWER_MANAGER_EM1);

  // Send command to start sensor (hardware-specific implementation would start here)

  // In real implementation, this would be replaced by hardware interrupt when conversion completes
  // NOTE: The callback sensor_callback is called after 10 ms to mimic the acquisition time
  sl_sleeptimer_start_timer_ms(&sensor_timer, 10, sensor_callback, NULL, 0, 0);
}

void app_init(void)
{
  // Configure periodic timer to trigger sensor readings every 5 seconds
  // Note: This only triggers the START of sensor reading, not the completion
  // The sensor reading itself is asynchronous and managed separately
  sl_sleeptimer_start_periodic_timer_ms(&periodic_timer, 5000, NULL, NULL, 0, 0);
}

void app_process_action(void)
{
  // Check if sensor data is ready for processing
  if (sensor_reading_pending) {
    sensor_reading_pending = false;

    // Process the completed sensor reading
    printf("Temperature: %lu.%02lu°C (reading #%lu)\n", reading / 100, reading % 100, reading_count);

    printf("Sensor reading complete, going to deep sleep (EM2)\n");
  } else {
    // No data pending - it's time to start a new sensor reading
    start_temperature_sensor();

    printf("Sensor reading in progress, going to sleep (EM1)\n");
  }
}

int main(void)
{
  sl_main_init();  // Power Manager automatically initialized here
  app_init();

  while (1) {
    sl_main_process_action();
    app_process_action();

    // Sleep at lowest allowed energy mode (EM2/EM3 when there are no requirements)
    sl_power_manager_sleep();
  }
}

Scenario 2: Ultra-Low Power EM4 Sleep with Data Retention#

This scenario demonstrates EM4 deep sleep operation with BURAM data persistence and with EM4 wakeup from BURTC and GPIO.

Application Requirements:#

  • Enter EM4 for maximum power savings

  • Periodic wake-up every 5 minutes via BURTC timer

  • Preserve critical data across EM4 resets using BURAM

  • Simple battery monitoring with wake-up counting

Implementation Strategy:#

  1. Initialization: Check if this is an EM4 wake-up or normal boot

  2. Data Management: Use BURAM registers to persist data across EM4 resets

  3. Timer Setup: Configure BURTC for periodic EM4 wake-ups

  4. EM4 Entry: Enter deepest sleep mode (system resets on wake-up)

Expected Power Behavior:#

  • EM4 Deep Sleep: Ultra-low consumption during 5-minute sleep periods

  • Simple State Management: Only two states: awake (brief) and EM4 sleep (majority of time)

  • Data Persistence: Wake count and battery data preserved across resets using BURAM

  • Reset-Based Operation: Each wake-up is actually a full device reset

Complete Implementation:#

// In sl_power_manager_config.h, configure the EM4 pin retention mode
#define SL_POWER_MANAGER_INIT_EMU_EM4_PIN_RETENTION_MODE EMU_EM4CTRL_EM4IORETMODE_SWUNLATCH
#include "sl_main_init.h"
#include "sl_main_process_action.h"
#include "sl_clock_manager.h"
#include "sl_power_manager.h"
#include "sl_hal_emu.h"
#include "sl_hal_burtc.h"
#include "sl_gpio.h"
#include "sl_hal_gpio.h"
#include <stdio.h>
#include <string.h>

#define BATTERY_CRITICAL_MV 2000

// Data retained across EM4 resets
typedef struct {
  uint32_t wake_count;    // Number of EM4 wake-ups
  uint32_t battery_mv;    // Battery voltage in millivolts
} buram_data_t;

static buram_data_t buram_data = { 0 };
static uint32_t reset_cause;

// Configure GPIO pin as input for EM4 wake-up
// This example uses GPIO PB1 as a button input
void init_gpio_em4(void)
{
  // Configure PB1 as input
  sl_gpio_set_pin_mode(PB1, SL_GPIO_MODE_INPUT, false);

  // Configure PB1 as EM4 wake-up source
  int32_t interrupt_number = sl_hal_gpio_get_em4_interrupt_number(PB1);
  sl_gpio_configure_wakeup_em4_interrupt(PB1, &interrupt_number, false, NULL, NULL);

  printf("GPIO PB1 configured for EM4 wake-up (button press)\n");
}

// Configure BURTC for EM4 wake-up every 5 minutes
void init_burtc(void)
{
  sl_clock_manager_enable_bus_clock(SL_BUS_CLOCK_BURTC);

  sl_hal_burtc_init_config_t config = {
    .debug_run = false,
    .clock_divider = 32768,
    .compare0_top = true,
    .em4_comparator = true,            // Enable EM4 wake-up for compare interrupt
    .em4_overflow = false
  };

  sl_hal_burtc_init(&config);
  sl_hal_burtc_enable();
  sl_hal_burtc_set_compare(300);       // 5 minutes at 32.768 kHz with 32768 divider
  sl_hal_burtc_enable_interrupts(BURTC_IF_COMP);
  sl_hal_burtc_start();
}

// Simulate battery voltage reading with degradation over time
uint32_t read_battery_voltage()
{
  // Simulate gradual battery discharge over multiple EM4 cycles
  // In a real application, use ADC
  return buram_data.battery_mv - 1;  // Voltage drops slowly
}

void normal_boot(void)
{
  // Initialize data structure
  buram_data.wake_count = 0;
  buram_data.battery_mv = 3000;  // Starting at 3.0 V

  // Configure a GPIO to wake-up from EM4
  init_gpio_em4();

  // Configure BURTC for EM4 wake-up every 5 minutes
  init_burtc();

  // Add a 1 second delay to let the debugger connect
  sl_sleeptimer_delay_millisecond(1000);
}

void em4_boot(void)
{
  // Restore data from BURAM after EM4 wake-up
  sl_clock_manager_enable_bus_clock(SL_BUS_CLOCK_BURAM);
  memcpy(&buram_data, &BURAM->RET[0], sizeof(buram_data));

  // Increment the number of wake-up
  buram_data.wake_count++;
  printf("Wake-up from EM4 #%lu\n", buram_data.wake_count);

  // Configure a GPIO to wake-up from EM4
  // Required as GPIO is not retained in EM4
  init_gpio_em4();

  // BURTC is retained in EM4 so no need to initialize it again
  // Resetting the counter to 0
  sl_hal_burtc_reset_counter();
}

// The interrupts caused by EM4 wake-up sources and the unlatching of pins
// Handle as early as possible
void app_init_pre_clock(void)
{
  // Check reset cause to determine boot type
  // Note: This only works without the bootloader. If using a bootloader, refer to specific documentation
  reset_cause = sl_hal_emu_get_reset_cause();
  sl_hal_emu_clear_reset_cause();

  if (reset_cause == EMU_RSTCAUSE_EM4) {
    sl_clock_manager_enable_bus_clock(SL_BUS_CLOCK_GPIO);
    sl_clock_manager_enable_bus_clock(SL_BUS_CLOCK_BURTC);

    // Reset the interrupts from EM4 wake-up sources
    int32_t interrupt_number = sl_hal_gpio_get_em4_interrupt_number(PB1);
    sl_gpio_deconfigure_wakeup_em4_interrupt(interrupt_number);
    sl_hal_burtc_clear_interrupts(BURTC_IF_COMP);

    // Set pins that need to always stay steady before unlatching pin retention
    sl_gpio_set_pin_mode(PB2, SL_GPIO_MODE_PUSH_PULL, true);

    // This unlocks pins from their EM4-preserved state back to peripheral control
    sl_power_manager_em4_unlatch_pin_retention();
  }
}

void app_init(void)
{
  // Branch based on wake-up source
  if (reset_cause == EMU_RSTCAUSE_EM4) {
    // EM4 wake-up path: restored from ultra-low power state
    em4_boot();
  } else {
    // Normal boot path: starts fresh initialization
    normal_boot();
  }
}

void app_process_action(void)
{
  if (buram_data.battery_mv <= BATTERY_CRITICAL_MV) {
    printf("CRITICAL: Battery too low, shutting down\n");
  } else {
    // Check the battery voltage
    buram_data.battery_mv = read_battery_voltage();
    printf("Battery: %lu mV\n", buram_data.battery_mv);

    // Do some processing...
  }

  // Save key variables to BURAM for persistence across EM4 reset
  sl_clock_manager_enable_bus_clock(SL_BUS_CLOCK_BURAM);
  memcpy(&BURAM->RET[0], &buram_data, sizeof(buram_data));

  // All GPIO outputs will be latched (preserved) when entering EM4
  // Power Manager's pin retention feature maintains this state during ultra-low power
  sl_gpio_set_pin_mode(PB2, SL_GPIO_MODE_PUSH_PULL, true);

  // Enters EM4 ultra-low power mode and never returns
  printf("Entering EM4 sleep\n");
  sl_power_manager_enter_em4();
}

int main(void)
{
  sl_main_init();  // Power Manager automatically initialized here
  app_init();

  while (1) {
    sl_main_process_action();
    app_process_action();

    // Sleep at lowest allowed energy mode (EM2/EM3 when there are no requirements)
    sl_power_manager_sleep();
  }
}