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

115 lines
6.2 KiB
Markdown

# 0x06 ADC Rust Driver
This repository contains a Bare-Metal Rust driver for the ADC (Analog-to-Digital Converter) peripheral on the **RP2350** (and RP2040) microcontrollers.
It includes:
- A thin demo (`src/main.rs`) that continuously reads the analog voltage on GPIO 26 and the internal chip temperature, printing both over UART.
- A reusable library module (`src/adc.rs`) providing a hardware-agnostic `adc_lib` with helper math to map raw ADC counts to millivolts and degrees Celsius.
- 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 ADC mathematical conversion 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 the ADC (for reading analog inputs).
* **`run(...)`**: The master setup function. Calls the helper initialization functions below, prints an announcement over UART, and enters the infinite ADC polling 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_adc(...)`**: Initializes the hardware ADC peripheral, allocates `GPIO 26` as an analog input pin, and claims the internal chip temperature sensor.
* **`adc_loop(...)`**: Loops infinitely, reading the latest voltage and temperature, formatting the line, transmitting it over UART, and sleeping for `POLL_MS` (500ms).
* **`read_adc(...)`**: Queries the hardware ADC peripheral for the current 12-bit analog counts for both `GPIO 26` and the internal temp sensor, then passes those raw counts to the `adc_lib` math helpers to convert them to millivolts (mV) and degrees Celsius.
* **`format_adc_line(...)`**: Safely constructs a formatted string like `ADC0: 1650 mV | Chip temp: 34.5 C\r\n` without using the heap or standard library formatting mechanisms, relying instead on custom integer-to-ascii division algorithms (`write_mv_digits`, `write_temp`, etc.).
### 3. The Reusable ADC Math Library (`src/adc.rs`)
The hardware ADC returns a raw 12-bit number between `0` and `4095`. This number represents a ratio of the measured voltage compared to the ADC reference voltage (which is 3.3V, or 3300mV).
* **`raw_to_mv(...)`**: Linearly maps a 12-bit ADC result (`0..=4095`) to a voltage in millivolts (`0..=3300`).
* **`raw_to_celsius(...)`**: Takes a 12-bit reading from the internal temperature sensor (Channel 4) and applies the datasheet-specified polynomial equation to yield the core temperature in degrees Celsius as a floating-point number.