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
182: RTIC Framework Basics
Think of RTIC (Real-Time Interrupt-driven Concurrency) like running a professional high-end kitchen. In a home kitchen, you might just do one thing at a time—boil the pasta, then chop the onions. But in a pro kitchen, everything is event-driven. The timer for the soufflé goes off (an interrupt), a waiter yells that a table is ready (another interrupt), and the head chef decides who gets to use the one available sauté pan (resource management).
The magic of a pro kitchen isn't just speed; it's priority. If the fish is burning, the chef doesn't care that the salad is being plated; the fish gets immediate attention. RTIC brings this exact philosophy to your microcontroller. Instead of a giant loop {} where you're constantly polling flags and hoping you didn't miss a button press, RTIC lets you define specific tasks that trigger only when they're needed, with strict priorities to ensure the most critical code always wins.
Mapping the Kitchen to the Code
When you look at an RTIC application, you'll see a structure that differs from a standard main function. Here is how the analogy maps to the framework:
- The Kitchen Layout (The
#[app]macro): This is where you define your entire system—what hardware you're using and how it's wired. - The Pantry (Shared Resources): These are variables or peripherals (like a GPIO pin or an I2C bus) that multiple tasks need to access. In RTIC, these are protected so you can't have two tasks fighting over the same "pan" at the same time.
- The Chef's Private Kit (Local Resources): These are variables that only one specific task can touch. No locking is required because no one else has the key to that drawer.
- The Order Tickets (Tasks): These are your functions. Some are "hardware tasks" (triggered by a timer or a pin change) and some are "software tasks" (triggered by other parts of your code).
Handling Shared Resources Without the Headache
In standard embedded Rust, sharing a peripheral between the main loop and an interrupt usually involves static mut and a lot of unsafe blocks, or a heavy Mutex. It's clunky. RTIC handles this via a "priority-based locking" mechanism.
#[rtic::app(device = stm32f4xx_hal::pac, dispatchers = [EXTI0])]
mod app {
#[shared]
struct Shared {
// This is our "shared pan"
led_status: bool,
}
#[local]
struct Local {
// This is the "private kit"
button: gpio::Pin,
}
#[init]
fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
// Setup hardware here...
(Shared { led_status: false }, Local { button }, init::Monotonics())
}
// A high-priority hardware task
#[task(binds = EXTI1, shared = [led_status], priority = 2)]
fn on_button_press(mut cx: on_button_press::Context) {
// To access the shared resource, we use a lock
cx.shared.led_status.lock(|status| {
*status = !*status;
});
}
}
Notice the .lock(|status| { ... }) call. This is where RTIC shines. Instead of a traditional mutex that might block your whole program, RTIC uses the microcontroller's own priority levels. If a task with priority 2 locks a resource, the system temporarily prevents any other task that *also* uses that resource from preempting it. It's an incredibly efficient way to guarantee data safety without the overhead of a full OS.
The Dance of Software and Hardware Tasks
I want to point out a distinction that often trips people up: the difference between binds = ... and software tasks. A hardware task is bound to a specific interrupt vector (like a timer or a GPIO pin). It's the "Order Ticket" that arrives from the outside world.
A software task, however, is something you trigger manually. Imagine the "fish burning" interrupt triggers a hardware task, which then spawns a software task to "alert the manager." You do this using the spawn method. This keeps your interrupt handlers lean—you do the bare minimum in the hardware task and push the heavy lifting to a lower-priority software task so you don't block other critical interrupts.
📋 Practical Task
Build a Priority-Based Emergency Stop System
You are tasked with implementing a safety system for a motor controller using RTIC. The system must handle two different inputs with different priorities.
Requirements:
- Shared Resource: Create a shared boolean called
motor_enabled. - High Priority Task: Create a hardware task bound to an
EmergencyStopinterrupt (priority 3). When triggered, it must immediately setmotor_enabledtofalse. - Low Priority Task: Create a hardware task bound to a
UserToggleinterrupt (priority 1). When triggered, it should toggle themotor_enabledstate using a.lock(). - The Logic: Ensure that if the
UserToggleis currently locking the resource, theEmergencyStopcan still preempt it (or handle the locking priority correctly) to ensure the motor stops as fast as possible.
Deliverable: Write the RTIC #[app] module structure including the Shared and Local structs, the init function, and the two task handlers described above. You don't need to provide the full HAL initialization code—you can assume the peripherals are passed in via the context.
There are no comments for now.