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_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": []
}
]
}
+79
View File
@@ -0,0 +1,79 @@
[package]
edition = "2024"
name = "dht11"
version = "0.1.0"
license = "MIT or Apache-2.0"
[lib]
name = "dht11_lib"
path = "src/lib.rs"
[[bin]]
name = "dht11"
path = "src/main.rs"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
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 @@
# 0x09 DHT11 Rust Driver
This repository contains a Bare-Metal Rust driver for the **DHT11 Temperature and Humidity Sensor** on the **RP2350** (and RP2040) microcontrollers, implemented strictly using a GPIO pin and a hardware timer (no specialized hardware peripheral like PIO or PWM).
It includes:
- A demo (`src/main.rs`) that continuously reads the sensor every 2 seconds and outputs the results over UART.
- A reusable library module (`src/dht11.rs`) providing a hardware-agnostic `dht11_lib` containing the pure-logic checksum validation, data parsing, and string formatting routines.
- 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 DHT11 data parsing, checksum validation, 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 delays), and a hardware timer (for microsecond precision measurement).
* **`run(...)`**: The master setup function. Calls the helper initialization functions below, prints an initialization message over UART, and enters the infinite polling loop.
* **`poll_sensor(...)`**: Called repeatedly in the main loop. It invokes the read sequence, formats the result, writes it to UART, and blocks for 2 seconds (the DHT11's minimum polling interval).
* **`read_sensor(...)`**: The high-level function for a single DHT11 read. It delegates to `acquire_data()` to handle the timing protocol, and then uses the `dht11_lib` to parse the resulting 5-byte array into temperature and humidity.
* **`acquire_data(...)`**: Executes the strict protocol: pulls the pin low for 18ms (`send_start_signal()`), waits for the sensor's acknowledgment (`wait_response()`), and reads 40 bits into an array (`read_40_bits()`).
* **`measure_bit_duration(...)`**: Crucial for decoding the data. It waits for the sensor to pull the pin high, takes a timestamp, waits for it to pull low, takes another timestamp, and returns the duration.
* **`read_bit(...)`**: Takes the measured pulse duration and calls the hardware-agnostic `accumulate_bit()` function in the `dht11_lib` to pack the 1 or 0 into the byte array.
* **`gpio_drive(...)` & `gpio_release(...)`**: Manually flips the `GPIO 4` pin direction (output/input) via the `SIO` (Single-Cycle IO) registers to support the bidirectional 1-wire protocol.
### 3. The Reusable DHT11 Library (`src/dht11.rs`)
Because manipulating bits across byte boundaries and formatting floats without `std::fmt` can be error-prone, this module handles all pure-logic data manipulation.
* **`accumulate_bit(...)`**: Shifts the current byte left and inserts a `1` if the measured pulse duration exceeds the 40-microsecond threshold.
* **`validate_checksum(...)`**: Adds the first four bytes (humidity int, humidity frac, temp int, temp frac) and ensures the lowest 8 bits match the fifth byte (the checksum).
* **`parse_humidity(...)` & `parse_temperature(...)`**: Converts the specific byte pairs into standard floating-point numbers.
* **`format_reading(...)` & `format_error(...)`**: Efficiently constructs the final ASCII string without allocating memory, allowing the `board.rs` code to pipe the raw bytes directly to UART.
+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 | */
+523
View File
@@ -0,0 +1,523 @@
//! 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.
// DHT11 pure-logic functions (checksum, parsing, formatting)
use dht11_lib::dht11;
// 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::{FunctionNull, FunctionUart, Pin, PullDown, PullNone};
// 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;
/// Timer device type for the HAL timer peripheral.
#[cfg(rp2350)]
pub(crate) type HalTimer = hal::Timer<hal::timer::CopyableTimer0>;
/// Timer type alias for RP2040 (non-generic).
#[cfg(rp2040)]
pub(crate) type HalTimer = hal::Timer;
/// 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;
/// GPIO pin number connected to the DHT11 data line.
pub(crate) const DHT11_GPIO: u8 = 4;
/// Polling interval in milliseconds (DHT11 minimum is 2 seconds).
pub(crate) const POLL_MS: u32 = 2_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())
}
/// Set or clear the DHT11 GPIO output level via SIO registers.
///
/// # Arguments
///
/// * `sio` - The `sio` parameter.
/// * `high` - The `high` parameter.
///
/// # Arguments
///
/// * `sio` - The `sio` parameter.
/// * `high` - The `high` parameter.
fn sio_set_level(sio: &hal::pac::sio::RegisterBlock, high: bool) {
if high { sio.gpio_out_set().write(|w| unsafe { w.bits(1u32 << DHT11_GPIO) }); }
else { sio.gpio_out_clr().write(|w| unsafe { w.bits(1u32 << DHT11_GPIO) }); }
}
/// Drive the DHT11 data pin to a given level (enables output).
///
/// # Arguments
///
/// * `high` - `true` to drive HIGH, `false` to drive LOW.
///
/// # Arguments
///
/// * `high` - The `high` parameter.
fn gpio_drive(high: bool) {
unsafe {
let sio = &*hal::pac::SIO::PTR; sio_set_level(sio, high);
sio.gpio_oe_set().write(|w| w.bits(1u32 << DHT11_GPIO));
}
}
/// Release the DHT11 data pin back to input mode (disable output driver).
fn gpio_release() {
unsafe {
let sio = &*hal::pac::SIO::PTR;
sio.gpio_oe_clr().write(|w| w.bits(1u32 << DHT11_GPIO));
}
}
/// Read the current logic level of the DHT11 data pin.
///
/// # Returns
///
/// `true` if the pin reads HIGH, `false` if LOW.
///
/// # Returns
///
/// `true` if successful or set, `false` otherwise.
fn gpio_read() -> bool {
unsafe { (*hal::pac::SIO::PTR).gpio_in().read().bits() & (1u32 << DHT11_GPIO) != 0 }
}
/// Read the free-running microsecond timer (lower 32 bits).
///
/// # Arguments
///
/// * `timer` - Reference to the HAL timer peripheral.
///
/// # Returns
///
/// Current timer value in microseconds (wrapping at 2^32).
///
/// # Arguments
///
/// * `timer` - The `timer` parameter.
///
/// # Returns
///
/// A 32-bit unsigned integer value.
fn time_us_32(timer: &HalTimer) -> u32 {
timer.get_counter().ticks() as u32
}
/// Send the DHT11 start signal on the data pin.
///
/// Drives the pin LOW for 18 ms then HIGH for 40 us before switching
/// the pin to input mode to listen for the sensor response.
///
/// # Arguments
///
/// * `delay` - Mutable reference to the blocking delay provider.
///
/// # Arguments
///
/// * `delay` - Delay value.
fn send_start_signal(d: &mut cortex_m::delay::Delay) {
gpio_drive(false); d.delay_ms(18);
gpio_drive(true); d.delay_us(40); gpio_release();
}
/// Spin until the pin leaves the given logic level, or time out.
///
/// # Arguments
///
/// * `level` - Logic level to wait through (`true` = HIGH, `false` = LOW).
///
/// # Returns
///
/// `true` once the level changed, `false` on timeout.
///
/// # Arguments
///
/// * `level` - The `level` parameter.
///
/// # Returns
///
/// `true` if successful or set, `false` otherwise.
fn wait_for_level(level: bool) -> bool {
let mut timeout: u32 = dht11::LEVEL_WAIT_TIMEOUT;
while gpio_read() == level {
timeout -= 1; if timeout == 0 { return false; }
}
true
}
/// Wait for the DHT11 response after the start signal.
///
/// The sensor pulls LOW then HIGH then LOW again; each transition
/// is awaited with a timeout.
///
/// # Returns
///
/// `true` if the full response was received, `false` on timeout.
///
/// # Returns
///
/// `true` if successful or set, `false` otherwise.
fn wait_response() -> bool {
wait_for_level(true) && wait_for_level(false) && wait_for_level(true)
}
/// Measure the high-period duration for a single DHT11 bit.
///
/// # Arguments
///
/// * `timer` - Reference to the HAL timer for microsecond measurement.
///
/// # Returns
///
/// Duration in microseconds, or `None` on timeout.
///
/// # Arguments
///
/// * `timer` - The `timer` parameter.
///
/// # Returns
///
/// An Optional value.
fn measure_bit_duration(timer: &HalTimer) -> Option<u32> {
if !wait_for_level(false) { return None; }
let start = time_us_32(timer);
if !wait_for_level(true) { return None; }
Some(time_us_32(timer).wrapping_sub(start))
}
/// Read a single bit from the DHT11 data stream.
///
/// Waits for the low-period to end, measures the high-period duration,
/// and accumulates the result into the data array via
/// [`dht11::accumulate_bit`].
///
/// # Arguments
///
/// * `data` - 5-byte array accumulating the received bits.
/// * `i` - Bit index (039).
/// * `timer` - Reference to the HAL timer for microsecond measurement.
///
/// # Returns
///
/// `true` on success, `false` on timeout.
///
/// # Arguments
///
/// * `data` - Data to send/write.
/// * `i` - The `i` parameter.
/// * `timer` - The `timer` parameter.
///
/// # Returns
///
/// `true` if successful or set, `false` otherwise.
fn read_bit(data: &mut [u8; 5], i: usize, timer: &HalTimer) -> bool {
let Some(duration) = measure_bit_duration(timer) else { return false; };
dht11::accumulate_bit(data, i, duration); true
}
/// Read all 40 data bits from the DHT11.
///
/// # Arguments
///
/// * `data` - 5-byte array filled with the received data.
/// * `timer` - Reference to the HAL timer for microsecond measurement.
///
/// # Returns
///
/// `true` if all 40 bits were read, `false` on timeout.
///
/// # Arguments
///
/// * `data` - Data to send/write.
/// * `timer` - The `timer` parameter.
///
/// # Returns
///
/// `true` if successful or set, `false` otherwise.
fn read_40_bits(data: &mut [u8; 5], timer: &HalTimer) -> bool {
for i in 0..40 { if !read_bit(data, i, timer) { return false; } }
true
}
/// Run the full DHT11 acquisition sequence (start, response, 40 bits, checksum).
///
/// # Arguments
///
/// * `timer` - The `timer` parameter.
/// * `delay` - Delay value.
/// * `data` - Data to send/write.
///
/// # Returns
///
/// `true` if successful or set, `false` otherwise.
///
/// # Arguments
///
/// * `timer` - The `timer` parameter.
/// * `delay` - Delay value.
/// * `data` - Data to send/write.
///
/// # Returns
///
/// `true` if successful or set, `false` otherwise.
fn acquire_data(timer: &HalTimer, delay: &mut cortex_m::delay::Delay, data: &mut [u8; 5]) -> bool {
send_start_signal(delay);
wait_response() && read_40_bits(data, timer) && dht11::validate_checksum(data)
}
/// Execute the full DHT11 read protocol.
///
/// Sends the start signal, waits for the sensor response, reads 40 bits
/// of data, validates the checksum, and parses humidity and temperature.
///
/// # Arguments
///
/// * `timer` - Reference to the HAL timer for microsecond measurement.
/// * `delay` - Mutable reference to the blocking delay provider.
///
/// # Returns
///
/// `Some((humidity, temperature))` on success, `None` on failure.
pub(crate) fn read_sensor(t: &HalTimer, d: &mut cortex_m::delay::Delay) -> Option<(f32, f32)> {
let mut data = [0u8; 5];
if !acquire_data(t, d, &mut data) { return None; }
Some((dht11::parse_humidity(&data), dht11::parse_temperature(&data)))
}
/// Format the sensor result (or error) with CRLF into `buf`.
///
/// # Arguments
///
/// * `buf` - The `buf` parameter.
/// * `result` - The `result` parameter.
/// * `f32)>` - The `f32)>` parameter.
///
/// # Returns
///
/// A value of type `usize`.
///
/// # Arguments
///
/// * `buf` - The `buf` parameter.
/// * `result` - The `result` parameter.
/// * `f32)>` - The `f32)>` parameter.
///
/// # Returns
///
/// A value of type `usize`.
fn format_sensor_output(buf: &mut [u8], result: Option<(f32, f32)>) -> usize {
let n = match result { Some((h, t)) => dht11::format_reading(buf, h, t), None => dht11::format_error(buf, DHT11_GPIO), };
buf[n] = b'\r'; buf[n + 1] = b'\n'; n + 2
}
/// Read the sensor, format the result, write it over UART, and wait.
///
/// On a successful read, prints humidity and temperature; on failure,
/// prints a wiring-check message. Always waits [`POLL_MS`] before
/// returning to respect the DHT11 minimum polling interval.
///
/// # Arguments
///
/// * `uart` - Reference to the enabled UART peripheral for serial output.
/// * `timer` - Reference to the HAL timer for microsecond measurement.
/// * `delay` - Mutable reference to the blocking delay provider.
pub(crate) fn poll_sensor(u: &EnabledUart, t: &HalTimer, d: &mut cortex_m::delay::Delay) {
let mut buf = [0u8; 64]; let n = format_sensor_output(&mut buf, read_sensor(t, d));
u.write_full_blocking(&buf[..n]); d.delay_ms(POLL_MS);
}
/// Initialise all peripherals and run the DHT11 sensor 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 c = init_clocks(p.XOSC, p.CLOCKS, p.PLL_SYS, p.PLL_USB, &mut p.RESETS, &mut hal::Watchdog::new(p.WATCHDOG));
let pins = init_pins(p.IO_BANK0, p.PADS_BANK0, p.SIO, &mut p.RESETS);
let (u, mut d) = (init_uart(p.UART0, pins.gpio0, pins.gpio1, &mut p.RESETS, &c), init_delay(&c));
#[cfg(rp2350)] let t = hal::Timer::new_timer0(p.TIMER0, &mut p.RESETS, &c); #[cfg(rp2040)] let t = hal::Timer::new(p.TIMER, &mut p.RESETS);
let _ = pins.gpio4.into_pull_up_input(); u.write_full_blocking(b"DHT11 driver initialized on GPIO 4\r\n");
loop { poll_sensor(&u, &t, &mut d); }
}
+510
View File
@@ -0,0 +1,510 @@
//! Implementation module
//!
//! **File:** `dht11.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.
/// Bit-duration threshold in microseconds.
///
/// If the high pulse is longer than this value, the bit is a 1; otherwise 0.
pub const BIT_THRESHOLD_US: u32 = 40;
/// Timeout in spin-loop iterations for waiting on a pin level change.
pub const LEVEL_WAIT_TIMEOUT: u32 = 10_000;
/// Accumulate a single received bit into the 5-byte data array.
///
/// Shifts `data[byte_index]` left by one and sets the LSB to 1 if the
/// measured high-pulse `duration_us` exceeds [`BIT_THRESHOLD_US`].
///
/// # Arguments
///
/// * `data` - 5-byte array accumulating the received bits.
/// * `bit_index` - Bit index (0..39).
/// * `duration_us` - Measured duration of the high pulse in microseconds.
///
/// # Arguments
///
/// * `data` - Data to send/write.
/// * `bit_index` - The `bit_index` parameter.
/// * `duration_us` - The `duration_us` parameter.
#[inline]
pub fn accumulate_bit(data: &mut [u8; 5], bit_index: usize, duration_us: u32) {
let byte = bit_index / 8;
data[byte] <<= 1;
if duration_us > BIT_THRESHOLD_US { data[byte] |= 1; }
}
/// Verify the DHT11 checksum byte.
///
/// The checksum (byte 4) must equal the lower 8 bits of the sum of
/// bytes 0 through 3.
///
/// # Arguments
///
/// * `data` - 5-byte received data (bytes 03 plus checksum in byte 4).
///
/// # Returns
///
/// `true` if the checksum matches, `false` otherwise.
///
/// # Arguments
///
/// * `data` - Data to send/write.
///
/// # Returns
///
/// `true` if successful or set, `false` otherwise.
#[inline]
pub fn validate_checksum(data: &[u8; 5]) -> bool {
data[4] == data[0].wrapping_add(data[1]).wrapping_add(data[2]).wrapping_add(data[3])
}
/// Parse humidity from the raw DHT11 data bytes.
///
/// Humidity is encoded as an integer part in byte 0 and a fractional
/// part (tenths) in byte 1.
///
/// # Arguments
///
/// * `data` - 5-byte received data.
///
/// # Returns
///
/// Humidity as a percentage (e.g. 65.3 for 65.3%).
///
/// # Arguments
///
/// * `data` - Data to send/write.
///
/// # Returns
///
/// A value of type `f32`.
#[inline]
pub fn parse_humidity(data: &[u8; 5]) -> f32 { data[0] as f32 + data[1] as f32 * 0.1 }
/// Parse temperature from the raw DHT11 data bytes.
///
/// Temperature is encoded as an integer part in byte 2 and a fractional
/// part (tenths) in byte 3.
///
/// # Arguments
///
/// * `data` - 5-byte received data.
///
/// # Returns
///
/// Temperature in degrees Celsius.
///
/// # Arguments
///
/// * `data` - Data to send/write.
///
/// # Returns
///
/// A value of type `f32`.
#[inline]
pub fn parse_temperature(data: &[u8; 5]) -> f32 { data[2] as f32 + data[3] as f32 * 0.1 }
/// Format a sensor reading as `"Humidity: XX.X% Temperature: XX.X C"`.
///
/// # Arguments
///
/// * `buf` - Mutable byte slice (must be at least 40 bytes).
/// * `humidity` - Humidity percentage.
/// * `temperature` - Temperature in degrees Celsius.
///
/// # Returns
///
/// Number of bytes written into the buffer.
///
/// # Arguments
///
/// * `buf` - The `buf` parameter.
/// * `humidity` - The `humidity` parameter.
/// * `temperature` - The `temperature` parameter.
///
/// # Returns
///
/// A value of type `usize`.
#[inline]
pub fn format_reading(buf: &mut [u8], humidity: f32, temperature: f32) -> usize {
let mut pos = copy_slice(buf, 0, b"Humidity: ");
pos += format_f32_1(&mut buf[pos..], humidity);
pos += copy_slice(buf, pos, b"% Temperature: ");
pos += format_f32_1(&mut buf[pos..], temperature);
pos + copy_slice(buf, pos, b" C")
}
/// Copy a byte slice into `buf` at the given offset, returning bytes written.
///
/// # Arguments
///
/// * `buf` - The `buf` parameter.
/// * `offset` - The `offset` parameter.
/// * `src` - The `src` parameter.
///
/// # Returns
///
/// A value of type `usize`.
///
/// # Arguments
///
/// * `buf` - The `buf` parameter.
/// * `offset` - The `offset` parameter.
/// * `src` - The `src` parameter.
///
/// # Returns
///
/// A value of type `usize`.
#[inline]
fn copy_slice(buf: &mut [u8], offset: usize, src: &[u8]) -> usize {
buf[offset..offset + src.len()].copy_from_slice(src); src.len()
}
/// Format a failed-read error message.
///
/// # Arguments
///
/// * `buf` - Mutable byte slice (must be at least 42 bytes).
/// * `gpio` - GPIO pin number to include in the message.
///
/// # Returns
///
/// Number of bytes written into the buffer.
///
/// # Arguments
///
/// * `buf` - The `buf` parameter.
/// * `gpio` - The `gpio` parameter.
///
/// # Returns
///
/// A value of type `usize`.
#[inline]
pub fn format_error(buf: &mut [u8], gpio: u8) -> usize {
let p = b"DHT11 read failed - check wiring on GPIO ";
buf[..p.len()].copy_from_slice(p);
p.len() + format_u8(&mut buf[p.len()..], gpio)
}
/// Return the number of decimal digits needed for a `u8`.
///
/// # Arguments
///
/// * `val` - Value to use.
///
/// # Returns
///
/// A value of type `usize`.
///
/// # Arguments
///
/// * `val` - Value to use.
///
/// # Returns
///
/// A value of type `usize`.
#[inline]
fn u8_digit_count(val: u8) -> usize {
if val >= 100 { 3 } else if val >= 10 { 2 } else { 1 }
}
/// Write `count` decimal digits of `val` into `buf`.
///
/// # Arguments
///
/// * `buf` - The `buf` parameter.
/// * `val` - Value to use.
/// * `count` - The `count` parameter.
///
/// # Arguments
///
/// * `buf` - The `buf` parameter.
/// * `val` - Value to use.
/// * `count` - The `count` parameter.
#[inline]
fn write_u8_digits(buf: &mut [u8], val: u8, count: usize) {
if count >= 3 { buf[0] = b'0' + val / 100; }
if count >= 2 { buf[count - 2] = b'0' + (val / 10) % 10; }
buf[count - 1] = b'0' + val % 10;
}
/// Format a `u8` as decimal ASCII digits.
///
/// # Arguments
///
/// * `buf` - Output buffer.
/// * `val` - Value to format.
///
/// # Returns
///
/// Number of bytes written.
///
/// # Arguments
///
/// * `buf` - The `buf` parameter.
/// * `val` - Value to use.
///
/// # Returns
///
/// A value of type `usize`.
#[inline]
fn format_u8(buf: &mut [u8], val: u8) -> usize {
let n = u8_digit_count(val); write_u8_digits(buf, val, n); n
}
/// Format an `f32` with one decimal place (e.g. `"25.3"`).
///
/// # Arguments
///
/// * `buf` - Output buffer.
/// * `val` - Value to format.
///
/// # Returns
///
/// Number of bytes written.
///
/// # Arguments
///
/// * `buf` - The `buf` parameter.
/// * `val` - Value to use.
///
/// # Returns
///
/// A value of type `usize`.
#[inline]
fn format_f32_1(buf: &mut [u8], val: f32) -> usize {
let scaled = (val * 10.0) as u32;
let pos = format_u32_minimal(buf, scaled / 10);
buf[pos] = b'.'; buf[pos + 1] = b'0' + (scaled % 10) as u8;
pos + 2
}
/// Write a conditional digit into `buf` if `val` meets the threshold.
///
/// # Arguments
///
/// * `buf` - The `buf` parameter.
/// * `pos` - The `pos` parameter.
/// * `val` - Value to use.
/// * `threshold` - The `threshold` parameter.
/// * `divisor` - The `divisor` parameter.
///
/// # Arguments
///
/// * `buf` - The `buf` parameter.
/// * `pos` - The `pos` parameter.
/// * `val` - Value to use.
/// * `threshold` - The `threshold` parameter.
/// * `divisor` - The `divisor` parameter.
#[inline]
fn write_digit_if(buf: &mut [u8], pos: &mut usize, val: u32, threshold: u32, divisor: u32) {
if val >= threshold { buf[*pos] = b'0' + ((val / divisor) % 10) as u8; *pos += 1; }
}
/// Format a u32 as minimal decimal digits (no leading zeros).
///
/// # Arguments
///
/// * `buf` - The `buf` parameter.
/// * `value` - Value to use.
///
/// # Returns
///
/// A value of type `usize`.
///
/// # Arguments
///
/// * `buf` - The `buf` parameter.
/// * `value` - Value to use.
///
/// # Returns
///
/// A value of type `usize`.
#[inline]
fn format_u32_minimal(buf: &mut [u8], value: u32) -> usize {
let mut pos = 0;
write_digit_if(buf, &mut pos, value, 100, 100); write_digit_if(buf, &mut pos, value, 10, 10);
buf[pos] = b'0' + (value % 10) as u8; pos + 1
}
#[cfg(test)]
mod tests {
// Import all parent module items
use super::*;
/// Executes the accumulate bit zero short pulse operation.
#[test]
fn accumulate_bit_zero_short_pulse() {
let mut data = [0u8; 5];
accumulate_bit(&mut data, 0, 30);
assert_eq!(data[0], 0);
}
/// Executes the accumulate bit one long pulse operation.
#[test]
fn accumulate_bit_one_long_pulse() {
let mut data = [0u8; 5];
accumulate_bit(&mut data, 0, 70);
assert_eq!(data[0], 1);
}
/// Executes the accumulate bit threshold exact operation.
#[test]
fn accumulate_bit_threshold_exact() {
let mut data = [0u8; 5];
accumulate_bit(&mut data, 0, BIT_THRESHOLD_US);
assert_eq!(data[0], 0);
}
/// Executes the accumulate bit two bits operation.
#[test]
fn accumulate_bit_two_bits() {
let mut data = [0u8; 5];
accumulate_bit(&mut data, 0, 70);
accumulate_bit(&mut data, 1, 30);
assert_eq!(data[0], 0b10);
}
/// Executes the accumulate bit crosses byte operation.
#[test]
fn accumulate_bit_crosses_byte() {
let mut data = [0u8; 5];
accumulate_bit(&mut data, 7, 70);
accumulate_bit(&mut data, 8, 70);
assert_eq!(data[0], 1);
assert_eq!(data[1], 1);
}
/// Executes the validate checksum valid operation.
#[test]
fn validate_checksum_valid() {
let data = [0x28, 0x00, 0x1A, 0x00, 0x42];
assert!(validate_checksum(&data));
}
/// Executes the validate checksum invalid operation.
#[test]
fn validate_checksum_invalid() {
let data = [0x28, 0x00, 0x1A, 0x00, 0xFF];
assert!(!validate_checksum(&data));
}
/// Executes the validate checksum wraps operation.
#[test]
fn validate_checksum_wraps() {
let data = [0xFF, 0x01, 0x00, 0x00, 0x00];
assert!(validate_checksum(&data));
}
/// Executes the parse humidity integer only operation.
#[test]
fn parse_humidity_integer_only() {
let data = [0x41, 0x00, 0x00, 0x00, 0x41];
let h = parse_humidity(&data);
assert!((h - 65.0).abs() < 0.01);
}
/// Executes the parse humidity with fraction operation.
#[test]
fn parse_humidity_with_fraction() {
let data = [0x41, 0x03, 0x00, 0x00, 0x44];
let h = parse_humidity(&data);
assert!((h - 65.3).abs() < 0.01);
}
/// Executes the parse temperature integer only operation.
#[test]
fn parse_temperature_integer_only() {
let data = [0x00, 0x00, 0x19, 0x00, 0x19];
let t = parse_temperature(&data);
assert!((t - 25.0).abs() < 0.01);
}
/// Executes the parse temperature with fraction operation.
#[test]
fn parse_temperature_with_fraction() {
let data = [0x00, 0x00, 0x19, 0x05, 0x1E];
let t = parse_temperature(&data);
assert!((t - 25.5).abs() < 0.01);
}
/// Executes the format reading typical operation.
#[test]
fn format_reading_typical() {
let mut buf = [0u8; 48];
let n = format_reading(&mut buf, 65.0, 25.0);
assert_eq!(&buf[..n], b"Humidity: 65.0% Temperature: 25.0 C");
}
/// Executes the format reading with fractions operation.
#[test]
fn format_reading_with_fractions() {
let mut buf = [0u8; 48];
let n = format_reading(&mut buf, 65.3, 25.5);
assert_eq!(&buf[..n], b"Humidity: 65.3% Temperature: 25.5 C");
}
/// Executes the format error gpio4 operation.
#[test]
fn format_error_gpio4() {
let mut buf = [0u8; 48];
let n = format_error(&mut buf, 4);
assert_eq!(&buf[..n], b"DHT11 read failed - check wiring on GPIO 4");
}
/// Executes the format error gpio15 operation.
#[test]
fn format_error_gpio15() {
let mut buf = [0u8; 48];
let n = format_error(&mut buf, 15);
assert_eq!(&buf[..n], b"DHT11 read failed - check wiring on GPIO 15");
}
/// Executes the format u8 single digit operation.
#[test]
fn format_u8_single_digit() {
let mut buf = [0u8; 4];
let n = format_u8(&mut buf, 4);
assert_eq!(&buf[..n], b"4");
}
/// Executes the format u8 two digits operation.
#[test]
fn format_u8_two_digits() {
let mut buf = [0u8; 4];
let n = format_u8(&mut buf, 15);
assert_eq!(&buf[..n], b"15");
}
/// Executes the format u8 three digits operation.
#[test]
fn format_u8_three_digits() {
let mut buf = [0u8; 4];
let n = format_u8(&mut buf, 255);
assert_eq!(&buf[..n], b"255");
}
}
+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 dht11;
+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;
// DHT11 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 DHT11 sensor 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"DHT11 Sensor 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::*;
}