init project

This commit is contained in:
2025-04-24 12:07:40 +02:00
commit 788d9bd6ea
305 changed files with 61443 additions and 0 deletions

63
embassy-rp/CHANGELOG.md Normal file
View File

@@ -0,0 +1,63 @@
# Changelog for embassy-rp
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
## 0.4.0 - 2025-03-09
- Add PIO functions. ([#3857](https://github.com/embassy-rs/embassy/pull/3857))
The functions added in this change are `get_addr` `get_tx_threshold`, `set_tx_threshold`, `get_rx_threshold`, `set_rx_threshold`, `set_thresholds`.
- Expose the watchdog reset reason. ([#3877](https://github.com/embassy-rs/embassy/pull/3877))
- Update pio-rs, reexport, move instr methods to SM. ([#3865](https://github.com/embassy-rs/embassy/pull/3865))
- rp235x: add ImageDef features. ([#3890](https://github.com/embassy-rs/embassy/pull/3890))
- doc: Fix "the the" ([#3903](https://github.com/embassy-rs/embassy/pull/3903))
- pio: Add access to DMA engine byte swapping ([#3935](https://github.com/embassy-rs/embassy/pull/3935))
## 0.3.1 - 2025-02-06
Small release fixing a few gnarly bugs, upgrading is strongly recommended.
- Fix a race condition in the time driver that could cause missed interrupts. ([#3758](https://github.com/embassy-rs/embassy/issues/3758), [#3763](https://github.com/embassy-rs/embassy/pull/3763))
- rp235x: Make atomics work across cores. ([#3851](https://github.com/embassy-rs/embassy/pull/3851))
- rp235x: add workaround "SIO spinlock stuck after reset" bug, same as RP2040 ([#3851](https://github.com/embassy-rs/embassy/pull/3851))
- rp235x: Ensure core1 is reset if core0 resets. ([#3851](https://github.com/embassy-rs/embassy/pull/3851))
- rp235xb: correct ADC channel numbers. ([#3823](https://github.com/embassy-rs/embassy/pull/3823))
- rp235x: enable watchdog tick generator. ([#3777](https://github.com/embassy-rs/embassy/pull/3777))
- Relax I2C address validity check to allow using 7-bit addresses that would be reserved for 10-bit addresses. ([#3809](https://github.com/embassy-rs/embassy/issues/3809), [#3810](https://github.com/embassy-rs/embassy/pull/3810))
## 0.3.0 - 2025-01-05
- Updated `embassy-time` to v0.4
- Initial rp235x support
- Setup timer0 tick when initializing clocks
- Allow separate control of duty cycle for each channel in a pwm slice by splitting the Pwm driver.
- Implement `embedded_io::Write` for Uart<'d, T: Instance, Blocking> and UartTx<'d, T: Instance, Blocking>
- Add `set_pullup()` to OutputOpenDrain.
## 0.2.0 - 2024-08-05
- Add read_to_break_with_count
- add option to provide your own boot2
- Add multichannel ADC
- Add collapse_debuginfo to fmt.rs macros.
- Use raw slices .len() method instead of unsafe hacks.
- Add missing word "pin" in rp pwm documentation
- Add Clone and Copy to Error types
- fix spinlocks staying locked after reset.
- wait until read matches for PSM accesses.
- Remove generics
- fix drop implementation of BufferedUartRx and BufferedUartTx
- implement `embedded_storage_async::nor_flash::MultiwriteNorFlash`
- rp usb: wake ep-wakers after stalling
- rp usb: add stall implementation
- Add parameter for enabling pull-up and pull-down in RP PWM input mode
- rp: remove mod sealed.
- rename pins data type and the macro
- rename pwm channels to pwm slices, including in documentation
- rename the Channel trait to Slice and the PwmPin to PwmChannel
- i2c: Fix race condition that appears on fast repeated transfers.
- Add a basic "read to break" function

176
embassy-rp/Cargo.toml Normal file
View File

@@ -0,0 +1,176 @@
[package]
name = "embassy-rp"
version = "0.4.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Embassy Hardware Abstraction Layer (HAL) for the Raspberry Pi RP2040 microcontroller"
keywords = ["embedded", "async", "raspberry-pi", "rp2040", "embedded-hal"]
categories = ["embedded", "hardware-support", "no-std", "asynchronous"]
repository = "https://github.com/embassy-rs/embassy"
documentation = "https://docs.embassy.dev/embassy-rp"
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-rp-v$VERSION/embassy-rp/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-rp/src/"
features = ["defmt", "unstable-pac", "time-driver"]
flavors = [
{ name = "rp2040", target = "thumbv6m-none-eabi", features = ["rp2040"] },
{ name = "rp235xa", target = "thumbv8m.main-none-eabihf", features = ["rp235xa"] },
{ name = "rp235xb", target = "thumbv8m.main-none-eabihf", features = ["rp235xb"] },
]
[package.metadata.docs.rs]
# TODO: it's not GREAT to set a specific target, but docs.rs builds will fail otherwise
# for now, default to rp2040
features = ["defmt", "unstable-pac", "time-driver", "rp2040"]
[features]
default = [ "rt" ]
## Enable the rt feature of [`rp-pac`](https://docs.rs/rp-pac). This brings in the [`cortex-m-rt`](https://docs.rs/cortex-m-rt) crate, which adds startup code and minimal runtime initialization.
rt = [ "rp-pac/rt" ]
## Enable [defmt support](https://docs.rs/defmt) and enables `defmt` debug-log messages and formatting in embassy drivers.
defmt = ["dep:defmt", "embassy-usb-driver/defmt", "embassy-hal-internal/defmt"]
## Configure the [`critical-section`](https://docs.rs/critical-section) crate to use an implementation that is safe for multicore use on rp2040.
critical-section-impl = ["critical-section/restore-state-u8"]
## Reexport the PAC for the currently enabled chip at `embassy_rp::pac`.
## This is unstable because semver-minor (non-breaking) releases of `embassy-rp` may major-bump (breaking) the PAC version.
## If this is an issue for you, you're encouraged to directly depend on a fixed version of the PAC.
## There are no plans to make this stable.
unstable-pac = []
## Enable the timer for use with `embassy-time` with a 1MHz tick rate.
time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-1_000_000", "dep:embassy-time-queue-utils"]
## Enable ROM function cache. This will store the address of a ROM function when first used, improving performance of subsequent calls.
rom-func-cache = []
## Enable implementations of some compiler intrinsics using functions in the rp2040 Mask ROM.
## These should be as fast or faster than the implementations in compiler-builtins. They also save code space and reduce memory contention.
## Compiler intrinsics are used automatically, you do not need to change your code to get performance improvements from this feature.
intrinsics = []
## Enable intrinsics based on extra ROM functions added in the v2 version of the rp2040 Mask ROM.
## This version added a lot more floating point operations - many f64 functions and a few f32 functions were added in ROM v2.
rom-v2-intrinsics = []
## Allow using QSPI pins as GPIO pins. This is mostly not what you want (because your flash is attached via QSPI pins)
## and adds code and memory overhead when this feature is enabled.
qspi-as-gpio = []
## Indicate code is running from RAM.
## Set this if all code is in RAM, and the cores never access memory-mapped flash memory through XIP.
## This allows the flash driver to not force pausing execution on both cores when doing flash operations.
run-from-ram = []
#! ### boot2 flash chip support
#! RP2040's internal bootloader is only able to run code from the first 256 bytes of flash.
#! A 2nd stage bootloader (boot2) is required to run larger programs from external flash.
#! Select from existing boot2 implementations via the following features. If none are selected,
#! boot2-w25q080 will be used (w25q080 is the flash chip used on the pico).
#! Each implementation uses flash commands and timings specific to a QSPI flash chip family for better performance.
## Use boot2 with support for Renesas/Dialog AT25SF128a SPI flash.
boot2-at25sf128a = []
## Use boot2 with support for Gigadevice GD25Q64C SPI flash.
boot2-gd25q64cs = []
## Use boot2 that only uses generic flash commands - these are supported by all SPI flash, but are slower.
boot2-generic-03h = []
## Use boot2 with support for ISSI IS25LP080 SPI flash.
boot2-is25lp080 = []
## Use boot2 that copies the entire program to RAM before booting. This uses generic flash commands to perform the copy.
boot2-ram-memcpy = []
## Use boot2 with support for Winbond W25Q080 SPI flash.
boot2-w25q080 = []
## Use boot2 with support for Winbond W25X10CL SPI flash.
boot2-w25x10cl = []
## Have embassy-rp not provide the boot2 so you can use your own.
## Place your own in the ".boot2" section like:
## ```
## #[link_section = ".boot2"]
## #[used]
## static BOOT2: [u8; 256] = [0; 256]; // Provide your own with e.g. include_bytes!
## ```
boot2-none = []
#! ### Image Definition support
#! RP2350's internal bootloader will only execute firmware if it has a valid Image Definition.
#! There are other tags that can be used (for example, you can tag an image as "DATA")
#! but programs will want to have an exe header. "SECURE_EXE" is usually what you want.
#! Use imagedef-none if you want to configure the Image Definition manually
#! If none are selected, imagedef-secure-exe will be used
## Image is executable and starts in Secure mode
imagedef-secure-exe = []
## Image is executable and starts in Non-secure mode
imagedef-nonsecure-exe = []
## Have embassy-rp not provide the Image Definition so you can use your own.
## Place your own in the ".start_block" section like:
## ```ignore
## use embassy_rp::block::ImageDef;
##
## #[link_section = ".start_block"]
## #[used]
## static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); // Update this with your own implementation.
## ```
imagedef-none = []
## Configure the hal for use with the rp2040
rp2040 = ["rp-pac/rp2040"]
_rp235x = ["rp-pac/rp235x"]
## Configure the hal for use with the rp235xA
rp235xa = ["_rp235x"]
## Configure the hal for use with the rp235xB
rp235xb = ["_rp235x"]
# hack around cortex-m peripherals being wrong when running tests.
_test = []
## Add a binary-info header block containing picotool-compatible metadata.
##
## Takes up a little flash space, but picotool can then report the name of your
## program and other details.
binary-info = ["rt", "dep:rp-binary-info", "rp-binary-info?/binary-info"]
[dependencies]
embassy-sync = { version = "0.6.2", path = "../embassy-sync" }
embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true }
embassy-time-queue-utils = { version = "0.1", path = "../embassy-time-queue-utils", optional = true }
embassy-time = { version = "0.4.0", path = "../embassy-time" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-hal-internal = { version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] }
embassy-embedded-hal = { version = "0.3.0", path = "../embassy-embedded-hal" }
embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" }
atomic-polyfill = "1.0.1"
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
nb = "1.1.0"
cfg-if = "1.0.0"
cortex-m-rt = ">=0.6.15,<0.8"
cortex-m = "0.7.6"
critical-section = "1.2.0"
chrono = { version = "0.4", default-features = false, optional = true }
embedded-io = { version = "0.6.1" }
embedded-io-async = { version = "0.6.1" }
embedded-storage = { version = "0.3" }
embedded-storage-async = { version = "0.4.1" }
rand_core = "0.6.4"
fixed = "1.28.0"
rp-pac = { version = "7.0.0" }
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
embedded-hal-1 = { package = "embedded-hal", version = "1.0" }
embedded-hal-async = { version = "1.0" }
embedded-hal-nb = { version = "1.0" }
pio = { version = "0.3" }
rp2040-boot2 = "0.3"
document-features = "0.2.10"
sha2-const-stable = "0.1"
rp-binary-info = { version = "0.1.0", optional = true }
smart-leds = "0.4.0"
[dev-dependencies]
embassy-executor = { version = "0.7.0", path = "../embassy-executor", features = ["arch-std", "executor-thread"] }
static_cell = { version = "2" }

202
embassy-rp/LICENSE-APACHE Normal file
View File

@@ -0,0 +1,202 @@
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) Embassy project contributors
Portions copyright (c) rp-rs organization
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.

26
embassy-rp/LICENSE-MIT Normal file
View File

@@ -0,0 +1,26 @@
Copyright (c) Embassy project contributors
Portions copyright (c) rp-rs organization
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.

27
embassy-rp/README.md Normal file
View File

@@ -0,0 +1,27 @@
# Embassy RP HAL
HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed.
The embassy-rp HAL targets the Raspberry Pi RP2040 microcontroller. The HAL implements both blocking and async APIs
for many peripherals. The benefit of using the async APIs is that the HAL takes care of waiting for peripherals to
complete operations in low power mode and handling interrupts, so that applications can focus on more important matters.
* [embassy-rp on crates.io](https://crates.io/crates/embassy-rp)
* [Documentation](https://docs.embassy.dev/embassy-rp/)
* [Source](https://github.com/embassy-rs/embassy/tree/main/embassy-rp)
* [Examples](https://github.com/embassy-rs/embassy/tree/main/examples/rp/src/bin)
## `embassy-time` time driver
If the `time-driver` feature is enabled, the HAL uses the TIMER peripheral as a global time driver for [embassy-time](https://crates.io/crates/embassy-time), with a tick rate of 1MHz.
## Embedded-hal
The `embassy-rp` HAL implements the traits from [embedded-hal](https://crates.io/crates/embedded-hal) (v0.2 and 1.0) and [embedded-hal-async](https://crates.io/crates/embedded-hal-async), as well as [embedded-io](https://crates.io/crates/embedded-io) and [embedded-io-async](https://crates.io/crates/embedded-io-async).
## Interoperability
This crate can run on any executor.
Optionally, some features requiring [`embassy-time`](https://crates.io/crates/embassy-time) can be activated with the `time-driver` feature. If you enable it,
you must link an `embassy-time` driver in your project.

39
embassy-rp/build.rs Normal file
View File

@@ -0,0 +1,39 @@
use std::env;
use std::ffi::OsStr;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
fn main() {
if env::var("CARGO_FEATURE_RP2040").is_ok() {
// Put the linker script somewhere the linker can find it
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
let link_x = include_bytes!("link-rp.x.in");
let mut f = File::create(out.join("link-rp.x")).unwrap();
f.write_all(link_x).unwrap();
println!("cargo:rustc-link-search={}", out.display());
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=link-rp.x.in");
}
// code below taken from https://github.com/rust-embedded/cortex-m/blob/master/cortex-m-rt/build.rs
let mut target = env::var("TARGET").unwrap();
// When using a custom target JSON, `$TARGET` contains the path to that JSON file. By
// convention, these files are named after the actual target triple, eg.
// `thumbv7m-customos-elf.json`, so we extract the file stem here to allow custom target specs.
let path = Path::new(&target);
if path.extension() == Some(OsStr::new("json")) {
target = path
.file_stem()
.map_or(target.clone(), |stem| stem.to_str().unwrap().to_string());
}
println!("cargo::rustc-check-cfg=cfg(has_fpu)");
if target.ends_with("-eabihf") {
println!("cargo:rustc-cfg=has_fpu");
}
}

30
embassy-rp/funcsel.txt Normal file
View File

@@ -0,0 +1,30 @@
0 SPI0 RX UART0 TX I2C0 SDA PWM0 A SIO PIO0 PIO1 USB OVCUR DET
1 SPI0 CSn UART0 RX I2C0 SCL PWM0 B SIO PIO0 PIO1 USB VBUS DET
2 SPI0 SCK UART0 CTS I2C1 SDA PWM1 A SIO PIO0 PIO1 USB VBUS EN
3 SPI0 TX UART0 RTS I2C1 SCL PWM1 B SIO PIO0 PIO1 USB OVCUR DET
4 SPI0 RX UART1 TX I2C0 SDA PWM2 A SIO PIO0 PIO1 USB VBUS DET
5 SPI0 CSn UART1 RX I2C0 SCL PWM2 B SIO PIO0 PIO1 USB VBUS EN
6 SPI0 SCK UART1 CTS I2C1 SDA PWM3 A SIO PIO0 PIO1 USB OVCUR DET
7 SPI0 TX UART1 RTS I2C1 SCL PWM3 B SIO PIO0 PIO1 USB VBUS DET
8 SPI1 RX UART1 TX I2C0 SDA PWM4 A SIO PIO0 PIO1 USB VBUS EN
9 SPI1 CSn UART1 RX I2C0 SCL PWM4 B SIO PIO0 PIO1 USB OVCUR DET
10 SPI1 SCK UART1 CTS I2C1 SDA PWM5 A SIO PIO0 PIO1 USB VBUS DET
11 SPI1 TX UART1 RTS I2C1 SCL PWM5 B SIO PIO0 PIO1 USB VBUS EN
12 SPI1 RX UART0 TX I2C0 SDA PWM6 A SIO PIO0 PIO1 USB OVCUR DET
13 SPI1 CSn UART0 RX I2C0 SCL PWM6 B SIO PIO0 PIO1 USB VBUS DET
14 SPI1 SCK UART0 CTS I2C1 SDA PWM7 A SIO PIO0 PIO1 USB VBUS EN
15 SPI1 TX UART0 RTS I2C1 SCL PWM7 B SIO PIO0 PIO1 USB OVCUR DET
16 SPI0 RX UART0 TX I2C0 SDA PWM0 A SIO PIO0 PIO1 USB VBUS DET
17 SPI0 CSn UART0 RX I2C0 SCL PWM0 B SIO PIO0 PIO1 USB VBUS EN
18 SPI0 SCK UART0 CTS I2C1 SDA PWM1 A SIO PIO0 PIO1 USB OVCUR DET
19 SPI0 TX UART0 RTS I2C1 SCL PWM1 B SIO PIO0 PIO1 USB VBUS DET
20 SPI0 RX UART1 TX I2C0 SDA PWM2 A SIO PIO0 PIO1 CLOCK GPIN0 USB VBUS EN
21 SPI0 CSn UART1 RX I2C0 SCL PWM2 B SIO PIO0 PIO1 CLOCK GPOUT0 USB OVCUR DET
22 SPI0 SCK UART1 CTS I2C1 SDA PWM3 A SIO PIO0 PIO1 CLOCK GPIN1 USB VBUS DET
23 SPI0 TX UART1 RTS I2C1 SCL PWM3 B SIO PIO0 PIO1 CLOCK GPOUT1 USB VBUS EN
24 SPI1 RX UART1 TX I2C0 SDA PWM4 A SIO PIO0 PIO1 CLOCK GPOUT2 USB OVCUR DET
25 SPI1 CSn UART1 RX I2C0 SCL PWM4 B SIO PIO0 PIO1 CLOCK GPOUT3 USB VBUS DET
26 SPI1 SCK UART1 CTS I2C1 SDA PWM5 A SIO PIO0 PIO1 USB VBUS EN
27 SPI1 TX UART1 RTS I2C1 SCL PWM5 B SIO PIO0 PIO1 USB OVCUR DET
28 SPI1 RX UART0 TX I2C0 SDA PWM6 A SIO PIO0 PIO1 USB VBUS DET
29 SPI1 CSn UART0 RX I2C0 SCL PWM6 B SIO PIO0 PIO1 USB VBUS EN

8
embassy-rp/link-rp.x.in Normal file
View File

@@ -0,0 +1,8 @@
SECTIONS {
/* ### Boot loader */
.boot2 ORIGIN(BOOT2) :
{
KEEP(*(.boot2));
} > BOOT2
}

469
embassy-rp/src/adc.rs Normal file
View File

@@ -0,0 +1,469 @@
//! ADC driver.
use core::future::{poll_fn, Future};
use core::marker::PhantomData;
use core::mem;
use core::sync::atomic::{compiler_fence, Ordering};
use core::task::Poll;
use embassy_hal_internal::{into_ref, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
use crate::gpio::{self, AnyPin, Pull, SealedPin as GpioPin};
use crate::interrupt::typelevel::Binding;
use crate::interrupt::InterruptExt;
use crate::pac::dma::vals::TreqSel;
use crate::peripherals::{ADC, ADC_TEMP_SENSOR};
use crate::{dma, interrupt, pac, peripherals, Peripheral, RegExt};
static WAKER: AtomicWaker = AtomicWaker::new();
/// ADC config.
#[non_exhaustive]
#[derive(Default)]
pub struct Config {}
enum Source<'p> {
Pin(PeripheralRef<'p, AnyPin>),
TempSensor(PeripheralRef<'p, ADC_TEMP_SENSOR>),
}
/// ADC channel.
pub struct Channel<'p>(Source<'p>);
impl<'p> Channel<'p> {
/// Create a new ADC channel from pin with the provided [Pull] configuration.
pub fn new_pin(pin: impl Peripheral<P = impl AdcPin + 'p> + 'p, pull: Pull) -> Self {
into_ref!(pin);
pin.pad_ctrl().modify(|w| {
#[cfg(feature = "_rp235x")]
w.set_iso(false);
// manual says:
//
// > When using an ADC input shared with a GPIO pin, the pins
// > digital functions must be disabled by setting IE low and OD
// > high in the pins pad control register
w.set_ie(false);
w.set_od(true);
w.set_pue(pull == Pull::Up);
w.set_pde(pull == Pull::Down);
});
Self(Source::Pin(pin.map_into()))
}
/// Create a new ADC channel for the internal temperature sensor.
pub fn new_temp_sensor(s: impl Peripheral<P = ADC_TEMP_SENSOR> + 'p) -> Self {
let r = pac::ADC;
r.cs().write_set(|w| w.set_ts_en(true));
Self(Source::TempSensor(s.into_ref()))
}
fn channel(&self) -> u8 {
#[cfg(any(feature = "rp2040", feature = "rp235xa"))]
const CH_OFFSET: u8 = 26;
#[cfg(feature = "rp235xb")]
const CH_OFFSET: u8 = 40;
#[cfg(any(feature = "rp2040", feature = "rp235xa"))]
const TS_CHAN: u8 = 4;
#[cfg(feature = "rp235xb")]
const TS_CHAN: u8 = 8;
match &self.0 {
// this requires adc pins to be sequential and matching the adc channels,
// which is the case for rp2040/rp235xy
Source::Pin(p) => p._pin() - CH_OFFSET,
Source::TempSensor(_) => TS_CHAN,
}
}
}
impl<'p> Drop for Source<'p> {
fn drop(&mut self) {
match self {
Source::Pin(p) => {
p.pad_ctrl().modify(|w| {
w.set_ie(true);
w.set_od(false);
w.set_pue(false);
w.set_pde(true);
});
}
Source::TempSensor(_) => {
pac::ADC.cs().write_clear(|w| w.set_ts_en(true));
}
}
}
}
/// ADC sample.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(transparent)]
pub struct Sample(u16);
impl Sample {
/// Sample is valid.
pub fn good(&self) -> bool {
self.0 < 0x8000
}
/// Sample value.
pub fn value(&self) -> u16 {
self.0 & !0x8000
}
}
/// ADC error.
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
/// Error converting value.
ConversionFailed,
}
/// ADC mode.
pub trait Mode {}
/// ADC async mode.
pub struct Async;
impl Mode for Async {}
/// ADC blocking mode.
pub struct Blocking;
impl Mode for Blocking {}
/// ADC driver.
pub struct Adc<'d, M: Mode> {
phantom: PhantomData<(&'d ADC, M)>,
}
impl<'d, M: Mode> Drop for Adc<'d, M> {
fn drop(&mut self) {
let r = Self::regs();
// disable ADC. leaving it enabled comes with a ~150µA static
// current draw. the temperature sensor has already been disabled
// by the temperature-reading methods, so we don't need to touch that.
r.cs().write(|w| w.set_en(false));
}
}
impl<'d, M: Mode> Adc<'d, M> {
#[inline]
fn regs() -> pac::adc::Adc {
pac::ADC
}
#[inline]
fn reset() -> pac::resets::regs::Peripherals {
let mut ret = pac::resets::regs::Peripherals::default();
ret.set_adc(true);
ret
}
fn setup() {
let reset = Self::reset();
crate::reset::reset(reset);
crate::reset::unreset_wait(reset);
let r = Self::regs();
// Enable ADC
r.cs().write(|w| w.set_en(true));
// Wait for ADC ready
while !r.cs().read().ready() {}
}
/// Sample a value from a channel in blocking mode.
pub fn blocking_read(&mut self, ch: &mut Channel) -> Result<u16, Error> {
let r = Self::regs();
r.cs().modify(|w| {
w.set_ainsel(ch.channel());
w.set_start_once(true);
w.set_err(true);
});
while !r.cs().read().ready() {}
match r.cs().read().err() {
true => Err(Error::ConversionFailed),
false => Ok(r.result().read().result()),
}
}
}
impl<'d> Adc<'d, Async> {
/// Create ADC driver in async mode.
pub fn new(
_inner: impl Peripheral<P = ADC> + 'd,
_irq: impl Binding<interrupt::typelevel::ADC_IRQ_FIFO, InterruptHandler>,
_config: Config,
) -> Self {
Self::setup();
// Setup IRQ
interrupt::ADC_IRQ_FIFO.unpend();
unsafe { interrupt::ADC_IRQ_FIFO.enable() };
Self { phantom: PhantomData }
}
fn wait_for_ready() -> impl Future<Output = ()> {
let r = Self::regs();
r.inte().write(|w| w.set_fifo(true));
compiler_fence(Ordering::SeqCst);
poll_fn(move |cx| {
WAKER.register(cx.waker());
if r.cs().read().ready() {
return Poll::Ready(());
}
Poll::Pending
})
}
/// Sample a value from a channel until completed.
pub async fn read(&mut self, ch: &mut Channel<'_>) -> Result<u16, Error> {
let r = Self::regs();
r.cs().modify(|w| {
w.set_ainsel(ch.channel());
w.set_start_once(true);
w.set_err(true);
});
Self::wait_for_ready().await;
match r.cs().read().err() {
true => Err(Error::ConversionFailed),
false => Ok(r.result().read().result()),
}
}
// Note for refactoring: we don't require the actual Channels here, just the channel numbers.
// The public api is responsible for asserting ownership of the actual Channels.
async fn read_many_inner<W: dma::Word>(
&mut self,
channels: impl Iterator<Item = u8>,
buf: &mut [W],
fcs_err: bool,
div: u16,
dma: impl Peripheral<P = impl dma::Channel>,
) -> Result<(), Error> {
#[cfg(feature = "rp2040")]
let mut rrobin = 0_u8;
#[cfg(feature = "_rp235x")]
let mut rrobin = 0_u16;
for c in channels {
rrobin |= 1 << c;
}
let first_ch = rrobin.trailing_zeros() as u8;
if rrobin.count_ones() == 1 {
rrobin = 0;
}
let r = Self::regs();
// clear previous errors and set channel
r.cs().modify(|w| {
w.set_ainsel(first_ch);
w.set_rrobin(rrobin);
w.set_err_sticky(true); // clear previous errors
w.set_start_many(false);
});
// wait for previous conversions and drain fifo. an earlier batch read may have
// been cancelled, leaving the adc running.
while !r.cs().read().ready() {}
while !r.fcs().read().empty() {
r.fifo().read();
}
// set up fifo for dma
r.fcs().write(|w| {
w.set_thresh(1);
w.set_dreq_en(true);
w.set_shift(mem::size_of::<W>() == 1);
w.set_en(true);
w.set_err(fcs_err);
});
// reset dma config on drop, regardless of whether it was a future being cancelled
// or the method returning normally.
struct ResetDmaConfig;
impl Drop for ResetDmaConfig {
fn drop(&mut self) {
pac::ADC.cs().write_clear(|w| w.set_start_many(true));
while !pac::ADC.cs().read().ready() {}
pac::ADC.fcs().write_clear(|w| {
w.set_dreq_en(true);
w.set_shift(true);
w.set_en(true);
});
}
}
let auto_reset = ResetDmaConfig;
let dma = unsafe { dma::read(dma, r.fifo().as_ptr() as *const W, buf as *mut [W], TreqSel::ADC) };
// start conversions and wait for dma to finish. we can't report errors early
// because there's no interrupt to signal them, and inspecting every element
// of the fifo is too costly to do here.
r.div().write_set(|w| w.set_int(div));
r.cs().write_set(|w| w.set_start_many(true));
dma.await;
mem::drop(auto_reset);
// we can't report errors before the conversions have ended since no interrupt
// exists to report them early, and since they're exceedingly rare we probably don't
// want to anyway.
match r.cs().read().err_sticky() {
false => Ok(()),
true => Err(Error::ConversionFailed),
}
}
/// Sample multiple values from multiple channels using DMA.
/// Samples are stored in an interleaved fashion inside the buffer.
/// `div` is the integer part of the clock divider and can be calculated with `floor(48MHz / sample_rate * num_channels - 1)`
/// Any `div` value of less than 96 will have the same effect as setting it to 0
#[inline]
pub async fn read_many_multichannel<S: AdcSample>(
&mut self,
ch: &mut [Channel<'_>],
buf: &mut [S],
div: u16,
dma: impl Peripheral<P = impl dma::Channel>,
) -> Result<(), Error> {
self.read_many_inner(ch.iter().map(|c| c.channel()), buf, false, div, dma)
.await
}
/// Sample multiple values from multiple channels using DMA, with errors inlined in samples.
/// Samples are stored in an interleaved fashion inside the buffer.
/// `div` is the integer part of the clock divider and can be calculated with `floor(48MHz / sample_rate * num_channels - 1)`
/// Any `div` value of less than 96 will have the same effect as setting it to 0
#[inline]
pub async fn read_many_multichannel_raw(
&mut self,
ch: &mut [Channel<'_>],
buf: &mut [Sample],
div: u16,
dma: impl Peripheral<P = impl dma::Channel>,
) {
// errors are reported in individual samples
let _ = self
.read_many_inner(
ch.iter().map(|c| c.channel()),
unsafe { mem::transmute::<_, &mut [u16]>(buf) },
true,
div,
dma,
)
.await;
}
/// Sample multiple values from a channel using DMA.
/// `div` is the integer part of the clock divider and can be calculated with `floor(48MHz / sample_rate - 1)`
/// Any `div` value of less than 96 will have the same effect as setting it to 0
#[inline]
pub async fn read_many<S: AdcSample>(
&mut self,
ch: &mut Channel<'_>,
buf: &mut [S],
div: u16,
dma: impl Peripheral<P = impl dma::Channel>,
) -> Result<(), Error> {
self.read_many_inner([ch.channel()].into_iter(), buf, false, div, dma)
.await
}
/// Sample multiple values from a channel using DMA, with errors inlined in samples.
/// `div` is the integer part of the clock divider and can be calculated with `floor(48MHz / sample_rate - 1)`
/// Any `div` value of less than 96 will have the same effect as setting it to 0
#[inline]
pub async fn read_many_raw(
&mut self,
ch: &mut Channel<'_>,
buf: &mut [Sample],
div: u16,
dma: impl Peripheral<P = impl dma::Channel>,
) {
// errors are reported in individual samples
let _ = self
.read_many_inner(
[ch.channel()].into_iter(),
unsafe { mem::transmute::<_, &mut [u16]>(buf) },
true,
div,
dma,
)
.await;
}
}
impl<'d> Adc<'d, Blocking> {
/// Create ADC driver in blocking mode.
pub fn new_blocking(_inner: impl Peripheral<P = ADC> + 'd, _config: Config) -> Self {
Self::setup();
Self { phantom: PhantomData }
}
}
/// Interrupt handler.
pub struct InterruptHandler {
_empty: (),
}
impl interrupt::typelevel::Handler<interrupt::typelevel::ADC_IRQ_FIFO> for InterruptHandler {
unsafe fn on_interrupt() {
let r = Adc::<Async>::regs();
r.inte().write(|w| w.set_fifo(false));
WAKER.wake();
}
}
trait SealedAdcSample: crate::dma::Word {}
trait SealedAdcChannel {}
/// ADC sample.
#[allow(private_bounds)]
pub trait AdcSample: SealedAdcSample {}
impl SealedAdcSample for u16 {}
impl AdcSample for u16 {}
impl SealedAdcSample for u8 {}
impl AdcSample for u8 {}
/// ADC channel.
#[allow(private_bounds)]
pub trait AdcChannel: SealedAdcChannel {}
/// ADC pin.
pub trait AdcPin: AdcChannel + gpio::Pin {}
macro_rules! impl_pin {
($pin:ident, $channel:expr) => {
impl SealedAdcChannel for peripherals::$pin {}
impl AdcChannel for peripherals::$pin {}
impl AdcPin for peripherals::$pin {}
};
}
#[cfg(any(feature = "rp235xa", feature = "rp2040"))]
impl_pin!(PIN_26, 0);
#[cfg(any(feature = "rp235xa", feature = "rp2040"))]
impl_pin!(PIN_27, 1);
#[cfg(any(feature = "rp235xa", feature = "rp2040"))]
impl_pin!(PIN_28, 2);
#[cfg(any(feature = "rp235xa", feature = "rp2040"))]
impl_pin!(PIN_29, 3);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_40, 0);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_41, 1);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_42, 2);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_43, 3);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_44, 4);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_45, 5);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_46, 6);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_47, 7);
impl SealedAdcChannel for peripherals::ADC_TEMP_SENSOR {}
impl AdcChannel for peripherals::ADC_TEMP_SENSOR {}

1079
embassy-rp/src/block.rs Normal file

File diff suppressed because it is too large Load Diff

83
embassy-rp/src/bootsel.rs Normal file
View File

@@ -0,0 +1,83 @@
//! Boot Select button
//!
//! The RP2040 rom supports a BOOTSEL button that is used to enter the USB bootloader
//! if held during reset. To avoid wasting GPIO pins, the button is multiplexed onto
//! the CS pin of the QSPI flash, but that makes it somewhat expensive and complicated
//! to utilize outside of the rom's bootloader.
//!
//! This module provides functionality to poll BOOTSEL from an embassy application.
use crate::flash::in_ram;
impl crate::peripherals::BOOTSEL {
/// Polls the BOOTSEL button. Returns true if the button is pressed.
///
/// Polling isn't cheap, as this function waits for core 1 to finish it's current
/// task and for any DMAs from flash to complete
pub fn is_pressed(&mut self) -> bool {
let mut cs_status = Default::default();
unsafe { in_ram(|| cs_status = ram_helpers::read_cs_status()) }.expect("Must be called from Core 0");
// bootsel is active low, so invert
!cs_status.infrompad()
}
}
mod ram_helpers {
use rp_pac::io::regs::GpioStatus;
/// Temporally reconfigures the CS gpio and returns the GpioStatus.
/// This function runs from RAM so it can disable flash XIP.
///
/// # Safety
///
/// The caller must ensure flash is idle and will remain idle.
/// This function must live in ram. It uses inline asm to avoid any
/// potential calls to ABI functions that might be in flash.
#[inline(never)]
#[link_section = ".data.ram_func"]
#[cfg(target_arch = "arm")]
pub unsafe fn read_cs_status() -> GpioStatus {
let result: u32;
// Magic value, used as both OEOVER::DISABLE and delay loop counter
let magic = 0x2000;
core::arch::asm!(
".equiv GPIO_STATUS, 0x0",
".equiv GPIO_CTRL, 0x4",
"ldr {orig_ctrl}, [{cs_gpio}, $GPIO_CTRL]",
// The BOOTSEL pulls the flash's CS line low though a 1K resistor.
// this is weak enough to avoid disrupting normal operation.
// But, if we disable CS's output drive and allow it to float...
"str {val}, [{cs_gpio}, $GPIO_CTRL]",
// ...then wait for the state to settle...
"2:", // ~4000 cycle delay loop
"subs {val}, #8",
"bne 2b",
// ...we can read the current state of bootsel
"ldr {val}, [{cs_gpio}, $GPIO_STATUS]",
// Finally, restore CS to normal operation so XIP can continue
"str {orig_ctrl}, [{cs_gpio}, $GPIO_CTRL]",
cs_gpio = in(reg) rp_pac::IO_QSPI.gpio(1).as_ptr(),
orig_ctrl = out(reg) _,
val = inout(reg) magic => result,
options(nostack),
);
core::mem::transmute(result)
}
#[cfg(not(target_arch = "arm"))]
pub unsafe fn read_cs_status() -> GpioStatus {
unimplemented!()
}
}

1196
embassy-rp/src/clocks.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,137 @@
use core::sync::atomic::{AtomicU8, Ordering};
use crate::pac;
struct RpSpinlockCs;
critical_section::set_impl!(RpSpinlockCs);
/// Marker value to indicate no-one has the lock.
///
/// Initialising `LOCK_OWNER` to 0 means cheaper static initialisation so it's the best choice
const LOCK_UNOWNED: u8 = 0;
/// Indicates which core owns the lock so that we can call critical_section recursively.
///
/// 0 = no one has the lock, 1 = core0 has the lock, 2 = core1 has the lock
static LOCK_OWNER: AtomicU8 = AtomicU8::new(LOCK_UNOWNED);
/// Marker value to indicate that we already owned the lock when we started the `critical_section`.
///
/// Since we can't take the spinlock when we already have it, we need some other way to keep track of `critical_section` ownership.
/// `critical_section` provides a token for communicating between `acquire` and `release` so we use that.
/// If we're the outermost call to `critical_section` we use the values 0 and 1 to indicate we should release the spinlock and set the interrupts back to disabled and enabled, respectively.
/// The value 2 indicates that we aren't the outermost call, and should not release the spinlock or re-enable interrupts in `release`
const LOCK_ALREADY_OWNED: u8 = 2;
unsafe impl critical_section::Impl for RpSpinlockCs {
unsafe fn acquire() -> u8 {
RpSpinlockCs::acquire()
}
unsafe fn release(token: u8) {
RpSpinlockCs::release(token);
}
}
impl RpSpinlockCs {
unsafe fn acquire() -> u8 {
// Store the initial interrupt state and current core id in stack variables
let interrupts_active = cortex_m::register::primask::read().is_active();
// We reserved 0 as our `LOCK_UNOWNED` value, so add 1 to core_id so we get 1 for core0, 2 for core1.
let core = pac::SIO.cpuid().read() as u8 + 1;
// Do we already own the spinlock?
if LOCK_OWNER.load(Ordering::Acquire) == core {
// We already own the lock, so we must have called acquire within a critical_section.
// Return the magic inner-loop value so that we know not to re-enable interrupts in release()
LOCK_ALREADY_OWNED
} else {
// Spin until we get the lock
loop {
// Need to disable interrupts to ensure that we will not deadlock
// if an interrupt enters critical_section::Impl after we acquire the lock
cortex_m::interrupt::disable();
// Ensure the compiler doesn't re-order accesses and violate safety here
core::sync::atomic::compiler_fence(Ordering::SeqCst);
// Read the spinlock reserved for `critical_section`
if let Some(lock) = Spinlock31::try_claim() {
// We just acquired the lock.
// 1. Forget it, so we don't immediately unlock
core::mem::forget(lock);
// 2. Store which core we are so we can tell if we're called recursively
LOCK_OWNER.store(core, Ordering::Relaxed);
break;
}
// We didn't get the lock, enable interrupts if they were enabled before we started
if interrupts_active {
cortex_m::interrupt::enable();
}
}
// If we broke out of the loop we have just acquired the lock
// As the outermost loop, we want to return the interrupt status to restore later
interrupts_active as _
}
}
unsafe fn release(token: u8) {
// Did we already own the lock at the start of the `critical_section`?
if token != LOCK_ALREADY_OWNED {
// No, it wasn't owned at the start of this `critical_section`, so this core no longer owns it.
// Set `LOCK_OWNER` back to `LOCK_UNOWNED` to ensure the next critical section tries to obtain the spinlock instead
LOCK_OWNER.store(LOCK_UNOWNED, Ordering::Relaxed);
// Ensure the compiler doesn't re-order accesses and violate safety here
core::sync::atomic::compiler_fence(Ordering::SeqCst);
// Release the spinlock to allow others to enter critical_section again
Spinlock31::release();
// Re-enable interrupts if they were enabled when we first called acquire()
// We only do this on the outermost `critical_section` to ensure interrupts stay disabled
// for the whole time that we have the lock
if token != 0 {
cortex_m::interrupt::enable();
}
}
}
}
pub struct Spinlock<const N: usize>(core::marker::PhantomData<()>)
where
Spinlock<N>: SpinlockValid;
impl<const N: usize> Spinlock<N>
where
Spinlock<N>: SpinlockValid,
{
/// Try to claim the spinlock. Will return `Some(Self)` if the lock is obtained, and `None` if the lock is
/// already in use somewhere else.
pub fn try_claim() -> Option<Self> {
let lock = pac::SIO.spinlock(N).read();
if lock > 0 {
Some(Self(core::marker::PhantomData))
} else {
None
}
}
/// Clear a locked spin-lock.
///
/// # Safety
///
/// Only call this function if you hold the spin-lock.
pub unsafe fn release() {
// Write (any value): release the lock
pac::SIO.spinlock(N).write_value(1);
}
}
impl<const N: usize> Drop for Spinlock<N>
where
Spinlock<N>: SpinlockValid,
{
fn drop(&mut self) {
// This is safe because we own the object, and hence hold the lock.
unsafe { Self::release() }
}
}
pub(crate) type Spinlock31 = Spinlock<31>;
pub trait SpinlockValid {}
impl SpinlockValid for Spinlock<31> {}

315
embassy-rp/src/dma.rs Normal file
View File

@@ -0,0 +1,315 @@
//! Direct Memory Access (DMA)
use core::future::Future;
use core::pin::Pin;
use core::sync::atomic::{compiler_fence, Ordering};
use core::task::{Context, Poll};
use embassy_hal_internal::{impl_peripheral, into_ref, Peripheral, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
use pac::dma::vals::DataSize;
use crate::interrupt::InterruptExt;
use crate::pac::dma::vals;
use crate::{interrupt, pac, peripherals};
#[cfg(feature = "rt")]
#[interrupt]
fn DMA_IRQ_0() {
let ints0 = pac::DMA.ints(0).read();
for channel in 0..CHANNEL_COUNT {
let ctrl_trig = pac::DMA.ch(channel).ctrl_trig().read();
if ctrl_trig.ahb_error() {
panic!("DMA: error on DMA_0 channel {}", channel);
}
if ints0 & (1 << channel) == (1 << channel) {
CHANNEL_WAKERS[channel].wake();
}
}
pac::DMA.ints(0).write_value(ints0);
}
pub(crate) unsafe fn init() {
interrupt::DMA_IRQ_0.disable();
interrupt::DMA_IRQ_0.set_priority(interrupt::Priority::P3);
pac::DMA.inte(0).write_value(0xFFFF);
interrupt::DMA_IRQ_0.enable();
}
/// DMA read.
///
/// SAFETY: Slice must point to a valid location reachable by DMA.
pub unsafe fn read<'a, C: Channel, W: Word>(
ch: impl Peripheral<P = C> + 'a,
from: *const W,
to: *mut [W],
dreq: vals::TreqSel,
) -> Transfer<'a, C> {
copy_inner(
ch,
from as *const u32,
to as *mut W as *mut u32,
to.len(),
W::size(),
false,
true,
dreq,
)
}
/// DMA write.
///
/// SAFETY: Slice must point to a valid location reachable by DMA.
pub unsafe fn write<'a, C: Channel, W: Word>(
ch: impl Peripheral<P = C> + 'a,
from: *const [W],
to: *mut W,
dreq: vals::TreqSel,
) -> Transfer<'a, C> {
copy_inner(
ch,
from as *const W as *const u32,
to as *mut u32,
from.len(),
W::size(),
true,
false,
dreq,
)
}
// static mut so that this is allocated in RAM.
static mut DUMMY: u32 = 0;
/// DMA repeated write.
///
/// SAFETY: Slice must point to a valid location reachable by DMA.
pub unsafe fn write_repeated<'a, C: Channel, W: Word>(
ch: impl Peripheral<P = C> + 'a,
to: *mut W,
len: usize,
dreq: vals::TreqSel,
) -> Transfer<'a, C> {
copy_inner(
ch,
core::ptr::addr_of_mut!(DUMMY) as *const u32,
to as *mut u32,
len,
W::size(),
false,
false,
dreq,
)
}
/// DMA copy between slices.
///
/// SAFETY: Slices must point to locations reachable by DMA.
pub unsafe fn copy<'a, C: Channel, W: Word>(
ch: impl Peripheral<P = C> + 'a,
from: &[W],
to: &mut [W],
) -> Transfer<'a, C> {
let from_len = from.len();
let to_len = to.len();
assert_eq!(from_len, to_len);
copy_inner(
ch,
from.as_ptr() as *const u32,
to.as_mut_ptr() as *mut u32,
from_len,
W::size(),
true,
true,
vals::TreqSel::PERMANENT,
)
}
fn copy_inner<'a, C: Channel>(
ch: impl Peripheral<P = C> + 'a,
from: *const u32,
to: *mut u32,
len: usize,
data_size: DataSize,
incr_read: bool,
incr_write: bool,
dreq: vals::TreqSel,
) -> Transfer<'a, C> {
into_ref!(ch);
let p = ch.regs();
p.read_addr().write_value(from as u32);
p.write_addr().write_value(to as u32);
#[cfg(feature = "rp2040")]
p.trans_count().write(|w| {
*w = len as u32;
});
#[cfg(feature = "_rp235x")]
p.trans_count().write(|w| {
w.set_mode(0.into());
w.set_count(len as u32);
});
compiler_fence(Ordering::SeqCst);
p.ctrl_trig().write(|w| {
w.set_treq_sel(dreq);
w.set_data_size(data_size);
w.set_incr_read(incr_read);
w.set_incr_write(incr_write);
w.set_chain_to(ch.number());
w.set_en(true);
});
compiler_fence(Ordering::SeqCst);
Transfer::new(ch)
}
/// DMA transfer driver.
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Transfer<'a, C: Channel> {
channel: PeripheralRef<'a, C>,
}
impl<'a, C: Channel> Transfer<'a, C> {
pub(crate) fn new(channel: impl Peripheral<P = C> + 'a) -> Self {
into_ref!(channel);
Self { channel }
}
}
impl<'a, C: Channel> Drop for Transfer<'a, C> {
fn drop(&mut self) {
let p = self.channel.regs();
pac::DMA
.chan_abort()
.modify(|m| m.set_chan_abort(1 << self.channel.number()));
while p.ctrl_trig().read().busy() {}
}
}
impl<'a, C: Channel> Unpin for Transfer<'a, C> {}
impl<'a, C: Channel> Future for Transfer<'a, C> {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// We need to register/re-register the waker for each poll because any
// calls to wake will deregister the waker.
CHANNEL_WAKERS[self.channel.number() as usize].register(cx.waker());
if self.channel.regs().ctrl_trig().read().busy() {
Poll::Pending
} else {
Poll::Ready(())
}
}
}
#[cfg(feature = "rp2040")]
pub(crate) const CHANNEL_COUNT: usize = 12;
#[cfg(feature = "_rp235x")]
pub(crate) const CHANNEL_COUNT: usize = 16;
static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT];
trait SealedChannel {}
trait SealedWord {}
/// DMA channel interface.
#[allow(private_bounds)]
pub trait Channel: Peripheral<P = Self> + SealedChannel + Into<AnyChannel> + Sized + 'static {
/// Channel number.
fn number(&self) -> u8;
/// Channel registry block.
fn regs(&self) -> pac::dma::Channel {
pac::DMA.ch(self.number() as _)
}
/// Convert into type-erased [AnyChannel].
fn degrade(self) -> AnyChannel {
AnyChannel { number: self.number() }
}
}
/// DMA word.
#[allow(private_bounds)]
pub trait Word: SealedWord {
/// Word size.
fn size() -> vals::DataSize;
}
impl SealedWord for u8 {}
impl Word for u8 {
fn size() -> vals::DataSize {
vals::DataSize::SIZE_BYTE
}
}
impl SealedWord for u16 {}
impl Word for u16 {
fn size() -> vals::DataSize {
vals::DataSize::SIZE_HALFWORD
}
}
impl SealedWord for u32 {}
impl Word for u32 {
fn size() -> vals::DataSize {
vals::DataSize::SIZE_WORD
}
}
/// Type erased DMA channel.
pub struct AnyChannel {
number: u8,
}
impl_peripheral!(AnyChannel);
impl SealedChannel for AnyChannel {}
impl Channel for AnyChannel {
fn number(&self) -> u8 {
self.number
}
}
macro_rules! channel {
($name:ident, $num:expr) => {
impl SealedChannel for peripherals::$name {}
impl Channel for peripherals::$name {
fn number(&self) -> u8 {
$num
}
}
impl From<peripherals::$name> for crate::dma::AnyChannel {
fn from(val: peripherals::$name) -> Self {
crate::dma::Channel::degrade(val)
}
}
};
}
channel!(DMA_CH0, 0);
channel!(DMA_CH1, 1);
channel!(DMA_CH2, 2);
channel!(DMA_CH3, 3);
channel!(DMA_CH4, 4);
channel!(DMA_CH5, 5);
channel!(DMA_CH6, 6);
channel!(DMA_CH7, 7);
channel!(DMA_CH8, 8);
channel!(DMA_CH9, 9);
channel!(DMA_CH10, 10);
channel!(DMA_CH11, 11);
#[cfg(feature = "_rp235x")]
channel!(DMA_CH12, 12);
#[cfg(feature = "_rp235x")]
channel!(DMA_CH13, 13);
#[cfg(feature = "_rp235x")]
channel!(DMA_CH14, 14);
#[cfg(feature = "_rp235x")]
channel!(DMA_CH15, 15);

989
embassy-rp/src/flash.rs Normal file
View File

@@ -0,0 +1,989 @@
//! Flash driver.
use core::future::Future;
use core::marker::PhantomData;
use core::pin::Pin;
use core::task::{Context, Poll};
use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
use embedded_storage::nor_flash::{
check_erase, check_read, check_write, ErrorType, MultiwriteNorFlash, NorFlash, NorFlashError, NorFlashErrorKind,
ReadNorFlash,
};
use crate::dma::{AnyChannel, Channel, Transfer};
use crate::pac;
use crate::peripherals::FLASH;
/// Flash base address.
pub const FLASH_BASE: *const u32 = 0x10000000 as _;
/// Address for xip setup function set up by the 235x bootrom.
#[cfg(feature = "_rp235x")]
pub const BOOTROM_BASE: *const u32 = 0x400e0000 as _;
/// If running from RAM, we might have no boot2. Use bootrom `flash_enter_cmd_xip` instead.
// TODO: when run-from-ram is set, completely skip the "pause cores and jumpp to RAM" dance.
pub const USE_BOOT2: bool = !cfg!(feature = "run-from-ram") | cfg!(feature = "_rp235x");
// **NOTE**:
//
// These limitations are currently enforced because of using the
// RP2040 boot-rom flash functions, that are optimized for flash compatibility
// rather than performance.
/// Flash page size.
pub const PAGE_SIZE: usize = 256;
/// Flash write size.
pub const WRITE_SIZE: usize = 1;
/// Flash read size.
pub const READ_SIZE: usize = 1;
/// Flash erase size.
pub const ERASE_SIZE: usize = 4096;
/// Flash DMA read size.
pub const ASYNC_READ_SIZE: usize = 4;
/// Error type for NVMC operations.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
/// Operation using a location not in flash.
OutOfBounds,
/// Unaligned operation or using unaligned buffers.
Unaligned,
/// Accessed from the wrong core.
InvalidCore,
/// Other error
Other,
}
impl From<NorFlashErrorKind> for Error {
fn from(e: NorFlashErrorKind) -> Self {
match e {
NorFlashErrorKind::NotAligned => Self::Unaligned,
NorFlashErrorKind::OutOfBounds => Self::OutOfBounds,
_ => Self::Other,
}
}
}
impl NorFlashError for Error {
fn kind(&self) -> NorFlashErrorKind {
match self {
Self::OutOfBounds => NorFlashErrorKind::OutOfBounds,
Self::Unaligned => NorFlashErrorKind::NotAligned,
_ => NorFlashErrorKind::Other,
}
}
}
/// Future that waits for completion of a background read
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct BackgroundRead<'a, 'd, T: Instance, const FLASH_SIZE: usize> {
flash: PhantomData<&'a mut Flash<'d, T, Async, FLASH_SIZE>>,
transfer: Transfer<'a, AnyChannel>,
}
impl<'a, 'd, T: Instance, const FLASH_SIZE: usize> Future for BackgroundRead<'a, 'd, T, FLASH_SIZE> {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.transfer).poll(cx)
}
}
impl<'a, 'd, T: Instance, const FLASH_SIZE: usize> Drop for BackgroundRead<'a, 'd, T, FLASH_SIZE> {
fn drop(&mut self) {
if pac::XIP_CTRL.stream_ctr().read().0 == 0 {
return;
}
pac::XIP_CTRL
.stream_ctr()
.write_value(pac::xip_ctrl::regs::StreamCtr(0));
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
// Errata RP2040-E8: Perform an uncached read to make sure there's not a transfer in
// flight that might effect an address written to start a new transfer. This stalls
// until after any transfer is complete, so the address will not change anymore.
#[cfg(feature = "rp2040")]
const XIP_NOCACHE_NOALLOC_BASE: *const u32 = 0x13000000 as *const _;
#[cfg(feature = "_rp235x")]
const XIP_NOCACHE_NOALLOC_BASE: *const u32 = 0x14000000 as *const _;
unsafe {
core::ptr::read_volatile(XIP_NOCACHE_NOALLOC_BASE);
}
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}
}
/// Flash driver.
pub struct Flash<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> {
dma: Option<PeripheralRef<'d, AnyChannel>>,
phantom: PhantomData<(&'d mut T, M)>,
}
impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SIZE> {
/// Blocking read.
///
/// The offset and buffer must be aligned.
///
/// NOTE: `offset` is an offset from the flash start, NOT an absolute address.
pub fn blocking_read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> {
trace!(
"Reading from 0x{:x} to 0x{:x}",
FLASH_BASE as u32 + offset,
FLASH_BASE as u32 + offset + bytes.len() as u32
);
check_read(self, offset, bytes.len())?;
let flash_data = unsafe { core::slice::from_raw_parts((FLASH_BASE as u32 + offset) as *const u8, bytes.len()) };
bytes.copy_from_slice(flash_data);
Ok(())
}
/// Flash capacity.
pub fn capacity(&self) -> usize {
FLASH_SIZE
}
/// Blocking erase.
///
/// NOTE: `offset` is an offset from the flash start, NOT an absolute address.
pub fn blocking_erase(&mut self, from: u32, to: u32) -> Result<(), Error> {
check_erase(self, from, to)?;
trace!(
"Erasing from 0x{:x} to 0x{:x}",
FLASH_BASE as u32 + from,
FLASH_BASE as u32 + to
);
let len = to - from;
unsafe { in_ram(|| ram_helpers::flash_range_erase(from, len))? };
Ok(())
}
/// Blocking write.
///
/// The offset and buffer must be aligned.
///
/// NOTE: `offset` is an offset from the flash start, NOT an absolute address.
pub fn blocking_write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> {
check_write(self, offset, bytes.len())?;
trace!("Writing {:?} bytes to 0x{:x}", bytes.len(), FLASH_BASE as u32 + offset);
let end_offset = offset as usize + bytes.len();
let padded_offset = (offset as *const u8).align_offset(PAGE_SIZE);
let start_padding = core::cmp::min(padded_offset, bytes.len());
// Pad in the beginning
if start_padding > 0 {
let start = PAGE_SIZE - padded_offset;
let end = start + start_padding;
let mut pad_buf = [0xFF_u8; PAGE_SIZE];
pad_buf[start..end].copy_from_slice(&bytes[..start_padding]);
let unaligned_offset = offset as usize - start;
unsafe { in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? }
}
let remaining_len = bytes.len() - start_padding;
let end_padding = start_padding + PAGE_SIZE * (remaining_len / PAGE_SIZE);
// Write aligned slice of length in multiples of 256 bytes
// If the remaining bytes to be written is more than a full page.
if remaining_len >= PAGE_SIZE {
let mut aligned_offset = if start_padding > 0 {
offset as usize + padded_offset
} else {
offset as usize
};
if bytes.as_ptr() as usize >= 0x2000_0000 {
let aligned_data = &bytes[start_padding..end_padding];
unsafe { in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, aligned_data))? }
} else {
for chunk in bytes[start_padding..end_padding].chunks_exact(PAGE_SIZE) {
let mut ram_buf = [0xFF_u8; PAGE_SIZE];
ram_buf.copy_from_slice(chunk);
unsafe { in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, &ram_buf))? }
aligned_offset += PAGE_SIZE;
}
}
}
// Pad in the end
let rem_offset = (end_offset as *const u8).align_offset(PAGE_SIZE);
let rem_padding = remaining_len % PAGE_SIZE;
if rem_padding > 0 {
let mut pad_buf = [0xFF_u8; PAGE_SIZE];
pad_buf[..rem_padding].copy_from_slice(&bytes[end_padding..]);
let unaligned_offset = end_offset - (PAGE_SIZE - rem_offset);
unsafe { in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? }
}
Ok(())
}
/// Read SPI flash unique ID
#[cfg(feature = "rp2040")]
pub fn blocking_unique_id(&mut self, uid: &mut [u8]) -> Result<(), Error> {
unsafe { in_ram(|| ram_helpers::flash_unique_id(uid))? };
Ok(())
}
/// Read SPI flash JEDEC ID
#[cfg(feature = "rp2040")]
pub fn blocking_jedec_id(&mut self) -> Result<u32, Error> {
let mut jedec = None;
unsafe {
in_ram(|| {
jedec.replace(ram_helpers::flash_jedec_id());
})?;
};
Ok(jedec.unwrap())
}
}
impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, Blocking, FLASH_SIZE> {
/// Create a new flash driver in blocking mode.
pub fn new_blocking(_flash: impl Peripheral<P = T> + 'd) -> Self {
Self {
dma: None,
phantom: PhantomData,
}
}
}
impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, Async, FLASH_SIZE> {
/// Create a new flash driver in async mode.
pub fn new(_flash: impl Peripheral<P = T> + 'd, dma: impl Peripheral<P = impl Channel> + 'd) -> Self {
into_ref!(dma);
Self {
dma: Some(dma.map_into()),
phantom: PhantomData,
}
}
/// Start a background read operation.
///
/// The offset and buffer must be aligned.
///
/// NOTE: `offset` is an offset from the flash start, NOT an absolute address.
pub fn background_read<'a>(
&'a mut self,
offset: u32,
data: &'a mut [u32],
) -> Result<BackgroundRead<'a, 'd, T, FLASH_SIZE>, Error> {
trace!(
"Reading in background from 0x{:x} to 0x{:x}",
FLASH_BASE as u32 + offset,
FLASH_BASE as u32 + offset + (data.len() * 4) as u32
);
// Can't use check_read because we need to enforce 4-byte alignment
let offset = offset as usize;
let length = data.len() * 4;
if length > self.capacity() || offset > self.capacity() - length {
return Err(Error::OutOfBounds);
}
if offset % 4 != 0 {
return Err(Error::Unaligned);
}
while !pac::XIP_CTRL.stat().read().fifo_empty() {
pac::XIP_CTRL.stream_fifo().read();
}
pac::XIP_CTRL
.stream_addr()
.write_value(pac::xip_ctrl::regs::StreamAddr(FLASH_BASE as u32 + offset as u32));
pac::XIP_CTRL
.stream_ctr()
.write_value(pac::xip_ctrl::regs::StreamCtr(data.len() as u32));
// Use the XIP AUX bus port, rather than the FIFO register access (e.x.
// pac::XIP_CTRL.stream_fifo().as_ptr()) to avoid DMA stalling on
// general XIP access.
#[cfg(feature = "rp2040")]
const XIP_AUX_BASE: *const u32 = 0x50400000 as *const _;
#[cfg(feature = "_rp235x")]
const XIP_AUX_BASE: *const u32 = 0x50500000 as *const _;
let transfer = unsafe {
crate::dma::read(
self.dma.as_mut().unwrap(),
XIP_AUX_BASE,
data,
pac::dma::vals::TreqSel::XIP_STREAM,
)
};
Ok(BackgroundRead {
flash: PhantomData,
transfer,
})
}
/// Async read.
///
/// The offset and buffer must be aligned.
///
/// NOTE: `offset` is an offset from the flash start, NOT an absolute address.
pub async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> {
use core::mem::MaybeUninit;
// Checked early to simplify address validity checks
if bytes.len() % 4 != 0 {
return Err(Error::Unaligned);
}
// If the destination address is already aligned, then we can just DMA directly
if (bytes.as_ptr() as u32) % 4 == 0 {
// Safety: alignment and size have been checked for compatibility
let buf: &mut [u32] =
unsafe { core::slice::from_raw_parts_mut(bytes.as_mut_ptr() as *mut u32, bytes.len() / 4) };
self.background_read(offset, buf)?.await;
return Ok(());
}
// Destination address is unaligned, so use an intermediate buffer
const REALIGN_CHUNK: usize = PAGE_SIZE;
// Safety: MaybeUninit requires no initialization
let mut buf: [MaybeUninit<u32>; REALIGN_CHUNK / 4] = unsafe { MaybeUninit::uninit().assume_init() };
let mut chunk_offset: usize = 0;
while chunk_offset < bytes.len() {
let chunk_size = (bytes.len() - chunk_offset).min(REALIGN_CHUNK);
let buf = &mut buf[..(chunk_size / 4)];
// Safety: this is written to completely by DMA before any reads
let buf = unsafe { &mut *(buf as *mut [MaybeUninit<u32>] as *mut [u32]) };
self.background_read(offset + chunk_offset as u32, buf)?.await;
// Safety: [u8] has more relaxed alignment and size requirements than [u32], so this is just aliasing
let buf = unsafe { core::slice::from_raw_parts(buf.as_ptr() as *const _, buf.len() * 4) };
bytes[chunk_offset..(chunk_offset + chunk_size)].copy_from_slice(&buf[..chunk_size]);
chunk_offset += chunk_size;
}
Ok(())
}
}
impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> ErrorType for Flash<'d, T, M, FLASH_SIZE> {
type Error = Error;
}
impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> ReadNorFlash for Flash<'d, T, M, FLASH_SIZE> {
const READ_SIZE: usize = READ_SIZE;
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
self.blocking_read(offset, bytes)
}
fn capacity(&self) -> usize {
self.capacity()
}
}
impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> MultiwriteNorFlash for Flash<'d, T, M, FLASH_SIZE> {}
impl<'d, T: Instance, const FLASH_SIZE: usize> embedded_storage_async::nor_flash::MultiwriteNorFlash
for Flash<'d, T, Async, FLASH_SIZE>
{
}
impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> NorFlash for Flash<'d, T, M, FLASH_SIZE> {
const WRITE_SIZE: usize = WRITE_SIZE;
const ERASE_SIZE: usize = ERASE_SIZE;
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
self.blocking_erase(from, to)
}
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
self.blocking_write(offset, bytes)
}
}
impl<'d, T: Instance, const FLASH_SIZE: usize> embedded_storage_async::nor_flash::ReadNorFlash
for Flash<'d, T, Async, FLASH_SIZE>
{
const READ_SIZE: usize = ASYNC_READ_SIZE;
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
self.read(offset, bytes).await
}
fn capacity(&self) -> usize {
self.capacity()
}
}
impl<'d, T: Instance, const FLASH_SIZE: usize> embedded_storage_async::nor_flash::NorFlash
for Flash<'d, T, Async, FLASH_SIZE>
{
const WRITE_SIZE: usize = WRITE_SIZE;
const ERASE_SIZE: usize = ERASE_SIZE;
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
self.blocking_erase(from, to)
}
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
self.blocking_write(offset, bytes)
}
}
#[allow(dead_code)]
mod ram_helpers {
use super::*;
use crate::rom_data;
#[repr(C)]
struct FlashFunctionPointers<'a> {
connect_internal_flash: unsafe extern "C" fn() -> (),
flash_exit_xip: unsafe extern "C" fn() -> (),
flash_range_erase: Option<unsafe extern "C" fn(addr: u32, count: usize, block_size: u32, block_cmd: u8) -> ()>,
flash_range_program: Option<unsafe extern "C" fn(addr: u32, data: *const u8, count: usize) -> ()>,
flash_flush_cache: unsafe extern "C" fn() -> (),
flash_enter_cmd_xip: unsafe extern "C" fn() -> (),
phantom: PhantomData<&'a ()>,
}
#[allow(unused)]
fn flash_function_pointers(erase: bool, write: bool) -> FlashFunctionPointers<'static> {
FlashFunctionPointers {
connect_internal_flash: rom_data::connect_internal_flash::ptr(),
flash_exit_xip: rom_data::flash_exit_xip::ptr(),
flash_range_erase: if erase {
Some(rom_data::flash_range_erase::ptr())
} else {
None
},
flash_range_program: if write {
Some(rom_data::flash_range_program::ptr())
} else {
None
},
flash_flush_cache: rom_data::flash_flush_cache::ptr(),
flash_enter_cmd_xip: rom_data::flash_enter_cmd_xip::ptr(),
phantom: PhantomData,
}
}
#[allow(unused)]
/// # Safety
///
/// `boot2` must contain a valid 2nd stage boot loader which can be called to re-initialize XIP mode
unsafe fn flash_function_pointers_with_boot2(erase: bool, write: bool, boot2: &[u32; 64]) -> FlashFunctionPointers {
let boot2_fn_ptr = (boot2 as *const u32 as *const u8).offset(1);
let boot2_fn: unsafe extern "C" fn() -> () = core::mem::transmute(boot2_fn_ptr);
FlashFunctionPointers {
connect_internal_flash: rom_data::connect_internal_flash::ptr(),
flash_exit_xip: rom_data::flash_exit_xip::ptr(),
flash_range_erase: if erase {
Some(rom_data::flash_range_erase::ptr())
} else {
None
},
flash_range_program: if write {
Some(rom_data::flash_range_program::ptr())
} else {
None
},
flash_flush_cache: rom_data::flash_flush_cache::ptr(),
flash_enter_cmd_xip: boot2_fn,
phantom: PhantomData,
}
}
/// Erase a flash range starting at `addr` with length `len`.
///
/// `addr` and `len` must be multiples of 4096
///
/// If `USE_BOOT2` is `true`, a copy of the 2nd stage boot loader
/// is used to re-initialize the XIP engine after flashing.
///
/// # Safety
///
/// Nothing must access flash while this is running.
/// Usually this means:
/// - interrupts must be disabled
/// - 2nd core must be running code from RAM or ROM with interrupts disabled
/// - DMA must not access flash memory
///
/// `addr` and `len` parameters must be valid and are not checked.
pub unsafe fn flash_range_erase(addr: u32, len: u32) {
let mut boot2 = [0u32; 256 / 4];
let ptrs = if USE_BOOT2 {
#[cfg(feature = "rp2040")]
rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256);
#[cfg(feature = "_rp235x")]
core::ptr::copy_nonoverlapping(BOOTROM_BASE as *const u8, boot2.as_mut_ptr() as *mut u8, 256);
flash_function_pointers_with_boot2(true, false, &boot2)
} else {
flash_function_pointers(true, false)
};
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
write_flash_inner(addr, len, None, &ptrs as *const FlashFunctionPointers);
}
/// Erase and rewrite a flash range starting at `addr` with data `data`.
///
/// `addr` and `data.len()` must be multiples of 4096
///
/// If `USE_BOOT2` is `true`, a copy of the 2nd stage boot loader
/// is used to re-initialize the XIP engine after flashing.
///
/// # Safety
///
/// Nothing must access flash while this is running.
/// Usually this means:
/// - interrupts must be disabled
/// - 2nd core must be running code from RAM or ROM with interrupts disabled
/// - DMA must not access flash memory
///
/// `addr` and `len` parameters must be valid and are not checked.
pub unsafe fn flash_range_erase_and_program(addr: u32, data: &[u8]) {
let mut boot2 = [0u32; 256 / 4];
let ptrs = if USE_BOOT2 {
#[cfg(feature = "rp2040")]
rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256);
#[cfg(feature = "_rp235x")]
core::ptr::copy_nonoverlapping(BOOTROM_BASE as *const u8, (boot2).as_mut_ptr() as *mut u8, 256);
flash_function_pointers_with_boot2(true, true, &boot2)
} else {
flash_function_pointers(true, true)
};
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
write_flash_inner(
addr,
data.len() as u32,
Some(data),
&ptrs as *const FlashFunctionPointers,
);
}
/// Write a flash range starting at `addr` with data `data`.
///
/// `addr` and `data.len()` must be multiples of 256
///
/// If `USE_BOOT2` is `true`, a copy of the 2nd stage boot loader
/// is used to re-initialize the XIP engine after flashing.
///
/// # Safety
///
/// Nothing must access flash while this is running.
/// Usually this means:
/// - interrupts must be disabled
/// - 2nd core must be running code from RAM or ROM with interrupts disabled
/// - DMA must not access flash memory
///
/// `addr` and `len` parameters must be valid and are not checked.
pub unsafe fn flash_range_program(addr: u32, data: &[u8]) {
let mut boot2 = [0u32; 256 / 4];
let ptrs = if USE_BOOT2 {
#[cfg(feature = "rp2040")]
rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256);
#[cfg(feature = "_rp235x")]
core::ptr::copy_nonoverlapping(BOOTROM_BASE as *const u8, boot2.as_mut_ptr() as *mut u8, 256);
flash_function_pointers_with_boot2(false, true, &boot2)
} else {
flash_function_pointers(false, true)
};
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
write_flash_inner(
addr,
data.len() as u32,
Some(data),
&ptrs as *const FlashFunctionPointers,
);
}
/// # Safety
///
/// Nothing must access flash while this is running.
/// Usually this means:
/// - interrupts must be disabled
/// - 2nd core must be running code from RAM or ROM with interrupts disabled
/// - DMA must not access flash memory
/// Length of data must be a multiple of 4096
/// addr must be aligned to 4096
#[inline(never)]
#[link_section = ".data.ram_func"]
#[cfg(feature = "rp2040")]
unsafe fn write_flash_inner(addr: u32, len: u32, data: Option<&[u8]>, ptrs: *const FlashFunctionPointers) {
#[cfg(target_arch = "arm")]
core::arch::asm!(
"mov r8, r0",
"mov r9, r2",
"mov r10, r1",
"ldr r4, [{ptrs}, #0]",
"blx r4", // connect_internal_flash()
"ldr r4, [{ptrs}, #4]",
"blx r4", // flash_exit_xip()
"mov r0, r8", // r0 = addr
"mov r1, r10", // r1 = len
"movs r2, #1",
"lsls r2, r2, #31", // r2 = 1 << 31
"movs r3, #0", // r3 = 0
"ldr r4, [{ptrs}, #8]",
"cmp r4, #0",
"beq 2f",
"blx r4", // flash_range_erase(addr, len, 1 << 31, 0)
"2:",
"mov r0, r8", // r0 = addr
"mov r1, r9", // r0 = data
"mov r2, r10", // r2 = len
"ldr r4, [{ptrs}, #12]",
"cmp r4, #0",
"beq 2f",
"blx r4", // flash_range_program(addr, data, len);
"2:",
"ldr r4, [{ptrs}, #16]",
"blx r4", // flash_flush_cache();
"ldr r4, [{ptrs}, #20]",
"blx r4", // flash_enter_cmd_xip();
ptrs = in(reg) ptrs,
// Registers r8-r15 are not allocated automatically,
// so assign them manually. We need to use them as
// otherwise there are not enough registers available.
in("r0") addr,
in("r2") data.map(|d| d.as_ptr()).unwrap_or(core::ptr::null()),
in("r1") len,
out("r3") _,
out("r4") _,
lateout("r8") _,
lateout("r9") _,
lateout("r10") _,
clobber_abi("C"),
);
}
/// # Safety
///
/// Nothing must access flash while this is running.
/// Usually this means:
/// - interrupts must be disabled
/// - 2nd core must be running code from RAM or ROM with interrupts disabled
/// - DMA must not access flash memory
/// Length of data must be a multiple of 4096
/// addr must be aligned to 4096
#[inline(never)]
#[link_section = ".data.ram_func"]
#[cfg(feature = "_rp235x")]
unsafe fn write_flash_inner(addr: u32, len: u32, data: Option<&[u8]>, ptrs: *const FlashFunctionPointers) {
let data = data.map(|d| d.as_ptr()).unwrap_or(core::ptr::null());
((*ptrs).connect_internal_flash)();
((*ptrs).flash_exit_xip)();
if (*ptrs).flash_range_erase.is_some() {
((*ptrs).flash_range_erase.unwrap())(addr, len as usize, 1 << 31, 0);
}
if (*ptrs).flash_range_program.is_some() {
((*ptrs).flash_range_program.unwrap())(addr, data as *const _, len as usize);
}
((*ptrs).flash_flush_cache)();
((*ptrs).flash_enter_cmd_xip)();
}
#[repr(C)]
struct FlashCommand {
cmd_addr: *const u8,
cmd_addr_len: u32,
dummy_len: u32,
data: *mut u8,
data_len: u32,
}
/// Return SPI flash unique ID
///
/// Not all SPI flashes implement this command, so check the JEDEC
/// ID before relying on it. The Winbond parts commonly seen on
/// RP2040 devboards (JEDEC=0xEF7015) support an 8-byte unique ID;
/// https://forums.raspberrypi.com/viewtopic.php?t=331949 suggests
/// that LCSC (Zetta) parts have a 16-byte unique ID (which is
/// *not* unique in just its first 8 bytes),
/// JEDEC=0xBA6015. Macronix and Spansion parts do not have a
/// unique ID.
///
/// The returned bytes are relatively predictable and should be
/// salted and hashed before use if that is an issue (e.g. for MAC
/// addresses).
///
/// # Safety
///
/// Nothing must access flash while this is running.
/// Usually this means:
/// - interrupts must be disabled
/// - 2nd core must be running code from RAM or ROM with interrupts disabled
/// - DMA must not access flash memory
///
/// Credit: taken from `rp2040-flash` (also licensed Apache+MIT)
#[cfg(feature = "rp2040")]
pub unsafe fn flash_unique_id(out: &mut [u8]) {
let mut boot2 = [0u32; 256 / 4];
let ptrs = if USE_BOOT2 {
rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256);
flash_function_pointers_with_boot2(false, false, &boot2)
} else {
flash_function_pointers(false, false)
};
// 4B - read unique ID
let cmd = [0x4B];
read_flash(&cmd[..], 4, out, &ptrs as *const FlashFunctionPointers);
}
/// Return SPI flash JEDEC ID
///
/// This is the three-byte manufacturer-and-model identifier
/// commonly used to check before using manufacturer-specific SPI
/// flash features, e.g. 0xEF7015 for Winbond W25Q16JV.
///
/// # Safety
///
/// Nothing must access flash while this is running.
/// Usually this means:
/// - interrupts must be disabled
/// - 2nd core must be running code from RAM or ROM with interrupts disabled
/// - DMA must not access flash memory
///
/// Credit: taken from `rp2040-flash` (also licensed Apache+MIT)
#[cfg(feature = "rp2040")]
pub unsafe fn flash_jedec_id() -> u32 {
let mut boot2 = [0u32; 256 / 4];
let ptrs = if USE_BOOT2 {
rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256);
flash_function_pointers_with_boot2(false, false, &boot2)
} else {
flash_function_pointers(false, false)
};
let mut id = [0u8; 4];
// 9F - read JEDEC ID
let cmd = [0x9F];
read_flash(&cmd[..], 0, &mut id[1..4], &ptrs as *const FlashFunctionPointers);
u32::from_be_bytes(id)
}
#[cfg(feature = "rp2040")]
unsafe fn read_flash(cmd_addr: &[u8], dummy_len: u32, out: &mut [u8], ptrs: *const FlashFunctionPointers) {
read_flash_inner(
FlashCommand {
cmd_addr: cmd_addr.as_ptr(),
cmd_addr_len: cmd_addr.len() as u32,
dummy_len,
data: out.as_mut_ptr(),
data_len: out.len() as u32,
},
ptrs,
);
}
/// Issue a generic SPI flash read command
///
/// # Arguments
///
/// * `cmd` - `FlashCommand` structure
/// * `ptrs` - Flash function pointers as per `write_flash_inner`
///
/// Credit: taken from `rp2040-flash` (also licensed Apache+MIT)
#[inline(never)]
#[link_section = ".data.ram_func"]
#[cfg(feature = "rp2040")]
unsafe fn read_flash_inner(cmd: FlashCommand, ptrs: *const FlashFunctionPointers) {
#[cfg(target_arch = "arm")]
core::arch::asm!(
"mov r10, r0", // cmd
"mov r5, r1", // ptrs
"ldr r4, [r5, #0]",
"blx r4", // connect_internal_flash()
"ldr r4, [r5, #4]",
"blx r4", // flash_exit_xip()
"movs r4, #0x18",
"lsls r4, r4, #24", // 0x18000000, SSI, RP2040 datasheet 4.10.13
// Disable, write 0 to SSIENR
"movs r0, #0",
"str r0, [r4, #8]", // SSIENR
// Write ctrlr0
"movs r0, #0x3",
"lsls r0, r0, #8", // TMOD=0x300
"ldr r1, [r4, #0]", // CTRLR0
"orrs r1, r0",
"str r1, [r4, #0]",
// Write ctrlr1 with len-1
"mov r3, r10", // cmd
"ldr r0, [r3, #8]", // dummy_len
"ldr r1, [r3, #16]", // data_len
"add r0, r1",
"subs r0, #1",
"str r0, [r4, #0x04]", // CTRLR1
// Enable, write 1 to ssienr
"movs r0, #1",
"str r0, [r4, #8]", // SSIENR
// Write cmd/addr phase to DR
"mov r2, r4",
"adds r2, 0x60", // &DR
"ldr r0, [r3, #0]", // cmd_addr
"ldr r1, [r3, #4]", // cmd_addr_len
"3:",
"ldrb r3, [r0]",
"strb r3, [r2]", // DR
"adds r0, #1",
"subs r1, #1",
"bne 3b",
// Skip any dummy cycles
"mov r3, r10", // cmd
"ldr r1, [r3, #8]", // dummy_len
"cmp r1, #0",
"beq 9f",
"4:",
"ldr r3, [r4, #0x28]", // SR
"movs r2, #0x8",
"tst r3, r2", // SR.RFNE
"beq 4b",
"mov r2, r4",
"adds r2, 0x60", // &DR
"ldrb r3, [r2]", // DR
"subs r1, #1",
"bne 4b",
// Read RX fifo
"9:",
"mov r2, r10", // cmd
"ldr r0, [r2, #12]", // data
"ldr r1, [r2, #16]", // data_len
"2:",
"ldr r3, [r4, #0x28]", // SR
"movs r2, #0x8",
"tst r3, r2", // SR.RFNE
"beq 2b",
"mov r2, r4",
"adds r2, 0x60", // &DR
"ldrb r3, [r2]", // DR
"strb r3, [r0]",
"adds r0, #1",
"subs r1, #1",
"bne 2b",
// Disable, write 0 to ssienr
"movs r0, #0",
"str r0, [r4, #8]", // SSIENR
// Write 0 to CTRLR1 (returning to its default value)
//
// flash_enter_cmd_xip does NOT do this, and everything goes
// wrong unless we do it here
"str r0, [r4, #4]", // CTRLR1
"ldr r4, [r5, #20]",
"blx r4", // flash_enter_cmd_xip();
in("r0") &cmd as *const FlashCommand,
in("r1") ptrs,
out("r2") _,
out("r3") _,
out("r4") _,
out("r5") _,
// Registers r8-r10 are used to store values
// from r0-r2 in registers not clobbered by
// function calls.
// The values can't be passed in using r8-r10 directly
// due to https://github.com/rust-lang/rust/issues/99071
out("r10") _,
clobber_abi("C"),
);
}
}
/// Make sure to uphold the contract points with rp2040-flash.
/// - interrupts must be disabled
/// - DMA must not access flash memory
pub(crate) unsafe fn in_ram(operation: impl FnOnce()) -> Result<(), Error> {
// Make sure we're running on CORE0
let core_id: u32 = pac::SIO.cpuid().read();
if core_id != 0 {
return Err(Error::InvalidCore);
}
// Make sure CORE1 is paused during the entire duration of the RAM function
crate::multicore::pause_core1();
critical_section::with(|_| {
// Wait for all DMA channels in flash to finish before ram operation
const SRAM_LOWER: u32 = 0x2000_0000;
for n in 0..crate::dma::CHANNEL_COUNT {
let ch = crate::pac::DMA.ch(n);
while ch.read_addr().read() < SRAM_LOWER && ch.ctrl_trig().read().busy() {}
}
// Wait for completion of any background reads
while pac::XIP_CTRL.stream_ctr().read().0 > 0 {}
// Run our flash operation in RAM
operation();
});
// Resume CORE1 execution
crate::multicore::resume_core1();
Ok(())
}
trait SealedInstance {}
trait SealedMode {}
/// Flash instance.
#[allow(private_bounds)]
pub trait Instance: SealedInstance {}
/// Flash mode.
#[allow(private_bounds)]
pub trait Mode: SealedMode {}
impl SealedInstance for FLASH {}
impl Instance for FLASH {}
macro_rules! impl_mode {
($name:ident) => {
impl SealedMode for $name {}
impl Mode for $name {}
};
}
/// Flash blocking mode.
pub struct Blocking;
/// Flash async mode.
pub struct Async;
impl_mode!(Blocking);
impl_mode!(Async);

View File

@@ -0,0 +1,92 @@
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/add_sub.rs
use super::{Float, Int};
use crate::rom_data;
trait ROMAdd {
fn rom_add(self, b: Self) -> Self;
}
impl ROMAdd for f32 {
fn rom_add(self, b: Self) -> Self {
rom_data::float_funcs::fadd(self, b)
}
}
impl ROMAdd for f64 {
fn rom_add(self, b: Self) -> Self {
rom_data::double_funcs::dadd(self, b)
}
}
fn add<F: Float + ROMAdd>(a: F, b: F) -> F {
if a.is_not_finite() {
if b.is_not_finite() {
let class_a = a.repr() & (F::SIGNIFICAND_MASK | F::SIGN_MASK);
let class_b = b.repr() & (F::SIGNIFICAND_MASK | F::SIGN_MASK);
if class_a == F::Int::ZERO && class_b == F::Int::ZERO {
// inf + inf = inf
return a;
}
if class_a == F::SIGN_MASK && class_b == F::SIGN_MASK {
// -inf + (-inf) = -inf
return a;
}
// Sign mismatch, or either is NaN already
return F::NAN;
}
// [-]inf/NaN + X = [-]inf/NaN
return a;
}
if b.is_not_finite() {
// X + [-]inf/NaN = [-]inf/NaN
return b;
}
a.rom_add(b)
}
intrinsics! {
#[alias = __addsf3vfp]
#[aeabi = __aeabi_fadd]
extern "C" fn __addsf3(a: f32, b: f32) -> f32 {
add(a, b)
}
#[bootrom_v2]
#[alias = __adddf3vfp]
#[aeabi = __aeabi_dadd]
extern "C" fn __adddf3(a: f64, b: f64) -> f64 {
add(a, b)
}
// The ROM just implements subtraction the same way, so just do it here
// and save the work of implementing more complicated NaN/inf handling.
#[alias = __subsf3vfp]
#[aeabi = __aeabi_fsub]
extern "C" fn __subsf3(a: f32, b: f32) -> f32 {
add(a, -b)
}
#[bootrom_v2]
#[alias = __subdf3vfp]
#[aeabi = __aeabi_dsub]
extern "C" fn __subdf3(a: f64, b: f64) -> f64 {
add(a, -b)
}
extern "aapcs" fn __aeabi_frsub(a: f32, b: f32) -> f32 {
add(b, -a)
}
#[bootrom_v2]
extern "aapcs" fn __aeabi_drsub(a: f64, b: f64) -> f64 {
add(b, -a)
}
}

201
embassy-rp/src/float/cmp.rs Normal file
View File

@@ -0,0 +1,201 @@
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/cmp.rs
use super::Float;
use crate::rom_data;
trait ROMCmp {
fn rom_cmp(self, b: Self) -> i32;
}
impl ROMCmp for f32 {
fn rom_cmp(self, b: Self) -> i32 {
rom_data::float_funcs::fcmp(self, b)
}
}
impl ROMCmp for f64 {
fn rom_cmp(self, b: Self) -> i32 {
rom_data::double_funcs::dcmp(self, b)
}
}
fn le_abi<F: Float + ROMCmp>(a: F, b: F) -> i32 {
if a.is_nan() || b.is_nan() {
1
} else {
a.rom_cmp(b)
}
}
fn ge_abi<F: Float + ROMCmp>(a: F, b: F) -> i32 {
if a.is_nan() || b.is_nan() {
-1
} else {
a.rom_cmp(b)
}
}
intrinsics! {
#[slower_than_default]
#[bootrom_v2]
#[alias = __eqsf2, __ltsf2, __nesf2]
extern "C" fn __lesf2(a: f32, b: f32) -> i32 {
le_abi(a, b)
}
#[slower_than_default]
#[bootrom_v2]
#[alias = __eqdf2, __ltdf2, __nedf2]
extern "C" fn __ledf2(a: f64, b: f64) -> i32 {
le_abi(a, b)
}
#[slower_than_default]
#[bootrom_v2]
#[alias = __gtsf2]
extern "C" fn __gesf2(a: f32, b: f32) -> i32 {
ge_abi(a, b)
}
#[slower_than_default]
#[bootrom_v2]
#[alias = __gtdf2]
extern "C" fn __gedf2(a: f64, b: f64) -> i32 {
ge_abi(a, b)
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_fcmple(a: f32, b: f32) -> i32 {
(le_abi(a, b) <= 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_fcmpge(a: f32, b: f32) -> i32 {
(ge_abi(a, b) >= 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_fcmpeq(a: f32, b: f32) -> i32 {
(le_abi(a, b) == 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_fcmplt(a: f32, b: f32) -> i32 {
(le_abi(a, b) < 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_fcmpgt(a: f32, b: f32) -> i32 {
(ge_abi(a, b) > 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_dcmple(a: f64, b: f64) -> i32 {
(le_abi(a, b) <= 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_dcmpge(a: f64, b: f64) -> i32 {
(ge_abi(a, b) >= 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_dcmpeq(a: f64, b: f64) -> i32 {
(le_abi(a, b) == 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_dcmplt(a: f64, b: f64) -> i32 {
(le_abi(a, b) < 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "aapcs" fn __aeabi_dcmpgt(a: f64, b: f64) -> i32 {
(ge_abi(a, b) > 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __gesf2vfp(a: f32, b: f32) -> i32 {
(ge_abi(a, b) >= 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __gedf2vfp(a: f64, b: f64) -> i32 {
(ge_abi(a, b) >= 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __gtsf2vfp(a: f32, b: f32) -> i32 {
(ge_abi(a, b) > 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __gtdf2vfp(a: f64, b: f64) -> i32 {
(ge_abi(a, b) > 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __ltsf2vfp(a: f32, b: f32) -> i32 {
(le_abi(a, b) < 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __ltdf2vfp(a: f64, b: f64) -> i32 {
(le_abi(a, b) < 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __lesf2vfp(a: f32, b: f32) -> i32 {
(le_abi(a, b) <= 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __ledf2vfp(a: f64, b: f64) -> i32 {
(le_abi(a, b) <= 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __nesf2vfp(a: f32, b: f32) -> i32 {
(le_abi(a, b) != 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __nedf2vfp(a: f64, b: f64) -> i32 {
(le_abi(a, b) != 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __eqsf2vfp(a: f32, b: f32) -> i32 {
(le_abi(a, b) == 0) as i32
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn __eqdf2vfp(a: f64, b: f64) -> i32 {
(le_abi(a, b) == 0) as i32
}
}

View File

@@ -0,0 +1,157 @@
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/conv.rs
use super::Float;
use crate::rom_data;
// Some of these are also not connected in the Pico SDK. This is probably
// because the ROM version actually does a fixed point conversion, just with
// the fractional width set to zero.
intrinsics! {
// Not connected in the Pico SDK
#[slower_than_default]
#[aeabi = __aeabi_i2f]
extern "C" fn __floatsisf(i: i32) -> f32 {
rom_data::float_funcs::int_to_float(i)
}
// Not connected in the Pico SDK
#[slower_than_default]
#[aeabi = __aeabi_i2d]
extern "C" fn __floatsidf(i: i32) -> f64 {
rom_data::double_funcs::int_to_double(i)
}
// Questionable gain
#[aeabi = __aeabi_l2f]
extern "C" fn __floatdisf(i: i64) -> f32 {
rom_data::float_funcs::int64_to_float(i)
}
#[bootrom_v2]
#[aeabi = __aeabi_l2d]
extern "C" fn __floatdidf(i: i64) -> f64 {
rom_data::double_funcs::int64_to_double(i)
}
// Not connected in the Pico SDK
#[slower_than_default]
#[aeabi = __aeabi_ui2f]
extern "C" fn __floatunsisf(i: u32) -> f32 {
rom_data::float_funcs::uint_to_float(i)
}
// Questionable gain
#[bootrom_v2]
#[aeabi = __aeabi_ui2d]
extern "C" fn __floatunsidf(i: u32) -> f64 {
rom_data::double_funcs::uint_to_double(i)
}
// Questionable gain
#[bootrom_v2]
#[aeabi = __aeabi_ul2f]
extern "C" fn __floatundisf(i: u64) -> f32 {
rom_data::float_funcs::uint64_to_float(i)
}
#[bootrom_v2]
#[aeabi = __aeabi_ul2d]
extern "C" fn __floatundidf(i: u64) -> f64 {
rom_data::double_funcs::uint64_to_double(i)
}
// The Pico SDK does some optimization here (e.x. fast paths for zero and
// one), but we can just directly connect it.
#[aeabi = __aeabi_f2iz]
extern "C" fn __fixsfsi(f: f32) -> i32 {
rom_data::float_funcs::float_to_int(f)
}
#[bootrom_v2]
#[aeabi = __aeabi_f2lz]
extern "C" fn __fixsfdi(f: f32) -> i64 {
rom_data::float_funcs::float_to_int64(f)
}
// Not connected in the Pico SDK
#[slower_than_default]
#[bootrom_v2]
#[aeabi = __aeabi_d2iz]
extern "C" fn __fixdfsi(f: f64) -> i32 {
rom_data::double_funcs::double_to_int(f)
}
// Like with the 32 bit version, there's optimization that we just
// skip.
#[bootrom_v2]
#[aeabi = __aeabi_d2lz]
extern "C" fn __fixdfdi(f: f64) -> i64 {
rom_data::double_funcs::double_to_int64(f)
}
#[slower_than_default]
#[aeabi = __aeabi_f2uiz]
extern "C" fn __fixunssfsi(f: f32) -> u32 {
rom_data::float_funcs::float_to_uint(f)
}
#[slower_than_default]
#[bootrom_v2]
#[aeabi = __aeabi_f2ulz]
extern "C" fn __fixunssfdi(f: f32) -> u64 {
rom_data::float_funcs::float_to_uint64(f)
}
#[slower_than_default]
#[bootrom_v2]
#[aeabi = __aeabi_d2uiz]
extern "C" fn __fixunsdfsi(f: f64) -> u32 {
rom_data::double_funcs::double_to_uint(f)
}
#[slower_than_default]
#[bootrom_v2]
#[aeabi = __aeabi_d2ulz]
extern "C" fn __fixunsdfdi(f: f64) -> u64 {
rom_data::double_funcs::double_to_uint64(f)
}
#[bootrom_v2]
#[alias = __extendsfdf2vfp]
#[aeabi = __aeabi_f2d]
extern "C" fn __extendsfdf2(f: f32) -> f64 {
if f.is_not_finite() {
return f64::from_repr(
// Not finite
f64::EXPONENT_MASK |
// Preserve NaN or inf
((f.repr() & f32::SIGNIFICAND_MASK) as u64) |
// Preserve sign
((f.repr() & f32::SIGN_MASK) as u64) << (f64::BITS-f32::BITS)
);
}
rom_data::float_funcs::float_to_double(f)
}
#[bootrom_v2]
#[alias = __truncdfsf2vfp]
#[aeabi = __aeabi_d2f]
extern "C" fn __truncdfsf2(f: f64) -> f32 {
if f.is_not_finite() {
let mut repr: u32 =
// Not finite
f32::EXPONENT_MASK |
// Preserve sign
((f.repr() & f64::SIGN_MASK) >> (f64::BITS-f32::BITS)) as u32;
// Set NaN
if (f.repr() & f64::SIGNIFICAND_MASK) != 0 {
repr |= 1;
}
return f32::from_repr(repr);
}
rom_data::double_funcs::double_to_float(f)
}
}

139
embassy-rp/src/float/div.rs Normal file
View File

@@ -0,0 +1,139 @@
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/conv.rs
use super::Float;
use crate::rom_data;
// Make sure this stays as a separate call, because when it's inlined the
// compiler will move the save of the registers used to contain the divider
// state into the function prologue. That save and restore (push/pop) takes
// longer than the actual division, so doing it in the common case where
// they are not required wastes a lot of time.
#[inline(never)]
#[cold]
fn save_divider_and_call<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
let sio = rp_pac::SIO;
// Since we can't save the signed-ness of the calculation, we have to make
// sure that there's at least an 8 cycle delay before we read the result.
// The Pico SDK ensures this by using a 6 cycle push and two 1 cycle reads.
// Since we can't be sure the Rust implementation will optimize to the same,
// just use an explicit wait.
while !sio.div().csr().read().ready() {}
// Read the quotient last, since that's what clears the dirty flag
let dividend = sio.div().udividend().read();
let divisor = sio.div().udivisor().read();
let remainder = sio.div().remainder().read();
let quotient = sio.div().quotient().read();
// If we get interrupted here (before a write sets the DIRTY flag) its fine, since
// we have the full state, so the interruptor doesn't have to restore it. Once the
// write happens and the DIRTY flag is set, the interruptor becomes responsible for
// restoring our state.
let result = f();
// If we are interrupted here, then the interruptor will start an incorrect calculation
// using a wrong divisor, but we'll restore the divisor and result ourselves correctly.
// This sets DIRTY, so any interruptor will save the state.
sio.div().udividend().write_value(dividend);
// If we are interrupted here, the interruptor may start the calculation using
// incorrectly signed inputs, but we'll restore the result ourselves.
// This sets DIRTY, so any interruptor will save the state.
sio.div().udivisor().write_value(divisor);
// If we are interrupted here, the interruptor will have restored everything but the
// quotient may be wrongly signed. If the calculation started by the above writes is
// still ongoing it is stopped, so it won't replace the result we're restoring.
// DIRTY and READY set, but only DIRTY matters to make the interruptor save the state.
sio.div().remainder().write_value(remainder);
// State fully restored after the quotient write. This sets both DIRTY and READY, so
// whatever we may have interrupted can read the result.
sio.div().quotient().write_value(quotient);
result
}
fn save_divider<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
let sio = rp_pac::SIO;
if !sio.div().csr().read().dirty() {
// Not dirty, so nothing is waiting for the calculation. So we can just
// issue it directly without a save/restore.
f()
} else {
save_divider_and_call(f)
}
}
trait ROMDiv {
fn rom_div(self, b: Self) -> Self;
}
impl ROMDiv for f32 {
fn rom_div(self, b: Self) -> Self {
// ROM implementation uses the hardware divider, so we have to save it
save_divider(|| rom_data::float_funcs::fdiv(self, b))
}
}
impl ROMDiv for f64 {
fn rom_div(self, b: Self) -> Self {
// ROM implementation uses the hardware divider, so we have to save it
save_divider(|| rom_data::double_funcs::ddiv(self, b))
}
}
fn div<F: Float + ROMDiv>(a: F, b: F) -> F {
if a.is_not_finite() {
if b.is_not_finite() {
// inf/NaN / inf/NaN = NaN
return F::NAN;
}
if b.is_zero() {
// inf/NaN / 0 = NaN
return F::NAN;
}
return if b.is_sign_negative() {
// [+/-]inf/NaN / (-X) = [-/+]inf/NaN
a.negate()
} else {
// [-]inf/NaN / X = [-]inf/NaN
a
};
}
if b.is_nan() {
// X / NaN = NaN
return b;
}
// ROM handles X / 0 = [-]inf and X / [-]inf = [-]0, so we only
// need to catch 0 / 0
if b.is_zero() && a.is_zero() {
return F::NAN;
}
a.rom_div(b)
}
intrinsics! {
#[alias = __divsf3vfp]
#[aeabi = __aeabi_fdiv]
extern "C" fn __divsf3(a: f32, b: f32) -> f32 {
div(a, b)
}
#[bootrom_v2]
#[alias = __divdf3vfp]
#[aeabi = __aeabi_ddiv]
extern "C" fn __divdf3(a: f64, b: f64) -> f64 {
div(a, b)
}
}

View File

@@ -0,0 +1,239 @@
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/functions.rs
use crate::float::{Float, Int};
use crate::rom_data;
trait ROMFunctions {
fn sqrt(self) -> Self;
fn ln(self) -> Self;
fn exp(self) -> Self;
fn sin(self) -> Self;
fn cos(self) -> Self;
fn tan(self) -> Self;
fn atan2(self, y: Self) -> Self;
fn to_trig_range(self) -> Self;
}
impl ROMFunctions for f32 {
fn sqrt(self) -> Self {
rom_data::float_funcs::fsqrt(self)
}
fn ln(self) -> Self {
rom_data::float_funcs::fln(self)
}
fn exp(self) -> Self {
rom_data::float_funcs::fexp(self)
}
fn sin(self) -> Self {
rom_data::float_funcs::fsin(self)
}
fn cos(self) -> Self {
rom_data::float_funcs::fcos(self)
}
fn tan(self) -> Self {
rom_data::float_funcs::ftan(self)
}
fn atan2(self, y: Self) -> Self {
rom_data::float_funcs::fatan2(self, y)
}
fn to_trig_range(self) -> Self {
// -128 < X < 128, logic from the Pico SDK
let exponent = (self.repr() & Self::EXPONENT_MASK) >> Self::SIGNIFICAND_BITS;
if exponent < 134 {
self
} else {
self % (core::f32::consts::PI * 2.0)
}
}
}
impl ROMFunctions for f64 {
fn sqrt(self) -> Self {
rom_data::double_funcs::dsqrt(self)
}
fn ln(self) -> Self {
rom_data::double_funcs::dln(self)
}
fn exp(self) -> Self {
rom_data::double_funcs::dexp(self)
}
fn sin(self) -> Self {
rom_data::double_funcs::dsin(self)
}
fn cos(self) -> Self {
rom_data::double_funcs::dcos(self)
}
fn tan(self) -> Self {
rom_data::double_funcs::dtan(self)
}
fn atan2(self, y: Self) -> Self {
rom_data::double_funcs::datan2(self, y)
}
fn to_trig_range(self) -> Self {
// -1024 < X < 1024, logic from the Pico SDK
let exponent = (self.repr() & Self::EXPONENT_MASK) >> Self::SIGNIFICAND_BITS;
if exponent < 1033 {
self
} else {
self % (core::f64::consts::PI * 2.0)
}
}
}
fn is_negative_nonzero_or_nan<F: Float>(f: F) -> bool {
let repr = f.repr();
if (repr & F::SIGN_MASK) != F::Int::ZERO {
// Negative, so anything other than exactly zero
return (repr & (!F::SIGN_MASK)) != F::Int::ZERO;
}
// NaN
(repr & (F::EXPONENT_MASK | F::SIGNIFICAND_MASK)) > F::EXPONENT_MASK
}
fn sqrt<F: Float + ROMFunctions>(f: F) -> F {
if is_negative_nonzero_or_nan(f) {
F::NAN
} else {
f.sqrt()
}
}
fn ln<F: Float + ROMFunctions>(f: F) -> F {
if is_negative_nonzero_or_nan(f) {
F::NAN
} else {
f.ln()
}
}
fn exp<F: Float + ROMFunctions>(f: F) -> F {
if f.is_nan() {
F::NAN
} else {
f.exp()
}
}
fn sin<F: Float + ROMFunctions>(f: F) -> F {
if f.is_not_finite() {
F::NAN
} else {
f.to_trig_range().sin()
}
}
fn cos<F: Float + ROMFunctions>(f: F) -> F {
if f.is_not_finite() {
F::NAN
} else {
f.to_trig_range().cos()
}
}
fn tan<F: Float + ROMFunctions>(f: F) -> F {
if f.is_not_finite() {
F::NAN
} else {
f.to_trig_range().tan()
}
}
fn atan2<F: Float + ROMFunctions>(x: F, y: F) -> F {
if x.is_nan() || y.is_nan() {
F::NAN
} else {
x.to_trig_range().atan2(y)
}
}
// Name collisions
mod intrinsics {
intrinsics! {
extern "C" fn sqrtf(f: f32) -> f32 {
super::sqrt(f)
}
#[bootrom_v2]
extern "C" fn sqrt(f: f64) -> f64 {
super::sqrt(f)
}
extern "C" fn logf(f: f32) -> f32 {
super::ln(f)
}
#[bootrom_v2]
extern "C" fn log(f: f64) -> f64 {
super::ln(f)
}
extern "C" fn expf(f: f32) -> f32 {
super::exp(f)
}
#[bootrom_v2]
extern "C" fn exp(f: f64) -> f64 {
super::exp(f)
}
#[slower_than_default]
extern "C" fn sinf(f: f32) -> f32 {
super::sin(f)
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn sin(f: f64) -> f64 {
super::sin(f)
}
#[slower_than_default]
extern "C" fn cosf(f: f32) -> f32 {
super::cos(f)
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn cos(f: f64) -> f64 {
super::cos(f)
}
#[slower_than_default]
extern "C" fn tanf(f: f32) -> f32 {
super::tan(f)
}
#[slower_than_default]
#[bootrom_v2]
extern "C" fn tan(f: f64) -> f64 {
super::tan(f)
}
// Questionable gain
#[bootrom_v2]
extern "C" fn atan2f(a: f32, b: f32) -> f32 {
super::atan2(a, b)
}
// Questionable gain
#[bootrom_v2]
extern "C" fn atan2(a: f64, b: f64) -> f64 {
super::atan2(a, b)
}
}
}

150
embassy-rp/src/float/mod.rs Normal file
View File

@@ -0,0 +1,150 @@
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/mod.rs
use core::ops;
// Borrowed and simplified from compiler-builtins so we can use bit ops
// on floating point without macro soup.
pub(crate) trait Int:
Copy
+ core::fmt::Debug
+ PartialEq
+ PartialOrd
+ ops::AddAssign
+ ops::SubAssign
+ ops::BitAndAssign
+ ops::BitOrAssign
+ ops::BitXorAssign
+ ops::ShlAssign<i32>
+ ops::ShrAssign<u32>
+ ops::Add<Output = Self>
+ ops::Sub<Output = Self>
+ ops::Div<Output = Self>
+ ops::Shl<u32, Output = Self>
+ ops::Shr<u32, Output = Self>
+ ops::BitOr<Output = Self>
+ ops::BitXor<Output = Self>
+ ops::BitAnd<Output = Self>
+ ops::Not<Output = Self>
{
const ZERO: Self;
}
macro_rules! int_impl {
($ty:ty) => {
impl Int for $ty {
const ZERO: Self = 0;
}
};
}
int_impl!(u32);
int_impl!(u64);
pub(crate) trait Float:
Copy
+ core::fmt::Debug
+ PartialEq
+ PartialOrd
+ ops::AddAssign
+ ops::MulAssign
+ ops::Add<Output = Self>
+ ops::Sub<Output = Self>
+ ops::Div<Output = Self>
+ ops::Rem<Output = Self>
{
/// A uint of the same with as the float
type Int: Int;
/// NaN representation for the float
const NAN: Self;
/// The bitwidth of the float type
const BITS: u32;
/// The bitwidth of the significand
const SIGNIFICAND_BITS: u32;
/// A mask for the sign bit
const SIGN_MASK: Self::Int;
/// A mask for the significand
const SIGNIFICAND_MASK: Self::Int;
/// A mask for the exponent
const EXPONENT_MASK: Self::Int;
/// Returns `self` transmuted to `Self::Int`
fn repr(self) -> Self::Int;
/// Returns a `Self::Int` transmuted back to `Self`
fn from_repr(a: Self::Int) -> Self;
/// Return a sign swapped `self`
fn negate(self) -> Self;
/// Returns true if `self` is either NaN or infinity
fn is_not_finite(self) -> bool {
(self.repr() & Self::EXPONENT_MASK) == Self::EXPONENT_MASK
}
/// Returns true if `self` is infinity
#[allow(unused)]
fn is_infinity(self) -> bool {
(self.repr() & (Self::EXPONENT_MASK | Self::SIGNIFICAND_MASK)) == Self::EXPONENT_MASK
}
/// Returns true if `self is NaN
fn is_nan(self) -> bool {
(self.repr() & (Self::EXPONENT_MASK | Self::SIGNIFICAND_MASK)) > Self::EXPONENT_MASK
}
/// Returns true if `self` is negative
fn is_sign_negative(self) -> bool {
(self.repr() & Self::SIGN_MASK) != Self::Int::ZERO
}
/// Returns true if `self` is zero (either sign)
fn is_zero(self) -> bool {
(self.repr() & (Self::SIGNIFICAND_MASK | Self::EXPONENT_MASK)) == Self::Int::ZERO
}
}
macro_rules! float_impl {
($ty:ident, $ity:ident, $bits:expr, $significand_bits:expr) => {
impl Float for $ty {
type Int = $ity;
const NAN: Self = <$ty>::NAN;
const BITS: u32 = $bits;
const SIGNIFICAND_BITS: u32 = $significand_bits;
const SIGN_MASK: Self::Int = 1 << (Self::BITS - 1);
const SIGNIFICAND_MASK: Self::Int = (1 << Self::SIGNIFICAND_BITS) - 1;
const EXPONENT_MASK: Self::Int = !(Self::SIGN_MASK | Self::SIGNIFICAND_MASK);
fn repr(self) -> Self::Int {
self.to_bits()
}
fn from_repr(a: Self::Int) -> Self {
Self::from_bits(a)
}
fn negate(self) -> Self {
-self
}
}
};
}
float_impl!(f32, u32, 32, 23);
float_impl!(f64, u64, 64, 52);
mod add_sub;
mod cmp;
mod conv;
mod div;
mod functions;
mod mul;

View File

@@ -0,0 +1,70 @@
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/mul.rs
use super::Float;
use crate::rom_data;
trait ROMMul {
fn rom_mul(self, b: Self) -> Self;
}
impl ROMMul for f32 {
fn rom_mul(self, b: Self) -> Self {
rom_data::float_funcs::fmul(self, b)
}
}
impl ROMMul for f64 {
fn rom_mul(self, b: Self) -> Self {
rom_data::double_funcs::dmul(self, b)
}
}
fn mul<F: Float + ROMMul>(a: F, b: F) -> F {
if a.is_not_finite() {
if b.is_zero() {
// [-]inf/NaN * 0 = NaN
return F::NAN;
}
return if b.is_sign_negative() {
// [+/-]inf/NaN * (-X) = [-/+]inf/NaN
a.negate()
} else {
// [-]inf/NaN * X = [-]inf/NaN
a
};
}
if b.is_not_finite() {
if a.is_zero() {
// 0 * [-]inf/NaN = NaN
return F::NAN;
}
return if b.is_sign_negative() {
// (-X) * [+/-]inf/NaN = [-/+]inf/NaN
b.negate()
} else {
// X * [-]inf/NaN = [-]inf/NaN
b
};
}
a.rom_mul(b)
}
intrinsics! {
#[alias = __mulsf3vfp]
#[aeabi = __aeabi_fmul]
extern "C" fn __mulsf3(a: f32, b: f32) -> f32 {
mul(a, b)
}
#[bootrom_v2]
#[alias = __muldf3vfp]
#[aeabi = __aeabi_dmul]
extern "C" fn __muldf3(a: f64, b: f64) -> f64 {
mul(a, b)
}
}

270
embassy-rp/src/fmt.rs Normal file
View File

@@ -0,0 +1,270 @@
#![macro_use]
#![allow(unused)]
use core::fmt::{Debug, Display, LowerHex};
#[cfg(all(feature = "defmt", feature = "log"))]
compile_error!("You may not enable both `defmt` and `log` features.");
#[collapse_debuginfo(yes)]
macro_rules! assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_eq!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_ne!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug_assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug_assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_eq!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug_assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_ne!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! todo {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::todo!($($x)*);
#[cfg(feature = "defmt")]
::defmt::todo!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! panic {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::panic!($($x)*);
#[cfg(feature = "defmt")]
::defmt::panic!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! trace {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::trace!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::trace!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::debug!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::debug!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! info {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::info!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::info!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! warn {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::warn!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::warn!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! error {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::error!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::error!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[cfg(feature = "defmt")]
#[collapse_debuginfo(yes)]
macro_rules! unwrap {
($($x:tt)*) => {
::defmt::unwrap!($($x)*)
};
}
#[cfg(not(feature = "defmt"))]
#[collapse_debuginfo(yes)]
macro_rules! unwrap {
($arg:expr) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
}
}
};
($arg:expr, $($msg:expr),+ $(,)? ) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
}
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct NoneError;
pub trait Try {
type Ok;
type Error;
fn into_result(self) -> Result<Self::Ok, Self::Error>;
}
impl<T> Try for Option<T> {
type Ok = T;
type Error = NoneError;
#[inline]
fn into_result(self) -> Result<T, NoneError> {
self.ok_or(NoneError)
}
}
impl<T, E> Try for Result<T, E> {
type Ok = T;
type Error = E;
#[inline]
fn into_result(self) -> Self {
self
}
}
pub(crate) struct Bytes<'a>(pub &'a [u8]);
impl<'a> Debug for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#02x?}", self.0)
}
}
impl<'a> Display for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#02x?}", self.0)
}
}
impl<'a> LowerHex for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#02x?}", self.0)
}
}
#[cfg(feature = "defmt")]
impl<'a> defmt::Format for Bytes<'a> {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(fmt, "{:02x}", self.0)
}
}

1389
embassy-rp/src/gpio.rs Normal file

File diff suppressed because it is too large Load Diff

919
embassy-rp/src/i2c.rs Normal file
View File

@@ -0,0 +1,919 @@
#![cfg_attr(feature = "defmt", allow(deprecated))] // Suppress warnings for defmt::Format using Error::AddressReserved
//! I2C driver.
use core::future;
use core::marker::PhantomData;
use core::task::Poll;
use embassy_hal_internal::{into_ref, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
use pac::i2c;
use crate::gpio::AnyPin;
use crate::interrupt::typelevel::{Binding, Interrupt};
use crate::{interrupt, pac, peripherals, Peripheral};
/// I2C error abort reason
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AbortReason {
/// A bus operation was not acknowledged, e.g. due to the addressed device
/// not being available on the bus or the device not being ready to process
/// requests at the moment
NoAcknowledge,
/// The arbitration was lost, e.g. electrical problems with the clock signal
ArbitrationLoss,
/// Transmit ended with data still in fifo
TxNotEmpty(u16),
/// Other reason.
Other(u32),
}
/// I2C error
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
/// I2C abort with error
Abort(AbortReason),
/// User passed in a read buffer that was 0 length
InvalidReadBufferLength,
/// User passed in a write buffer that was 0 length
InvalidWriteBufferLength,
/// Target i2c address is out of range
AddressOutOfRange(u16),
/// Target i2c address is reserved
#[deprecated = "embassy_rp no longer prevents accesses to reserved addresses."]
AddressReserved(u16),
}
/// I2C Config error
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ConfigError {
/// Max i2c speed is 1MHz
FrequencyTooHigh,
/// The sys clock is too slow to support given frequency
ClockTooSlow,
/// The sys clock is too fast to support given frequency
ClockTooFast,
}
/// I2C config.
#[non_exhaustive]
#[derive(Copy, Clone)]
pub struct Config {
/// Frequency.
pub frequency: u32,
}
impl Default for Config {
fn default() -> Self {
Self { frequency: 100_000 }
}
}
/// Size of I2C FIFO.
pub const FIFO_SIZE: u8 = 16;
/// I2C driver.
pub struct I2c<'d, T: Instance, M: Mode> {
phantom: PhantomData<(&'d mut T, M)>,
}
impl<'d, T: Instance> I2c<'d, T, Blocking> {
/// Create a new driver instance in blocking mode.
pub fn new_blocking(
peri: impl Peripheral<P = T> + 'd,
scl: impl Peripheral<P = impl SclPin<T>> + 'd,
sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
config: Config,
) -> Self {
into_ref!(scl, sda);
Self::new_inner(peri, scl.map_into(), sda.map_into(), config)
}
}
impl<'d, T: Instance> I2c<'d, T, Async> {
/// Create a new driver instance in async mode.
pub fn new_async(
peri: impl Peripheral<P = T> + 'd,
scl: impl Peripheral<P = impl SclPin<T>> + 'd,
sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
_irq: impl Binding<T::Interrupt, InterruptHandler<T>>,
config: Config,
) -> Self {
into_ref!(scl, sda);
let i2c = Self::new_inner(peri, scl.map_into(), sda.map_into(), config);
let r = T::regs();
// mask everything initially
r.ic_intr_mask().write_value(i2c::regs::IcIntrMask(0));
T::Interrupt::unpend();
unsafe { T::Interrupt::enable() };
i2c
}
/// Calls `f` to check if we are ready or not.
/// If not, `g` is called once the waker is set (to eg enable the required interrupts).
async fn wait_on<F, U, G>(&mut self, mut f: F, mut g: G) -> U
where
F: FnMut(&mut Self) -> Poll<U>,
G: FnMut(&mut Self),
{
future::poll_fn(|cx| {
let r = f(self);
if r.is_pending() {
T::waker().register(cx.waker());
g(self);
}
r
})
.await
}
async fn read_async_internal(&mut self, buffer: &mut [u8], restart: bool, send_stop: bool) -> Result<(), Error> {
if buffer.is_empty() {
return Err(Error::InvalidReadBufferLength);
}
let p = T::regs();
let mut remaining = buffer.len();
let mut remaining_queue = buffer.len();
let mut abort_reason = Ok(());
while remaining > 0 {
// Waggle SCK - basically the same as write
let tx_fifo_space = Self::tx_fifo_capacity();
let mut batch = 0;
debug_assert!(remaining_queue > 0);
for _ in 0..remaining_queue.min(tx_fifo_space as usize) {
remaining_queue -= 1;
let last = remaining_queue == 0;
batch += 1;
p.ic_data_cmd().write(|w| {
w.set_restart(restart && remaining_queue == buffer.len() - 1);
w.set_stop(last && send_stop);
w.set_cmd(true);
});
}
// We've either run out of txfifo or just plain finished setting up
// the clocks for the message - either way we need to wait for rx
// data.
debug_assert!(batch > 0);
let res = self
.wait_on(
|me| {
let rxfifo = Self::rx_fifo_len();
if let Err(abort_reason) = me.read_and_clear_abort_reason() {
Poll::Ready(Err(abort_reason))
} else if rxfifo >= batch {
Poll::Ready(Ok(rxfifo))
} else {
Poll::Pending
}
},
|_me| {
// Set the read threshold to the number of bytes we're
// expecting so we don't get spurious interrupts.
p.ic_rx_tl().write(|w| w.set_rx_tl(batch - 1));
p.ic_intr_mask().modify(|w| {
w.set_m_rx_full(true);
w.set_m_tx_abrt(true);
});
},
)
.await;
match res {
Err(reason) => {
abort_reason = Err(reason);
break;
}
Ok(rxfifo) => {
// Fetch things from rx fifo. We're assuming we're the only
// rxfifo reader, so nothing else can take things from it.
let rxbytes = (rxfifo as usize).min(remaining);
let received = buffer.len() - remaining;
for b in &mut buffer[received..received + rxbytes] {
*b = p.ic_data_cmd().read().dat();
}
remaining -= rxbytes;
}
};
}
self.wait_stop_det(abort_reason, send_stop).await
}
async fn write_async_internal(
&mut self,
bytes: impl IntoIterator<Item = u8>,
send_stop: bool,
) -> Result<(), Error> {
let p = T::regs();
let mut bytes = bytes.into_iter().peekable();
let res = 'xmit: loop {
let tx_fifo_space = Self::tx_fifo_capacity();
for _ in 0..tx_fifo_space {
if let Some(byte) = bytes.next() {
let last = bytes.peek().is_none();
p.ic_data_cmd().write(|w| {
w.set_stop(last && send_stop);
w.set_cmd(false);
w.set_dat(byte);
});
} else {
break 'xmit Ok(());
}
}
let res = self
.wait_on(
|me| {
if let abort_reason @ Err(_) = me.read_and_clear_abort_reason() {
Poll::Ready(abort_reason)
} else if !Self::tx_fifo_full() {
// resume if there's any space free in the tx fifo
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
},
|_me| {
// Set tx "free" threshold a little high so that we get
// woken before the fifo completely drains to minimize
// transfer stalls.
p.ic_tx_tl().write(|w| w.set_tx_tl(1));
p.ic_intr_mask().modify(|w| {
w.set_m_tx_empty(true);
w.set_m_tx_abrt(true);
})
},
)
.await;
if res.is_err() {
break res;
}
};
self.wait_stop_det(res, send_stop).await
}
/// Helper to wait for a stop bit, for both tx and rx. If we had an abort,
/// then we'll get a hardware-generated stop, otherwise wait for a stop if
/// we're expecting it.
///
/// Also handles an abort which arises while processing the tx fifo.
async fn wait_stop_det(&mut self, had_abort: Result<(), Error>, do_stop: bool) -> Result<(), Error> {
if had_abort.is_err() || do_stop {
let p = T::regs();
let had_abort2 = self
.wait_on(
|me| {
// We could see an abort while processing fifo backlog,
// so handle it here.
let abort = me.read_and_clear_abort_reason();
if had_abort.is_ok() && abort.is_err() {
Poll::Ready(abort)
} else if p.ic_raw_intr_stat().read().stop_det() {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
},
|_me| {
p.ic_intr_mask().modify(|w| {
w.set_m_stop_det(true);
w.set_m_tx_abrt(true);
});
},
)
.await;
p.ic_clr_stop_det().read();
had_abort.and(had_abort2)
} else {
had_abort
}
}
/// Read from address into buffer asynchronously.
pub async fn read_async(&mut self, addr: impl Into<u16>, buffer: &mut [u8]) -> Result<(), Error> {
Self::setup(addr.into())?;
self.read_async_internal(buffer, true, true).await
}
/// Write to address from buffer asynchronously.
pub async fn write_async(
&mut self,
addr: impl Into<u16>,
bytes: impl IntoIterator<Item = u8>,
) -> Result<(), Error> {
Self::setup(addr.into())?;
self.write_async_internal(bytes, true).await
}
/// Write to address from bytes and read from address into buffer asynchronously.
pub async fn write_read_async(
&mut self,
addr: impl Into<u16>,
bytes: impl IntoIterator<Item = u8>,
buffer: &mut [u8],
) -> Result<(), Error> {
Self::setup(addr.into())?;
self.write_async_internal(bytes, false).await?;
self.read_async_internal(buffer, true, true).await
}
}
/// Interrupt handler.
pub struct InterruptHandler<T: Instance> {
_uart: PhantomData<T>,
}
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
// Mask interrupts and wake any task waiting for this interrupt
unsafe fn on_interrupt() {
let i2c = T::regs();
i2c.ic_intr_mask().write_value(pac::i2c::regs::IcIntrMask::default());
T::waker().wake();
}
}
pub(crate) fn set_up_i2c_pin<P, T>(pin: &P)
where
P: core::ops::Deref<Target = T>,
T: crate::gpio::Pin,
{
pin.gpio().ctrl().write(|w| w.set_funcsel(3));
pin.pad_ctrl().write(|w| {
#[cfg(feature = "_rp235x")]
w.set_iso(false);
w.set_schmitt(true);
w.set_slewfast(false);
w.set_ie(true);
w.set_od(false);
w.set_pue(true);
w.set_pde(false);
});
}
impl<'d, T: Instance + 'd, M: Mode> I2c<'d, T, M> {
fn new_inner(
_peri: impl Peripheral<P = T> + 'd,
scl: PeripheralRef<'d, AnyPin>,
sda: PeripheralRef<'d, AnyPin>,
config: Config,
) -> Self {
into_ref!(_peri);
let reset = T::reset();
crate::reset::reset(reset);
crate::reset::unreset_wait(reset);
// Configure SCL & SDA pins
set_up_i2c_pin(&scl);
set_up_i2c_pin(&sda);
let mut me = Self { phantom: PhantomData };
if let Err(e) = me.set_config_inner(&config) {
panic!("Error configuring i2c: {:?}", e);
}
me
}
fn set_config_inner(&mut self, config: &Config) -> Result<(), ConfigError> {
if config.frequency > 1_000_000 {
return Err(ConfigError::FrequencyTooHigh);
}
let p = T::regs();
p.ic_enable().write(|w| w.set_enable(false));
// Configure baudrate
// There are some subtleties to I2C timing which we are completely
// ignoring here See:
// https://github.com/raspberrypi/pico-sdk/blob/bfcbefafc5d2a210551a4d9d80b4303d4ae0adf7/src/rp2_common/hardware_i2c/i2c.c#L69
let clk_base = crate::clocks::clk_peri_freq();
let period = (clk_base + config.frequency / 2) / config.frequency;
let lcnt = period * 3 / 5; // spend 3/5 (60%) of the period low
let hcnt = period - lcnt; // and 2/5 (40%) of the period high
// Check for out-of-range divisors:
if hcnt > 0xffff || lcnt > 0xffff {
return Err(ConfigError::ClockTooFast);
}
if hcnt < 8 || lcnt < 8 {
return Err(ConfigError::ClockTooSlow);
}
// Per I2C-bus specification a device in standard or fast mode must
// internally provide a hold time of at least 300ns for the SDA
// signal to bridge the undefined region of the falling edge of SCL.
// A smaller hold time of 120ns is used for fast mode plus.
let sda_tx_hold_count = if config.frequency < 1_000_000 {
// sda_tx_hold_count = clk_base [cycles/s] * 300ns * (1s /
// 1e9ns) Reduce 300/1e9 to 3/1e7 to avoid numbers that don't
// fit in uint. Add 1 to avoid division truncation.
((clk_base * 3) / 10_000_000) + 1
} else {
// fast mode plus requires a clk_base > 32MHz
if clk_base <= 32_000_000 {
return Err(ConfigError::ClockTooSlow);
}
// sda_tx_hold_count = clk_base [cycles/s] * 120ns * (1s /
// 1e9ns) Reduce 120/1e9 to 3/25e6 to avoid numbers that don't
// fit in uint. Add 1 to avoid division truncation.
((clk_base * 3) / 25_000_000) + 1
};
if sda_tx_hold_count > lcnt - 2 {
return Err(ConfigError::ClockTooSlow);
}
p.ic_fs_scl_hcnt().write(|w| w.set_ic_fs_scl_hcnt(hcnt as u16));
p.ic_fs_scl_lcnt().write(|w| w.set_ic_fs_scl_lcnt(lcnt as u16));
p.ic_fs_spklen()
.write(|w| w.set_ic_fs_spklen(if lcnt < 16 { 1 } else { (lcnt / 16) as u8 }));
p.ic_sda_hold()
.modify(|w| w.set_ic_sda_tx_hold(sda_tx_hold_count as u16));
p.ic_enable().write(|w| w.set_enable(true));
Ok(())
}
fn setup(addr: u16) -> Result<(), Error> {
if addr >= 0x80 {
return Err(Error::AddressOutOfRange(addr));
}
let p = T::regs();
p.ic_enable().write(|w| w.set_enable(false));
p.ic_tar().write(|w| w.set_ic_tar(addr));
p.ic_enable().write(|w| w.set_enable(true));
Ok(())
}
#[inline]
fn tx_fifo_full() -> bool {
Self::tx_fifo_capacity() == 0
}
#[inline]
fn tx_fifo_capacity() -> u8 {
let p = T::regs();
FIFO_SIZE - p.ic_txflr().read().txflr()
}
#[inline]
fn rx_fifo_len() -> u8 {
let p = T::regs();
p.ic_rxflr().read().rxflr()
}
fn read_and_clear_abort_reason(&mut self) -> Result<(), Error> {
let p = T::regs();
let abort_reason = p.ic_tx_abrt_source().read();
if abort_reason.0 != 0 {
// Note clearing the abort flag also clears the reason, and this
// instance of flag is clear-on-read! Note also the
// IC_CLR_TX_ABRT register always reads as 0.
p.ic_clr_tx_abrt().read();
let reason = if abort_reason.abrt_7b_addr_noack()
| abort_reason.abrt_10addr1_noack()
| abort_reason.abrt_10addr2_noack()
{
AbortReason::NoAcknowledge
} else if abort_reason.arb_lost() {
AbortReason::ArbitrationLoss
} else {
AbortReason::Other(abort_reason.0)
};
Err(Error::Abort(reason))
} else {
Ok(())
}
}
fn read_blocking_internal(&mut self, read: &mut [u8], restart: bool, send_stop: bool) -> Result<(), Error> {
if read.is_empty() {
return Err(Error::InvalidReadBufferLength);
}
let p = T::regs();
let lastindex = read.len() - 1;
for (i, byte) in read.iter_mut().enumerate() {
let first = i == 0;
let last = i == lastindex;
// wait until there is space in the FIFO to write the next byte
while Self::tx_fifo_full() {}
p.ic_data_cmd().write(|w| {
w.set_restart(restart && first);
w.set_stop(send_stop && last);
w.set_cmd(true);
});
while Self::rx_fifo_len() == 0 {
self.read_and_clear_abort_reason()?;
}
*byte = p.ic_data_cmd().read().dat();
}
Ok(())
}
fn write_blocking_internal(&mut self, write: &[u8], send_stop: bool) -> Result<(), Error> {
if write.is_empty() {
return Err(Error::InvalidWriteBufferLength);
}
let p = T::regs();
for (i, byte) in write.iter().enumerate() {
let last = i == write.len() - 1;
p.ic_data_cmd().write(|w| {
w.set_stop(send_stop && last);
w.set_dat(*byte);
});
// Wait until the transmission of the address/data from the
// internal shift register has completed. For this to function
// correctly, the TX_EMPTY_CTRL flag in IC_CON must be set. The
// TX_EMPTY_CTRL flag was set in i2c_init.
while !p.ic_raw_intr_stat().read().tx_empty() {}
let abort_reason = self.read_and_clear_abort_reason();
if abort_reason.is_err() || (send_stop && last) {
// If the transaction was aborted or if it completed
// successfully wait until the STOP condition has occurred.
while !p.ic_raw_intr_stat().read().stop_det() {}
p.ic_clr_stop_det().read().clr_stop_det();
}
// Note the hardware issues a STOP automatically on an abort
// condition. Note also the hardware clears RX FIFO as well as
// TX on abort, ecause we set hwparam
// IC_AVOID_RX_FIFO_FLUSH_ON_TX_ABRT to 0.
abort_reason?;
}
Ok(())
}
// =========================
// Blocking public API
// =========================
/// Read from address into buffer blocking caller until done.
pub fn blocking_read(&mut self, address: impl Into<u16>, read: &mut [u8]) -> Result<(), Error> {
Self::setup(address.into())?;
self.read_blocking_internal(read, true, true)
// Automatic Stop
}
/// Write to address from buffer blocking caller until done.
pub fn blocking_write(&mut self, address: impl Into<u16>, write: &[u8]) -> Result<(), Error> {
Self::setup(address.into())?;
self.write_blocking_internal(write, true)
}
/// Write to address from bytes and read from address into buffer blocking caller until done.
pub fn blocking_write_read(&mut self, address: impl Into<u16>, write: &[u8], read: &mut [u8]) -> Result<(), Error> {
Self::setup(address.into())?;
self.write_blocking_internal(write, false)?;
self.read_blocking_internal(read, true, true)
// Automatic Stop
}
}
impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Read for I2c<'d, T, M> {
type Error = Error;
fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
self.blocking_read(address, buffer)
}
}
impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Write for I2c<'d, T, M> {
type Error = Error;
fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> {
self.blocking_write(address, bytes)
}
}
impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, T, M> {
type Error = Error;
fn write_read(&mut self, address: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Self::Error> {
self.blocking_write_read(address, bytes, buffer)
}
}
impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Transactional for I2c<'d, T, M> {
type Error = Error;
fn exec(
&mut self,
address: u8,
operations: &mut [embedded_hal_02::blocking::i2c::Operation<'_>],
) -> Result<(), Self::Error> {
Self::setup(address.into())?;
for i in 0..operations.len() {
let last = i == operations.len() - 1;
match &mut operations[i] {
embedded_hal_02::blocking::i2c::Operation::Read(buf) => {
self.read_blocking_internal(buf, false, last)?
}
embedded_hal_02::blocking::i2c::Operation::Write(buf) => self.write_blocking_internal(buf, last)?,
}
}
Ok(())
}
}
impl embedded_hal_1::i2c::Error for Error {
fn kind(&self) -> embedded_hal_1::i2c::ErrorKind {
match *self {
Self::Abort(AbortReason::ArbitrationLoss) => embedded_hal_1::i2c::ErrorKind::ArbitrationLoss,
Self::Abort(AbortReason::NoAcknowledge) => {
embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Address)
}
Self::Abort(AbortReason::TxNotEmpty(_)) => embedded_hal_1::i2c::ErrorKind::Other,
Self::Abort(AbortReason::Other(_)) => embedded_hal_1::i2c::ErrorKind::Other,
Self::InvalidReadBufferLength => embedded_hal_1::i2c::ErrorKind::Other,
Self::InvalidWriteBufferLength => embedded_hal_1::i2c::ErrorKind::Other,
Self::AddressOutOfRange(_) => embedded_hal_1::i2c::ErrorKind::Other,
#[allow(deprecated)]
Self::AddressReserved(_) => embedded_hal_1::i2c::ErrorKind::Other,
}
}
}
impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::ErrorType for I2c<'d, T, M> {
type Error = Error;
}
impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::I2c for I2c<'d, T, M> {
fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
self.blocking_read(address, read)
}
fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
self.blocking_write(address, write)
}
fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
self.blocking_write_read(address, write, read)
}
fn transaction(
&mut self,
address: u8,
operations: &mut [embedded_hal_1::i2c::Operation<'_>],
) -> Result<(), Self::Error> {
Self::setup(address.into())?;
for i in 0..operations.len() {
let last = i == operations.len() - 1;
match &mut operations[i] {
embedded_hal_1::i2c::Operation::Read(buf) => self.read_blocking_internal(buf, false, last)?,
embedded_hal_1::i2c::Operation::Write(buf) => self.write_blocking_internal(buf, last)?,
}
}
Ok(())
}
}
impl<'d, A, T> embedded_hal_async::i2c::I2c<A> for I2c<'d, T, Async>
where
A: embedded_hal_async::i2c::AddressMode + Into<u16> + 'static,
T: Instance + 'd,
{
async fn read(&mut self, address: A, read: &mut [u8]) -> Result<(), Self::Error> {
self.read_async(address, read).await
}
async fn write(&mut self, address: A, write: &[u8]) -> Result<(), Self::Error> {
self.write_async(address, write.iter().copied()).await
}
async fn write_read(&mut self, address: A, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
self.write_read_async(address, write.iter().copied(), read).await
}
async fn transaction(
&mut self,
address: A,
operations: &mut [embedded_hal_1::i2c::Operation<'_>],
) -> Result<(), Self::Error> {
use embedded_hal_1::i2c::Operation;
let addr: u16 = address.into();
if !operations.is_empty() {
Self::setup(addr)?;
}
let mut iterator = operations.iter_mut();
while let Some(op) = iterator.next() {
let last = iterator.len() == 0;
match op {
Operation::Read(buffer) => {
self.read_async_internal(buffer, false, last).await?;
}
Operation::Write(buffer) => {
self.write_async_internal(buffer.iter().cloned(), last).await?;
}
}
}
Ok(())
}
}
impl<'d, T: Instance, M: Mode> embassy_embedded_hal::SetConfig for I2c<'d, T, M> {
type Config = Config;
type ConfigError = ConfigError;
fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> {
self.set_config_inner(config)
}
}
pub(crate) trait SealedInstance {
fn regs() -> crate::pac::i2c::I2c;
fn reset() -> crate::pac::resets::regs::Peripherals;
fn waker() -> &'static AtomicWaker;
}
trait SealedMode {}
/// Driver mode.
#[allow(private_bounds)]
pub trait Mode: SealedMode {}
macro_rules! impl_mode {
($name:ident) => {
impl SealedMode for $name {}
impl Mode for $name {}
};
}
/// Blocking mode.
pub struct Blocking;
/// Async mode.
pub struct Async;
impl_mode!(Blocking);
impl_mode!(Async);
/// I2C instance.
#[allow(private_bounds)]
pub trait Instance: SealedInstance {
/// Interrupt for this peripheral.
type Interrupt: interrupt::typelevel::Interrupt;
}
macro_rules! impl_instance {
($type:ident, $irq:ident, $reset:ident) => {
impl SealedInstance for peripherals::$type {
#[inline]
fn regs() -> pac::i2c::I2c {
pac::$type
}
#[inline]
fn reset() -> pac::resets::regs::Peripherals {
let mut ret = pac::resets::regs::Peripherals::default();
ret.$reset(true);
ret
}
#[inline]
fn waker() -> &'static AtomicWaker {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
}
impl Instance for peripherals::$type {
type Interrupt = crate::interrupt::typelevel::$irq;
}
};
}
impl_instance!(I2C0, I2C0_IRQ, set_i2c0);
impl_instance!(I2C1, I2C1_IRQ, set_i2c1);
/// SDA pin.
pub trait SdaPin<T: Instance>: crate::gpio::Pin {}
/// SCL pin.
pub trait SclPin<T: Instance>: crate::gpio::Pin {}
macro_rules! impl_pin {
($pin:ident, $instance:ident, $function:ident) => {
impl $function<peripherals::$instance> for peripherals::$pin {}
};
}
impl_pin!(PIN_0, I2C0, SdaPin);
impl_pin!(PIN_1, I2C0, SclPin);
impl_pin!(PIN_2, I2C1, SdaPin);
impl_pin!(PIN_3, I2C1, SclPin);
impl_pin!(PIN_4, I2C0, SdaPin);
impl_pin!(PIN_5, I2C0, SclPin);
impl_pin!(PIN_6, I2C1, SdaPin);
impl_pin!(PIN_7, I2C1, SclPin);
impl_pin!(PIN_8, I2C0, SdaPin);
impl_pin!(PIN_9, I2C0, SclPin);
impl_pin!(PIN_10, I2C1, SdaPin);
impl_pin!(PIN_11, I2C1, SclPin);
impl_pin!(PIN_12, I2C0, SdaPin);
impl_pin!(PIN_13, I2C0, SclPin);
impl_pin!(PIN_14, I2C1, SdaPin);
impl_pin!(PIN_15, I2C1, SclPin);
impl_pin!(PIN_16, I2C0, SdaPin);
impl_pin!(PIN_17, I2C0, SclPin);
impl_pin!(PIN_18, I2C1, SdaPin);
impl_pin!(PIN_19, I2C1, SclPin);
impl_pin!(PIN_20, I2C0, SdaPin);
impl_pin!(PIN_21, I2C0, SclPin);
impl_pin!(PIN_22, I2C1, SdaPin);
impl_pin!(PIN_23, I2C1, SclPin);
impl_pin!(PIN_24, I2C0, SdaPin);
impl_pin!(PIN_25, I2C0, SclPin);
impl_pin!(PIN_26, I2C1, SdaPin);
impl_pin!(PIN_27, I2C1, SclPin);
impl_pin!(PIN_28, I2C0, SdaPin);
impl_pin!(PIN_29, I2C0, SclPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_30, I2C1, SdaPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_31, I2C1, SclPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_32, I2C0, SdaPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_33, I2C0, SclPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_34, I2C1, SdaPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_35, I2C1, SclPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_36, I2C0, SdaPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_37, I2C0, SclPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_38, I2C1, SdaPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_39, I2C1, SclPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_40, I2C0, SdaPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_41, I2C0, SclPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_42, I2C1, SdaPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_43, I2C1, SclPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_44, I2C0, SdaPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_45, I2C0, SclPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_46, I2C1, SdaPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_47, I2C1, SclPin);

395
embassy-rp/src/i2c_slave.rs Normal file
View File

@@ -0,0 +1,395 @@
//! I2C slave driver.
use core::future;
use core::marker::PhantomData;
use core::task::Poll;
use embassy_hal_internal::into_ref;
use pac::i2c;
use crate::i2c::{set_up_i2c_pin, AbortReason, Instance, InterruptHandler, SclPin, SdaPin, FIFO_SIZE};
use crate::interrupt::typelevel::{Binding, Interrupt};
use crate::{pac, Peripheral};
/// I2C error
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Error {
/// I2C abort with error
Abort(AbortReason),
/// User passed in a response buffer that was 0 length
InvalidResponseBufferLength,
/// The response buffer length was too short to contain the message
///
/// The length parameter will always be the length of the buffer, and is
/// provided as a convenience for matching alongside `Command::Write`.
PartialWrite(usize),
/// The response buffer length was too short to contain the message
///
/// The length parameter will always be the length of the buffer, and is
/// provided as a convenience for matching alongside `Command::GeneralCall`.
PartialGeneralCall(usize),
}
/// Received command
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Command {
/// General Call
GeneralCall(usize),
/// Read
Read,
/// Write+read
WriteRead(usize),
/// Write
Write(usize),
}
/// Possible responses to responding to a read
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ReadStatus {
/// Transaction Complete, controller naked our last byte
Done,
/// Transaction Incomplete, controller trying to read more bytes than were provided
NeedMoreBytes,
/// Transaction Complere, but controller stopped reading bytes before we ran out
LeftoverBytes(u16),
}
/// Slave Configuration
#[non_exhaustive]
#[derive(Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Config {
/// Target Address
pub addr: u16,
/// Control if the peripheral should ack to and report general calls.
pub general_call: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
addr: 0x55,
general_call: true,
}
}
}
/// I2CSlave driver.
pub struct I2cSlave<'d, T: Instance> {
phantom: PhantomData<&'d mut T>,
pending_byte: Option<u8>,
config: Config,
}
impl<'d, T: Instance> I2cSlave<'d, T> {
/// Create a new instance.
pub fn new(
_peri: impl Peripheral<P = T> + 'd,
scl: impl Peripheral<P = impl SclPin<T>> + 'd,
sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
_irq: impl Binding<T::Interrupt, InterruptHandler<T>>,
config: Config,
) -> Self {
into_ref!(_peri, scl, sda);
assert!(config.addr != 0);
// Configure SCL & SDA pins
set_up_i2c_pin(&scl);
set_up_i2c_pin(&sda);
let mut ret = Self {
phantom: PhantomData,
pending_byte: None,
config,
};
ret.reset();
ret
}
/// Reset the i2c peripheral. If you cancel a respond_to_read, you may stall the bus.
/// You can recover the bus by calling this function, but doing so will almost certainly cause
/// an i/o error in the master.
pub fn reset(&mut self) {
let p = T::regs();
let reset = T::reset();
crate::reset::reset(reset);
crate::reset::unreset_wait(reset);
p.ic_enable().write(|w| w.set_enable(false));
p.ic_sar().write(|w| w.set_ic_sar(self.config.addr));
p.ic_con().modify(|w| {
w.set_master_mode(false);
w.set_ic_slave_disable(false);
w.set_tx_empty_ctrl(true);
w.set_rx_fifo_full_hld_ctrl(true);
// This typically makes no sense for a slave, but it is used to
// tune spike suppression, according to the datasheet.
w.set_speed(pac::i2c::vals::Speed::FAST);
// Generate stop interrupts for general calls
// This also causes stop interrupts for other devices on the bus but those will not be
// propagated up to the application.
w.set_stop_det_ifaddressed(!self.config.general_call);
});
p.ic_ack_general_call()
.write(|w| w.set_ack_gen_call(self.config.general_call));
// Set FIFO watermarks to 1 to make things simpler. This is encoded
// by a register value of 0. Rx watermark should never change, but Tx watermark will be
// adjusted in operation.
p.ic_tx_tl().write(|w| w.set_tx_tl(0));
p.ic_rx_tl().write(|w| w.set_rx_tl(0));
// Clear interrupts
p.ic_clr_intr().read();
// Enable I2C block
p.ic_enable().write(|w| w.set_enable(true));
// mask everything initially
p.ic_intr_mask().write_value(i2c::regs::IcIntrMask(0));
T::Interrupt::unpend();
unsafe { T::Interrupt::enable() };
}
/// Calls `f` to check if we are ready or not.
/// If not, `g` is called once the waker is set (to eg enable the required interrupts).
#[inline(always)]
async fn wait_on<F, U, G>(&mut self, mut f: F, mut g: G) -> U
where
F: FnMut(&mut Self) -> Poll<U>,
G: FnMut(&mut Self),
{
future::poll_fn(|cx| {
let r = f(self);
if r.is_pending() {
T::waker().register(cx.waker());
g(self);
}
r
})
.await
}
#[inline(always)]
fn drain_fifo(&mut self, buffer: &mut [u8], offset: &mut usize) {
let p = T::regs();
if let Some(pending) = self.pending_byte.take() {
buffer[*offset] = pending;
*offset += 1;
}
for b in &mut buffer[*offset..] {
if !p.ic_status().read().rfne() {
break;
}
let dat = p.ic_data_cmd().read();
if *offset != 0 && dat.first_data_byte() {
// The RP2040 state machine will keep placing bytes into the
// FIFO, even if they are part of a subsequent write transaction.
//
// Unfortunately merely reading ic_data_cmd will consume that
// byte, the first byte of the next transaction, so we need
// to store it elsewhere
self.pending_byte = Some(dat.dat());
break;
}
*b = dat.dat();
*offset += 1;
}
}
/// Wait asynchronously for commands from an I2C master.
/// `buffer` is provided in case master does a 'write', 'write read', or 'general call' and is unused for 'read'.
pub async fn listen(&mut self, buffer: &mut [u8]) -> Result<Command, Error> {
let p = T::regs();
// set rx fifo watermark to 1 byte
p.ic_rx_tl().write(|w| w.set_rx_tl(0));
let mut len = 0;
self.wait_on(
|me| {
let stat = p.ic_raw_intr_stat().read();
trace!("ls:{:013b} len:{}", stat.0, len);
if p.ic_rxflr().read().rxflr() > 0 || me.pending_byte.is_some() {
me.drain_fifo(buffer, &mut len);
// we're recieving data, set rx fifo watermark to 12 bytes (3/4 full) to reduce interrupt noise
p.ic_rx_tl().write(|w| w.set_rx_tl(11));
}
if buffer.len() == len {
if stat.gen_call() {
return Poll::Ready(Err(Error::PartialGeneralCall(buffer.len())));
} else {
return Poll::Ready(Err(Error::PartialWrite(buffer.len())));
}
}
trace!("len:{}, pend:{:?}", len, me.pending_byte);
if me.pending_byte.is_some() {
warn!("pending")
}
if stat.restart_det() && stat.rd_req() {
p.ic_clr_restart_det().read();
Poll::Ready(Ok(Command::WriteRead(len)))
} else if stat.gen_call() && stat.stop_det() && len > 0 {
p.ic_clr_gen_call().read();
p.ic_clr_stop_det().read();
Poll::Ready(Ok(Command::GeneralCall(len)))
} else if stat.stop_det() && len > 0 {
p.ic_clr_stop_det().read();
Poll::Ready(Ok(Command::Write(len)))
} else if stat.rd_req() {
p.ic_clr_stop_det().read();
p.ic_clr_restart_det().read();
p.ic_clr_gen_call().read();
Poll::Ready(Ok(Command::Read))
} else if stat.stop_det() {
// clear stuck stop bit
// This can happen if the SDA/SCL pullups are enabled after calling this func
p.ic_clr_stop_det().read();
Poll::Pending
} else {
Poll::Pending
}
},
|_me| {
p.ic_intr_mask().write(|w| {
w.set_m_stop_det(true);
w.set_m_restart_det(true);
w.set_m_gen_call(true);
w.set_m_rd_req(true);
w.set_m_rx_full(true);
});
},
)
.await
}
/// Respond to an I2C master READ command, asynchronously.
pub async fn respond_to_read(&mut self, buffer: &[u8]) -> Result<ReadStatus, Error> {
let p = T::regs();
if buffer.is_empty() {
return Err(Error::InvalidResponseBufferLength);
}
let mut chunks = buffer.chunks(FIFO_SIZE as usize);
self.wait_on(
|me| {
let stat = p.ic_raw_intr_stat().read();
trace!("rs:{:013b}", stat.0);
if stat.tx_abrt() {
if let Err(abort_reason) = me.read_and_clear_abort_reason() {
if let Error::Abort(AbortReason::TxNotEmpty(bytes)) = abort_reason {
p.ic_clr_intr().read();
return Poll::Ready(Ok(ReadStatus::LeftoverBytes(bytes)));
} else {
return Poll::Ready(Err(abort_reason));
}
}
}
if let Some(chunk) = chunks.next() {
for byte in chunk {
p.ic_clr_rd_req().read();
p.ic_data_cmd().write(|w| w.set_dat(*byte));
}
Poll::Pending
} else if stat.rx_done() {
p.ic_clr_rx_done().read();
Poll::Ready(Ok(ReadStatus::Done))
} else if stat.rd_req() && stat.tx_empty() {
Poll::Ready(Ok(ReadStatus::NeedMoreBytes))
} else {
Poll::Pending
}
},
|_me| {
p.ic_intr_mask().write(|w| {
w.set_m_rx_done(true);
w.set_m_tx_empty(true);
w.set_m_tx_abrt(true);
})
},
)
.await
}
/// Respond to reads with the fill byte until the controller stops asking
pub async fn respond_till_stop(&mut self, fill: u8) -> Result<(), Error> {
// Send fill bytes a full fifo at a time, to reduce interrupt noise.
// This does mean we'll almost certainly abort the write, but since these are fill bytes,
// we don't care.
let buff = [fill; FIFO_SIZE as usize];
loop {
match self.respond_to_read(&buff).await {
Ok(ReadStatus::NeedMoreBytes) => (),
Ok(ReadStatus::LeftoverBytes(_)) => break Ok(()),
Ok(_) => break Ok(()),
Err(e) => break Err(e),
}
}
}
/// Respond to a master read, then fill any remaining read bytes with `fill`
pub async fn respond_and_fill(&mut self, buffer: &[u8], fill: u8) -> Result<ReadStatus, Error> {
let resp_stat = self.respond_to_read(buffer).await?;
if resp_stat == ReadStatus::NeedMoreBytes {
self.respond_till_stop(fill).await?;
Ok(ReadStatus::Done)
} else {
Ok(resp_stat)
}
}
#[inline(always)]
fn read_and_clear_abort_reason(&mut self) -> Result<(), Error> {
let p = T::regs();
let abort_reason = p.ic_tx_abrt_source().read();
if abort_reason.0 != 0 {
// Note clearing the abort flag also clears the reason, and this
// instance of flag is clear-on-read! Note also the
// IC_CLR_TX_ABRT register always reads as 0.
p.ic_clr_tx_abrt().read();
let reason = if abort_reason.abrt_7b_addr_noack()
| abort_reason.abrt_10addr1_noack()
| abort_reason.abrt_10addr2_noack()
{
AbortReason::NoAcknowledge
} else if abort_reason.arb_lost() {
AbortReason::ArbitrationLoss
} else if abort_reason.tx_flush_cnt() > 0 {
AbortReason::TxNotEmpty(abort_reason.tx_flush_cnt())
} else {
AbortReason::Other(abort_reason.0)
};
Err(Error::Abort(reason))
} else {
Ok(())
}
}
}

View File

@@ -0,0 +1,476 @@
#![macro_use]
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/intrinsics.rs
/// Generate a series of aliases for an intrinsic function.
macro_rules! intrinsics_aliases {
(
extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty,
) => {};
(
unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty,
) => {};
(
extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty,
$alias:ident
$($rest:ident)*
) => {
#[cfg(all(target_arch = "arm", feature = "intrinsics"))]
intrinsics! {
extern $abi fn $alias( $($argname: $ty),* ) -> $ret {
$name($($argname),*)
}
}
intrinsics_aliases! {
extern $abi fn $name( $($argname: $ty),* ) -> $ret,
$($rest)*
}
};
(
unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty,
$alias:ident
$($rest:ident)*
) => {
#[cfg(all(target_arch = "arm", feature = "intrinsics"))]
intrinsics! {
unsafe extern $abi fn $alias( $($argname: $ty),* ) -> $ret {
$name($($argname),*)
}
}
intrinsics_aliases! {
unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret,
$($rest)*
}
};
}
/// The macro used to define overridden intrinsics.
///
/// This is heavily inspired by the macro used by compiler-builtins. The idea
/// is to abstract anything special that needs to be done to override an
/// intrinsic function. Intrinsic generation is disabled for non-ARM targets
/// so things like CI and docs generation do not have problems. Additionally
/// they can be disabled by disabling the crate feature `intrinsics` for
/// testing or comparing performance.
///
/// Like the compiler-builtins macro, it accepts a series of functions that
/// looks like normal Rust code:
///
/// ```rust,ignore
/// intrinsics! {
/// extern "C" fn foo(a: i32) -> u32 {
/// // ...
/// }
/// #[nonstandard_attribute]
/// extern "C" fn bar(a: i32) -> u32 {
/// // ...
/// }
/// }
/// ```
///
/// Each function can also be decorated with nonstandard attributes to control
/// additional behaviour:
///
/// * `slower_than_default` - indicates that the override is slower than the
/// default implementation. Currently this just disables the override
/// entirely.
/// * `bootrom_v2` - indicates that the override is only available
/// on a V2 bootrom or higher. Only enabled when the feature
/// `rom-v2-intrinsics` is set.
/// * `alias` - accepts a list of names to alias the intrinsic to.
/// * `aeabi` - accepts a list of ARM EABI names to alias to.
///
macro_rules! intrinsics {
() => {};
(
#[slower_than_default]
$(#[$($attr:tt)*])*
extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty {
$($body:tt)*
}
$($rest:tt)*
) => {
// Not exported, but defined so the actual implementation is
// considered used
#[allow(dead_code)]
fn $name( $($argname: $ty),* ) -> $ret {
$($body)*
}
intrinsics!($($rest)*);
};
(
#[bootrom_v2]
$(#[$($attr:tt)*])*
extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty {
$($body:tt)*
}
$($rest:tt)*
) => {
// Not exported, but defined so the actual implementation is
// considered used
#[cfg(not(feature = "rom-v2-intrinsics"))]
#[allow(dead_code)]
fn $name( $($argname: $ty),* ) -> $ret {
$($body)*
}
#[cfg(feature = "rom-v2-intrinsics")]
intrinsics! {
$(#[$($attr)*])*
extern $abi fn $name( $($argname: $ty),* ) -> $ret {
$($body)*
}
}
intrinsics!($($rest)*);
};
(
#[alias = $($alias:ident),*]
$(#[$($attr:tt)*])*
extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty {
$($body:tt)*
}
$($rest:tt)*
) => {
intrinsics! {
$(#[$($attr)*])*
extern $abi fn $name( $($argname: $ty),* ) -> $ret {
$($body)*
}
}
intrinsics_aliases! {
extern $abi fn $name( $($argname: $ty),* ) -> $ret,
$($alias) *
}
intrinsics!($($rest)*);
};
(
#[alias = $($alias:ident),*]
$(#[$($attr:tt)*])*
unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty {
$($body:tt)*
}
$($rest:tt)*
) => {
intrinsics! {
$(#[$($attr)*])*
unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret {
$($body)*
}
}
intrinsics_aliases! {
unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret,
$($alias) *
}
intrinsics!($($rest)*);
};
(
#[aeabi = $($alias:ident),*]
$(#[$($attr:tt)*])*
extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty {
$($body:tt)*
}
$($rest:tt)*
) => {
intrinsics! {
$(#[$($attr)*])*
extern $abi fn $name( $($argname: $ty),* ) -> $ret {
$($body)*
}
}
intrinsics_aliases! {
extern "aapcs" fn $name( $($argname: $ty),* ) -> $ret,
$($alias) *
}
intrinsics!($($rest)*);
};
(
$(#[$($attr:tt)*])*
extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty {
$($body:tt)*
}
$($rest:tt)*
) => {
#[cfg(all(target_arch = "arm", feature = "intrinsics"))]
$(#[$($attr)*])*
extern $abi fn $name( $($argname: $ty),* ) -> $ret {
$($body)*
}
#[cfg(all(target_arch = "arm", feature = "intrinsics"))]
mod $name {
#[no_mangle]
$(#[$($attr)*])*
pub extern $abi fn $name( $($argname: $ty),* ) -> $ret {
super::$name($($argname),*)
}
}
// Not exported, but defined so the actual implementation is
// considered used
#[cfg(not(all(target_arch = "arm", feature = "intrinsics")))]
#[allow(dead_code)]
fn $name( $($argname: $ty),* ) -> $ret {
$($body)*
}
intrinsics!($($rest)*);
};
(
$(#[$($attr:tt)*])*
unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty {
$($body:tt)*
}
$($rest:tt)*
) => {
#[cfg(all(target_arch = "arm", feature = "intrinsics"))]
$(#[$($attr)*])*
unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret {
$($body)*
}
#[cfg(all(target_arch = "arm", feature = "intrinsics"))]
mod $name {
#[no_mangle]
$(#[$($attr)*])*
pub unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret {
super::$name($($argname),*)
}
}
// Not exported, but defined so the actual implementation is
// considered used
#[cfg(not(all(target_arch = "arm", feature = "intrinsics")))]
#[allow(dead_code)]
unsafe fn $name( $($argname: $ty),* ) -> $ret {
$($body)*
}
intrinsics!($($rest)*);
};
}
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/sio.rs
// This takes advantage of how AAPCS defines a 64-bit return on 32-bit registers
// by packing it into r0[0:31] and r1[32:63]. So all we need to do is put
// the remainder in the high order 32 bits of a 64 bit result. We can also
// alias the division operators to these for a similar reason r0 is the
// result either way and r1 a scratch register, so the caller can't assume it
// retains the argument value.
#[cfg(target_arch = "arm")]
core::arch::global_asm!(
".macro hwdivider_head",
"ldr r2, =(0xd0000000)", // SIO_BASE
// Check the DIRTY state of the divider by shifting it into the C
// status bit.
"ldr r3, [r2, #0x078]", // DIV_CSR
"lsrs r3, #2", // DIRTY = 1, so shift 2 down
// We only need to save the state when DIRTY, otherwise we can just do the
// division directly.
"bcs 2f",
"1:",
// Do the actual division now, we're either not DIRTY, or we've saved the
// state and branched back here so it's safe now.
".endm",
".macro hwdivider_tail",
// 8 cycle delay to wait for the result. Each branch takes two cycles
// and fits into a 2-byte Thumb instruction, so this is smaller than
// 8 NOPs.
"b 3f",
"3: b 3f",
"3: b 3f",
"3: b 3f",
"3:",
// Read the quotient last, since that's what clears the dirty flag.
"ldr r1, [r2, #0x074]", // DIV_REMAINDER
"ldr r0, [r2, #0x070]", // DIV_QUOTIENT
// Either return to the caller or back to the state restore.
"bx lr",
"2:",
// Since we can't save the signed-ness of the calculation, we have to make
// sure that there's at least an 8 cycle delay before we read the result.
// The push takes 5 cycles, and we've already spent at least 7 checking
// the DIRTY state to get here.
"push {{r4-r6, lr}}",
// Read the quotient last, since that's what clears the dirty flag.
"ldr r3, [r2, #0x060]", // DIV_UDIVIDEND
"ldr r4, [r2, #0x064]", // DIV_UDIVISOR
"ldr r5, [r2, #0x074]", // DIV_REMAINDER
"ldr r6, [r2, #0x070]", // DIV_QUOTIENT
// If we get interrupted here (before a write sets the DIRTY flag) it's
// fine, since we have the full state, so the interruptor doesn't have to
// restore it. Once the write happens and the DIRTY flag is set, the
// interruptor becomes responsible for restoring our state.
"bl 1b",
// If we are interrupted here, then the interruptor will start an incorrect
// calculation using a wrong divisor, but we'll restore the divisor and
// result ourselves correctly. This sets DIRTY, so any interruptor will
// save the state.
"str r3, [r2, #0x060]", // DIV_UDIVIDEND
// If we are interrupted here, the interruptor may start the calculation
// using incorrectly signed inputs, but we'll restore the result ourselves.
// This sets DIRTY, so any interruptor will save the state.
"str r4, [r2, #0x064]", // DIV_UDIVISOR
// If we are interrupted here, the interruptor will have restored
// everything but the quotient may be wrongly signed. If the calculation
// started by the above writes is still ongoing it is stopped, so it won't
// replace the result we're restoring. DIRTY and READY set, but only
// DIRTY matters to make the interruptor save the state.
"str r5, [r2, #0x074]", // DIV_REMAINDER
// State fully restored after the quotient write. This sets both DIRTY
// and READY, so whatever we may have interrupted can read the result.
"str r6, [r2, #0x070]", // DIV_QUOTIENT
"pop {{r4-r6, pc}}",
".endm",
);
macro_rules! division_function {
(
$name:ident $($intrinsic:ident)* ( $argty:ty ) {
$($begin:literal),+
}
) => {
#[cfg(all(target_arch = "arm", feature = "intrinsics"))]
core::arch::global_asm!(
// Mangle the name slightly, since this is a global symbol.
concat!(".section .text._erphal_", stringify!($name)),
concat!(".global _erphal_", stringify!($name)),
concat!(".type _erphal_", stringify!($name), ", %function"),
".align 2",
concat!("_erphal_", stringify!($name), ":"),
$(
concat!(".global ", stringify!($intrinsic)),
concat!(".type ", stringify!($intrinsic), ", %function"),
concat!(stringify!($intrinsic), ":"),
)*
"hwdivider_head",
$($begin),+ ,
"hwdivider_tail",
);
#[cfg(all(target_arch = "arm", not(feature = "intrinsics")))]
core::arch::global_asm!(
// Mangle the name slightly, since this is a global symbol.
concat!(".section .text._erphal_", stringify!($name)),
concat!(".global _erphal_", stringify!($name)),
concat!(".type _erphal_", stringify!($name), ", %function"),
".align 2",
concat!("_erphal_", stringify!($name), ":"),
"hwdivider_head",
$($begin),+ ,
"hwdivider_tail",
);
#[cfg(target_arch = "arm")]
extern "aapcs" {
// Connect a local name to global symbol above through FFI.
#[link_name = concat!("_erphal_", stringify!($name)) ]
fn $name(n: $argty, d: $argty) -> u64;
}
#[cfg(not(target_arch = "arm"))]
#[allow(unused_variables)]
unsafe fn $name(n: $argty, d: $argty) -> u64 { 0 }
};
}
division_function! {
unsigned_divmod __aeabi_uidivmod __aeabi_uidiv ( u32 ) {
"str r0, [r2, #0x060]", // DIV_UDIVIDEND
"str r1, [r2, #0x064]" // DIV_UDIVISOR
}
}
division_function! {
signed_divmod __aeabi_idivmod __aeabi_idiv ( i32 ) {
"str r0, [r2, #0x068]", // DIV_SDIVIDEND
"str r1, [r2, #0x06c]" // DIV_SDIVISOR
}
}
fn divider_unsigned(n: u32, d: u32) -> DivResult<u32> {
let packed = unsafe { unsigned_divmod(n, d) };
DivResult {
quotient: packed as u32,
remainder: (packed >> 32) as u32,
}
}
fn divider_signed(n: i32, d: i32) -> DivResult<i32> {
let packed = unsafe { signed_divmod(n, d) };
// Double casts to avoid sign extension
DivResult {
quotient: packed as u32 as i32,
remainder: (packed >> 32) as u32 as i32,
}
}
/// Result of divide/modulo operation
struct DivResult<T> {
/// The quotient of divide/modulo operation
pub quotient: T,
/// The remainder of divide/modulo operation
pub remainder: T,
}
intrinsics! {
extern "C" fn __udivsi3(n: u32, d: u32) -> u32 {
divider_unsigned(n, d).quotient
}
extern "C" fn __umodsi3(n: u32, d: u32) -> u32 {
divider_unsigned(n, d).remainder
}
extern "C" fn __udivmodsi4(n: u32, d: u32, rem: Option<&mut u32>) -> u32 {
let quo_rem = divider_unsigned(n, d);
if let Some(rem) = rem {
*rem = quo_rem.remainder;
}
quo_rem.quotient
}
extern "C" fn __divsi3(n: i32, d: i32) -> i32 {
divider_signed(n, d).quotient
}
extern "C" fn __modsi3(n: i32, d: i32) -> i32 {
divider_signed(n, d).remainder
}
extern "C" fn __divmodsi4(n: i32, d: i32, rem: &mut i32) -> i32 {
let quo_rem = divider_signed(n, d);
*rem = quo_rem.remainder;
quo_rem.quotient
}
}

759
embassy-rp/src/lib.rs Normal file
View File

@@ -0,0 +1,759 @@
#![no_std]
#![allow(async_fn_in_trait)]
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
//! ## Feature flags
#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
// This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt;
#[cfg(feature = "binary-info")]
pub use rp_binary_info as binary_info;
#[cfg(feature = "critical-section-impl")]
mod critical_section_impl;
#[cfg(feature = "rp2040")]
mod intrinsics;
pub mod adc;
#[cfg(feature = "_rp235x")]
pub mod block;
#[cfg(feature = "rp2040")]
pub mod bootsel;
pub mod clocks;
pub mod dma;
pub mod flash;
#[cfg(feature = "rp2040")]
mod float;
pub mod gpio;
pub mod i2c;
pub mod i2c_slave;
pub mod multicore;
#[cfg(feature = "_rp235x")]
pub mod otp;
pub mod pio_programs;
pub mod pwm;
mod reset;
pub mod rom_data;
#[cfg(feature = "rp2040")]
pub mod rtc;
pub mod spi;
#[cfg(feature = "time-driver")]
pub mod time_driver;
#[cfg(feature = "_rp235x")]
pub mod trng;
pub mod uart;
pub mod usb;
pub mod watchdog;
// PIO
pub mod pio;
pub(crate) mod relocate;
// Reexports
pub use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
#[cfg(feature = "unstable-pac")]
pub use rp_pac as pac;
#[cfg(not(feature = "unstable-pac"))]
pub(crate) use rp_pac as pac;
#[cfg(feature = "rt")]
pub use crate::pac::NVIC_PRIO_BITS;
#[cfg(feature = "rp2040")]
embassy_hal_internal::interrupt_mod!(
TIMER_IRQ_0,
TIMER_IRQ_1,
TIMER_IRQ_2,
TIMER_IRQ_3,
PWM_IRQ_WRAP,
USBCTRL_IRQ,
XIP_IRQ,
PIO0_IRQ_0,
PIO0_IRQ_1,
PIO1_IRQ_0,
PIO1_IRQ_1,
DMA_IRQ_0,
DMA_IRQ_1,
IO_IRQ_BANK0,
IO_IRQ_QSPI,
SIO_IRQ_PROC0,
SIO_IRQ_PROC1,
CLOCKS_IRQ,
SPI0_IRQ,
SPI1_IRQ,
UART0_IRQ,
UART1_IRQ,
ADC_IRQ_FIFO,
I2C0_IRQ,
I2C1_IRQ,
RTC_IRQ,
SWI_IRQ_0,
SWI_IRQ_1,
SWI_IRQ_2,
SWI_IRQ_3,
SWI_IRQ_4,
SWI_IRQ_5,
);
#[cfg(feature = "_rp235x")]
embassy_hal_internal::interrupt_mod!(
TIMER0_IRQ_0,
TIMER0_IRQ_1,
TIMER0_IRQ_2,
TIMER0_IRQ_3,
TIMER1_IRQ_0,
TIMER1_IRQ_1,
TIMER1_IRQ_2,
TIMER1_IRQ_3,
PWM_IRQ_WRAP_0,
PWM_IRQ_WRAP_1,
DMA_IRQ_0,
DMA_IRQ_1,
USBCTRL_IRQ,
PIO0_IRQ_0,
PIO0_IRQ_1,
PIO1_IRQ_0,
PIO1_IRQ_1,
PIO2_IRQ_0,
PIO2_IRQ_1,
IO_IRQ_BANK0,
IO_IRQ_BANK0_NS,
IO_IRQ_QSPI,
IO_IRQ_QSPI_NS,
SIO_IRQ_FIFO,
SIO_IRQ_BELL,
SIO_IRQ_FIFO_NS,
SIO_IRQ_BELL_NS,
CLOCKS_IRQ,
SPI0_IRQ,
SPI1_IRQ,
UART0_IRQ,
UART1_IRQ,
ADC_IRQ_FIFO,
I2C0_IRQ,
I2C1_IRQ,
TRNG_IRQ,
PLL_SYS_IRQ,
PLL_USB_IRQ,
SWI_IRQ_0,
SWI_IRQ_1,
SWI_IRQ_2,
SWI_IRQ_3,
SWI_IRQ_4,
SWI_IRQ_5,
);
/// Macro to bind interrupts to handlers.
///
/// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`)
/// and implements the right [`Binding`]s for it. You can pass this struct to drivers to
/// prove at compile-time that the right interrupts have been bound.
///
/// Example of how to bind one interrupt:
///
/// ```rust,ignore
/// use embassy_rp::{bind_interrupts, usb, peripherals};
///
/// bind_interrupts!(struct Irqs {
/// USBCTRL_IRQ => usb::InterruptHandler<peripherals::USB>;
/// });
/// ```
///
// developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`.
#[macro_export]
macro_rules! bind_interrupts {
($vis:vis struct $name:ident {
$(
$(#[cfg($cond_irq:meta)])?
$irq:ident => $(
$(#[cfg($cond_handler:meta)])?
$handler:ty
),*;
)*
}) => {
#[derive(Copy, Clone)]
$vis struct $name;
$(
#[allow(non_snake_case)]
#[no_mangle]
$(#[cfg($cond_irq)])?
unsafe extern "C" fn $irq() {
$(
$(#[cfg($cond_handler)])?
<$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt();
)*
}
$(#[cfg($cond_irq)])?
$crate::bind_interrupts!(@inner
$(
$(#[cfg($cond_handler)])?
unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {}
)*
);
)*
};
(@inner $($t:tt)*) => {
$($t)*
}
}
#[cfg(feature = "rp2040")]
embassy_hal_internal::peripherals! {
PIN_0,
PIN_1,
PIN_2,
PIN_3,
PIN_4,
PIN_5,
PIN_6,
PIN_7,
PIN_8,
PIN_9,
PIN_10,
PIN_11,
PIN_12,
PIN_13,
PIN_14,
PIN_15,
PIN_16,
PIN_17,
PIN_18,
PIN_19,
PIN_20,
PIN_21,
PIN_22,
PIN_23,
PIN_24,
PIN_25,
PIN_26,
PIN_27,
PIN_28,
PIN_29,
PIN_QSPI_SCLK,
PIN_QSPI_SS,
PIN_QSPI_SD0,
PIN_QSPI_SD1,
PIN_QSPI_SD2,
PIN_QSPI_SD3,
UART0,
UART1,
SPI0,
SPI1,
I2C0,
I2C1,
DMA_CH0,
DMA_CH1,
DMA_CH2,
DMA_CH3,
DMA_CH4,
DMA_CH5,
DMA_CH6,
DMA_CH7,
DMA_CH8,
DMA_CH9,
DMA_CH10,
DMA_CH11,
PWM_SLICE0,
PWM_SLICE1,
PWM_SLICE2,
PWM_SLICE3,
PWM_SLICE4,
PWM_SLICE5,
PWM_SLICE6,
PWM_SLICE7,
USB,
RTC,
FLASH,
ADC,
ADC_TEMP_SENSOR,
CORE1,
PIO0,
PIO1,
WATCHDOG,
BOOTSEL,
}
#[cfg(feature = "_rp235x")]
embassy_hal_internal::peripherals! {
PIN_0,
PIN_1,
PIN_2,
PIN_3,
PIN_4,
PIN_5,
PIN_6,
PIN_7,
PIN_8,
PIN_9,
PIN_10,
PIN_11,
PIN_12,
PIN_13,
PIN_14,
PIN_15,
PIN_16,
PIN_17,
PIN_18,
PIN_19,
PIN_20,
PIN_21,
PIN_22,
PIN_23,
PIN_24,
PIN_25,
PIN_26,
PIN_27,
PIN_28,
PIN_29,
#[cfg(feature = "rp235xb")]
PIN_30,
#[cfg(feature = "rp235xb")]
PIN_31,
#[cfg(feature = "rp235xb")]
PIN_32,
#[cfg(feature = "rp235xb")]
PIN_33,
#[cfg(feature = "rp235xb")]
PIN_34,
#[cfg(feature = "rp235xb")]
PIN_35,
#[cfg(feature = "rp235xb")]
PIN_36,
#[cfg(feature = "rp235xb")]
PIN_37,
#[cfg(feature = "rp235xb")]
PIN_38,
#[cfg(feature = "rp235xb")]
PIN_39,
#[cfg(feature = "rp235xb")]
PIN_40,
#[cfg(feature = "rp235xb")]
PIN_41,
#[cfg(feature = "rp235xb")]
PIN_42,
#[cfg(feature = "rp235xb")]
PIN_43,
#[cfg(feature = "rp235xb")]
PIN_44,
#[cfg(feature = "rp235xb")]
PIN_45,
#[cfg(feature = "rp235xb")]
PIN_46,
#[cfg(feature = "rp235xb")]
PIN_47,
PIN_QSPI_SCLK,
PIN_QSPI_SS,
PIN_QSPI_SD0,
PIN_QSPI_SD1,
PIN_QSPI_SD2,
PIN_QSPI_SD3,
UART0,
UART1,
SPI0,
SPI1,
I2C0,
I2C1,
DMA_CH0,
DMA_CH1,
DMA_CH2,
DMA_CH3,
DMA_CH4,
DMA_CH5,
DMA_CH6,
DMA_CH7,
DMA_CH8,
DMA_CH9,
DMA_CH10,
DMA_CH11,
DMA_CH12,
DMA_CH13,
DMA_CH14,
DMA_CH15,
PWM_SLICE0,
PWM_SLICE1,
PWM_SLICE2,
PWM_SLICE3,
PWM_SLICE4,
PWM_SLICE5,
PWM_SLICE6,
PWM_SLICE7,
PWM_SLICE8,
PWM_SLICE9,
PWM_SLICE10,
PWM_SLICE11,
USB,
RTC,
FLASH,
ADC,
ADC_TEMP_SENSOR,
CORE1,
PIO0,
PIO1,
PIO2,
WATCHDOG,
BOOTSEL,
TRNG
}
#[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))]
macro_rules! select_bootloader {
( $( $feature:literal => $loader:ident, )+ default => $default:ident ) => {
$(
#[cfg(feature = $feature)]
#[link_section = ".boot2"]
#[used]
static BOOT2: [u8; 256] = rp2040_boot2::$loader;
)*
#[cfg(not(any( $( feature = $feature),* )))]
#[link_section = ".boot2"]
#[used]
static BOOT2: [u8; 256] = rp2040_boot2::$default;
}
}
#[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))]
select_bootloader! {
"boot2-at25sf128a" => BOOT_LOADER_AT25SF128A,
"boot2-gd25q64cs" => BOOT_LOADER_GD25Q64CS,
"boot2-generic-03h" => BOOT_LOADER_GENERIC_03H,
"boot2-is25lp080" => BOOT_LOADER_IS25LP080,
"boot2-ram-memcpy" => BOOT_LOADER_RAM_MEMCPY,
"boot2-w25q080" => BOOT_LOADER_W25Q080,
"boot2-w25x10cl" => BOOT_LOADER_W25X10CL,
default => BOOT_LOADER_W25Q080
}
#[cfg(all(not(feature = "imagedef-none"), feature = "_rp235x"))]
macro_rules! select_imagedef {
( $( $feature:literal => $imagedef:ident, )+ default => $default:ident ) => {
$(
#[cfg(feature = $feature)]
#[link_section = ".start_block"]
#[used]
static IMAGE_DEF: crate::block::ImageDef = crate::block::ImageDef::$imagedef();
)*
#[cfg(not(any( $( feature = $feature),* )))]
#[link_section = ".start_block"]
#[used]
static IMAGE_DEF: crate::block::ImageDef = crate::block::ImageDef::$default();
}
}
#[cfg(all(not(feature = "imagedef-none"), feature = "_rp235x"))]
select_imagedef! {
"imagedef-secure-exe" => secure_exe,
"imagedef-nonsecure-exe" => non_secure_exe,
default => secure_exe
}
/// Installs a stack guard for the CORE0 stack in MPU region 0.
/// Will fail if the MPU is already configured. This function requires
/// a `_stack_end` symbol to be defined by the linker script, and expects
/// `_stack_end` to be located at the lowest address (largest depth) of
/// the stack.
///
/// This method can *only* set up stack guards on the currently
/// executing core. Stack guards for CORE1 are set up automatically,
/// only CORE0 should ever use this.
///
/// # Usage
///
/// ```no_run
/// use embassy_rp::install_core0_stack_guard;
/// use embassy_executor::{Executor, Spawner};
///
/// #[embassy_executor::main]
/// async fn main(_spawner: Spawner) {
/// // set up by the linker as follows:
/// //
/// // MEMORY {
/// // STACK0: ORIGIN = 0x20040000, LENGTH = 4K
/// // }
/// //
/// // _stack_end = ORIGIN(STACK0);
/// // _stack_start = _stack_end + LENGTH(STACK0);
/// //
/// install_core0_stack_guard().expect("MPU already configured");
/// let p = embassy_rp::init(Default::default());
///
/// // ...
/// }
/// ```
pub fn install_core0_stack_guard() -> Result<(), ()> {
extern "C" {
static mut _stack_end: usize;
}
unsafe { install_stack_guard(core::ptr::addr_of_mut!(_stack_end)) }
}
#[cfg(all(feature = "rp2040", not(feature = "_test")))]
#[inline(always)]
unsafe fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> {
let core = unsafe { cortex_m::Peripherals::steal() };
// Fail if MPU is already configured
if core.MPU.ctrl.read() != 0 {
return Err(());
}
// The minimum we can protect is 32 bytes on a 32 byte boundary, so round up which will
// just shorten the valid stack range a tad.
let addr = (stack_bottom as u32 + 31) & !31;
// Mask is 1 bit per 32 bytes of the 256 byte range... clear the bit for the segment we want
let subregion_select = 0xff ^ (1 << ((addr >> 5) & 7));
unsafe {
core.MPU.ctrl.write(5); // enable mpu with background default map
core.MPU.rbar.write((addr & !0xff) | (1 << 4)); // set address and update RNR
core.MPU.rasr.write(
1 // enable region
| (0x7 << 1) // size 2^(7 + 1) = 256
| (subregion_select << 8)
| 0x10000000, // XN = disable instruction fetch; no other bits means no permissions
);
}
Ok(())
}
#[cfg(all(feature = "_rp235x", not(feature = "_test")))]
#[inline(always)]
unsafe fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> {
let core = unsafe { cortex_m::Peripherals::steal() };
// Fail if MPU is already configured
if core.MPU.ctrl.read() != 0 {
return Err(());
}
unsafe {
core.MPU.ctrl.write(5); // enable mpu with background default map
core.MPU.rbar.write(stack_bottom as u32 & !0xff); // set address
core.MPU.rlar.write(1); // enable region
}
Ok(())
}
// This is to hack around cortex_m defaulting to ARMv7 when building tests,
// so the compile fails when we try to use ARMv8 peripherals.
#[cfg(feature = "_test")]
#[inline(always)]
unsafe fn install_stack_guard(_stack_bottom: *mut usize) -> Result<(), ()> {
Ok(())
}
/// HAL configuration for RP.
pub mod config {
use crate::clocks::ClockConfig;
/// HAL configuration passed when initializing.
#[non_exhaustive]
pub struct Config {
/// Clock configuration.
pub clocks: ClockConfig,
}
impl Default for Config {
fn default() -> Self {
Self {
clocks: ClockConfig::crystal(12_000_000),
}
}
}
impl Config {
/// Create a new configuration with the provided clock config.
pub fn new(clocks: ClockConfig) -> Self {
Self { clocks }
}
}
}
/// Initialize the `embassy-rp` HAL with the provided configuration.
///
/// This returns the peripheral singletons that can be used for creating drivers.
///
/// This should only be called once at startup, otherwise it panics.
pub fn init(config: config::Config) -> Peripherals {
// Do this first, so that it panics if user is calling `init` a second time
// before doing anything important.
let peripherals = Peripherals::take();
unsafe {
clocks::init(config.clocks);
#[cfg(feature = "time-driver")]
time_driver::init();
dma::init();
gpio::init();
}
peripherals
}
#[cfg(feature = "rt")]
#[cortex_m_rt::pre_init]
unsafe fn pre_init() {
// SIO does not get reset when core0 is reset with either `scb::sys_reset()` or with SWD.
// Since we're using SIO spinlock 31 for the critical-section impl, this causes random
// hangs if we reset in the middle of a CS, because the next boot sees the spinlock
// as locked and waits forever.
//
// See https://github.com/embassy-rs/embassy/issues/1736
// and https://github.com/rp-rs/rp-hal/issues/292
// and https://matrix.to/#/!vhKMWjizPZBgKeknOo:matrix.org/$VfOkQgyf1PjmaXZbtycFzrCje1RorAXd8BQFHTl4d5M
//
// According to Raspberry Pi, this is considered Working As Intended, and not an errata,
// even though this behavior is different from every other ARM chip (sys_reset usually resets
// the *system* as its name implies, not just the current core).
//
// To fix this, reset SIO on boot. We must do this in pre_init because it's unsound to do it
// in `embassy_rp::init`, since the user could've acquired a CS by then. pre_init is guaranteed
// to run before any user code.
//
// A similar thing could happen with PROC1. It is unclear whether it's possible for PROC1
// to stay unreset through a PROC0 reset, so we reset it anyway just in case.
//
// Important info from PSM logic (from Luke Wren in above Matrix thread)
//
// The logic is, each PSM stage is reset if either of the following is true:
// - The previous stage is in reset and FRCE_ON is false
// - FRCE_OFF is true
//
// The PSM order is SIO -> PROC0 -> PROC1.
// So, we have to force-on PROC0 to prevent it from getting reset when resetting SIO.
#[cfg(feature = "rp2040")]
{
pac::PSM.frce_on().write_and_wait(|w| {
w.set_proc0(true);
});
// Then reset SIO and PROC1.
pac::PSM.frce_off().write_and_wait(|w| {
w.set_sio(true);
w.set_proc1(true);
});
// clear force_off first, force_on second. The other way around would reset PROC0.
pac::PSM.frce_off().write_and_wait(|_| {});
pac::PSM.frce_on().write_and_wait(|_| {});
}
#[cfg(feature = "_rp235x")]
{
// on RP235x, datasheet says "The FRCE_ON register is a development feature that does nothing in production devices."
// No idea why they removed it. Removing it means we can't use PSM to reset SIO, because it comes before
// PROC0, so we'd need FRCE_ON to prevent resetting ourselves.
//
// So we just unlock the spinlock manually.
pac::SIO.spinlock(31).write_value(1);
// We can still use PSM to reset PROC1 since it comes after PROC0 in the state machine.
pac::PSM.frce_off().write_and_wait(|w| w.set_proc1(true));
pac::PSM.frce_off().write_and_wait(|_| {});
// Make atomics work between cores.
enable_actlr_extexclall();
}
}
/// Set the EXTEXCLALL bit in ACTLR.
///
/// The default MPU memory map marks all memory as non-shareable, so atomics don't
/// synchronize memory accesses between cores at all. This bit forces all memory to be
/// considered shareable regardless of what the MPU says.
///
/// TODO: does this interfere somehow if the user wants to use a custom MPU configuration?
/// maybe we need to add a way to disable this?
///
/// This must be done FOR EACH CORE.
#[cfg(feature = "_rp235x")]
unsafe fn enable_actlr_extexclall() {
(&*cortex_m::peripheral::ICB::PTR).actlr.modify(|w| w | (1 << 29));
}
/// Extension trait for PAC regs, adding atomic xor/bitset/bitclear writes.
#[allow(unused)]
trait RegExt<T: Copy> {
#[allow(unused)]
fn write_xor<R>(&self, f: impl FnOnce(&mut T) -> R) -> R;
fn write_set<R>(&self, f: impl FnOnce(&mut T) -> R) -> R;
fn write_clear<R>(&self, f: impl FnOnce(&mut T) -> R) -> R;
fn write_and_wait<R>(&self, f: impl FnOnce(&mut T) -> R) -> R
where
T: PartialEq;
}
impl<T: Default + Copy, A: pac::common::Write> RegExt<T> for pac::common::Reg<T, A> {
fn write_xor<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
let mut val = Default::default();
let res = f(&mut val);
unsafe {
let ptr = (self.as_ptr() as *mut u8).add(0x1000) as *mut T;
ptr.write_volatile(val);
}
res
}
fn write_set<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
let mut val = Default::default();
let res = f(&mut val);
unsafe {
let ptr = (self.as_ptr() as *mut u8).add(0x2000) as *mut T;
ptr.write_volatile(val);
}
res
}
fn write_clear<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
let mut val = Default::default();
let res = f(&mut val);
unsafe {
let ptr = (self.as_ptr() as *mut u8).add(0x3000) as *mut T;
ptr.write_volatile(val);
}
res
}
fn write_and_wait<R>(&self, f: impl FnOnce(&mut T) -> R) -> R
where
T: PartialEq,
{
let mut val = Default::default();
let res = f(&mut val);
unsafe {
self.as_ptr().write_volatile(val);
while self.as_ptr().read_volatile() != val {}
}
res
}
}

348
embassy-rp/src/multicore.rs Normal file
View File

@@ -0,0 +1,348 @@
//! Multicore support
//!
//! This module handles setup of the 2nd cpu core on the rp2040, which we refer to as core1.
//! It provides functionality for setting up the stack, and starting core1.
//!
//! The entrypoint for core1 can be any function that never returns, including closures.
//!
//! Enable the `critical-section-impl` feature in embassy-rp when sharing data across cores using
//! the `embassy-sync` primitives and `CriticalSectionRawMutex`.
//!
//! # Usage
//!
//! ```no_run
//! use embassy_rp::multicore::Stack;
//! use static_cell::StaticCell;
//! use embassy_executor::Executor;
//!
//! static mut CORE1_STACK: Stack<4096> = Stack::new();
//! static EXECUTOR0: StaticCell<Executor> = StaticCell::new();
//! static EXECUTOR1: StaticCell<Executor> = StaticCell::new();
//!
//! # // workaround weird error: `main` function not found in crate `rust_out`
//! # let _ = ();
//!
//! #[embassy_executor::task]
//! async fn core0_task() {
//! // ...
//! }
//!
//! #[embassy_executor::task]
//! async fn core1_task() {
//! // ...
//! }
//!
//! #[cortex_m_rt::entry]
//! fn main() -> ! {
//! let p = embassy_rp::init(Default::default());
//!
//! embassy_rp::multicore::spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || {
//! let executor1 = EXECUTOR1.init(Executor::new());
//! executor1.run(|spawner| spawner.spawn(core1_task()).unwrap());
//! });
//!
//! let executor0 = EXECUTOR0.init(Executor::new());
//! executor0.run(|spawner| spawner.spawn(core0_task()).unwrap())
//! }
//! ```
use core::mem::ManuallyDrop;
use core::sync::atomic::{compiler_fence, AtomicBool, Ordering};
use crate::interrupt::InterruptExt;
use crate::peripherals::CORE1;
use crate::{gpio, install_stack_guard, interrupt, pac};
const PAUSE_TOKEN: u32 = 0xDEADBEEF;
const RESUME_TOKEN: u32 = !0xDEADBEEF;
static IS_CORE1_INIT: AtomicBool = AtomicBool::new(false);
#[inline(always)]
unsafe fn core1_setup(stack_bottom: *mut usize) {
if install_stack_guard(stack_bottom).is_err() {
// currently only happens if the MPU was already set up, which
// would indicate that the core is already in use from outside
// embassy, somehow. trap if so since we can't deal with that.
cortex_m::asm::udf();
}
#[cfg(feature = "_rp235x")]
crate::enable_actlr_extexclall();
unsafe {
gpio::init();
}
}
/// Data type for a properly aligned stack of N bytes
#[repr(C, align(32))]
pub struct Stack<const SIZE: usize> {
/// Memory to be used for the stack
pub mem: [u8; SIZE],
}
impl<const SIZE: usize> Stack<SIZE> {
/// Construct a stack of length SIZE, initialized to 0
pub const fn new() -> Stack<SIZE> {
Stack { mem: [0_u8; SIZE] }
}
}
#[cfg(all(feature = "rt", feature = "rp2040"))]
#[interrupt]
#[link_section = ".data.ram_func"]
unsafe fn SIO_IRQ_PROC1() {
let sio = pac::SIO;
// Clear IRQ
sio.fifo().st().write(|w| w.set_wof(false));
while sio.fifo().st().read().vld() {
// Pause CORE1 execution and disable interrupts
if fifo_read_wfe() == PAUSE_TOKEN {
cortex_m::interrupt::disable();
// Signal to CORE0 that execution is paused
fifo_write(PAUSE_TOKEN);
// Wait for `resume` signal from CORE0
while fifo_read_wfe() != RESUME_TOKEN {
cortex_m::asm::nop();
}
cortex_m::interrupt::enable();
// Signal to CORE0 that execution is resumed
fifo_write(RESUME_TOKEN);
}
}
}
#[cfg(all(feature = "rt", feature = "_rp235x"))]
#[interrupt]
#[link_section = ".data.ram_func"]
unsafe fn SIO_IRQ_FIFO() {
let sio = pac::SIO;
// Clear IRQ
sio.fifo().st().write(|w| w.set_wof(false));
while sio.fifo().st().read().vld() {
// Pause CORE1 execution and disable interrupts
if fifo_read_wfe() == PAUSE_TOKEN {
cortex_m::interrupt::disable();
// Signal to CORE0 that execution is paused
fifo_write(PAUSE_TOKEN);
// Wait for `resume` signal from CORE0
while fifo_read_wfe() != RESUME_TOKEN {
cortex_m::asm::nop();
}
cortex_m::interrupt::enable();
// Signal to CORE0 that execution is resumed
fifo_write(RESUME_TOKEN);
}
}
}
/// Spawn a function on this core
pub fn spawn_core1<F, const SIZE: usize>(_core1: CORE1, stack: &'static mut Stack<SIZE>, entry: F)
where
F: FnOnce() -> bad::Never + Send + 'static,
{
// The first two ignored `u64` parameters are there to take up all of the registers,
// which means that the rest of the arguments are taken from the stack,
// where we're able to put them from core 0.
extern "C" fn core1_startup<F: FnOnce() -> bad::Never>(
_: u64,
_: u64,
entry: *mut ManuallyDrop<F>,
stack_bottom: *mut usize,
) -> ! {
unsafe { core1_setup(stack_bottom) };
let entry = unsafe { ManuallyDrop::take(&mut *entry) };
// make sure the preceding read doesn't get reordered past the following fifo write
compiler_fence(Ordering::SeqCst);
// Signal that it's safe for core 0 to get rid of the original value now.
fifo_write(1);
IS_CORE1_INIT.store(true, Ordering::Release);
// Enable fifo interrupt on CORE1 for `pause` functionality.
#[cfg(feature = "rp2040")]
unsafe {
interrupt::SIO_IRQ_PROC1.enable()
};
#[cfg(feature = "_rp235x")]
unsafe {
interrupt::SIO_IRQ_FIFO.enable()
};
// Enable FPU
#[cfg(all(feature = "_rp235x", has_fpu))]
unsafe {
let p = cortex_m::Peripherals::steal();
p.SCB.cpacr.modify(|cpacr| cpacr | (3 << 20) | (3 << 22));
}
entry()
}
// Reset the core
let psm = pac::PSM;
psm.frce_off().modify(|w| w.set_proc1(true));
while !psm.frce_off().read().proc1() {
cortex_m::asm::nop();
}
psm.frce_off().modify(|w| w.set_proc1(false));
// The ARM AAPCS ABI requires 8-byte stack alignment.
// #[align] on `struct Stack` ensures the bottom is aligned, but the top could still be
// unaligned if the user chooses a stack size that's not multiple of 8.
// So, we round down to the next multiple of 8.
let stack_words = stack.mem.len() / 8 * 2;
let mem = unsafe { core::slice::from_raw_parts_mut(stack.mem.as_mut_ptr() as *mut usize, stack_words) };
// Set up the stack
let mut stack_ptr = unsafe { mem.as_mut_ptr().add(mem.len()) };
// We don't want to drop this, since it's getting moved to the other core.
let mut entry = ManuallyDrop::new(entry);
// Push the arguments to `core1_startup` onto the stack.
unsafe {
// Push `stack_bottom`.
stack_ptr = stack_ptr.sub(1);
stack_ptr.cast::<*mut usize>().write(mem.as_mut_ptr());
// Push `entry`.
stack_ptr = stack_ptr.sub(1);
stack_ptr.cast::<*mut ManuallyDrop<F>>().write(&mut entry);
}
// Make sure the compiler does not reorder the stack writes after to after the
// below FIFO writes, which would result in them not being seen by the second
// core.
//
// From the compiler perspective, this doesn't guarantee that the second core
// actually sees those writes. However, we know that the RP2040 doesn't have
// memory caches, and writes happen in-order.
compiler_fence(Ordering::Release);
let p = unsafe { cortex_m::Peripherals::steal() };
let vector_table = p.SCB.vtor.read();
// After reset, core 1 is waiting to receive commands over FIFO.
// This is the sequence to have it jump to some code.
let cmd_seq = [
0,
0,
1,
vector_table as usize,
stack_ptr as usize,
core1_startup::<F> as usize,
];
let mut seq = 0;
let mut fails = 0;
loop {
let cmd = cmd_seq[seq] as u32;
if cmd == 0 {
fifo_drain();
cortex_m::asm::sev();
}
fifo_write(cmd);
let response = fifo_read();
if cmd == response {
seq += 1;
} else {
seq = 0;
fails += 1;
if fails > 16 {
// The second core isn't responding, and isn't going to take the entrypoint
panic!("CORE1 not responding");
}
}
if seq >= cmd_seq.len() {
break;
}
}
// Wait until the other core has copied `entry` before returning.
fifo_read();
}
/// Pause execution on CORE1.
pub fn pause_core1() {
if IS_CORE1_INIT.load(Ordering::Acquire) {
fifo_write(PAUSE_TOKEN);
// Wait for CORE1 to signal it has paused execution.
while fifo_read() != PAUSE_TOKEN {}
}
}
/// Resume CORE1 execution.
pub fn resume_core1() {
if IS_CORE1_INIT.load(Ordering::Acquire) {
fifo_write(RESUME_TOKEN);
// Wait for CORE1 to signal it has resumed execution.
while fifo_read() != RESUME_TOKEN {}
}
}
// Push a value to the inter-core FIFO, block until space is available
#[inline(always)]
fn fifo_write(value: u32) {
let sio = pac::SIO;
// Wait for the FIFO to have enough space
while !sio.fifo().st().read().rdy() {
cortex_m::asm::nop();
}
sio.fifo().wr().write_value(value);
// Fire off an event to the other core.
// This is required as the other core may be `wfe` (waiting for event)
cortex_m::asm::sev();
}
// Pop a value from inter-core FIFO, block until available
#[inline(always)]
fn fifo_read() -> u32 {
let sio = pac::SIO;
// Wait until FIFO has data
while !sio.fifo().st().read().vld() {
cortex_m::asm::nop();
}
sio.fifo().rd().read()
}
// Pop a value from inter-core FIFO, `wfe` until available
#[inline(always)]
#[allow(unused)]
fn fifo_read_wfe() -> u32 {
let sio = pac::SIO;
// Wait until FIFO has data
while !sio.fifo().st().read().vld() {
cortex_m::asm::wfe();
}
sio.fifo().rd().read()
}
// Drain inter-core FIFO
#[inline(always)]
fn fifo_drain() {
let sio = pac::SIO;
while sio.fifo().st().read().vld() {
let _ = sio.fifo().rd().read();
}
}
// https://github.com/nvzqz/bad-rs/blob/master/src/never.rs
mod bad {
pub(crate) type Never = <F as HasOutput>::Output;
pub trait HasOutput {
type Output;
}
impl<O> HasOutput for fn() -> O {
type Output = O;
}
type F = fn() -> !;
}

175
embassy-rp/src/otp.rs Normal file
View File

@@ -0,0 +1,175 @@
//! Interface to the RP2350's One Time Programmable Memory
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp235x-hal/src/rom_data.rs
use crate::rom_data::otp_access;
/// The ways in which we can fail to access OTP
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
/// The user passed an invalid index to a function.
InvalidIndex,
/// The hardware refused to let us read this word, probably due to
/// read or write lock set earlier in the boot process.
InvalidPermissions,
/// Modification is impossible based on current state; e.g.
/// attempted to clear an OTP bit.
UnsupportedModification,
/// Value being written is bigger than 24 bits allowed for raw writes.
Overflow,
/// An unexpected failure that contains the exact return code
UnexpectedFailure(i32),
}
/// OTP read address, using automatic Error Correction.
///
/// A 32-bit read returns the ECC-corrected data for two neighbouring rows, or
/// all-ones on permission failure. Only the first 8 KiB is populated.
pub const OTP_DATA_BASE: *const u32 = 0x4013_0000 as *const u32;
/// OTP read address, without using any automatic Error Correction.
///
/// A 32-bit read returns 24-bits of raw data from the OTP word.
pub const OTP_DATA_RAW_BASE: *const u32 = 0x4013_4000 as *const u32;
/// How many pages in OTP (post error-correction)
pub const NUM_PAGES: usize = 64;
/// How many rows in one page in OTP (post error-correction)
pub const NUM_ROWS_PER_PAGE: usize = 64;
/// How many rows in OTP (post error-correction)
pub const NUM_ROWS: usize = NUM_PAGES * NUM_ROWS_PER_PAGE;
/// 24bit mask for raw writes
pub const RAW_WRITE_BIT_MASK: u32 = 0x00FF_FFFF;
/// Read one ECC protected word from the OTP
pub fn read_ecc_word(row: usize) -> Result<u16, Error> {
if row >= NUM_ROWS {
return Err(Error::InvalidIndex);
}
// First do a raw read to check permissions
let _ = read_raw_word(row)?;
// One 32-bit read gets us two rows
let offset = row >> 1;
// # Safety
//
// We checked this offset was in range already.
let value = unsafe { OTP_DATA_BASE.add(offset).read() };
if (row & 1) == 0 {
Ok(value as u16)
} else {
Ok((value >> 16) as u16)
}
}
/// Read one raw word from the OTP
///
/// You get the 24-bit raw value in the lower part of the 32-bit result.
pub fn read_raw_word(row: usize) -> Result<u32, Error> {
if row >= NUM_ROWS {
return Err(Error::InvalidIndex);
}
// One 32-bit read gets us one row
// # Safety
//
// We checked this offset was in range already.
let value = unsafe { OTP_DATA_RAW_BASE.add(row).read() };
if value == 0xFFFF_FFFF {
Err(Error::InvalidPermissions)
} else {
Ok(value)
}
}
/// Write one raw word to the OTP
///
/// 24 bit value will be written to the OTP
pub fn write_raw_word(row: usize, data: u32) -> Result<(), Error> {
if data > RAW_WRITE_BIT_MASK {
return Err(Error::Overflow);
}
if row >= NUM_ROWS {
return Err(Error::InvalidIndex);
}
let row_with_write_bit = row | 0x00010000;
// # Safety
//
// We checked this row was in range already.
let result = unsafe { otp_access(data.to_le_bytes().as_mut_ptr(), 4, row_with_write_bit as u32) };
if result == 0 {
Ok(())
} else {
// 5.4.3. API Function Return Codes
let error = match result {
-4 => Error::InvalidPermissions,
-18 => Error::UnsupportedModification,
_ => Error::UnexpectedFailure(result),
};
Err(error)
}
}
/// Write one raw word to the OTP with ECC
///
/// 16 bit value will be written + ECC
pub fn write_ecc_word(row: usize, data: u16) -> Result<(), Error> {
if row >= NUM_ROWS {
return Err(Error::InvalidIndex);
}
let row_with_write_and_ecc_bit = row | 0x00030000;
// # Safety
//
// We checked this row was in range already.
let result = unsafe { otp_access(data.to_le_bytes().as_mut_ptr(), 2, row_with_write_and_ecc_bit as u32) };
if result == 0 {
Ok(())
} else {
// 5.4.3. API Function Return Codes
// 5.4.3. API Function Return Codes
let error = match result {
-4 => Error::InvalidPermissions,
-18 => Error::UnsupportedModification,
_ => Error::UnexpectedFailure(result),
};
Err(error)
}
}
/// Get the random 64bit chipid from rows 0x0-0x3.
pub fn get_chipid() -> Result<u64, Error> {
let w0 = read_ecc_word(0x000)?.to_be_bytes();
let w1 = read_ecc_word(0x001)?.to_be_bytes();
let w2 = read_ecc_word(0x002)?.to_be_bytes();
let w3 = read_ecc_word(0x003)?.to_be_bytes();
Ok(u64::from_be_bytes([
w3[0], w3[1], w2[0], w2[1], w1[0], w1[1], w0[0], w0[1],
]))
}
/// Get the 128bit private random number from rows 0x4-0xb.
///
/// This ID is not exposed through the USB PICOBOOT GET_INFO command
/// or the ROM get_sys_info() API. However note that the USB PICOBOOT OTP
/// access point can read the entirety of page 0, so this value is not
/// meaningfully private unless the USB PICOBOOT interface is disabled via the
//// DISABLE_BOOTSEL_USB_PICOBOOT_IFC flag in BOOT_FLAGS0
pub fn get_private_random_number() -> Result<u128, Error> {
let w0 = read_ecc_word(0x004)?.to_be_bytes();
let w1 = read_ecc_word(0x005)?.to_be_bytes();
let w2 = read_ecc_word(0x006)?.to_be_bytes();
let w3 = read_ecc_word(0x007)?.to_be_bytes();
let w4 = read_ecc_word(0x008)?.to_be_bytes();
let w5 = read_ecc_word(0x009)?.to_be_bytes();
let w6 = read_ecc_word(0x00a)?.to_be_bytes();
let w7 = read_ecc_word(0x00b)?.to_be_bytes();
Ok(u128::from_be_bytes([
w7[0], w7[1], w6[0], w6[1], w5[0], w5[1], w4[0], w4[1], w3[0], w3[1], w2[0], w2[1], w1[0], w1[1], w0[0], w0[1],
]))
}

103
embassy-rp/src/pio/instr.rs Normal file
View File

@@ -0,0 +1,103 @@
//! Instructions controlling the PIO.
use pio::{InSource, InstructionOperands, JmpCondition, OutDestination, SetDestination};
use crate::pio::{Instance, StateMachine};
impl<'d, PIO: Instance, const SM: usize> StateMachine<'d, PIO, SM> {
/// Set value of scratch register X.
pub unsafe fn set_x(&mut self, value: u32) {
const OUT: u16 = InstructionOperands::OUT {
destination: OutDestination::X,
bit_count: 32,
}
.encode();
self.tx().push(value);
self.exec_instr(OUT);
}
/// Get value of scratch register X.
pub unsafe fn get_x(&mut self) -> u32 {
const IN: u16 = InstructionOperands::IN {
source: InSource::X,
bit_count: 32,
}
.encode();
self.exec_instr(IN);
self.rx().pull()
}
/// Set value of scratch register Y.
pub unsafe fn set_y(&mut self, value: u32) {
const OUT: u16 = InstructionOperands::OUT {
destination: OutDestination::Y,
bit_count: 32,
}
.encode();
self.tx().push(value);
self.exec_instr(OUT);
}
/// Get value of scratch register Y.
pub unsafe fn get_y(&mut self) -> u32 {
const IN: u16 = InstructionOperands::IN {
source: InSource::Y,
bit_count: 32,
}
.encode();
self.exec_instr(IN);
self.rx().pull()
}
/// Set instruction for pindir destination.
pub unsafe fn set_pindir(&mut self, data: u8) {
let set: u16 = InstructionOperands::SET {
destination: SetDestination::PINDIRS,
data,
}
.encode();
self.exec_instr(set);
}
/// Set instruction for pin destination.
pub unsafe fn set_pin(&mut self, data: u8) {
let set: u16 = InstructionOperands::SET {
destination: SetDestination::PINS,
data,
}
.encode();
self.exec_instr(set);
}
/// Out instruction for pin destination.
pub unsafe fn set_out_pin(&mut self, data: u32) {
const OUT: u16 = InstructionOperands::OUT {
destination: OutDestination::PINS,
bit_count: 32,
}
.encode();
self.tx().push(data);
self.exec_instr(OUT);
}
/// Out instruction for pindir destination.
pub unsafe fn set_out_pindir(&mut self, data: u32) {
const OUT: u16 = InstructionOperands::OUT {
destination: OutDestination::PINDIRS,
bit_count: 32,
}
.encode();
self.tx().push(data);
self.exec_instr(OUT);
}
/// Jump instruction to address.
pub unsafe fn exec_jmp(&mut self, to_addr: u8) {
let jmp: u16 = InstructionOperands::JMP {
address: to_addr,
condition: JmpCondition::Always,
}
.encode();
self.exec_instr(jmp);
}
}

1479
embassy-rp/src/pio/mod.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,203 @@
//! [HD44780 display driver](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf)
use crate::dma::{AnyChannel, Channel};
use crate::pio::{
Common, Config, Direction, FifoJoin, Instance, Irq, LoadedProgram, PioPin, ShiftConfig, ShiftDirection,
StateMachine,
};
use crate::{into_ref, Peripheral, PeripheralRef};
/// This struct represents a HD44780 program that takes command words (<wait:24> <command:4> <0:4>)
pub struct PioHD44780CommandWordProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioHD44780CommandWordProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio::pio_asm!(
r#"
.side_set 1 opt
.origin 20
loop:
out x, 24
delay:
jmp x--, delay
out pins, 4 side 1
out null, 4 side 0
jmp !osre, loop
irq 0
"#,
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// This struct represents a HD44780 program that takes command sequences (<rs:1> <count:7>, data...)
pub struct PioHD44780CommandSequenceProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioHD44780CommandSequenceProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
// many side sets are only there to free up a delay bit!
let prg = pio::pio_asm!(
r#"
.origin 27
.side_set 1
.wrap_target
pull side 0
out x 1 side 0 ; !rs
out y 7 side 0 ; #data - 1
; rs/rw to e: >= 60ns
; e high time: >= 500ns
; e low time: >= 500ns
; read data valid after e falling: ~5ns
; write data hold after e falling: ~10ns
loop:
pull side 0
jmp !x data side 0
command:
set pins 0b00 side 0
jmp shift side 0
data:
set pins 0b01 side 0
shift:
out pins 4 side 1 [9]
nop side 0 [9]
out pins 4 side 1 [9]
mov osr null side 0 [7]
out pindirs 4 side 0
set pins 0b10 side 0
busy:
nop side 1 [9]
jmp pin more side 0 [9]
mov osr ~osr side 1 [9]
nop side 0 [4]
out pindirs 4 side 0
jmp y-- loop side 0
.wrap
more:
nop side 1 [9]
jmp busy side 0 [9]
"#
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// Pio backed HD44780 driver
pub struct PioHD44780<'l, P: Instance, const S: usize> {
dma: PeripheralRef<'l, AnyChannel>,
sm: StateMachine<'l, P, S>,
buf: [u8; 40],
}
impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> {
/// Configure the given state machine to first init, then write data to, a HD44780 display.
pub async fn new(
common: &mut Common<'l, P>,
mut sm: StateMachine<'l, P, S>,
mut irq: Irq<'l, P, S>,
dma: impl Peripheral<P = impl Channel> + 'l,
rs: impl PioPin,
rw: impl PioPin,
e: impl PioPin,
db4: impl PioPin,
db5: impl PioPin,
db6: impl PioPin,
db7: impl PioPin,
word_prg: &PioHD44780CommandWordProgram<'l, P>,
seq_prg: &PioHD44780CommandSequenceProgram<'l, P>,
) -> PioHD44780<'l, P, S> {
into_ref!(dma);
let rs = common.make_pio_pin(rs);
let rw = common.make_pio_pin(rw);
let e = common.make_pio_pin(e);
let db4 = common.make_pio_pin(db4);
let db5 = common.make_pio_pin(db5);
let db6 = common.make_pio_pin(db6);
let db7 = common.make_pio_pin(db7);
sm.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]);
let mut cfg = Config::default();
cfg.use_program(&word_prg.prg, &[&e]);
cfg.clock_divider = 125u8.into();
cfg.set_out_pins(&[&db4, &db5, &db6, &db7]);
cfg.shift_out = ShiftConfig {
auto_fill: true,
direction: ShiftDirection::Left,
threshold: 32,
};
cfg.fifo_join = FifoJoin::TxOnly;
sm.set_config(&cfg);
sm.set_enable(true);
// init to 8 bit thrice
sm.tx().push((50000 << 8) | 0x30);
sm.tx().push((5000 << 8) | 0x30);
sm.tx().push((200 << 8) | 0x30);
// init 4 bit
sm.tx().push((200 << 8) | 0x20);
// set font and lines
sm.tx().push((50 << 8) | 0x20);
sm.tx().push(0b1100_0000);
irq.wait().await;
sm.set_enable(false);
let mut cfg = Config::default();
cfg.use_program(&seq_prg.prg, &[&e]);
cfg.clock_divider = 8u8.into(); // ~64ns/insn
cfg.set_jmp_pin(&db7);
cfg.set_set_pins(&[&rs, &rw]);
cfg.set_out_pins(&[&db4, &db5, &db6, &db7]);
cfg.shift_out.direction = ShiftDirection::Left;
cfg.fifo_join = FifoJoin::TxOnly;
sm.set_config(&cfg);
sm.set_enable(true);
// display on and cursor on and blinking, reset display
sm.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1], false).await;
Self {
dma: dma.map_into(),
sm,
buf: [0x20; 40],
}
}
/// Write a line to the display
pub async fn add_line(&mut self, s: &[u8]) {
// move cursor to 0:0, prepare 16 characters
self.buf[..3].copy_from_slice(&[0x80, 0x80, 15]);
// move line 2 up
self.buf.copy_within(22..38, 3);
// move cursor to 1:0, prepare 16 characters
self.buf[19..22].copy_from_slice(&[0x80, 0xc0, 15]);
// file line 2 with spaces
self.buf[22..38].fill(0x20);
// copy input line
let len = s.len().min(16);
self.buf[22..22 + len].copy_from_slice(&s[0..len]);
// set cursor to 1:15
self.buf[38..].copy_from_slice(&[0x80, 0xcf]);
self.sm.tx().dma_push(self.dma.reborrow(), &self.buf, false).await;
}
}

View File

@@ -0,0 +1,95 @@
//! Pio backed I2s output
use fixed::traits::ToFixed;
use crate::dma::{AnyChannel, Channel, Transfer};
use crate::pio::{
Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine,
};
use crate::{into_ref, Peripheral, PeripheralRef};
/// This struct represents an i2s output driver program
pub struct PioI2sOutProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioI2sOutProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio::pio_asm!(
".side_set 2",
" set x, 14 side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock
"left_data:",
" out pins, 1 side 0b00",
" jmp x-- left_data side 0b01",
" out pins 1 side 0b10",
" set x, 14 side 0b11",
"right_data:",
" out pins 1 side 0b10",
" jmp x-- right_data side 0b11",
" out pins 1 side 0b00",
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// Pio backed I2s output driver
pub struct PioI2sOut<'a, P: Instance, const S: usize> {
dma: PeripheralRef<'a, AnyChannel>,
sm: StateMachine<'a, P, S>,
}
impl<'a, P: Instance, const S: usize> PioI2sOut<'a, P, S> {
/// Configure a state machine to output I2s
pub fn new(
common: &mut Common<'a, P>,
mut sm: StateMachine<'a, P, S>,
dma: impl Peripheral<P = impl Channel> + 'a,
data_pin: impl PioPin,
bit_clock_pin: impl PioPin,
lr_clock_pin: impl PioPin,
sample_rate: u32,
bit_depth: u32,
channels: u32,
program: &PioI2sOutProgram<'a, P>,
) -> Self {
into_ref!(dma);
let data_pin = common.make_pio_pin(data_pin);
let bit_clock_pin = common.make_pio_pin(bit_clock_pin);
let left_right_clock_pin = common.make_pio_pin(lr_clock_pin);
let cfg = {
let mut cfg = Config::default();
cfg.use_program(&program.prg, &[&bit_clock_pin, &left_right_clock_pin]);
cfg.set_out_pins(&[&data_pin]);
let clock_frequency = sample_rate * bit_depth * channels;
cfg.clock_divider = (crate::clocks::clk_sys_freq() as f64 / clock_frequency as f64 / 2.).to_fixed();
cfg.shift_out = ShiftConfig {
threshold: 32,
direction: ShiftDirection::Left,
auto_fill: true,
};
// join fifos to have twice the time to start the next dma transfer
cfg.fifo_join = FifoJoin::TxOnly;
cfg
};
sm.set_config(&cfg);
sm.set_pin_dirs(Direction::Out, &[&data_pin, &left_right_clock_pin, &bit_clock_pin]);
sm.set_enable(true);
Self {
dma: dma.map_into(),
sm,
}
}
/// Return an in-prograss dma transfer future. Awaiting it will guarentee a complete transfer.
pub fn write<'b>(&'b mut self, buff: &'b [u32]) -> Transfer<'b, AnyChannel> {
self.sm.tx().dma_push(self.dma.reborrow(), buff, false)
}
}

View File

@@ -0,0 +1,10 @@
//! Pre-built pio programs for common interfaces
pub mod hd44780;
pub mod i2s;
pub mod onewire;
pub mod pwm;
pub mod rotary_encoder;
pub mod stepper;
pub mod uart;
pub mod ws2812;

View File

@@ -0,0 +1,109 @@
//! OneWire pio driver
use crate::pio::{Common, Config, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine};
/// This struct represents an onewire driver program
pub struct PioOneWireProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioOneWireProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio::pio_asm!(
r#"
.wrap_target
again:
pull block
mov x, osr
jmp !x, read
write:
set pindirs, 1
set pins, 0
loop1:
jmp x--,loop1
set pindirs, 0 [31]
wait 1 pin 0 [31]
pull block
mov x, osr
bytes1:
pull block
set y, 7
set pindirs, 1
bit1:
set pins, 0 [1]
out pins,1 [31]
set pins, 1 [20]
jmp y--,bit1
jmp x--,bytes1
set pindirs, 0 [31]
jmp again
read:
pull block
mov x, osr
bytes2:
set y, 7
bit2:
set pindirs, 1
set pins, 0 [1]
set pindirs, 0 [5]
in pins,1 [10]
jmp y--,bit2
jmp x--,bytes2
.wrap
"#,
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// Pio backed OneWire driver
pub struct PioOneWire<'d, PIO: Instance, const SM: usize> {
sm: StateMachine<'d, PIO, SM>,
}
impl<'d, PIO: Instance, const SM: usize> PioOneWire<'d, PIO, SM> {
/// Create a new instance the driver
pub fn new(
common: &mut Common<'d, PIO>,
mut sm: StateMachine<'d, PIO, SM>,
pin: impl PioPin,
program: &PioOneWireProgram<'d, PIO>,
) -> Self {
let pin = common.make_pio_pin(pin);
let mut cfg = Config::default();
cfg.use_program(&program.prg, &[]);
cfg.set_out_pins(&[&pin]);
cfg.set_in_pins(&[&pin]);
cfg.set_set_pins(&[&pin]);
cfg.shift_in = ShiftConfig {
auto_fill: true,
direction: ShiftDirection::Right,
threshold: 8,
};
cfg.clock_divider = 255_u8.into();
sm.set_config(&cfg);
sm.set_enable(true);
Self { sm }
}
/// Write bytes over the wire
pub async fn write_bytes(&mut self, bytes: &[u8]) {
self.sm.tx().wait_push(250).await;
self.sm.tx().wait_push(bytes.len() as u32 - 1).await;
for b in bytes {
self.sm.tx().wait_push(*b as u32).await;
}
}
/// Read bytes from the wire
pub async fn read_bytes(&mut self, bytes: &mut [u8]) {
self.sm.tx().wait_push(0).await;
self.sm.tx().wait_push(bytes.len() as u32 - 1).await;
for b in bytes.iter_mut() {
*b = (self.sm.rx().wait_pull().await >> 24) as u8;
}
}
}

View File

@@ -0,0 +1,121 @@
//! PIO backed PWM driver
use core::time::Duration;
use pio::InstructionOperands;
use crate::clocks;
use crate::gpio::Level;
use crate::pio::{Common, Config, Direction, Instance, LoadedProgram, Pin, PioPin, StateMachine};
/// This converts the duration provided into the number of cycles the PIO needs to run to make it take the same time
fn to_pio_cycles(duration: Duration) -> u32 {
(clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow
}
/// This struct represents a PWM program loaded into pio instruction memory.
pub struct PioPwmProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioPwmProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio::pio_asm!(
".side_set 1 opt"
"pull noblock side 0"
"mov x, osr"
"mov y, isr"
"countloop:"
"jmp x!=y noset"
"jmp skip side 1"
"noset:"
"nop"
"skip:"
"jmp y-- countloop"
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// Pio backed PWM output
pub struct PioPwm<'d, T: Instance, const SM: usize> {
sm: StateMachine<'d, T, SM>,
pin: Pin<'d, T>,
}
impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> {
/// Configure a state machine as a PWM output
pub fn new(
pio: &mut Common<'d, T>,
mut sm: StateMachine<'d, T, SM>,
pin: impl PioPin,
program: &PioPwmProgram<'d, T>,
) -> Self {
let pin = pio.make_pio_pin(pin);
sm.set_pins(Level::High, &[&pin]);
sm.set_pin_dirs(Direction::Out, &[&pin]);
let mut cfg = Config::default();
cfg.use_program(&program.prg, &[&pin]);
sm.set_config(&cfg);
Self { sm, pin }
}
/// Enable's the PIO program, continuing the wave generation from the PIO program.
pub fn start(&mut self) {
self.sm.set_enable(true);
}
/// Stops the PIO program, ceasing all signals from the PIN that were generated via PIO.
pub fn stop(&mut self) {
self.sm.set_enable(false);
}
/// Sets the pwm period, which is the length of time for each pio wave until reset.
pub fn set_period(&mut self, duration: Duration) {
let is_enabled = self.sm.is_enabled();
while !self.sm.tx().empty() {} // Make sure that the queue is empty
self.sm.set_enable(false);
self.sm.tx().push(to_pio_cycles(duration));
unsafe {
self.sm.exec_instr(
InstructionOperands::PULL {
if_empty: false,
block: false,
}
.encode(),
);
self.sm.exec_instr(
InstructionOperands::OUT {
destination: ::pio::OutDestination::ISR,
bit_count: 32,
}
.encode(),
);
};
if is_enabled {
self.sm.set_enable(true) // Enable if previously enabled
}
}
/// Set the number of pio cycles to set the wave on high to.
pub fn set_level(&mut self, level: u32) {
self.sm.tx().push(level);
}
/// Set the pulse width high time
pub fn write(&mut self, duration: Duration) {
self.set_level(to_pio_cycles(duration));
}
/// Return the state machine and pin.
pub fn release(self) -> (StateMachine<'d, T, SM>, Pin<'d, T>) {
(self.sm, self.pin)
}
}

View File

@@ -0,0 +1,75 @@
//! PIO backed quadrature encoder
use fixed::traits::ToFixed;
use crate::gpio::Pull;
use crate::pio::{
Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine,
};
/// This struct represents an Encoder program loaded into pio instruction memory.
pub struct PioEncoderProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioEncoderProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// Pio Backed quadrature encoder reader
pub struct PioEncoder<'d, T: Instance, const SM: usize> {
sm: StateMachine<'d, T, SM>,
}
impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> {
/// Configure a state machine with the loaded [PioEncoderProgram]
pub fn new(
pio: &mut Common<'d, T>,
mut sm: StateMachine<'d, T, SM>,
pin_a: impl PioPin,
pin_b: impl PioPin,
program: &PioEncoderProgram<'d, T>,
) -> Self {
let mut pin_a = pio.make_pio_pin(pin_a);
let mut pin_b = pio.make_pio_pin(pin_b);
pin_a.set_pull(Pull::Up);
pin_b.set_pull(Pull::Up);
sm.set_pin_dirs(PioDirection::In, &[&pin_a, &pin_b]);
let mut cfg = Config::default();
cfg.set_in_pins(&[&pin_a, &pin_b]);
cfg.fifo_join = FifoJoin::RxOnly;
cfg.shift_in.direction = ShiftDirection::Left;
cfg.clock_divider = 10_000.to_fixed();
cfg.use_program(&program.prg, &[]);
sm.set_config(&cfg);
sm.set_enable(true);
Self { sm }
}
/// Read a single count from the encoder
pub async fn read(&mut self) -> Direction {
loop {
match self.sm.rx().wait_pull().await {
0 => return Direction::CounterClockwise,
1 => return Direction::Clockwise,
_ => {}
}
}
}
}
/// Encoder Count Direction
pub enum Direction {
/// Encoder turned clockwise
Clockwise,
/// Encoder turned counter clockwise
CounterClockwise,
}

View File

@@ -0,0 +1,147 @@
//! Pio Stepper Driver for 5-wire steppers
use core::mem::{self, MaybeUninit};
use fixed::traits::ToFixed;
use fixed::types::extra::U8;
use fixed::FixedU32;
use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine};
/// This struct represents a Stepper driver program loaded into pio instruction memory.
pub struct PioStepperProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioStepperProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio::pio_asm!(
"pull block",
"mov x, osr",
"pull block",
"mov y, osr",
"jmp !x end",
"loop:",
"jmp !osre step",
"mov osr, y",
"step:",
"out pins, 4 [31]"
"jmp x-- loop",
"end:",
"irq 0 rel"
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// Pio backed Stepper driver
pub struct PioStepper<'d, T: Instance, const SM: usize> {
irq: Irq<'d, T, SM>,
sm: StateMachine<'d, T, SM>,
}
impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> {
/// Configure a state machine to drive a stepper
pub fn new(
pio: &mut Common<'d, T>,
mut sm: StateMachine<'d, T, SM>,
irq: Irq<'d, T, SM>,
pin0: impl PioPin,
pin1: impl PioPin,
pin2: impl PioPin,
pin3: impl PioPin,
program: &PioStepperProgram<'d, T>,
) -> Self {
let pin0 = pio.make_pio_pin(pin0);
let pin1 = pio.make_pio_pin(pin1);
let pin2 = pio.make_pio_pin(pin2);
let pin3 = pio.make_pio_pin(pin3);
sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]);
let mut cfg = Config::default();
cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]);
cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed();
cfg.use_program(&program.prg, &[]);
sm.set_config(&cfg);
sm.set_enable(true);
Self { irq, sm }
}
/// Set pulse frequency
pub fn set_frequency(&mut self, freq: u32) {
let clock_divider: FixedU32<U8> = (125_000_000 / (freq * 136)).to_fixed();
assert!(clock_divider <= 65536, "clkdiv must be <= 65536");
assert!(clock_divider >= 1, "clkdiv must be >= 1");
self.sm.set_clock_divider(clock_divider);
self.sm.clkdiv_restart();
}
/// Full step, one phase
pub async fn step(&mut self, steps: i32) {
if steps > 0 {
self.run(steps, 0b1000_0100_0010_0001_1000_0100_0010_0001).await
} else {
self.run(-steps, 0b0001_0010_0100_1000_0001_0010_0100_1000).await
}
}
/// Full step, two phase
pub async fn step2(&mut self, steps: i32) {
if steps > 0 {
self.run(steps, 0b1001_1100_0110_0011_1001_1100_0110_0011).await
} else {
self.run(-steps, 0b0011_0110_1100_1001_0011_0110_1100_1001).await
}
}
/// Half step
pub async fn step_half(&mut self, steps: i32) {
if steps > 0 {
self.run(steps, 0b1001_1000_1100_0100_0110_0010_0011_0001).await
} else {
self.run(-steps, 0b0001_0011_0010_0110_0100_1100_1000_1001).await
}
}
async fn run(&mut self, steps: i32, pattern: u32) {
self.sm.tx().wait_push(steps as u32).await;
self.sm.tx().wait_push(pattern).await;
let drop = OnDrop::new(|| {
self.sm.clear_fifos();
unsafe {
self.sm.exec_instr(
pio::InstructionOperands::JMP {
address: 0,
condition: pio::JmpCondition::Always,
}
.encode(),
);
}
});
self.irq.wait().await;
drop.defuse();
}
}
struct OnDrop<F: FnOnce()> {
f: MaybeUninit<F>,
}
impl<F: FnOnce()> OnDrop<F> {
pub fn new(f: F) -> Self {
Self { f: MaybeUninit::new(f) }
}
pub fn defuse(self) {
mem::forget(self)
}
}
impl<F: FnOnce()> Drop for OnDrop<F> {
fn drop(&mut self) {
unsafe { self.f.as_ptr().read()() }
}
}

View File

@@ -0,0 +1,185 @@
//! Pio backed uart drivers
use core::convert::Infallible;
use embedded_io_async::{ErrorType, Read, Write};
use fixed::traits::ToFixed;
use crate::clocks::clk_sys_freq;
use crate::gpio::Level;
use crate::pio::{
Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine,
};
/// This struct represents a uart tx program loaded into pio instruction memory.
pub struct PioUartTxProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioUartTxProgram<'a, PIO> {
/// Load the uart tx program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio::pio_asm!(
r#"
.side_set 1 opt
; An 8n1 UART transmit program.
; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin.
pull side 1 [7] ; Assert stop bit, or stall with line in idle state
set x, 7 side 0 [7] ; Preload bit counter, assert start bit for 8 clocks
bitloop: ; This loop will run 8 times (8n1 UART)
out pins, 1 ; Shift 1 bit from OSR to the first OUT pin
jmp x-- bitloop [6] ; Each loop iteration is 8 cycles.
"#
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// PIO backed Uart transmitter
pub struct PioUartTx<'a, PIO: Instance, const SM: usize> {
sm_tx: StateMachine<'a, PIO, SM>,
}
impl<'a, PIO: Instance, const SM: usize> PioUartTx<'a, PIO, SM> {
/// Configure a pio state machine to use the loaded tx program.
pub fn new(
baud: u32,
common: &mut Common<'a, PIO>,
mut sm_tx: StateMachine<'a, PIO, SM>,
tx_pin: impl PioPin,
program: &PioUartTxProgram<'a, PIO>,
) -> Self {
let tx_pin = common.make_pio_pin(tx_pin);
sm_tx.set_pins(Level::High, &[&tx_pin]);
sm_tx.set_pin_dirs(PioDirection::Out, &[&tx_pin]);
let mut cfg = Config::default();
cfg.set_out_pins(&[&tx_pin]);
cfg.use_program(&program.prg, &[&tx_pin]);
cfg.shift_out.auto_fill = false;
cfg.shift_out.direction = ShiftDirection::Right;
cfg.fifo_join = FifoJoin::TxOnly;
cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed();
sm_tx.set_config(&cfg);
sm_tx.set_enable(true);
Self { sm_tx }
}
/// Write a single u8
pub async fn write_u8(&mut self, data: u8) {
self.sm_tx.tx().wait_push(data as u32).await;
}
}
impl<PIO: Instance, const SM: usize> ErrorType for PioUartTx<'_, PIO, SM> {
type Error = Infallible;
}
impl<PIO: Instance, const SM: usize> Write for PioUartTx<'_, PIO, SM> {
async fn write(&mut self, buf: &[u8]) -> Result<usize, Infallible> {
for byte in buf {
self.write_u8(*byte).await;
}
Ok(buf.len())
}
}
/// This struct represents a Uart Rx program loaded into pio instruction memory.
pub struct PioUartRxProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioUartRxProgram<'a, PIO> {
/// Load the uart rx program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio::pio_asm!(
r#"
; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and
; break conditions more gracefully.
; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX.
start:
wait 0 pin 0 ; Stall until start bit is asserted
set x, 7 [10] ; Preload bit counter, then delay until halfway through
rx_bitloop: ; the first data bit (12 cycles incl wait, set).
in pins, 1 ; Shift data bit into ISR
jmp x-- rx_bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles
jmp pin good_rx_stop ; Check stop bit (should be high)
irq 4 rel ; Either a framing error or a break. Set a sticky flag,
wait 1 pin 0 ; and wait for line to return to idle state.
jmp start ; Don't push data if we didn't see good framing.
good_rx_stop: ; No delay before returning to start; a little slack is
in null 24
push ; important in case the TX clock is slightly too fast.
"#
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// PIO backed Uart reciever
pub struct PioUartRx<'a, PIO: Instance, const SM: usize> {
sm_rx: StateMachine<'a, PIO, SM>,
}
impl<'a, PIO: Instance, const SM: usize> PioUartRx<'a, PIO, SM> {
/// Configure a pio state machine to use the loaded rx program.
pub fn new(
baud: u32,
common: &mut Common<'a, PIO>,
mut sm_rx: StateMachine<'a, PIO, SM>,
rx_pin: impl PioPin,
program: &PioUartRxProgram<'a, PIO>,
) -> Self {
let mut cfg = Config::default();
cfg.use_program(&program.prg, &[]);
let rx_pin = common.make_pio_pin(rx_pin);
sm_rx.set_pins(Level::High, &[&rx_pin]);
cfg.set_in_pins(&[&rx_pin]);
cfg.set_jmp_pin(&rx_pin);
sm_rx.set_pin_dirs(PioDirection::In, &[&rx_pin]);
cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed();
cfg.shift_in.auto_fill = false;
cfg.shift_in.direction = ShiftDirection::Right;
cfg.shift_in.threshold = 32;
cfg.fifo_join = FifoJoin::RxOnly;
sm_rx.set_config(&cfg);
sm_rx.set_enable(true);
Self { sm_rx }
}
/// Wait for a single u8
pub async fn read_u8(&mut self) -> u8 {
self.sm_rx.rx().wait_pull().await as u8
}
}
impl<PIO: Instance, const SM: usize> ErrorType for PioUartRx<'_, PIO, SM> {
type Error = Infallible;
}
impl<PIO: Instance, const SM: usize> Read for PioUartRx<'_, PIO, SM> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Infallible> {
let mut i = 0;
while i < buf.len() {
buf[i] = self.read_u8().await;
i += 1;
}
Ok(i)
}
}

View File

@@ -0,0 +1,118 @@
//! [ws2812](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf)
use embassy_time::Timer;
use fixed::types::U24F8;
use smart_leds::RGB8;
use crate::clocks::clk_sys_freq;
use crate::dma::{AnyChannel, Channel};
use crate::pio::{
Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine,
};
use crate::{into_ref, Peripheral, PeripheralRef};
const T1: u8 = 2; // start bit
const T2: u8 = 5; // data bit
const T3: u8 = 3; // stop bit
const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32;
/// This struct represents a ws2812 program loaded into pio instruction memory.
pub struct PioWs2812Program<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioWs2812Program<'a, PIO> {
/// Load the ws2812 program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let side_set = pio::SideSet::new(false, 1, false);
let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set);
let mut wrap_target = a.label();
let mut wrap_source = a.label();
let mut do_zero = a.label();
a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0);
a.bind(&mut wrap_target);
// Do stop bit
a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0);
// Do start bit
a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1);
// Do data bit = 1
a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1);
a.bind(&mut do_zero);
// Do data bit = 0
a.nop_with_delay_and_side_set(T2 - 1, 0);
a.bind(&mut wrap_source);
let prg = a.assemble_with_wrap(wrap_source, wrap_target);
let prg = common.load_program(&prg);
Self { prg }
}
}
/// Pio backed ws2812 driver
/// Const N is the number of ws2812 leds attached to this pin
pub struct PioWs2812<'d, P: Instance, const S: usize, const N: usize> {
dma: PeripheralRef<'d, AnyChannel>,
sm: StateMachine<'d, P, S>,
}
impl<'d, P: Instance, const S: usize, const N: usize> PioWs2812<'d, P, S, N> {
/// Configure a pio state machine to use the loaded ws2812 program.
pub fn new(
pio: &mut Common<'d, P>,
mut sm: StateMachine<'d, P, S>,
dma: impl Peripheral<P = impl Channel> + 'd,
pin: impl PioPin,
program: &PioWs2812Program<'d, P>,
) -> Self {
into_ref!(dma);
// Setup sm0
let mut cfg = Config::default();
// Pin config
let out_pin = pio.make_pio_pin(pin);
cfg.set_out_pins(&[&out_pin]);
cfg.set_set_pins(&[&out_pin]);
cfg.use_program(&program.prg, &[&out_pin]);
// Clock config, measured in kHz to avoid overflows
let clock_freq = U24F8::from_num(clk_sys_freq() / 1000);
let ws2812_freq = U24F8::from_num(800);
let bit_freq = ws2812_freq * CYCLES_PER_BIT;
cfg.clock_divider = clock_freq / bit_freq;
// FIFO config
cfg.fifo_join = FifoJoin::TxOnly;
cfg.shift_out = ShiftConfig {
auto_fill: true,
threshold: 24,
direction: ShiftDirection::Left,
};
sm.set_config(&cfg);
sm.set_enable(true);
Self {
dma: dma.map_into(),
sm,
}
}
/// Write a buffer of [smart_leds::RGB8] to the ws2812 string
pub async fn write(&mut self, colors: &[RGB8; N]) {
// Precompute the word bytes from the colors
let mut words = [0u32; N];
for i in 0..N {
let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8);
words[i] = word;
}
// DMA transfer
self.sm.tx().dma_push(self.dma.reborrow(), &words, false).await;
Timer::after_micros(55).await;
}
}

611
embassy-rp/src/pwm.rs Normal file
View File

@@ -0,0 +1,611 @@
//! Pulse Width Modulation (PWM)
use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
pub use embedded_hal_1::pwm::SetDutyCycle;
use embedded_hal_1::pwm::{Error, ErrorKind, ErrorType};
use fixed::traits::ToFixed;
use fixed::FixedU16;
use pac::pwm::regs::{ChDiv, Intr};
use pac::pwm::vals::Divmode;
use crate::gpio::{AnyPin, Pin as GpioPin, Pull, SealedPin as _};
use crate::{pac, peripherals, RegExt};
/// The configuration of a PWM slice.
/// Note the period in clock cycles of a slice can be computed as:
/// `(top + 1) * (phase_correct ? 1 : 2) * divider`
#[non_exhaustive]
#[derive(Clone)]
pub struct Config {
/// Inverts the PWM output signal on channel A.
pub invert_a: bool,
/// Inverts the PWM output signal on channel B.
pub invert_b: bool,
/// Enables phase-correct mode for PWM operation.
/// In phase-correct mode, the PWM signal is generated in such a way that
/// the pulse is always centered regardless of the duty cycle.
/// The output frequency is halved when phase-correct mode is enabled.
pub phase_correct: bool,
/// Enables the PWM slice, allowing it to generate an output.
pub enable: bool,
/// A fractional clock divider, represented as a fixed-point number with
/// 8 integer bits and 4 fractional bits. It allows precise control over
/// the PWM output frequency by gating the PWM counter increment.
/// A higher value will result in a slower output frequency.
pub divider: fixed::FixedU16<fixed::types::extra::U4>,
/// The output on channel A goes high when `compare_a` is higher than the
/// counter. A compare of 0 will produce an always low output, while a
/// compare of `top + 1` will produce an always high output.
pub compare_a: u16,
/// The output on channel B goes high when `compare_b` is higher than the
/// counter. A compare of 0 will produce an always low output, while a
/// compare of `top + 1` will produce an always high output.
pub compare_b: u16,
/// The point at which the counter wraps, representing the maximum possible
/// period. The counter will either wrap to 0 or reverse depending on the
/// setting of `phase_correct`.
pub top: u16,
}
impl Default for Config {
fn default() -> Self {
Self {
invert_a: false,
invert_b: false,
phase_correct: false,
enable: true, // differs from reset value
divider: 1.to_fixed(),
compare_a: 0,
compare_b: 0,
top: 0xffff,
}
}
}
/// PWM input mode.
pub enum InputMode {
/// Level mode.
Level,
/// Rising edge mode.
RisingEdge,
/// Falling edge mode.
FallingEdge,
}
impl From<InputMode> for Divmode {
fn from(value: InputMode) -> Self {
match value {
InputMode::Level => Divmode::LEVEL,
InputMode::RisingEdge => Divmode::RISE,
InputMode::FallingEdge => Divmode::FALL,
}
}
}
/// PWM error.
#[derive(Debug)]
pub enum PwmError {
/// Invalid Duty Cycle.
InvalidDutyCycle,
}
impl Error for PwmError {
fn kind(&self) -> ErrorKind {
match self {
PwmError::InvalidDutyCycle => ErrorKind::Other,
}
}
}
/// PWM driver.
pub struct Pwm<'d> {
pin_a: Option<PeripheralRef<'d, AnyPin>>,
pin_b: Option<PeripheralRef<'d, AnyPin>>,
slice: usize,
}
impl<'d> ErrorType for Pwm<'d> {
type Error = PwmError;
}
impl<'d> SetDutyCycle for Pwm<'d> {
fn max_duty_cycle(&self) -> u16 {
pac::PWM.ch(self.slice).top().read().top()
}
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
let max_duty = self.max_duty_cycle();
if duty > max_duty {
return Err(PwmError::InvalidDutyCycle);
}
let p = pac::PWM.ch(self.slice);
p.cc().modify(|w| {
w.set_a(duty);
w.set_b(duty);
});
Ok(())
}
}
impl<'d> Pwm<'d> {
fn new_inner(
slice: usize,
a: Option<PeripheralRef<'d, AnyPin>>,
b: Option<PeripheralRef<'d, AnyPin>>,
b_pull: Pull,
config: Config,
divmode: Divmode,
) -> Self {
let p = pac::PWM.ch(slice);
p.csr().modify(|w| {
w.set_divmode(divmode);
w.set_en(false);
});
p.ctr().write(|w| w.0 = 0);
Self::configure(p, &config);
if let Some(pin) = &a {
pin.gpio().ctrl().write(|w| w.set_funcsel(4));
#[cfg(feature = "_rp235x")]
pin.pad_ctrl().modify(|w| {
w.set_iso(false);
});
}
if let Some(pin) = &b {
pin.gpio().ctrl().write(|w| w.set_funcsel(4));
pin.pad_ctrl().modify(|w| {
#[cfg(feature = "_rp235x")]
w.set_iso(false);
w.set_pue(b_pull == Pull::Up);
w.set_pde(b_pull == Pull::Down);
});
}
Self {
// inner: p.into(),
pin_a: a,
pin_b: b,
slice,
}
}
/// Create PWM driver without any configured pins.
#[inline]
pub fn new_free<T: Slice>(slice: impl Peripheral<P = T> + 'd, config: Config) -> Self {
into_ref!(slice);
Self::new_inner(slice.number(), None, None, Pull::None, config, Divmode::DIV)
}
/// Create PWM driver with a single 'a' pin as output.
#[inline]
pub fn new_output_a<T: Slice>(
slice: impl Peripheral<P = T> + 'd,
a: impl Peripheral<P = impl ChannelAPin<T>> + 'd,
config: Config,
) -> Self {
into_ref!(slice, a);
Self::new_inner(
slice.number(),
Some(a.map_into()),
None,
Pull::None,
config,
Divmode::DIV,
)
}
/// Create PWM driver with a single 'b' pin as output.
#[inline]
pub fn new_output_b<T: Slice>(
slice: impl Peripheral<P = T> + 'd,
b: impl Peripheral<P = impl ChannelBPin<T>> + 'd,
config: Config,
) -> Self {
into_ref!(slice, b);
Self::new_inner(
slice.number(),
None,
Some(b.map_into()),
Pull::None,
config,
Divmode::DIV,
)
}
/// Create PWM driver with a 'a' and 'b' pins as output.
#[inline]
pub fn new_output_ab<T: Slice>(
slice: impl Peripheral<P = T> + 'd,
a: impl Peripheral<P = impl ChannelAPin<T>> + 'd,
b: impl Peripheral<P = impl ChannelBPin<T>> + 'd,
config: Config,
) -> Self {
into_ref!(slice, a, b);
Self::new_inner(
slice.number(),
Some(a.map_into()),
Some(b.map_into()),
Pull::None,
config,
Divmode::DIV,
)
}
/// Create PWM driver with a single 'b' as input pin.
#[inline]
pub fn new_input<T: Slice>(
slice: impl Peripheral<P = T> + 'd,
b: impl Peripheral<P = impl ChannelBPin<T>> + 'd,
b_pull: Pull,
mode: InputMode,
config: Config,
) -> Self {
into_ref!(slice, b);
Self::new_inner(slice.number(), None, Some(b.map_into()), b_pull, config, mode.into())
}
/// Create PWM driver with a 'a' and 'b' pins in the desired input mode.
#[inline]
pub fn new_output_input<T: Slice>(
slice: impl Peripheral<P = T> + 'd,
a: impl Peripheral<P = impl ChannelAPin<T>> + 'd,
b: impl Peripheral<P = impl ChannelBPin<T>> + 'd,
b_pull: Pull,
mode: InputMode,
config: Config,
) -> Self {
into_ref!(slice, a, b);
Self::new_inner(
slice.number(),
Some(a.map_into()),
Some(b.map_into()),
b_pull,
config,
mode.into(),
)
}
/// Set the PWM config.
pub fn set_config(&mut self, config: &Config) {
Self::configure(pac::PWM.ch(self.slice), config);
}
fn configure(p: pac::pwm::Channel, config: &Config) {
if config.divider > FixedU16::<fixed::types::extra::U4>::from_bits(0xFFF) {
panic!("Requested divider is too large");
}
p.div().write_value(ChDiv(config.divider.to_bits() as u32));
p.cc().write(|w| {
w.set_a(config.compare_a);
w.set_b(config.compare_b);
});
p.top().write(|w| w.set_top(config.top));
p.csr().modify(|w| {
w.set_a_inv(config.invert_a);
w.set_b_inv(config.invert_b);
w.set_ph_correct(config.phase_correct);
w.set_en(config.enable);
});
}
/// Advances a slice's output phase by one count while it is running
/// by inserting a pulse into the clock enable. The counter
/// will not count faster than once per cycle.
#[inline]
pub fn phase_advance(&mut self) {
let p = pac::PWM.ch(self.slice);
p.csr().write_set(|w| w.set_ph_adv(true));
while p.csr().read().ph_adv() {}
}
/// Retards a slice's output phase by one count while it is running
/// by deleting a pulse from the clock enable. The counter will not
/// count backward when clock enable is permanently low.
#[inline]
pub fn phase_retard(&mut self) {
let p = pac::PWM.ch(self.slice);
p.csr().write_set(|w| w.set_ph_ret(true));
while p.csr().read().ph_ret() {}
}
/// Read PWM counter.
#[inline]
pub fn counter(&self) -> u16 {
pac::PWM.ch(self.slice).ctr().read().ctr()
}
/// Write PWM counter.
#[inline]
pub fn set_counter(&self, ctr: u16) {
pac::PWM.ch(self.slice).ctr().write(|w| w.set_ctr(ctr))
}
/// Wait for channel interrupt.
#[inline]
pub fn wait_for_wrap(&mut self) {
while !self.wrapped() {}
self.clear_wrapped();
}
/// Check if interrupt for channel is set.
#[inline]
pub fn wrapped(&mut self) -> bool {
pac::PWM.intr().read().0 & self.bit() != 0
}
#[inline]
/// Clear interrupt flag.
pub fn clear_wrapped(&mut self) {
pac::PWM.intr().write_value(Intr(self.bit() as _));
}
#[inline]
fn bit(&self) -> u32 {
1 << self.slice as usize
}
/// Splits the PWM driver into separate `PwmOutput` instances for channels A and B.
#[inline]
pub fn split(mut self) -> (Option<PwmOutput<'d>>, Option<PwmOutput<'d>>) {
(
self.pin_a
.take()
.map(|pin| PwmOutput::new(PwmChannelPin::A(pin), self.slice.clone(), true)),
self.pin_b
.take()
.map(|pin| PwmOutput::new(PwmChannelPin::B(pin), self.slice.clone(), true)),
)
}
/// Splits the PWM driver by reference to allow for separate duty cycle control
/// of each channel (A and B) without taking ownership of the PWM instance.
#[inline]
pub fn split_by_ref(&mut self) -> (Option<PwmOutput<'_>>, Option<PwmOutput<'_>>) {
(
self.pin_a
.as_mut()
.map(|pin| PwmOutput::new(PwmChannelPin::A(pin.reborrow()), self.slice.clone(), false)),
self.pin_b
.as_mut()
.map(|pin| PwmOutput::new(PwmChannelPin::B(pin.reborrow()), self.slice.clone(), false)),
)
}
}
enum PwmChannelPin<'d> {
A(PeripheralRef<'d, AnyPin>),
B(PeripheralRef<'d, AnyPin>),
}
/// Single channel of Pwm driver.
pub struct PwmOutput<'d> {
//pin that can be ether ChannelAPin or ChannelBPin
channel_pin: PwmChannelPin<'d>,
slice: usize,
is_owned: bool,
}
impl<'d> PwmOutput<'d> {
fn new(channel_pin: PwmChannelPin<'d>, slice: usize, is_owned: bool) -> Self {
Self {
channel_pin,
slice,
is_owned,
}
}
}
impl<'d> Drop for PwmOutput<'d> {
fn drop(&mut self) {
if self.is_owned {
let p = pac::PWM.ch(self.slice);
match &self.channel_pin {
PwmChannelPin::A(pin) => {
p.cc().modify(|w| {
w.set_a(0);
});
pin.gpio().ctrl().write(|w| w.set_funcsel(31));
//Enable pin PULL-DOWN
pin.pad_ctrl().modify(|w| {
w.set_pde(true);
});
}
PwmChannelPin::B(pin) => {
p.cc().modify(|w| {
w.set_b(0);
});
pin.gpio().ctrl().write(|w| w.set_funcsel(31));
//Enable pin PULL-DOWN
pin.pad_ctrl().modify(|w| {
w.set_pde(true);
});
}
}
}
}
}
impl<'d> ErrorType for PwmOutput<'d> {
type Error = PwmError;
}
impl<'d> SetDutyCycle for PwmOutput<'d> {
fn max_duty_cycle(&self) -> u16 {
pac::PWM.ch(self.slice).top().read().top()
}
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
let max_duty = self.max_duty_cycle();
if duty > max_duty {
return Err(PwmError::InvalidDutyCycle);
}
let p = pac::PWM.ch(self.slice);
match self.channel_pin {
PwmChannelPin::A(_) => {
p.cc().modify(|w| {
w.set_a(duty);
});
}
PwmChannelPin::B(_) => {
p.cc().modify(|w| {
w.set_b(duty);
});
}
}
Ok(())
}
}
/// Batch representation of PWM slices.
pub struct PwmBatch(u32);
impl PwmBatch {
#[inline]
/// Enable a PWM slice in this batch.
pub fn enable(&mut self, pwm: &Pwm<'_>) {
self.0 |= pwm.bit();
}
#[inline]
/// Enable slices in this batch in a PWM.
pub fn set_enabled(enabled: bool, batch: impl FnOnce(&mut PwmBatch)) {
let mut en = PwmBatch(0);
batch(&mut en);
if enabled {
pac::PWM.en().write_set(|w| w.0 = en.0);
} else {
pac::PWM.en().write_clear(|w| w.0 = en.0);
}
}
}
impl<'d> Drop for Pwm<'d> {
fn drop(&mut self) {
pac::PWM.ch(self.slice).csr().write_clear(|w| w.set_en(false));
if let Some(pin) = &self.pin_a {
pin.gpio().ctrl().write(|w| w.set_funcsel(31));
}
if let Some(pin) = &self.pin_b {
pin.gpio().ctrl().write(|w| w.set_funcsel(31));
}
}
}
trait SealedSlice {}
/// PWM Slice.
#[allow(private_bounds)]
pub trait Slice: Peripheral<P = Self> + SealedSlice + Sized + 'static {
/// Slice number.
fn number(&self) -> usize;
}
macro_rules! slice {
($name:ident, $num:expr) => {
impl SealedSlice for peripherals::$name {}
impl Slice for peripherals::$name {
fn number(&self) -> usize {
$num
}
}
};
}
slice!(PWM_SLICE0, 0);
slice!(PWM_SLICE1, 1);
slice!(PWM_SLICE2, 2);
slice!(PWM_SLICE3, 3);
slice!(PWM_SLICE4, 4);
slice!(PWM_SLICE5, 5);
slice!(PWM_SLICE6, 6);
slice!(PWM_SLICE7, 7);
#[cfg(feature = "_rp235x")]
slice!(PWM_SLICE8, 8);
#[cfg(feature = "_rp235x")]
slice!(PWM_SLICE9, 9);
#[cfg(feature = "_rp235x")]
slice!(PWM_SLICE10, 10);
#[cfg(feature = "_rp235x")]
slice!(PWM_SLICE11, 11);
/// PWM Channel A.
pub trait ChannelAPin<T: Slice>: GpioPin {}
/// PWM Channel B.
pub trait ChannelBPin<T: Slice>: GpioPin {}
macro_rules! impl_pin {
($pin:ident, $channel:ident, $kind:ident) => {
impl $kind<peripherals::$channel> for peripherals::$pin {}
};
}
impl_pin!(PIN_0, PWM_SLICE0, ChannelAPin);
impl_pin!(PIN_1, PWM_SLICE0, ChannelBPin);
impl_pin!(PIN_2, PWM_SLICE1, ChannelAPin);
impl_pin!(PIN_3, PWM_SLICE1, ChannelBPin);
impl_pin!(PIN_4, PWM_SLICE2, ChannelAPin);
impl_pin!(PIN_5, PWM_SLICE2, ChannelBPin);
impl_pin!(PIN_6, PWM_SLICE3, ChannelAPin);
impl_pin!(PIN_7, PWM_SLICE3, ChannelBPin);
impl_pin!(PIN_8, PWM_SLICE4, ChannelAPin);
impl_pin!(PIN_9, PWM_SLICE4, ChannelBPin);
impl_pin!(PIN_10, PWM_SLICE5, ChannelAPin);
impl_pin!(PIN_11, PWM_SLICE5, ChannelBPin);
impl_pin!(PIN_12, PWM_SLICE6, ChannelAPin);
impl_pin!(PIN_13, PWM_SLICE6, ChannelBPin);
impl_pin!(PIN_14, PWM_SLICE7, ChannelAPin);
impl_pin!(PIN_15, PWM_SLICE7, ChannelBPin);
impl_pin!(PIN_16, PWM_SLICE0, ChannelAPin);
impl_pin!(PIN_17, PWM_SLICE0, ChannelBPin);
impl_pin!(PIN_18, PWM_SLICE1, ChannelAPin);
impl_pin!(PIN_19, PWM_SLICE1, ChannelBPin);
impl_pin!(PIN_20, PWM_SLICE2, ChannelAPin);
impl_pin!(PIN_21, PWM_SLICE2, ChannelBPin);
impl_pin!(PIN_22, PWM_SLICE3, ChannelAPin);
impl_pin!(PIN_23, PWM_SLICE3, ChannelBPin);
impl_pin!(PIN_24, PWM_SLICE4, ChannelAPin);
impl_pin!(PIN_25, PWM_SLICE4, ChannelBPin);
impl_pin!(PIN_26, PWM_SLICE5, ChannelAPin);
impl_pin!(PIN_27, PWM_SLICE5, ChannelBPin);
impl_pin!(PIN_28, PWM_SLICE6, ChannelAPin);
impl_pin!(PIN_29, PWM_SLICE6, ChannelBPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_30, PWM_SLICE7, ChannelAPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_31, PWM_SLICE7, ChannelBPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_32, PWM_SLICE8, ChannelAPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_33, PWM_SLICE8, ChannelBPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_34, PWM_SLICE9, ChannelAPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_35, PWM_SLICE9, ChannelBPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_36, PWM_SLICE10, ChannelAPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_37, PWM_SLICE10, ChannelBPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_38, PWM_SLICE11, ChannelAPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_39, PWM_SLICE11, ChannelBPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_40, PWM_SLICE8, ChannelAPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_41, PWM_SLICE8, ChannelBPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_42, PWM_SLICE9, ChannelAPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_43, PWM_SLICE9, ChannelBPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_44, PWM_SLICE10, ChannelAPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_45, PWM_SLICE10, ChannelBPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_46, PWM_SLICE11, ChannelAPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_47, PWM_SLICE11, ChannelBPin);

View File

@@ -0,0 +1,66 @@
use pio::{Program, SideSet, Wrap};
pub struct CodeIterator<'a, I>
where
I: Iterator<Item = &'a u16>,
{
iter: I,
offset: u8,
}
impl<'a, I: Iterator<Item = &'a u16>> CodeIterator<'a, I> {
pub fn new(iter: I, offset: u8) -> CodeIterator<'a, I> {
CodeIterator { iter, offset }
}
}
impl<'a, I> Iterator for CodeIterator<'a, I>
where
I: Iterator<Item = &'a u16>,
{
type Item = u16;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|&instr| {
if instr & 0b1110_0000_0000_0000 == 0 {
// this is a JMP instruction -> add offset to address
let address = (instr & 0b1_1111) as u8;
let address = address.wrapping_add(self.offset) % 32;
instr & (!0b11111) | address as u16
} else {
instr
}
})
}
}
pub struct RelocatedProgram<'a, const PROGRAM_SIZE: usize> {
program: &'a Program<PROGRAM_SIZE>,
origin: u8,
}
impl<'a, const PROGRAM_SIZE: usize> RelocatedProgram<'a, PROGRAM_SIZE> {
pub fn new_with_origin(program: &Program<PROGRAM_SIZE>, origin: u8) -> RelocatedProgram<PROGRAM_SIZE> {
RelocatedProgram { program, origin }
}
pub fn code(&'a self) -> CodeIterator<'a, core::slice::Iter<'a, u16>> {
CodeIterator::new(self.program.code.iter(), self.origin)
}
pub fn wrap(&self) -> Wrap {
let wrap = self.program.wrap;
let origin = self.origin;
Wrap {
source: wrap.source.wrapping_add(origin) % 32,
target: wrap.target.wrapping_add(origin) % 32,
}
}
pub fn side_set(&self) -> SideSet {
self.program.side_set
}
pub fn origin(&self) -> u8 {
self.origin
}
}

15
embassy-rp/src/reset.rs Normal file
View File

@@ -0,0 +1,15 @@
pub use pac::resets::regs::Peripherals;
use crate::pac;
pub const ALL_PERIPHERALS: Peripherals = Peripherals(0x01ff_ffff);
pub(crate) fn reset(peris: Peripherals) {
pac::RESETS.reset().write_value(peris);
}
pub(crate) fn unreset_wait(peris: Peripherals) {
// TODO use the "atomic clear" register version
pac::RESETS.reset().modify(|v| *v = Peripherals(v.0 & !peris.0));
while ((!pac::RESETS.reset_done().read().0) & peris.0) != 0 {}
}

View File

@@ -0,0 +1,33 @@
#![cfg_attr(
feature = "rp2040",
doc = r"
//! Functions and data from the RPI Bootrom.
//!
//! From the [RP2040 datasheet](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf), Section 2.8.2.1:
//!
//! > The Bootrom contains a number of public functions that provide useful
//! > RP2040 functionality that might be needed in the absence of any other code
//! > on the device, as well as highly optimized versions of certain key
//! > functionality that would otherwise have to take up space in most user
//! > binaries.
"
)]
#![cfg_attr(
feature = "_rp235x",
doc = r"
//! Functions and data from the RPI Bootrom.
//!
//! From [Section 5.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the
//! RP2350 datasheet:
//!
//! > Whilst some ROM space is dedicated to the implementation of the boot
//! > sequence and USB/UART boot interfaces, the bootrom also contains public
//! > functions that provide useful RP2350 functionality that may be useful for
//! > any code or runtime running on the device
"
)]
#[cfg_attr(feature = "rp2040", path = "rp2040.rs")]
#[cfg_attr(feature = "_rp235x", path = "rp235x.rs")]
mod inner;
pub use inner::*;

View File

@@ -0,0 +1,756 @@
//! Functions and data from the RPI Bootrom.
//!
//! From the [RP2040 datasheet](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf), Section 2.8.2.1:
//!
//! > The Bootrom contains a number of public functions that provide useful
//! > RP2040 functionality that might be needed in the absence of any other code
//! > on the device, as well as highly optimized versions of certain key
//! > functionality that would otherwise have to take up space in most user
//! > binaries.
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/rom_data.rs
/// A bootrom function table code.
pub type RomFnTableCode = [u8; 2];
/// This function searches for (table)
type RomTableLookupFn<T> = unsafe extern "C" fn(*const u16, u32) -> T;
/// The following addresses are described at `2.8.2. Bootrom Contents`
/// Pointer to the lookup table function supplied by the rom.
const ROM_TABLE_LOOKUP_PTR: *const u16 = 0x0000_0018 as _;
/// Pointer to helper functions lookup table.
const FUNC_TABLE: *const u16 = 0x0000_0014 as _;
/// Pointer to the public data lookup table.
const DATA_TABLE: *const u16 = 0x0000_0016 as _;
/// Address of the version number of the ROM.
const VERSION_NUMBER: *const u8 = 0x0000_0013 as _;
/// Retrive rom content from a table using a code.
fn rom_table_lookup<T>(table: *const u16, tag: RomFnTableCode) -> T {
unsafe {
let rom_table_lookup_ptr: *const u32 = rom_hword_as_ptr(ROM_TABLE_LOOKUP_PTR);
let rom_table_lookup: RomTableLookupFn<T> = core::mem::transmute(rom_table_lookup_ptr);
rom_table_lookup(rom_hword_as_ptr(table) as *const u16, u16::from_le_bytes(tag) as u32)
}
}
/// To save space, the ROM likes to store memory pointers (which are 32-bit on
/// the Cortex-M0+) using only the bottom 16-bits. The assumption is that the
/// values they point at live in the first 64 KiB of ROM, and the ROM is mapped
/// to address `0x0000_0000` and so 16-bits are always sufficient.
///
/// This functions grabs a 16-bit value from ROM and expands it out to a full 32-bit pointer.
unsafe fn rom_hword_as_ptr(rom_address: *const u16) -> *const u32 {
let ptr: u16 = *rom_address;
ptr as *const u32
}
macro_rules! declare_rom_function {
(
$(#[$outer:meta])*
fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty
$lookup:block
) => {
declare_rom_function!{
__internal ,
$(#[$outer])*
fn $name( $($argname: $ty),* ) -> $ret
$lookup
}
};
(
$(#[$outer:meta])*
unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty
$lookup:block
) => {
declare_rom_function!{
__internal unsafe ,
$(#[$outer])*
fn $name( $($argname: $ty),* ) -> $ret
$lookup
}
};
(
__internal
$( $maybe_unsafe:ident )? ,
$(#[$outer:meta])*
fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty
$lookup:block
) => {
#[doc = r"Additional access for the `"]
#[doc = stringify!($name)]
#[doc = r"` ROM function."]
pub mod $name {
#[cfg(not(feature = "rom-func-cache"))]
pub(crate) fn outer_call() -> $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret {
let p: *const u32 = $lookup;
unsafe {
let func : $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret
= core::mem::transmute(p);
func
}
}
/// Retrieve a function pointer.
#[cfg(not(feature = "rom-func-cache"))]
pub fn ptr() -> $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret {
outer_call()
}
#[cfg(feature = "rom-func-cache")]
// unlike rp2040-hal we store a full word, containing the full function pointer.
// rp2040-hal saves two bytes by storing only the rom offset, at the cost of
// having to do an indirection and an atomic operation on every rom call.
static mut CACHE: $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret
= trampoline;
#[cfg(feature = "rom-func-cache")]
$( $maybe_unsafe )? extern "C" fn trampoline( $($argname: $ty),* ) -> $ret {
use core::sync::atomic::{compiler_fence, Ordering};
let p: *const u32 = $lookup;
#[allow(unused_unsafe)]
unsafe {
CACHE = core::mem::transmute(p);
compiler_fence(Ordering::Release);
CACHE($($argname),*)
}
}
#[cfg(feature = "rom-func-cache")]
pub(crate) fn outer_call() -> $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret {
use core::sync::atomic::{compiler_fence, Ordering};
// This is safe because the lookup will always resolve
// to the same value. So even if an interrupt or another
// core starts at the same time, it just repeats some
// work and eventually writes back the correct value.
//
// We easily get away with using only compiler fences here
// because RP2040 SRAM is not cached. If it were we'd need
// to make sure updates propagate quickly, or just take the
// hit and let each core resolve every function once.
compiler_fence(Ordering::Acquire);
unsafe {
CACHE
}
}
/// Retrieve a function pointer.
#[cfg(feature = "rom-func-cache")]
pub fn ptr() -> $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret {
use core::sync::atomic::{compiler_fence, Ordering};
// We can't just return the trampoline here because we need
// the actual resolved function address (e.x. flash operations
// can't reference a trampoline which itself is in flash). We
// can still utilize the cache, but we have to make sure it has
// been resolved already. Like the normal call path, we
// don't need anything stronger than fences because the
// final value always resolves to the same thing and SRAM
// itself is not cached.
compiler_fence(Ordering::Acquire);
#[allow(unused_unsafe)]
unsafe {
// ROM is 16kB in size at 0x0, so anything outside is cached
if CACHE as u32 >> 14 != 0 {
let p: *const u32 = $lookup;
CACHE = core::mem::transmute(p);
compiler_fence(Ordering::Release);
}
CACHE
}
}
}
$(#[$outer])*
pub $( $maybe_unsafe )? extern "C" fn $name( $($argname: $ty),* ) -> $ret {
$name::outer_call()($($argname),*)
}
};
}
macro_rules! rom_functions {
() => {};
(
$(#[$outer:meta])*
$c:literal fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty;
$($rest:tt)*
) => {
declare_rom_function! {
$(#[$outer])*
fn $name( $($argname: $ty),* ) -> $ret {
$crate::rom_data::inner::rom_table_lookup($crate::rom_data::inner::FUNC_TABLE, *$c)
}
}
rom_functions!($($rest)*);
};
(
$(#[$outer:meta])*
$c:literal unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty;
$($rest:tt)*
) => {
declare_rom_function! {
$(#[$outer])*
unsafe fn $name( $($argname: $ty),* ) -> $ret {
$crate::rom_data::inner::rom_table_lookup($crate::rom_data::inner::FUNC_TABLE, *$c)
}
}
rom_functions!($($rest)*);
};
}
rom_functions! {
/// Return a count of the number of 1 bits in value.
b"P3" fn popcount32(value: u32) -> u32;
/// Return the bits of value in the reverse order.
b"R3" fn reverse32(value: u32) -> u32;
/// Return the number of consecutive high order 0 bits of value. If value is zero, returns 32.
b"L3" fn clz32(value: u32) -> u32;
/// Return the number of consecutive low order 0 bits of value. If value is zero, returns 32.
b"T3" fn ctz32(value: u32) -> u32;
/// Resets the RP2040 and uses the watchdog facility to re-start in BOOTSEL mode:
/// * gpio_activity_pin_mask is provided to enable an 'activity light' via GPIO attached LED
/// for the USB Mass Storage Device:
/// * 0 No pins are used as per cold boot.
/// * Otherwise a single bit set indicating which GPIO pin should be set to output and
/// raised whenever there is mass storage activity from the host.
/// * disable_interface_mask may be used to control the exposed USB interfaces:
/// * 0 To enable both interfaces (as per cold boot).
/// * 1 To disable the USB Mass Storage Interface.
/// * 2 to Disable the USB PICOBOOT Interface.
b"UB" fn reset_to_usb_boot(gpio_activity_pin_mask: u32, disable_interface_mask: u32) -> ();
/// Sets n bytes start at ptr to the value c and returns ptr
b"MS" unsafe fn memset(ptr: *mut u8, c: u8, n: u32) -> *mut u8;
/// Sets n bytes start at ptr to the value c and returns ptr.
///
/// Note this is a slightly more efficient variant of _memset that may only
/// be used if ptr is word aligned.
// Note the datasheet does not match the actual ROM for the code here, see
// https://github.com/raspberrypi/pico-feedback/issues/217
b"S4" unsafe fn memset4(ptr: *mut u32, c: u8, n: u32) -> *mut u32;
/// Copies n bytes starting at src to dest and returns dest. The results are undefined if the
/// regions overlap.
b"MC" unsafe fn memcpy(dest: *mut u8, src: *const u8, n: u32) -> *mut u8;
/// Copies n bytes starting at src to dest and returns dest. The results are undefined if the
/// regions overlap.
///
/// Note this is a slightly more efficient variant of _memcpy that may only be
/// used if dest and src are word aligned.
b"C4" unsafe fn memcpy44(dest: *mut u32, src: *const u32, n: u32) -> *mut u8;
/// Restore all QSPI pad controls to their default state, and connect the SSI to the QSPI pads.
b"IF" unsafe fn connect_internal_flash() -> ();
/// First set up the SSI for serial-mode operations, then issue the fixed XIP exit sequence.
///
/// Note that the bootrom code uses the IO forcing logic to drive the CS pin, which must be
/// cleared before returning the SSI to XIP mode (e.g. by a call to _flash_flush_cache). This
/// function configures the SSI with a fixed SCK clock divisor of /6.
b"EX" unsafe fn flash_exit_xip() -> ();
/// Erase a count bytes, starting at addr (offset from start of flash). Optionally, pass a
/// block erase command e.g. D8h block erase, and the size of the block erased by this
/// command — this function will use the larger block erase where possible, for much higher
/// erase speed. addr must be aligned to a 4096-byte sector, and count must be a multiple of
/// 4096 bytes.
b"RE" unsafe fn flash_range_erase(addr: u32, count: usize, block_size: u32, block_cmd: u8) -> ();
/// Program data to a range of flash addresses starting at `addr` (and
/// offset from the start of flash) and `count` bytes in size. The value
/// `addr` must be aligned to a 256-byte boundary, and `count` must be a
/// multiple of 256.
b"RP" unsafe fn flash_range_program(addr: u32, data: *const u8, count: usize) -> ();
/// Flush and enable the XIP cache. Also clears the IO forcing on QSPI CSn, so that the SSI can
/// drive the flashchip select as normal.
b"FC" unsafe fn flash_flush_cache() -> ();
/// Configure the SSI to generate a standard 03h serial read command, with 24 address bits,
/// upon each XIP access. This is a very slow XIP configuration, but is very widely supported.
/// The debugger calls this function after performing a flash erase/programming operation, so
/// that the freshly-programmed code and data is visible to the debug host, without having to
/// know exactly what kind of flash device is connected.
b"CX" unsafe fn flash_enter_cmd_xip() -> ();
/// This is the method that is entered by core 1 on reset to wait to be launched by core 0.
/// There are few cases where you should call this method (resetting core 1 is much better).
/// This method does not return and should only ever be called on core 1.
b"WV" unsafe fn wait_for_vector() -> !;
}
// Various C intrinsics in the ROM
intrinsics! {
#[alias = __popcountdi2]
extern "C" fn __popcountsi2(x: u32) -> u32 {
popcount32(x)
}
#[alias = __clzdi2]
extern "C" fn __clzsi2(x: u32) -> u32 {
clz32(x)
}
#[alias = __ctzdi2]
extern "C" fn __ctzsi2(x: u32) -> u32 {
ctz32(x)
}
// __rbit is only unofficial, but it show up in the ARM documentation,
// so may as well hook it up.
#[alias = __rbitl]
extern "C" fn __rbit(x: u32) -> u32 {
reverse32(x)
}
unsafe extern "aapcs" fn __aeabi_memset(dest: *mut u8, n: usize, c: i32) -> () {
// Different argument order
memset(dest, c as u8, n as u32);
}
#[alias = __aeabi_memset8]
unsafe extern "aapcs" fn __aeabi_memset4(dest: *mut u8, n: usize, c: i32) -> () {
// Different argument order
memset4(dest as *mut u32, c as u8, n as u32);
}
unsafe extern "aapcs" fn __aeabi_memclr(dest: *mut u8, n: usize) -> () {
memset(dest, 0, n as u32);
}
#[alias = __aeabi_memclr8]
unsafe extern "aapcs" fn __aeabi_memclr4(dest: *mut u8, n: usize) -> () {
memset4(dest as *mut u32, 0, n as u32);
}
unsafe extern "aapcs" fn __aeabi_memcpy(dest: *mut u8, src: *const u8, n: usize) -> () {
memcpy(dest, src, n as u32);
}
#[alias = __aeabi_memcpy8]
unsafe extern "aapcs" fn __aeabi_memcpy4(dest: *mut u8, src: *const u8, n: usize) -> () {
memcpy44(dest as *mut u32, src as *const u32, n as u32);
}
}
unsafe fn convert_str(s: *const u8) -> &'static str {
let mut end = s;
while *end != 0 {
end = end.add(1);
}
let s = core::slice::from_raw_parts(s, end.offset_from(s) as usize);
core::str::from_utf8_unchecked(s)
}
/// The version number of the rom.
pub fn rom_version_number() -> u8 {
unsafe { *VERSION_NUMBER }
}
/// The Raspberry Pi Trading Ltd copyright string.
pub fn copyright_string() -> &'static str {
let s: *const u8 = rom_table_lookup(DATA_TABLE, *b"CR");
unsafe { convert_str(s) }
}
/// The 8 most significant hex digits of the Bootrom git revision.
pub fn git_revision() -> u32 {
let s: *const u32 = rom_table_lookup(DATA_TABLE, *b"GR");
unsafe { *s }
}
/// The start address of the floating point library code and data.
///
/// This and fplib_end along with the individual function pointers in
/// soft_float_table can be used to copy the floating point implementation into
/// RAM if desired.
pub fn fplib_start() -> *const u8 {
rom_table_lookup(DATA_TABLE, *b"FS")
}
/// See Table 180 in the RP2040 datasheet for the contents of this table.
#[cfg_attr(feature = "rom-func-cache", inline(never))]
pub fn soft_float_table() -> *const usize {
rom_table_lookup(DATA_TABLE, *b"SF")
}
/// The end address of the floating point library code and data.
pub fn fplib_end() -> *const u8 {
rom_table_lookup(DATA_TABLE, *b"FE")
}
/// This entry is only present in the V2 bootrom. See Table 182 in the RP2040 datasheet for the contents of this table.
#[cfg_attr(feature = "rom-func-cache", inline(never))]
pub fn soft_double_table() -> *const usize {
if rom_version_number() < 2 {
panic!(
"Double precision operations require V2 bootrom (found: V{})",
rom_version_number()
);
}
rom_table_lookup(DATA_TABLE, *b"SD")
}
/// ROM functions using single-precision arithmetic (i.e. 'f32' in Rust terms)
pub mod float_funcs {
macro_rules! make_functions {
(
$(
$(#[$outer:meta])*
$offset:literal $name:ident (
$( $aname:ident : $aty:ty ),*
) -> $ret:ty;
)*
) => {
$(
declare_rom_function! {
$(#[$outer])*
fn $name( $( $aname : $aty ),* ) -> $ret {
let table: *const usize = $crate::rom_data::soft_float_table();
unsafe {
// This is the entry in the table. Our offset is given as a
// byte offset, but we want the table index (each pointer in
// the table is 4 bytes long)
let entry: *const usize = table.offset($offset / 4);
// Read the pointer from the table
core::ptr::read(entry) as *const u32
}
}
}
)*
}
}
make_functions! {
/// Calculates `a + b`
0x00 fadd(a: f32, b: f32) -> f32;
/// Calculates `a - b`
0x04 fsub(a: f32, b: f32) -> f32;
/// Calculates `a * b`
0x08 fmul(a: f32, b: f32) -> f32;
/// Calculates `a / b`
0x0c fdiv(a: f32, b: f32) -> f32;
// 0x10 and 0x14 are deprecated
/// Calculates `sqrt(v)` (or return -Infinity if v is negative)
0x18 fsqrt(v: f32) -> f32;
/// Converts an f32 to a signed integer,
/// rounding towards -Infinity, and clamping the result to lie within the
/// range `-0x80000000` to `0x7FFFFFFF`
0x1c float_to_int(v: f32) -> i32;
/// Converts an f32 to an signed fixed point
/// integer representation where n specifies the position of the binary
/// point in the resulting fixed point representation, e.g.
/// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity,
/// and clamps the resulting integer to lie within the range `0x00000000` to
/// `0xFFFFFFFF`
0x20 float_to_fix(v: f32, n: i32) -> i32;
/// Converts an f32 to an unsigned integer,
/// rounding towards -Infinity, and clamping the result to lie within the
/// range `0x00000000` to `0xFFFFFFFF`
0x24 float_to_uint(v: f32) -> u32;
/// Converts an f32 to an unsigned fixed point
/// integer representation where n specifies the position of the binary
/// point in the resulting fixed point representation, e.g.
/// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity,
/// and clamps the resulting integer to lie within the range `0x00000000` to
/// `0xFFFFFFFF`
0x28 float_to_ufix(v: f32, n: i32) -> u32;
/// Converts a signed integer to the nearest
/// f32 value, rounding to even on tie
0x2c int_to_float(v: i32) -> f32;
/// Converts a signed fixed point integer
/// representation to the nearest f32 value, rounding to even on tie. `n`
/// specifies the position of the binary point in fixed point, so `f =
/// nearest(v/(2^n))`
0x30 fix_to_float(v: i32, n: i32) -> f32;
/// Converts an unsigned integer to the nearest
/// f32 value, rounding to even on tie
0x34 uint_to_float(v: u32) -> f32;
/// Converts an unsigned fixed point integer
/// representation to the nearest f32 value, rounding to even on tie. `n`
/// specifies the position of the binary point in fixed point, so `f =
/// nearest(v/(2^n))`
0x38 ufix_to_float(v: u32, n: i32) -> f32;
/// Calculates the cosine of `angle`. The value
/// of `angle` is in radians, and must be in the range `-1024` to `1024`
0x3c fcos(angle: f32) -> f32;
/// Calculates the sine of `angle`. The value of
/// `angle` is in radians, and must be in the range `-1024` to `1024`
0x40 fsin(angle: f32) -> f32;
/// Calculates the tangent of `angle`. The value
/// of `angle` is in radians, and must be in the range `-1024` to `1024`
0x44 ftan(angle: f32) -> f32;
// 0x48 is deprecated
/// Calculates the exponential value of `v`,
/// i.e. `e ** v`
0x4c fexp(v: f32) -> f32;
/// Calculates the natural logarithm of `v`. If `v <= 0` return -Infinity
0x50 fln(v: f32) -> f32;
}
macro_rules! make_functions_v2 {
(
$(
$(#[$outer:meta])*
$offset:literal $name:ident (
$( $aname:ident : $aty:ty ),*
) -> $ret:ty;
)*
) => {
$(
declare_rom_function! {
$(#[$outer])*
fn $name( $( $aname : $aty ),* ) -> $ret {
if $crate::rom_data::rom_version_number() < 2 {
panic!(
"Floating point function requires V2 bootrom (found: V{})",
$crate::rom_data::rom_version_number()
);
}
let table: *const usize = $crate::rom_data::soft_float_table();
unsafe {
// This is the entry in the table. Our offset is given as a
// byte offset, but we want the table index (each pointer in
// the table is 4 bytes long)
let entry: *const usize = table.offset($offset / 4);
// Read the pointer from the table
core::ptr::read(entry) as *const u32
}
}
}
)*
}
}
// These are only on BootROM v2 or higher
make_functions_v2! {
/// Compares two floating point numbers, returning:
/// • 0 if a == b
/// • -1 if a < b
/// • 1 if a > b
0x54 fcmp(a: f32, b: f32) -> i32;
/// Computes the arc tangent of `y/x` using the
/// signs of arguments to determine the correct quadrant
0x58 fatan2(y: f32, x: f32) -> f32;
/// Converts a signed 64-bit integer to the
/// nearest f32 value, rounding to even on tie
0x5c int64_to_float(v: i64) -> f32;
/// Converts a signed fixed point 64-bit integer
/// representation to the nearest f32 value, rounding to even on tie. `n`
/// specifies the position of the binary point in fixed point, so `f =
/// nearest(v/(2^n))`
0x60 fix64_to_float(v: i64, n: i32) -> f32;
/// Converts an unsigned 64-bit integer to the
/// nearest f32 value, rounding to even on tie
0x64 uint64_to_float(v: u64) -> f32;
/// Converts an unsigned fixed point 64-bit
/// integer representation to the nearest f32 value, rounding to even on
/// tie. `n` specifies the position of the binary point in fixed point, so
/// `f = nearest(v/(2^n))`
0x68 ufix64_to_float(v: u64, n: i32) -> f32;
/// Convert an f32 to a signed 64-bit integer, rounding towards -Infinity,
/// and clamping the result to lie within the range `-0x8000000000000000` to
/// `0x7FFFFFFFFFFFFFFF`
0x6c float_to_int64(v: f32) -> i64;
/// Converts an f32 to a signed fixed point
/// 64-bit integer representation where n specifies the position of the
/// binary point in the resulting fixed point representation - e.g. `f(0.5f,
/// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the
/// resulting integer to lie within the range `-0x8000000000000000` to
/// `0x7FFFFFFFFFFFFFFF`
0x70 float_to_fix64(v: f32, n: i32) -> f32;
/// Converts an f32 to an unsigned 64-bit
/// integer, rounding towards -Infinity, and clamping the result to lie
/// within the range `0x0000000000000000` to `0xFFFFFFFFFFFFFFFF`
0x74 float_to_uint64(v: f32) -> u64;
/// Converts an f32 to an unsigned fixed point
/// 64-bit integer representation where n specifies the position of the
/// binary point in the resulting fixed point representation, e.g. `f(0.5f,
/// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the
/// resulting integer to lie within the range `0x0000000000000000` to
/// `0xFFFFFFFFFFFFFFFF`
0x78 float_to_ufix64(v: f32, n: i32) -> u64;
/// Converts an f32 to an f64.
0x7c float_to_double(v: f32) -> f64;
}
}
/// Functions using double-precision arithmetic (i.e. 'f64' in Rust terms)
pub mod double_funcs {
macro_rules! make_double_funcs {
(
$(
$(#[$outer:meta])*
$offset:literal $name:ident (
$( $aname:ident : $aty:ty ),*
) -> $ret:ty;
)*
) => {
$(
declare_rom_function! {
$(#[$outer])*
fn $name( $( $aname : $aty ),* ) -> $ret {
let table: *const usize = $crate::rom_data::soft_double_table();
unsafe {
// This is the entry in the table. Our offset is given as a
// byte offset, but we want the table index (each pointer in
// the table is 4 bytes long)
let entry: *const usize = table.offset($offset / 4);
// Read the pointer from the table
core::ptr::read(entry) as *const u32
}
}
}
)*
}
}
make_double_funcs! {
/// Calculates `a + b`
0x00 dadd(a: f64, b: f64) -> f64;
/// Calculates `a - b`
0x04 dsub(a: f64, b: f64) -> f64;
/// Calculates `a * b`
0x08 dmul(a: f64, b: f64) -> f64;
/// Calculates `a / b`
0x0c ddiv(a: f64, b: f64) -> f64;
// 0x10 and 0x14 are deprecated
/// Calculates `sqrt(v)` (or return -Infinity if v is negative)
0x18 dsqrt(v: f64) -> f64;
/// Converts an f64 to a signed integer,
/// rounding towards -Infinity, and clamping the result to lie within the
/// range `-0x80000000` to `0x7FFFFFFF`
0x1c double_to_int(v: f64) -> i32;
/// Converts an f64 to an signed fixed point
/// integer representation where n specifies the position of the binary
/// point in the resulting fixed point representation, e.g.
/// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity,
/// and clamps the resulting integer to lie within the range `0x00000000` to
/// `0xFFFFFFFF`
0x20 double_to_fix(v: f64, n: i32) -> i32;
/// Converts an f64 to an unsigned integer,
/// rounding towards -Infinity, and clamping the result to lie within the
/// range `0x00000000` to `0xFFFFFFFF`
0x24 double_to_uint(v: f64) -> u32;
/// Converts an f64 to an unsigned fixed point
/// integer representation where n specifies the position of the binary
/// point in the resulting fixed point representation, e.g.
/// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity,
/// and clamps the resulting integer to lie within the range `0x00000000` to
/// `0xFFFFFFFF`
0x28 double_to_ufix(v: f64, n: i32) -> u32;
/// Converts a signed integer to the nearest
/// double value, rounding to even on tie
0x2c int_to_double(v: i32) -> f64;
/// Converts a signed fixed point integer
/// representation to the nearest double value, rounding to even on tie. `n`
/// specifies the position of the binary point in fixed point, so `f =
/// nearest(v/(2^n))`
0x30 fix_to_double(v: i32, n: i32) -> f64;
/// Converts an unsigned integer to the nearest
/// double value, rounding to even on tie
0x34 uint_to_double(v: u32) -> f64;
/// Converts an unsigned fixed point integer
/// representation to the nearest double value, rounding to even on tie. `n`
/// specifies the position of the binary point in fixed point, so f =
/// nearest(v/(2^n))
0x38 ufix_to_double(v: u32, n: i32) -> f64;
/// Calculates the cosine of `angle`. The value
/// of `angle` is in radians, and must be in the range `-1024` to `1024`
0x3c dcos(angle: f64) -> f64;
/// Calculates the sine of `angle`. The value of
/// `angle` is in radians, and must be in the range `-1024` to `1024`
0x40 dsin(angle: f64) -> f64;
/// Calculates the tangent of `angle`. The value
/// of `angle` is in radians, and must be in the range `-1024` to `1024`
0x44 dtan(angle: f64) -> f64;
// 0x48 is deprecated
/// Calculates the exponential value of `v`,
/// i.e. `e ** v`
0x4c dexp(v: f64) -> f64;
/// Calculates the natural logarithm of v. If v <= 0 return -Infinity
0x50 dln(v: f64) -> f64;
// These are only on BootROM v2 or higher
/// Compares two floating point numbers, returning:
/// • 0 if a == b
/// • -1 if a < b
/// • 1 if a > b
0x54 dcmp(a: f64, b: f64) -> i32;
/// Computes the arc tangent of `y/x` using the
/// signs of arguments to determine the correct quadrant
0x58 datan2(y: f64, x: f64) -> f64;
/// Converts a signed 64-bit integer to the
/// nearest double value, rounding to even on tie
0x5c int64_to_double(v: i64) -> f64;
/// Converts a signed fixed point 64-bit integer
/// representation to the nearest double value, rounding to even on tie. `n`
/// specifies the position of the binary point in fixed point, so `f =
/// nearest(v/(2^n))`
0x60 fix64_to_doubl(v: i64, n: i32) -> f64;
/// Converts an unsigned 64-bit integer to the
/// nearest double value, rounding to even on tie
0x64 uint64_to_double(v: u64) -> f64;
/// Converts an unsigned fixed point 64-bit
/// integer representation to the nearest double value, rounding to even on
/// tie. `n` specifies the position of the binary point in fixed point, so
/// `f = nearest(v/(2^n))`
0x68 ufix64_to_double(v: u64, n: i32) -> f64;
/// Convert an f64 to a signed 64-bit integer, rounding towards -Infinity,
/// and clamping the result to lie within the range `-0x8000000000000000` to
/// `0x7FFFFFFFFFFFFFFF`
0x6c double_to_int64(v: f64) -> i64;
/// Converts an f64 to a signed fixed point
/// 64-bit integer representation where n specifies the position of the
/// binary point in the resulting fixed point representation - e.g. `f(0.5f,
/// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the
/// resulting integer to lie within the range `-0x8000000000000000` to
/// `0x7FFFFFFFFFFFFFFF`
0x70 double_to_fix64(v: f64, n: i32) -> i64;
/// Converts an f64 to an unsigned 64-bit
/// integer, rounding towards -Infinity, and clamping the result to lie
/// within the range `0x0000000000000000` to `0xFFFFFFFFFFFFFFFF`
0x74 double_to_uint64(v: f64) -> u64;
/// Converts an f64 to an unsigned fixed point
/// 64-bit integer representation where n specifies the position of the
/// binary point in the resulting fixed point representation, e.g. `f(0.5f,
/// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the
/// resulting integer to lie within the range `0x0000000000000000` to
/// `0xFFFFFFFFFFFFFFFF`
0x78 double_to_ufix64(v: f64, n: i32) -> u64;
/// Converts an f64 to an f32
0x7c double_to_float(v: f64) -> f32;
}
}

View File

@@ -0,0 +1,752 @@
//! Functions and data from the RPI Bootrom.
//!
//! From [Section 5.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the
//! RP2350 datasheet:
//!
//! > Whilst some ROM space is dedicated to the implementation of the boot
//! > sequence and USB/UART boot interfaces, the bootrom also contains public
//! > functions that provide useful RP2350 functionality that may be useful for
//! > any code or runtime running on the device
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp235x-hal/src/rom_data.rs
/// A bootrom function table code.
pub type RomFnTableCode = [u8; 2];
/// This function searches for the tag which matches the mask.
type RomTableLookupFn = unsafe extern "C" fn(code: u32, mask: u32) -> usize;
/// Pointer to the value lookup function supplied by the ROM.
///
/// This address is described at `5.5.1. Locating the API Functions`
#[cfg(all(target_arch = "arm", target_os = "none"))]
const ROM_TABLE_LOOKUP_A2: *const u16 = 0x0000_0016 as _;
/// Pointer to the value lookup function supplied by the ROM.
///
/// This address is described at `5.5.1. Locating the API Functions`
#[cfg(all(target_arch = "arm", target_os = "none"))]
const ROM_TABLE_LOOKUP_A1: *const u32 = 0x0000_0018 as _;
/// Pointer to the data lookup function supplied by the ROM.
///
/// On Arm, the same function is used to look up code and data.
#[cfg(all(target_arch = "arm", target_os = "none"))]
const ROM_DATA_LOOKUP_A2: *const u16 = ROM_TABLE_LOOKUP_A2;
/// Pointer to the data lookup function supplied by the ROM.
///
/// On Arm, the same function is used to look up code and data.
#[cfg(all(target_arch = "arm", target_os = "none"))]
const ROM_DATA_LOOKUP_A1: *const u32 = ROM_TABLE_LOOKUP_A1;
/// Pointer to the value lookup function supplied by the ROM.
///
/// This address is described at `5.5.1. Locating the API Functions`
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
const ROM_TABLE_LOOKUP_A2: *const u16 = 0x0000_7DFA as _;
/// Pointer to the value lookup function supplied by the ROM.
///
/// This address is described at `5.5.1. Locating the API Functions`
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
const ROM_TABLE_LOOKUP_A1: *const u32 = 0x0000_7DF8 as _;
/// Pointer to the data lookup function supplied by the ROM.
///
/// On RISC-V, a different function is used to look up data.
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
const ROM_DATA_LOOKUP_A2: *const u16 = 0x0000_7DF8 as _;
/// Pointer to the data lookup function supplied by the ROM.
///
/// On RISC-V, a different function is used to look up data.
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
const ROM_DATA_LOOKUP_A1: *const u32 = 0x0000_7DF4 as _;
/// Address of the version number of the ROM.
const VERSION_NUMBER: *const u8 = 0x0000_0013 as _;
#[allow(unused)]
mod rt_flags {
pub const FUNC_RISCV: u32 = 0x0001;
pub const FUNC_RISCV_FAR: u32 = 0x0003;
pub const FUNC_ARM_SEC: u32 = 0x0004;
// reserved for 32-bit pointer: 0x0008
pub const FUNC_ARM_NONSEC: u32 = 0x0010;
// reserved for 32-bit pointer: 0x0020
pub const DATA: u32 = 0x0040;
// reserved for 32-bit pointer: 0x0080
#[cfg(all(target_arch = "arm", target_os = "none"))]
pub const FUNC_ARM_SEC_RISCV: u32 = FUNC_ARM_SEC;
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
pub const FUNC_ARM_SEC_RISCV: u32 = FUNC_RISCV;
}
/// Retrieve rom content from a table using a code.
pub fn rom_table_lookup(tag: RomFnTableCode, mask: u32) -> usize {
let tag = u16::from_le_bytes(tag) as u32;
unsafe {
let lookup_func = if rom_version_number() == 1 {
ROM_TABLE_LOOKUP_A1.read() as usize
} else {
ROM_TABLE_LOOKUP_A2.read() as usize
};
let lookup_func: RomTableLookupFn = core::mem::transmute(lookup_func);
lookup_func(tag, mask)
}
}
/// Retrieve rom data content from a table using a code.
pub fn rom_data_lookup(tag: RomFnTableCode, mask: u32) -> usize {
let tag = u16::from_le_bytes(tag) as u32;
unsafe {
let lookup_func = if rom_version_number() == 1 {
ROM_DATA_LOOKUP_A1.read() as usize
} else {
ROM_DATA_LOOKUP_A2.read() as usize
};
let lookup_func: RomTableLookupFn = core::mem::transmute(lookup_func);
lookup_func(tag, mask)
}
}
macro_rules! declare_rom_function {
(
$(#[$outer:meta])*
fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty
$lookup:block
) => {
#[doc = r"Additional access for the `"]
#[doc = stringify!($name)]
#[doc = r"` ROM function."]
pub mod $name {
/// Retrieve a function pointer.
#[cfg(not(feature = "rom-func-cache"))]
pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret {
let p: usize = $lookup;
unsafe {
let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p);
func
}
}
/// Retrieve a function pointer.
#[cfg(feature = "rom-func-cache")]
pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret {
use core::sync::atomic::{AtomicU16, Ordering};
// All pointers in the ROM fit in 16 bits, so we don't need a
// full width word to store the cached value.
static CACHED_PTR: AtomicU16 = AtomicU16::new(0);
// This is safe because the lookup will always resolve
// to the same value. So even if an interrupt or another
// core starts at the same time, it just repeats some
// work and eventually writes back the correct value.
let p: usize = match CACHED_PTR.load(Ordering::Relaxed) {
0 => {
let raw: usize = $lookup;
CACHED_PTR.store(raw as u16, Ordering::Relaxed);
raw
},
val => val as usize,
};
unsafe {
let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p);
func
}
}
}
$(#[$outer])*
pub extern "C" fn $name( $($argname: $ty),* ) -> $ret {
$name::ptr()($($argname),*)
}
};
(
$(#[$outer:meta])*
unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty
$lookup:block
) => {
#[doc = r"Additional access for the `"]
#[doc = stringify!($name)]
#[doc = r"` ROM function."]
pub mod $name {
/// Retrieve a function pointer.
#[cfg(not(feature = "rom-func-cache"))]
pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret {
let p: usize = $lookup;
unsafe {
let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p);
func
}
}
/// Retrieve a function pointer.
#[cfg(feature = "rom-func-cache")]
pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret {
use core::sync::atomic::{AtomicU16, Ordering};
// All pointers in the ROM fit in 16 bits, so we don't need a
// full width word to store the cached value.
static CACHED_PTR: AtomicU16 = AtomicU16::new(0);
// This is safe because the lookup will always resolve
// to the same value. So even if an interrupt or another
// core starts at the same time, it just repeats some
// work and eventually writes back the correct value.
let p: usize = match CACHED_PTR.load(Ordering::Relaxed) {
0 => {
let raw: usize = $lookup;
CACHED_PTR.store(raw as u16, Ordering::Relaxed);
raw
},
val => val as usize,
};
unsafe {
let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p);
func
}
}
}
$(#[$outer])*
/// # Safety
///
/// This is a low-level C function. It may be difficult to call safely from
/// Rust. If in doubt, check the rp235x datasheet for details and do your own
/// safety evaluation.
pub unsafe extern "C" fn $name( $($argname: $ty),* ) -> $ret {
$name::ptr()($($argname),*)
}
};
}
// **************** 5.5.7 Low-level Flash Commands ****************
declare_rom_function! {
/// Restore all QSPI pad controls to their default state, and connect the
/// QMI peripheral to the QSPI pads.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn connect_internal_flash() -> () {
crate::rom_data::rom_table_lookup(*b"IF", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Initialise the QMI for serial operations (direct mode)
///
/// Also initialise a basic XIP mode, where the QMI will perform 03h serial
/// read commands at low speed (CLKDIV=12) in response to XIP reads.
///
/// Then, issue a sequence to the QSPI device on chip select 0, designed to
/// return it from continuous read mode ("XIP mode") and/or QPI mode to a
/// state where it will accept serial commands. This is necessary after
/// system reset to restore the QSPI device to a known state, because
/// resetting RP2350 does not reset attached QSPI devices. It is also
/// necessary when user code, having already performed some
/// continuous-read-mode or QPI-mode accesses, wishes to return the QSPI
/// device to a state where it will accept the serial erase and programming
/// commands issued by the bootroms flash access functions.
///
/// If a GPIO for the secondary chip select is configured via FLASH_DEVINFO,
/// then the XIP exit sequence is also issued to chip select 1.
///
/// The QSPI device should be accessible for XIP reads after calling this
/// function; the name flash_exit_xip refers to returning the QSPI device
/// from its XIP state to a serial command state.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn flash_exit_xip() -> () {
crate::rom_data::rom_table_lookup(*b"EX", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Erase count bytes, starting at addr (offset from start of flash).
///
/// Optionally, pass a block erase command e.g. D8h block erase, and the
/// size of the block erased by this command — this function will use the
/// larger block erase where possible, for much higher erase speed. addr
/// must be aligned to a 4096-byte sector, and count must be a multiple of
/// 4096 bytes.
///
/// This is a low-level flash API, and no validation of the arguments is
/// performed. See flash_op() for a higher-level API which checks alignment,
/// flash bounds and partition permissions, and can transparently apply a
/// runtime-to-storage address translation.
///
/// The QSPI device must be in a serial command state before calling this
/// API, which can be achieved by calling connect_internal_flash() followed
/// by flash_exit_xip(). After the erase, the flash cache should be flushed
/// via flash_flush_cache() to ensure the modified flash data is visible to
/// cached XIP accesses.
///
/// Finally, the original XIP mode should be restored by copying the saved
/// XIP setup function from bootram into SRAM, and executing it: the bootrom
/// provides a default function which restores the flash mode/clkdiv
/// discovered during flash scanning, and user programs can override this
/// with their own XIP setup function.
///
/// For the duration of the erase operation, QMI is in direct mode (Section
/// 12.14.5) and attempting to access XIP from DMA, the debugger or the
/// other core will return a bus fault. XIP becomes accessible again once
/// the function returns.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn flash_range_erase(addr: u32, count: usize, block_size: u32, block_cmd: u8) -> () {
crate::rom_data::rom_table_lookup(*b"RE", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Program data to a range of flash storage addresses starting at addr
/// (offset from the start of flash) and count bytes in size.
///
/// `addr` must be aligned to a 256-byte boundary, and count must be a
/// multiple of 256.
///
/// This is a low-level flash API, and no validation of the arguments is
/// performed. See flash_op() for a higher-level API which checks alignment,
/// flash bounds and partition permissions, and can transparently apply a
/// runtime-to-storage address translation.
///
/// The QSPI device must be in a serial command state before calling this
/// API — see notes on flash_range_erase().
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn flash_range_program(addr: u32, data: *const u8, count: usize) -> () {
crate::rom_data::rom_table_lookup(*b"RP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Flush the entire XIP cache, by issuing an invalidate by set/way
/// maintenance operation to every cache line (Section 4.4.1).
///
/// This ensures that flash program/erase operations are visible to
/// subsequent cached XIP reads.
///
/// Note that this unpins pinned cache lines, which may interfere with
/// cache-as-SRAM use of the XIP cache.
///
/// No other operations are performed.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn flash_flush_cache() -> () {
crate::rom_data::rom_table_lookup(*b"FC", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Configure the QMI to generate a standard 03h serial read command, with
/// 24 address bits, upon each XIP access.
///
/// This is a slow XIP configuration, but is widely supported. CLKDIV is set
/// to 12. The debugger may call this function to ensure that flash is
/// readable following a program/erase operation.
///
/// Note that the same setup is performed by flash_exit_xip(), and the
/// RP2350 flash program/erase functions do not leave XIP in an inaccessible
/// state, so calls to this function are largely redundant. It is provided
/// for compatibility with RP2040.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn flash_enter_cmd_xip() -> () {
crate::rom_data::rom_table_lookup(*b"CX", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Configure QMI for one of a small menu of XIP read modes supported by the
/// bootrom. This mode is configured for both memory windows (both chip
/// selects), and the clock divisor is also applied to direct mode.
///
/// The available modes are:
///
/// * 0: `03h` serial read: serial address, serial data, no wait cycles
/// * 1: `0Bh` serial read: serial address, serial data, 8 wait cycles
/// * 2: `BBh` dual-IO read: dual address, dual data, 4 wait cycles
/// (including MODE bits, which are driven to 0)
/// * 3: `EBh` quad-IO read: quad address, quad data, 6 wait cycles
/// (including MODE bits, which are driven to 0)
///
/// The XIP write command/format are not configured by this function. When
/// booting from flash, the bootrom tries each of these modes in turn, from
/// 3 down to 0. The first mode that is found to work is remembered, and a
/// default XIP setup function is written into bootram that calls this
/// function (flash_select_xip_read_mode) with the parameters discovered
/// during flash scanning. This can be called at any time to restore the
/// flash parameters discovered during flash boot.
///
/// All XIP modes configured by the bootrom have an 8-bit serial command
/// prefix, so that the flash can remain in a serial command state, meaning
/// XIP accesses can be mixed more freely with program/erase serial
/// operations. This has a performance penalty, so users can perform their
/// own flash setup after flash boot using continuous read mode or QPI mode
/// to avoid or alleviate the command prefix cost.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn flash_select_xip_read_mode(bootrom_xip_mode: u8, clkdiv: u8) -> () {
crate::rom_data::rom_table_lookup(*b"XM", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Restore the QMI address translation registers, ATRANS0 through ATRANS7,
/// to their reset state. This makes the runtime- to-storage address map an
/// identity map, i.e. the mapped and unmapped address are equal, and the
/// entire space is fully mapped.
///
/// See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350
/// datasheet.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn flash_reset_address_trans() -> () {
crate::rom_data::rom_table_lookup(*b"RA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
// **************** High-level Flash Commands ****************
declare_rom_function! {
/// Applies the address translation currently configured by QMI address
/// translation registers, ATRANS0 through ATRANS7.
///
/// See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350
/// datasheet.
///
/// Translating an address outside of the XIP runtime address window, or
/// beyond the bounds of an ATRANSx_SIZE field, returns
/// BOOTROM_ERROR_INVALID_ADDRESS, which is not a valid flash storage
/// address. Otherwise, return the storage address which QMI would access
/// when presented with the runtime address addr. This is effectively a
/// virtual-to-physical address translation for QMI.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn flash_runtime_to_storage_addr(addr: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"FA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Non-secure version of [flash_runtime_to_storage_addr()]
///
/// Supported architectures: ARM-NS
#[cfg(all(target_arch = "arm", target_os = "none"))]
unsafe fn flash_runtime_to_storage_addr_ns(addr: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"FA", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC)
}
}
declare_rom_function! {
/// Perform a flash read, erase, or program operation.
///
/// Erase operations must be sector-aligned (4096 bytes) and sector-
/// multiple-sized, and program operations must be page-aligned (256 bytes)
/// and page-multiple-sized; misaligned erase and program operations will
/// return BOOTROM_ERROR_BAD_ALIGNMENT. The operation — erase, read, program
/// — is selected by the CFLASH_OP_BITS bitfield of the flags argument.
///
/// See datasheet section 5.5.8.2 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn flash_op(flags: u32, addr: u32, size_bytes: u32, buffer: *mut u8) -> i32 {
crate::rom_data::rom_table_lookup(*b"FO", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Non-secure version of [flash_op()]
///
/// Supported architectures: ARM-NS
#[cfg(all(target_arch = "arm", target_os = "none"))]
unsafe fn flash_op_ns(flags: u32, addr: u32, size_bytes: u32, buffer: *mut u8) -> i32 {
crate::rom_data::rom_table_lookup(*b"FO", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC)
}
}
// **************** Security Related Functions ****************
declare_rom_function! {
/// Allow or disallow the specific NS API (note all NS APIs default to
/// disabled).
///
/// See datasheet section 5.5.9.1 for more details.
///
/// Supported architectures: ARM-S
#[cfg(all(target_arch = "arm", target_os = "none"))]
unsafe fn set_ns_api_permission(ns_api_num: u32, allowed: u8) -> i32 {
crate::rom_data::rom_table_lookup(*b"SP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC)
}
}
declare_rom_function! {
/// Utility method that can be used by secure ARM code to validate a buffer
/// passed to it from Non-secure code.
///
/// See datasheet section 5.5.9.2 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn validate_ns_buffer() -> () {
crate::rom_data::rom_table_lookup(*b"VB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
// **************** Miscellaneous Functions ****************
declare_rom_function! {
/// Resets the RP2350 and uses the watchdog facility to restart.
///
/// See datasheet section 5.5.10.1 for more details.
///
/// Supported architectures: ARM-S, RISC-V
fn reboot(flags: u32, delay_ms: u32, p0: u32, p1: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"RB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Non-secure version of [reboot()]
///
/// Supported architectures: ARM-NS
#[cfg(all(target_arch = "arm", target_os = "none"))]
fn reboot_ns(flags: u32, delay_ms: u32, p0: u32, p1: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"RB", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC)
}
}
declare_rom_function! {
/// Resets internal bootrom state.
///
/// See datasheet section 5.5.10.2 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn bootrom_state_reset(flags: u32) -> () {
crate::rom_data::rom_table_lookup(*b"SR", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Set a boot ROM callback.
///
/// The only supported callback_number is 0 which sets the callback used for
/// the secure_call API.
///
/// See datasheet section 5.5.10.3 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn set_rom_callback(callback_number: i32, callback_fn: *const ()) -> i32 {
crate::rom_data::rom_table_lookup(*b"RC", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
// **************** System Information Functions ****************
declare_rom_function! {
/// Fills a buffer with various system information.
///
/// Note that this API is also used to return information over the PICOBOOT
/// interface.
///
/// See datasheet section 5.5.11.1 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn get_sys_info(out_buffer: *mut u32, out_buffer_word_size: usize, flags: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"GS", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Non-secure version of [get_sys_info()]
///
/// Supported architectures: ARM-NS
#[cfg(all(target_arch = "arm", target_os = "none"))]
unsafe fn get_sys_info_ns(out_buffer: *mut u32, out_buffer_word_size: usize, flags: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"GS", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC)
}
}
declare_rom_function! {
/// Fills a buffer with information from the partition table.
///
/// Note that this API is also used to return information over the PICOBOOT
/// interface.
///
/// See datasheet section 5.5.11.2 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn get_partition_table_info(out_buffer: *mut u32, out_buffer_word_size: usize, flags_and_partition: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"GP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Non-secure version of [get_partition_table_info()]
///
/// Supported architectures: ARM-NS
#[cfg(all(target_arch = "arm", target_os = "none"))]
unsafe fn get_partition_table_info_ns(out_buffer: *mut u32, out_buffer_word_size: usize, flags_and_partition: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"GP", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC)
}
}
declare_rom_function! {
/// Loads the current partition table from flash, if present.
///
/// See datasheet section 5.5.11.3 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn load_partition_table(workarea_base: *mut u8, workarea_size: usize, force_reload: bool) -> i32 {
crate::rom_data::rom_table_lookup(*b"LP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Writes data from a buffer into OTP, or reads data from OTP into a buffer.
///
/// See datasheet section 5.5.11.4 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn otp_access(buf: *mut u8, buf_len: usize, row_and_flags: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"OA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Non-secure version of [otp_access()]
///
/// Supported architectures: ARM-NS
#[cfg(all(target_arch = "arm", target_os = "none"))]
unsafe fn otp_access_ns(buf: *mut u8, buf_len: usize, row_and_flags: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"OA", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC)
}
}
// **************** Boot Related Functions ****************
declare_rom_function! {
/// Determines which of the partitions has the "better" IMAGE_DEF. In the
/// case of executable images, this is the one that would be booted.
///
/// See datasheet section 5.5.12.1 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn pick_ab_parition(workarea_base: *mut u8, workarea_size: usize, partition_a_num: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"AB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Searches a memory region for a launchable image, and executes it if
/// possible.
///
/// See datasheet section 5.5.12.2 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn chain_image(workarea_base: *mut u8, workarea_size: usize, region_base: i32, region_size: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"CI", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Perform an "explicit" buy of an executable launched via an IMAGE_DEF
/// which was "explicit buy" flagged.
///
/// See datasheet section 5.5.12.3 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn explicit_buy(buffer: *mut u8, buffer_size: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"EB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Not yet documented.
///
/// See datasheet section 5.5.12.4 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn get_uf2_target_partition(workarea_base: *mut u8, workarea_size: usize, family_id: u32, partition_out: *mut u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"GU", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Returns: The index of the B partition of partition A if a partition
/// table is present and loaded, and there is a partition A with a B
/// partition; otherwise returns BOOTROM_ERROR_NOT_FOUND.
///
/// See datasheet section 5.5.12.5 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn get_b_partition(partition_a: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"GB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
// **************** Non-secure-specific Functions ****************
// NB: The "secure_call" function should be here, but it doesn't have a fixed
// function signature as it is designed to let you bounce into any secure
// function from non-secure mode.
// **************** RISC-V Functions ****************
declare_rom_function! {
/// Set stack for RISC-V bootrom functions to use.
///
/// See datasheet section 5.5.14.1 for more details.
///
/// Supported architectures: RISC-V
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
unsafe fn set_bootrom_stack(base_size: *mut u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"SS", crate::rom_data::inner::rt_flags::FUNC_RISCV)
}
}
/// The version number of the rom.
pub fn rom_version_number() -> u8 {
unsafe { *VERSION_NUMBER }
}
/// The 8 most significant hex digits of the Bootrom git revision.
pub fn git_revision() -> u32 {
let ptr = rom_data_lookup(*b"GR", rt_flags::DATA) as *const u32;
unsafe { ptr.read() }
}
/// A pointer to the resident partition table info.
///
/// The resident partition table is the subset of the full partition table that
/// is kept in memory, and used for flash permissions.
pub fn partition_table_pointer() -> *const u32 {
let ptr = rom_data_lookup(*b"PT", rt_flags::DATA) as *const *const u32;
unsafe { ptr.read() }
}
/// Determine if we are in secure mode
///
/// Returns `true` if we are in secure mode and `false` if we are in non-secure
/// mode.
#[cfg(all(target_arch = "arm", target_os = "none"))]
pub fn is_secure_mode() -> bool {
// Look at the start of ROM, which is always readable
#[allow(clippy::zero_ptr)]
let rom_base: *mut u32 = 0x0000_0000 as *mut u32;
// Use the 'tt' instruction to check the permissions for that address
let tt = cortex_m::asm::tt(rom_base);
// Is the secure bit set? => secure mode
(tt & (1 << 22)) != 0
}
/// Determine if we are in secure mode
///
/// Always returns `false` on RISC-V as it is impossible to determine if
/// you are in Machine Mode or User Mode by design.
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
pub fn is_secure_mode() -> bool {
false
}

View File

@@ -0,0 +1,62 @@
use chrono::{Datelike, Timelike};
use crate::pac::rtc::regs::{Rtc0, Rtc1, Setup0, Setup1};
/// Alias for [`chrono::NaiveDateTime`]
pub type DateTime = chrono::NaiveDateTime;
/// Alias for [`chrono::Weekday`]
pub type DayOfWeek = chrono::Weekday;
/// Errors regarding the [`DateTime`] and [`DateTimeFilter`] structs.
///
/// [`DateTimeFilter`]: struct.DateTimeFilter.html
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Error {
/// The [DateTime] has an invalid year. The year must be between 0 and 4095.
InvalidYear,
/// The [DateTime] contains an invalid date.
InvalidDate,
/// The [DateTime] contains an invalid time.
InvalidTime,
}
pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 {
dotw.num_days_from_sunday() as u8
}
pub(crate) fn validate_datetime(dt: &DateTime) -> Result<(), Error> {
if dt.year() < 0 || dt.year() > 4095 {
// rp2040 can't hold these years
Err(Error::InvalidYear)
} else {
// The rest of the chrono date is assumed to be valid
Ok(())
}
}
pub(super) fn write_setup_0(dt: &DateTime, w: &mut Setup0) {
w.set_year(dt.year() as u16);
w.set_month(dt.month() as u8);
w.set_day(dt.day() as u8);
}
pub(super) fn write_setup_1(dt: &DateTime, w: &mut Setup1) {
w.set_dotw(dt.weekday().num_days_from_sunday() as u8);
w.set_hour(dt.hour() as u8);
w.set_min(dt.minute() as u8);
w.set_sec(dt.second() as u8);
}
pub(super) fn datetime_from_registers(rtc_0: Rtc0, rtc_1: Rtc1) -> Result<DateTime, Error> {
let year = rtc_1.year() as i32;
let month = rtc_1.month() as u32;
let day = rtc_1.day() as u32;
let hour = rtc_0.hour() as u32;
let minute = rtc_0.min() as u32;
let second = rtc_0.sec() as u32;
let date = chrono::NaiveDate::from_ymd_opt(year, month, day).ok_or(Error::InvalidDate)?;
let time = chrono::NaiveTime::from_hms_opt(hour, minute, second).ok_or(Error::InvalidTime)?;
Ok(DateTime::new(date, time))
}

View File

@@ -0,0 +1,128 @@
use crate::pac::rtc::regs::{Rtc0, Rtc1, Setup0, Setup1};
/// Errors regarding the [`DateTime`] and [`DateTimeFilter`] structs.
///
/// [`DateTimeFilter`]: struct.DateTimeFilter.html
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Error {
/// The [DateTime] contains an invalid year value. Must be between `0..=4095`.
InvalidYear,
/// The [DateTime] contains an invalid month value. Must be between `1..=12`.
InvalidMonth,
/// The [DateTime] contains an invalid day value. Must be between `1..=31`.
InvalidDay,
/// The [DateTime] contains an invalid day of week. Must be between `0..=6` where 0 is Sunday.
InvalidDayOfWeek(
/// The value of the DayOfWeek that was given.
u8,
),
/// The [DateTime] contains an invalid hour value. Must be between `0..=23`.
InvalidHour,
/// The [DateTime] contains an invalid minute value. Must be between `0..=59`.
InvalidMinute,
/// The [DateTime] contains an invalid second value. Must be between `0..=59`.
InvalidSecond,
}
/// Structure containing date and time information
#[derive(Clone, Debug)]
pub struct DateTime {
/// 0..4095
pub year: u16,
/// 1..12, 1 is January
pub month: u8,
/// 1..28,29,30,31 depending on month
pub day: u8,
///
pub day_of_week: DayOfWeek,
/// 0..23
pub hour: u8,
/// 0..59
pub minute: u8,
/// 0..59
pub second: u8,
}
/// A day of the week
#[repr(u8)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[allow(missing_docs)]
pub enum DayOfWeek {
Sunday = 0,
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6,
}
fn day_of_week_from_u8(v: u8) -> Result<DayOfWeek, Error> {
Ok(match v {
0 => DayOfWeek::Sunday,
1 => DayOfWeek::Monday,
2 => DayOfWeek::Tuesday,
3 => DayOfWeek::Wednesday,
4 => DayOfWeek::Thursday,
5 => DayOfWeek::Friday,
6 => DayOfWeek::Saturday,
x => return Err(Error::InvalidDayOfWeek(x)),
})
}
pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 {
dotw as u8
}
pub(super) fn validate_datetime(dt: &DateTime) -> Result<(), Error> {
if dt.year > 4095 {
Err(Error::InvalidYear)
} else if dt.month < 1 || dt.month > 12 {
Err(Error::InvalidMonth)
} else if dt.day < 1 || dt.day > 31 {
Err(Error::InvalidDay)
} else if dt.hour > 23 {
Err(Error::InvalidHour)
} else if dt.minute > 59 {
Err(Error::InvalidMinute)
} else if dt.second > 59 {
Err(Error::InvalidSecond)
} else {
Ok(())
}
}
pub(super) fn write_setup_0(dt: &DateTime, w: &mut Setup0) {
w.set_year(dt.year);
w.set_month(dt.month);
w.set_day(dt.day);
}
pub(super) fn write_setup_1(dt: &DateTime, w: &mut Setup1) {
w.set_dotw(dt.day_of_week as u8);
w.set_hour(dt.hour);
w.set_min(dt.minute);
w.set_sec(dt.second);
}
pub(super) fn datetime_from_registers(rtc_0: Rtc0, rtc_1: Rtc1) -> Result<DateTime, Error> {
let year = rtc_1.year();
let month = rtc_1.month();
let day = rtc_1.day();
let day_of_week = rtc_0.dotw();
let hour = rtc_0.hour();
let minute = rtc_0.min();
let second = rtc_0.sec();
let day_of_week = day_of_week_from_u8(day_of_week)?;
Ok(DateTime {
year,
month,
day,
day_of_week,
hour,
minute,
second,
})
}

View File

@@ -0,0 +1,100 @@
use super::DayOfWeek;
use crate::pac::rtc::regs::{IrqSetup0, IrqSetup1};
/// A filter used for [`RealTimeClock::schedule_alarm`].
///
/// [`RealTimeClock::schedule_alarm`]: struct.RealTimeClock.html#method.schedule_alarm
#[derive(Default)]
pub struct DateTimeFilter {
/// The year that this alarm should trigger on, `None` if the RTC alarm should not trigger on a year value.
pub year: Option<u16>,
/// The month that this alarm should trigger on, `None` if the RTC alarm should not trigger on a month value.
pub month: Option<u8>,
/// The day that this alarm should trigger on, `None` if the RTC alarm should not trigger on a day value.
pub day: Option<u8>,
/// The day of week that this alarm should trigger on, `None` if the RTC alarm should not trigger on a day of week value.
pub day_of_week: Option<DayOfWeek>,
/// The hour that this alarm should trigger on, `None` if the RTC alarm should not trigger on a hour value.
pub hour: Option<u8>,
/// The minute that this alarm should trigger on, `None` if the RTC alarm should not trigger on a minute value.
pub minute: Option<u8>,
/// The second that this alarm should trigger on, `None` if the RTC alarm should not trigger on a second value.
pub second: Option<u8>,
}
impl DateTimeFilter {
/// Set a filter on the given year
pub fn year(mut self, year: u16) -> Self {
self.year = Some(year);
self
}
/// Set a filter on the given month
pub fn month(mut self, month: u8) -> Self {
self.month = Some(month);
self
}
/// Set a filter on the given day
pub fn day(mut self, day: u8) -> Self {
self.day = Some(day);
self
}
/// Set a filter on the given day of the week
pub fn day_of_week(mut self, day_of_week: DayOfWeek) -> Self {
self.day_of_week = Some(day_of_week);
self
}
/// Set a filter on the given hour
pub fn hour(mut self, hour: u8) -> Self {
self.hour = Some(hour);
self
}
/// Set a filter on the given minute
pub fn minute(mut self, minute: u8) -> Self {
self.minute = Some(minute);
self
}
/// Set a filter on the given second
pub fn second(mut self, second: u8) -> Self {
self.second = Some(second);
self
}
}
// register helper functions
impl DateTimeFilter {
pub(super) fn write_setup_0(&self, w: &mut IrqSetup0) {
if let Some(year) = self.year {
w.set_year_ena(true);
w.set_year(year);
}
if let Some(month) = self.month {
w.set_month_ena(true);
w.set_month(month);
}
if let Some(day) = self.day {
w.set_day_ena(true);
w.set_day(day);
}
}
pub(super) fn write_setup_1(&self, w: &mut IrqSetup1) {
if let Some(day_of_week) = self.day_of_week {
w.set_dotw_ena(true);
let bits = super::datetime::day_of_week_to_u8(day_of_week);
w.set_dotw(bits);
}
if let Some(hour) = self.hour {
w.set_hour_ena(true);
w.set_hour(hour);
}
if let Some(minute) = self.minute {
w.set_min_ena(true);
w.set_min(minute);
}
if let Some(second) = self.second {
w.set_sec_ena(true);
w.set_sec(second);
}
}
}

204
embassy-rp/src/rtc/mod.rs Normal file
View File

@@ -0,0 +1,204 @@
//! RTC driver.
mod filter;
use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
pub use self::filter::DateTimeFilter;
#[cfg_attr(feature = "chrono", path = "datetime_chrono.rs")]
#[cfg_attr(not(feature = "chrono"), path = "datetime_no_deps.rs")]
mod datetime;
pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError};
use crate::clocks::clk_rtc_freq;
/// A reference to the real time clock of the system
pub struct Rtc<'d, T: Instance> {
inner: PeripheralRef<'d, T>,
}
impl<'d, T: Instance> Rtc<'d, T> {
/// Create a new instance of the real time clock, with the given date as an initial value.
///
/// # Errors
///
/// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range.
pub fn new(inner: impl Peripheral<P = T> + 'd) -> Self {
into_ref!(inner);
// Set the RTC divider
inner.regs().clkdiv_m1().write(|w| w.set_clkdiv_m1(clk_rtc_freq() - 1));
Self { inner }
}
/// Enable or disable the leap year check. The rp2040 chip will always add a Feb 29th on every year that is divisable by 4, but this may be incorrect (e.g. on century years). This function allows you to disable this check.
///
/// Leap year checking is enabled by default.
pub fn set_leap_year_check(&mut self, leap_year_check_enabled: bool) {
self.inner.regs().ctrl().modify(|w| {
w.set_force_notleapyear(!leap_year_check_enabled);
});
}
/// Set the time from internal format
pub fn restore(&mut self, ymd: rp_pac::rtc::regs::Rtc1, hms: rp_pac::rtc::regs::Rtc0) {
// disable RTC while we configure it
self.inner.regs().ctrl().modify(|w| w.set_rtc_enable(false));
while self.inner.regs().ctrl().read().rtc_active() {
core::hint::spin_loop();
}
self.inner.regs().setup_0().write(|w| {
*w = rp_pac::rtc::regs::Setup0(ymd.0);
});
self.inner.regs().setup_1().write(|w| {
*w = rp_pac::rtc::regs::Setup1(hms.0);
});
// Load the new datetime and re-enable RTC
self.inner.regs().ctrl().write(|w| w.set_load(true));
self.inner.regs().ctrl().write(|w| w.set_rtc_enable(true));
while !self.inner.regs().ctrl().read().rtc_active() {
core::hint::spin_loop();
}
}
/// Get the time in internal format
pub fn save(&mut self) -> (rp_pac::rtc::regs::Rtc1, rp_pac::rtc::regs::Rtc0) {
let rtc_0: rp_pac::rtc::regs::Rtc0 = self.inner.regs().rtc_0().read();
let rtc_1 = self.inner.regs().rtc_1().read();
(rtc_1, rtc_0)
}
/// Checks to see if this Rtc is running
pub fn is_running(&self) -> bool {
self.inner.regs().ctrl().read().rtc_active()
}
/// Set the datetime to a new value.
///
/// # Errors
///
/// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range.
pub fn set_datetime(&mut self, t: DateTime) -> Result<(), RtcError> {
self::datetime::validate_datetime(&t).map_err(RtcError::InvalidDateTime)?;
// disable RTC while we configure it
self.inner.regs().ctrl().modify(|w| w.set_rtc_enable(false));
while self.inner.regs().ctrl().read().rtc_active() {
core::hint::spin_loop();
}
self.inner.regs().setup_0().write(|w| {
self::datetime::write_setup_0(&t, w);
});
self.inner.regs().setup_1().write(|w| {
self::datetime::write_setup_1(&t, w);
});
// Load the new datetime and re-enable RTC
self.inner.regs().ctrl().write(|w| w.set_load(true));
self.inner.regs().ctrl().write(|w| w.set_rtc_enable(true));
while !self.inner.regs().ctrl().read().rtc_active() {
core::hint::spin_loop();
}
Ok(())
}
/// Return the current datetime.
///
/// # Errors
///
/// Will return an `RtcError::InvalidDateTime` if the stored value in the system is not a valid [`DayOfWeek`].
pub fn now(&self) -> Result<DateTime, RtcError> {
if !self.is_running() {
return Err(RtcError::NotRunning);
}
let rtc_0 = self.inner.regs().rtc_0().read();
let rtc_1 = self.inner.regs().rtc_1().read();
self::datetime::datetime_from_registers(rtc_0, rtc_1).map_err(RtcError::InvalidDateTime)
}
/// Disable the alarm that was scheduled with [`schedule_alarm`].
///
/// [`schedule_alarm`]: #method.schedule_alarm
pub fn disable_alarm(&mut self) {
self.inner.regs().irq_setup_0().modify(|s| s.set_match_ena(false));
while self.inner.regs().irq_setup_0().read().match_active() {
core::hint::spin_loop();
}
}
/// Schedule an alarm. The `filter` determines at which point in time this alarm is set.
///
/// Keep in mind that the filter only triggers on the specified time. If you want to schedule this alarm every minute, you have to call:
/// ```no_run
/// # #[cfg(feature = "chrono")]
/// # fn main() { }
/// # #[cfg(not(feature = "chrono"))]
/// # fn main() {
/// # use embassy_rp::rtc::{Rtc, DateTimeFilter};
/// # let mut real_time_clock: Rtc<embassy_rp::peripherals::RTC> = unsafe { core::mem::zeroed() };
/// let now = real_time_clock.now().unwrap();
/// real_time_clock.schedule_alarm(
/// DateTimeFilter::default()
/// .minute(if now.minute == 59 { 0 } else { now.minute + 1 })
/// );
/// # }
/// ```
pub fn schedule_alarm(&mut self, filter: DateTimeFilter) {
self.disable_alarm();
self.inner.regs().irq_setup_0().write(|w| {
filter.write_setup_0(w);
});
self.inner.regs().irq_setup_1().write(|w| {
filter.write_setup_1(w);
});
self.inner.regs().inte().modify(|w| w.set_rtc(true));
// Set the enable bit and check if it is set
self.inner.regs().irq_setup_0().modify(|w| w.set_match_ena(true));
while !self.inner.regs().irq_setup_0().read().match_active() {
core::hint::spin_loop();
}
}
/// Clear the interrupt. This should be called every time the `RTC_IRQ` interrupt is triggered,
/// or the next [`schedule_alarm`] will never fire.
///
/// [`schedule_alarm`]: #method.schedule_alarm
pub fn clear_interrupt(&mut self) {
self.disable_alarm();
}
}
/// Errors that can occur on methods on [Rtc]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RtcError {
/// An invalid DateTime was given or stored on the hardware.
InvalidDateTime(DateTimeError),
/// The RTC clock is not running
NotRunning,
}
trait SealedInstance {
fn regs(&self) -> crate::pac::rtc::Rtc;
}
/// RTC peripheral instance.
#[allow(private_bounds)]
pub trait Instance: SealedInstance {}
impl SealedInstance for crate::peripherals::RTC {
fn regs(&self) -> crate::pac::rtc::Rtc {
crate::pac::RTC
}
}
impl Instance for crate::peripherals::RTC {}

728
embassy-rp/src/spi.rs Normal file
View File

@@ -0,0 +1,728 @@
//! Serial Peripheral Interface
use core::marker::PhantomData;
use embassy_embedded_hal::SetConfig;
use embassy_futures::join::join;
use embassy_hal_internal::{into_ref, PeripheralRef};
pub use embedded_hal_02::spi::{Phase, Polarity};
use crate::dma::{AnyChannel, Channel};
use crate::gpio::{AnyPin, Pin as GpioPin, SealedPin as _};
use crate::{pac, peripherals, Peripheral};
/// SPI errors.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Error {
// No errors for now
}
/// SPI configuration.
#[non_exhaustive]
#[derive(Clone)]
pub struct Config {
/// Frequency.
pub frequency: u32,
/// Phase.
pub phase: Phase,
/// Polarity.
pub polarity: Polarity,
}
impl Default for Config {
fn default() -> Self {
Self {
frequency: 1_000_000,
phase: Phase::CaptureOnFirstTransition,
polarity: Polarity::IdleLow,
}
}
}
/// SPI driver.
pub struct Spi<'d, T: Instance, M: Mode> {
inner: PeripheralRef<'d, T>,
tx_dma: Option<PeripheralRef<'d, AnyChannel>>,
rx_dma: Option<PeripheralRef<'d, AnyChannel>>,
phantom: PhantomData<(&'d mut T, M)>,
}
fn div_roundup(a: u32, b: u32) -> u32 {
(a + b - 1) / b
}
fn calc_prescs(freq: u32) -> (u8, u8) {
let clk_peri = crate::clocks::clk_peri_freq();
// final SPI frequency: spi_freq = clk_peri / presc / postdiv
// presc must be in 2..=254, and must be even
// postdiv must be in 1..=256
// divide extra by 2, so we get rid of the "presc must be even" requirement
let ratio = div_roundup(clk_peri, freq * 2);
if ratio > 127 * 256 {
panic!("Requested too low SPI frequency");
}
let presc = div_roundup(ratio, 256);
let postdiv = if presc == 1 { ratio } else { div_roundup(ratio, presc) };
((presc * 2) as u8, (postdiv - 1) as u8)
}
impl<'d, T: Instance, M: Mode> Spi<'d, T, M> {
fn new_inner(
inner: impl Peripheral<P = T> + 'd,
clk: Option<PeripheralRef<'d, AnyPin>>,
mosi: Option<PeripheralRef<'d, AnyPin>>,
miso: Option<PeripheralRef<'d, AnyPin>>,
cs: Option<PeripheralRef<'d, AnyPin>>,
tx_dma: Option<PeripheralRef<'d, AnyChannel>>,
rx_dma: Option<PeripheralRef<'d, AnyChannel>>,
config: Config,
) -> Self {
into_ref!(inner);
Self::apply_config(&inner, &config);
let p = inner.regs();
// Always enable DREQ signals -- harmless if DMA is not listening
p.dmacr().write(|reg| {
reg.set_rxdmae(true);
reg.set_txdmae(true);
});
// finally, enable.
p.cr1().write(|w| w.set_sse(true));
if let Some(pin) = &clk {
pin.gpio().ctrl().write(|w| w.set_funcsel(1));
pin.pad_ctrl().write(|w| {
#[cfg(feature = "_rp235x")]
w.set_iso(false);
w.set_schmitt(true);
w.set_slewfast(false);
w.set_ie(true);
w.set_od(false);
w.set_pue(false);
w.set_pde(false);
});
}
if let Some(pin) = &mosi {
pin.gpio().ctrl().write(|w| w.set_funcsel(1));
pin.pad_ctrl().write(|w| {
#[cfg(feature = "_rp235x")]
w.set_iso(false);
w.set_schmitt(true);
w.set_slewfast(false);
w.set_ie(true);
w.set_od(false);
w.set_pue(false);
w.set_pde(false);
});
}
if let Some(pin) = &miso {
pin.gpio().ctrl().write(|w| w.set_funcsel(1));
pin.pad_ctrl().write(|w| {
#[cfg(feature = "_rp235x")]
w.set_iso(false);
w.set_schmitt(true);
w.set_slewfast(false);
w.set_ie(true);
w.set_od(false);
w.set_pue(false);
w.set_pde(false);
});
}
if let Some(pin) = &cs {
pin.gpio().ctrl().write(|w| w.set_funcsel(1));
pin.pad_ctrl().write(|w| {
#[cfg(feature = "_rp235x")]
w.set_iso(false);
w.set_schmitt(true);
w.set_slewfast(false);
w.set_ie(true);
w.set_od(false);
w.set_pue(false);
w.set_pde(false);
});
}
Self {
inner,
tx_dma,
rx_dma,
phantom: PhantomData,
}
}
/// Private function to apply SPI configuration (phase, polarity, frequency) settings.
///
/// Driver should be disabled before making changes and reenabled after the modifications
/// are applied.
fn apply_config(inner: &PeripheralRef<'d, T>, config: &Config) {
let p = inner.regs();
let (presc, postdiv) = calc_prescs(config.frequency);
p.cpsr().write(|w| w.set_cpsdvsr(presc));
p.cr0().write(|w| {
w.set_dss(0b0111); // 8bit
w.set_spo(config.polarity == Polarity::IdleHigh);
w.set_sph(config.phase == Phase::CaptureOnSecondTransition);
w.set_scr(postdiv);
});
}
/// Write data to SPI blocking execution until done.
pub fn blocking_write(&mut self, data: &[u8]) -> Result<(), Error> {
let p = self.inner.regs();
for &b in data {
while !p.sr().read().tnf() {}
p.dr().write(|w| w.set_data(b as _));
while !p.sr().read().rne() {}
let _ = p.dr().read();
}
self.flush()?;
Ok(())
}
/// Transfer data in place to SPI blocking execution until done.
pub fn blocking_transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Error> {
let p = self.inner.regs();
for b in data {
while !p.sr().read().tnf() {}
p.dr().write(|w| w.set_data(*b as _));
while !p.sr().read().rne() {}
*b = p.dr().read().data() as u8;
}
self.flush()?;
Ok(())
}
/// Read data from SPI blocking execution until done.
pub fn blocking_read(&mut self, data: &mut [u8]) -> Result<(), Error> {
let p = self.inner.regs();
for b in data {
while !p.sr().read().tnf() {}
p.dr().write(|w| w.set_data(0));
while !p.sr().read().rne() {}
*b = p.dr().read().data() as u8;
}
self.flush()?;
Ok(())
}
/// Transfer data to SPI blocking execution until done.
pub fn blocking_transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> {
let p = self.inner.regs();
let len = read.len().max(write.len());
for i in 0..len {
let wb = write.get(i).copied().unwrap_or(0);
while !p.sr().read().tnf() {}
p.dr().write(|w| w.set_data(wb as _));
while !p.sr().read().rne() {}
let rb = p.dr().read().data() as u8;
if let Some(r) = read.get_mut(i) {
*r = rb;
}
}
self.flush()?;
Ok(())
}
/// Block execution until SPI is done.
pub fn flush(&mut self) -> Result<(), Error> {
let p = self.inner.regs();
while p.sr().read().bsy() {}
Ok(())
}
/// Set SPI frequency.
pub fn set_frequency(&mut self, freq: u32) {
let (presc, postdiv) = calc_prescs(freq);
let p = self.inner.regs();
// disable
p.cr1().write(|w| w.set_sse(false));
// change stuff
p.cpsr().write(|w| w.set_cpsdvsr(presc));
p.cr0().modify(|w| {
w.set_scr(postdiv);
});
// enable
p.cr1().write(|w| w.set_sse(true));
}
/// Set SPI config.
pub fn set_config(&mut self, config: &Config) {
let p = self.inner.regs();
// disable
p.cr1().write(|w| w.set_sse(false));
// change stuff
Self::apply_config(&self.inner, config);
// enable
p.cr1().write(|w| w.set_sse(true));
}
}
impl<'d, T: Instance> Spi<'d, T, Blocking> {
/// Create an SPI driver in blocking mode.
pub fn new_blocking(
inner: impl Peripheral<P = T> + 'd,
clk: impl Peripheral<P = impl ClkPin<T> + 'd> + 'd,
mosi: impl Peripheral<P = impl MosiPin<T> + 'd> + 'd,
miso: impl Peripheral<P = impl MisoPin<T> + 'd> + 'd,
config: Config,
) -> Self {
into_ref!(clk, mosi, miso);
Self::new_inner(
inner,
Some(clk.map_into()),
Some(mosi.map_into()),
Some(miso.map_into()),
None,
None,
None,
config,
)
}
/// Create an SPI driver in blocking mode supporting writes only.
pub fn new_blocking_txonly(
inner: impl Peripheral<P = T> + 'd,
clk: impl Peripheral<P = impl ClkPin<T> + 'd> + 'd,
mosi: impl Peripheral<P = impl MosiPin<T> + 'd> + 'd,
config: Config,
) -> Self {
into_ref!(clk, mosi);
Self::new_inner(
inner,
Some(clk.map_into()),
Some(mosi.map_into()),
None,
None,
None,
None,
config,
)
}
/// Create an SPI driver in blocking mode supporting reads only.
pub fn new_blocking_rxonly(
inner: impl Peripheral<P = T> + 'd,
clk: impl Peripheral<P = impl ClkPin<T> + 'd> + 'd,
miso: impl Peripheral<P = impl MisoPin<T> + 'd> + 'd,
config: Config,
) -> Self {
into_ref!(clk, miso);
Self::new_inner(
inner,
Some(clk.map_into()),
None,
Some(miso.map_into()),
None,
None,
None,
config,
)
}
}
impl<'d, T: Instance> Spi<'d, T, Async> {
/// Create an SPI driver in async mode supporting DMA operations.
pub fn new(
inner: impl Peripheral<P = T> + 'd,
clk: impl Peripheral<P = impl ClkPin<T> + 'd> + 'd,
mosi: impl Peripheral<P = impl MosiPin<T> + 'd> + 'd,
miso: impl Peripheral<P = impl MisoPin<T> + 'd> + 'd,
tx_dma: impl Peripheral<P = impl Channel> + 'd,
rx_dma: impl Peripheral<P = impl Channel> + 'd,
config: Config,
) -> Self {
into_ref!(tx_dma, rx_dma, clk, mosi, miso);
Self::new_inner(
inner,
Some(clk.map_into()),
Some(mosi.map_into()),
Some(miso.map_into()),
None,
Some(tx_dma.map_into()),
Some(rx_dma.map_into()),
config,
)
}
/// Create an SPI driver in async mode supporting DMA write operations only.
pub fn new_txonly(
inner: impl Peripheral<P = T> + 'd,
clk: impl Peripheral<P = impl ClkPin<T> + 'd> + 'd,
mosi: impl Peripheral<P = impl MosiPin<T> + 'd> + 'd,
tx_dma: impl Peripheral<P = impl Channel> + 'd,
config: Config,
) -> Self {
into_ref!(tx_dma, clk, mosi);
Self::new_inner(
inner,
Some(clk.map_into()),
Some(mosi.map_into()),
None,
None,
Some(tx_dma.map_into()),
None,
config,
)
}
/// Create an SPI driver in async mode supporting DMA read operations only.
pub fn new_rxonly(
inner: impl Peripheral<P = T> + 'd,
clk: impl Peripheral<P = impl ClkPin<T> + 'd> + 'd,
miso: impl Peripheral<P = impl MisoPin<T> + 'd> + 'd,
tx_dma: impl Peripheral<P = impl Channel> + 'd,
rx_dma: impl Peripheral<P = impl Channel> + 'd,
config: Config,
) -> Self {
into_ref!(tx_dma, rx_dma, clk, miso);
Self::new_inner(
inner,
Some(clk.map_into()),
None,
Some(miso.map_into()),
None,
Some(tx_dma.map_into()),
Some(rx_dma.map_into()),
config,
)
}
/// Write data to SPI using DMA.
pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> {
let tx_ch = self.tx_dma.as_mut().unwrap();
let tx_transfer = unsafe {
// If we don't assign future to a variable, the data register pointer
// is held across an await and makes the future non-Send.
crate::dma::write(tx_ch, buffer, self.inner.regs().dr().as_ptr() as *mut _, T::TX_DREQ)
};
tx_transfer.await;
let p = self.inner.regs();
while p.sr().read().bsy() {}
// clear RX FIFO contents to prevent stale reads
while p.sr().read().rne() {
let _: u16 = p.dr().read().data();
}
// clear RX overrun interrupt
p.icr().write(|w| w.set_roric(true));
Ok(())
}
/// Read data from SPI using DMA.
pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
// Start RX first. Transfer starts when TX starts, if RX
// is not started yet we might lose bytes.
let rx_ch = self.rx_dma.as_mut().unwrap();
let rx_transfer = unsafe {
// If we don't assign future to a variable, the data register pointer
// is held across an await and makes the future non-Send.
crate::dma::read(rx_ch, self.inner.regs().dr().as_ptr() as *const _, buffer, T::RX_DREQ)
};
let tx_ch = self.tx_dma.as_mut().unwrap();
let tx_transfer = unsafe {
// If we don't assign future to a variable, the data register pointer
// is held across an await and makes the future non-Send.
crate::dma::write_repeated(
tx_ch,
self.inner.regs().dr().as_ptr() as *mut u8,
buffer.len(),
T::TX_DREQ,
)
};
join(tx_transfer, rx_transfer).await;
Ok(())
}
/// Transfer data to SPI using DMA.
pub async fn transfer(&mut self, rx_buffer: &mut [u8], tx_buffer: &[u8]) -> Result<(), Error> {
self.transfer_inner(rx_buffer, tx_buffer).await
}
/// Transfer data in place to SPI using DMA.
pub async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Error> {
self.transfer_inner(words, words).await
}
async fn transfer_inner(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {
// Start RX first. Transfer starts when TX starts, if RX
// is not started yet we might lose bytes.
let rx_ch = self.rx_dma.as_mut().unwrap();
let rx_transfer = unsafe {
// If we don't assign future to a variable, the data register pointer
// is held across an await and makes the future non-Send.
crate::dma::read(rx_ch, self.inner.regs().dr().as_ptr() as *const _, rx, T::RX_DREQ)
};
let mut tx_ch = self.tx_dma.as_mut().unwrap();
// If we don't assign future to a variable, the data register pointer
// is held across an await and makes the future non-Send.
let tx_transfer = async {
let p = self.inner.regs();
unsafe {
crate::dma::write(&mut tx_ch, tx, p.dr().as_ptr() as *mut _, T::TX_DREQ).await;
if rx.len() > tx.len() {
let write_bytes_len = rx.len() - tx.len();
// write dummy data
// this will disable incrementation of the buffers
crate::dma::write_repeated(tx_ch, p.dr().as_ptr() as *mut u8, write_bytes_len, T::TX_DREQ).await
}
}
};
join(tx_transfer, rx_transfer).await;
// if tx > rx we should clear any overflow of the FIFO SPI buffer
if tx.len() > rx.len() {
let p = self.inner.regs();
while p.sr().read().bsy() {}
// clear RX FIFO contents to prevent stale reads
while p.sr().read().rne() {
let _: u16 = p.dr().read().data();
}
// clear RX overrun interrupt
p.icr().write(|w| w.set_roric(true));
}
Ok(())
}
}
trait SealedMode {}
trait SealedInstance {
const TX_DREQ: pac::dma::vals::TreqSel;
const RX_DREQ: pac::dma::vals::TreqSel;
fn regs(&self) -> pac::spi::Spi;
}
/// Mode.
#[allow(private_bounds)]
pub trait Mode: SealedMode {}
/// SPI instance trait.
#[allow(private_bounds)]
pub trait Instance: SealedInstance {}
macro_rules! impl_instance {
($type:ident, $irq:ident, $tx_dreq:expr, $rx_dreq:expr) => {
impl SealedInstance for peripherals::$type {
const TX_DREQ: pac::dma::vals::TreqSel = $tx_dreq;
const RX_DREQ: pac::dma::vals::TreqSel = $rx_dreq;
fn regs(&self) -> pac::spi::Spi {
pac::$type
}
}
impl Instance for peripherals::$type {}
};
}
impl_instance!(
SPI0,
Spi0,
pac::dma::vals::TreqSel::SPI0_TX,
pac::dma::vals::TreqSel::SPI0_RX
);
impl_instance!(
SPI1,
Spi1,
pac::dma::vals::TreqSel::SPI1_TX,
pac::dma::vals::TreqSel::SPI1_RX
);
/// CLK pin.
pub trait ClkPin<T: Instance>: GpioPin {}
/// CS pin.
pub trait CsPin<T: Instance>: GpioPin {}
/// MOSI pin.
pub trait MosiPin<T: Instance>: GpioPin {}
/// MISO pin.
pub trait MisoPin<T: Instance>: GpioPin {}
macro_rules! impl_pin {
($pin:ident, $instance:ident, $function:ident) => {
impl $function<peripherals::$instance> for peripherals::$pin {}
};
}
impl_pin!(PIN_0, SPI0, MisoPin);
impl_pin!(PIN_1, SPI0, CsPin);
impl_pin!(PIN_2, SPI0, ClkPin);
impl_pin!(PIN_3, SPI0, MosiPin);
impl_pin!(PIN_4, SPI0, MisoPin);
impl_pin!(PIN_5, SPI0, CsPin);
impl_pin!(PIN_6, SPI0, ClkPin);
impl_pin!(PIN_7, SPI0, MosiPin);
impl_pin!(PIN_8, SPI1, MisoPin);
impl_pin!(PIN_9, SPI1, CsPin);
impl_pin!(PIN_10, SPI1, ClkPin);
impl_pin!(PIN_11, SPI1, MosiPin);
impl_pin!(PIN_12, SPI1, MisoPin);
impl_pin!(PIN_13, SPI1, CsPin);
impl_pin!(PIN_14, SPI1, ClkPin);
impl_pin!(PIN_15, SPI1, MosiPin);
impl_pin!(PIN_16, SPI0, MisoPin);
impl_pin!(PIN_17, SPI0, CsPin);
impl_pin!(PIN_18, SPI0, ClkPin);
impl_pin!(PIN_19, SPI0, MosiPin);
impl_pin!(PIN_20, SPI0, MisoPin);
impl_pin!(PIN_21, SPI0, CsPin);
impl_pin!(PIN_22, SPI0, ClkPin);
impl_pin!(PIN_23, SPI0, MosiPin);
impl_pin!(PIN_24, SPI1, MisoPin);
impl_pin!(PIN_25, SPI1, CsPin);
impl_pin!(PIN_26, SPI1, ClkPin);
impl_pin!(PIN_27, SPI1, MosiPin);
impl_pin!(PIN_28, SPI1, MisoPin);
impl_pin!(PIN_29, SPI1, CsPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_30, SPI1, ClkPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_31, SPI1, MosiPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_32, SPI0, MisoPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_33, SPI0, CsPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_34, SPI0, ClkPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_35, SPI0, MosiPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_36, SPI0, MisoPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_37, SPI0, CsPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_38, SPI0, ClkPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_39, SPI0, MosiPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_40, SPI1, MisoPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_41, SPI1, CsPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_42, SPI1, ClkPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_43, SPI1, MosiPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_44, SPI1, MisoPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_45, SPI1, CsPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_46, SPI1, ClkPin);
#[cfg(feature = "rp235xb")]
impl_pin!(PIN_47, SPI1, MosiPin);
macro_rules! impl_mode {
($name:ident) => {
impl SealedMode for $name {}
impl Mode for $name {}
};
}
/// Blocking mode.
pub struct Blocking;
/// Async mode.
pub struct Async;
impl_mode!(Blocking);
impl_mode!(Async);
// ====================
impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::spi::Transfer<u8> for Spi<'d, T, M> {
type Error = Error;
fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> {
self.blocking_transfer_in_place(words)?;
Ok(words)
}
}
impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::spi::Write<u8> for Spi<'d, T, M> {
type Error = Error;
fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
self.blocking_write(words)
}
}
impl embedded_hal_1::spi::Error for Error {
fn kind(&self) -> embedded_hal_1::spi::ErrorKind {
match *self {}
}
}
impl<'d, T: Instance, M: Mode> embedded_hal_1::spi::ErrorType for Spi<'d, T, M> {
type Error = Error;
}
impl<'d, T: Instance, M: Mode> embedded_hal_1::spi::SpiBus<u8> for Spi<'d, T, M> {
fn flush(&mut self) -> Result<(), Self::Error> {
Ok(())
}
fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
self.blocking_transfer(words, &[])
}
fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
self.blocking_write(words)
}
fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
self.blocking_transfer(read, write)
}
fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
self.blocking_transfer_in_place(words)
}
}
impl<'d, T: Instance> embedded_hal_async::spi::SpiBus<u8> for Spi<'d, T, Async> {
async fn flush(&mut self) -> Result<(), Self::Error> {
Ok(())
}
async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
self.write(words).await
}
async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
self.read(words).await
}
async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
self.transfer(read, write).await
}
async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
self.transfer_in_place(words).await
}
}
impl<'d, T: Instance, M: Mode> SetConfig for Spi<'d, T, M> {
type Config = Config;
type ConfigError = ();
fn set_config(&mut self, config: &Self::Config) -> Result<(), ()> {
self.set_config(config);
Ok(())
}
}

View File

@@ -0,0 +1,144 @@
//! Timer driver.
use core::cell::{Cell, RefCell};
use critical_section::CriticalSection;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::blocking_mutex::Mutex;
use embassy_time_driver::Driver;
use embassy_time_queue_utils::Queue;
#[cfg(feature = "rp2040")]
use pac::TIMER;
#[cfg(feature = "_rp235x")]
use pac::TIMER0 as TIMER;
use crate::interrupt::InterruptExt;
use crate::{interrupt, pac};
struct AlarmState {
timestamp: Cell<u64>,
}
unsafe impl Send for AlarmState {}
struct TimerDriver {
alarms: Mutex<CriticalSectionRawMutex, AlarmState>,
queue: Mutex<CriticalSectionRawMutex, RefCell<Queue>>,
}
embassy_time_driver::time_driver_impl!(static DRIVER: TimerDriver = TimerDriver{
alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState {
timestamp: Cell::new(0),
}),
queue: Mutex::new(RefCell::new(Queue::new()))
});
impl Driver for TimerDriver {
fn now(&self) -> u64 {
loop {
let hi = TIMER.timerawh().read();
let lo = TIMER.timerawl().read();
let hi2 = TIMER.timerawh().read();
if hi == hi2 {
return (hi as u64) << 32 | (lo as u64);
}
}
}
fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
critical_section::with(|cs| {
let mut queue = self.queue.borrow(cs).borrow_mut();
if queue.schedule_wake(at, waker) {
let mut next = queue.next_expiration(self.now());
while !self.set_alarm(cs, next) {
next = queue.next_expiration(self.now());
}
}
})
}
}
impl TimerDriver {
fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
let n = 0;
let alarm = &self.alarms.borrow(cs);
alarm.timestamp.set(timestamp);
// Arm it.
// Note that we're not checking the high bits at all. This means the irq may fire early
// if the alarm is more than 72 minutes (2^32 us) in the future. This is OK, since on irq fire
// it is checked if the alarm time has passed.
TIMER.alarm(n).write_value(timestamp as u32);
let now = self.now();
if timestamp <= now {
// If alarm timestamp has passed the alarm will not fire.
// Disarm the alarm and return `false` to indicate that.
TIMER.armed().write(|w| w.set_armed(1 << n));
alarm.timestamp.set(u64::MAX);
false
} else {
true
}
}
fn check_alarm(&self) {
let n = 0;
critical_section::with(|cs| {
// clear the irq
TIMER.intr().write(|w| w.set_alarm(n, true));
let alarm = &self.alarms.borrow(cs);
let timestamp = alarm.timestamp.get();
if timestamp <= self.now() {
self.trigger_alarm(cs)
} else {
// Not elapsed, arm it again.
// This can happen if it was set more than 2^32 us in the future.
TIMER.alarm(n).write_value(timestamp as u32);
}
});
}
fn trigger_alarm(&self, cs: CriticalSection) {
let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());
while !self.set_alarm(cs, next) {
next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());
}
}
}
/// safety: must be called exactly once at bootup
pub unsafe fn init() {
// init alarms
critical_section::with(|cs| {
let alarm = DRIVER.alarms.borrow(cs);
alarm.timestamp.set(u64::MAX);
});
// enable irq
TIMER.inte().write(|w| {
w.set_alarm(0, true);
});
#[cfg(feature = "rp2040")]
{
interrupt::TIMER_IRQ_0.enable();
}
#[cfg(feature = "_rp235x")]
{
interrupt::TIMER0_IRQ_0.enable();
}
}
#[cfg(all(feature = "rt", feature = "rp2040"))]
#[interrupt]
fn TIMER_IRQ_0() {
DRIVER.check_alarm()
}
#[cfg(all(feature = "rt", feature = "_rp235x"))]
#[interrupt]
fn TIMER0_IRQ_0() {
DRIVER.check_alarm()
}

405
embassy-rp/src/trng.rs Normal file
View File

@@ -0,0 +1,405 @@
//! True Random Number Generator (TRNG) driver.
use core::future::poll_fn;
use core::marker::PhantomData;
use core::ops::Not;
use core::task::Poll;
use embassy_hal_internal::Peripheral;
use embassy_sync::waitqueue::AtomicWaker;
use rand_core::Error;
use crate::interrupt::typelevel::{Binding, Interrupt};
use crate::peripherals::TRNG;
use crate::{interrupt, pac};
trait SealedInstance {
fn regs() -> pac::trng::Trng;
fn waker() -> &'static AtomicWaker;
}
/// TRNG peripheral instance.
#[allow(private_bounds)]
pub trait Instance: SealedInstance {
/// Interrupt for this peripheral.
type Interrupt: Interrupt;
}
impl SealedInstance for TRNG {
fn regs() -> rp_pac::trng::Trng {
pac::TRNG
}
fn waker() -> &'static AtomicWaker {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
}
impl Instance for TRNG {
type Interrupt = interrupt::typelevel::TRNG_IRQ;
}
#[derive(Copy, Clone, Debug)]
#[allow(missing_docs)]
/// TRNG ROSC Inverter chain length options.
pub enum InverterChainLength {
None = 0,
One,
Two,
Three,
Four,
}
impl From<InverterChainLength> for u8 {
fn from(value: InverterChainLength) -> Self {
value as u8
}
}
/// Configuration for the TRNG.
///
/// - Three built in entropy checks
/// - ROSC frequency controlled by selecting one of ROSC chain lengths
/// - Sample period in terms of system clock ticks
///
///
/// Default configuration is based on the following from documentation:
///
/// ----
///
/// RP2350 Datasheet 12.12.2
///
/// ...
///
/// When configuring the TRNG block, consider the following principles:
/// • As average generation time increases, result quality increases and failed entropy checks decrease.
/// • A low sample count decreases average generation time, but increases the chance of NIST test-failing results and
/// failed entropy checks.
/// For acceptable results with an average generation time of about 2 milliseconds, use ROSC chain length settings of 0 or
/// 1 and sample count settings of 20-25.
///
/// ---
///
/// Note, Pico SDK and Bootrom don't use any of the entropy checks and sample the ROSC directly
/// by setting the sample period to 0. Random data collected this way is then passed through
/// either hardware accelerated SHA256 (Bootrom) or xoroshiro128** (version 1.0!).
#[non_exhaustive]
#[derive(Copy, Clone, Debug)]
pub struct Config {
/// Bypass TRNG autocorrelation test
pub disable_autocorrelation_test: bool,
/// Bypass CRNGT test
pub disable_crngt_test: bool,
/// When set, the Von-Neuman balancer is bypassed (including the
/// 32 consecutive bits test)
pub disable_von_neumann_balancer: bool,
/// Sets the number of rng_clk cycles between two consecutive
/// ring oscillator samples.
/// Note: If the von Neumann decorrelator is bypassed, the minimum value for
/// sample counter must not be less than seventeen
pub sample_count: u32,
/// Selects the number of inverters (out of four possible
/// selections) in the ring oscillator (the entropy source). Higher values select
/// longer inverter chain lengths.
pub inverter_chain_length: InverterChainLength,
}
impl Default for Config {
fn default() -> Self {
Config {
disable_autocorrelation_test: true,
disable_crngt_test: true,
disable_von_neumann_balancer: true,
sample_count: 25,
inverter_chain_length: InverterChainLength::One,
}
}
}
/// True Random Number Generator Driver for RP2350
///
/// This driver provides async and blocking options.
///
/// See [Config] for configuration details.
///
/// Usage example:
/// ```no_run
/// use embassy_executor::Spawner;
/// use embassy_rp::trng::Trng;
/// use embassy_rp::peripherals::TRNG;
/// use embassy_rp::bind_interrupts;
///
/// bind_interrupts!(struct Irqs {
/// TRNG_IRQ => embassy_rp::trng::InterruptHandler<TRNG>;
/// });
///
/// #[embassy_executor::main]
/// async fn main(spawner: Spawner) {
/// let peripherals = embassy_rp::init(Default::default());
/// let mut trng = Trng::new(peripherals.TRNG, Irqs, embassy_rp::trng::Config::default());
///
/// let mut randomness = [0u8; 58];
/// loop {
/// trng.fill_bytes(&mut randomness).await;
/// assert_ne!(randomness, [0u8; 58]);
/// }
///}
/// ```
pub struct Trng<'d, T: Instance> {
phantom: PhantomData<&'d mut T>,
}
/// 12.12.1. Overview
/// On request, the TRNG block generates a block of 192 entropy bits generated by automatically processing a series of
/// periodic samples from the TRNG blocks internal Ring Oscillator (ROSC).
const TRNG_BLOCK_SIZE_BITS: usize = 192;
const TRNG_BLOCK_SIZE_BYTES: usize = TRNG_BLOCK_SIZE_BITS / 8;
impl<'d, T: Instance> Trng<'d, T> {
/// Create a new TRNG driver.
pub fn new(
_trng: impl Peripheral<P = T> + 'd,
_irq: impl Binding<T::Interrupt, InterruptHandler<T>> + 'd,
config: Config,
) -> Self {
let regs = T::regs();
regs.rng_imr().write(|w| w.set_ehr_valid_int_mask(false));
let trng_config_register = regs.trng_config();
trng_config_register.write(|w| {
w.set_rnd_src_sel(config.inverter_chain_length.clone().into());
});
let sample_count_register = regs.sample_cnt1();
sample_count_register.write(|w| {
*w = config.sample_count;
});
let debug_control_register = regs.trng_debug_control();
debug_control_register.write(|w| {
w.set_auto_correlate_bypass(config.disable_autocorrelation_test);
w.set_trng_crngt_bypass(config.disable_crngt_test);
w.set_vnc_bypass(config.disable_von_neumann_balancer)
});
Trng { phantom: PhantomData }
}
fn start_rng(&self) {
let regs = T::regs();
let source_enable_register = regs.rnd_source_enable();
// Enable TRNG ROSC
source_enable_register.write(|w| w.set_rnd_src_en(true));
}
fn stop_rng(&self) {
let regs = T::regs();
let source_enable_register = regs.rnd_source_enable();
source_enable_register.write(|w| w.set_rnd_src_en(false));
let reset_bits_counter_register = regs.rst_bits_counter();
reset_bits_counter_register.write(|w| w.set_rst_bits_counter(true));
}
fn enable_irq(&self) {
unsafe { T::Interrupt::enable() }
}
fn disable_irq(&self) {
T::Interrupt::disable();
}
fn blocking_wait_for_successful_generation(&self) {
let regs = T::regs();
let trng_busy_register = regs.trng_busy();
let trng_valid_register = regs.trng_valid();
let mut success = false;
while success.not() {
while trng_busy_register.read().trng_busy() {}
if trng_valid_register.read().ehr_valid().not() {
if regs.rng_isr().read().autocorr_err() {
regs.trng_sw_reset().write(|w| w.set_trng_sw_reset(true));
} else {
panic!("RNG not busy, but ehr is not valid!")
}
} else {
success = true
}
}
}
fn read_ehr_registers_into_array(&mut self, buffer: &mut [u8; TRNG_BLOCK_SIZE_BYTES]) {
let regs = T::regs();
let ehr_data_regs = [
regs.ehr_data0(),
regs.ehr_data1(),
regs.ehr_data2(),
regs.ehr_data3(),
regs.ehr_data4(),
regs.ehr_data5(),
];
for (i, reg) in ehr_data_regs.iter().enumerate() {
buffer[i * 4..i * 4 + 4].copy_from_slice(&reg.read().to_ne_bytes());
}
}
fn blocking_read_ehr_registers_into_array(&mut self, buffer: &mut [u8; TRNG_BLOCK_SIZE_BYTES]) {
self.blocking_wait_for_successful_generation();
self.read_ehr_registers_into_array(buffer);
}
/// Fill the buffer with random bytes, async version.
pub async fn fill_bytes(&mut self, destination: &mut [u8]) {
if destination.is_empty() {
return; // Nothing to fill
}
self.start_rng();
self.enable_irq();
let mut bytes_transferred = 0usize;
let mut buffer = [0u8; TRNG_BLOCK_SIZE_BYTES];
let regs = T::regs();
let trng_busy_register = regs.trng_busy();
let trng_valid_register = regs.trng_valid();
let waker = T::waker();
let destination_length = destination.len();
poll_fn(|context| {
waker.register(context.waker());
if bytes_transferred == destination_length {
self.stop_rng();
self.disable_irq();
Poll::Ready(())
} else {
if trng_busy_register.read().trng_busy() {
Poll::Pending
} else {
if trng_valid_register.read().ehr_valid().not() {
panic!("RNG not busy, but ehr is not valid!")
}
self.read_ehr_registers_into_array(&mut buffer);
let remaining = destination_length - bytes_transferred;
if remaining > TRNG_BLOCK_SIZE_BYTES {
destination[bytes_transferred..bytes_transferred + TRNG_BLOCK_SIZE_BYTES]
.copy_from_slice(&buffer);
bytes_transferred += TRNG_BLOCK_SIZE_BYTES
} else {
destination[bytes_transferred..bytes_transferred + remaining]
.copy_from_slice(&buffer[0..remaining]);
bytes_transferred += remaining
}
if bytes_transferred == destination_length {
self.stop_rng();
self.disable_irq();
Poll::Ready(())
} else {
Poll::Pending
}
}
}
})
.await
}
/// Fill the buffer with random bytes, blocking version.
pub fn blocking_fill_bytes(&mut self, destination: &mut [u8]) {
if destination.is_empty() {
return; // Nothing to fill
}
self.start_rng();
let mut buffer = [0u8; TRNG_BLOCK_SIZE_BYTES];
for chunk in destination.chunks_mut(TRNG_BLOCK_SIZE_BYTES) {
self.blocking_wait_for_successful_generation();
self.blocking_read_ehr_registers_into_array(&mut buffer);
chunk.copy_from_slice(&buffer[..chunk.len()])
}
self.stop_rng()
}
/// Return a random u32, blocking.
pub fn blocking_next_u32(&mut self) -> u32 {
let regs = T::regs();
self.start_rng();
self.blocking_wait_for_successful_generation();
// 12.12.3 After successful generation, read the last result register, EHR_DATA[5] to
// clear all of the result registers.
let result = regs.ehr_data5().read();
self.stop_rng();
result
}
/// Return a random u64, blocking.
pub fn blocking_next_u64(&mut self) -> u64 {
let regs = T::regs();
self.start_rng();
self.blocking_wait_for_successful_generation();
let low = regs.ehr_data4().read() as u64;
// 12.12.3 After successful generation, read the last result register, EHR_DATA[5] to
// clear all of the result registers.
let result = (regs.ehr_data5().read() as u64) << 32 | low;
self.stop_rng();
result
}
}
impl<'d, T: Instance> rand_core::RngCore for Trng<'d, T> {
fn next_u32(&mut self) -> u32 {
self.blocking_next_u32()
}
fn next_u64(&mut self) -> u64 {
self.blocking_next_u64()
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
self.blocking_fill_bytes(dest)
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
self.blocking_fill_bytes(dest);
Ok(())
}
}
/// TRNG interrupt handler.
pub struct InterruptHandler<T: Instance> {
_trng: PhantomData<T>,
}
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
unsafe fn on_interrupt() {
let regs = T::regs();
let isr = regs.rng_isr().read();
// Clear ehr bit
regs.rng_icr().write(|w| {
w.set_ehr_valid(true);
});
if isr.ehr_valid() {
T::waker().wake();
} else {
// 12.12.5. List of Registers
// ...
// TRNG: RNG_ISR Register
// ...
// AUTOCORR_ERR: 1 indicates Autocorrelation test failed four times in a row.
// When set, RNG ceases functioning until next reset
if isr.autocorr_err() {
warn!("TRNG Autocorrect error! Resetting TRNG");
regs.trng_sw_reset().write(|w| {
w.set_trng_sw_reset(true);
});
}
}
}
}

View File

@@ -0,0 +1,839 @@
//! Buffered UART driver.
use core::future::Future;
use core::slice;
use atomic_polyfill::AtomicU8;
use embassy_hal_internal::atomic_ring_buffer::RingBuffer;
use super::*;
pub struct State {
tx_waker: AtomicWaker,
tx_buf: RingBuffer,
rx_waker: AtomicWaker,
rx_buf: RingBuffer,
rx_error: AtomicU8,
}
// these must match bits 8..11 in UARTDR
const RXE_OVERRUN: u8 = 8;
const RXE_BREAK: u8 = 4;
const RXE_PARITY: u8 = 2;
const RXE_FRAMING: u8 = 1;
impl State {
pub const fn new() -> Self {
Self {
rx_buf: RingBuffer::new(),
tx_buf: RingBuffer::new(),
rx_waker: AtomicWaker::new(),
tx_waker: AtomicWaker::new(),
rx_error: AtomicU8::new(0),
}
}
}
/// Buffered UART driver.
pub struct BufferedUart<'d, T: Instance> {
pub(crate) rx: BufferedUartRx<'d, T>,
pub(crate) tx: BufferedUartTx<'d, T>,
}
/// Buffered UART RX handle.
pub struct BufferedUartRx<'d, T: Instance> {
pub(crate) phantom: PhantomData<&'d mut T>,
}
/// Buffered UART TX handle.
pub struct BufferedUartTx<'d, T: Instance> {
pub(crate) phantom: PhantomData<&'d mut T>,
}
pub(crate) fn init_buffers<'d, T: Instance + 'd>(
_irq: impl Binding<T::Interrupt, BufferedInterruptHandler<T>>,
tx_buffer: Option<&'d mut [u8]>,
rx_buffer: Option<&'d mut [u8]>,
) {
let state = T::buffered_state();
if let Some(tx_buffer) = tx_buffer {
let len = tx_buffer.len();
unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), len) };
}
if let Some(rx_buffer) = rx_buffer {
let len = rx_buffer.len();
unsafe { state.rx_buf.init(rx_buffer.as_mut_ptr(), len) };
}
// From the datasheet:
// "The transmit interrupt is based on a transition through a level, rather
// than on the level itself. When the interrupt and the UART is enabled
// before any data is written to the transmit FIFO the interrupt is not set.
// The interrupt is only set, after written data leaves the single location
// of the transmit FIFO and it becomes empty."
//
// This means we can leave the interrupt enabled the whole time as long as
// we clear it after it happens. The downside is that the we manually have
// to pend the ISR when we want data transmission to start.
let regs = T::regs();
regs.uartimsc().write(|w| {
w.set_rxim(true);
w.set_rtim(true);
w.set_txim(true);
});
T::Interrupt::unpend();
unsafe { T::Interrupt::enable() };
}
impl<'d, T: Instance> BufferedUart<'d, T> {
/// Create a buffered UART instance.
pub fn new(
_uart: impl Peripheral<P = T> + 'd,
irq: impl Binding<T::Interrupt, BufferedInterruptHandler<T>>,
tx: impl Peripheral<P = impl TxPin<T>> + 'd,
rx: impl Peripheral<P = impl RxPin<T>> + 'd,
tx_buffer: &'d mut [u8],
rx_buffer: &'d mut [u8],
config: Config,
) -> Self {
into_ref!(tx, rx);
super::Uart::<'d, T, Async>::init(Some(tx.map_into()), Some(rx.map_into()), None, None, config);
init_buffers::<T>(irq, Some(tx_buffer), Some(rx_buffer));
Self {
rx: BufferedUartRx { phantom: PhantomData },
tx: BufferedUartTx { phantom: PhantomData },
}
}
/// Create a buffered UART instance with flow control.
pub fn new_with_rtscts(
_uart: impl Peripheral<P = T> + 'd,
irq: impl Binding<T::Interrupt, BufferedInterruptHandler<T>>,
tx: impl Peripheral<P = impl TxPin<T>> + 'd,
rx: impl Peripheral<P = impl RxPin<T>> + 'd,
rts: impl Peripheral<P = impl RtsPin<T>> + 'd,
cts: impl Peripheral<P = impl CtsPin<T>> + 'd,
tx_buffer: &'d mut [u8],
rx_buffer: &'d mut [u8],
config: Config,
) -> Self {
into_ref!(tx, rx, cts, rts);
super::Uart::<'d, T, Async>::init(
Some(tx.map_into()),
Some(rx.map_into()),
Some(rts.map_into()),
Some(cts.map_into()),
config,
);
init_buffers::<T>(irq, Some(tx_buffer), Some(rx_buffer));
Self {
rx: BufferedUartRx { phantom: PhantomData },
tx: BufferedUartTx { phantom: PhantomData },
}
}
/// Write to UART TX buffer blocking execution until done.
pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<usize, Error> {
self.tx.blocking_write(buffer)
}
/// Flush UART TX blocking execution until done.
pub fn blocking_flush(&mut self) -> Result<(), Error> {
self.tx.blocking_flush()
}
/// Read from UART RX buffer blocking execution until done.
pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<usize, Error> {
self.rx.blocking_read(buffer)
}
/// Check if UART is busy transmitting.
pub fn busy(&self) -> bool {
self.tx.busy()
}
/// Wait until TX is empty and send break condition.
pub async fn send_break(&mut self, bits: u32) {
self.tx.send_break(bits).await
}
/// sets baudrate on runtime
pub fn set_baudrate(&mut self, baudrate: u32) {
super::Uart::<'d, T, Async>::set_baudrate_inner(baudrate);
}
/// Split into separate RX and TX handles.
pub fn split(self) -> (BufferedUartTx<'d, T>, BufferedUartRx<'d, T>) {
(self.tx, self.rx)
}
/// Split the Uart into a transmitter and receiver by mutable reference,
/// which is particularly useful when having two tasks correlating to
/// transmitting and receiving.
pub fn split_ref(&mut self) -> (&mut BufferedUartTx<'d, T>, &mut BufferedUartRx<'d, T>) {
(&mut self.tx, &mut self.rx)
}
}
impl<'d, T: Instance> BufferedUartRx<'d, T> {
/// Create a new buffered UART RX.
pub fn new(
_uart: impl Peripheral<P = T> + 'd,
irq: impl Binding<T::Interrupt, BufferedInterruptHandler<T>>,
rx: impl Peripheral<P = impl RxPin<T>> + 'd,
rx_buffer: &'d mut [u8],
config: Config,
) -> Self {
into_ref!(rx);
super::Uart::<'d, T, Async>::init(None, Some(rx.map_into()), None, None, config);
init_buffers::<T>(irq, None, Some(rx_buffer));
Self { phantom: PhantomData }
}
/// Create a new buffered UART RX with flow control.
pub fn new_with_rts(
_uart: impl Peripheral<P = T> + 'd,
irq: impl Binding<T::Interrupt, BufferedInterruptHandler<T>>,
rx: impl Peripheral<P = impl RxPin<T>> + 'd,
rts: impl Peripheral<P = impl RtsPin<T>> + 'd,
rx_buffer: &'d mut [u8],
config: Config,
) -> Self {
into_ref!(rx, rts);
super::Uart::<'d, T, Async>::init(None, Some(rx.map_into()), Some(rts.map_into()), None, config);
init_buffers::<T>(irq, None, Some(rx_buffer));
Self { phantom: PhantomData }
}
fn read<'a>(buf: &'a mut [u8]) -> impl Future<Output = Result<usize, Error>> + 'a
where
T: 'd,
{
poll_fn(move |cx| {
if let Poll::Ready(r) = Self::try_read(buf) {
return Poll::Ready(r);
}
T::buffered_state().rx_waker.register(cx.waker());
Poll::Pending
})
}
fn get_rx_error() -> Option<Error> {
let errs = T::buffered_state().rx_error.swap(0, Ordering::Relaxed);
if errs & RXE_OVERRUN != 0 {
Some(Error::Overrun)
} else if errs & RXE_BREAK != 0 {
Some(Error::Break)
} else if errs & RXE_PARITY != 0 {
Some(Error::Parity)
} else if errs & RXE_FRAMING != 0 {
Some(Error::Framing)
} else {
None
}
}
fn try_read(buf: &mut [u8]) -> Poll<Result<usize, Error>>
where
T: 'd,
{
if buf.is_empty() {
return Poll::Ready(Ok(0));
}
let state = T::buffered_state();
let mut rx_reader = unsafe { state.rx_buf.reader() };
let n = rx_reader.pop(|data| {
let n = data.len().min(buf.len());
buf[..n].copy_from_slice(&data[..n]);
n
});
let result = if n == 0 {
match Self::get_rx_error() {
None => return Poll::Pending,
Some(e) => Err(e),
}
} else {
Ok(n)
};
// (Re-)Enable the interrupt to receive more data in case it was
// disabled because the buffer was full or errors were detected.
let regs = T::regs();
regs.uartimsc().write_set(|w| {
w.set_rxim(true);
w.set_rtim(true);
});
Poll::Ready(result)
}
/// Read from UART RX buffer blocking execution until done.
pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
loop {
match Self::try_read(buf) {
Poll::Ready(res) => return res,
Poll::Pending => continue,
}
}
}
fn fill_buf<'a>() -> impl Future<Output = Result<&'a [u8], Error>>
where
T: 'd,
{
poll_fn(move |cx| {
let state = T::buffered_state();
let mut rx_reader = unsafe { state.rx_buf.reader() };
let (p, n) = rx_reader.pop_buf();
let result = if n == 0 {
match Self::get_rx_error() {
None => {
state.rx_waker.register(cx.waker());
return Poll::Pending;
}
Some(e) => Err(e),
}
} else {
let buf = unsafe { slice::from_raw_parts(p, n) };
Ok(buf)
};
Poll::Ready(result)
})
}
fn consume(amt: usize) {
let state = T::buffered_state();
let mut rx_reader = unsafe { state.rx_buf.reader() };
rx_reader.pop_done(amt);
// (Re-)Enable the interrupt to receive more data in case it was
// disabled because the buffer was full or errors were detected.
let regs = T::regs();
regs.uartimsc().write_set(|w| {
w.set_rxim(true);
w.set_rtim(true);
});
}
/// we are ready to read if there is data in the buffer
fn read_ready() -> Result<bool, Error> {
let state = T::buffered_state();
Ok(!state.rx_buf.is_empty())
}
}
impl<'d, T: Instance> BufferedUartTx<'d, T> {
/// Create a new buffered UART TX.
pub fn new(
_uart: impl Peripheral<P = T> + 'd,
irq: impl Binding<T::Interrupt, BufferedInterruptHandler<T>>,
tx: impl Peripheral<P = impl TxPin<T>> + 'd,
tx_buffer: &'d mut [u8],
config: Config,
) -> Self {
into_ref!(tx);
super::Uart::<'d, T, Async>::init(Some(tx.map_into()), None, None, None, config);
init_buffers::<T>(irq, Some(tx_buffer), None);
Self { phantom: PhantomData }
}
/// Create a new buffered UART TX with flow control.
pub fn new_with_cts(
_uart: impl Peripheral<P = T> + 'd,
irq: impl Binding<T::Interrupt, BufferedInterruptHandler<T>>,
tx: impl Peripheral<P = impl TxPin<T>> + 'd,
cts: impl Peripheral<P = impl CtsPin<T>> + 'd,
tx_buffer: &'d mut [u8],
config: Config,
) -> Self {
into_ref!(tx, cts);
super::Uart::<'d, T, Async>::init(Some(tx.map_into()), None, None, Some(cts.map_into()), config);
init_buffers::<T>(irq, Some(tx_buffer), None);
Self { phantom: PhantomData }
}
fn write(buf: &[u8]) -> impl Future<Output = Result<usize, Error>> + '_ {
poll_fn(move |cx| {
if buf.is_empty() {
return Poll::Ready(Ok(0));
}
let state = T::buffered_state();
let mut tx_writer = unsafe { state.tx_buf.writer() };
let n = tx_writer.push(|data| {
let n = data.len().min(buf.len());
data[..n].copy_from_slice(&buf[..n]);
n
});
if n == 0 {
state.tx_waker.register(cx.waker());
return Poll::Pending;
}
// The TX interrupt only triggers when the there was data in the
// FIFO and the number of bytes drops below a threshold. When the
// FIFO was empty we have to manually pend the interrupt to shovel
// TX data from the buffer into the FIFO.
T::Interrupt::pend();
Poll::Ready(Ok(n))
})
}
fn flush() -> impl Future<Output = Result<(), Error>> {
poll_fn(move |cx| {
let state = T::buffered_state();
if !state.tx_buf.is_empty() {
state.tx_waker.register(cx.waker());
return Poll::Pending;
}
Poll::Ready(Ok(()))
})
}
/// Write to UART TX buffer blocking execution until done.
pub fn blocking_write(&mut self, buf: &[u8]) -> Result<usize, Error> {
if buf.is_empty() {
return Ok(0);
}
loop {
let state = T::buffered_state();
let mut tx_writer = unsafe { state.tx_buf.writer() };
let n = tx_writer.push(|data| {
let n = data.len().min(buf.len());
data[..n].copy_from_slice(&buf[..n]);
n
});
if n != 0 {
// The TX interrupt only triggers when the there was data in the
// FIFO and the number of bytes drops below a threshold. When the
// FIFO was empty we have to manually pend the interrupt to shovel
// TX data from the buffer into the FIFO.
T::Interrupt::pend();
return Ok(n);
}
}
}
/// Flush UART TX blocking execution until done.
pub fn blocking_flush(&mut self) -> Result<(), Error> {
loop {
let state = T::buffered_state();
if state.tx_buf.is_empty() {
return Ok(());
}
}
}
/// Check if UART is busy.
pub fn busy(&self) -> bool {
T::regs().uartfr().read().busy()
}
/// Assert a break condition after waiting for the transmit buffers to empty,
/// for the specified number of bit times. This condition must be asserted
/// for at least two frame times to be effective, `bits` will adjusted
/// according to frame size, parity, and stop bit settings to ensure this.
///
/// This method may block for a long amount of time since it has to wait
/// for the transmit fifo to empty, which may take a while on slow links.
pub async fn send_break(&mut self, bits: u32) {
let regs = T::regs();
let bits = bits.max({
let lcr = regs.uartlcr_h().read();
let width = lcr.wlen() as u32 + 5;
let parity = lcr.pen() as u32;
let stops = 1 + lcr.stp2() as u32;
2 * (1 + width + parity + stops)
});
let divx64 = (((regs.uartibrd().read().baud_divint() as u32) << 6)
+ regs.uartfbrd().read().baud_divfrac() as u32) as u64;
let div_clk = clk_peri_freq() as u64 * 64;
let wait_usecs = (1_000_000 * bits as u64 * divx64 * 16 + div_clk - 1) / div_clk;
Self::flush().await.unwrap();
while self.busy() {}
regs.uartlcr_h().write_set(|w| w.set_brk(true));
Timer::after_micros(wait_usecs).await;
regs.uartlcr_h().write_clear(|w| w.set_brk(true));
}
}
impl<'d, T: Instance> Drop for BufferedUartRx<'d, T> {
fn drop(&mut self) {
let state = T::buffered_state();
unsafe { state.rx_buf.deinit() }
// TX is inactive if the buffer is not available.
// We can now unregister the interrupt handler
if !state.tx_buf.is_available() {
T::Interrupt::disable();
}
}
}
impl<'d, T: Instance> Drop for BufferedUartTx<'d, T> {
fn drop(&mut self) {
let state = T::buffered_state();
unsafe { state.tx_buf.deinit() }
// RX is inactive if the buffer is not available.
// We can now unregister the interrupt handler
if !state.rx_buf.is_available() {
T::Interrupt::disable();
}
}
}
/// Interrupt handler.
pub struct BufferedInterruptHandler<T: Instance> {
_uart: PhantomData<T>,
}
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for BufferedInterruptHandler<T> {
unsafe fn on_interrupt() {
let r = T::regs();
if r.uartdmacr().read().rxdmae() {
return;
}
let s = T::buffered_state();
// Clear TX and error interrupt flags
// RX interrupt flags are cleared by reading from the FIFO.
let ris = r.uartris().read();
r.uarticr().write(|w| {
w.set_txic(ris.txris());
w.set_feic(ris.feris());
w.set_peic(ris.peris());
w.set_beic(ris.beris());
w.set_oeic(ris.oeris());
});
// Errors
if ris.feris() {
warn!("Framing error");
}
if ris.peris() {
warn!("Parity error");
}
if ris.beris() {
warn!("Break error");
}
if ris.oeris() {
warn!("Overrun error");
}
// RX
if s.rx_buf.is_available() {
let mut rx_writer = unsafe { s.rx_buf.writer() };
let rx_buf = rx_writer.push_slice();
let mut n_read = 0;
let mut error = false;
for rx_byte in rx_buf {
if r.uartfr().read().rxfe() {
break;
}
let dr = r.uartdr().read();
if (dr.0 >> 8) != 0 {
s.rx_error.fetch_or((dr.0 >> 8) as u8, Ordering::Relaxed);
error = true;
// only fill the buffer with valid characters. the current character is fine
// if the error is an overrun, but if we add it to the buffer we'll report
// the overrun one character too late. drop it instead and pretend we were
// a bit slower at draining the rx fifo than we actually were.
// this is consistent with blocking uart error reporting.
break;
}
*rx_byte = dr.data();
n_read += 1;
}
if n_read > 0 {
rx_writer.push_done(n_read);
s.rx_waker.wake();
} else if error {
s.rx_waker.wake();
}
// Disable any further RX interrupts when the buffer becomes full or
// errors have occurred. This lets us buffer additional errors in the
// fifo without needing more error storage locations, and most applications
// will want to do a full reset of their uart state anyway once an error
// has happened.
if s.rx_buf.is_full() || error {
r.uartimsc().write_clear(|w| {
w.set_rxim(true);
w.set_rtim(true);
});
}
}
// TX
if s.tx_buf.is_available() {
let mut tx_reader = unsafe { s.tx_buf.reader() };
let tx_buf = tx_reader.pop_slice();
let mut n_written = 0;
for tx_byte in tx_buf.iter_mut() {
if r.uartfr().read().txff() {
break;
}
r.uartdr().write(|w| w.set_data(*tx_byte));
n_written += 1;
}
if n_written > 0 {
tx_reader.pop_done(n_written);
s.tx_waker.wake();
}
// The TX interrupt only triggers once when the FIFO threshold is
// crossed. No need to disable it when the buffer becomes empty
// as it does re-trigger anymore once we have cleared it.
}
}
}
impl embedded_io::Error for Error {
fn kind(&self) -> embedded_io::ErrorKind {
embedded_io::ErrorKind::Other
}
}
impl<'d, T: Instance> embedded_io_async::ErrorType for BufferedUart<'d, T> {
type Error = Error;
}
impl<'d, T: Instance> embedded_io_async::ErrorType for BufferedUartRx<'d, T> {
type Error = Error;
}
impl<'d, T: Instance> embedded_io_async::ErrorType for BufferedUartTx<'d, T> {
type Error = Error;
}
impl<'d, T: Instance + 'd> embedded_io_async::Read for BufferedUart<'d, T> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
BufferedUartRx::<'d, T>::read(buf).await
}
}
impl<'d, T: Instance + 'd> embedded_io_async::Read for BufferedUartRx<'d, T> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
Self::read(buf).await
}
}
impl<'d, T: Instance + 'd> embedded_io_async::ReadReady for BufferedUart<'d, T> {
fn read_ready(&mut self) -> Result<bool, Self::Error> {
BufferedUartRx::<'d, T>::read_ready()
}
}
impl<'d, T: Instance + 'd> embedded_io_async::ReadReady for BufferedUartRx<'d, T> {
fn read_ready(&mut self) -> Result<bool, Self::Error> {
Self::read_ready()
}
}
impl<'d, T: Instance + 'd> embedded_io_async::BufRead for BufferedUart<'d, T> {
async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> {
BufferedUartRx::<'d, T>::fill_buf().await
}
fn consume(&mut self, amt: usize) {
BufferedUartRx::<'d, T>::consume(amt)
}
}
impl<'d, T: Instance + 'd> embedded_io_async::BufRead for BufferedUartRx<'d, T> {
async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> {
Self::fill_buf().await
}
fn consume(&mut self, amt: usize) {
Self::consume(amt)
}
}
impl<'d, T: Instance + 'd> embedded_io_async::Write for BufferedUart<'d, T> {
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
BufferedUartTx::<'d, T>::write(buf).await
}
async fn flush(&mut self) -> Result<(), Self::Error> {
BufferedUartTx::<'d, T>::flush().await
}
}
impl<'d, T: Instance + 'd> embedded_io_async::Write for BufferedUartTx<'d, T> {
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
Self::write(buf).await
}
async fn flush(&mut self) -> Result<(), Self::Error> {
Self::flush().await
}
}
impl<'d, T: Instance + 'd> embedded_io::Read for BufferedUart<'d, T> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.rx.blocking_read(buf)
}
}
impl<'d, T: Instance + 'd> embedded_io::Read for BufferedUartRx<'d, T> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.blocking_read(buf)
}
}
impl<'d, T: Instance + 'd> embedded_io::Write for BufferedUart<'d, T> {
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.tx.blocking_write(buf)
}
fn flush(&mut self) -> Result<(), Self::Error> {
self.tx.blocking_flush()
}
}
impl<'d, T: Instance + 'd> embedded_io::Write for BufferedUartTx<'d, T> {
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.blocking_write(buf)
}
fn flush(&mut self) -> Result<(), Self::Error> {
self.blocking_flush()
}
}
impl<'d, T: Instance> embedded_hal_02::serial::Read<u8> for BufferedUartRx<'d, T> {
type Error = Error;
fn read(&mut self) -> Result<u8, nb::Error<Self::Error>> {
let r = T::regs();
if r.uartfr().read().rxfe() {
return Err(nb::Error::WouldBlock);
}
let dr = r.uartdr().read();
if dr.oe() {
Err(nb::Error::Other(Error::Overrun))
} else if dr.be() {
Err(nb::Error::Other(Error::Break))
} else if dr.pe() {
Err(nb::Error::Other(Error::Parity))
} else if dr.fe() {
Err(nb::Error::Other(Error::Framing))
} else {
Ok(dr.data())
}
}
}
impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write<u8> for BufferedUartTx<'d, T> {
type Error = Error;
fn bwrite_all(&mut self, mut buffer: &[u8]) -> Result<(), Self::Error> {
while !buffer.is_empty() {
match self.blocking_write(buffer) {
Ok(0) => panic!("zero-length write."),
Ok(n) => buffer = &buffer[n..],
Err(e) => return Err(e),
}
}
Ok(())
}
fn bflush(&mut self) -> Result<(), Self::Error> {
self.blocking_flush()
}
}
impl<'d, T: Instance> embedded_hal_02::serial::Read<u8> for BufferedUart<'d, T> {
type Error = Error;
fn read(&mut self) -> Result<u8, nb::Error<Self::Error>> {
embedded_hal_02::serial::Read::read(&mut self.rx)
}
}
impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write<u8> for BufferedUart<'d, T> {
type Error = Error;
fn bwrite_all(&mut self, mut buffer: &[u8]) -> Result<(), Self::Error> {
while !buffer.is_empty() {
match self.blocking_write(buffer) {
Ok(0) => panic!("zero-length write."),
Ok(n) => buffer = &buffer[n..],
Err(e) => return Err(e),
}
}
Ok(())
}
fn bflush(&mut self) -> Result<(), Self::Error> {
self.blocking_flush()
}
}
impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUartRx<'d, T> {
type Error = Error;
}
impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUartTx<'d, T> {
type Error = Error;
}
impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUart<'d, T> {
type Error = Error;
}
impl<'d, T: Instance> embedded_hal_nb::serial::Read for BufferedUartRx<'d, T> {
fn read(&mut self) -> nb::Result<u8, Self::Error> {
embedded_hal_02::serial::Read::read(self)
}
}
impl<'d, T: Instance> embedded_hal_nb::serial::Write for BufferedUartTx<'d, T> {
fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> {
self.blocking_write(&[char]).map(drop).map_err(nb::Error::Other)
}
fn flush(&mut self) -> nb::Result<(), Self::Error> {
self.blocking_flush().map_err(nb::Error::Other)
}
}
impl<'d, T: Instance> embedded_hal_nb::serial::Read for BufferedUart<'d, T> {
fn read(&mut self) -> Result<u8, nb::Error<Self::Error>> {
embedded_hal_02::serial::Read::read(&mut self.rx)
}
}
impl<'d, T: Instance> embedded_hal_nb::serial::Write for BufferedUart<'d, T> {
fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> {
self.blocking_write(&[char]).map(drop).map_err(nb::Error::Other)
}
fn flush(&mut self) -> nb::Result<(), Self::Error> {
self.blocking_flush().map_err(nb::Error::Other)
}
}

1509
embassy-rp/src/uart/mod.rs Normal file

File diff suppressed because it is too large Load Diff

827
embassy-rp/src/usb.rs Normal file
View File

@@ -0,0 +1,827 @@
//! USB driver.
use core::future::poll_fn;
use core::marker::PhantomData;
use core::slice;
use core::sync::atomic::{compiler_fence, Ordering};
use core::task::Poll;
use embassy_sync::waitqueue::AtomicWaker;
use embassy_usb_driver as driver;
use embassy_usb_driver::{
Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointInfo, EndpointType, Event, Unsupported,
};
use crate::interrupt::typelevel::{Binding, Interrupt};
use crate::{interrupt, pac, peripherals, Peripheral, RegExt};
trait SealedInstance {
fn regs() -> crate::pac::usb::Usb;
fn dpram() -> crate::pac::usb_dpram::UsbDpram;
}
/// USB peripheral instance.
#[allow(private_bounds)]
pub trait Instance: SealedInstance + 'static {
/// Interrupt for this peripheral.
type Interrupt: interrupt::typelevel::Interrupt;
}
impl crate::usb::SealedInstance for peripherals::USB {
fn regs() -> pac::usb::Usb {
pac::USB
}
fn dpram() -> crate::pac::usb_dpram::UsbDpram {
pac::USB_DPRAM
}
}
impl crate::usb::Instance for peripherals::USB {
type Interrupt = crate::interrupt::typelevel::USBCTRL_IRQ;
}
const EP_COUNT: usize = 16;
const EP_MEMORY_SIZE: usize = 4096;
const EP_MEMORY: *mut u8 = pac::USB_DPRAM.as_ptr() as *mut u8;
static BUS_WAKER: AtomicWaker = AtomicWaker::new();
static EP_IN_WAKERS: [AtomicWaker; EP_COUNT] = [const { AtomicWaker::new() }; EP_COUNT];
static EP_OUT_WAKERS: [AtomicWaker; EP_COUNT] = [const { AtomicWaker::new() }; EP_COUNT];
struct EndpointBuffer<T: Instance> {
addr: u16,
len: u16,
_phantom: PhantomData<T>,
}
impl<T: Instance> EndpointBuffer<T> {
const fn new(addr: u16, len: u16) -> Self {
Self {
addr,
len,
_phantom: PhantomData,
}
}
fn read(&mut self, buf: &mut [u8]) {
assert!(buf.len() <= self.len as usize);
compiler_fence(Ordering::SeqCst);
let mem = unsafe { slice::from_raw_parts(EP_MEMORY.add(self.addr as _), buf.len()) };
buf.copy_from_slice(mem);
compiler_fence(Ordering::SeqCst);
}
fn write(&mut self, buf: &[u8]) {
assert!(buf.len() <= self.len as usize);
compiler_fence(Ordering::SeqCst);
let mem = unsafe { slice::from_raw_parts_mut(EP_MEMORY.add(self.addr as _), buf.len()) };
mem.copy_from_slice(buf);
compiler_fence(Ordering::SeqCst);
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
struct EndpointData {
ep_type: EndpointType, // only valid if used
max_packet_size: u16,
used: bool,
}
impl EndpointData {
const fn new() -> Self {
Self {
ep_type: EndpointType::Bulk,
max_packet_size: 0,
used: false,
}
}
}
/// RP2040 USB driver handle.
pub struct Driver<'d, T: Instance> {
phantom: PhantomData<&'d mut T>,
ep_in: [EndpointData; EP_COUNT],
ep_out: [EndpointData; EP_COUNT],
ep_mem_free: u16, // first free address in EP mem, in bytes.
}
impl<'d, T: Instance> Driver<'d, T> {
/// Create a new USB driver.
pub fn new(_usb: impl Peripheral<P = T> + 'd, _irq: impl Binding<T::Interrupt, InterruptHandler<T>>) -> Self {
T::Interrupt::unpend();
unsafe { T::Interrupt::enable() };
let regs = T::regs();
unsafe {
// zero fill regs
let p = regs.as_ptr() as *mut u32;
for i in 0..0x9c / 4 {
p.add(i).write_volatile(0)
}
// zero fill epmem
let p = EP_MEMORY as *mut u32;
for i in 0..0x100 / 4 {
p.add(i).write_volatile(0)
}
}
regs.usb_muxing().write(|w| {
w.set_to_phy(true);
w.set_softcon(true);
});
regs.usb_pwr().write(|w| {
w.set_vbus_detect(true);
w.set_vbus_detect_override_en(true);
});
regs.main_ctrl().write(|w| {
w.set_controller_en(true);
});
// Initialize the bus so that it signals that power is available
BUS_WAKER.wake();
Self {
phantom: PhantomData,
ep_in: [EndpointData::new(); EP_COUNT],
ep_out: [EndpointData::new(); EP_COUNT],
ep_mem_free: 0x180, // data buffer region
}
}
fn alloc_endpoint<D: Dir>(
&mut self,
ep_type: EndpointType,
max_packet_size: u16,
interval_ms: u8,
) -> Result<Endpoint<'d, T, D>, driver::EndpointAllocError> {
trace!(
"allocating type={:?} mps={:?} interval_ms={}, dir={:?}",
ep_type,
max_packet_size,
interval_ms,
D::dir()
);
let alloc = match D::dir() {
Direction::Out => &mut self.ep_out,
Direction::In => &mut self.ep_in,
};
let index = alloc.iter_mut().enumerate().find(|(i, ep)| {
if *i == 0 {
return false; // reserved for control pipe
}
!ep.used
});
let (index, ep) = index.ok_or(EndpointAllocError)?;
assert!(!ep.used);
// as per datasheet, the maximum buffer size is 64, except for isochronous
// endpoints, which are allowed to be up to 1023 bytes.
if (ep_type != EndpointType::Isochronous && max_packet_size > 64) || max_packet_size > 1023 {
warn!("max_packet_size too high: {}", max_packet_size);
return Err(EndpointAllocError);
}
// ep mem addrs must be 64-byte aligned, so there's no point in trying
// to allocate smaller chunks to save memory.
let len = (max_packet_size + 63) / 64 * 64;
let addr = self.ep_mem_free;
if addr + len > EP_MEMORY_SIZE as u16 {
warn!("Endpoint memory full");
return Err(EndpointAllocError);
}
self.ep_mem_free += len;
let buf = EndpointBuffer {
addr,
len,
_phantom: PhantomData,
};
trace!(" index={} addr={} len={}", index, buf.addr, buf.len);
ep.ep_type = ep_type;
ep.used = true;
ep.max_packet_size = max_packet_size;
let ep_type_reg = match ep_type {
EndpointType::Bulk => pac::usb_dpram::vals::EpControlEndpointType::BULK,
EndpointType::Control => pac::usb_dpram::vals::EpControlEndpointType::CONTROL,
EndpointType::Interrupt => pac::usb_dpram::vals::EpControlEndpointType::INTERRUPT,
EndpointType::Isochronous => pac::usb_dpram::vals::EpControlEndpointType::ISOCHRONOUS,
};
match D::dir() {
Direction::Out => T::dpram().ep_out_control(index - 1).write(|w| {
w.set_enable(false);
w.set_buffer_address(addr);
w.set_interrupt_per_buff(true);
w.set_endpoint_type(ep_type_reg);
}),
Direction::In => T::dpram().ep_in_control(index - 1).write(|w| {
w.set_enable(false);
w.set_buffer_address(addr);
w.set_interrupt_per_buff(true);
w.set_endpoint_type(ep_type_reg);
}),
}
Ok(Endpoint {
_phantom: PhantomData,
info: EndpointInfo {
addr: EndpointAddress::from_parts(index, D::dir()),
ep_type,
max_packet_size,
interval_ms,
},
buf,
})
}
}
/// USB interrupt handler.
pub struct InterruptHandler<T: Instance> {
_uart: PhantomData<T>,
}
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
unsafe fn on_interrupt() {
let regs = T::regs();
//let x = regs.istr().read().0;
//trace!("USB IRQ: {:08x}", x);
let ints = regs.ints().read();
if ints.bus_reset() {
regs.inte().write_clear(|w| w.set_bus_reset(true));
BUS_WAKER.wake();
}
if ints.dev_resume_from_host() {
regs.inte().write_clear(|w| w.set_dev_resume_from_host(true));
BUS_WAKER.wake();
}
if ints.dev_suspend() {
regs.inte().write_clear(|w| w.set_dev_suspend(true));
BUS_WAKER.wake();
}
if ints.setup_req() {
regs.inte().write_clear(|w| w.set_setup_req(true));
EP_OUT_WAKERS[0].wake();
}
if ints.buff_status() {
let s = regs.buff_status().read();
regs.buff_status().write_value(s);
for i in 0..EP_COUNT {
if s.ep_in(i) {
EP_IN_WAKERS[i].wake();
}
if s.ep_out(i) {
EP_OUT_WAKERS[i].wake();
}
}
}
}
}
impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> {
type EndpointOut = Endpoint<'d, T, Out>;
type EndpointIn = Endpoint<'d, T, In>;
type ControlPipe = ControlPipe<'d, T>;
type Bus = Bus<'d, T>;
fn alloc_endpoint_in(
&mut self,
ep_type: EndpointType,
max_packet_size: u16,
interval_ms: u8,
) -> Result<Self::EndpointIn, driver::EndpointAllocError> {
self.alloc_endpoint(ep_type, max_packet_size, interval_ms)
}
fn alloc_endpoint_out(
&mut self,
ep_type: EndpointType,
max_packet_size: u16,
interval_ms: u8,
) -> Result<Self::EndpointOut, driver::EndpointAllocError> {
self.alloc_endpoint(ep_type, max_packet_size, interval_ms)
}
fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) {
let regs = T::regs();
regs.inte().write(|w| {
w.set_bus_reset(true);
w.set_buff_status(true);
w.set_dev_resume_from_host(true);
w.set_dev_suspend(true);
w.set_setup_req(true);
});
regs.int_ep_ctrl().write(|w| {
w.set_int_ep_active(0xFFFE); // all EPs
});
regs.sie_ctrl().write(|w| {
w.set_ep0_int_1buf(true);
w.set_pullup_en(true);
});
trace!("enabled");
(
Bus {
phantom: PhantomData,
inited: false,
ep_out: self.ep_out,
},
ControlPipe {
_phantom: PhantomData,
max_packet_size: control_max_packet_size,
},
)
}
}
/// Type representing the RP USB bus.
pub struct Bus<'d, T: Instance> {
phantom: PhantomData<&'d mut T>,
ep_out: [EndpointData; EP_COUNT],
inited: bool,
}
impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
async fn poll(&mut self) -> Event {
poll_fn(move |cx| {
BUS_WAKER.register(cx.waker());
// TODO: implement VBUS detection.
if !self.inited {
self.inited = true;
return Poll::Ready(Event::PowerDetected);
}
let regs = T::regs();
let siestatus = regs.sie_status().read();
let intrstatus = regs.intr().read();
if siestatus.resume() || intrstatus.dev_resume_from_host() {
regs.sie_status().write(|w| w.set_resume(true));
return Poll::Ready(Event::Resume);
}
if siestatus.bus_reset() {
regs.sie_status().write(|w| {
w.set_bus_reset(true);
w.set_setup_rec(true);
});
regs.buff_status().write(|w| w.0 = 0xFFFF_FFFF);
regs.addr_endp().write(|w| w.set_address(0));
for i in 1..EP_COUNT {
T::dpram().ep_in_control(i - 1).modify(|w| w.set_enable(false));
T::dpram().ep_out_control(i - 1).modify(|w| w.set_enable(false));
}
for w in &EP_IN_WAKERS {
w.wake()
}
for w in &EP_OUT_WAKERS {
w.wake()
}
return Poll::Ready(Event::Reset);
}
if siestatus.suspended() && intrstatus.dev_suspend() {
regs.sie_status().write(|w| w.set_suspended(true));
return Poll::Ready(Event::Suspend);
}
// no pending event. Reenable all irqs.
regs.inte().write_set(|w| {
w.set_bus_reset(true);
w.set_dev_resume_from_host(true);
w.set_dev_suspend(true);
});
Poll::Pending
})
.await
}
fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) {
let n = ep_addr.index();
if n == 0 {
T::regs().ep_stall_arm().modify(|w| {
if ep_addr.is_in() {
w.set_ep0_in(stalled);
} else {
w.set_ep0_out(stalled);
}
});
}
let ctrl = if ep_addr.is_in() {
T::dpram().ep_in_buffer_control(n)
} else {
T::dpram().ep_out_buffer_control(n)
};
ctrl.modify(|w| w.set_stall(stalled));
let wakers = if ep_addr.is_in() { &EP_IN_WAKERS } else { &EP_OUT_WAKERS };
wakers[n].wake();
}
fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool {
let n = ep_addr.index();
let ctrl = if ep_addr.is_in() {
T::dpram().ep_in_buffer_control(n)
} else {
T::dpram().ep_out_buffer_control(n)
};
ctrl.read().stall()
}
fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) {
trace!("set_enabled {:?} {}", ep_addr, enabled);
if ep_addr.index() == 0 {
return;
}
let n = ep_addr.index();
match ep_addr.direction() {
Direction::In => {
T::dpram().ep_in_control(n - 1).modify(|w| w.set_enable(enabled));
T::dpram().ep_in_buffer_control(ep_addr.index()).write(|w| {
w.set_pid(0, true); // first packet is DATA0, but PID is flipped before
});
EP_IN_WAKERS[n].wake();
}
Direction::Out => {
T::dpram().ep_out_control(n - 1).modify(|w| w.set_enable(enabled));
T::dpram().ep_out_buffer_control(ep_addr.index()).write(|w| {
w.set_pid(0, false);
w.set_length(0, self.ep_out[n].max_packet_size);
});
cortex_m::asm::delay(12);
T::dpram().ep_out_buffer_control(ep_addr.index()).write(|w| {
w.set_pid(0, false);
w.set_length(0, self.ep_out[n].max_packet_size);
w.set_available(0, true);
});
EP_OUT_WAKERS[n].wake();
}
}
}
async fn enable(&mut self) {}
async fn disable(&mut self) {}
async fn remote_wakeup(&mut self) -> Result<(), Unsupported> {
Err(Unsupported)
}
}
trait Dir {
fn dir() -> Direction;
}
/// Type for In direction.
pub enum In {}
impl Dir for In {
fn dir() -> Direction {
Direction::In
}
}
/// Type for Out direction.
pub enum Out {}
impl Dir for Out {
fn dir() -> Direction {
Direction::Out
}
}
/// Endpoint for RP USB driver.
pub struct Endpoint<'d, T: Instance, D> {
_phantom: PhantomData<(&'d mut T, D)>,
info: EndpointInfo,
buf: EndpointBuffer<T>,
}
impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, In> {
fn info(&self) -> &EndpointInfo {
&self.info
}
async fn wait_enabled(&mut self) {
trace!("wait_enabled IN WAITING");
let index = self.info.addr.index();
poll_fn(|cx| {
EP_IN_WAKERS[index].register(cx.waker());
let val = T::dpram().ep_in_control(self.info.addr.index() - 1).read();
if val.enable() {
Poll::Ready(())
} else {
Poll::Pending
}
})
.await;
trace!("wait_enabled IN OK");
}
}
impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, Out> {
fn info(&self) -> &EndpointInfo {
&self.info
}
async fn wait_enabled(&mut self) {
trace!("wait_enabled OUT WAITING");
let index = self.info.addr.index();
poll_fn(|cx| {
EP_OUT_WAKERS[index].register(cx.waker());
let val = T::dpram().ep_out_control(self.info.addr.index() - 1).read();
if val.enable() {
Poll::Ready(())
} else {
Poll::Pending
}
})
.await;
trace!("wait_enabled OUT OK");
}
}
impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, EndpointError> {
trace!("READ WAITING, buf.len() = {}", buf.len());
let index = self.info.addr.index();
let val = poll_fn(|cx| {
EP_OUT_WAKERS[index].register(cx.waker());
let val = T::dpram().ep_out_buffer_control(index).read();
if val.available(0) {
Poll::Pending
} else {
Poll::Ready(val)
}
})
.await;
let rx_len = val.length(0) as usize;
if rx_len > buf.len() {
return Err(EndpointError::BufferOverflow);
}
self.buf.read(&mut buf[..rx_len]);
trace!("READ OK, rx_len = {}", rx_len);
let pid = !val.pid(0);
T::dpram().ep_out_buffer_control(index).write(|w| {
w.set_pid(0, pid);
w.set_length(0, self.info.max_packet_size);
});
cortex_m::asm::delay(12);
T::dpram().ep_out_buffer_control(index).write(|w| {
w.set_pid(0, pid);
w.set_length(0, self.info.max_packet_size);
w.set_available(0, true);
});
Ok(rx_len)
}
}
impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> {
async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> {
if buf.len() > self.info.max_packet_size as usize {
return Err(EndpointError::BufferOverflow);
}
trace!("WRITE WAITING");
let index = self.info.addr.index();
let val = poll_fn(|cx| {
EP_IN_WAKERS[index].register(cx.waker());
let val = T::dpram().ep_in_buffer_control(index).read();
if val.available(0) {
Poll::Pending
} else {
Poll::Ready(val)
}
})
.await;
self.buf.write(buf);
let pid = !val.pid(0);
T::dpram().ep_in_buffer_control(index).write(|w| {
w.set_pid(0, pid);
w.set_length(0, buf.len() as _);
w.set_full(0, true);
});
cortex_m::asm::delay(12);
T::dpram().ep_in_buffer_control(index).write(|w| {
w.set_pid(0, pid);
w.set_length(0, buf.len() as _);
w.set_full(0, true);
w.set_available(0, true);
});
trace!("WRITE OK");
Ok(())
}
}
/// Control pipe for RP USB driver.
pub struct ControlPipe<'d, T: Instance> {
_phantom: PhantomData<&'d mut T>,
max_packet_size: u16,
}
impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> {
fn max_packet_size(&self) -> usize {
64
}
async fn setup(&mut self) -> [u8; 8] {
loop {
trace!("SETUP read waiting");
let regs = T::regs();
regs.inte().write_set(|w| w.set_setup_req(true));
poll_fn(|cx| {
EP_OUT_WAKERS[0].register(cx.waker());
let regs = T::regs();
if regs.sie_status().read().setup_rec() {
Poll::Ready(())
} else {
Poll::Pending
}
})
.await;
let mut buf = [0; 8];
EndpointBuffer::<T>::new(0, 8).read(&mut buf);
let regs = T::regs();
regs.sie_status().write(|w| w.set_setup_rec(true));
// set PID to 0, so (after toggling) first DATA is PID 1
T::dpram().ep_in_buffer_control(0).write(|w| w.set_pid(0, false));
T::dpram().ep_out_buffer_control(0).write(|w| w.set_pid(0, false));
trace!("SETUP read ok");
return buf;
}
}
async fn data_out(&mut self, buf: &mut [u8], first: bool, last: bool) -> Result<usize, EndpointError> {
let bufcontrol = T::dpram().ep_out_buffer_control(0);
let pid = !bufcontrol.read().pid(0);
bufcontrol.write(|w| {
w.set_length(0, self.max_packet_size);
w.set_pid(0, pid);
});
cortex_m::asm::delay(12);
bufcontrol.write(|w| {
w.set_length(0, self.max_packet_size);
w.set_pid(0, pid);
w.set_available(0, true);
});
trace!("control: data_out len={} first={} last={}", buf.len(), first, last);
let val = poll_fn(|cx| {
EP_OUT_WAKERS[0].register(cx.waker());
let val = T::dpram().ep_out_buffer_control(0).read();
if val.available(0) {
Poll::Pending
} else {
Poll::Ready(val)
}
})
.await;
let rx_len = val.length(0) as _;
trace!("control data_out DONE, rx_len = {}", rx_len);
if rx_len > buf.len() {
return Err(EndpointError::BufferOverflow);
}
EndpointBuffer::<T>::new(0x100, 64).read(&mut buf[..rx_len]);
Ok(rx_len)
}
async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError> {
trace!("control: data_in len={} first={} last={}", data.len(), first, last);
if data.len() > 64 {
return Err(EndpointError::BufferOverflow);
}
EndpointBuffer::<T>::new(0x100, 64).write(data);
let bufcontrol = T::dpram().ep_in_buffer_control(0);
let pid = !bufcontrol.read().pid(0);
bufcontrol.write(|w| {
w.set_length(0, data.len() as _);
w.set_pid(0, pid);
w.set_full(0, true);
});
cortex_m::asm::delay(12);
bufcontrol.write(|w| {
w.set_length(0, data.len() as _);
w.set_pid(0, pid);
w.set_full(0, true);
w.set_available(0, true);
});
poll_fn(|cx| {
EP_IN_WAKERS[0].register(cx.waker());
let bufcontrol = T::dpram().ep_in_buffer_control(0);
if bufcontrol.read().available(0) {
Poll::Pending
} else {
Poll::Ready(())
}
})
.await;
trace!("control: data_in DONE");
if last {
// prepare status phase right away.
let bufcontrol = T::dpram().ep_out_buffer_control(0);
bufcontrol.write(|w| {
w.set_length(0, 0);
w.set_pid(0, true);
});
cortex_m::asm::delay(12);
bufcontrol.write(|w| {
w.set_length(0, 0);
w.set_pid(0, true);
w.set_available(0, true);
});
}
Ok(())
}
async fn accept(&mut self) {
trace!("control: accept");
let bufcontrol = T::dpram().ep_in_buffer_control(0);
bufcontrol.write(|w| {
w.set_length(0, 0);
w.set_pid(0, true);
w.set_full(0, true);
});
cortex_m::asm::delay(12);
bufcontrol.write(|w| {
w.set_length(0, 0);
w.set_pid(0, true);
w.set_full(0, true);
w.set_available(0, true);
});
// wait for completion before returning, needed so
// set_address() doesn't happen early.
poll_fn(|cx| {
EP_IN_WAKERS[0].register(cx.waker());
if bufcontrol.read().available(0) {
Poll::Pending
} else {
Poll::Ready(())
}
})
.await;
}
async fn reject(&mut self) {
trace!("control: reject");
let regs = T::regs();
regs.ep_stall_arm().write_set(|w| {
w.set_ep0_in(true);
w.set_ep0_out(true);
});
T::dpram().ep_out_buffer_control(0).write(|w| w.set_stall(true));
T::dpram().ep_in_buffer_control(0).write(|w| w.set_stall(true));
}
async fn accept_set_address(&mut self, addr: u8) {
self.accept().await;
let regs = T::regs();
trace!("setting addr: {}", addr);
regs.addr_endp().write(|w| w.set_address(addr))
}
}

165
embassy-rp/src/watchdog.rs Normal file
View File

@@ -0,0 +1,165 @@
//! Watchdog
//!
//! The watchdog is a countdown timer that can restart parts of the chip if it reaches zero. This can be used to restart the
//! processor if software gets stuck in an infinite loop. The programmer must periodically write a value to the watchdog to
//! stop it from reaching zero.
//!
//! Credit: based on `rp-hal` implementation (also licensed Apache+MIT)
use core::marker::PhantomData;
use embassy_time::Duration;
use crate::pac;
use crate::peripherals::WATCHDOG;
/// The reason for a system reset from the watchdog.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ResetReason {
/// The reset was forced.
Forced,
/// The watchdog was not fed in time.
TimedOut,
}
/// Watchdog peripheral
pub struct Watchdog {
phantom: PhantomData<WATCHDOG>,
load_value: u32, // decremented by 2 per tick (µs)
}
impl Watchdog {
/// Create a new `Watchdog`
pub fn new(_watchdog: WATCHDOG) -> Self {
Self {
phantom: PhantomData,
load_value: 0,
}
}
/// Start tick generation on clk_tick which is driven from clk_ref.
///
/// # Arguments
///
/// * `cycles` - Total number of tick cycles before the next tick is generated.
/// It is expected to be the frequency in MHz of clk_ref.
#[cfg(feature = "rp2040")]
pub fn enable_tick_generation(&mut self, cycles: u8) {
let watchdog = pac::WATCHDOG;
watchdog.tick().write(|w| {
w.set_enable(true);
w.set_cycles(cycles.into())
});
}
/// Defines whether or not the watchdog timer should be paused when processor(s) are in debug mode
/// or when JTAG is accessing bus fabric
pub fn pause_on_debug(&mut self, pause: bool) {
let watchdog = pac::WATCHDOG;
watchdog.ctrl().modify(|w| {
w.set_pause_dbg0(pause);
w.set_pause_dbg1(pause);
w.set_pause_jtag(pause);
})
}
fn load_counter(&self, counter: u32) {
let watchdog = pac::WATCHDOG;
watchdog.load().write_value(pac::watchdog::regs::Load(counter));
}
fn enable(&self, bit: bool) {
let watchdog = pac::WATCHDOG;
watchdog.ctrl().modify(|w| w.set_enable(bit))
}
// Configure which hardware will be reset by the watchdog
// (everything except ROSC, XOSC)
fn configure_wdog_reset_triggers(&self) {
let psm = pac::PSM;
psm.wdsel().write_value(pac::psm::regs::Wdsel(
0x0001ffff & !(0x01 << 0usize) & !(0x01 << 1usize),
));
}
/// Feed the watchdog timer
pub fn feed(&mut self) {
self.load_counter(self.load_value)
}
/// Start the watchdog timer
pub fn start(&mut self, period: Duration) {
const MAX_PERIOD: u32 = 0xFFFFFF;
let delay_us = period.as_micros();
if delay_us > (MAX_PERIOD / 2) as u64 {
panic!("Period cannot exceed {} microseconds", MAX_PERIOD / 2);
}
let delay_us = delay_us as u32;
// Due to a logic error, the watchdog decrements by 2 and
// the load value must be compensated; see RP2040-E1
self.load_value = delay_us * 2;
self.enable(false);
self.configure_wdog_reset_triggers();
self.load_counter(self.load_value);
self.enable(true);
}
/// Trigger a system reset
pub fn trigger_reset(&mut self) {
self.configure_wdog_reset_triggers();
self.pause_on_debug(false);
self.enable(true);
let watchdog = pac::WATCHDOG;
watchdog.ctrl().write(|w| {
w.set_trigger(true);
})
}
/// Store data in scratch register
pub fn set_scratch(&mut self, index: usize, value: u32) {
let watchdog = pac::WATCHDOG;
match index {
0 => watchdog.scratch0().write(|w| *w = value),
1 => watchdog.scratch1().write(|w| *w = value),
2 => watchdog.scratch2().write(|w| *w = value),
3 => watchdog.scratch3().write(|w| *w = value),
4 => watchdog.scratch4().write(|w| *w = value),
5 => watchdog.scratch5().write(|w| *w = value),
6 => watchdog.scratch6().write(|w| *w = value),
7 => watchdog.scratch7().write(|w| *w = value),
_ => panic!("Invalid watchdog scratch index"),
}
}
/// Read data from scratch register
pub fn get_scratch(&mut self, index: usize) -> u32 {
let watchdog = pac::WATCHDOG;
match index {
0 => watchdog.scratch0().read(),
1 => watchdog.scratch1().read(),
2 => watchdog.scratch2().read(),
3 => watchdog.scratch3().read(),
4 => watchdog.scratch4().read(),
5 => watchdog.scratch5().read(),
6 => watchdog.scratch6().read(),
7 => watchdog.scratch7().read(),
_ => panic!("Invalid watchdog scratch index"),
}
}
/// Get the reason for the last system reset, if it was caused by the watchdog.
pub fn reset_reason(&self) -> Option<ResetReason> {
let watchdog = pac::WATCHDOG;
let reason = watchdog.reason().read();
if reason.force() {
Some(ResetReason::Forced)
} else if reason.timer() {
Some(ResetReason::TimedOut)
} else {
None
}
}
}