From c68e158f1690cd63b93d6d03900cdfe2ff64a3da Mon Sep 17 00:00:00 2001 From: Kevin Thomas Date: Wed, 25 Mar 2026 17:31:05 -0400 Subject: [PATCH] feat: add 0x08_lcd1602_rust driver with 14 unit tests --- README.md | 1 + drivers/0x08_lcd1602_rust/.cargo/config.toml | 102 ++++++ drivers/0x08_lcd1602_rust/.gitignore | 113 ++++++ drivers/0x08_lcd1602_rust/.pico-rs | 1 + .../0x08_lcd1602_rust/.vscode/extensions.json | 8 + drivers/0x08_lcd1602_rust/.vscode/launch.json | 41 +++ .../0x08_lcd1602_rust/.vscode/settings.json | 8 + drivers/0x08_lcd1602_rust/.vscode/tasks.json | 124 +++++++ drivers/0x08_lcd1602_rust/Cargo.toml | 40 +++ drivers/0x08_lcd1602_rust/LICENSE-APACHE | 204 +++++++++++ drivers/0x08_lcd1602_rust/LICENSE-MIT | 24 ++ drivers/0x08_lcd1602_rust/build.rs | 68 ++++ drivers/0x08_lcd1602_rust/rp2040.x | 91 +++++ drivers/0x08_lcd1602_rust/rp2350.x | 83 +++++ drivers/0x08_lcd1602_rust/rp2350_riscv.x | 259 ++++++++++++++ drivers/0x08_lcd1602_rust/src/lcd1602.rs | 143 ++++++++ drivers/0x08_lcd1602_rust/src/lib.rs | 3 + drivers/0x08_lcd1602_rust/src/main.rs | 326 ++++++++++++++++++ 18 files changed, 1639 insertions(+) create mode 100644 drivers/0x08_lcd1602_rust/.cargo/config.toml create mode 100644 drivers/0x08_lcd1602_rust/.gitignore create mode 100644 drivers/0x08_lcd1602_rust/.pico-rs create mode 100644 drivers/0x08_lcd1602_rust/.vscode/extensions.json create mode 100644 drivers/0x08_lcd1602_rust/.vscode/launch.json create mode 100644 drivers/0x08_lcd1602_rust/.vscode/settings.json create mode 100644 drivers/0x08_lcd1602_rust/.vscode/tasks.json create mode 100644 drivers/0x08_lcd1602_rust/Cargo.toml create mode 100644 drivers/0x08_lcd1602_rust/LICENSE-APACHE create mode 100644 drivers/0x08_lcd1602_rust/LICENSE-MIT create mode 100644 drivers/0x08_lcd1602_rust/build.rs create mode 100644 drivers/0x08_lcd1602_rust/rp2040.x create mode 100644 drivers/0x08_lcd1602_rust/rp2350.x create mode 100644 drivers/0x08_lcd1602_rust/rp2350_riscv.x create mode 100644 drivers/0x08_lcd1602_rust/src/lcd1602.rs create mode 100644 drivers/0x08_lcd1602_rust/src/lib.rs create mode 100644 drivers/0x08_lcd1602_rust/src/main.rs diff --git a/README.md b/README.md index d475383..0bf8cc6 100644 --- a/README.md +++ b/README.md @@ -530,6 +530,7 @@ cargo test --lib --target x86_64-pc-windows-msvc | [0x05_servo_rust](drivers/0x05_servo_rust) | SG90 servo motor control | `rp235x-hal`, `embedded-hal` | | [0x06_adc_rust](drivers/0x06_adc_rust) | 12-bit ADC voltage + temperature | `rp235x-hal`, `embedded-hal` | | [0x07_i2c_rust](drivers/0x07_i2c_rust) | I2C bus scanner (7-bit addresses) | `rp235x-hal`, `embedded-hal` | +| [0x08_lcd1602_rust](drivers/0x08_lcd1602_rust) | HD44780 16x2 LCD (PCF8574 I2C) | `rp235x-hal`, `embedded-hal` |
diff --git a/drivers/0x08_lcd1602_rust/.cargo/config.toml b/drivers/0x08_lcd1602_rust/.cargo/config.toml new file mode 100644 index 0000000..1791d74 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/.cargo/config.toml @@ -0,0 +1,102 @@ +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2021–2024 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" diff --git a/drivers/0x08_lcd1602_rust/.gitignore b/drivers/0x08_lcd1602_rust/.gitignore new file mode 100644 index 0000000..03381c1 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/.gitignore @@ -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 diff --git a/drivers/0x08_lcd1602_rust/.pico-rs b/drivers/0x08_lcd1602_rust/.pico-rs new file mode 100644 index 0000000..1b6702a --- /dev/null +++ b/drivers/0x08_lcd1602_rust/.pico-rs @@ -0,0 +1 @@ +rp2350 \ No newline at end of file diff --git a/drivers/0x08_lcd1602_rust/.vscode/extensions.json b/drivers/0x08_lcd1602_rust/.vscode/extensions.json new file mode 100644 index 0000000..5f2fd0c --- /dev/null +++ b/drivers/0x08_lcd1602_rust/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "rust-lang.rust-analyzer", + "probe-rs.probe-rs-debugger", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/drivers/0x08_lcd1602_rust/.vscode/launch.json b/drivers/0x08_lcd1602_rust/.vscode/launch.json new file mode 100644 index 0000000..0bc38c3 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/.vscode/launch.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/drivers/0x08_lcd1602_rust/.vscode/settings.json b/drivers/0x08_lcd1602_rust/.vscode/settings.json new file mode 100644 index 0000000..b03cd57 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/.vscode/settings.json @@ -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 + } +} \ No newline at end of file diff --git a/drivers/0x08_lcd1602_rust/.vscode/tasks.json b/drivers/0x08_lcd1602_rust/.vscode/tasks.json new file mode 100644 index 0000000..4194af4 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/.vscode/tasks.json @@ -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": [] + } + ] +} \ No newline at end of file diff --git a/drivers/0x08_lcd1602_rust/Cargo.toml b/drivers/0x08_lcd1602_rust/Cargo.toml new file mode 100644 index 0000000..fc2f7c4 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/Cargo.toml @@ -0,0 +1,40 @@ +[package] +edition = "2024" +name = "lcd1602" +version = "0.1.0" +license = "MIT or Apache-2.0" + +[lib] +name = "lcd1602_lib" +path = "src/lib.rs" + +[[bin]] +name = "lcd1602" +path = "src/main.rs" + +[build-dependencies] +regex = "1.11.0" + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +embedded-hal = "1.0.0" +fugit = "0.3" +defmt = "1" +defmt-rtt = "1" + +[target.'cfg( target_arch = "arm" )'.dependencies] +panic-probe = { version = "1", features = ["print-defmt"] } + +[target.'cfg( target_arch = "riscv32" )'.dependencies] +panic-halt = { version = "1.0.0" } + +[target.thumbv6m-none-eabi.dependencies] +rp2040-boot2 = "0.3" +rp2040-hal = { version = "0.11", features = ["rt", "critical-section-impl"] } + +[target.riscv32imac-unknown-none-elf.dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +[target."thumbv8m.main-none-eabihf".dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } diff --git a/drivers/0x08_lcd1602_rust/LICENSE-APACHE b/drivers/0x08_lcd1602_rust/LICENSE-APACHE new file mode 100644 index 0000000..8d99cbc --- /dev/null +++ b/drivers/0x08_lcd1602_rust/LICENSE-APACHE @@ -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) 2021–2024 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. \ No newline at end of file diff --git a/drivers/0x08_lcd1602_rust/LICENSE-MIT b/drivers/0x08_lcd1602_rust/LICENSE-MIT new file mode 100644 index 0000000..5369c70 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/LICENSE-MIT @@ -0,0 +1,24 @@ +MIT License + +Copyright (c) 2021–2024 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. + \ No newline at end of file diff --git a/drivers/0x08_lcd1602_rust/build.rs b/drivers/0x08_lcd1602_rust/build.rs new file mode 100644 index 0000000..e6de3f1 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/build.rs @@ -0,0 +1,68 @@ +//! SPDX-License-Identifier: MIT OR Apache-2.0 +//! +//! Copyright (c) 2021–2024 The rp-rs Developers +//! Copyright (c) 2021 rp-rs organization +//! Copyright (c) 2025 Raspberry Pi Ltd. +//! +//! Set up linker scripts + +use std::fs::{ File, read_to_string }; +use std::io::Write; +use std::path::PathBuf; + +use regex::Regex; + +fn main() { + println!("cargo::rustc-check-cfg=cfg(rp2040)"); + println!("cargo::rustc-check-cfg=cfg(rp2350)"); + + // Put the linker script somewhere the linker can find it + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + println!("cargo:rustc-link-search={}", out.display()); + + println!("cargo:rerun-if-changed=.pico-rs"); + let contents = read_to_string(".pico-rs") + .map(|s| s.trim().to_string().to_lowercase()) + .unwrap_or_else(|e| { + eprintln!("Failed to read file: {}", e); + String::new() + }); + + // The file `memory.x` is loaded by cortex-m-rt's `link.x` script, which + // is what we specify in `.cargo/config.toml` for Arm builds + let target; + if contents == "rp2040" { + target = "thumbv6m-none-eabi"; + let memory_x = include_bytes!("rp2040.x"); + let mut f = File::create(out.join("memory.x")).unwrap(); + f.write_all(memory_x).unwrap(); + println!("cargo::rustc-cfg=rp2040"); + println!("cargo:rerun-if-changed=rp2040.x"); + } else { + if contents.contains("riscv") { + target = "riscv32imac-unknown-none-elf"; + } else { + target = "thumbv8m.main-none-eabihf"; + } + let memory_x = include_bytes!("rp2350.x"); + let mut f = File::create(out.join("memory.x")).unwrap(); + f.write_all(memory_x).unwrap(); + println!("cargo::rustc-cfg=rp2350"); + println!("cargo:rerun-if-changed=rp2350.x"); + } + + let re = Regex::new(r"target = .*").unwrap(); + let config_toml = include_str!(".cargo/config.toml"); + let result = re.replace(config_toml, format!("target = \"{}\"", target)); + let mut f = File::create(".cargo/config.toml").unwrap(); + f.write_all(result.as_bytes()).unwrap(); + + // The file `rp2350_riscv.x` is what we specify in `.cargo/config.toml` for + // RISC-V builds + let rp2350_riscv_x = include_bytes!("rp2350_riscv.x"); + let mut f = File::create(out.join("rp2350_riscv.x")).unwrap(); + f.write_all(rp2350_riscv_x).unwrap(); + println!("cargo:rerun-if-changed=rp2350_riscv.x"); + + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/drivers/0x08_lcd1602_rust/rp2040.x b/drivers/0x08_lcd1602_rust/rp2040.x new file mode 100644 index 0000000..0cc665b --- /dev/null +++ b/drivers/0x08_lcd1602_rust/rp2040.x @@ -0,0 +1,91 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 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; diff --git a/drivers/0x08_lcd1602_rust/rp2350.x b/drivers/0x08_lcd1602_rust/rp2350.x new file mode 100644 index 0000000..bda94d8 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/rp2350.x @@ -0,0 +1,83 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 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); + \ No newline at end of file diff --git a/drivers/0x08_lcd1602_rust/rp2350_riscv.x b/drivers/0x08_lcd1602_rust/rp2350_riscv.x new file mode 100644 index 0000000..84388aa --- /dev/null +++ b/drivers/0x08_lcd1602_rust/rp2350_riscv.x @@ -0,0 +1,259 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 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 | */ diff --git a/drivers/0x08_lcd1602_rust/src/lcd1602.rs b/drivers/0x08_lcd1602_rust/src/lcd1602.rs new file mode 100644 index 0000000..fc3d114 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/src/lcd1602.rs @@ -0,0 +1,143 @@ +/// PCF8574 -> LCD control pin: Register Select. +pub const PIN_RS: u8 = 0x01; + +/// PCF8574 -> LCD control pin: Read/Write. +pub const PIN_RW: u8 = 0x02; + +/// PCF8574 -> LCD control pin: Enable. +pub const PIN_EN: u8 = 0x04; + +/// Build a PCF8574 output byte for a 4-bit LCD nibble. +pub fn build_nibble(nibble: u8, nibble_shift: u8, mode: u8, backlight_mask: u8) -> u8 { + let mut data = (nibble & 0x0F) << nibble_shift; + if mode != 0 { data |= PIN_RS; } + data |= backlight_mask; + data +} + +/// Build the PCF8574 byte with EN asserted. +pub fn nibble_with_en(nibble_byte: u8) -> u8 { + nibble_byte | PIN_EN +} + +/// Build the PCF8574 byte with EN de-asserted. +pub fn nibble_without_en(nibble_byte: u8) -> u8 { + nibble_byte & !PIN_EN +} + +/// HD44780 row-offset lookup. +const ROW_OFFSETS: [u8; 2] = [0x00, 0x40]; + +/// Compute the DDRAM address byte for `lcd_set_cursor`. +pub fn cursor_address(line: u8, position: u8) -> u8 { + let row = if line > 1 { 1 } else { line as usize }; + 0x80 | (position + ROW_OFFSETS[row]) +} + +/// Format a counter value as `"Count: NNNNNN"` (right-justified, 6 digits). +pub fn format_counter(buf: &mut [u8], count: u32) -> usize { + let prefix = b"Count: "; + buf[..7].copy_from_slice(prefix); + let mut pos = 7; + let digits = [ + ((count / 100000) % 10) as u8, + ((count / 10000) % 10) as u8, + ((count / 1000) % 10) as u8, + ((count / 100) % 10) as u8, + ((count / 10) % 10) as u8, + (count % 10) as u8, + ]; + let mut leading = true; + for (i, &d) in digits.iter().enumerate() { + if i == 5 || d != 0 { leading = false; } + buf[pos] = if leading { b' ' } else { b'0' + d }; + pos += 1; + } + pos +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn build_nibble_command_mode() { + let b = build_nibble(0x03, 4, 0, 0x08); + assert_eq!(b, 0x38); + } + + #[test] + fn build_nibble_data_mode() { + let b = build_nibble(0x04, 4, 1, 0x08); + assert_eq!(b, 0x49); + } + + #[test] + fn build_nibble_no_backlight() { + let b = build_nibble(0x0F, 4, 0, 0x00); + assert_eq!(b, 0xF0); + } + + #[test] + fn nibble_with_en_sets_bit() { + assert_eq!(nibble_with_en(0x38), 0x3C); + } + + #[test] + fn nibble_without_en_clears_bit() { + assert_eq!(nibble_without_en(0x3C), 0x38); + } + + #[test] + fn cursor_address_line0_col0() { + assert_eq!(cursor_address(0, 0), 0x80); + } + + #[test] + fn cursor_address_line1_col0() { + assert_eq!(cursor_address(1, 0), 0xC0); + } + + #[test] + fn cursor_address_line0_col5() { + assert_eq!(cursor_address(0, 5), 0x85); + } + + #[test] + fn cursor_address_line1_col15() { + assert_eq!(cursor_address(1, 15), 0xCF); + } + + #[test] + fn cursor_address_clamps_line() { + assert_eq!(cursor_address(5, 0), 0xC0); + } + + #[test] + fn format_counter_zero() { + let mut buf = [0u8; 16]; + let n = format_counter(&mut buf, 0); + assert_eq!(&buf[..n], b"Count: 0"); + } + + #[test] + fn format_counter_one() { + let mut buf = [0u8; 16]; + let n = format_counter(&mut buf, 1); + assert_eq!(&buf[..n], b"Count: 1"); + } + + #[test] + fn format_counter_large() { + let mut buf = [0u8; 16]; + let n = format_counter(&mut buf, 123456); + assert_eq!(&buf[..n], b"Count: 123456"); + } + + #[test] + fn format_counter_six_digits() { + let mut buf = [0u8; 16]; + let n = format_counter(&mut buf, 999999); + assert_eq!(&buf[..n], b"Count: 999999"); + } +} diff --git a/drivers/0x08_lcd1602_rust/src/lib.rs b/drivers/0x08_lcd1602_rust/src/lib.rs new file mode 100644 index 0000000..6f93728 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/src/lib.rs @@ -0,0 +1,3 @@ +#![no_std] + +pub mod lcd1602; diff --git a/drivers/0x08_lcd1602_rust/src/main.rs b/drivers/0x08_lcd1602_rust/src/main.rs new file mode 100644 index 0000000..4b0586a --- /dev/null +++ b/drivers/0x08_lcd1602_rust/src/main.rs @@ -0,0 +1,326 @@ +//! @file main.rs +//! @brief HD44780 16x2 LCD (PCF8574 I2C backpack) demonstration +//! @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. +//! +//! ----------------------------------------------------------------------------- +//! +//! Demonstrates 16x2 HD44780 LCD connected via a PCF8574 I2C backpack using +//! the lcd1602 driver (lcd1602.rs). Line 0 shows a static title and line 1 +//! displays a live up-counter that increments every second. The counter +//! value is also printed over UART for debugging. +//! +//! Wiring: +//! GPIO2 (SDA) -> PCF8574 backpack SDA (4.7 kohm pull-up to 3.3 V) +//! GPIO3 (SCL) -> PCF8574 backpack SCL (4.7 kohm pull-up to 3.3 V) +//! 3.3V or 5V -> PCF8574 backpack VCC +//! GND -> PCF8574 backpack GND + +#![no_std] +#![no_main] + +#[allow(dead_code)] +mod lcd1602; + +use defmt_rtt as _; +#[cfg(target_arch = "riscv32")] +use panic_halt as _; +#[cfg(target_arch = "arm")] +use panic_probe as _; + +use embedded_hal::i2c::I2c; +use fugit::RateExtU32; +use hal::entry; +use hal::Clock; +use hal::gpio::{FunctionI2C, FunctionNull, FunctionUart, Pin, PullDown, PullNone, PullUp}; +use hal::uart::{DataBits, Enabled, StopBits, UartConfig, UartPeripheral}; + +#[cfg(rp2350)] +use rp235x_hal as hal; + +#[cfg(rp2040)] +use rp2040_hal as hal; + +#[unsafe(link_section = ".boot2")] +#[used] +#[cfg(rp2040)] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +#[unsafe(link_section = ".start_block")] +#[used] +#[cfg(rp2350)] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +const UART_BAUD: u32 = 115_200; +const I2C_BAUD: u32 = 100_000; +const LCD_I2C_ADDR: u8 = 0x27; +const NIBBLE_SHIFT: u8 = 4; +const BACKLIGHT_MASK: u8 = 0x08; +const COUNTER_DELAY_MS: u32 = 1_000; + +type TxPin = Pin; +type RxPin = Pin; +type TxPinDefault = Pin; +type RxPinDefault = Pin; +type EnabledUart = UartPeripheral; + +/// 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. +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. +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. +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::(), + rx_pin.reconfigure::(), + ); + 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. +fn init_delay(clocks: &hal::clocks::ClocksManager) -> cortex_m::delay::Delay { + let core = cortex_m::Peripherals::take().unwrap(); + cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()) +} + +/// Write one raw byte to the PCF8574 expander over I2C. +fn pcf_write_byte(i2c: &mut impl I2c, addr: u8, data: u8) { + let _ = i2c.write(addr, &[data]); +} + +/// Toggle EN to latch a nibble into the LCD controller. +fn pcf_pulse_enable(i2c: &mut impl I2c, addr: u8, data: u8, delay: &mut cortex_m::delay::Delay) { + pcf_write_byte(i2c, addr, lcd1602::nibble_with_en(data)); + delay.delay_us(1); + pcf_write_byte(i2c, addr, lcd1602::nibble_without_en(data)); + delay.delay_us(50); +} + +/// Write one 4-bit nibble to the LCD. +fn lcd_write4(i2c: &mut impl I2c, addr: u8, nibble: u8, mode: u8, delay: &mut cortex_m::delay::Delay) { + let data = lcd1602::build_nibble(nibble, NIBBLE_SHIFT, mode, BACKLIGHT_MASK); + pcf_pulse_enable(i2c, addr, data, delay); +} + +/// Send one full 8-bit command/data value as two nibbles. +fn lcd_send(i2c: &mut impl I2c, addr: u8, value: u8, mode: u8, delay: &mut cortex_m::delay::Delay) { + lcd_write4(i2c, addr, (value >> 4) & 0x0F, mode, delay); + lcd_write4(i2c, addr, value & 0x0F, mode, delay); +} + +/// Execute the HD44780 4-bit mode power-on reset sequence. +fn lcd_hd44780_reset(i2c: &mut impl I2c, addr: u8, delay: &mut cortex_m::delay::Delay) { + lcd_write4(i2c, addr, 0x03, 0, delay); + delay.delay_ms(5); + lcd_write4(i2c, addr, 0x03, 0, delay); + delay.delay_us(150); + lcd_write4(i2c, addr, 0x03, 0, delay); + delay.delay_us(150); + lcd_write4(i2c, addr, 0x02, 0, delay); + delay.delay_us(150); +} + +/// Send post-reset configuration commands to the HD44780. +fn lcd_hd44780_configure(i2c: &mut impl I2c, addr: u8, delay: &mut cortex_m::delay::Delay) { + lcd_send(i2c, addr, 0x28, 0, delay); + lcd_send(i2c, addr, 0x0C, 0, delay); + lcd_send(i2c, addr, 0x01, 0, delay); + delay.delay_ms(2); + lcd_send(i2c, addr, 0x06, 0, delay); +} + +/// Set the LCD cursor position. +fn lcd_set_cursor(i2c: &mut impl I2c, addr: u8, line: u8, position: u8, delay: &mut cortex_m::delay::Delay) { + lcd_send(i2c, addr, lcd1602::cursor_address(line, position), 0, delay); +} + +/// Write a byte slice as character data to the LCD. +fn lcd_puts(i2c: &mut impl I2c, addr: u8, s: &[u8], delay: &mut cortex_m::delay::Delay) { + for &ch in s { + lcd_send(i2c, addr, ch, 1, delay); + } +} + +/// Initialize the LCD, display the title, and log over UART. +fn setup_display( + i2c: &mut impl I2c, + uart: &EnabledUart, + delay: &mut cortex_m::delay::Delay, +) { + lcd_hd44780_reset(i2c, LCD_I2C_ADDR, delay); + lcd_hd44780_configure(i2c, LCD_I2C_ADDR, delay); + lcd_set_cursor(i2c, LCD_I2C_ADDR, 0, 0, delay); + lcd_puts(i2c, LCD_I2C_ADDR, b"Reverse Eng.", delay); + uart.write_full_blocking(b"LCD 1602 driver initialized at I2C addr 0x27\r\n"); +} + +/// Format and display the next counter value on LCD line 1. +fn update_counter( + i2c: &mut impl I2c, + uart: &EnabledUart, + delay: &mut cortex_m::delay::Delay, + count: &mut u32, +) { + let mut buf = [0u8; 16]; + let n = lcd1602::format_counter(&mut buf, *count); + *count += 1; + lcd_set_cursor(i2c, LCD_I2C_ADDR, 1, 0, delay); + lcd_puts(i2c, LCD_I2C_ADDR, &buf[..n], delay); + uart.write_full_blocking(&buf[..n]); + uart.write_full_blocking(b"\r\n"); + delay.delay_ms(COUNTER_DELAY_MS); +} + +/// Application entry point for the LCD 1602 counter demo. +/// +/// Initializes the LCD over I2C with a static title on line 0 and +/// continuously increments a counter on line 1 every second. +/// +/// # Returns +/// +/// Does not return. +#[entry] +fn main() -> ! { + let mut pac = hal::pac::Peripherals::take().unwrap(); + let clocks = init_clocks( + pac.XOSC, pac.CLOCKS, pac.PLL_SYS, pac.PLL_USB, &mut pac.RESETS, + &mut hal::Watchdog::new(pac.WATCHDOG), + ); + let pins = init_pins(pac.IO_BANK0, pac.PADS_BANK0, pac.SIO, &mut pac.RESETS); + let uart = init_uart(pac.UART0, pins.gpio0, pins.gpio1, &mut pac.RESETS, &clocks); + let mut delay = init_delay(&clocks); + let sda_pin = pins.gpio2.reconfigure::(); + let scl_pin = pins.gpio3.reconfigure::(); + let mut i2c = hal::I2C::i2c1( + pac.I2C1, sda_pin, scl_pin, I2C_BAUD.Hz(), + &mut pac.RESETS, clocks.system_clock.freq(), + ); + setup_display(&mut i2c, &uart, &mut delay); + let mut count: u32 = 0; + loop { + update_counter(&mut i2c, &uart, &mut delay, &mut count); + } +} + +#[unsafe(link_section = ".bi_entries")] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"LCD 1602 Counter Demo"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file