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
+102
View File
@@ -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 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 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 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": []
}
]
}
+79
View File
@@ -0,0 +1,79 @@
[package]
edition = "2024"
name = "uart"
version = "0.1.0"
license = "MIT or Apache-2.0"
[lib]
name = "uart_lib"
path = "src/lib.rs"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = "1.0.0"
embedded-hal-nb = "1.0.0"
nb = "1.1.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.
+115
View File
@@ -0,0 +1,115 @@
# 0x01 UART Rust Driver
This repository contains a Bare-Metal Rust driver for the UART (Universal Asynchronous Receiver-Transmitter) peripheral on the **RP2350** (and RP2040) microcontrollers.
It includes:
- A thin demo (`src/main.rs`) that runs an uppercase echo server over UART.
- A reusable library module (`src/uart.rs`) providing a hardware-agnostic `UartDriver`.
- 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 UART driver logic is separated into a reusable library, you can run the unit tests natively on your computer (no microcontroller required!).
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 need to wake up the specific hardware subsystems we want to use (Clocks, Pins, and the UART peripheral).
* **`run(...)`**: The master setup function. It sequentially calls the helper initialization functions below, and then kicks off the echo server.
* **`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_uart_pins(...)`**: Takes control of physical pins `GPIO0` and `GPIO1` and configures them specifically for UART TX (Transmit) and RX (Receive) mode.
* **`init_uart_peri(...)`**: Configures the hardware `UART0` peripheral to operate at a standard `115200` baud rate with an `8N1` configuration (8 data bits, no parity, 1 stop bit).
* **`start_echo_server(...)`**: Wraps the hardware UART peripheral in our custom `UartDriver`, prints a "UART ready" welcome message to the serial console, and jumps into the `echo_loop`.
* **`echo_loop(...)`**: An infinite `loop { ... }` that waits for a character to arrive, reads it, and immediately transmits the uppercase version back.
### 3. The Reusable UART Driver (`src/uart.rs`)
The hardware-specific logic from `board.rs` uses the `UartDriver` struct to abstract away the messy details of sending and receiving bytes.
* **`UartDriver::init(...)`**: Creates a new instance of our driver, taking ownership of the hardware UART peripheral.
* **`getchar(&mut self)`**: A blocking function that waits until a byte physically arrives over the RX wire, reads it out of the hardware buffer, and returns it.
* **`putchar(&mut self, c: u8)`**: A blocking function that waits until the TX wire is ready, and then writes a single byte into the hardware buffer to be transmitted.
* **`puts(&mut self, s: &[u8])`**: A convenience function that takes an array of characters (a string) and transmits them one-by-one by repeatedly calling `putchar()`.
* **`to_upper(c: u8)`**: A helper function that takes an ASCII byte and uses Rust's built-in `.to_ascii_uppercase()` to convert it to an uppercase letter.
+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 | */
+160
View File
@@ -0,0 +1,160 @@
//! 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.
// Import rp235x_hal as hal
#[cfg(rp2350)]
use rp235x_hal as hal;
// Import rp2040_hal as hal
#[cfg(rp2040)]
use rp2040_hal as hal;
// Import fugit::RateExtU32
use fugit::RateExtU32;
// Import hal::Clock
use hal::Clock;
// Import uart_lib::uart
use uart_lib::uart;
/// 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;
/// UART TX pin type alias.
pub type UartTx = hal::gpio::Pin<hal::gpio::bank0::Gpio0, hal::gpio::FunctionUart, hal::gpio::PullNone>;
/// UART RX pin type alias.
pub type UartRx = hal::gpio::Pin<hal::gpio::bank0::Gpio1, hal::gpio::FunctionUart, hal::gpio::PullNone>;
/// UART Pins tuple.
pub type UartPins = (UartTx, UartRx);
/// Initialise system clocks and PLLs from the external 12 MHz crystal.
///
/// # Arguments
///
/// * `x` - XOSC peripheral.
/// * `c` - CLOCKS peripheral.
/// * `ps` - PLL_SYS peripheral.
/// * `pu` - PLL_USB peripheral.
/// * `r` - Mutable reference to RESETS.
/// * `w` - Mutable reference to Watchdog.
///
/// # Returns
///
/// Configured clocks manager.
pub(crate) fn init_clocks(
x: hal::pac::XOSC, c: hal::pac::CLOCKS, ps: hal::pac::PLL_SYS, pu: hal::pac::PLL_USB,
r: &mut hal::pac::RESETS, w: &mut hal::Watchdog,
) -> hal::clocks::ClocksManager {
hal::clocks::init_clocks_and_plls(XTAL_FREQ_HZ, x, c, ps, pu, r, w).unwrap()
}
/// Unlock the GPIO bank and return configured UART pins.
///
/// # Arguments
///
/// * `io` - IO_BANK0 peripheral.
/// * `pads` - PADS_BANK0 peripheral.
/// * `sio` - SIO peripheral.
/// * `resets` - Mutable reference to the RESETS peripheral.
///
/// # Returns
///
/// Tuple of (TX, RX) pins configured for UART.
pub(crate) fn init_uart_pins(
io: hal::pac::IO_BANK0, pads: hal::pac::PADS_BANK0, sio: hal::pac::SIO,
resets: &mut hal::pac::RESETS,
) -> UartPins {
let p = hal::gpio::Pins::new(io, pads, hal::Sio::new(sio).gpio_bank0, resets);
(p.gpio0.into_pull_type().into_function(), p.gpio1.into_pull_type().into_function())
}
/// Initialises the UART peripheral.
///
/// # Arguments
///
/// * `u0` - UART0 peripheral.
/// * `pins` - Configured UART pins.
/// * `r` - Mutable reference to RESETS.
/// * `freq` - Peripheral clock frequency.
///
/// # Returns
///
/// Enabled UART peripheral.
pub(crate) fn init_uart_peri(
u0: hal::pac::UART0, pins: UartPins, r: &mut hal::pac::RESETS, freq: fugit::HertzU32,
) -> hal::uart::UartPeripheral<hal::uart::Enabled, hal::pac::UART0, UartPins> {
let cfg = hal::uart::UartConfig::new(UART_BAUD.Hz(), hal::uart::DataBits::Eight, None, hal::uart::StopBits::One);
hal::uart::UartPeripheral::new(u0, pins, r).enable(cfg, freq).unwrap()
}
/// Starts the UART driver and echo server.
///
/// # Arguments
///
/// * `u` - Enabled UART peripheral.
///
/// # Returns
///
/// A value of type `!`.
pub(crate) fn start_echo_server(
u: hal::uart::UartPeripheral<hal::uart::Enabled, hal::pac::UART0, UartPins>,
) -> ! {
let mut drv = uart::UartDriver::init(u);
drv.puts(b"UART ready\r\n");
echo_loop(&mut drv)
}
/// Initialise all peripherals and run the UART echo demo.
///
/// # Arguments
///
/// * `pac` - PAC Peripherals singleton (consumed).
///
/// # Returns
///
/// A value of type `!`.
pub(crate) fn run(mut pac: hal::pac::Peripherals) -> ! {
let mut wdg = hal::Watchdog::new(pac.WATCHDOG);
let clk = init_clocks(pac.XOSC, pac.CLOCKS, pac.PLL_SYS, pac.PLL_USB, &mut pac.RESETS, &mut wdg);
let pins = init_uart_pins(pac.IO_BANK0, pac.PADS_BANK0, pac.SIO, &mut pac.RESETS);
let u = init_uart_peri(pac.UART0, pins, &mut pac.RESETS, clk.peripheral_clock.freq());
start_echo_server(u)
}
/// Run the uppercase echo loop forever.
///
/// # Arguments
///
/// * `drv` - Mutable reference to the UART driver.
fn echo_loop<T: embedded_hal_nb::serial::Read<u8> + embedded_hal_nb::serial::Write<u8>>(
drv: &mut uart::UartDriver<T>,
) -> ! {
loop {
let c = drv.getchar();
drv.putchar(uart::UartDriver::<T>::to_upper(c));
}
}
+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 uart;
+101
View File
@@ -0,0 +1,101 @@
//! 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;
// 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 UART uppercase echo 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"UART Uppercase Echo"),
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::*;
}
+257
View File
@@ -0,0 +1,257 @@
//! Implementation module
//!
//! **File:** `uart.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.
// Embedded HAL traits for UART
use embedded_hal_nb::serial::{Read, Write};
// Non-blocking block macro
use nb::block;
/// Generic UART driver that owns a serial peripheral.
pub struct UartDriver<T: Read<u8> + Write<u8>> {
/// The underlying UART peripheral.
uart: T,
}
impl<T: Read<u8> + Write<u8>> UartDriver<T> {
/// Initialize UART driver with a configured peripheral.
///
/// # Arguments
///
/// * `uart` - The hardware UART or mock peripheral.
///
/// # Returns
///
/// A new UartDriver instance.
///
/// # Arguments
///
/// * `uart` - The `uart` parameter.
///
/// # Returns
///
/// A new instance of the struct.
#[inline]
pub fn init(uart: T) -> Self {
Self { uart }
}
/// Read one character from UART (blocking).
///
/// Blocks until a byte arrives, then returns it.
///
/// # Returns
///
/// The received byte.
///
/// # Returns
///
/// An 8-bit unsigned integer value.
#[inline]
pub fn getchar(&mut self) -> u8 {
block!(self.uart.read()).unwrap()
}
/// Transmit one character over UART (blocking).
///
/// Waits until there is space, then transmits.
///
/// # Arguments
///
/// * `c` - Byte to transmit.
///
/// # Arguments
///
/// * `c` - Command byte.
#[inline]
pub fn putchar(&mut self, c: u8) {
block!(self.uart.write(c)).unwrap();
}
/// Transmit a null-terminated string over UART.
///
/// Sends every byte in the slice sequentially.
///
/// # Arguments
///
/// * `s` - Byte slice to transmit.
///
/// # Arguments
///
/// * `s` - The `s` parameter.
#[inline]
pub fn puts(&mut self, s: &[u8]) {
for &c in s {
self.putchar(c);
}
}
/// Convert a lowercase ASCII character to uppercase.
///
/// Returns the uppercase equivalent if the character is in `b'a'``b'z'`.
///
/// # Arguments
///
/// * `c` - Input byte.
///
/// # Returns
///
/// Uppercase equivalent, or the original byte.
///
/// # Arguments
///
/// * `c` - Command byte.
///
/// # Returns
///
/// An 8-bit unsigned integer value.
#[inline]
pub fn to_upper(c: u8) -> u8 {
c.to_ascii_uppercase()
}
}
#[cfg(test)]
mod tests {
// Import all parent module items
use super::*;
// Import dependencies from embedded_hal_nb::serial
use embedded_hal_nb::serial::{ErrorType, Read, Write};
struct MockUart {
rx_buf: std::collections::VecDeque<u8>,
tx_buf: std::collections::VecDeque<u8>,
}
impl MockUart {
/// Creates a new hardware wrapper instance.
///
/// # Returns
///
/// A new instance of the struct.
fn new() -> Self {
Self {
rx_buf: std::collections::VecDeque::new(),
tx_buf: std::collections::VecDeque::new(),
}
}
}
impl ErrorType for MockUart {
type Error = core::convert::Infallible;
}
impl Read<u8> for MockUart {
/// Executes the read operation.
///
/// # Returns
///
/// A Result indicating success or failure.
fn read(&mut self) -> nb::Result<u8, Self::Error> {
if let Some(c) = self.rx_buf.pop_front() {
Ok(c)
} else {
Err(nb::Error::WouldBlock)
}
}
}
impl Write<u8> for MockUart {
/// Executes the write operation.
///
/// # Arguments
///
/// * `c` - Command byte.
///
/// # Returns
///
/// A Result indicating success or failure.
fn write(&mut self, c: u8) -> nb::Result<(), Self::Error> {
self.tx_buf.push_back(c);
Ok(())
}
/// Executes the flush operation.
///
/// # Returns
///
/// A Result indicating success or failure.
fn flush(&mut self) -> nb::Result<(), Self::Error> {
Ok(())
}
}
/// Executes the to upper lowercase a operation.
#[test]
fn to_upper_lowercase_a() {
assert_eq!(UartDriver::<MockUart>::to_upper(b'a'), b'A');
}
/// Executes the to upper lowercase z operation.
#[test]
fn to_upper_lowercase_z() {
assert_eq!(UartDriver::<MockUart>::to_upper(b'z'), b'Z');
}
/// Executes the to upper already uppercase operation.
#[test]
fn to_upper_already_uppercase() {
assert_eq!(UartDriver::<MockUart>::to_upper(b'A'), b'A');
}
/// Executes the to upper digit unchanged operation.
#[test]
fn to_upper_digit_unchanged() {
assert_eq!(UartDriver::<MockUart>::to_upper(b'5'), b'5');
}
/// Executes the getchar reads from buffer operation.
#[test]
fn getchar_reads_from_buffer() {
let mut mock = MockUart::new();
mock.rx_buf.push_back(b'x');
let mut drv = UartDriver::init(mock);
assert_eq!(drv.getchar(), b'x');
}
/// Executes the putchar writes to buffer operation.
#[test]
fn putchar_writes_to_buffer() {
let mut drv = UartDriver::init(MockUart::new());
drv.putchar(b'y');
assert_eq!(drv.uart.tx_buf.pop_front(), Some(b'y'));
}
/// Executes the puts writes all chars operation.
#[test]
fn puts_writes_all_chars() {
let mut drv = UartDriver::init(MockUart::new());
drv.puts(b"Hi");
assert_eq!(drv.uart.tx_buf.pop_front(), Some(b'H'));
assert_eq!(drv.uart.tx_buf.pop_front(), Some(b'i'));
}
}