Files
Embedded-Hacking/drivers/0x0d_timer_rust/README.md
T
Kevin Thomas f62db776e1 Initial commit
2026-07-06 14:32:12 -04:00

114 lines
5.9 KiB
Markdown

# 0x0d Timer Rust Driver
This repository contains a Bare-Metal Rust driver demonstrating **Hardware Timers** and **Periodic Execution** on the **RP2350** (and RP2040) microcontrollers.
It includes:
- A demo (`src/main.rs`) that starts a 1000ms repeating timer and prints a heartbeat message to the UART console.
- A reusable library module (`src/timer.rs`) providing a hardware-agnostic `timer_lib` containing the driver state machine and formatting helpers.
- Board initialization logic (`src/board.rs`).
## 🚀 Getting Started from Scratch
If you're starting with a fresh machine, follow these exact steps to install the toolchain, build the code, and flash it to your microcontroller.
### 1. Install Rust
First, install `rustup` (the Rust toolchain installer) if you haven't already:
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
*Note: Restart your terminal or run `source $HOME/.cargo/env` after this finishes.*
Ensure your Rust compiler is up to date:
```bash
rustup update
```
### 2. Install the Target Architecture
This project is configured for the **RP2350** (ARM Cortex-M33). We need to install the cross-compilation target for it:
```bash
rustup target add thumbv8m.main-none-eabihf
```
*(If you were targeting the RP2040, you would use `thumbv6m-none-eabi` instead).*
### 3. Install Build Tools
You will need a few extra tools to help link and format the firmware for the RP-series chips.
Install `flip-link` (adds zero-cost stack overflow protection):
```bash
cargo install flip-link
```
Install `picotool` (used by `cargo run` to flash the chip):
- **macOS:** `brew install picotool`
- **Linux/Windows:** Follow the official Raspberry Pi documentation to install `picotool` or build it from source.
### 4. Building the Code
To compile the code for the microcontroller, simply run:
```bash
cargo build
```
To build a highly optimized release version (smaller and faster):
```bash
cargo build --release
```
### 5. Flashing to the Microcontroller
This project is pre-configured in `.cargo/config.toml` to use `picotool` as the custom runner.
To flash the code:
1. Hold down the **BOOTSEL** button on your RP2350 board.
2. Plug it into your computer via USB (or press the RUN/RESET button while holding BOOTSEL).
3. Run the following command:
```bash
cargo run --release
```
*`cargo` will compile the code and automatically use `picotool` to upload the `.elf` file directly to your board and start executing it!*
### 6. Testing on the Host
Because the data manipulation and string formatting logic is separated into a reusable library without touching hardware registers, you can run the unit tests natively on your computer!
However, because this project sets a default bare-metal target (`thumbv8m.main-none-eabihf`) in `.cargo/config.toml`, running a plain `cargo test` will fail because the standard library doesn't exist on the microcontroller. You must explicitly tell Cargo to compile the tests for your host computer's processor architecture:
**Mac (Apple Silicon):**
```bash
cargo test --lib --target aarch64-apple-darwin
```
**Linux (Intel/AMD 64-bit):**
```bash
cargo test --lib --target x86_64-unknown-linux-gnu
```
**Windows (64-bit):**
```bash
cargo test --lib --target x86_64-pc-windows-msvc
```
## 🧠 Code Walkthrough
This section explains exactly how the code works, where the entry point is, and traces the flow of execution as if you were stepping through it line-by-line.
### 1. The Entry Point (`src/main.rs`)
Unlike a standard computer program, bare-metal microcontrollers do not have an operating system to call `main()`. Instead, we use the `#[entry]` macro from the HAL (Hardware Abstraction Layer) to define the very first function that runs after the chip boots up.
* **`main() -> !`**: This is the absolute start of our code. It takes ownership of all the hardware peripherals (`hal::pac::Peripherals::take().unwrap()`) and immediately passes them into `board::run(...)`. The `-> !` means this function never returns (because embedded devices run in an infinite loop).
### 2. Board Initialization (`src/board.rs`)
Once execution enters `board.rs`, we initialize the system clocks, pins, UART (for logging), SysTick (for delay), and the hardware Timer peripheral.
* **`run(...)`**: The master setup function. Calls the helper initialization functions, gets a handle to the `hal::Timer`, initializes the driver state, and enters the infinite `heartbeat_loop`.
* **`heartbeat_loop(...)`**: An infinite loop that polls the hardware timer's microsecond counter. It continuously checks if the elapsed time exceeds the configured period. When it does, it updates the checkpoint and fires the callback.
* **`tick_elapsed(...)`**: A helper function that handles the 32-bit wrapping arithmetic required to safely calculate elapsed microseconds from the free-running hardware counter.
* **`fire_heartbeat(...)`**: Called by the loop when the period elapses. It asks the driver state to record the fire event, and if active, formats and prints the heartbeat message over UART.
### 3. The Reusable Timer Library (`src/timer.rs`)
While `board.rs` directly polls the hardware counter registers, `timer.rs` tracks the logical state of our repeating timer and handles string formatting for logging.
* **`TimerDriverState`**: A struct that acts as a state machine. It stores whether the timer is `active`, the configured `period_ms`, and the total `fire_count`. It abstracts away the global variables commonly used in C SDKs.
* **`start(...)` / `cancel(...)`**: Methods to transition the driver state.
* **`on_fire(&mut self)`**: Called by the board shim when the hardware triggers. It increments the fire count and returns whether the timer should continue repeating.
* **`format_heartbeat(...)` / `format_started(...)`**: Helpers to generate the UART console messages.
* **`format_u32(...)`**: Implements custom `u32` to decimal ASCII string conversion without allocating dynamic memory, enabling `core` library compatibility (`no_std`).