Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
184: Power Management in Embedded Rust
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.
There are no comments for now.