init project
This commit is contained in:
63
embassy-rp/CHANGELOG.md
Normal file
63
embassy-rp/CHANGELOG.md
Normal 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
176
embassy-rp/Cargo.toml
Normal 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
202
embassy-rp/LICENSE-APACHE
Normal 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
26
embassy-rp/LICENSE-MIT
Normal 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
27
embassy-rp/README.md
Normal 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
39
embassy-rp/build.rs
Normal 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
30
embassy-rp/funcsel.txt
Normal 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
8
embassy-rp/link-rp.x.in
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
SECTIONS {
|
||||
/* ### Boot loader */
|
||||
.boot2 ORIGIN(BOOT2) :
|
||||
{
|
||||
KEEP(*(.boot2));
|
||||
} > BOOT2
|
||||
}
|
||||
469
embassy-rp/src/adc.rs
Normal file
469
embassy-rp/src/adc.rs
Normal 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 pin’s
|
||||
// > digital functions must be disabled by setting IE low and OD
|
||||
// > high in the pin’s 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
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
83
embassy-rp/src/bootsel.rs
Normal 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
1196
embassy-rp/src/clocks.rs
Normal file
File diff suppressed because it is too large
Load Diff
137
embassy-rp/src/critical_section_impl.rs
Normal file
137
embassy-rp/src/critical_section_impl.rs
Normal 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
315
embassy-rp/src/dma.rs
Normal 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
989
embassy-rp/src/flash.rs
Normal 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);
|
||||
92
embassy-rp/src/float/add_sub.rs
Normal file
92
embassy-rp/src/float/add_sub.rs
Normal 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
201
embassy-rp/src/float/cmp.rs
Normal 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
|
||||
}
|
||||
}
|
||||
157
embassy-rp/src/float/conv.rs
Normal file
157
embassy-rp/src/float/conv.rs
Normal 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
139
embassy-rp/src/float/div.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
239
embassy-rp/src/float/functions.rs
Normal file
239
embassy-rp/src/float/functions.rs
Normal 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
150
embassy-rp/src/float/mod.rs
Normal 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;
|
||||
70
embassy-rp/src/float/mul.rs
Normal file
70
embassy-rp/src/float/mul.rs
Normal 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
270
embassy-rp/src/fmt.rs
Normal 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
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
919
embassy-rp/src/i2c.rs
Normal 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
395
embassy-rp/src/i2c_slave.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
476
embassy-rp/src/intrinsics.rs
Normal file
476
embassy-rp/src/intrinsics.rs
Normal 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
759
embassy-rp/src/lib.rs
Normal 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
348
embassy-rp/src/multicore.rs
Normal 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
175
embassy-rp/src/otp.rs
Normal 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
103
embassy-rp/src/pio/instr.rs
Normal 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
1479
embassy-rp/src/pio/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
203
embassy-rp/src/pio_programs/hd44780.rs
Normal file
203
embassy-rp/src/pio_programs/hd44780.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
95
embassy-rp/src/pio_programs/i2s.rs
Normal file
95
embassy-rp/src/pio_programs/i2s.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
10
embassy-rp/src/pio_programs/mod.rs
Normal file
10
embassy-rp/src/pio_programs/mod.rs
Normal 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;
|
||||
109
embassy-rp/src/pio_programs/onewire.rs
Normal file
109
embassy-rp/src/pio_programs/onewire.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
121
embassy-rp/src/pio_programs/pwm.rs
Normal file
121
embassy-rp/src/pio_programs/pwm.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
75
embassy-rp/src/pio_programs/rotary_encoder.rs
Normal file
75
embassy-rp/src/pio_programs/rotary_encoder.rs
Normal 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,
|
||||
}
|
||||
147
embassy-rp/src/pio_programs/stepper.rs
Normal file
147
embassy-rp/src/pio_programs/stepper.rs
Normal 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()() }
|
||||
}
|
||||
}
|
||||
185
embassy-rp/src/pio_programs/uart.rs
Normal file
185
embassy-rp/src/pio_programs/uart.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
118
embassy-rp/src/pio_programs/ws2812.rs
Normal file
118
embassy-rp/src/pio_programs/ws2812.rs
Normal 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
611
embassy-rp/src/pwm.rs
Normal 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);
|
||||
66
embassy-rp/src/relocate.rs
Normal file
66
embassy-rp/src/relocate.rs
Normal 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
15
embassy-rp/src/reset.rs
Normal 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 {}
|
||||
}
|
||||
33
embassy-rp/src/rom_data/mod.rs
Normal file
33
embassy-rp/src/rom_data/mod.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
#, 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.
|
||||
"
|
||||
)]
|
||||
# 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::*;
|
||||
756
embassy-rp/src/rom_data/rp2040.rs
Normal file
756
embassy-rp/src/rom_data/rp2040.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
752
embassy-rp/src/rom_data/rp235x.rs
Normal file
752
embassy-rp/src/rom_data/rp235x.rs
Normal 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 bootrom’s 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
|
||||
}
|
||||
62
embassy-rp/src/rtc/datetime_chrono.rs
Normal file
62
embassy-rp/src/rtc/datetime_chrono.rs
Normal 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))
|
||||
}
|
||||
128
embassy-rp/src/rtc/datetime_no_deps.rs
Normal file
128
embassy-rp/src/rtc/datetime_no_deps.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
100
embassy-rp/src/rtc/filter.rs
Normal file
100
embassy-rp/src/rtc/filter.rs
Normal 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
204
embassy-rp/src/rtc/mod.rs
Normal 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
728
embassy-rp/src/spi.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
144
embassy-rp/src/time_driver.rs
Normal file
144
embassy-rp/src/time_driver.rs
Normal 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
405
embassy-rp/src/trng.rs
Normal 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 block’s 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(®.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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
839
embassy-rp/src/uart/buffered.rs
Normal file
839
embassy-rp/src/uart/buffered.rs
Normal 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
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
827
embassy-rp/src/usb.rs
Normal 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
165
embassy-rp/src/watchdog.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user