Initial commit

This commit is contained in:
Kevin Thomas
2026-07-06 14:32:12 -04:00
commit f62db776e1
615 changed files with 72344 additions and 0 deletions
@@ -0,0 +1,102 @@
# SPDX-License-Identifier: MIT OR Apache-2.0
#
# Copyright (c) 20212024 The rp-rs Developers
# Copyright (c) 2021 rp-rs organization
# Copyright (c) 2025 Raspberry Pi Ltd.
#
# Cargo Configuration for the https://github.com/rp-rs/rp-hal.git repository.
#
# You might want to make a similar file in your own repository if you are
# writing programs for Raspberry Silicon microcontrollers.
#
[build]
target = "thumbv8m.main-none-eabihf"
# Set the default target to match the Cortex-M33 in the RP2350
# target = "thumbv8m.main-none-eabihf"
# target = "thumbv6m-none-eabi"
# target = "riscv32imac-unknown-none-elf"
# Target specific options
[target.thumbv6m-none-eabi]
# Pass some extra options to rustc, some of which get passed on to the linker.
#
# * linker argument --nmagic turns off page alignment of sections (which saves
# flash space)
# * linker argument -Tlink.x tells the linker to use link.x as the linker
# script. This is usually provided by the cortex-m-rt crate, and by default
# the version in that crate will include a file called `memory.x` which
# describes the particular memory layout for your specific chip.
# * no-vectorize-loops turns off the loop vectorizer (seeing as the M0+ doesn't
# have SIMD)
linker = "flip-link"
rustflags = [
"-C", "link-arg=--nmagic",
"-C", "link-arg=-Tlink.x",
"-C", "link-arg=-Tdefmt.x",
"-C", "no-vectorize-loops",
]
# Use picotool for loading.
#
# Load an elf, skipping unchanged flash sectors, verify it, and execute it
runner = "${PICOTOOL_PATH} load -u -v -x -t elf"
#runner = "probe-rs run --chip ${CHIP} --protocol swd"
# This is the hard-float ABI for Arm mode.
#
# The FPU is enabled by default, and float function arguments use FPU
# registers.
[target.thumbv8m.main-none-eabihf]
# Pass some extra options to rustc, some of which get passed on to the linker.
#
# * linker argument --nmagic turns off page alignment of sections (which saves
# flash space)
# * linker argument -Tlink.x tells the linker to use link.x as a linker script.
# This is usually provided by the cortex-m-rt crate, and by default the
# version in that crate will include a file called `memory.x` which describes
# the particular memory layout for your specific chip.
# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a
# secondary linker script. This is required to make defmt_rtt work.
rustflags = [
"-C", "link-arg=--nmagic",
"-C", "link-arg=-Tlink.x",
"-C", "link-arg=-Tdefmt.x",
"-C", "target-cpu=cortex-m33",
]
# Use picotool for loading.
#
# Load an elf, skipping unchanged flash sectors, verify it, and execute it
runner = "${PICOTOOL_PATH} load -u -v -x -t elf"
#runner = "probe-rs run --chip ${CHIP} --protocol swd"
# This is the soft-float ABI for RISC-V mode.
#
# Hazard 3 does not have an FPU and so float function arguments use integer
# registers.
[target.riscv32imac-unknown-none-elf]
# Pass some extra options to rustc, some of which get passed on to the linker.
#
# * linker argument --nmagic turns off page alignment of sections (which saves
# flash space)
# * linker argument -Trp235x_riscv.x also tells the linker to use
# `rp235x_riscv.x` as a linker script. This adds in RP2350 RISC-V specific
# things that the riscv-rt crate's `link.x` requires and then includes
# `link.x` automatically. This is the reverse of how we do it on Cortex-M.
# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a
# secondary linker script. This is required to make defmt_rtt work.
rustflags = [
"-C", "link-arg=--nmagic",
"-C", "link-arg=-Trp2350_riscv.x",
"-C", "link-arg=-Tdefmt.x",
]
# Use picotool for loading.
#
# Load an elf, skipping unchanged flash sectors, verify it, and execute it
runner = "${PICOTOOL_PATH} load -u -v -x -t elf"
#runner = "probe-rs run --chip ${CHIP} --protocol swd"
[env]
DEFMT_LOG = "debug"
+113
View File
@@ -0,0 +1,113 @@
# Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux
# Edit at https://www.toptal.com/developers/gitignore?templates=rust,visualstudiocode,macos,windows,linux
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### Rust ###
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux
+1
View File
@@ -0,0 +1 @@
rp2350
+8
View File
@@ -0,0 +1,8 @@
{
"recommendations": [
"marus25.cortex-debug",
"rust-lang.rust-analyzer",
"probe-rs.probe-rs-debugger",
"raspberry-pi.raspberry-pi-pico"
]
}
+41
View File
@@ -0,0 +1,41 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Pico Debug (probe-rs)",
"cwd": "${workspaceFolder}",
"request": "launch",
"type": "probe-rs-debug",
"connectUnderReset": false,
"speed": 5000,
"runtimeExecutable": "probe-rs",
"chip": "${command:raspberry-pi-pico.getChip}",
"runtimeArgs": [
"dap-server"
],
"flashingConfig": {
"flashingEnabled": true,
"haltAfterReset": false
},
"coreConfigs": [
{
"coreIndex": 0,
"programBinary": "${command:raspberry-pi-pico.launchTargetPath}",
"rttEnabled": true,
"svdFile": "${command:raspberry-pi-pico.getSVDPath}",
"rttChannelFormats": [
{
"channelNumber": 0,
"dataFormat": "Defmt",
"mode": "NoBlockSkip",
"showTimestamps": true
}
]
}
],
"preLaunchTask": "Build + Generate SBOM (debug)",
"consoleLogLevel": "Debug",
"wireProtocol": "Swd"
}
]
}
+8
View File
@@ -0,0 +1,8 @@
{
"rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf",
"rust-analyzer.check.allTargets": false,
"editor.formatOnSave": true,
"files.exclude": {
".pico-rs": true
}
}
+124
View File
@@ -0,0 +1,124 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Compile Project",
"type": "process",
"isBuildCommand": true,
"command": "cargo",
"args": [
"build",
"--release"
],
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "dedicated"
},
"problemMatcher": "$rustc",
"options": {
"env": {
"PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}",
"CHIP": "${command:raspberry-pi-pico.getChip}"
}
}
},
{
"label": "Build + Generate SBOM (release)",
"type": "shell",
"command": "bash",
"args": [
"-lc",
"cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathRelease}"
],
"windows": {
"command": "powershell",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-Command",
"cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathRelease}"
]
},
"dependsOn": "Compile Project",
"presentation": {
"reveal": "silent",
"panel": "shared"
},
"problemMatcher": []
},
{
"label": "Compile Project (debug)",
"type": "process",
"isBuildCommand": true,
"command": "cargo",
"args": [
"build"
],
"group": {
"kind": "build",
"isDefault": false
},
"presentation": {
"reveal": "always",
"panel": "dedicated"
},
"problemMatcher": "$rustc",
"options": {
"env": {
"PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}",
"CHIP": "${command:raspberry-pi-pico.getChip}"
}
}
},
{
"label": "Build + Generate SBOM (debug)",
"type": "shell",
"command": "bash",
"args": [
"-lc",
"cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathDebug}"
],
"windows": {
"command": "powershell",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-Command",
"cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathDebug}"
]
},
"dependsOn": "Compile Project (debug)",
"presentation": {
"reveal": "silent",
"panel": "shared"
},
"problemMatcher": []
},
{
"label": "Run Project",
"type": "shell",
"dependsOn": [
"Build + Generate SBOM (release)"
],
"command": "${command:raspberry-pi-pico.getPicotoolPath}",
"args": [
"load",
"-x",
"${command:raspberry-pi-pico.launchTargetPathRelease}",
"-t",
"elf"
],
"presentation": {
"reveal": "always",
"panel": "dedicated"
},
"problemMatcher": []
}
]
}
+80
View File
@@ -0,0 +1,80 @@
[package]
edition = "2024"
name = "lcd1602"
version = "0.1.0"
license = "MIT or Apache-2.0"
[lib]
name = "lcd1602_lib"
path = "src/lib.rs"
[[bin]]
name = "lcd1602"
path = "src/main.rs"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = "1.0.0"
fugit = "0.3"
defmt = "1"
defmt-rtt = "1"
[target.'cfg( target_arch = "arm" )'.dependencies]
panic-probe = { version = "1", features = ["print-defmt"] }
[target.'cfg( target_arch = "riscv32" )'.dependencies]
panic-halt = { version = "1.0.0" }
[target.thumbv6m-none-eabi.dependencies]
rp2040-boot2 = "0.3"
rp2040-hal = { version = "0.11", features = ["rt", "critical-section-impl"] }
[target.riscv32imac-unknown-none-elf.dependencies]
rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] }
[target."thumbv8m.main-none-eabihf".dependencies]
rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] }
# cargo build/run
[profile.dev]
debug = 2
debug-assertions = true
opt-level = 2
overflow-checks = true
# cargo build/run --release
[profile.release]
debug = 2
debug-assertions = false
lto = "fat"
opt-level = 2
overflow-checks = false
# do not optimize proc-macro crates = faster builds from scratch
[profile.dev.build-override]
debug = false
debug-assertions = false
overflow-checks = false
opt-level = 0
[profile.release.build-override]
debug = false
debug-assertions = false
overflow-checks = false
opt-level = 0
# cargo test
[profile.test]
debug = 2
debug-assertions = true
opt-level = 2
overflow-checks = true
# cargo test --release
[profile.bench]
debug = 2
debug-assertions = false
lto = "fat"
opt-level = 3
+204
View File
@@ -0,0 +1,204 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (c) 20212024 The rp-rs Developers
Copyright (c) 2021 rp-rs organization
Copyright (c) 2025 Raspberry Pi Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+24
View File
@@ -0,0 +1,24 @@
MIT License
Copyright (c) 20212024 The rp-rs Developers
Copyright (c) 2021 rp-rs organization
Copyright (c) 2025 Raspberry Pi Ltd.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+114
View File
@@ -0,0 +1,114 @@
# 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.
+105
View File
@@ -0,0 +1,105 @@
//! Implementation module
//!
//! **File:** `build.rs`
//! **Author:** Kevin Thomas
//! **Date:** 2025
//!
//! MIT License
//!
//! Copyright (c) 2025 Kevin Thomas
//!
//! Permission is hereby granted, free of charge, to any person obtaining a copy
//! of this software and associated documentation files (the "Software"), to deal
//! in the Software without restriction, including without limitation the rights
//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//! copies of the Software, and to permit persons to whom the Software is
//! furnished to do so, subject to the following conditions:
//!
//! The above copyright notice and this permission notice shall be included in
//! all copies or substantial portions of the Software.
//!
//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//! SOFTWARE.
// Import dependencies from std::fs
use std::fs::{read_to_string, File};
// Import std::io::Write
use std::io::Write;
// Import std::path::{Path, PathBuf}
use std::path::{Path, PathBuf};
/// The main entry point.
fn main() {
let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
let c = read_to_string(".pico-rs").unwrap_or_default().trim().to_lowercase();
setup_target(&c, &out);
write_riscv(&out);
print_cfgs(&out);
}
/// Executes the setup target operation.
///
/// # Arguments
///
/// * `c` - Target string from config.
/// * `out` - Output path.
fn setup_target(c: &str, out: &Path) {
if c == "rp2040" {
write_rp2040(out);
} else {
write_rp2350(out);
}
}
/// Executes the write rp2040 operation.
///
/// # Arguments
///
/// * `out` - Output path.
fn write_rp2040(out: &Path) {
let b = include_bytes!("rp2040.x");
File::create(out.join("memory.x")).unwrap().write_all(b).unwrap();
println!("cargo::rustc-cfg=rp2040");
println!("cargo:rerun-if-changed=rp2040.x");
}
/// Executes the write rp2350 operation.
///
/// # Arguments
///
/// * `out` - Output path.
fn write_rp2350(out: &Path) {
let b = include_bytes!("rp2350.x");
File::create(out.join("memory.x")).unwrap().write_all(b).unwrap();
println!("cargo::rustc-cfg=rp2350");
println!("cargo:rerun-if-changed=rp2350.x");
}
/// Executes the write riscv operation.
///
/// # Arguments
///
/// * `out` - Output path.
fn write_riscv(out: &Path) {
let b = include_bytes!("rp2350_riscv.x");
File::create(out.join("rp2350_riscv.x")).unwrap().write_all(b).unwrap();
}
/// Executes the print cfgs operation.
///
/// # Arguments
///
/// * `out` - Output path.
fn print_cfgs(out: &Path) {
println!("cargo::rustc-check-cfg=cfg(rp2040)");
println!("cargo::rustc-check-cfg=cfg(rp2350)");
println!("cargo:rustc-link-search={}", out.display());
println!("cargo:rerun-if-changed=.pico-rs");
println!("cargo:rerun-if-changed=rp2350_riscv.x");
println!("cargo:rerun-if-changed=build.rs");
}
+91
View File
@@ -0,0 +1,91 @@
/*
* SPDX-License-Identifier: MIT OR Apache-2.0
*
* Copyright (c) 20212024 The rp-rs Developers
* Copyright (c) 2021 rp-rs organization
* Copyright (c) 2025 Raspberry Pi Ltd.
*/
MEMORY {
BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100
/*
* Here we assume you have 2048 KiB of Flash. This is what the Pi Pico
* has, but your board may have more or less Flash and you should adjust
* this value to suit.
*/
FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100
/*
* RAM consists of 4 banks, SRAM0-SRAM3, with a striped mapping.
* This is usually good for performance, as it distributes load on
* those banks evenly.
*/
RAM : ORIGIN = 0x20000000, LENGTH = 256K
/*
* RAM banks 4 and 5 use a direct mapping. They can be used to have
* memory areas dedicated for some specific job, improving predictability
* of access times.
* Example: Separate stacks for core0 and core1.
*/
SRAM4 : ORIGIN = 0x20040000, LENGTH = 4k
SRAM5 : ORIGIN = 0x20041000, LENGTH = 4k
/* SRAM banks 0-3 can also be accessed directly. However, those ranges
alias with the RAM mapping, above. So don't use them at the same time!
SRAM0 : ORIGIN = 0x21000000, LENGTH = 64k
SRAM1 : ORIGIN = 0x21010000, LENGTH = 64k
SRAM2 : ORIGIN = 0x21020000, LENGTH = 64k
SRAM3 : ORIGIN = 0x21030000, LENGTH = 64k
*/
}
EXTERN(BOOT2_FIRMWARE)
SECTIONS {
/* ### Boot loader
*
* An executable block of code which sets up the QSPI interface for
* 'Execute-In-Place' (or XIP) mode. Also sends chip-specific commands to
* the external flash chip.
*
* Must go at the start of external flash, where the Boot ROM expects it.
*/
.boot2 ORIGIN(BOOT2) :
{
KEEP(*(.boot2));
} > BOOT2
} INSERT BEFORE .text;
SECTIONS {
/* ### Boot ROM info
*
* Goes after .vector_table, to keep it in the first 512 bytes of flash,
* where picotool can find it
*/
.boot_info : ALIGN(4)
{
KEEP(*(.boot_info));
} > FLASH
} INSERT AFTER .vector_table;
/* move .text to start /after/ the boot info */
_stext = ADDR(.boot_info) + SIZEOF(.boot_info);
SECTIONS {
/* ### Picotool 'Binary Info' Entries
*
* Picotool looks through this block (as we have pointers to it in our
* header) to find interesting information.
*/
.bi_entries : ALIGN(4)
{
/* We put this in the header */
__bi_entries_start = .;
/* Here are the entries */
KEEP(*(.bi_entries));
/* Keep this block a nice round size */
. = ALIGN(4);
/* We put this in the header */
__bi_entries_end = .;
} > FLASH
} INSERT AFTER .text;
+83
View File
@@ -0,0 +1,83 @@
/*
* SPDX-License-Identifier: MIT OR Apache-2.0
*
* Copyright (c) 20212024 The rp-rs Developers
* Copyright (c) 2021 rp-rs organization
* Copyright (c) 2025 Raspberry Pi Ltd.
*/
MEMORY {
/*
* The RP2350 has either external or internal flash.
*
* 2 MiB is a safe default here, although a Pico 2 has 4 MiB.
*/
FLASH : ORIGIN = 0x10000000, LENGTH = 2048K
/*
* RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping.
* This is usually good for performance, as it distributes load on
* those banks evenly.
*/
RAM : ORIGIN = 0x20000000, LENGTH = 512K
/*
* RAM banks 8 and 9 use a direct mapping. They can be used to have
* memory areas dedicated for some specific job, improving predictability
* of access times.
* Example: Separate stacks for core0 and core1.
*/
SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K
SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K
}
SECTIONS {
/* ### Boot ROM info
*
* Goes after .vector_table, to keep it in the first 4K of flash
* where the Boot ROM (and picotool) can find it
*/
.start_block : ALIGN(4)
{
__start_block_addr = .;
KEEP(*(.start_block));
} > FLASH
} INSERT AFTER .vector_table;
/* move .text to start /after/ the boot info */
_stext = ADDR(.start_block) + SIZEOF(.start_block);
SECTIONS {
/* ### Picotool 'Binary Info' Entries
*
* Picotool looks through this block (as we have pointers to it in our
* header) to find interesting information.
*/
.bi_entries : ALIGN(4)
{
/* We put this in the header */
__bi_entries_start = .;
/* Here are the entries */
KEEP(*(.bi_entries));
/* Keep this block a nice round size */
. = ALIGN(4);
/* We put this in the header */
__bi_entries_end = .;
} > FLASH
} INSERT AFTER .text;
SECTIONS {
/* ### Boot ROM extra info
*
* Goes after everything in our program, so it can contain a signature.
*/
.end_block : ALIGN(4)
{
__end_block_addr = .;
KEEP(*(.end_block));
} > FLASH
} INSERT AFTER .uninit;
PROVIDE(start_to_end = __end_block_addr - __start_block_addr);
PROVIDE(end_to_start = __start_block_addr - __end_block_addr);
+259
View File
@@ -0,0 +1,259 @@
/*
* SPDX-License-Identifier: MIT OR Apache-2.0
*
* Copyright (c) 20212024 The rp-rs Developers
* Copyright (c) 2021 rp-rs organization
* Copyright (c) 2025 Raspberry Pi Ltd.
*/
MEMORY {
/*
* The RP2350 has either external or internal flash.
*
* 2 MiB is a safe default here, although a Pico 2 has 4 MiB.
*/
FLASH : ORIGIN = 0x10000000, LENGTH = 2048K
/*
* RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping.
* This is usually good for performance, as it distributes load on
* those banks evenly.
*/
RAM : ORIGIN = 0x20000000, LENGTH = 512K
/*
* RAM banks 8 and 9 use a direct mapping. They can be used to have
* memory areas dedicated for some specific job, improving predictability
* of access times.
* Example: Separate stacks for core0 and core1.
*/
SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K
SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K
}
/* # Developer notes
- Symbols that start with a double underscore (__) are considered "private"
- Symbols that start with a single underscore (_) are considered "semi-public"; they can be
overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" {
static mut _heap_size }`).
- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a
symbol is not dropped if it appears in or near the front of the linker arguments and "it's not
needed" by any of the preceding objects (linker arguments)
- `PROVIDE` is used to provide default values that can be overridden by a user linker script
- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and*
the LMA of .data are all `32`-byte aligned. These alignments are assumed by the RAM
initialization routine. There's also a second benefit: `32`-byte aligned boundaries
means that you won't see "Address (..) is out of bounds" in the disassembly produced by `objdump`.
*/
PROVIDE(_stext = ORIGIN(FLASH));
PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM));
PROVIDE(_max_hart_id = 0);
PROVIDE(_hart_stack_size = 2K);
PROVIDE(_heap_size = 0);
PROVIDE(InstructionMisaligned = ExceptionHandler);
PROVIDE(InstructionFault = ExceptionHandler);
PROVIDE(IllegalInstruction = ExceptionHandler);
PROVIDE(Breakpoint = ExceptionHandler);
PROVIDE(LoadMisaligned = ExceptionHandler);
PROVIDE(LoadFault = ExceptionHandler);
PROVIDE(StoreMisaligned = ExceptionHandler);
PROVIDE(StoreFault = ExceptionHandler);
PROVIDE(UserEnvCall = ExceptionHandler);
PROVIDE(SupervisorEnvCall = ExceptionHandler);
PROVIDE(MachineEnvCall = ExceptionHandler);
PROVIDE(InstructionPageFault = ExceptionHandler);
PROVIDE(LoadPageFault = ExceptionHandler);
PROVIDE(StorePageFault = ExceptionHandler);
PROVIDE(SupervisorSoft = DefaultHandler);
PROVIDE(MachineSoft = DefaultHandler);
PROVIDE(SupervisorTimer = DefaultHandler);
PROVIDE(MachineTimer = DefaultHandler);
PROVIDE(SupervisorExternal = DefaultHandler);
PROVIDE(MachineExternal = DefaultHandler);
PROVIDE(DefaultHandler = DefaultInterruptHandler);
PROVIDE(ExceptionHandler = DefaultExceptionHandler);
/* # Pre-initialization function */
/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function,
then the function this points to will be called before the RAM is initialized. */
PROVIDE(__pre_init = default_pre_init);
/* A PAC/HAL defined routine that should initialize custom interrupt controller if needed. */
PROVIDE(_setup_interrupts = default_setup_interrupts);
/* # Multi-processing hook function
fn _mp_hook() -> bool;
This function is called from all the harts and must return true only for one hart,
which will perform memory initialization. For other harts it must return false
and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt).
*/
PROVIDE(_mp_hook = default_mp_hook);
/* # Start trap function override
By default uses the riscv crates default trap handler
but by providing the `_start_trap` symbol external crates can override.
*/
PROVIDE(_start_trap = default_start_trap);
SECTIONS
{
.text.dummy (NOLOAD) :
{
/* This section is intended to make _stext address work */
. = ABSOLUTE(_stext);
} > FLASH
.text _stext :
{
/* Put reset handler first in .text section so it ends up as the entry */
/* point of the program. */
KEEP(*(.init));
KEEP(*(.init.rust));
. = ALIGN(4);
__start_block_addr = .;
KEEP(*(.start_block));
. = ALIGN(4);
*(.trap);
*(.trap.rust);
*(.text.abort);
*(.text .text.*);
. = ALIGN(4);
} > FLASH
/* ### Picotool 'Binary Info' Entries
*
* Picotool looks through this block (as we have pointers to it in our
* header) to find interesting information.
*/
.bi_entries : ALIGN(4)
{
/* We put this in the header */
__bi_entries_start = .;
/* Here are the entries */
KEEP(*(.bi_entries));
/* Keep this block a nice round size */
. = ALIGN(4);
/* We put this in the header */
__bi_entries_end = .;
} > FLASH
.rodata : ALIGN(4)
{
*(.srodata .srodata.*);
*(.rodata .rodata.*);
/* 4-byte align the end (VMA) of this section.
This is required by LLD to ensure the LMA of the following .data
section will have the correct alignment. */
. = ALIGN(4);
} > FLASH
.data : ALIGN(32)
{
_sidata = LOADADDR(.data);
__sidata = LOADADDR(.data);
_sdata = .;
__sdata = .;
/* Must be called __global_pointer$ for linker relaxations to work. */
PROVIDE(__global_pointer$ = . + 0x800);
*(.sdata .sdata.* .sdata2 .sdata2.*);
*(.data .data.*);
. = ALIGN(32);
_edata = .;
__edata = .;
} > RAM AT > FLASH
.bss (NOLOAD) : ALIGN(32)
{
_sbss = .;
*(.sbss .sbss.* .bss .bss.*);
. = ALIGN(32);
_ebss = .;
} > RAM
.end_block : ALIGN(4)
{
__end_block_addr = .;
KEEP(*(.end_block));
} > FLASH
/* fictitious region that represents the memory available for the heap */
.heap (NOLOAD) :
{
_sheap = .;
. += _heap_size;
. = ALIGN(4);
_eheap = .;
} > RAM
/* fictitious region that represents the memory available for the stack */
.stack (NOLOAD) :
{
_estack = .;
. = ABSOLUTE(_stack_start);
_sstack = .;
} > RAM
/* fake output .got section */
/* Dynamic relocations are unsupported. This section is only used to detect
relocatable code in the input files and raise an error if relocatable code
is found */
.got (INFO) :
{
KEEP(*(.got .got.*));
}
.eh_frame (INFO) : { KEEP(*(.eh_frame)) }
.eh_frame_hdr (INFO) : { *(.eh_frame_hdr) }
}
PROVIDE(start_to_end = __end_block_addr - __start_block_addr);
PROVIDE(end_to_start = __start_block_addr - __end_block_addr);
/* Do not exceed this mark in the error messages above | */
ASSERT(ORIGIN(FLASH) % 4 == 0, "
ERROR(riscv-rt): the start of the FLASH must be 4-byte aligned");
ASSERT(ORIGIN(RAM) % 32 == 0, "
ERROR(riscv-rt): the start of the RAM must be 32-byte aligned");
ASSERT(_stext % 4 == 0, "
ERROR(riscv-rt): `_stext` must be 4-byte aligned");
ASSERT(_sdata % 32 == 0 && _edata % 32 == 0, "
BUG(riscv-rt): .data is not 32-byte aligned");
ASSERT(_sidata % 32 == 0, "
BUG(riscv-rt): the LMA of .data is not 32-byte aligned");
ASSERT(_sbss % 32 == 0 && _ebss % 32 == 0, "
BUG(riscv-rt): .bss is not 32-byte aligned");
ASSERT(_sheap % 4 == 0, "
BUG(riscv-rt): start of .heap is not 4-byte aligned");
ASSERT(_stext + SIZEOF(.text) < ORIGIN(FLASH) + LENGTH(FLASH), "
ERROR(riscv-rt): The .text section must be placed inside the FLASH region.
Set _stext to an address smaller than 'ORIGIN(FLASH) + LENGTH(FLASH)'");
ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, "
ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts.
Consider changing `_max_hart_id` or `_hart_stack_size`.");
ASSERT(SIZEOF(.got) == 0, "
.got section detected in the input files. Dynamic relocations are not
supported. If you are linking to C code compiled using the `gcc` crate
then modify your build script to compile the C code _without_ the
-fPIC flag. See the documentation of the `gcc::Config.fpic` method for
details.");
/* Do not exceed this mark in the error messages above | */
+506
View File
@@ -0,0 +1,506 @@
//! Implementation module
//!
//! **File:** `board.rs`
//! **Author:** Kevin Thomas
//! **Date:** 2025
//!
//! MIT License
//!
//! Copyright (c) 2025 Kevin Thomas
//!
//! Permission is hereby granted, free of charge, to any person obtaining a copy
//! of this software and associated documentation files (the "Software"), to deal
//! in the Software without restriction, including without limitation the rights
//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//! copies of the Software, and to permit persons to whom the Software is
//! furnished to do so, subject to the following conditions:
//!
//! The above copyright notice and this permission notice shall be included in
//! all copies or substantial portions of the Software.
//!
//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//! SOFTWARE.
// I2C bus trait for LCD communication
use embedded_hal::i2c::I2c;
// Rate extension trait for .Hz() baud rate construction
use fugit::RateExtU32;
// Clock trait for accessing system clock frequency
use hal::Clock;
// GPIO pin types and function selectors
use hal::gpio::{FunctionI2C, FunctionNull, FunctionUart, Pin, PullDown, PullNone, PullUp};
// UART configuration and peripheral types
use hal::uart::{DataBits, Enabled, StopBits, UartConfig, UartPeripheral};
// Alias our HAL crate
#[cfg(rp2350)]
// Import rp235x_hal as hal
use rp235x_hal as hal;
#[cfg(rp2040)]
// Import rp2040_hal as hal
use rp2040_hal as hal;
/// External crystal frequency in Hz (12 MHz).
pub(crate) const XTAL_FREQ_HZ: u32 = 12_000_000u32;
/// UART baud rate in bits per second.
pub(crate) const UART_BAUD: u32 = 115_200;
/// I2C bus speed in Hz (100 kHz standard mode).
pub(crate) const I2C_BAUD: u32 = 100_000;
/// 7-bit I2C address of the PCF8574 LCD backpack.
pub(crate) const LCD_I2C_ADDR: u8 = 0x27;
/// Number of bit positions to shift a 4-bit nibble.
pub(crate) const NIBBLE_SHIFT: u8 = 4;
/// PCF8574 backlight enable mask.
pub(crate) const BACKLIGHT_MASK: u8 = 0x08;
/// Delay between counter updates in milliseconds.
pub(crate) const COUNTER_DELAY_MS: u32 = 1_000;
/// Type alias for the configured TX pin (GPIO 0, UART function, no pull).
pub(crate) type TxPin = Pin<hal::gpio::bank0::Gpio0, FunctionUart, PullNone>;
/// Type alias for the configured RX pin (GPIO 1, UART function, no pull).
pub(crate) type RxPin = Pin<hal::gpio::bank0::Gpio1, FunctionUart, PullNone>;
/// Type alias for the default TX pin state from `Pins::new()`.
pub(crate) type TxPinDefault = Pin<hal::gpio::bank0::Gpio0, FunctionNull, PullDown>;
/// Type alias for the default RX pin state from `Pins::new()`.
pub(crate) type RxPinDefault = Pin<hal::gpio::bank0::Gpio1, FunctionNull, PullDown>;
/// Type alias for the fully-enabled UART0 peripheral with TX/RX p.
pub(crate) type EnabledUart = UartPeripheral<Enabled, hal::pac::UART0, (TxPin, RxPin)>;
/// Initialise system clocks and PLLs from the external 12 MHz crystal.
///
/// # Arguments
///
/// * `xosc` - XOSC peripheral singleton.
/// * `clocks` - CLOCKS peripheral singleton.
/// * `pll_sys` - PLL_SYS peripheral singleton.
/// * `pll_usb` - PLL_USB peripheral singleton.
/// * `resets` - Mutable reference to the RESETS peripheral.
/// * `watchdog` - Mutable reference to the watchdog timer.
///
/// # Returns
///
/// Configured clocks manager.
///
/// # Panics
///
/// Panics if clock initialisation fails.
pub(crate) fn init_clocks(
xosc: hal::pac::XOSC,
clocks: hal::pac::CLOCKS,
pll_sys: hal::pac::PLL_SYS,
pll_usb: hal::pac::PLL_USB,
resets: &mut hal::pac::RESETS,
watchdog: &mut hal::Watchdog,
) -> hal::clocks::ClocksManager {
hal::clocks::init_clocks_and_plls(
XTAL_FREQ_HZ,
xosc,
clocks,
pll_sys,
pll_usb,
resets,
watchdog,
)
.unwrap()
}
/// Unlock the GPIO bank and return the pin set.
///
/// # Arguments
///
/// * `io_bank0` - IO_BANK0 peripheral singleton.
/// * `pads_bank0` - PADS_BANK0 peripheral singleton.
/// * `sio` - SIO peripheral singleton.
/// * `resets` - Mutable reference to the RESETS peripheral.
///
/// # Returns
///
/// GPIO pin set for the entire bank.
pub(crate) fn init_pins(
io_bank0: hal::pac::IO_BANK0,
pads_bank0: hal::pac::PADS_BANK0,
sio: hal::pac::SIO,
resets: &mut hal::pac::RESETS,
) -> hal::gpio::Pins {
let sio = hal::Sio::new(sio);
hal::gpio::Pins::new(io_bank0, pads_bank0, sio.gpio_bank0, resets)
}
/// Initialise UART0 for serial output (stdio equivalent).
///
/// # Arguments
///
/// * `uart0` - PAC UART0 peripheral singleton.
/// * `tx_pin` - GPIO pin to use as UART0 TX (GPIO 0).
/// * `rx_pin` - GPIO pin to use as UART0 RX (GPIO 1).
/// * `resets` - Mutable reference to the RESETS peripheral.
/// * `clocks` - Reference to the initialised clock configuration.
///
/// # Returns
///
/// Enabled UART0 peripheral ready for blocking writes.
///
/// # Panics
///
/// Panics if the HAL cannot achieve the requested baud rate.
pub(crate) fn init_uart(
uart0: hal::pac::UART0,
tx_pin: TxPinDefault,
rx_pin: RxPinDefault,
resets: &mut hal::pac::RESETS,
clocks: &hal::clocks::ClocksManager,
) -> EnabledUart {
let pins = (
tx_pin.reconfigure::<FunctionUart, PullNone>(),
rx_pin.reconfigure::<FunctionUart, PullNone>(),
);
let cfg = UartConfig::new(UART_BAUD.Hz(), DataBits::Eight, None, StopBits::One);
UartPeripheral::new(uart0, pins, resets)
.enable(cfg, clocks.peripheral_clock.freq())
.unwrap()
}
/// Create a blocking delay timer from the ARM SysTick peripheral.
///
/// # Arguments
///
/// * `clocks` - Reference to the initialised clock configuration.
///
/// # Returns
///
/// Blocking delay provider.
///
/// # Panics
///
/// Panics if the cortex-m core peripherals have already been taken.
///
/// # Arguments
///
/// * `clocks` - The `clocks` parameter.
///
/// # Returns
///
/// A value of type `cortex_m::delay::Delay`.
pub(crate) fn init_delay(clocks: &hal::clocks::ClocksManager) -> cortex_m::delay::Delay {
let core = cortex_m::Peripherals::take().unwrap();
cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz())
}
/// Write one raw byte to the PCF8574 expander over I2C.
///
/// # Arguments
///
/// * `i2c` - Mutable reference to the I2C bus.
/// * `addr` - 7-bit I2C address of the PCF8574.
/// * `data` - Byte to write.
///
/// # Arguments
///
/// * `i2c` - The `i2c` parameter.
/// * `addr` - Device address.
/// * `data` - Data to send/write.
fn pcf_write_byte(i2c: &mut impl I2c, addr: u8, data: u8) {
let _ = i2c.write(addr, &[data]);
}
/// Toggle EN to latch a nibble into the LCD controller.
///
/// # Arguments
///
/// * `i2c` - Mutable reference to the I2C bus.
/// * `addr` - 7-bit I2C address of the PCF8574.
/// * `data` - Nibble byte without EN asserted.
/// * `delay` - Delay provider for timing.
///
/// # Arguments
///
/// * `i2c` - The `i2c` parameter.
/// * `addr` - Device address.
/// * `data` - Data to send/write.
/// * `delay` - Delay value.
fn pcf_pulse_enable(i2c: &mut impl I2c, addr: u8, data: u8, d: &mut cortex_m::delay::Delay) {
pcf_write_byte(i2c, addr, lcd1602_lib::lcd1602::nibble_with_en(data)); d.delay_us(1);
pcf_write_byte(i2c, addr, lcd1602_lib::lcd1602::nibble_without_en(data)); d.delay_us(50);
}
/// Write one 4-bit nibble to the LCD.
///
/// # Arguments
///
/// * `i2c` - Mutable reference to the I2C bus.
/// * `addr` - 7-bit I2C address of the PCF8574.
/// * `nibble` - 4-bit value to send.
/// * `mode` - Register select: 0 for command, 1 for data.
/// * `delay` - Delay provider for timing.
fn lcd_write4(i2c: &mut impl I2c, addr: u8, nibble: u8, mode: u8, d: &mut cortex_m::delay::Delay) {
pcf_pulse_enable(i2c, addr, lcd1602_lib::lcd1602::build_nibble(nibble, NIBBLE_SHIFT, mode, BACKLIGHT_MASK), d);
}
/// Send one full 8-bit command/data value as two nibbles.
///
/// # Arguments
///
/// * `i2c` - Mutable reference to the I2C bus.
/// * `addr` - 7-bit I2C address of the PCF8574.
/// * `value` - 8-bit value to send.
/// * `mode` - Register select: 0 for command, 1 for data.
/// * `delay` - Delay provider for timing.
///
/// # Arguments
///
/// * `i2c` - The `i2c` parameter.
/// * `addr` - Device address.
/// * `value` - Value to use.
/// * `mode` - The `mode` parameter.
/// * `delay` - Delay value.
fn lcd_send(i2c: &mut impl I2c, addr: u8, value: u8, mode: u8, d: &mut cortex_m::delay::Delay) {
lcd_write4(i2c, addr, (value >> 4) & 0x0F, mode, d);
lcd_write4(i2c, addr, value & 0x0F, mode, d);
}
/// Send three 0x03 nibbles with required power-on delays.
///
/// # Arguments
///
/// * `i2c` - The `i2c` parameter.
/// * `addr` - Device address.
/// * `delay` - Delay value.
///
/// # Arguments
///
/// * `i2c` - The `i2c` parameter.
/// * `addr` - Device address.
/// * `delay` - Delay value.
fn lcd_reset_pulse_3x(i2c: &mut impl I2c, addr: u8, d: &mut cortex_m::delay::Delay) {
lcd_write4(i2c, addr, 0x03, 0, d); d.delay_ms(5);
lcd_write4(i2c, addr, 0x03, 0, d); d.delay_us(150);
lcd_write4(i2c, addr, 0x03, 0, d); d.delay_us(150);
}
/// Execute the HD44780 4-bit mode power-on reset sequence.
///
/// # Arguments
///
/// * `i2c` - Mutable reference to the I2C bus.
/// * `addr` - 7-bit I2C address of the PCF8574.
/// * `delay` - Delay provider for timing.
///
/// # Arguments
///
/// * `i2c` - The `i2c` parameter.
/// * `addr` - Device address.
/// * `delay` - Delay value.
fn lcd_hd44780_reset(i2c: &mut impl I2c, addr: u8, d: &mut cortex_m::delay::Delay) {
lcd_reset_pulse_3x(i2c, addr, d);
lcd_write4(i2c, addr, 0x02, 0, d); d.delay_us(150);
}
/// Send post-reset configuration commands to the HD44780.
///
/// # Arguments
///
/// * `i2c` - Mutable reference to the I2C bus.
/// * `addr` - 7-bit I2C address of the PCF8574.
/// * `delay` - Delay provider for timing.
///
/// # Arguments
///
/// * `i2c` - The `i2c` parameter.
/// * `addr` - Device address.
/// * `delay` - Delay value.
fn lcd_hd44780_configure(i2c: &mut impl I2c, addr: u8, d: &mut cortex_m::delay::Delay) {
lcd_send(i2c, addr, 0x28, 0, d); lcd_send(i2c, addr, 0x0C, 0, d);
lcd_send(i2c, addr, 0x01, 0, d); d.delay_ms(2); lcd_send(i2c, addr, 0x06, 0, d);
}
/// Set the LCD cursor position.
///
/// # Arguments
///
/// * `i2c` - Mutable reference to the I2C bus.
/// * `addr` - 7-bit I2C address of the PCF8574.
/// * `line` - Display row (0 or 1).
/// * `position` - Column offset.
/// * `delay` - Delay provider for timing.
fn lcd_set_cursor(i2c: &mut impl I2c, addr: u8, line: u8, position: u8, d: &mut cortex_m::delay::Delay) {
lcd_send(i2c, addr, lcd1602_lib::lcd1602::cursor_address(line, position), 0, d);
}
/// Write a byte slice as character data to the LCD.
///
/// # Arguments
///
/// * `i2c` - Mutable reference to the I2C bus.
/// * `addr` - 7-bit I2C address of the PCF8574.
/// * `s` - Byte slice of ASCII characters to display.
/// * `delay` - Delay provider for timing.
///
/// # Arguments
///
/// * `i2c` - The `i2c` parameter.
/// * `addr` - Device address.
/// * `s` - The `s` parameter.
/// * `delay` - Delay value.
fn lcd_puts(i2c: &mut impl I2c, addr: u8, s: &[u8], d: &mut cortex_m::delay::Delay) {
for &ch in s { lcd_send(i2c, addr, ch, 1, d); }
}
/// Initialize the LCD, display the title, and log over UART.
///
/// # Arguments
///
/// * `i2c` - Mutable reference to the I2C bus.
/// * `uart` - UART peripheral for serial log output.
/// * `delay` - Delay provider for timing.
pub(crate) fn setup_display(i2c: &mut impl I2c, u: &EnabledUart, d: &mut cortex_m::delay::Delay) {
lcd_hd44780_reset(i2c, LCD_I2C_ADDR, d); lcd_hd44780_configure(i2c, LCD_I2C_ADDR, d);
lcd_show_title(i2c, d); u.write_full_blocking(b"LCD 1602 driver initialized at I2C addr 0x27\r\n");
}
/// Write the title text on LCD row 0.
///
/// # Arguments
///
/// * `i2c` - The `i2c` parameter.
/// * `delay` - Delay value.
///
/// # Arguments
///
/// * `i2c` - The `i2c` parameter.
/// * `delay` - Delay value.
fn lcd_show_title(i2c: &mut impl I2c, d: &mut cortex_m::delay::Delay) {
lcd_set_cursor(i2c, LCD_I2C_ADDR, 0, 0, d); lcd_puts(i2c, LCD_I2C_ADDR, b"Reverse Eng.", d);
}
/// Format and display the next counter value on LCD line 1.
///
/// # Arguments
///
/// * `i2c` - Mutable reference to the I2C bus.
/// * `uart` - UART peripheral for serial log output.
/// * `delay` - Delay provider for timing.
/// * `count` - Mutable reference to the counter state.
pub(crate) fn update_counter(i2c: &mut impl I2c, u: &EnabledUart, d: &mut cortex_m::delay::Delay, count: &mut u32) {
let mut buf = [0u8; 16];
let n = lcd1602_lib::lcd1602::format_counter(&mut buf, *count); *count += 1;
lcd_display_counter(i2c, d, &buf[..n]); uart_log_counter(u, &buf[..n]); d.delay_ms(COUNTER_DELAY_MS);
}
/// Write counter text to LCD line 1.
///
/// # Arguments
///
/// * `i2c` - The `i2c` parameter.
/// * `delay` - Delay value.
/// * `text` - The `text` parameter.
///
/// # Arguments
///
/// * `i2c` - The `i2c` parameter.
/// * `delay` - Delay value.
/// * `text` - The `text` parameter.
fn lcd_display_counter(i2c: &mut impl I2c, d: &mut cortex_m::delay::Delay, text: &[u8]) {
lcd_set_cursor(i2c, LCD_I2C_ADDR, 1, 0, d); lcd_puts(i2c, LCD_I2C_ADDR, text, d);
}
/// Log counter text over UART with trailing CRLF.
///
/// # Arguments
///
/// * `uart` - The `uart` parameter.
/// * `text` - The `text` parameter.
///
/// # Arguments
///
/// * `uart` - The `uart` parameter.
/// * `text` - The `text` parameter.
fn uart_log_counter(u: &EnabledUart, text: &[u8]) {
u.write_full_blocking(text); u.write_full_blocking(b"\r\n");
}
/// Initialise all peripherals and run the LCD 1602 counter demo.
///
/// # Arguments
///
/// * `pac` - PAC Peripherals singleton (consumed).
///
/// # Returns
///
/// A value of type `!`.
///
/// # Arguments
///
/// * `pac` - The `pac` parameter.
///
/// # Returns
///
/// A value of type `!`.
pub(crate) fn run(mut p: hal::pac::Peripherals) -> ! {
let mut w = hal::Watchdog::new(p.WATCHDOG);
let c = init_clocks(p.XOSC, p.CLOCKS, p.PLL_SYS, p.PLL_USB, &mut p.RESETS, &mut w);
let pins = init_pins(p.IO_BANK0, p.PADS_BANK0, p.SIO, &mut p.RESETS);
let u = init_uart(p.UART0, pins.gpio0, pins.gpio1, &mut p.RESETS, &c);
let mut i2c = init_i2c(p.I2C1, pins.gpio2, pins.gpio3, &mut p.RESETS, &c);
setup_display(&mut i2c, &u, &mut init_delay(&c));
counter_loop(&mut i2c, &u, &mut init_delay(&c))
}
/// Initialise I2C1 on SDA=GPIO2 / SCL=GPIO3.
///
/// # Arguments
///
/// * `i2c1` - PAC I2C1 peripheral singleton.
/// * `sda` - Default GPIO 2 pin (will be reconfigured for I2C).
/// * `scl` - Default GPIO 3 pin (will be reconfigured for I2C).
/// * `resets` - Mutable reference to the RESETS peripheral.
/// * `clocks` - Reference to the initialised clock configuration.
///
/// # Returns
///
/// Configured I2C1 bus controller.
fn init_i2c(
i2c: hal::pac::I2C1, sda: Pin<hal::gpio::bank0::Gpio2, FunctionNull, PullDown>, scl: Pin<hal::gpio::bank0::Gpio3, FunctionNull, PullDown>, r: &mut hal::pac::RESETS, c: &hal::clocks::ClocksManager
) -> impl I2c {
hal::I2C::i2c1(i2c, sda.reconfigure::<FunctionI2C, PullUp>(), scl.reconfigure::<FunctionI2C, PullUp>(), I2C_BAUD.Hz(), r, c.system_clock.freq())
}
/// Run the counter display loop forever.
///
/// # Arguments
///
/// * `i2c` - Mutable reference to the I2C bus controller.
/// * `uart` - Reference to the enabled UART peripheral for serial output.
/// * `delay` - Mutable reference to the blocking delay provider.
///
/// # Returns
///
/// A value of type `!`.
///
/// # Arguments
///
/// * `i2c` - The `i2c` parameter.
/// * `uart` - The `uart` parameter.
/// * `delay` - Delay value.
///
/// # Returns
///
/// A value of type `!`.
fn counter_loop(i2c: &mut impl I2c, u: &EnabledUart, d: &mut cortex_m::delay::Delay) -> ! {
let mut count: u32 = 0;
loop { update_counter(i2c, u, d, &mut count); }
}
+316
View File
@@ -0,0 +1,316 @@
//! Implementation module
//!
//! **File:** `lcd1602.rs`
//! **Author:** Kevin Thomas
//! **Date:** 2025
//!
//! MIT License
//!
//! Copyright (c) 2025 Kevin Thomas
//!
//! Permission is hereby granted, free of charge, to any person obtaining a copy
//! of this software and associated documentation files (the "Software"), to deal
//! in the Software without restriction, including without limitation the rights
//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//! copies of the Software, and to permit persons to whom the Software is
//! furnished to do so, subject to the following conditions:
//!
//! The above copyright notice and this permission notice shall be included in
//! all copies or substantial portions of the Software.
//!
//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//! SOFTWARE.
/// PCF8574 -> LCD control pin: Register Select.
pub const PIN_RS: u8 = 0x01;
/// PCF8574 -> LCD control pin: Read/Write.
pub const PIN_RW: u8 = 0x02;
/// PCF8574 -> LCD control pin: Enable.
pub const PIN_EN: u8 = 0x04;
/// Build a PCF8574 output byte for a 4-bit LCD nibble.
///
/// # Arguments
///
/// * `nibble` - 4-bit data value (0x00..0x0F).
/// * `nibble_shift` - Number of bits to shift the nibble left.
/// * `mode` - Register select mode (0 = command, non-zero = data).
/// * `backlight_mask` - Bitmask to enable the backlight LED.
///
/// # Returns
///
/// Assembled PCF8574 output byte.
///
/// # Arguments
///
/// * `nibble` - The `nibble` parameter.
/// * `nibble_shift` - The `nibble_shift` parameter.
/// * `mode` - The `mode` parameter.
/// * `backlight_mask` - The `backlight_mask` parameter.
///
/// # Returns
///
/// An 8-bit unsigned integer value.
#[inline]
pub fn build_nibble(nibble: u8, nibble_shift: u8, mode: u8, backlight_mask: u8) -> u8 {
let mut data = (nibble & 0x0F) << nibble_shift;
if mode != 0 { data |= PIN_RS; }
data | backlight_mask
}
/// Build the PCF8574 byte with EN asserted.
///
/// # Arguments
///
/// * `nibble_byte` - PCF8574 output byte before EN assertion.
///
/// # Returns
///
/// Byte with the EN bit set.
///
/// # Arguments
///
/// * `nibble_byte` - The `nibble_byte` parameter.
///
/// # Returns
///
/// An 8-bit unsigned integer value.
#[inline]
pub fn nibble_with_en(nibble_byte: u8) -> u8 { nibble_byte | PIN_EN }
/// Build the PCF8574 byte with EN de-asserted.
///
/// # Arguments
///
/// * `nibble_byte` - PCF8574 output byte before EN de-assertion.
///
/// # Returns
///
/// Byte with the EN bit cleared.
///
/// # Arguments
///
/// * `nibble_byte` - The `nibble_byte` parameter.
///
/// # Returns
///
/// An 8-bit unsigned integer value.
#[inline]
pub fn nibble_without_en(nibble_byte: u8) -> u8 { nibble_byte & !PIN_EN }
/// HD44780 row-offset lookup.
const ROW_OFFSETS: [u8; 2] = [0x00, 0x40];
/// Compute the DDRAM address byte for `lcd_set_cursor`.
///
/// # Arguments
///
/// * `line` - Display row (0 or 1; values > 1 are clamped to 1).
/// * `position` - Column offset within the row.
///
/// # Returns
///
/// HD44780 set-DDRAM-address command byte (0x80 | offset).
///
/// # Arguments
///
/// * `line` - The `line` parameter.
/// * `position` - The `position` parameter.
///
/// # Returns
///
/// An 8-bit unsigned integer value.
#[inline]
pub fn cursor_address(line: u8, position: u8) -> u8 {
let row = if line > 1 { 1 } else { line as usize };
0x80 | (position + ROW_OFFSETS[row])
}
/// Extract the six decimal digits of a counter value into an array.
///
/// # Arguments
///
/// * `c` - Command byte.
///
/// # Returns
///
/// A value of type `[u8; 6]`.
///
/// # Arguments
///
/// * `c` - Command byte.
///
/// # Returns
///
/// A value of type `[u8; 6]`.
#[inline]
fn fill_digit_array(c: u32) -> [u8; 6] {
[
((c / 100000) % 10) as u8, ((c / 10000) % 10) as u8, ((c / 1000) % 10) as u8,
((c / 100) % 10) as u8, ((c / 10) % 10) as u8, (c % 10) as u8,
]
}
/// Write six counter digits into `buf` starting at `start`, suppressing leading zeros.
///
/// # Arguments
///
/// * `buf` - The `buf` parameter.
/// * `pos` - The `pos` parameter.
/// * `digits` - The `digits` parameter.
///
/// # Returns
///
/// A value of type `usize`.
///
/// # Arguments
///
/// * `buf` - The `buf` parameter.
/// * `pos` - The `pos` parameter.
/// * `digits` - The `digits` parameter.
///
/// # Returns
///
/// A value of type `usize`.
#[inline]
fn write_counter_digits(buf: &mut [u8], mut pos: usize, digits: &[u8; 6]) -> usize {
let mut leading = true;
for (i, &d) in digits.iter().enumerate() {
if i == 5 || d != 0 { leading = false; }
buf[pos] = if leading { b' ' } else { b'0' + d }; pos += 1;
}
pos
}
/// Format a counter value as `"Count: NNNNNN"` (right-justified, 6 digits).
///
/// # Arguments
///
/// * `buf` - Mutable byte slice (must be at least 13 bytes).
/// * `count` - Counter value to format (0..999999).
///
/// # Returns
///
/// Number of bytes written into the buffer.
///
/// # Arguments
///
/// * `buf` - The `buf` parameter.
/// * `count` - The `count` parameter.
///
/// # Returns
///
/// A value of type `usize`.
#[inline]
pub fn format_counter(buf: &mut [u8], count: u32) -> usize {
buf[..7].copy_from_slice(b"Count: ");
write_counter_digits(buf, 7, &fill_digit_array(count))
}
#[cfg(test)]
mod tests {
// Import all parent module items
use super::*;
/// Executes the build nibble command mode operation.
#[test]
fn build_nibble_command_mode() {
let b = build_nibble(0x03, 4, 0, 0x08);
assert_eq!(b, 0x38);
}
/// Executes the build nibble data mode operation.
#[test]
fn build_nibble_data_mode() {
let b = build_nibble(0x04, 4, 1, 0x08);
assert_eq!(b, 0x49);
}
/// Executes the build nibble no backlight operation.
#[test]
fn build_nibble_no_backlight() {
let b = build_nibble(0x0F, 4, 0, 0x00);
assert_eq!(b, 0xF0);
}
/// Executes the nibble with en sets bit operation.
#[test]
fn nibble_with_en_sets_bit() {
assert_eq!(nibble_with_en(0x38), 0x3C);
}
/// Executes the nibble without en clears bit operation.
#[test]
fn nibble_without_en_clears_bit() {
assert_eq!(nibble_without_en(0x3C), 0x38);
}
/// Executes the cursor address line0 col0 operation.
#[test]
fn cursor_address_line0_col0() {
assert_eq!(cursor_address(0, 0), 0x80);
}
/// Executes the cursor address line1 col0 operation.
#[test]
fn cursor_address_line1_col0() {
assert_eq!(cursor_address(1, 0), 0xC0);
}
/// Executes the cursor address line0 col5 operation.
#[test]
fn cursor_address_line0_col5() {
assert_eq!(cursor_address(0, 5), 0x85);
}
/// Executes the cursor address line1 col15 operation.
#[test]
fn cursor_address_line1_col15() {
assert_eq!(cursor_address(1, 15), 0xCF);
}
/// Executes the cursor address clamps line operation.
#[test]
fn cursor_address_clamps_line() {
assert_eq!(cursor_address(5, 0), 0xC0);
}
/// Executes the format counter zero operation.
#[test]
fn format_counter_zero() {
let mut buf = [0u8; 16];
let n = format_counter(&mut buf, 0);
assert_eq!(&buf[..n], b"Count: 0");
}
/// Executes the format counter one operation.
#[test]
fn format_counter_one() {
let mut buf = [0u8; 16];
let n = format_counter(&mut buf, 1);
assert_eq!(&buf[..n], b"Count: 1");
}
/// Executes the format counter large operation.
#[test]
fn format_counter_large() {
let mut buf = [0u8; 16];
let n = format_counter(&mut buf, 123456);
assert_eq!(&buf[..n], b"Count: 123456");
}
/// Executes the format counter six digits operation.
#[test]
fn format_counter_six_digits() {
let mut buf = [0u8; 16];
let n = format_counter(&mut buf, 999999);
assert_eq!(&buf[..n], b"Count: 999999");
}
}
+6
View File
@@ -0,0 +1,6 @@
//! Driver crate
#![deny(missing_docs)]
#![deny(clippy::missing_docs_in_private_items)]
#![cfg_attr(not(test), no_std)]
pub mod lcd1602;
+102
View File
@@ -0,0 +1,102 @@
//! Driver crate
#![deny(missing_docs)]
#![deny(clippy::missing_docs_in_private_items)]
//! Implementation module
//!
//! **File:** `main.rs`
//! **Author:** Kevin Thomas
//! **Date:** 2025
//!
//! MIT License
//!
//! Copyright (c) 2025 Kevin Thomas
//!
//! Permission is hereby granted, free of charge, to any person obtaining a copy
//! of this software and associated documentation files (the "Software"), to deal
//! in the Software without restriction, including without limitation the rights
//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//! copies of the Software, and to permit persons to whom the Software is
//! furnished to do so, subject to the following conditions:
//!
//! The above copyright notice and this permission notice shall be included in
//! all copies or substantial portions of the Software.
//!
//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//! SOFTWARE.
#![no_std]
#![no_main]
// Board-level helpers: constants, type aliases, and init functions
mod board;
// LCD1602 driver module — suppress warnings for unused public API functions
// Debugging output over RTT
use defmt_rtt as _;
// Panic handler for RISC-V targets
#[cfg(target_arch = "riscv32")]
// Import panic_halt as _
use panic_halt as _;
// Panic handler for ARM targets
#[cfg(target_arch = "arm")]
// Import panic_probe as _
use panic_probe as _;
// HAL entry-point macro
use hal::entry;
// Alias our HAL crate
#[cfg(rp2350)]
// Import rp235x_hal as hal
use rp235x_hal as hal;
#[cfg(rp2040)]
// Import rp2040_hal as hal
use rp2040_hal as hal;
/// Second-stage boot loader for RP2040
#[unsafe(link_section = ".boot2")]
#[used]
#[cfg(rp2040)]
pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080;
/// Boot metadata for the RP2350 Boot ROM
#[unsafe(link_section = ".start_block")]
#[used]
#[cfg(rp2350)]
pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe();
/// Application entry point for the LCD 1602 counter demo.
///
/// # Returns
///
/// A value of type `!`.
///
/// # Returns
///
/// A value of type `!`.
#[entry]
fn main() -> ! {
board::run(hal::pac::Peripherals::take().unwrap())
}
/// Picotool binary info metadata
#[unsafe(link_section = ".bi_entries")]
#[used]
pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [
hal::binary_info::rp_cargo_bin_name!(),
hal::binary_info::rp_cargo_version!(),
hal::binary_info::rp_program_description!(c"LCD 1602 Counter Demo"),
hal::binary_info::rp_cargo_homepage_url!(),
hal::binary_info::rp_program_build_attribute!(),
];
#[cfg(test)]
mod tests {
// Import all parent module items
use super::*;
}