Skip to Content
Course content

184: Power Management in Embedded Rust

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this a dozen times. You spend weeks perfecting your logic, the device works flawlessly on your desk while plugged into your debugger, but the moment you move to a LiPo battery, your "low power" device dies in four hours instead of four months. You check your code, see that you aren't doing any heavy calculations, and you're left wondering where the current is going.

// A typical "waiting for data" loop that kills batteries
loop {
    if sensor.data_ready() {
        let val = sensor.read();
        process(val);
    }
    // The developer thinks: "I'm not doing anything here, so it's low power!"
}

The Battery-Killing Busy Loop

The code above looks innocent. If sensor.data_ready() is false, the CPU isn't "doing" anything useful. But from the hardware's perspective, the CPU is running at full clock speed, fetching the if instruction, evaluating the branch, and jumping back to the start of the loop millions of times per second. You're essentially running a heater that occasionally checks a sensor.

When I first started with embedded Rust, I assumed the compiler might optimize this into some kind of sleep state. It doesn't. Rust guarantees that your code does exactly what you wrote, and what you wrote is a high-frequency polling loop. To actually save power, we have to tell the hardware to stop the clock to the CPU core entirely until something interesting happens.

Putting the Core to Sleep with WFI

In the ARM Cortex-M world (which most of our Rust crates target), the magic instruction is WFI—Wait For Interrupt. In Rust, we access this via cortex_m::asm::wfi(). This instruction tells the processor to enter a sleep state where the CPU clock is gated. The core stops executing instructions, and power consumption drops orders of magnitude.

use cortex_m::asm;

loop {
    if sensor.data_ready() {
        let val = sensor.read();
        process(val);
    } else {
        // Stop the CPU here. It will wake up only when an interrupt triggers.
        asm::wfi();
    }
}

Wait—there's a catch. If you just call wfi(), the CPU will wake up on any interrupt. If you have a system timer firing every millisecond, your CPU will wake up every millisecond, check the sensor, and go back to sleep. You're still wasting energy. The real trick is ensuring that only the interrupts you care about are enabled, or that your interrupt handlers are lean enough to let the CPU get back to sleep quickly.

Managing Peripheral Power Leakage

Stopping the CPU is only half the battle. I've had projects where the CPU was sleeping, but the board was still pulling 5mA because a UART peripheral was still clocked and idling. This is "leakage."

In Rust, we handle this by being explicit about peripheral ownership. If you aren't using a peripheral, don't just leave it in its default state. Many HALs provide methods to disable the clock to specific peripherals. If your HAL doesn't have a high-level disable() method, you'll need to hit the RCC (Reset and Clock Control) registers directly to turn off the clock gate for that peripheral.

I usually recommend a "Power-Down Checklist" before calling wfi():

  • Are all unused GPIO pins configured as Analog or Pull-down? (Floating pins can oscillate and draw current).
  • Are the high-speed oscillators disabled if the low-power timer is sufficient?
  • Are the peripherals that aren't needed for the wake-up event powered down?

Power management is less about writing "clever" code and more about understanding the hardware's state machine. You are no longer just writing a program; you are managing a power budget.




📋 Practical Task

Build a Low-Power Periodic Heartbeat LED

Your goal is to create a program that flashes an LED once every 5 seconds, but keeps the CPU in the lowest possible power state between flashes. You cannot use a busy-wait delay() function.

Requirements:

  • Configure a hardware timer (like the SysTick or a general-purpose timer) to trigger an interrupt every 5 seconds.
  • In the main loop, use cortex_m::asm::wfi() to put the processor to sleep.
  • Implement the Interrupt Service Routine (ISR) to toggle the LED pin and then immediately return, allowing the CPU to go back to sleep.
  • Ensure that no other interrupts are active that would cause "spurious" wake-ups.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.