# 0x05 Servo Rust Driver This repository contains a Bare-Metal Rust driver for controlling standard 50Hz hobby Servos via PWM on the **RP2350** (and RP2040) microcontrollers. It includes: - A thin demo (`src/main.rs`) that repeatedly sweeps a servo horn back and forth between 0 and 180 degrees while printing the current angle over UART. - A reusable library module (`src/servo.rs`) providing a hardware-agnostic `servo_lib` with helper math to map angles to precise pulse-width microsecond targets. - 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 Servo mapping logic is separated into a reusable math 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 PWM (for servo control). * **`run(...)`**: The master setup function. Calls the helper initialization functions below, prints an announcement over UART, and enters the servo sweep loop. * **`init_clocks(...)`**: Wakes up the external 12 MHz crystal (`XOSC`) and configures the PLLs (Phase-Locked Loops) to drive the system clock at its maximum speed. * **`init_pins(...)`**: Takes control of physical pins across `IO_BANK0`. * **`init_uart(...)`**: Configures the hardware `UART0` peripheral to operate at a standard `115200` baud rate with an `8N1` configuration for debug printing. * **`init_delay(...)`**: Captures the ARM Cortex-M `SYST` (SysTick) peripheral to create a blocking delay timer. * **`init_servo_pwm(...)`**: Obtains `PWM Slice 3` and binds its `channel_a` to `GPIO 6`. Sets up the clock divider and TOP value to achieve a standard 50 Hz PWM frequency. * **`servo_loop(...)`**: Loops infinitely, sweeping the servo angle from 0 to 180 degrees and back down to 0. * **`sweep_angle_up(...)`** / **`sweep_angle_down(...)`**: Walks through angles in 10-degree increments. * **`apply_angle(...)`**: Computes the hardware register level using `servo_lib`, sets the new duty cycle, formats an `Angle: XXX deg` string without heap allocation, transmits it, and delays 150ms to allow the physical servo motor to arrive at the target angle. ### 3. The Reusable Servo Math Library (`src/servo.rs`) Unlike basic LEDs which simply vary duty cycle from 0-100%, Servos expect a precise 50 Hz signal (20ms period) where the "HIGH" time represents the angle: usually ~1ms for 0 degrees, ~1.5ms for 90 degrees, and ~2ms for 180 degrees. * **`calc_clk_div(...)`**: Calculates the precise floating-point clock divider required to step down the fast system clock so the counter overflows exactly 50 times per second. * **`angle_to_pulse_us(...)`**: Linearly maps an angle (0.0 to 180.0 degrees) to a pulse width (e.g. 1000 to 2000 microseconds). * **`pulse_us_to_level(...)`**: Translates the desired microsecond pulse into a raw integer counter compare-value for the PWM hardware registers. * **`clamp_pulse_us(...)`** / **`clamp_degrees(...)`**: Safety constraints to ensure we do not physically damage the servo by driving it past its mechanical limits.