mirror of
https://github.com/mytechnotalent/Embedded-Hacking.git
synced 2026-07-07 13:08:09 +02:00
115 lines
6.5 KiB
Markdown
115 lines
6.5 KiB
Markdown
# 0x08 LCD1602 Rust Driver
|
|
|
|
This repository contains a Bare-Metal Rust driver for the HD44780-based LCD1602 character display (interfaced via a PCF8574 I2C backpack) on the **RP2350** (and RP2040) microcontrollers.
|
|
|
|
It includes:
|
|
- A demo (`src/main.rs`) that displays a title on the first row and a continuously incrementing 6-digit counter on the second row.
|
|
- A reusable library module (`src/lcd1602.rs`) providing a hardware-agnostic `lcd1602_lib` with helper math and string formatting functions to assemble commands, DDRAM addresses, and zero-padded counter strings.
|
|
- 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 LCD command/nibble construction and string formatting 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 I2C peripheral (for LCD communication).
|
|
|
|
* **`run(...)`**: The master setup function. Calls the helper initialization functions below, triggers the LCD setup sequence, and enters the infinite counter loop.
|
|
* **`init_i2c(...)`**: Initializes the hardware `I2C1` peripheral, reconfigures `GPIO 2` and `GPIO 3` as `SDA` and `SCL` lines (with internal pull-ups enabled), and sets the clock speed to 100 kHz.
|
|
* **`lcd_hd44780_reset(...)` & `lcd_hd44780_configure(...)`**: Executes the strict timing requirements of the HD44780 datasheet to force the LCD into 4-bit mode (using three successive `0x03` nibbles followed by `0x02`), then configures a 2-line display, turns on the screen, and clears the RAM.
|
|
* **`lcd_write4(...)` & `lcd_send(...)`**: The core functions for pushing data to the screen. Because the I2C backpack (PCF8574) uses an 8-bit GPIO expander, we must send an 8-bit LCD command by breaking it into two 4-bit nibbles.
|
|
* **`pcf_pulse_enable(...)`**: Simulates the physical `EN` (Enable) pin being toggled high, then low, to clock data into the HD44780 shift register over I2C.
|
|
* **`update_counter(...)`**: Uses the `lcd1602_lib` to format the current `count` into a byte array without memory allocation, then sends those bytes over I2C and UART.
|
|
|
|
### 3. The Reusable LCD1602 Library (`src/lcd1602.rs`)
|
|
Because manipulating bits for a 4-bit LCD over an 8-bit I2C GPIO expander can be messy, this module cleanly separates the bitwise logic from the hardware I/O.
|
|
|
|
* **`build_nibble(...)`**: Takes a 4-bit data value and combines it with the Register Select (`RS`) bit and the Backlight enable bit to construct the raw byte that must be written to the PCF8574 expander.
|
|
* **`nibble_with_en(...)` & `nibble_without_en(...)`**: Trivial helpers that apply/remove the Enable (`EN`) mask bit to the assembled I2C byte.
|
|
* **`cursor_address(...)`**: Safely translates a `(line, position)` coordinate into the memory-mapped DDRAM address required by the HD44780 controller.
|
|
* **`format_counter(...)`**: Efficiently splits a 32-bit integer into its component ASCII decimal digits using division, right-justifies them into a 6-character field (`Count: 12`), and returns the exact number of bytes written to the buffer so the caller knows how much to transmit.
|