Compare commits
201 Commits
embassy-ti
...
stm32-ring
Author | SHA1 | Date | |
---|---|---|---|
b49bc449d7 | |||
a21a2901ac | |||
374c92a4f0 | |||
433422b9f2 | |||
a85b34c1fe | |||
1078f6f4e7 | |||
2bb6e93e86 | |||
2afa08c923 | |||
a61701b756 | |||
7a36072a15 | |||
a167c77d39 | |||
8839f3f62a | |||
ac111f40d8 | |||
3229b5e809 | |||
b2047c4351 | |||
849011b826 | |||
6cec6fa09b | |||
0d224a00e1 | |||
47ae9b7981 | |||
8e22d57447 | |||
5f99ccf54c | |||
54e695b1b2 | |||
8fc92fdf62 | |||
c6424fdc11 | |||
3c31236c10 | |||
6096f0cf4b | |||
a1d45303c3 | |||
7601779693 | |||
1806422763 | |||
00cde67abe | |||
96e8a7ddb9 | |||
25864ae4dc | |||
14e0090cb1 | |||
45843034ec | |||
7757405908 | |||
fc268df6f5 | |||
4ea6662e55 | |||
49455792cb | |||
855c0d1423 | |||
05c36e05f9 | |||
b58b9ff390 | |||
1d5adb8974 | |||
be66e0f7ce | |||
861f49cfd4 | |||
7ab9fe0522 | |||
1c8492bab2 | |||
19588a9e6f | |||
1d2f6667df | |||
ac0ea406f9 | |||
7336b8cd88 | |||
bcbe3040a1 | |||
f4ade6af8b | |||
fa1ec29ae6 | |||
58e727d3b9 | |||
4cd5ed81aa | |||
4618b79b22 | |||
db16b6ff3f | |||
a9074fd09b | |||
f2469776f4 | |||
a10850a6da | |||
ce04b732d1 | |||
ff6748a0d8 | |||
7646f18836 | |||
41fe718ea8 | |||
94c6727b3f | |||
b77794c9a7 | |||
ba886b45b8 | |||
2119b8e1ca | |||
49bed094a3 | |||
49ecd8d7c5 | |||
29cc661dca | |||
3e730aa8b0 | |||
28a3454846 | |||
91cddd50f6 | |||
9d610c6866 | |||
03d6363d5a | |||
d960bf344a | |||
31b54e0fbd | |||
3ba73b5ff4 | |||
8c733c29cc | |||
278818395e | |||
42a8f1671d | |||
1cf26f0eb3 | |||
d91c37dae3 | |||
759d911b50 | |||
a277deeaa5 | |||
cb00fb18cb | |||
f8b359dc5a | |||
edef790e1a | |||
18af9f304a | |||
deadf40c85 | |||
405649ddc7 | |||
e2410cbb6a | |||
52decfb16c | |||
91047c61b9 | |||
054ca17f66 | |||
0d82ebea29 | |||
f729d2d060 | |||
73f25093c7 | |||
a3f727e2e1 | |||
0dea7b02d6 | |||
a4866ad278 | |||
d78edba0d4 | |||
b283f213d9 | |||
ba47fe9c41 | |||
8285263fc2 | |||
cc5bca8e83 | |||
0a2f7b4661 | |||
02c86bca52 | |||
fb27594b2e | |||
a73f9474a0 | |||
3bf41e9a06 | |||
0e01b28d5e | |||
8aca324c2d | |||
7ee9e8322c | |||
43c20dbe65 | |||
8cd117fd5d | |||
f64d1131b6 | |||
b153a5b0d7 | |||
9b51c8f4d4 | |||
510ae7e3dc | |||
f67eb84ec7 | |||
5de6bb3adf | |||
54fe50c685 | |||
837cdacd16 | |||
41e90e22e2 | |||
64b80c2e4d | |||
26f4d7d283 | |||
fdd6e08ed6 | |||
7d64de153f | |||
e24421a393 | |||
4de4039417 | |||
f589247c1f | |||
37181c79d9 | |||
a2ac1eed1b | |||
bfa3cbaf30 | |||
216b120f15 | |||
08d9e5981e | |||
8a9136e4e4 | |||
3260f6b2af | |||
2080d8bb6d | |||
a673b9aa29 | |||
38c5b97df0 | |||
21ea98810a | |||
fbd6eeb748 | |||
dbded7a6ce | |||
efc70debb3 | |||
173c65b543 | |||
a86a100879 | |||
bba8b0ded5 | |||
095f5ef279 | |||
1c68c62ebd | |||
f5216624bb | |||
46227bec1e | |||
e63a34ba21 | |||
82dd7a5f8c | |||
0dfa192992 | |||
e14fa11fc3 | |||
df7ef1d98f | |||
9202dbf32a | |||
4044d728a6 | |||
6acc361109 | |||
27ec29e2c5 | |||
90c1422381 | |||
9e1ddeac86 | |||
99dcbf00c4 | |||
776e001b5b | |||
6ba2bb1a7f | |||
a258e15c23 | |||
7a682ec02a | |||
e9ede443bc | |||
bc550cbfda | |||
8da9c07a65 | |||
bd6bb2d248 | |||
81f10e136a | |||
1fdce6e52a | |||
f395ec44e8 | |||
63941432e3 | |||
be0f93ff37 | |||
224eaaf797 | |||
f681b9d4e5 | |||
650589ab3f | |||
b9fc2a6b33 | |||
3002ee0dcf | |||
ce0e1a5db3 | |||
6a6c673c5f | |||
c1d5f86871 | |||
0289630fe4 | |||
1b86570cfd | |||
f51cbebffd | |||
c309797488 | |||
6b2aaacf83 | |||
ba9afbc26d | |||
5e74926907 | |||
218b44652c | |||
86113e199f | |||
9223b67306 | |||
aff265a7f5 | |||
79cee74151 | |||
62c0b18f10 | |||
a83560c6b1 |
3
.github/workflows/doc.yml
vendored
3
.github/workflows/doc.yml
vendored
@ -61,6 +61,7 @@ jobs:
|
||||
mkdir crates
|
||||
builder ./embassy-boot/boot crates/embassy-boot/git.zup
|
||||
builder ./embassy-boot/nrf crates/embassy-boot-nrf/git.zup
|
||||
builder ./embassy-boot/rp crates/embassy-boot-rp/git.zup
|
||||
builder ./embassy-boot/stm32 crates/embassy-boot-stm32/git.zup
|
||||
builder ./embassy-cortex-m crates/embassy-cortex-m/git.zup
|
||||
builder ./embassy-embedded-hal crates/embassy-embedded-hal/git.zup
|
||||
@ -84,5 +85,3 @@ jobs:
|
||||
echo "${{secrets.KUBECONFIG}}" > ~/.kube/config
|
||||
POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name})
|
||||
kubectl cp crates $POD:/data
|
||||
|
||||
|
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
@ -71,7 +71,7 @@ jobs:
|
||||
|
||||
- name: Test boot
|
||||
working-directory: ./embassy-boot/boot
|
||||
run: cargo test && cargo test --features "ed25519-dalek" && cargo test --features "ed25519-salty"
|
||||
run: cargo test && cargo test --features nightly && cargo test --features "ed25519-dalek,nightly" && cargo test --features "ed25519-salty,nightly"
|
||||
|
||||
- name: Test sync
|
||||
working-directory: ./embassy-sync
|
||||
|
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@ -16,10 +16,11 @@
|
||||
// "embassy-executor/Cargo.toml",
|
||||
// "embassy-sync/Cargo.toml",
|
||||
"examples/nrf52840/Cargo.toml",
|
||||
//"examples/nrf5340/Cargo.toml",
|
||||
// "examples/nrf5340/Cargo.toml",
|
||||
// "examples/nrf-rtos-trace/Cargo.toml",
|
||||
// "examples/rp/Cargo.toml",
|
||||
// "examples/std/Cargo.toml",
|
||||
// "examples/stm32c0/Cargo.toml",
|
||||
// "examples/stm32f0/Cargo.toml",
|
||||
// "examples/stm32f1/Cargo.toml",
|
||||
// "examples/stm32f2/Cargo.toml",
|
||||
@ -28,6 +29,7 @@
|
||||
// "examples/stm32f7/Cargo.toml",
|
||||
// "examples/stm32g0/Cargo.toml",
|
||||
// "examples/stm32g4/Cargo.toml",
|
||||
// "examples/stm32h5/Cargo.toml",
|
||||
// "examples/stm32h7/Cargo.toml",
|
||||
// "examples/stm32l0/Cargo.toml",
|
||||
// "examples/stm32l1/Cargo.toml",
|
||||
@ -35,9 +37,7 @@
|
||||
// "examples/stm32l5/Cargo.toml",
|
||||
// "examples/stm32u5/Cargo.toml",
|
||||
// "examples/stm32wb/Cargo.toml",
|
||||
// "examples/stm32wb55/Cargo.toml",
|
||||
// "examples/stm32wl/Cargo.toml",
|
||||
// "examples/stm32wl55/Cargo.toml",
|
||||
// "examples/wasm/Cargo.toml",
|
||||
],
|
||||
}
|
@ -35,7 +35,7 @@ The <a href="https://docs.embassy.dev/embassy-net/">embassy-net</a> network stac
|
||||
The <a href="https://github.com/embassy-rs/nrf-softdevice">nrf-softdevice</a> crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers.
|
||||
|
||||
- **LoRa** -
|
||||
<a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking on STM32WL wireless microcontrollers and Semtech SX126x and SX127x transceivers.
|
||||
<a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking.
|
||||
|
||||
- **USB** -
|
||||
<a href="https://docs.embassy.dev/embassy-usb/">embassy-usb</a> implements a device-side USB stack. Implementations for common classes such as USB serial (CDC ACM) and USB HID are available, and a rich builder API allows building your own.
|
||||
@ -106,10 +106,10 @@ git submodule init
|
||||
git submodule update
|
||||
```
|
||||
|
||||
- Install `probe-run` with defmt support.
|
||||
- Install `probe-rs-cli` with defmt support.
|
||||
|
||||
```bash
|
||||
cargo install probe-run
|
||||
cargo install probe-rs-cli
|
||||
```
|
||||
|
||||
- Change directory to the sample's base directory. For example:
|
||||
|
9
ci.sh
9
ci.sh
@ -22,6 +22,7 @@ cargo batch \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \
|
||||
--- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \
|
||||
--- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits,defmt,defmt-timestamp-uptime,tick-hz-32_768,generic-queue-8 \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,nightly \
|
||||
@ -68,10 +69,10 @@ cargo batch \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f100c4,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32h503rb,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32h562ag,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \
|
||||
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
|
||||
--- build --release --manifest-path embassy-boot/rp/Cargo.toml --target thumbv6m-none-eabi \
|
||||
--- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \
|
||||
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,nightly \
|
||||
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,nightly \
|
||||
--- build --release --manifest-path embassy-boot/rp/Cargo.toml --target thumbv6m-none-eabi --features nightly \
|
||||
--- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4,nightly \
|
||||
--- build --release --manifest-path docs/modules/ROOT/examples/basic/Cargo.toml --target thumbv7em-none-eabi \
|
||||
--- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-pac/Cargo.toml --target thumbv7em-none-eabi \
|
||||
--- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-hal/Cargo.toml --target thumbv7em-none-eabi \
|
||||
|
@ -9,10 +9,15 @@ export DEFMT_LOG=trace
|
||||
sed -i 's/channel.*/channel = "stable"/g' rust-toolchain.toml
|
||||
|
||||
cargo batch \
|
||||
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \
|
||||
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
|
||||
--- build --release --manifest-path embassy-boot/rp/Cargo.toml --target thumbv6m-none-eabi \
|
||||
--- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features log \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features defmt \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52805,gpiote,time-driver-rtc1 \
|
||||
|
@ -6,7 +6,7 @@ version = "0.1.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
embassy-executor = { version = "0.1.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] }
|
||||
embassy-executor = { version = "0.2.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] }
|
||||
embassy-time = { version = "0.1.0", path = "../../../../../embassy-time", features = ["defmt", "nightly"] }
|
||||
embassy-nrf = { version = "0.1.0", path = "../../../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "nightly"] }
|
||||
|
||||
|
@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
|
||||
cortex-m = "0.7"
|
||||
cortex-m-rt = "0.7"
|
||||
embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"], default-features = false }
|
||||
embassy-executor = { version = "0.1.0", default-features = false, features = ["nightly", "arch-cortex-m", "executor-thread"] }
|
||||
embassy-executor = { version = "0.2.0", default-features = false, features = ["nightly", "arch-cortex-m", "executor-thread"] }
|
||||
|
||||
defmt = "0.3.0"
|
||||
defmt-rtt = "0.3.0"
|
||||
|
@ -29,7 +29,7 @@ log = { version = "0.4", optional = true }
|
||||
ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true }
|
||||
embassy-sync = { version = "0.2.0", path = "../../embassy-sync" }
|
||||
embedded-storage = "0.3.0"
|
||||
embedded-storage-async = "0.4.0"
|
||||
embedded-storage-async = { version = "0.4.0", optional = true}
|
||||
salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true }
|
||||
signature = { version = "1.6.4", default-features = false }
|
||||
|
||||
@ -48,5 +48,7 @@ features = ["rand", "std", "u32_backend"]
|
||||
ed25519-dalek = ["dep:ed25519-dalek", "_verify"]
|
||||
ed25519-salty = ["dep:salty", "_verify"]
|
||||
|
||||
nightly = ["dep:embedded-storage-async"]
|
||||
|
||||
#Internal features
|
||||
_verify = []
|
||||
|
@ -13,11 +13,12 @@ By design, the bootloader does not provide any network capabilities. Networking
|
||||
The bootloader supports different hardware in separate crates:
|
||||
|
||||
* `embassy-boot-nrf` - for the nRF microcontrollers.
|
||||
* `embassy-boot-rp` - for the RP2040 microcontrollers.
|
||||
* `embassy-boot-stm32` - for the STM32 microcontrollers.
|
||||
|
||||
## Minimum supported Rust version (MSRV)
|
||||
|
||||
`embassy-boot` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals.
|
||||
`embassy-boot` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release.
|
||||
|
||||
## License
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
use digest::Digest;
|
||||
use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind};
|
||||
#[cfg(feature = "nightly")]
|
||||
use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash;
|
||||
|
||||
use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC};
|
||||
@ -78,6 +79,7 @@ impl FirmwareUpdater {
|
||||
/// This is useful to check if the bootloader has just done a swap, in order
|
||||
/// to do verifications and self-tests of the new image before calling
|
||||
/// `mark_booted`.
|
||||
#[cfg(feature = "nightly")]
|
||||
pub async fn get_state<F: AsyncNorFlash>(
|
||||
&mut self,
|
||||
state_flash: &mut F,
|
||||
@ -108,7 +110,7 @@ impl FirmwareUpdater {
|
||||
///
|
||||
/// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
|
||||
/// and written to.
|
||||
#[cfg(feature = "_verify")]
|
||||
#[cfg(all(feature = "_verify", feature = "nightly"))]
|
||||
pub async fn verify_and_mark_updated<F: AsyncNorFlash>(
|
||||
&mut self,
|
||||
_state_and_dfu_flash: &mut F,
|
||||
@ -172,6 +174,7 @@ impl FirmwareUpdater {
|
||||
}
|
||||
|
||||
/// Verify the update in DFU with any digest.
|
||||
#[cfg(feature = "nightly")]
|
||||
pub async fn hash<F: AsyncNorFlash, D: Digest>(
|
||||
&mut self,
|
||||
dfu_flash: &mut F,
|
||||
@ -194,7 +197,7 @@ impl FirmwareUpdater {
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
|
||||
#[cfg(not(feature = "_verify"))]
|
||||
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
|
||||
pub async fn mark_updated<F: AsyncNorFlash>(
|
||||
&mut self,
|
||||
state_flash: &mut F,
|
||||
@ -209,6 +212,7 @@ impl FirmwareUpdater {
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
|
||||
#[cfg(feature = "nightly")]
|
||||
pub async fn mark_booted<F: AsyncNorFlash>(
|
||||
&mut self,
|
||||
state_flash: &mut F,
|
||||
@ -218,6 +222,7 @@ impl FirmwareUpdater {
|
||||
self.set_magic(aligned, BOOT_MAGIC, state_flash).await
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
async fn set_magic<F: AsyncNorFlash>(
|
||||
&mut self,
|
||||
aligned: &mut [u8],
|
||||
@ -258,6 +263,7 @@ impl FirmwareUpdater {
|
||||
/// # Safety
|
||||
///
|
||||
/// Failing to meet alignment and size requirements may result in a panic.
|
||||
#[cfg(feature = "nightly")]
|
||||
pub async fn write_firmware<F: AsyncNorFlash>(
|
||||
&mut self,
|
||||
offset: usize,
|
||||
@ -280,6 +286,7 @@ impl FirmwareUpdater {
|
||||
///
|
||||
/// Using this instead of `write_firmware` allows for an optimized API in
|
||||
/// exchange for added complexity.
|
||||
#[cfg(feature = "nightly")]
|
||||
pub async fn prepare_update<F: AsyncNorFlash>(
|
||||
&mut self,
|
||||
dfu_flash: &mut F,
|
||||
@ -513,6 +520,7 @@ mod tests {
|
||||
use crate::mem_flash::MemFlash;
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "nightly")]
|
||||
fn can_verify_sha1() {
|
||||
const STATE: Partition = Partition::new(0, 4096);
|
||||
const DFU: Partition = Partition::new(65536, 131072);
|
||||
|
@ -1,4 +1,4 @@
|
||||
#![feature(async_fn_in_trait)]
|
||||
#![cfg_attr(feature = "nightly", feature(async_fn_in_trait))]
|
||||
#![allow(incomplete_features)]
|
||||
#![no_std]
|
||||
#![warn(missing_docs)]
|
||||
@ -83,7 +83,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "_verify"))]
|
||||
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
|
||||
fn test_swap_state() {
|
||||
const STATE: Partition = Partition::new(0, 4096);
|
||||
const ACTIVE: Partition = Partition::new(4096, 61440);
|
||||
@ -136,7 +136,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "_verify"))]
|
||||
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
|
||||
fn test_separate_flash_active_page_biggest() {
|
||||
const STATE: Partition = Partition::new(2048, 4096);
|
||||
const ACTIVE: Partition = Partition::new(4096, 16384);
|
||||
@ -173,7 +173,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "_verify"))]
|
||||
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
|
||||
fn test_separate_flash_dfu_page_biggest() {
|
||||
const STATE: Partition = Partition::new(2048, 4096);
|
||||
const ACTIVE: Partition = Partition::new(4096, 16384);
|
||||
@ -212,7 +212,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "_verify")]
|
||||
#[cfg(all(feature = "nightly", feature = "_verify"))]
|
||||
fn test_verify() {
|
||||
// The following key setup is based on:
|
||||
// https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example
|
||||
|
@ -3,6 +3,7 @@
|
||||
use core::ops::{Bound, Range, RangeBounds};
|
||||
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
|
||||
#[cfg(feature = "nightly")]
|
||||
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
|
||||
|
||||
pub struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> {
|
||||
@ -134,6 +135,7 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFla
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
@ -148,6 +150,7 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncR
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
|
@ -1,4 +1,5 @@
|
||||
use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
|
||||
#[cfg(feature = "nightly")]
|
||||
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
|
||||
|
||||
/// A region in flash used by the bootloader.
|
||||
@ -23,6 +24,7 @@ impl Partition {
|
||||
}
|
||||
|
||||
/// Read from the partition on the provided flash
|
||||
#[cfg(feature = "nightly")]
|
||||
pub async fn read<F: AsyncReadNorFlash>(
|
||||
&self,
|
||||
flash: &mut F,
|
||||
@ -34,6 +36,7 @@ impl Partition {
|
||||
}
|
||||
|
||||
/// Write to the partition on the provided flash
|
||||
#[cfg(feature = "nightly")]
|
||||
pub async fn write<F: AsyncNorFlash>(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> {
|
||||
let offset = self.from as u32 + offset;
|
||||
flash.write(offset, bytes).await?;
|
||||
@ -42,6 +45,7 @@ impl Partition {
|
||||
}
|
||||
|
||||
/// Erase part of the partition on the provided flash
|
||||
#[cfg(feature = "nightly")]
|
||||
pub async fn erase<F: AsyncNorFlash>(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> {
|
||||
let from = self.from as u32 + from;
|
||||
let to = self.from as u32 + to;
|
||||
@ -51,6 +55,7 @@ impl Partition {
|
||||
}
|
||||
|
||||
/// Erase the entire partition
|
||||
#[cfg(feature = "nightly")]
|
||||
pub(crate) async fn wipe<F: AsyncNorFlash>(&self, flash: &mut F) -> Result<(), F::Error> {
|
||||
let from = self.from as u32;
|
||||
let to = self.to as u32;
|
||||
|
@ -17,12 +17,12 @@ target = "thumbv7em-none-eabi"
|
||||
defmt = { version = "0.3", optional = true }
|
||||
|
||||
embassy-sync = { path = "../../embassy-sync" }
|
||||
embassy-nrf = { path = "../../embassy-nrf", default-features = false, features = ["nightly"] }
|
||||
embassy-nrf = { path = "../../embassy-nrf", default-features = false }
|
||||
embassy-boot = { path = "../boot", default-features = false }
|
||||
cortex-m = { version = "0.7.6" }
|
||||
cortex-m-rt = { version = "0.7" }
|
||||
embedded-storage = "0.3.0"
|
||||
embedded-storage-async = "0.4.0"
|
||||
embedded-storage-async = { version = "0.4.0", optional = true }
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
nrf-softdevice-mbr = { version = "0.1.0", git = "https://github.com/embassy-rs/nrf-softdevice.git", branch = "master", optional = true }
|
||||
@ -36,3 +36,8 @@ defmt = [
|
||||
softdevice = [
|
||||
"nrf-softdevice-mbr",
|
||||
]
|
||||
nightly = [
|
||||
"dep:embedded-storage-async",
|
||||
"embassy-boot/nightly",
|
||||
"embassy-nrf/nightly"
|
||||
]
|
||||
|
@ -13,7 +13,7 @@ An adaptation of `embassy-boot` for nRF.
|
||||
|
||||
## Minimum supported Rust version (MSRV)
|
||||
|
||||
`embassy-boot-nrf` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals.
|
||||
`embassy-boot-nrf` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release.
|
||||
|
||||
## License
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
#![no_std]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
#![warn(missing_docs)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
mod fmt;
|
||||
|
@ -18,14 +18,14 @@ defmt-rtt = { version = "0.4", optional = true }
|
||||
log = { version = "0.4", optional = true }
|
||||
|
||||
embassy-sync = { path = "../../embassy-sync" }
|
||||
embassy-rp = { path = "../../embassy-rp", default-features = false, features = ["nightly"] }
|
||||
embassy-rp = { path = "../../embassy-rp", default-features = false }
|
||||
embassy-boot = { path = "../boot", default-features = false }
|
||||
embassy-time = { path = "../../embassy-time", features = ["nightly"] }
|
||||
embassy-time = { path = "../../embassy-time" }
|
||||
|
||||
cortex-m = { version = "0.7.6" }
|
||||
cortex-m-rt = { version = "0.7" }
|
||||
embedded-storage = "0.3.0"
|
||||
embedded-storage-async = "0.4.0"
|
||||
embedded-storage-async = { version = "0.4.0", optional = true }
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
[features]
|
||||
@ -40,6 +40,12 @@ log = [
|
||||
"embassy-rp/log",
|
||||
]
|
||||
debug = ["defmt-rtt"]
|
||||
nightly = [
|
||||
"dep:embedded-storage-async",
|
||||
"embassy-boot/nightly",
|
||||
"embassy-rp/nightly",
|
||||
"embassy-time/nightly"
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
debug = 2
|
||||
|
@ -13,7 +13,7 @@ NOTE: The applications using this bootloader should not link with the `link-rp.x
|
||||
|
||||
## Minimum supported Rust version (MSRV)
|
||||
|
||||
`embassy-boot-rp` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals.
|
||||
`embassy-boot-rp` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release.
|
||||
|
||||
## License
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
#![no_std]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
#![warn(missing_docs)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
mod fmt;
|
||||
|
@ -19,12 +19,12 @@ defmt-rtt = { version = "0.4", optional = true }
|
||||
log = { version = "0.4", optional = true }
|
||||
|
||||
embassy-sync = { path = "../../embassy-sync" }
|
||||
embassy-stm32 = { path = "../../embassy-stm32", default-features = false, features = ["nightly"] }
|
||||
embassy-stm32 = { path = "../../embassy-stm32", default-features = false }
|
||||
embassy-boot = { path = "../boot", default-features = false }
|
||||
cortex-m = { version = "0.7.6" }
|
||||
cortex-m-rt = { version = "0.7" }
|
||||
embedded-storage = "0.3.0"
|
||||
embedded-storage-async = "0.4.0"
|
||||
embedded-storage-async = { version = "0.4.0", optional = true }
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
[features]
|
||||
@ -39,6 +39,11 @@ log = [
|
||||
"embassy-stm32/log",
|
||||
]
|
||||
debug = ["defmt-rtt"]
|
||||
nightly = [
|
||||
"dep:embedded-storage-async",
|
||||
"embassy-boot/nightly",
|
||||
"embassy-stm32/nightly"
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
debug = 2
|
||||
|
@ -11,7 +11,7 @@ An adaptation of `embassy-boot` for STM32.
|
||||
|
||||
## Minimum supported Rust version (MSRV)
|
||||
|
||||
`embassy-boot-stm32` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals.
|
||||
`embassy-boot-stm32` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release.
|
||||
|
||||
## License
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
#![no_std]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
#![warn(missing_docs)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
mod fmt;
|
||||
|
@ -37,8 +37,8 @@ defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.14", optional = true }
|
||||
|
||||
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
|
||||
embassy-executor = { version = "0.1.0", path = "../embassy-executor"}
|
||||
embassy-macros = { version = "0.1.0", path = "../embassy-macros"}
|
||||
embassy-executor = { version = "0.2.0", path = "../embassy-executor"}
|
||||
embassy-macros = { version = "0.2.0", path = "../embassy-macros"}
|
||||
embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common"}
|
||||
atomic-polyfill = "1.0.1"
|
||||
critical-section = "1.1"
|
||||
|
@ -131,48 +131,6 @@ where
|
||||
type Error = E;
|
||||
}
|
||||
|
||||
#[cfg(feature = "_todo_embedded_hal_serial")]
|
||||
impl<T, E> embedded_hal_async::serial::Read for BlockingAsync<T>
|
||||
where
|
||||
T: serial::Read<u8, Error = E>,
|
||||
E: embedded_hal_1::serial::Error + 'static,
|
||||
{
|
||||
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where T: 'a;
|
||||
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
|
||||
async move {
|
||||
let mut pos = 0;
|
||||
while pos < buf.len() {
|
||||
match self.wrapped.read() {
|
||||
Err(nb::Error::WouldBlock) => {}
|
||||
Err(nb::Error::Other(e)) => return Err(e),
|
||||
Ok(b) => {
|
||||
buf[pos] = b;
|
||||
pos += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "_todo_embedded_hal_serial")]
|
||||
impl<T, E> embedded_hal_async::serial::Write for BlockingAsync<T>
|
||||
where
|
||||
T: blocking::serial::Write<u8, Error = E> + serial::Read<u8, Error = E>,
|
||||
E: embedded_hal_1::serial::Error + 'static,
|
||||
{
|
||||
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where T: 'a;
|
||||
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
|
||||
async move { self.wrapped.bwrite_all(buf) }
|
||||
}
|
||||
|
||||
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where T: 'a;
|
||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
async move { self.wrapped.bflush() }
|
||||
}
|
||||
}
|
||||
|
||||
/// NOR flash wrapper
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
||||
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
|
||||
|
23
embassy-executor/CHANGELOG.md
Normal file
23
embassy-executor/CHANGELOG.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Changelog
|
||||
|
||||
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).
|
||||
|
||||
## 0.2.0 - 2023-04-27
|
||||
|
||||
- Replace unnecessary atomics in runqueue
|
||||
- add Pender, rework Cargo features.
|
||||
- add support for turbo-wakers.
|
||||
- Allow TaskStorage to auto-implement `Sync`
|
||||
- Use AtomicPtr for signal_ctx, removes 1 unsafe.
|
||||
- Replace unsound critical sections with atomics
|
||||
|
||||
## 0.1.1 - 2022-11-23
|
||||
|
||||
- Fix features for documentation
|
||||
|
||||
## 0.1.0 - 2022-11-23
|
||||
|
||||
- First release
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "embassy-executor"
|
||||
version = "0.1.1"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "async/await executor designed for embedded usage"
|
||||
@ -61,7 +61,7 @@ log = { version = "0.4.14", optional = true }
|
||||
rtos-trace = { version = "0.1.2", optional = true }
|
||||
|
||||
futures-util = { version = "0.3.17", default-features = false }
|
||||
embassy-macros = { version = "0.1.0", path = "../embassy-macros" }
|
||||
embassy-macros = { version = "0.2.0", path = "../embassy-macros" }
|
||||
embassy-time = { version = "0.1.0", path = "../embassy-time", optional = true}
|
||||
atomic-polyfill = "1.0.1"
|
||||
critical-section = "1.1"
|
||||
|
@ -4,15 +4,16 @@ use core::ptr::NonNull;
|
||||
use atomic_polyfill::{AtomicPtr, Ordering};
|
||||
|
||||
use super::{TaskHeader, TaskRef};
|
||||
use crate::raw::util::SyncUnsafeCell;
|
||||
|
||||
pub(crate) struct RunQueueItem {
|
||||
next: AtomicPtr<TaskHeader>,
|
||||
next: SyncUnsafeCell<Option<TaskRef>>,
|
||||
}
|
||||
|
||||
impl RunQueueItem {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
next: AtomicPtr::new(ptr::null_mut()),
|
||||
next: SyncUnsafeCell::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -51,7 +52,12 @@ impl RunQueue {
|
||||
self.head
|
||||
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev| {
|
||||
was_empty = prev.is_null();
|
||||
task.header().run_queue_item.next.store(prev, Ordering::Relaxed);
|
||||
unsafe {
|
||||
// safety: the pointer is either null or valid
|
||||
let prev = NonNull::new(prev).map(|ptr| TaskRef::from_ptr(ptr.as_ptr()));
|
||||
// safety: there are no concurrent accesses to `next`
|
||||
task.header().run_queue_item.next.set(prev);
|
||||
}
|
||||
Some(task.as_ptr() as *mut _)
|
||||
})
|
||||
.ok();
|
||||
@ -64,18 +70,19 @@ impl RunQueue {
|
||||
/// and will be processed by the *next* call to `dequeue_all`, *not* the current one.
|
||||
pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) {
|
||||
// Atomically empty the queue.
|
||||
let mut ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel);
|
||||
let ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel);
|
||||
|
||||
// safety: the pointer is either null or valid
|
||||
let mut next = unsafe { NonNull::new(ptr).map(|ptr| TaskRef::from_ptr(ptr.as_ptr())) };
|
||||
|
||||
// Iterate the linked list of tasks that were previously in the queue.
|
||||
while let Some(task) = NonNull::new(ptr) {
|
||||
let task = unsafe { TaskRef::from_ptr(task.as_ptr()) };
|
||||
while let Some(task) = next {
|
||||
// If the task re-enqueues itself, the `next` pointer will get overwritten.
|
||||
// Therefore, first read the next pointer, and only then process the task.
|
||||
let next = task.header().run_queue_item.next.load(Ordering::Relaxed);
|
||||
// safety: there are no concurrent accesses to `next`
|
||||
next = unsafe { task.header().run_queue_item.next.get() };
|
||||
|
||||
on_task(task);
|
||||
|
||||
ptr = next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
#[macro_export]
|
||||
macro_rules! peripherals {
|
||||
macro_rules! peripherals_definition {
|
||||
($($(#[$cfg:meta])? $name:ident),*$(,)?) => {
|
||||
/// Types for the peripheral singletons.
|
||||
pub mod peripherals {
|
||||
@ -26,7 +26,12 @@ macro_rules! peripherals {
|
||||
$crate::impl_peripheral!($name);
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! peripherals_struct {
|
||||
($($(#[$cfg:meta])? $name:ident),*$(,)?) => {
|
||||
/// Struct containing all the peripheral singletons.
|
||||
///
|
||||
/// To obtain the peripherals, you must initialize the HAL, by calling [`crate::init`].
|
||||
@ -76,6 +81,24 @@ macro_rules! peripherals {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! peripherals {
|
||||
($($(#[$cfg:meta])? $name:ident),*$(,)?) => {
|
||||
$crate::peripherals_definition!(
|
||||
$(
|
||||
$(#[$cfg])?
|
||||
$name,
|
||||
)*
|
||||
);
|
||||
$crate::peripherals_struct!(
|
||||
$(
|
||||
$(#[$cfg])?
|
||||
$name,
|
||||
)*
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! into_ref {
|
||||
($($name:ident),*) => {
|
||||
|
@ -7,21 +7,13 @@ license = "MIT OR Apache-2.0"
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-lora-v$VERSION/embassy-lora/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-lora/src/"
|
||||
features = ["time", "defmt"]
|
||||
flavors = [
|
||||
{ name = "sx126x", target = "thumbv7em-none-eabihf", features = ["sx126x"] },
|
||||
{ name = "sx127x", target = "thumbv7em-none-eabihf", features = ["sx127x"] },
|
||||
{ name = "stm32wl", target = "thumbv7em-none-eabihf", features = ["stm32wl", "embassy-stm32?/stm32wl55jc-cm4", "embassy-stm32?/time-driver-any"] },
|
||||
]
|
||||
|
||||
[lib]
|
||||
features = ["stm32wl", "time", "defmt"]
|
||||
target = "thumbv7em-none-eabi"
|
||||
|
||||
[features]
|
||||
sx126x = []
|
||||
sx127x = []
|
||||
stm32wl = ["dep:embassy-stm32"]
|
||||
time = []
|
||||
defmt = ["dep:defmt", "lorawan/defmt", "lorawan-device/defmt"]
|
||||
defmt = ["dep:defmt", "lorawan-device/defmt"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -38,5 +30,5 @@ futures = { version = "0.3.17", default-features = false, features = [ "async-aw
|
||||
embedded-hal = { version = "0.2", features = ["unproven"] }
|
||||
bit_field = { version = "0.10" }
|
||||
|
||||
lorawan-device = { version = "0.8.0", default-features = false, features = ["async"] }
|
||||
lorawan = { version = "0.7.1", default-features = false }
|
||||
lora-phy = { version = "1" }
|
||||
lorawan-device = { version = "0.10.0", default-features = false, features = ["async"] }
|
||||
|
325
embassy-lora/src/iv.rs
Normal file
325
embassy-lora/src/iv.rs
Normal file
@ -0,0 +1,325 @@
|
||||
#[cfg(feature = "stm32wl")]
|
||||
use embassy_stm32::interrupt::*;
|
||||
#[cfg(feature = "stm32wl")]
|
||||
use embassy_stm32::{pac, PeripheralRef};
|
||||
#[cfg(feature = "stm32wl")]
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
#[cfg(feature = "stm32wl")]
|
||||
use embassy_sync::signal::Signal;
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use embedded_hal_async::delay::DelayUs;
|
||||
use embedded_hal_async::digital::Wait;
|
||||
use lora_phy::mod_params::RadioError::*;
|
||||
use lora_phy::mod_params::{BoardType, RadioError};
|
||||
use lora_phy::mod_traits::InterfaceVariant;
|
||||
|
||||
#[cfg(feature = "stm32wl")]
|
||||
static IRQ_SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new();
|
||||
|
||||
#[cfg(feature = "stm32wl")]
|
||||
/// Base for the InterfaceVariant implementation for an stm32wl/sx1262 combination
|
||||
pub struct Stm32wlInterfaceVariant<'a, CTRL> {
|
||||
board_type: BoardType,
|
||||
irq: PeripheralRef<'a, SUBGHZ_RADIO>,
|
||||
rf_switch_rx: Option<CTRL>,
|
||||
rf_switch_tx: Option<CTRL>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "stm32wl")]
|
||||
impl<'a, CTRL> Stm32wlInterfaceVariant<'a, CTRL>
|
||||
where
|
||||
CTRL: OutputPin,
|
||||
{
|
||||
/// Create an InterfaceVariant instance for an stm32wl/sx1262 combination
|
||||
pub fn new(
|
||||
irq: PeripheralRef<'a, SUBGHZ_RADIO>,
|
||||
rf_switch_rx: Option<CTRL>,
|
||||
rf_switch_tx: Option<CTRL>,
|
||||
) -> Result<Self, RadioError> {
|
||||
irq.disable();
|
||||
irq.set_handler(Self::on_interrupt);
|
||||
Ok(Self {
|
||||
board_type: BoardType::Stm32wlSx1262, // updated when associated with a specific LoRa board
|
||||
irq,
|
||||
rf_switch_rx,
|
||||
rf_switch_tx,
|
||||
})
|
||||
}
|
||||
|
||||
fn on_interrupt(_: *mut ()) {
|
||||
unsafe { SUBGHZ_RADIO::steal() }.disable();
|
||||
IRQ_SIGNAL.signal(());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stm32wl")]
|
||||
impl<CTRL> InterfaceVariant for Stm32wlInterfaceVariant<'_, CTRL>
|
||||
where
|
||||
CTRL: OutputPin,
|
||||
{
|
||||
fn set_board_type(&mut self, board_type: BoardType) {
|
||||
self.board_type = board_type;
|
||||
}
|
||||
async fn set_nss_low(&mut self) -> Result<(), RadioError> {
|
||||
let pwr = pac::PWR;
|
||||
unsafe {
|
||||
pwr.subghzspicr().modify(|w| w.set_nss(pac::pwr::vals::Nss::LOW));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn set_nss_high(&mut self) -> Result<(), RadioError> {
|
||||
let pwr = pac::PWR;
|
||||
unsafe {
|
||||
pwr.subghzspicr().modify(|w| w.set_nss(pac::pwr::vals::Nss::HIGH));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn reset(&mut self, _delay: &mut impl DelayUs) -> Result<(), RadioError> {
|
||||
let rcc = pac::RCC;
|
||||
unsafe {
|
||||
rcc.csr().modify(|w| w.set_rfrst(true));
|
||||
rcc.csr().modify(|w| w.set_rfrst(false));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn wait_on_busy(&mut self) -> Result<(), RadioError> {
|
||||
let pwr = pac::PWR;
|
||||
while unsafe { pwr.sr2().read().rfbusys() == pac::pwr::vals::Rfbusys::BUSY } {}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn await_irq(&mut self) -> Result<(), RadioError> {
|
||||
self.irq.enable();
|
||||
IRQ_SIGNAL.wait().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn enable_rf_switch_rx(&mut self) -> Result<(), RadioError> {
|
||||
match &mut self.rf_switch_tx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx)?,
|
||||
None => (),
|
||||
};
|
||||
match &mut self.rf_switch_rx {
|
||||
Some(pin) => pin.set_high().map_err(|_| RfSwitchRx),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
async fn enable_rf_switch_tx(&mut self) -> Result<(), RadioError> {
|
||||
match &mut self.rf_switch_rx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
|
||||
None => (),
|
||||
};
|
||||
match &mut self.rf_switch_tx {
|
||||
Some(pin) => pin.set_high().map_err(|_| RfSwitchTx),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
async fn disable_rf_switch(&mut self) -> Result<(), RadioError> {
|
||||
match &mut self.rf_switch_rx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
|
||||
None => (),
|
||||
};
|
||||
match &mut self.rf_switch_tx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Base for the InterfaceVariant implementation for an stm32l0/sx1276 combination
|
||||
pub struct Stm32l0InterfaceVariant<CTRL, WAIT> {
|
||||
board_type: BoardType,
|
||||
nss: CTRL,
|
||||
reset: CTRL,
|
||||
irq: WAIT,
|
||||
rf_switch_rx: Option<CTRL>,
|
||||
rf_switch_tx: Option<CTRL>,
|
||||
}
|
||||
|
||||
impl<CTRL, WAIT> Stm32l0InterfaceVariant<CTRL, WAIT>
|
||||
where
|
||||
CTRL: OutputPin,
|
||||
WAIT: Wait,
|
||||
{
|
||||
/// Create an InterfaceVariant instance for an stm32l0/sx1276 combination
|
||||
pub fn new(
|
||||
nss: CTRL,
|
||||
reset: CTRL,
|
||||
irq: WAIT,
|
||||
rf_switch_rx: Option<CTRL>,
|
||||
rf_switch_tx: Option<CTRL>,
|
||||
) -> Result<Self, RadioError> {
|
||||
Ok(Self {
|
||||
board_type: BoardType::Stm32l0Sx1276, // updated when associated with a specific LoRa board
|
||||
nss,
|
||||
reset,
|
||||
irq,
|
||||
rf_switch_rx,
|
||||
rf_switch_tx,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<CTRL, WAIT> InterfaceVariant for Stm32l0InterfaceVariant<CTRL, WAIT>
|
||||
where
|
||||
CTRL: OutputPin,
|
||||
WAIT: Wait,
|
||||
{
|
||||
fn set_board_type(&mut self, board_type: BoardType) {
|
||||
self.board_type = board_type;
|
||||
}
|
||||
async fn set_nss_low(&mut self) -> Result<(), RadioError> {
|
||||
self.nss.set_low().map_err(|_| NSS)
|
||||
}
|
||||
async fn set_nss_high(&mut self) -> Result<(), RadioError> {
|
||||
self.nss.set_high().map_err(|_| NSS)
|
||||
}
|
||||
async fn reset(&mut self, delay: &mut impl DelayUs) -> Result<(), RadioError> {
|
||||
delay.delay_ms(10).await;
|
||||
self.reset.set_low().map_err(|_| Reset)?;
|
||||
delay.delay_ms(10).await;
|
||||
self.reset.set_high().map_err(|_| Reset)?;
|
||||
delay.delay_ms(10).await;
|
||||
Ok(())
|
||||
}
|
||||
async fn wait_on_busy(&mut self) -> Result<(), RadioError> {
|
||||
Ok(())
|
||||
}
|
||||
async fn await_irq(&mut self) -> Result<(), RadioError> {
|
||||
self.irq.wait_for_high().await.map_err(|_| Irq)
|
||||
}
|
||||
|
||||
async fn enable_rf_switch_rx(&mut self) -> Result<(), RadioError> {
|
||||
match &mut self.rf_switch_tx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx)?,
|
||||
None => (),
|
||||
};
|
||||
match &mut self.rf_switch_rx {
|
||||
Some(pin) => pin.set_high().map_err(|_| RfSwitchRx),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
async fn enable_rf_switch_tx(&mut self) -> Result<(), RadioError> {
|
||||
match &mut self.rf_switch_rx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
|
||||
None => (),
|
||||
};
|
||||
match &mut self.rf_switch_tx {
|
||||
Some(pin) => pin.set_high().map_err(|_| RfSwitchTx),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
async fn disable_rf_switch(&mut self) -> Result<(), RadioError> {
|
||||
match &mut self.rf_switch_rx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
|
||||
None => (),
|
||||
};
|
||||
match &mut self.rf_switch_tx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Base for the InterfaceVariant implementation for a generic Sx126x LoRa board
|
||||
pub struct GenericSx126xInterfaceVariant<CTRL, WAIT> {
|
||||
board_type: BoardType,
|
||||
nss: CTRL,
|
||||
reset: CTRL,
|
||||
dio1: WAIT,
|
||||
busy: WAIT,
|
||||
rf_switch_rx: Option<CTRL>,
|
||||
rf_switch_tx: Option<CTRL>,
|
||||
}
|
||||
|
||||
impl<CTRL, WAIT> GenericSx126xInterfaceVariant<CTRL, WAIT>
|
||||
where
|
||||
CTRL: OutputPin,
|
||||
WAIT: Wait,
|
||||
{
|
||||
/// Create an InterfaceVariant instance for an nrf52840/sx1262 combination
|
||||
pub fn new(
|
||||
nss: CTRL,
|
||||
reset: CTRL,
|
||||
dio1: WAIT,
|
||||
busy: WAIT,
|
||||
rf_switch_rx: Option<CTRL>,
|
||||
rf_switch_tx: Option<CTRL>,
|
||||
) -> Result<Self, RadioError> {
|
||||
Ok(Self {
|
||||
board_type: BoardType::Rak4631Sx1262, // updated when associated with a specific LoRa board
|
||||
nss,
|
||||
reset,
|
||||
dio1,
|
||||
busy,
|
||||
rf_switch_rx,
|
||||
rf_switch_tx,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<CTRL, WAIT> InterfaceVariant for GenericSx126xInterfaceVariant<CTRL, WAIT>
|
||||
where
|
||||
CTRL: OutputPin,
|
||||
WAIT: Wait,
|
||||
{
|
||||
fn set_board_type(&mut self, board_type: BoardType) {
|
||||
self.board_type = board_type;
|
||||
}
|
||||
async fn set_nss_low(&mut self) -> Result<(), RadioError> {
|
||||
self.nss.set_low().map_err(|_| NSS)
|
||||
}
|
||||
async fn set_nss_high(&mut self) -> Result<(), RadioError> {
|
||||
self.nss.set_high().map_err(|_| NSS)
|
||||
}
|
||||
async fn reset(&mut self, delay: &mut impl DelayUs) -> Result<(), RadioError> {
|
||||
delay.delay_ms(10).await;
|
||||
self.reset.set_low().map_err(|_| Reset)?;
|
||||
delay.delay_ms(20).await;
|
||||
self.reset.set_high().map_err(|_| Reset)?;
|
||||
delay.delay_ms(10).await;
|
||||
Ok(())
|
||||
}
|
||||
async fn wait_on_busy(&mut self) -> Result<(), RadioError> {
|
||||
self.busy.wait_for_low().await.map_err(|_| Busy)
|
||||
}
|
||||
async fn await_irq(&mut self) -> Result<(), RadioError> {
|
||||
if self.board_type != BoardType::RpPicoWaveshareSx1262 {
|
||||
self.dio1.wait_for_high().await.map_err(|_| DIO1)?;
|
||||
} else {
|
||||
self.dio1.wait_for_rising_edge().await.map_err(|_| DIO1)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn enable_rf_switch_rx(&mut self) -> Result<(), RadioError> {
|
||||
match &mut self.rf_switch_tx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx)?,
|
||||
None => (),
|
||||
};
|
||||
match &mut self.rf_switch_rx {
|
||||
Some(pin) => pin.set_high().map_err(|_| RfSwitchRx),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
async fn enable_rf_switch_tx(&mut self) -> Result<(), RadioError> {
|
||||
match &mut self.rf_switch_rx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
|
||||
None => (),
|
||||
};
|
||||
match &mut self.rf_switch_tx {
|
||||
Some(pin) => pin.set_high().map_err(|_| RfSwitchTx),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
async fn disable_rf_switch(&mut self) -> Result<(), RadioError> {
|
||||
match &mut self.rf_switch_rx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
|
||||
None => (),
|
||||
};
|
||||
match &mut self.rf_switch_tx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +1,12 @@
|
||||
#![no_std]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
//! embassy-lora is a collection of async radio drivers that integrate with the lorawan-device
|
||||
//! crate's async LoRaWAN MAC implementation.
|
||||
#![feature(async_fn_in_trait, impl_trait_projections)]
|
||||
#![allow(incomplete_features)]
|
||||
//! embassy-lora holds LoRa-specific functionality.
|
||||
|
||||
pub(crate) mod fmt;
|
||||
|
||||
#[cfg(feature = "stm32wl")]
|
||||
pub mod stm32wl;
|
||||
#[cfg(feature = "sx126x")]
|
||||
pub mod sx126x;
|
||||
#[cfg(feature = "sx127x")]
|
||||
pub mod sx127x;
|
||||
/// interface variants required by the external lora physical layer crate (lora-phy)
|
||||
pub mod iv;
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
use embassy_time::{Duration, Instant, Timer};
|
||||
@ -34,13 +30,11 @@ impl lorawan_device::async_device::radio::Timer for LoraTimer {
|
||||
self.start = Instant::now();
|
||||
}
|
||||
|
||||
type AtFuture<'m> = impl core::future::Future<Output = ()> + 'm;
|
||||
fn at<'m>(&'m mut self, millis: u64) -> Self::AtFuture<'m> {
|
||||
Timer::at(self.start + Duration::from_millis(millis))
|
||||
async fn at(&mut self, millis: u64) {
|
||||
Timer::at(self.start + Duration::from_millis(millis)).await
|
||||
}
|
||||
|
||||
type DelayFuture<'m> = impl core::future::Future<Output = ()> + 'm;
|
||||
fn delay_ms<'m>(&'m mut self, millis: u64) -> Self::DelayFuture<'m> {
|
||||
Timer::after(Duration::from_millis(millis))
|
||||
async fn delay_ms(&mut self, millis: u64) {
|
||||
Timer::after(Duration::from_millis(millis)).await
|
||||
}
|
||||
}
|
||||
|
@ -1,292 +0,0 @@
|
||||
//! A radio driver integration for the radio found on STM32WL family devices.
|
||||
use core::future::{poll_fn, Future};
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
|
||||
use embassy_stm32::dma::NoDma;
|
||||
use embassy_stm32::interrupt::{Interrupt, InterruptExt, SUBGHZ_RADIO};
|
||||
use embassy_stm32::subghz::{
|
||||
CalibrateImage, CfgIrq, CodingRate, Error, HeaderType, HseTrim, Irq, LoRaBandwidth, LoRaModParams,
|
||||
LoRaPacketParams, LoRaSyncWord, Ocp, PaConfig, PacketType, RegMode, RfFreq, SpreadingFactor as SF, StandbyClk,
|
||||
Status, SubGhz, TcxoMode, TcxoTrim, Timeout, TxParams,
|
||||
};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig};
|
||||
use lorawan_device::async_device::Timings;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum State {
|
||||
Idle,
|
||||
Txing,
|
||||
Rxing,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct RadioError;
|
||||
|
||||
static IRQ_WAKER: AtomicWaker = AtomicWaker::new();
|
||||
|
||||
/// The radio peripheral keeping the radio state and owning the radio IRQ.
|
||||
pub struct SubGhzRadio<'d, RS> {
|
||||
radio: SubGhz<'d, NoDma, NoDma>,
|
||||
switch: RS,
|
||||
irq: PeripheralRef<'d, SUBGHZ_RADIO>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct SubGhzRadioConfig {
|
||||
pub reg_mode: RegMode,
|
||||
pub calibrate_image: CalibrateImage,
|
||||
pub pa_config: PaConfig,
|
||||
pub tx_params: TxParams,
|
||||
}
|
||||
|
||||
impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> {
|
||||
/// Create a new instance of a SubGhz radio for LoRaWAN.
|
||||
pub fn new(
|
||||
mut radio: SubGhz<'d, NoDma, NoDma>,
|
||||
switch: RS,
|
||||
irq: impl Peripheral<P = SUBGHZ_RADIO> + 'd,
|
||||
config: SubGhzRadioConfig,
|
||||
) -> Result<Self, RadioError> {
|
||||
into_ref!(irq);
|
||||
|
||||
radio.reset();
|
||||
|
||||
irq.disable();
|
||||
irq.set_handler(|_| {
|
||||
IRQ_WAKER.wake();
|
||||
unsafe { SUBGHZ_RADIO::steal().disable() };
|
||||
});
|
||||
|
||||
configure_radio(&mut radio, config)?;
|
||||
|
||||
Ok(Self { radio, switch, irq })
|
||||
}
|
||||
|
||||
/// Perform a transmission with the given parameters and payload. Returns any time adjustements needed form
|
||||
/// the upcoming RX window start.
|
||||
async fn do_tx(&mut self, config: TxConfig, buf: &[u8]) -> Result<u32, RadioError> {
|
||||
trace!("TX request: {:?}", config);
|
||||
self.switch.set_tx();
|
||||
|
||||
self.radio
|
||||
.set_rf_frequency(&RfFreq::from_frequency(config.rf.frequency))?;
|
||||
|
||||
self.set_lora_mod_params(config.rf)?;
|
||||
|
||||
let packet_params = LoRaPacketParams::new()
|
||||
.set_preamble_len(8)
|
||||
.set_header_type(HeaderType::Variable)
|
||||
.set_payload_len(buf.len() as u8)
|
||||
.set_crc_en(true)
|
||||
.set_invert_iq(false);
|
||||
|
||||
self.radio.set_lora_packet_params(&packet_params)?;
|
||||
|
||||
let irq_cfg = CfgIrq::new().irq_enable_all(Irq::TxDone).irq_enable_all(Irq::Timeout);
|
||||
self.radio.set_irq_cfg(&irq_cfg)?;
|
||||
|
||||
self.radio.set_buffer_base_address(0, 0)?;
|
||||
self.radio.write_buffer(0, buf)?;
|
||||
|
||||
// The maximum airtime for any LoRaWAN package is 2793.5ms.
|
||||
// The value of 4000ms is copied from C driver and gives us a good safety margin.
|
||||
self.radio.set_tx(Timeout::from_millis_sat(4000))?;
|
||||
trace!("TX started");
|
||||
|
||||
loop {
|
||||
let (_status, irq_status) = self.irq_wait().await;
|
||||
|
||||
if irq_status & Irq::TxDone.mask() != 0 {
|
||||
trace!("TX done");
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
if irq_status & Irq::Timeout.mask() != 0 {
|
||||
return Err(RadioError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_lora_mod_params(&mut self, config: RfConfig) -> Result<(), Error> {
|
||||
let mod_params = LoRaModParams::new()
|
||||
.set_sf(convert_spreading_factor(&config.spreading_factor))
|
||||
.set_bw(convert_bandwidth(&config.bandwidth))
|
||||
.set_cr(CodingRate::Cr45)
|
||||
.set_ldro_en(matches!(
|
||||
(config.spreading_factor, config.bandwidth),
|
||||
(SpreadingFactor::_12, Bandwidth::_125KHz)
|
||||
| (SpreadingFactor::_12, Bandwidth::_250KHz)
|
||||
| (SpreadingFactor::_11, Bandwidth::_125KHz)
|
||||
));
|
||||
self.radio.set_lora_mod_params(&mod_params)
|
||||
}
|
||||
|
||||
/// Perform a radio receive operation with the radio config and receive buffer. The receive buffer must
|
||||
/// be able to hold a single LoRaWAN packet.
|
||||
async fn do_rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), RadioError> {
|
||||
assert!(buf.len() >= 255);
|
||||
trace!("RX request: {:?}", config);
|
||||
self.switch.set_rx();
|
||||
|
||||
self.radio.set_rf_frequency(&RfFreq::from_frequency(config.frequency))?;
|
||||
|
||||
self.set_lora_mod_params(config)?;
|
||||
|
||||
let packet_params = LoRaPacketParams::new()
|
||||
.set_preamble_len(8)
|
||||
.set_header_type(HeaderType::Variable)
|
||||
.set_payload_len(0xFF)
|
||||
.set_crc_en(false)
|
||||
.set_invert_iq(true);
|
||||
self.radio.set_lora_packet_params(&packet_params)?;
|
||||
|
||||
let irq_cfg = CfgIrq::new()
|
||||
.irq_enable_all(Irq::RxDone)
|
||||
.irq_enable_all(Irq::PreambleDetected)
|
||||
.irq_enable_all(Irq::HeaderValid)
|
||||
.irq_enable_all(Irq::HeaderErr)
|
||||
.irq_enable_all(Irq::Err)
|
||||
.irq_enable_all(Irq::Timeout);
|
||||
self.radio.set_irq_cfg(&irq_cfg)?;
|
||||
|
||||
self.radio.set_buffer_base_address(0, 0)?;
|
||||
|
||||
// NOTE: Upper layer handles timeout by cancelling the future
|
||||
self.radio.set_rx(Timeout::DISABLED)?;
|
||||
|
||||
trace!("RX started");
|
||||
|
||||
loop {
|
||||
let (_status, irq_status) = self.irq_wait().await;
|
||||
|
||||
if irq_status & Irq::RxDone.mask() != 0 {
|
||||
let (_status, len, ptr) = self.radio.rx_buffer_status()?;
|
||||
let packet_status = self.radio.lora_packet_status()?;
|
||||
let rssi = packet_status.rssi_pkt().to_integer();
|
||||
let snr = packet_status.snr_pkt().to_integer();
|
||||
self.radio.read_buffer(ptr, &mut buf[..len as usize])?;
|
||||
self.radio.set_standby(StandbyClk::Rc)?;
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
trace!("RX done: {=[u8]:#02X}", &mut buf[..len as usize]);
|
||||
|
||||
#[cfg(feature = "log")]
|
||||
trace!("RX done: {:02x?}", &mut buf[..len as usize]);
|
||||
return Ok((len as usize, RxQuality::new(rssi, snr as i8)));
|
||||
}
|
||||
|
||||
if irq_status & Irq::Timeout.mask() != 0 {
|
||||
return Err(RadioError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn irq_wait(&mut self) -> (Status, u16) {
|
||||
poll_fn(|cx| {
|
||||
self.irq.unpend();
|
||||
self.irq.enable();
|
||||
IRQ_WAKER.register(cx.waker());
|
||||
|
||||
let (status, irq_status) = self.radio.irq_status().expect("error getting irq status");
|
||||
self.radio
|
||||
.clear_irq_status(irq_status)
|
||||
.expect("error clearing irq status");
|
||||
|
||||
trace!("SUGHZ IRQ 0b{:016b}, {:?}", irq_status, status);
|
||||
|
||||
if irq_status == 0 {
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready((status, irq_status))
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_radio(radio: &mut SubGhz<'_, NoDma, NoDma>, config: SubGhzRadioConfig) -> Result<(), RadioError> {
|
||||
trace!("Configuring STM32WL SUBGHZ radio");
|
||||
|
||||
radio.set_regulator_mode(config.reg_mode)?;
|
||||
radio.set_standby(StandbyClk::Rc)?;
|
||||
|
||||
let tcxo_mode = TcxoMode::new()
|
||||
.set_txco_trim(TcxoTrim::Volts1pt7)
|
||||
.set_timeout(Timeout::from_duration_sat(core::time::Duration::from_millis(100)));
|
||||
radio.set_tcxo_mode(&tcxo_mode)?;
|
||||
// Reduce input capacitance as shown in Reference Manual "Figure 23. HSE32 TCXO control".
|
||||
// The STM32CUBE C driver also does this.
|
||||
radio.set_hse_in_trim(HseTrim::MIN)?;
|
||||
|
||||
// Re-calibrate everything after setting the TXCO config.
|
||||
radio.calibrate(0x7F)?;
|
||||
radio.calibrate_image(config.calibrate_image)?;
|
||||
|
||||
radio.set_pa_config(&config.pa_config)?;
|
||||
radio.set_tx_params(&config.tx_params)?;
|
||||
radio.set_pa_ocp(Ocp::Max140m)?;
|
||||
|
||||
radio.set_packet_type(PacketType::LoRa)?;
|
||||
radio.set_lora_sync_word(LoRaSyncWord::Public)?;
|
||||
|
||||
trace!("Done initializing STM32WL SUBGHZ radio");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<'d, RS: RadioSwitch> PhyRxTx for SubGhzRadio<'d, RS> {
|
||||
type PhyError = RadioError;
|
||||
|
||||
type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm where Self: 'm;
|
||||
fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> {
|
||||
async move { self.do_tx(config, buf).await }
|
||||
}
|
||||
|
||||
type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm where Self: 'm;
|
||||
fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> {
|
||||
async move { self.do_rx(config, buf).await }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<embassy_stm32::spi::Error> for RadioError {
|
||||
fn from(_: embassy_stm32::spi::Error) -> Self {
|
||||
RadioError
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, RS> Timings for SubGhzRadio<'d, RS> {
|
||||
fn get_rx_window_offset_ms(&self) -> i32 {
|
||||
-3
|
||||
}
|
||||
fn get_rx_window_duration_ms(&self) -> u32 {
|
||||
1003
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RadioSwitch {
|
||||
fn set_rx(&mut self);
|
||||
fn set_tx(&mut self);
|
||||
}
|
||||
|
||||
fn convert_spreading_factor(sf: &SpreadingFactor) -> SF {
|
||||
match sf {
|
||||
SpreadingFactor::_7 => SF::Sf7,
|
||||
SpreadingFactor::_8 => SF::Sf8,
|
||||
SpreadingFactor::_9 => SF::Sf9,
|
||||
SpreadingFactor::_10 => SF::Sf10,
|
||||
SpreadingFactor::_11 => SF::Sf11,
|
||||
SpreadingFactor::_12 => SF::Sf12,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_bandwidth(bw: &Bandwidth) -> LoRaBandwidth {
|
||||
match bw {
|
||||
Bandwidth::_125KHz => LoRaBandwidth::Bw125,
|
||||
Bandwidth::_250KHz => LoRaBandwidth::Bw250,
|
||||
Bandwidth::_500KHz => LoRaBandwidth::Bw500,
|
||||
}
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
use core::future::Future;
|
||||
|
||||
use defmt::Format;
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use embedded_hal_async::digital::Wait;
|
||||
use embedded_hal_async::spi::*;
|
||||
use lorawan_device::async_device::radio::{PhyRxTx, RfConfig, RxQuality, TxConfig};
|
||||
use lorawan_device::async_device::Timings;
|
||||
|
||||
mod sx126x_lora;
|
||||
use sx126x_lora::LoRa;
|
||||
|
||||
use self::sx126x_lora::mod_params::RadioError;
|
||||
|
||||
/// Semtech Sx126x LoRa peripheral
|
||||
pub struct Sx126xRadio<SPI, CTRL, WAIT, BUS>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = BUS> + 'static,
|
||||
CTRL: OutputPin + 'static,
|
||||
WAIT: Wait + 'static,
|
||||
BUS: Error + Format + 'static,
|
||||
{
|
||||
pub lora: LoRa<SPI, CTRL, WAIT>,
|
||||
}
|
||||
|
||||
impl<SPI, CTRL, WAIT, BUS> Sx126xRadio<SPI, CTRL, WAIT, BUS>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = BUS> + 'static,
|
||||
CTRL: OutputPin + 'static,
|
||||
WAIT: Wait + 'static,
|
||||
BUS: Error + Format + 'static,
|
||||
{
|
||||
pub async fn new(
|
||||
spi: SPI,
|
||||
cs: CTRL,
|
||||
reset: CTRL,
|
||||
antenna_rx: CTRL,
|
||||
antenna_tx: CTRL,
|
||||
dio1: WAIT,
|
||||
busy: WAIT,
|
||||
enable_public_network: bool,
|
||||
) -> Result<Self, RadioError<BUS>> {
|
||||
let mut lora = LoRa::new(spi, cs, reset, antenna_rx, antenna_tx, dio1, busy);
|
||||
lora.init().await?;
|
||||
lora.set_lora_modem(enable_public_network).await?;
|
||||
Ok(Self { lora })
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CTRL, WAIT, BUS> Timings for Sx126xRadio<SPI, CTRL, WAIT, BUS>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = BUS> + 'static,
|
||||
CTRL: OutputPin + 'static,
|
||||
WAIT: Wait + 'static,
|
||||
BUS: Error + Format + 'static,
|
||||
{
|
||||
fn get_rx_window_offset_ms(&self) -> i32 {
|
||||
-50
|
||||
}
|
||||
fn get_rx_window_duration_ms(&self) -> u32 {
|
||||
1050
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CTRL, WAIT, BUS> PhyRxTx for Sx126xRadio<SPI, CTRL, WAIT, BUS>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = BUS> + 'static,
|
||||
CTRL: OutputPin + 'static,
|
||||
WAIT: Wait + 'static,
|
||||
BUS: Error + Format + 'static,
|
||||
{
|
||||
type PhyError = RadioError<BUS>;
|
||||
|
||||
type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm
|
||||
where
|
||||
SPI: 'm,
|
||||
CTRL: 'm,
|
||||
WAIT: 'm,
|
||||
BUS: 'm;
|
||||
|
||||
fn tx<'m>(&'m mut self, config: TxConfig, buffer: &'m [u8]) -> Self::TxFuture<'m> {
|
||||
trace!("TX START");
|
||||
async move {
|
||||
self.lora
|
||||
.set_tx_config(
|
||||
config.pw,
|
||||
config.rf.spreading_factor.into(),
|
||||
config.rf.bandwidth.into(),
|
||||
config.rf.coding_rate.into(),
|
||||
8,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
self.lora.set_max_payload_length(buffer.len() as u8).await?;
|
||||
self.lora.set_channel(config.rf.frequency).await?;
|
||||
self.lora.send(buffer, 0xffffff).await?;
|
||||
self.lora.process_irq(None, None, None).await?;
|
||||
trace!("TX DONE");
|
||||
return Ok(0);
|
||||
}
|
||||
}
|
||||
|
||||
type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm
|
||||
where
|
||||
SPI: 'm,
|
||||
CTRL: 'm,
|
||||
WAIT: 'm,
|
||||
BUS: 'm;
|
||||
|
||||
fn rx<'m>(&'m mut self, config: RfConfig, receiving_buffer: &'m mut [u8]) -> Self::RxFuture<'m> {
|
||||
trace!("RX START");
|
||||
async move {
|
||||
self.lora
|
||||
.set_rx_config(
|
||||
config.spreading_factor.into(),
|
||||
config.bandwidth.into(),
|
||||
config.coding_rate.into(),
|
||||
8,
|
||||
4,
|
||||
false,
|
||||
0u8,
|
||||
true,
|
||||
false,
|
||||
0,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
self.lora.set_max_payload_length(receiving_buffer.len() as u8).await?;
|
||||
self.lora.set_channel(config.frequency).await?;
|
||||
self.lora.rx(90 * 1000).await?;
|
||||
let mut received_len = 0u8;
|
||||
self.lora
|
||||
.process_irq(Some(receiving_buffer), Some(&mut received_len), None)
|
||||
.await?;
|
||||
trace!("RX DONE");
|
||||
|
||||
let packet_status = self.lora.get_latest_packet_status();
|
||||
let mut rssi = 0i16;
|
||||
let mut snr = 0i8;
|
||||
if packet_status.is_some() {
|
||||
rssi = packet_status.unwrap().rssi as i16;
|
||||
snr = packet_status.unwrap().snr;
|
||||
}
|
||||
|
||||
Ok((received_len as usize, RxQuality::new(rssi, snr)))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,256 +0,0 @@
|
||||
use embassy_time::{Duration, Timer};
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use embedded_hal_async::digital::Wait;
|
||||
use embedded_hal_async::spi::SpiBus;
|
||||
|
||||
use super::mod_params::RadioError::*;
|
||||
use super::mod_params::*;
|
||||
use super::LoRa;
|
||||
|
||||
// Defines the time required for the TCXO to wakeup [ms].
|
||||
const BRD_TCXO_WAKEUP_TIME: u32 = 10;
|
||||
|
||||
// Provides board-specific functionality for Semtech SX126x-based boards.
|
||||
|
||||
impl<SPI, CTRL, WAIT, BUS> LoRa<SPI, CTRL, WAIT>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = BUS>,
|
||||
CTRL: OutputPin,
|
||||
WAIT: Wait,
|
||||
{
|
||||
// De-initialize the radio I/Os pins interface. Useful when going into MCU low power modes.
|
||||
pub(super) async fn brd_io_deinit(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
Ok(()) // no operation currently
|
||||
}
|
||||
|
||||
// Initialize the TCXO power pin
|
||||
pub(super) async fn brd_io_tcxo_init(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
let timeout = self.brd_get_board_tcxo_wakeup_time() << 6;
|
||||
self.sub_set_dio3_as_tcxo_ctrl(TcxoCtrlVoltage::Ctrl1V7, timeout)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Initialize RF switch control pins
|
||||
pub(super) async fn brd_io_rf_switch_init(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_dio2_as_rf_switch_ctrl(true).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Initialize the radio debug pins
|
||||
pub(super) async fn brd_io_dbg_init(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
Ok(()) // no operation currently
|
||||
}
|
||||
|
||||
// Hardware reset of the radio
|
||||
pub(super) async fn brd_reset(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
Timer::after(Duration::from_millis(10)).await;
|
||||
self.reset.set_low().map_err(|_| Reset)?;
|
||||
Timer::after(Duration::from_millis(20)).await;
|
||||
self.reset.set_high().map_err(|_| Reset)?;
|
||||
Timer::after(Duration::from_millis(10)).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Wait while the busy pin is high
|
||||
pub(super) async fn brd_wait_on_busy(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.busy.wait_for_low().await.map_err(|_| Busy)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Wake up the radio
|
||||
pub(super) async fn brd_wakeup(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.cs.set_low().map_err(|_| CS)?;
|
||||
self.spi.write(&[OpCode::GetStatus.value()]).await.map_err(SPI)?;
|
||||
self.spi.write(&[0x00]).await.map_err(SPI)?;
|
||||
self.cs.set_high().map_err(|_| CS)?;
|
||||
|
||||
self.brd_wait_on_busy().await?;
|
||||
self.brd_set_operating_mode(RadioMode::StandbyRC);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Send a command that writes data to the radio
|
||||
pub(super) async fn brd_write_command(&mut self, op_code: OpCode, buffer: &[u8]) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_check_device_ready().await?;
|
||||
|
||||
self.cs.set_low().map_err(|_| CS)?;
|
||||
self.spi.write(&[op_code.value()]).await.map_err(SPI)?;
|
||||
self.spi.write(buffer).await.map_err(SPI)?;
|
||||
self.cs.set_high().map_err(|_| CS)?;
|
||||
|
||||
if op_code != OpCode::SetSleep {
|
||||
self.brd_wait_on_busy().await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Send a command that reads data from the radio, filling the provided buffer and returning a status
|
||||
pub(super) async fn brd_read_command(&mut self, op_code: OpCode, buffer: &mut [u8]) -> Result<u8, RadioError<BUS>> {
|
||||
let mut status = [0u8];
|
||||
let mut input = [0u8];
|
||||
|
||||
self.sub_check_device_ready().await?;
|
||||
|
||||
self.cs.set_low().map_err(|_| CS)?;
|
||||
self.spi.write(&[op_code.value()]).await.map_err(SPI)?;
|
||||
self.spi.transfer(&mut status, &[0x00]).await.map_err(SPI)?;
|
||||
for i in 0..buffer.len() {
|
||||
self.spi.transfer(&mut input, &[0x00]).await.map_err(SPI)?;
|
||||
buffer[i] = input[0];
|
||||
}
|
||||
self.cs.set_high().map_err(|_| CS)?;
|
||||
|
||||
self.brd_wait_on_busy().await?;
|
||||
|
||||
Ok(status[0])
|
||||
}
|
||||
|
||||
// Write one or more bytes of data to the radio memory
|
||||
pub(super) async fn brd_write_registers(
|
||||
&mut self,
|
||||
start_register: Register,
|
||||
buffer: &[u8],
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_check_device_ready().await?;
|
||||
|
||||
self.cs.set_low().map_err(|_| CS)?;
|
||||
self.spi.write(&[OpCode::WriteRegister.value()]).await.map_err(SPI)?;
|
||||
self.spi
|
||||
.write(&[
|
||||
((start_register.addr() & 0xFF00) >> 8) as u8,
|
||||
(start_register.addr() & 0x00FF) as u8,
|
||||
])
|
||||
.await
|
||||
.map_err(SPI)?;
|
||||
self.spi.write(buffer).await.map_err(SPI)?;
|
||||
self.cs.set_high().map_err(|_| CS)?;
|
||||
|
||||
self.brd_wait_on_busy().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Read one or more bytes of data from the radio memory
|
||||
pub(super) async fn brd_read_registers(
|
||||
&mut self,
|
||||
start_register: Register,
|
||||
buffer: &mut [u8],
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
let mut input = [0u8];
|
||||
|
||||
self.sub_check_device_ready().await?;
|
||||
|
||||
self.cs.set_low().map_err(|_| CS)?;
|
||||
self.spi.write(&[OpCode::ReadRegister.value()]).await.map_err(SPI)?;
|
||||
self.spi
|
||||
.write(&[
|
||||
((start_register.addr() & 0xFF00) >> 8) as u8,
|
||||
(start_register.addr() & 0x00FF) as u8,
|
||||
0x00u8,
|
||||
])
|
||||
.await
|
||||
.map_err(SPI)?;
|
||||
for i in 0..buffer.len() {
|
||||
self.spi.transfer(&mut input, &[0x00]).await.map_err(SPI)?;
|
||||
buffer[i] = input[0];
|
||||
}
|
||||
self.cs.set_high().map_err(|_| CS)?;
|
||||
|
||||
self.brd_wait_on_busy().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Write data to the buffer holding the payload in the radio
|
||||
pub(super) async fn brd_write_buffer(&mut self, offset: u8, buffer: &[u8]) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_check_device_ready().await?;
|
||||
|
||||
self.cs.set_low().map_err(|_| CS)?;
|
||||
self.spi.write(&[OpCode::WriteBuffer.value()]).await.map_err(SPI)?;
|
||||
self.spi.write(&[offset]).await.map_err(SPI)?;
|
||||
self.spi.write(buffer).await.map_err(SPI)?;
|
||||
self.cs.set_high().map_err(|_| CS)?;
|
||||
|
||||
self.brd_wait_on_busy().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Read data from the buffer holding the payload in the radio
|
||||
pub(super) async fn brd_read_buffer(&mut self, offset: u8, buffer: &mut [u8]) -> Result<(), RadioError<BUS>> {
|
||||
let mut input = [0u8];
|
||||
|
||||
self.sub_check_device_ready().await?;
|
||||
|
||||
self.cs.set_low().map_err(|_| CS)?;
|
||||
self.spi.write(&[OpCode::ReadBuffer.value()]).await.map_err(SPI)?;
|
||||
self.spi.write(&[offset]).await.map_err(SPI)?;
|
||||
self.spi.write(&[0x00]).await.map_err(SPI)?;
|
||||
for i in 0..buffer.len() {
|
||||
self.spi.transfer(&mut input, &[0x00]).await.map_err(SPI)?;
|
||||
buffer[i] = input[0];
|
||||
}
|
||||
self.cs.set_high().map_err(|_| CS)?;
|
||||
|
||||
self.brd_wait_on_busy().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio output power
|
||||
pub(super) async fn brd_set_rf_tx_power(&mut self, power: i8) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_tx_params(power, RampTime::Ramp40Us).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Get the radio type
|
||||
pub(super) fn brd_get_radio_type(&mut self) -> RadioType {
|
||||
RadioType::SX1262
|
||||
}
|
||||
|
||||
// Quiesce the antenna(s).
|
||||
pub(super) fn brd_ant_sleep(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.antenna_tx.set_low().map_err(|_| AntTx)?;
|
||||
self.antenna_rx.set_low().map_err(|_| AntRx)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Prepare the antenna(s) for a receive operation
|
||||
pub(super) fn brd_ant_set_rx(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.antenna_tx.set_low().map_err(|_| AntTx)?;
|
||||
self.antenna_rx.set_high().map_err(|_| AntRx)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Prepare the antenna(s) for a send operation
|
||||
pub(super) fn brd_ant_set_tx(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.antenna_rx.set_low().map_err(|_| AntRx)?;
|
||||
self.antenna_tx.set_high().map_err(|_| AntTx)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Check if the given RF frequency is supported by the hardware
|
||||
pub(super) async fn brd_check_rf_frequency(&mut self, _frequency: u32) -> Result<bool, RadioError<BUS>> {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
// Get the duration required for the TCXO to wakeup [ms].
|
||||
pub(super) fn brd_get_board_tcxo_wakeup_time(&mut self) -> u32 {
|
||||
BRD_TCXO_WAKEUP_TIME
|
||||
}
|
||||
|
||||
/* Get current state of the DIO1 pin - not currently needed if waiting on DIO1 instead of using an IRQ process
|
||||
pub(super) async fn brd_get_dio1_pin_state(
|
||||
&mut self,
|
||||
) -> Result<u32, RadioError<BUS>> {
|
||||
Ok(0)
|
||||
}
|
||||
*/
|
||||
|
||||
// Get the current radio operatiing mode
|
||||
pub(super) fn brd_get_operating_mode(&mut self) -> RadioMode {
|
||||
self.operating_mode
|
||||
}
|
||||
|
||||
// Set/Update the current radio operating mode This function is only required to reflect the current radio operating mode when processing interrupts.
|
||||
pub(super) fn brd_set_operating_mode(&mut self, mode: RadioMode) {
|
||||
self.operating_mode = mode;
|
||||
}
|
||||
}
|
@ -1,732 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use embassy_time::{Duration, Timer};
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use embedded_hal_async::digital::Wait;
|
||||
use embedded_hal_async::spi::SpiBus;
|
||||
|
||||
mod board_specific;
|
||||
pub mod mod_params;
|
||||
mod subroutine;
|
||||
|
||||
use mod_params::RadioError::*;
|
||||
use mod_params::*;
|
||||
|
||||
// Syncwords for public and private networks
|
||||
const LORA_MAC_PUBLIC_SYNCWORD: u16 = 0x3444;
|
||||
const LORA_MAC_PRIVATE_SYNCWORD: u16 = 0x1424;
|
||||
|
||||
// Maximum number of registers that can be added to the retention list
|
||||
const MAX_NUMBER_REGS_IN_RETENTION: u8 = 4;
|
||||
|
||||
// Possible LoRa bandwidths
|
||||
const LORA_BANDWIDTHS: [Bandwidth; 3] = [Bandwidth::_125KHz, Bandwidth::_250KHz, Bandwidth::_500KHz];
|
||||
|
||||
// Radio complete wakeup time with margin for temperature compensation [ms]
|
||||
const RADIO_WAKEUP_TIME: u32 = 3;
|
||||
|
||||
/// Provides high-level access to Semtech SX126x-based boards
|
||||
pub struct LoRa<SPI, CTRL, WAIT> {
|
||||
spi: SPI,
|
||||
cs: CTRL,
|
||||
reset: CTRL,
|
||||
antenna_rx: CTRL,
|
||||
antenna_tx: CTRL,
|
||||
dio1: WAIT,
|
||||
busy: WAIT,
|
||||
operating_mode: RadioMode,
|
||||
rx_continuous: bool,
|
||||
max_payload_length: u8,
|
||||
modulation_params: Option<ModulationParams>,
|
||||
packet_type: PacketType,
|
||||
packet_params: Option<PacketParams>,
|
||||
packet_status: Option<PacketStatus>,
|
||||
image_calibrated: bool,
|
||||
frequency_error: u32,
|
||||
}
|
||||
|
||||
impl<SPI, CTRL, WAIT, BUS> LoRa<SPI, CTRL, WAIT>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = BUS>,
|
||||
CTRL: OutputPin,
|
||||
WAIT: Wait,
|
||||
{
|
||||
/// Builds and returns a new instance of the radio. Only one instance of the radio should exist at a time ()
|
||||
pub fn new(spi: SPI, cs: CTRL, reset: CTRL, antenna_rx: CTRL, antenna_tx: CTRL, dio1: WAIT, busy: WAIT) -> Self {
|
||||
Self {
|
||||
spi,
|
||||
cs,
|
||||
reset,
|
||||
antenna_rx,
|
||||
antenna_tx,
|
||||
dio1,
|
||||
busy,
|
||||
operating_mode: RadioMode::Sleep,
|
||||
rx_continuous: false,
|
||||
max_payload_length: 0xFFu8,
|
||||
modulation_params: None,
|
||||
packet_type: PacketType::LoRa,
|
||||
packet_params: None,
|
||||
packet_status: None,
|
||||
image_calibrated: false,
|
||||
frequency_error: 0u32, // where is volatile FrequencyError modified ???
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the radio
|
||||
pub async fn init(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_init().await?;
|
||||
self.sub_set_standby(StandbyMode::RC).await?;
|
||||
self.sub_set_regulator_mode(RegulatorMode::UseDCDC).await?;
|
||||
self.sub_set_buffer_base_address(0x00u8, 0x00u8).await?;
|
||||
self.sub_set_tx_params(0i8, RampTime::Ramp200Us).await?;
|
||||
self.sub_set_dio_irq_params(
|
||||
IrqMask::All.value(),
|
||||
IrqMask::All.value(),
|
||||
IrqMask::None.value(),
|
||||
IrqMask::None.value(),
|
||||
)
|
||||
.await?;
|
||||
self.add_register_to_retention_list(Register::RxGain.addr()).await?;
|
||||
self.add_register_to_retention_list(Register::TxModulation.addr())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return current radio state
|
||||
pub fn get_status(&mut self) -> RadioState {
|
||||
match self.brd_get_operating_mode() {
|
||||
RadioMode::Transmit => RadioState::TxRunning,
|
||||
RadioMode::Receive => RadioState::RxRunning,
|
||||
RadioMode::ChannelActivityDetection => RadioState::ChannelActivityDetecting,
|
||||
_ => RadioState::Idle,
|
||||
}
|
||||
}
|
||||
|
||||
/// Configure the radio for LoRa (FSK support should be provided in a separate driver, if desired)
|
||||
pub async fn set_lora_modem(&mut self, enable_public_network: bool) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_packet_type(PacketType::LoRa).await?;
|
||||
if enable_public_network {
|
||||
self.brd_write_registers(
|
||||
Register::LoRaSyncword,
|
||||
&[
|
||||
((LORA_MAC_PUBLIC_SYNCWORD >> 8) & 0xFF) as u8,
|
||||
(LORA_MAC_PUBLIC_SYNCWORD & 0xFF) as u8,
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
self.brd_write_registers(
|
||||
Register::LoRaSyncword,
|
||||
&[
|
||||
((LORA_MAC_PRIVATE_SYNCWORD >> 8) & 0xFF) as u8,
|
||||
(LORA_MAC_PRIVATE_SYNCWORD & 0xFF) as u8,
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the channel frequency
|
||||
pub async fn set_channel(&mut self, frequency: u32) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_rf_frequency(frequency).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/* Checks if the channel is free for the given time. This is currently not implemented until a substitute
|
||||
for switching to the FSK modem is found.
|
||||
|
||||
pub async fn is_channel_free(&mut self, frequency: u32, rxBandwidth: u32, rssiThresh: i16, maxCarrierSenseTime: u32) -> bool;
|
||||
*/
|
||||
|
||||
/// Generate a 32 bit random value based on the RSSI readings, after disabling all interrupts. Ensure set_lora_modem() is called befrorehand.
|
||||
/// After calling this function either set_rx_config() or set_tx_config() must be called.
|
||||
pub async fn get_random_value(&mut self) -> Result<u32, RadioError<BUS>> {
|
||||
self.sub_set_dio_irq_params(
|
||||
IrqMask::None.value(),
|
||||
IrqMask::None.value(),
|
||||
IrqMask::None.value(),
|
||||
IrqMask::None.value(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let result = self.sub_get_random().await?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Set the reception parameters for the LoRa modem (only). Ensure set_lora_modem() is called befrorehand.
|
||||
/// spreading_factor [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips/symbol]
|
||||
/// bandwidth [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved]
|
||||
/// coding_rate [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
|
||||
/// preamble_length length in symbols (the hardware adds 4 more symbols)
|
||||
/// symb_timeout RxSingle timeout value in symbols
|
||||
/// fixed_len fixed length packets [0: variable, 1: fixed]
|
||||
/// payload_len payload length when fixed length is used
|
||||
/// crc_on [0: OFF, 1: ON]
|
||||
/// freq_hop_on intra-packet frequency hopping [0: OFF, 1: ON]
|
||||
/// hop_period number of symbols between each hop
|
||||
/// iq_inverted invert IQ signals [0: not inverted, 1: inverted]
|
||||
/// rx_continuous reception mode [false: single mode, true: continuous mode]
|
||||
pub async fn set_rx_config(
|
||||
&mut self,
|
||||
spreading_factor: SpreadingFactor,
|
||||
bandwidth: Bandwidth,
|
||||
coding_rate: CodingRate,
|
||||
preamble_length: u16,
|
||||
symb_timeout: u16,
|
||||
fixed_len: bool,
|
||||
payload_len: u8,
|
||||
crc_on: bool,
|
||||
_freq_hop_on: bool,
|
||||
_hop_period: u8,
|
||||
iq_inverted: bool,
|
||||
rx_continuous: bool,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
let mut symb_timeout_final = symb_timeout;
|
||||
|
||||
self.rx_continuous = rx_continuous;
|
||||
if self.rx_continuous {
|
||||
symb_timeout_final = 0;
|
||||
}
|
||||
if fixed_len {
|
||||
self.max_payload_length = payload_len;
|
||||
} else {
|
||||
self.max_payload_length = 0xFFu8;
|
||||
}
|
||||
|
||||
self.sub_set_stop_rx_timer_on_preamble_detect(false).await?;
|
||||
|
||||
let mut low_data_rate_optimize = 0x00u8;
|
||||
if (((spreading_factor == SpreadingFactor::_11) || (spreading_factor == SpreadingFactor::_12))
|
||||
&& (bandwidth == Bandwidth::_125KHz))
|
||||
|| ((spreading_factor == SpreadingFactor::_12) && (bandwidth == Bandwidth::_250KHz))
|
||||
{
|
||||
low_data_rate_optimize = 0x01u8;
|
||||
}
|
||||
|
||||
let modulation_params = ModulationParams {
|
||||
spreading_factor: spreading_factor,
|
||||
bandwidth: bandwidth,
|
||||
coding_rate: coding_rate,
|
||||
low_data_rate_optimize: low_data_rate_optimize,
|
||||
};
|
||||
|
||||
let mut preamble_length_final = preamble_length;
|
||||
if ((spreading_factor == SpreadingFactor::_5) || (spreading_factor == SpreadingFactor::_6))
|
||||
&& (preamble_length < 12)
|
||||
{
|
||||
preamble_length_final = 12;
|
||||
}
|
||||
|
||||
let packet_params = PacketParams {
|
||||
preamble_length: preamble_length_final,
|
||||
implicit_header: fixed_len,
|
||||
payload_length: self.max_payload_length,
|
||||
crc_on: crc_on,
|
||||
iq_inverted: iq_inverted,
|
||||
};
|
||||
|
||||
self.modulation_params = Some(modulation_params);
|
||||
self.packet_params = Some(packet_params);
|
||||
|
||||
self.standby().await?;
|
||||
self.sub_set_modulation_params().await?;
|
||||
self.sub_set_packet_params().await?;
|
||||
self.sub_set_lora_symb_num_timeout(symb_timeout_final).await?;
|
||||
|
||||
// Optimize the Inverted IQ Operation (see DS_SX1261-2_V1.2 datasheet chapter 15.4)
|
||||
let mut iq_polarity = [0x00u8];
|
||||
self.brd_read_registers(Register::IQPolarity, &mut iq_polarity).await?;
|
||||
if iq_inverted {
|
||||
self.brd_write_registers(Register::IQPolarity, &[iq_polarity[0] & (!(1 << 2))])
|
||||
.await?;
|
||||
} else {
|
||||
self.brd_write_registers(Register::IQPolarity, &[iq_polarity[0] | (1 << 2)])
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the transmission parameters for the LoRa modem (only).
|
||||
/// power output power [dBm]
|
||||
/// spreading_factor [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips/symbol]
|
||||
/// bandwidth [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved]
|
||||
/// coding_rate [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
|
||||
/// preamble_length length in symbols (the hardware adds 4 more symbols)
|
||||
/// fixed_len fixed length packets [0: variable, 1: fixed]
|
||||
/// crc_on [0: OFF, 1: ON]
|
||||
/// freq_hop_on intra-packet frequency hopping [0: OFF, 1: ON]
|
||||
/// hop_period number of symbols between each hop
|
||||
/// iq_inverted invert IQ signals [0: not inverted, 1: inverted]
|
||||
pub async fn set_tx_config(
|
||||
&mut self,
|
||||
power: i8,
|
||||
spreading_factor: SpreadingFactor,
|
||||
bandwidth: Bandwidth,
|
||||
coding_rate: CodingRate,
|
||||
preamble_length: u16,
|
||||
fixed_len: bool,
|
||||
crc_on: bool,
|
||||
_freq_hop_on: bool,
|
||||
_hop_period: u8,
|
||||
iq_inverted: bool,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
let mut low_data_rate_optimize = 0x00u8;
|
||||
if (((spreading_factor == SpreadingFactor::_11) || (spreading_factor == SpreadingFactor::_12))
|
||||
&& (bandwidth == Bandwidth::_125KHz))
|
||||
|| ((spreading_factor == SpreadingFactor::_12) && (bandwidth == Bandwidth::_250KHz))
|
||||
{
|
||||
low_data_rate_optimize = 0x01u8;
|
||||
}
|
||||
|
||||
let modulation_params = ModulationParams {
|
||||
spreading_factor: spreading_factor,
|
||||
bandwidth: bandwidth,
|
||||
coding_rate: coding_rate,
|
||||
low_data_rate_optimize: low_data_rate_optimize,
|
||||
};
|
||||
|
||||
let mut preamble_length_final = preamble_length;
|
||||
if ((spreading_factor == SpreadingFactor::_5) || (spreading_factor == SpreadingFactor::_6))
|
||||
&& (preamble_length < 12)
|
||||
{
|
||||
preamble_length_final = 12;
|
||||
}
|
||||
|
||||
let packet_params = PacketParams {
|
||||
preamble_length: preamble_length_final,
|
||||
implicit_header: fixed_len,
|
||||
payload_length: self.max_payload_length,
|
||||
crc_on: crc_on,
|
||||
iq_inverted: iq_inverted,
|
||||
};
|
||||
|
||||
self.modulation_params = Some(modulation_params);
|
||||
self.packet_params = Some(packet_params);
|
||||
|
||||
self.standby().await?;
|
||||
self.sub_set_modulation_params().await?;
|
||||
self.sub_set_packet_params().await?;
|
||||
|
||||
// Handle modulation quality with the 500 kHz LoRa bandwidth (see DS_SX1261-2_V1.2 datasheet chapter 15.1)
|
||||
|
||||
let mut tx_modulation = [0x00u8];
|
||||
self.brd_read_registers(Register::TxModulation, &mut tx_modulation)
|
||||
.await?;
|
||||
if bandwidth == Bandwidth::_500KHz {
|
||||
self.brd_write_registers(Register::TxModulation, &[tx_modulation[0] & (!(1 << 2))])
|
||||
.await?;
|
||||
} else {
|
||||
self.brd_write_registers(Register::TxModulation, &[tx_modulation[0] | (1 << 2)])
|
||||
.await?;
|
||||
}
|
||||
|
||||
self.brd_set_rf_tx_power(power).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if the given RF frequency is supported by the hardware [true: supported, false: unsupported]
|
||||
pub async fn check_rf_frequency(&mut self, frequency: u32) -> Result<bool, RadioError<BUS>> {
|
||||
Ok(self.brd_check_rf_frequency(frequency).await?)
|
||||
}
|
||||
|
||||
/// Computes the packet time on air in ms for the given payload for a LoRa modem (can only be called once set_rx_config or set_tx_config have been called)
|
||||
/// spreading_factor [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips/symbol]
|
||||
/// bandwidth [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved]
|
||||
/// coding_rate [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
|
||||
/// preamble_length length in symbols (the hardware adds 4 more symbols)
|
||||
/// fixed_len fixed length packets [0: variable, 1: fixed]
|
||||
/// payload_len sets payload length when fixed length is used
|
||||
/// crc_on [0: OFF, 1: ON]
|
||||
pub fn get_time_on_air(
|
||||
&mut self,
|
||||
spreading_factor: SpreadingFactor,
|
||||
bandwidth: Bandwidth,
|
||||
coding_rate: CodingRate,
|
||||
preamble_length: u16,
|
||||
fixed_len: bool,
|
||||
payload_len: u8,
|
||||
crc_on: bool,
|
||||
) -> Result<u32, RadioError<BUS>> {
|
||||
let numerator = 1000
|
||||
* Self::get_lora_time_on_air_numerator(
|
||||
spreading_factor,
|
||||
bandwidth,
|
||||
coding_rate,
|
||||
preamble_length,
|
||||
fixed_len,
|
||||
payload_len,
|
||||
crc_on,
|
||||
);
|
||||
let denominator = bandwidth.value_in_hz();
|
||||
if denominator == 0 {
|
||||
Err(RadioError::InvalidBandwidth)
|
||||
} else {
|
||||
Ok((numerator + denominator - 1) / denominator)
|
||||
}
|
||||
}
|
||||
|
||||
/// Send the buffer of the given size. Prepares the packet to be sent and sets the radio in transmission [timeout in ms]
|
||||
pub async fn send(&mut self, buffer: &[u8], timeout: u32) -> Result<(), RadioError<BUS>> {
|
||||
if self.packet_params.is_some() {
|
||||
self.sub_set_dio_irq_params(
|
||||
IrqMask::TxDone.value() | IrqMask::RxTxTimeout.value(),
|
||||
IrqMask::TxDone.value() | IrqMask::RxTxTimeout.value(),
|
||||
IrqMask::None.value(),
|
||||
IrqMask::None.value(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut packet_params = self.packet_params.as_mut().unwrap();
|
||||
packet_params.payload_length = buffer.len() as u8;
|
||||
self.sub_set_packet_params().await?;
|
||||
self.sub_send_payload(buffer, timeout).await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RadioError::PacketParamsMissing)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the radio in sleep mode
|
||||
pub async fn sleep(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_sleep(SleepParams {
|
||||
wakeup_rtc: false,
|
||||
reset: false,
|
||||
warm_start: true,
|
||||
})
|
||||
.await?;
|
||||
Timer::after(Duration::from_millis(2)).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the radio in standby mode
|
||||
pub async fn standby(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_standby(StandbyMode::RC).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the radio in reception mode for the given duration [0: continuous, others: timeout (ms)]
|
||||
pub async fn rx(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_dio_irq_params(
|
||||
IrqMask::All.value(),
|
||||
IrqMask::All.value(),
|
||||
IrqMask::None.value(),
|
||||
IrqMask::None.value(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if self.rx_continuous {
|
||||
self.sub_set_rx(0xFFFFFF).await?;
|
||||
} else {
|
||||
self.sub_set_rx(timeout << 6).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Start a Channel Activity Detection
|
||||
pub async fn start_cad(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_dio_irq_params(
|
||||
IrqMask::CADDone.value() | IrqMask::CADActivityDetected.value(),
|
||||
IrqMask::CADDone.value() | IrqMask::CADActivityDetected.value(),
|
||||
IrqMask::None.value(),
|
||||
IrqMask::None.value(),
|
||||
)
|
||||
.await?;
|
||||
self.sub_set_cad().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the radio in continuous wave transmission mode
|
||||
/// frequency channel RF frequency
|
||||
/// power output power [dBm]
|
||||
/// timeout transmission mode timeout [s]
|
||||
pub async fn set_tx_continuous_wave(
|
||||
&mut self,
|
||||
frequency: u32,
|
||||
power: i8,
|
||||
_timeout: u16,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_rf_frequency(frequency).await?;
|
||||
self.brd_set_rf_tx_power(power).await?;
|
||||
self.sub_set_tx_continuous_wave().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read the current RSSI value for the LoRa modem (only) [dBm]
|
||||
pub async fn get_rssi(&mut self) -> Result<i16, RadioError<BUS>> {
|
||||
let value = self.sub_get_rssi_inst().await?;
|
||||
Ok(value as i16)
|
||||
}
|
||||
|
||||
/// Write one or more radio registers with a buffer of a given size, starting at the first register address
|
||||
pub async fn write_registers_from_buffer(
|
||||
&mut self,
|
||||
start_register: Register,
|
||||
buffer: &[u8],
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_registers(start_register, buffer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read one or more radio registers into a buffer of a given size, starting at the first register address
|
||||
pub async fn read_registers_into_buffer(
|
||||
&mut self,
|
||||
start_register: Register,
|
||||
buffer: &mut [u8],
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_read_registers(start_register, buffer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the maximum payload length (in bytes) for a LoRa modem (only).
|
||||
pub async fn set_max_payload_length(&mut self, max: u8) -> Result<(), RadioError<BUS>> {
|
||||
if self.packet_params.is_some() {
|
||||
let packet_params = self.packet_params.as_mut().unwrap();
|
||||
self.max_payload_length = max;
|
||||
packet_params.payload_length = max;
|
||||
self.sub_set_packet_params().await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RadioError::PacketParamsMissing)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the time required for the board plus radio to get out of sleep [ms]
|
||||
pub fn get_wakeup_time(&mut self) -> u32 {
|
||||
self.brd_get_board_tcxo_wakeup_time() + RADIO_WAKEUP_TIME
|
||||
}
|
||||
|
||||
/// Process the radio irq
|
||||
pub async fn process_irq(
|
||||
&mut self,
|
||||
receiving_buffer: Option<&mut [u8]>,
|
||||
received_len: Option<&mut u8>,
|
||||
cad_activity_detected: Option<&mut bool>,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
loop {
|
||||
trace!("process_irq loop entered");
|
||||
|
||||
let de = self.sub_get_device_errors().await?;
|
||||
trace!("device_errors: rc_64khz_calibration = {}, rc_13mhz_calibration = {}, pll_calibration = {}, adc_calibration = {}, image_calibration = {}, xosc_start = {}, pll_lock = {}, pa_ramp = {}",
|
||||
de.rc_64khz_calibration, de.rc_13mhz_calibration, de.pll_calibration, de.adc_calibration, de.image_calibration, de.xosc_start, de.pll_lock, de.pa_ramp);
|
||||
let st = self.sub_get_status().await?;
|
||||
trace!(
|
||||
"radio status: cmd_status: {:x}, chip_mode: {:x}",
|
||||
st.cmd_status,
|
||||
st.chip_mode
|
||||
);
|
||||
|
||||
self.dio1.wait_for_high().await.map_err(|_| DIO1)?;
|
||||
let operating_mode = self.brd_get_operating_mode();
|
||||
let irq_flags = self.sub_get_irq_status().await?;
|
||||
self.sub_clear_irq_status(irq_flags).await?;
|
||||
trace!("process_irq DIO1 satisfied: irq_flags = {:x}", irq_flags);
|
||||
|
||||
// check for errors and unexpected interrupt masks (based on operation mode)
|
||||
if (irq_flags & IrqMask::HeaderError.value()) == IrqMask::HeaderError.value() {
|
||||
if !self.rx_continuous {
|
||||
self.brd_set_operating_mode(RadioMode::StandbyRC);
|
||||
}
|
||||
return Err(RadioError::HeaderError);
|
||||
} else if (irq_flags & IrqMask::CRCError.value()) == IrqMask::CRCError.value() {
|
||||
if operating_mode == RadioMode::Receive {
|
||||
if !self.rx_continuous {
|
||||
self.brd_set_operating_mode(RadioMode::StandbyRC);
|
||||
}
|
||||
return Err(RadioError::CRCErrorOnReceive);
|
||||
} else {
|
||||
return Err(RadioError::CRCErrorUnexpected);
|
||||
}
|
||||
} else if (irq_flags & IrqMask::RxTxTimeout.value()) == IrqMask::RxTxTimeout.value() {
|
||||
if operating_mode == RadioMode::Transmit {
|
||||
self.brd_set_operating_mode(RadioMode::StandbyRC);
|
||||
return Err(RadioError::TransmitTimeout);
|
||||
} else if operating_mode == RadioMode::Receive {
|
||||
self.brd_set_operating_mode(RadioMode::StandbyRC);
|
||||
return Err(RadioError::ReceiveTimeout);
|
||||
} else {
|
||||
return Err(RadioError::TimeoutUnexpected);
|
||||
}
|
||||
} else if ((irq_flags & IrqMask::TxDone.value()) == IrqMask::TxDone.value())
|
||||
&& (operating_mode != RadioMode::Transmit)
|
||||
{
|
||||
return Err(RadioError::TransmitDoneUnexpected);
|
||||
} else if ((irq_flags & IrqMask::RxDone.value()) == IrqMask::RxDone.value())
|
||||
&& (operating_mode != RadioMode::Receive)
|
||||
{
|
||||
return Err(RadioError::ReceiveDoneUnexpected);
|
||||
} else if (((irq_flags & IrqMask::CADActivityDetected.value()) == IrqMask::CADActivityDetected.value())
|
||||
|| ((irq_flags & IrqMask::CADDone.value()) == IrqMask::CADDone.value()))
|
||||
&& (operating_mode != RadioMode::ChannelActivityDetection)
|
||||
{
|
||||
return Err(RadioError::CADUnexpected);
|
||||
}
|
||||
|
||||
if (irq_flags & IrqMask::HeaderValid.value()) == IrqMask::HeaderValid.value() {
|
||||
trace!("HeaderValid");
|
||||
} else if (irq_flags & IrqMask::PreambleDetected.value()) == IrqMask::PreambleDetected.value() {
|
||||
trace!("PreambleDetected");
|
||||
} else if (irq_flags & IrqMask::SyncwordValid.value()) == IrqMask::SyncwordValid.value() {
|
||||
trace!("SyncwordValid");
|
||||
}
|
||||
|
||||
// handle completions
|
||||
if (irq_flags & IrqMask::TxDone.value()) == IrqMask::TxDone.value() {
|
||||
self.brd_set_operating_mode(RadioMode::StandbyRC);
|
||||
return Ok(());
|
||||
} else if (irq_flags & IrqMask::RxDone.value()) == IrqMask::RxDone.value() {
|
||||
if !self.rx_continuous {
|
||||
self.brd_set_operating_mode(RadioMode::StandbyRC);
|
||||
|
||||
// implicit header mode timeout behavior (see DS_SX1261-2_V1.2 datasheet chapter 15.3)
|
||||
self.brd_write_registers(Register::RTCCtrl, &[0x00]).await?;
|
||||
let mut evt_clr = [0x00u8];
|
||||
self.brd_read_registers(Register::EvtClr, &mut evt_clr).await?;
|
||||
evt_clr[0] |= 1 << 1;
|
||||
self.brd_write_registers(Register::EvtClr, &evt_clr).await?;
|
||||
}
|
||||
|
||||
if receiving_buffer.is_some() && received_len.is_some() {
|
||||
*(received_len.unwrap()) = self.sub_get_payload(receiving_buffer.unwrap()).await?;
|
||||
}
|
||||
self.packet_status = self.sub_get_packet_status().await?.into();
|
||||
return Ok(());
|
||||
} else if (irq_flags & IrqMask::CADDone.value()) == IrqMask::CADDone.value() {
|
||||
if cad_activity_detected.is_some() {
|
||||
*(cad_activity_detected.unwrap()) =
|
||||
(irq_flags & IrqMask::CADActivityDetected.value()) == IrqMask::CADActivityDetected.value();
|
||||
}
|
||||
self.brd_set_operating_mode(RadioMode::StandbyRC);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// if DIO1 was driven high for reasons other than an error or operation completion (currently, PreambleDetected, SyncwordValid, and HeaderValid
|
||||
// are in that category), loop to wait again
|
||||
}
|
||||
}
|
||||
|
||||
// SX126x-specific functions
|
||||
|
||||
/// Set the radio in reception mode with Max LNA gain for the given time (SX126x radios only) [0: continuous, others timeout in ms]
|
||||
pub async fn set_rx_boosted(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_dio_irq_params(
|
||||
IrqMask::All.value(),
|
||||
IrqMask::All.value(),
|
||||
IrqMask::None.value(),
|
||||
IrqMask::None.value(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if self.rx_continuous {
|
||||
self.sub_set_rx_boosted(0xFFFFFF).await?; // Rx continuous
|
||||
} else {
|
||||
self.sub_set_rx_boosted(timeout << 6).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the Rx duty cycle management parameters (SX126x radios only)
|
||||
/// rx_time structure describing reception timeout value
|
||||
/// sleep_time structure describing sleep timeout value
|
||||
pub async fn set_rx_duty_cycle(&mut self, rx_time: u32, sleep_time: u32) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_rx_duty_cycle(rx_time, sleep_time).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_latest_packet_status(&mut self) -> Option<PacketStatus> {
|
||||
self.packet_status
|
||||
}
|
||||
|
||||
// Utilities
|
||||
|
||||
async fn add_register_to_retention_list(&mut self, register_address: u16) -> Result<(), RadioError<BUS>> {
|
||||
let mut buffer = [0x00u8; (1 + (2 * MAX_NUMBER_REGS_IN_RETENTION)) as usize];
|
||||
|
||||
// Read the address and registers already added to the list
|
||||
self.brd_read_registers(Register::RetentionList, &mut buffer).await?;
|
||||
|
||||
let number_of_registers = buffer[0];
|
||||
for i in 0..number_of_registers {
|
||||
if register_address
|
||||
== ((buffer[(1 + (2 * i)) as usize] as u16) << 8) | (buffer[(2 + (2 * i)) as usize] as u16)
|
||||
{
|
||||
return Ok(()); // register already in list
|
||||
}
|
||||
}
|
||||
|
||||
if number_of_registers < MAX_NUMBER_REGS_IN_RETENTION {
|
||||
buffer[0] += 1; // increment number of registers
|
||||
|
||||
buffer[(1 + (2 * number_of_registers)) as usize] = ((register_address >> 8) & 0xFF) as u8;
|
||||
buffer[(2 + (2 * number_of_registers)) as usize] = (register_address & 0xFF) as u8;
|
||||
self.brd_write_registers(Register::RetentionList, &buffer).await?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RadioError::RetentionListExceeded)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_lora_time_on_air_numerator(
|
||||
spreading_factor: SpreadingFactor,
|
||||
bandwidth: Bandwidth,
|
||||
coding_rate: CodingRate,
|
||||
preamble_length: u16,
|
||||
fixed_len: bool,
|
||||
payload_len: u8,
|
||||
crc_on: bool,
|
||||
) -> u32 {
|
||||
let cell_denominator;
|
||||
let cr_denominator = (coding_rate.value() as i32) + 4;
|
||||
|
||||
// Ensure that the preamble length is at least 12 symbols when using SF5 or SF6
|
||||
let mut preamble_length_final = preamble_length;
|
||||
if ((spreading_factor == SpreadingFactor::_5) || (spreading_factor == SpreadingFactor::_6))
|
||||
&& (preamble_length < 12)
|
||||
{
|
||||
preamble_length_final = 12;
|
||||
}
|
||||
|
||||
let mut low_data_rate_optimize = false;
|
||||
if (((spreading_factor == SpreadingFactor::_11) || (spreading_factor == SpreadingFactor::_12))
|
||||
&& (bandwidth == Bandwidth::_125KHz))
|
||||
|| ((spreading_factor == SpreadingFactor::_12) && (bandwidth == Bandwidth::_250KHz))
|
||||
{
|
||||
low_data_rate_optimize = true;
|
||||
}
|
||||
|
||||
let mut cell_numerator = ((payload_len as i32) << 3) + (if crc_on { 16 } else { 0 })
|
||||
- (4 * spreading_factor.value() as i32)
|
||||
+ (if fixed_len { 0 } else { 20 });
|
||||
|
||||
if spreading_factor.value() <= 6 {
|
||||
cell_denominator = 4 * (spreading_factor.value() as i32);
|
||||
} else {
|
||||
cell_numerator += 8;
|
||||
if low_data_rate_optimize {
|
||||
cell_denominator = 4 * ((spreading_factor.value() as i32) - 2);
|
||||
} else {
|
||||
cell_denominator = 4 * (spreading_factor.value() as i32);
|
||||
}
|
||||
}
|
||||
|
||||
if cell_numerator < 0 {
|
||||
cell_numerator = 0;
|
||||
}
|
||||
|
||||
let mut intermediate: i32 = (((cell_numerator + cell_denominator - 1) / cell_denominator) * cr_denominator)
|
||||
+ (preamble_length_final as i32)
|
||||
+ 12;
|
||||
|
||||
if spreading_factor.value() <= 6 {
|
||||
intermediate = intermediate + 2;
|
||||
}
|
||||
|
||||
(((4 * intermediate) + 1) * (1 << (spreading_factor.value() - 2))) as u32
|
||||
}
|
||||
}
|
@ -1,469 +0,0 @@
|
||||
use core::fmt::Debug;
|
||||
|
||||
use lorawan_device::async_device::radio as device;
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum RadioError<BUS> {
|
||||
SPI(BUS),
|
||||
CS,
|
||||
Reset,
|
||||
AntRx,
|
||||
AntTx,
|
||||
Busy,
|
||||
DIO1,
|
||||
PayloadSizeMismatch(usize, usize),
|
||||
RetentionListExceeded,
|
||||
InvalidBandwidth,
|
||||
ModulationParamsMissing,
|
||||
PacketParamsMissing,
|
||||
HeaderError,
|
||||
CRCErrorUnexpected,
|
||||
CRCErrorOnReceive,
|
||||
TransmitTimeout,
|
||||
ReceiveTimeout,
|
||||
TimeoutUnexpected,
|
||||
TransmitDoneUnexpected,
|
||||
ReceiveDoneUnexpected,
|
||||
CADUnexpected,
|
||||
}
|
||||
|
||||
pub struct RadioSystemError {
|
||||
pub rc_64khz_calibration: bool,
|
||||
pub rc_13mhz_calibration: bool,
|
||||
pub pll_calibration: bool,
|
||||
pub adc_calibration: bool,
|
||||
pub image_calibration: bool,
|
||||
pub xosc_start: bool,
|
||||
pub pll_lock: bool,
|
||||
pub pa_ramp: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum PacketType {
|
||||
GFSK = 0x00,
|
||||
LoRa = 0x01,
|
||||
None = 0x0F,
|
||||
}
|
||||
|
||||
impl PacketType {
|
||||
pub const fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
pub fn to_enum(value: u8) -> Self {
|
||||
if value == 0x00 {
|
||||
PacketType::GFSK
|
||||
} else if value == 0x01 {
|
||||
PacketType::LoRa
|
||||
} else {
|
||||
PacketType::None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct PacketStatus {
|
||||
pub rssi: i8,
|
||||
pub snr: i8,
|
||||
pub signal_rssi: i8,
|
||||
pub freq_error: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum RadioType {
|
||||
SX1261,
|
||||
SX1262,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum RadioMode {
|
||||
Sleep = 0x00, // sleep mode
|
||||
StandbyRC = 0x01, // standby mode with RC oscillator
|
||||
StandbyXOSC = 0x02, // standby mode with XOSC oscillator
|
||||
FrequencySynthesis = 0x03, // frequency synthesis mode
|
||||
Transmit = 0x04, // transmit mode
|
||||
Receive = 0x05, // receive mode
|
||||
ReceiveDutyCycle = 0x06, // receive duty cycle mode
|
||||
ChannelActivityDetection = 0x07, // channel activity detection mode
|
||||
}
|
||||
|
||||
impl RadioMode {
|
||||
/// Returns the value of the mode.
|
||||
pub const fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
pub fn to_enum(value: u8) -> Self {
|
||||
if value == 0x00 {
|
||||
RadioMode::Sleep
|
||||
} else if value == 0x01 {
|
||||
RadioMode::StandbyRC
|
||||
} else if value == 0x02 {
|
||||
RadioMode::StandbyXOSC
|
||||
} else if value == 0x03 {
|
||||
RadioMode::FrequencySynthesis
|
||||
} else if value == 0x04 {
|
||||
RadioMode::Transmit
|
||||
} else if value == 0x05 {
|
||||
RadioMode::Receive
|
||||
} else if value == 0x06 {
|
||||
RadioMode::ReceiveDutyCycle
|
||||
} else if value == 0x07 {
|
||||
RadioMode::ChannelActivityDetection
|
||||
} else {
|
||||
RadioMode::Sleep
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum RadioState {
|
||||
Idle = 0x00,
|
||||
RxRunning = 0x01,
|
||||
TxRunning = 0x02,
|
||||
ChannelActivityDetecting = 0x03,
|
||||
}
|
||||
|
||||
impl RadioState {
|
||||
/// Returns the value of the state.
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RadioStatus {
|
||||
pub cmd_status: u8,
|
||||
pub chip_mode: u8,
|
||||
}
|
||||
|
||||
impl RadioStatus {
|
||||
pub fn value(self) -> u8 {
|
||||
(self.chip_mode << 4) | (self.cmd_status << 1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum IrqMask {
|
||||
None = 0x0000,
|
||||
TxDone = 0x0001,
|
||||
RxDone = 0x0002,
|
||||
PreambleDetected = 0x0004,
|
||||
SyncwordValid = 0x0008,
|
||||
HeaderValid = 0x0010,
|
||||
HeaderError = 0x0020,
|
||||
CRCError = 0x0040,
|
||||
CADDone = 0x0080,
|
||||
CADActivityDetected = 0x0100,
|
||||
RxTxTimeout = 0x0200,
|
||||
All = 0xFFFF,
|
||||
}
|
||||
|
||||
impl IrqMask {
|
||||
pub fn value(self) -> u16 {
|
||||
self as u16
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Register {
|
||||
PacketParams = 0x0704, // packet configuration
|
||||
PayloadLength = 0x0702, // payload size
|
||||
SynchTimeout = 0x0706, // recalculated number of symbols
|
||||
Syncword = 0x06C0, // Syncword values
|
||||
LoRaSyncword = 0x0740, // LoRa Syncword value
|
||||
GeneratedRandomNumber = 0x0819, //32-bit generated random number
|
||||
AnaLNA = 0x08E2, // disable the LNA
|
||||
AnaMixer = 0x08E5, // disable the mixer
|
||||
RxGain = 0x08AC, // RX gain (0x94: power saving, 0x96: rx boosted)
|
||||
XTATrim = 0x0911, // device internal trimming capacitor
|
||||
OCP = 0x08E7, // over current protection max value
|
||||
RetentionList = 0x029F, // retention list
|
||||
IQPolarity = 0x0736, // optimize the inverted IQ operation (see DS_SX1261-2_V1.2 datasheet chapter 15.4)
|
||||
TxModulation = 0x0889, // modulation quality with 500 kHz LoRa Bandwidth (see DS_SX1261-2_V1.2 datasheet chapter 15.1)
|
||||
TxClampCfg = 0x08D8, // better resistance to antenna mismatch (see DS_SX1261-2_V1.2 datasheet chapter 15.2)
|
||||
RTCCtrl = 0x0902, // RTC control
|
||||
EvtClr = 0x0944, // event clear
|
||||
}
|
||||
|
||||
impl Register {
|
||||
pub fn addr(self) -> u16 {
|
||||
self as u16
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum OpCode {
|
||||
GetStatus = 0xC0,
|
||||
WriteRegister = 0x0D,
|
||||
ReadRegister = 0x1D,
|
||||
WriteBuffer = 0x0E,
|
||||
ReadBuffer = 0x1E,
|
||||
SetSleep = 0x84,
|
||||
SetStandby = 0x80,
|
||||
SetFS = 0xC1,
|
||||
SetTx = 0x83,
|
||||
SetRx = 0x82,
|
||||
SetRxDutyCycle = 0x94,
|
||||
SetCAD = 0xC5,
|
||||
SetTxContinuousWave = 0xD1,
|
||||
SetTxContinuousPremable = 0xD2,
|
||||
SetPacketType = 0x8A,
|
||||
GetPacketType = 0x11,
|
||||
SetRFFrequency = 0x86,
|
||||
SetTxParams = 0x8E,
|
||||
SetPAConfig = 0x95,
|
||||
SetCADParams = 0x88,
|
||||
SetBufferBaseAddress = 0x8F,
|
||||
SetModulationParams = 0x8B,
|
||||
SetPacketParams = 0x8C,
|
||||
GetRxBufferStatus = 0x13,
|
||||
GetPacketStatus = 0x14,
|
||||
GetRSSIInst = 0x15,
|
||||
GetStats = 0x10,
|
||||
ResetStats = 0x00,
|
||||
CfgDIOIrq = 0x08,
|
||||
GetIrqStatus = 0x12,
|
||||
ClrIrqStatus = 0x02,
|
||||
Calibrate = 0x89,
|
||||
CalibrateImage = 0x98,
|
||||
SetRegulatorMode = 0x96,
|
||||
GetErrors = 0x17,
|
||||
ClrErrors = 0x07,
|
||||
SetTCXOMode = 0x97,
|
||||
SetTxFallbackMode = 0x93,
|
||||
SetRFSwitchMode = 0x9D,
|
||||
SetStopRxTimerOnPreamble = 0x9F,
|
||||
SetLoRaSymbTimeout = 0xA0,
|
||||
}
|
||||
|
||||
impl OpCode {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SleepParams {
|
||||
pub wakeup_rtc: bool, // get out of sleep mode if wakeup signal received from RTC
|
||||
pub reset: bool,
|
||||
pub warm_start: bool,
|
||||
}
|
||||
|
||||
impl SleepParams {
|
||||
pub fn value(self) -> u8 {
|
||||
((self.warm_start as u8) << 2) | ((self.reset as u8) << 1) | (self.wakeup_rtc as u8)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum StandbyMode {
|
||||
RC = 0x00,
|
||||
XOSC = 0x01,
|
||||
}
|
||||
|
||||
impl StandbyMode {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum RegulatorMode {
|
||||
UseLDO = 0x00,
|
||||
UseDCDC = 0x01,
|
||||
}
|
||||
|
||||
impl RegulatorMode {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CalibrationParams {
|
||||
pub rc64k_enable: bool, // calibrate RC64K clock
|
||||
pub rc13m_enable: bool, // calibrate RC13M clock
|
||||
pub pll_enable: bool, // calibrate PLL
|
||||
pub adc_pulse_enable: bool, // calibrate ADC Pulse
|
||||
pub adc_bulkn_enable: bool, // calibrate ADC bulkN
|
||||
pub adc_bulkp_enable: bool, // calibrate ADC bulkP
|
||||
pub img_enable: bool,
|
||||
}
|
||||
|
||||
impl CalibrationParams {
|
||||
pub fn value(self) -> u8 {
|
||||
((self.img_enable as u8) << 6)
|
||||
| ((self.adc_bulkp_enable as u8) << 5)
|
||||
| ((self.adc_bulkn_enable as u8) << 4)
|
||||
| ((self.adc_pulse_enable as u8) << 3)
|
||||
| ((self.pll_enable as u8) << 2)
|
||||
| ((self.rc13m_enable as u8) << 1)
|
||||
| ((self.rc64k_enable as u8) << 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum TcxoCtrlVoltage {
|
||||
Ctrl1V6 = 0x00,
|
||||
Ctrl1V7 = 0x01,
|
||||
Ctrl1V8 = 0x02,
|
||||
Ctrl2V2 = 0x03,
|
||||
Ctrl2V4 = 0x04,
|
||||
Ctrl2V7 = 0x05,
|
||||
Ctrl3V0 = 0x06,
|
||||
Ctrl3V3 = 0x07,
|
||||
}
|
||||
|
||||
impl TcxoCtrlVoltage {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum RampTime {
|
||||
Ramp10Us = 0x00,
|
||||
Ramp20Us = 0x01,
|
||||
Ramp40Us = 0x02,
|
||||
Ramp80Us = 0x03,
|
||||
Ramp200Us = 0x04,
|
||||
Ramp800Us = 0x05,
|
||||
Ramp1700Us = 0x06,
|
||||
Ramp3400Us = 0x07,
|
||||
}
|
||||
|
||||
impl RampTime {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum SpreadingFactor {
|
||||
_5 = 0x05,
|
||||
_6 = 0x06,
|
||||
_7 = 0x07,
|
||||
_8 = 0x08,
|
||||
_9 = 0x09,
|
||||
_10 = 0x0A,
|
||||
_11 = 0x0B,
|
||||
_12 = 0x0C,
|
||||
}
|
||||
|
||||
impl SpreadingFactor {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl From<device::SpreadingFactor> for SpreadingFactor {
|
||||
fn from(sf: device::SpreadingFactor) -> Self {
|
||||
match sf {
|
||||
device::SpreadingFactor::_7 => SpreadingFactor::_7,
|
||||
device::SpreadingFactor::_8 => SpreadingFactor::_8,
|
||||
device::SpreadingFactor::_9 => SpreadingFactor::_9,
|
||||
device::SpreadingFactor::_10 => SpreadingFactor::_10,
|
||||
device::SpreadingFactor::_11 => SpreadingFactor::_11,
|
||||
device::SpreadingFactor::_12 => SpreadingFactor::_12,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum Bandwidth {
|
||||
_500KHz = 0x06,
|
||||
_250KHz = 0x05,
|
||||
_125KHz = 0x04,
|
||||
}
|
||||
|
||||
impl Bandwidth {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
|
||||
pub fn value_in_hz(self) -> u32 {
|
||||
match self {
|
||||
Bandwidth::_125KHz => 125000u32,
|
||||
Bandwidth::_250KHz => 250000u32,
|
||||
Bandwidth::_500KHz => 500000u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<device::Bandwidth> for Bandwidth {
|
||||
fn from(bw: device::Bandwidth) -> Self {
|
||||
match bw {
|
||||
device::Bandwidth::_500KHz => Bandwidth::_500KHz,
|
||||
device::Bandwidth::_250KHz => Bandwidth::_250KHz,
|
||||
device::Bandwidth::_125KHz => Bandwidth::_125KHz,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum CodingRate {
|
||||
_4_5 = 0x01,
|
||||
_4_6 = 0x02,
|
||||
_4_7 = 0x03,
|
||||
_4_8 = 0x04,
|
||||
}
|
||||
|
||||
impl CodingRate {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl From<device::CodingRate> for CodingRate {
|
||||
fn from(cr: device::CodingRate) -> Self {
|
||||
match cr {
|
||||
device::CodingRate::_4_5 => CodingRate::_4_5,
|
||||
device::CodingRate::_4_6 => CodingRate::_4_6,
|
||||
device::CodingRate::_4_7 => CodingRate::_4_7,
|
||||
device::CodingRate::_4_8 => CodingRate::_4_8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ModulationParams {
|
||||
pub spreading_factor: SpreadingFactor,
|
||||
pub bandwidth: Bandwidth,
|
||||
pub coding_rate: CodingRate,
|
||||
pub low_data_rate_optimize: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct PacketParams {
|
||||
pub preamble_length: u16, // number of LoRa symbols in the preamble
|
||||
pub implicit_header: bool, // if the header is explicit, it will be transmitted in the LoRa packet, but is not transmitted if the header is implicit (known fixed length)
|
||||
pub payload_length: u8,
|
||||
pub crc_on: bool,
|
||||
pub iq_inverted: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum CADSymbols {
|
||||
_1 = 0x00,
|
||||
_2 = 0x01,
|
||||
_4 = 0x02,
|
||||
_8 = 0x03,
|
||||
_16 = 0x04,
|
||||
}
|
||||
|
||||
impl CADSymbols {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum CADExitMode {
|
||||
CADOnly = 0x00,
|
||||
CADRx = 0x01,
|
||||
CADLBT = 0x10,
|
||||
}
|
||||
|
||||
impl CADExitMode {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
@ -1,674 +0,0 @@
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use embedded_hal_async::digital::Wait;
|
||||
use embedded_hal_async::spi::SpiBus;
|
||||
|
||||
use super::mod_params::*;
|
||||
use super::LoRa;
|
||||
|
||||
// Internal frequency of the radio
|
||||
const SX126X_XTAL_FREQ: u32 = 32000000;
|
||||
|
||||
// Scaling factor used to perform fixed-point operations
|
||||
const SX126X_PLL_STEP_SHIFT_AMOUNT: u32 = 14;
|
||||
|
||||
// PLL step - scaled with SX126X_PLL_STEP_SHIFT_AMOUNT
|
||||
const SX126X_PLL_STEP_SCALED: u32 = SX126X_XTAL_FREQ >> (25 - SX126X_PLL_STEP_SHIFT_AMOUNT);
|
||||
|
||||
// Maximum value for parameter symbNum
|
||||
const SX126X_MAX_LORA_SYMB_NUM_TIMEOUT: u8 = 248;
|
||||
|
||||
// Provides board-specific functionality for Semtech SX126x-based boards
|
||||
|
||||
impl<SPI, CTRL, WAIT, BUS> LoRa<SPI, CTRL, WAIT>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = BUS>,
|
||||
CTRL: OutputPin,
|
||||
WAIT: Wait,
|
||||
{
|
||||
// Initialize the radio driver
|
||||
pub(super) async fn sub_init(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_reset().await?;
|
||||
self.brd_wakeup().await?;
|
||||
self.sub_set_standby(StandbyMode::RC).await?;
|
||||
self.brd_io_tcxo_init().await?;
|
||||
self.brd_io_rf_switch_init().await?;
|
||||
self.image_calibrated = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Wakeup the radio if it is in Sleep mode and check that Busy is low
|
||||
pub(super) async fn sub_check_device_ready(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
let operating_mode = self.brd_get_operating_mode();
|
||||
if operating_mode == RadioMode::Sleep || operating_mode == RadioMode::ReceiveDutyCycle {
|
||||
self.brd_wakeup().await?;
|
||||
}
|
||||
self.brd_wait_on_busy().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Save the payload to be sent in the radio buffer
|
||||
pub(super) async fn sub_set_payload(&mut self, payload: &[u8]) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_buffer(0x00, payload).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Read the payload received.
|
||||
pub(super) async fn sub_get_payload(&mut self, buffer: &mut [u8]) -> Result<u8, RadioError<BUS>> {
|
||||
let (size, offset) = self.sub_get_rx_buffer_status().await?;
|
||||
if (size as usize) > buffer.len() {
|
||||
Err(RadioError::PayloadSizeMismatch(size as usize, buffer.len()))
|
||||
} else {
|
||||
self.brd_read_buffer(offset, buffer).await?;
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
// Send a payload
|
||||
pub(super) async fn sub_send_payload(&mut self, payload: &[u8], timeout: u32) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_payload(payload).await?;
|
||||
self.sub_set_tx(timeout).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Get a 32-bit random value generated by the radio. A valid packet type must have been configured before using this command.
|
||||
//
|
||||
// The radio must be in reception mode before executing this function. This code can potentially result in interrupt generation. It is the responsibility of
|
||||
// the calling code to disable radio interrupts before calling this function, and re-enable them afterwards if necessary, or be certain that any interrupts
|
||||
// generated during this process will not cause undesired side-effects in the software.
|
||||
//
|
||||
// The random numbers produced by the generator do not have a uniform or Gaussian distribution. If uniformity is needed, perform appropriate software post-processing.
|
||||
pub(super) async fn sub_get_random(&mut self) -> Result<u32, RadioError<BUS>> {
|
||||
let mut reg_ana_lna_buffer_original = [0x00u8];
|
||||
let mut reg_ana_mixer_buffer_original = [0x00u8];
|
||||
let mut reg_ana_lna_buffer = [0x00u8];
|
||||
let mut reg_ana_mixer_buffer = [0x00u8];
|
||||
let mut number_buffer = [0x00u8, 0x00u8, 0x00u8, 0x00u8];
|
||||
|
||||
self.brd_read_registers(Register::AnaLNA, &mut reg_ana_lna_buffer_original)
|
||||
.await?;
|
||||
reg_ana_lna_buffer[0] = reg_ana_lna_buffer_original[0] & (!(1 << 0));
|
||||
self.brd_write_registers(Register::AnaLNA, ®_ana_lna_buffer).await?;
|
||||
|
||||
self.brd_read_registers(Register::AnaMixer, &mut reg_ana_mixer_buffer_original)
|
||||
.await?;
|
||||
reg_ana_mixer_buffer[0] = reg_ana_mixer_buffer_original[0] & (!(1 << 7));
|
||||
self.brd_write_registers(Register::AnaMixer, ®_ana_mixer_buffer)
|
||||
.await?;
|
||||
|
||||
// Set radio in continuous reception
|
||||
self.sub_set_rx(0xFFFFFFu32).await?;
|
||||
|
||||
self.brd_read_registers(Register::GeneratedRandomNumber, &mut number_buffer)
|
||||
.await?;
|
||||
|
||||
self.sub_set_standby(StandbyMode::RC).await?;
|
||||
|
||||
self.brd_write_registers(Register::AnaLNA, ®_ana_lna_buffer_original)
|
||||
.await?;
|
||||
self.brd_write_registers(Register::AnaMixer, ®_ana_mixer_buffer_original)
|
||||
.await?;
|
||||
|
||||
Ok(Self::convert_u8_buffer_to_u32(&number_buffer))
|
||||
}
|
||||
|
||||
// Set the radio in sleep mode
|
||||
pub(super) async fn sub_set_sleep(&mut self, sleep_config: SleepParams) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_ant_sleep()?;
|
||||
|
||||
if !sleep_config.warm_start {
|
||||
self.image_calibrated = false;
|
||||
}
|
||||
|
||||
self.brd_write_command(OpCode::SetSleep, &[sleep_config.value()])
|
||||
.await?;
|
||||
self.brd_set_operating_mode(RadioMode::Sleep);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio in configuration mode
|
||||
pub(super) async fn sub_set_standby(&mut self, mode: StandbyMode) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_command(OpCode::SetStandby, &[mode.value()]).await?;
|
||||
if mode == StandbyMode::RC {
|
||||
self.brd_set_operating_mode(RadioMode::StandbyRC);
|
||||
} else {
|
||||
self.brd_set_operating_mode(RadioMode::StandbyXOSC);
|
||||
}
|
||||
|
||||
self.brd_ant_sleep()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio in FS mode
|
||||
pub(super) async fn sub_set_fs(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
// antenna settings ???
|
||||
self.brd_write_command(OpCode::SetFS, &[]).await?;
|
||||
self.brd_set_operating_mode(RadioMode::FrequencySynthesis);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio in transmission mode with timeout specified
|
||||
pub(super) async fn sub_set_tx(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
|
||||
let buffer = [
|
||||
Self::timeout_1(timeout),
|
||||
Self::timeout_2(timeout),
|
||||
Self::timeout_3(timeout),
|
||||
];
|
||||
|
||||
self.brd_ant_set_tx()?;
|
||||
|
||||
self.brd_set_operating_mode(RadioMode::Transmit);
|
||||
self.brd_write_command(OpCode::SetTx, &buffer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio in reception mode with timeout specified
|
||||
pub(super) async fn sub_set_rx(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
|
||||
let buffer = [
|
||||
Self::timeout_1(timeout),
|
||||
Self::timeout_2(timeout),
|
||||
Self::timeout_3(timeout),
|
||||
];
|
||||
|
||||
self.brd_ant_set_rx()?;
|
||||
|
||||
self.brd_set_operating_mode(RadioMode::Receive);
|
||||
self.brd_write_registers(Register::RxGain, &[0x94u8]).await?;
|
||||
self.brd_write_command(OpCode::SetRx, &buffer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio in reception mode with Boosted LNA gain and timeout specified
|
||||
pub(super) async fn sub_set_rx_boosted(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
|
||||
let buffer = [
|
||||
Self::timeout_1(timeout),
|
||||
Self::timeout_2(timeout),
|
||||
Self::timeout_3(timeout),
|
||||
];
|
||||
|
||||
self.brd_ant_set_rx()?;
|
||||
|
||||
self.brd_set_operating_mode(RadioMode::Receive);
|
||||
// set max LNA gain, increase current by ~2mA for around ~3dB in sensitivity
|
||||
self.brd_write_registers(Register::RxGain, &[0x96u8]).await?;
|
||||
self.brd_write_command(OpCode::SetRx, &buffer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the Rx duty cycle management parameters
|
||||
pub(super) async fn sub_set_rx_duty_cycle(&mut self, rx_time: u32, sleep_time: u32) -> Result<(), RadioError<BUS>> {
|
||||
let buffer = [
|
||||
((rx_time >> 16) & 0xFF) as u8,
|
||||
((rx_time >> 8) & 0xFF) as u8,
|
||||
(rx_time & 0xFF) as u8,
|
||||
((sleep_time >> 16) & 0xFF) as u8,
|
||||
((sleep_time >> 8) & 0xFF) as u8,
|
||||
(sleep_time & 0xFF) as u8,
|
||||
];
|
||||
|
||||
// antenna settings ???
|
||||
|
||||
self.brd_write_command(OpCode::SetRxDutyCycle, &buffer).await?;
|
||||
self.brd_set_operating_mode(RadioMode::ReceiveDutyCycle);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio in CAD mode
|
||||
pub(super) async fn sub_set_cad(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_ant_set_rx()?;
|
||||
|
||||
self.brd_write_command(OpCode::SetCAD, &[]).await?;
|
||||
self.brd_set_operating_mode(RadioMode::ChannelActivityDetection);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio in continuous wave transmission mode
|
||||
pub(super) async fn sub_set_tx_continuous_wave(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_ant_set_tx()?;
|
||||
|
||||
self.brd_write_command(OpCode::SetTxContinuousWave, &[]).await?;
|
||||
self.brd_set_operating_mode(RadioMode::Transmit);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio in continuous preamble transmission mode
|
||||
pub(super) async fn sub_set_tx_infinite_preamble(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_ant_set_tx()?;
|
||||
|
||||
self.brd_write_command(OpCode::SetTxContinuousPremable, &[]).await?;
|
||||
self.brd_set_operating_mode(RadioMode::Transmit);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Decide which interrupt will stop the internal radio rx timer.
|
||||
// false timer stop after header/syncword detection
|
||||
// true timer stop after preamble detection
|
||||
pub(super) async fn sub_set_stop_rx_timer_on_preamble_detect(
|
||||
&mut self,
|
||||
enable: bool,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_command(OpCode::SetStopRxTimerOnPreamble, &[enable as u8])
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the number of symbols the radio will wait to validate a reception
|
||||
pub(super) async fn sub_set_lora_symb_num_timeout(&mut self, symb_num: u16) -> Result<(), RadioError<BUS>> {
|
||||
let mut exp = 0u8;
|
||||
let mut reg;
|
||||
let mut mant = ((core::cmp::min(symb_num, SX126X_MAX_LORA_SYMB_NUM_TIMEOUT as u16) as u8) + 1) >> 1;
|
||||
while mant > 31 {
|
||||
mant = (mant + 3) >> 2;
|
||||
exp += 1;
|
||||
}
|
||||
reg = mant << ((2 * exp) + 1);
|
||||
|
||||
self.brd_write_command(OpCode::SetLoRaSymbTimeout, &[reg]).await?;
|
||||
|
||||
if symb_num != 0 {
|
||||
reg = exp + (mant << 3);
|
||||
self.brd_write_registers(Register::SynchTimeout, &[reg]).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the power regulators operating mode (LDO or DC_DC). Using only LDO implies that the Rx or Tx current is doubled
|
||||
pub(super) async fn sub_set_regulator_mode(&mut self, mode: RegulatorMode) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_command(OpCode::SetRegulatorMode, &[mode.value()])
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Calibrate the given radio block
|
||||
pub(super) async fn sub_calibrate(&mut self, calibrate_params: CalibrationParams) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_command(OpCode::Calibrate, &[calibrate_params.value()])
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Calibrate the image rejection based on the given frequency
|
||||
pub(super) async fn sub_calibrate_image(&mut self, freq: u32) -> Result<(), RadioError<BUS>> {
|
||||
let mut cal_freq = [0x00u8, 0x00u8];
|
||||
|
||||
if freq > 900000000 {
|
||||
cal_freq[0] = 0xE1;
|
||||
cal_freq[1] = 0xE9;
|
||||
} else if freq > 850000000 {
|
||||
cal_freq[0] = 0xD7;
|
||||
cal_freq[1] = 0xDB;
|
||||
} else if freq > 770000000 {
|
||||
cal_freq[0] = 0xC1;
|
||||
cal_freq[1] = 0xC5;
|
||||
} else if freq > 460000000 {
|
||||
cal_freq[0] = 0x75;
|
||||
cal_freq[1] = 0x81;
|
||||
} else if freq > 425000000 {
|
||||
cal_freq[0] = 0x6B;
|
||||
cal_freq[1] = 0x6F;
|
||||
}
|
||||
self.brd_write_command(OpCode::CalibrateImage, &cal_freq).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Activate the extention of the timeout when a long preamble is used
|
||||
pub(super) async fn sub_set_long_preamble(&mut self, _enable: u8) -> Result<(), RadioError<BUS>> {
|
||||
Ok(()) // no operation currently
|
||||
}
|
||||
|
||||
// Set the transmission parameters
|
||||
// hp_max 0 for sx1261, 7 for sx1262
|
||||
// device_sel 1 for sx1261, 0 for sx1262
|
||||
// pa_lut 0 for 14dBm LUT, 1 for 22dBm LUT
|
||||
pub(super) async fn sub_set_pa_config(
|
||||
&mut self,
|
||||
pa_duty_cycle: u8,
|
||||
hp_max: u8,
|
||||
device_sel: u8,
|
||||
pa_lut: u8,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_command(OpCode::SetPAConfig, &[pa_duty_cycle, hp_max, device_sel, pa_lut])
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Define into which mode the chip goes after a TX / RX done
|
||||
pub(super) async fn sub_set_rx_tx_fallback_mode(&mut self, fallback_mode: u8) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_command(OpCode::SetTxFallbackMode, &[fallback_mode])
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the IRQ mask and DIO masks
|
||||
pub(super) async fn sub_set_dio_irq_params(
|
||||
&mut self,
|
||||
irq_mask: u16,
|
||||
dio1_mask: u16,
|
||||
dio2_mask: u16,
|
||||
dio3_mask: u16,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
let mut buffer = [0x00u8; 8];
|
||||
|
||||
buffer[0] = ((irq_mask >> 8) & 0x00FF) as u8;
|
||||
buffer[1] = (irq_mask & 0x00FF) as u8;
|
||||
buffer[2] = ((dio1_mask >> 8) & 0x00FF) as u8;
|
||||
buffer[3] = (dio1_mask & 0x00FF) as u8;
|
||||
buffer[4] = ((dio2_mask >> 8) & 0x00FF) as u8;
|
||||
buffer[5] = (dio2_mask & 0x00FF) as u8;
|
||||
buffer[6] = ((dio3_mask >> 8) & 0x00FF) as u8;
|
||||
buffer[7] = (dio3_mask & 0x00FF) as u8;
|
||||
self.brd_write_command(OpCode::CfgDIOIrq, &buffer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Return the current IRQ status
|
||||
pub(super) async fn sub_get_irq_status(&mut self) -> Result<u16, RadioError<BUS>> {
|
||||
let mut irq_status = [0x00u8, 0x00u8];
|
||||
self.brd_read_command(OpCode::GetIrqStatus, &mut irq_status).await?;
|
||||
Ok(((irq_status[0] as u16) << 8) | (irq_status[1] as u16))
|
||||
}
|
||||
|
||||
// Indicate if DIO2 is used to control an RF Switch
|
||||
pub(super) async fn sub_set_dio2_as_rf_switch_ctrl(&mut self, enable: bool) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_command(OpCode::SetRFSwitchMode, &[enable as u8]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Indicate if the radio main clock is supplied from a TCXO
|
||||
// tcxo_voltage voltage used to control the TCXO on/off from DIO3
|
||||
// timeout duration given to the TCXO to go to 32MHz
|
||||
pub(super) async fn sub_set_dio3_as_tcxo_ctrl(
|
||||
&mut self,
|
||||
tcxo_voltage: TcxoCtrlVoltage,
|
||||
timeout: u32,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
let buffer = [
|
||||
tcxo_voltage.value() & 0x07,
|
||||
Self::timeout_1(timeout),
|
||||
Self::timeout_2(timeout),
|
||||
Self::timeout_3(timeout),
|
||||
];
|
||||
self.brd_write_command(OpCode::SetTCXOMode, &buffer).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the RF frequency (Hz)
|
||||
pub(super) async fn sub_set_rf_frequency(&mut self, frequency: u32) -> Result<(), RadioError<BUS>> {
|
||||
let mut buffer = [0x00u8; 4];
|
||||
|
||||
if !self.image_calibrated {
|
||||
self.sub_calibrate_image(frequency).await?;
|
||||
self.image_calibrated = true;
|
||||
}
|
||||
|
||||
let freq_in_pll_steps = Self::convert_freq_in_hz_to_pll_step(frequency);
|
||||
|
||||
buffer[0] = ((freq_in_pll_steps >> 24) & 0xFF) as u8;
|
||||
buffer[1] = ((freq_in_pll_steps >> 16) & 0xFF) as u8;
|
||||
buffer[2] = ((freq_in_pll_steps >> 8) & 0xFF) as u8;
|
||||
buffer[3] = (freq_in_pll_steps & 0xFF) as u8;
|
||||
self.brd_write_command(OpCode::SetRFFrequency, &buffer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio for the given protocol (LoRa or GFSK). This method has to be called before setting RF frequency, modulation paramaters, and packet paramaters.
|
||||
pub(super) async fn sub_set_packet_type(&mut self, packet_type: PacketType) -> Result<(), RadioError<BUS>> {
|
||||
self.packet_type = packet_type;
|
||||
self.brd_write_command(OpCode::SetPacketType, &[packet_type.value()])
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Get the current radio protocol (LoRa or GFSK)
|
||||
pub(super) fn sub_get_packet_type(&mut self) -> PacketType {
|
||||
self.packet_type
|
||||
}
|
||||
|
||||
// Set the transmission parameters
|
||||
// power RF output power [-18..13] dBm
|
||||
// ramp_time transmission ramp up time
|
||||
pub(super) async fn sub_set_tx_params(
|
||||
&mut self,
|
||||
mut power: i8,
|
||||
ramp_time: RampTime,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
if self.brd_get_radio_type() == RadioType::SX1261 {
|
||||
if power == 15 {
|
||||
self.sub_set_pa_config(0x06, 0x00, 0x01, 0x01).await?;
|
||||
} else {
|
||||
self.sub_set_pa_config(0x04, 0x00, 0x01, 0x01).await?;
|
||||
}
|
||||
|
||||
if power >= 14 {
|
||||
power = 14;
|
||||
} else if power < -17 {
|
||||
power = -17;
|
||||
}
|
||||
} else {
|
||||
// Provide better resistance of the SX1262 Tx to antenna mismatch (see DS_SX1261-2_V1.2 datasheet chapter 15.2)
|
||||
let mut tx_clamp_cfg = [0x00u8];
|
||||
self.brd_read_registers(Register::TxClampCfg, &mut tx_clamp_cfg).await?;
|
||||
tx_clamp_cfg[0] = tx_clamp_cfg[0] | (0x0F << 1);
|
||||
self.brd_write_registers(Register::TxClampCfg, &tx_clamp_cfg).await?;
|
||||
|
||||
self.sub_set_pa_config(0x04, 0x07, 0x00, 0x01).await?;
|
||||
|
||||
if power > 22 {
|
||||
power = 22;
|
||||
} else if power < -9 {
|
||||
power = -9;
|
||||
}
|
||||
}
|
||||
|
||||
// power conversion of negative number from i8 to u8 ???
|
||||
self.brd_write_command(OpCode::SetTxParams, &[power as u8, ramp_time.value()])
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the modulation parameters
|
||||
pub(super) async fn sub_set_modulation_params(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
if self.modulation_params.is_some() {
|
||||
let mut buffer = [0x00u8; 4];
|
||||
|
||||
// Since this driver only supports LoRa, ensure the packet type is set accordingly
|
||||
self.sub_set_packet_type(PacketType::LoRa).await?;
|
||||
|
||||
let modulation_params = self.modulation_params.unwrap();
|
||||
buffer[0] = modulation_params.spreading_factor.value();
|
||||
buffer[1] = modulation_params.bandwidth.value();
|
||||
buffer[2] = modulation_params.coding_rate.value();
|
||||
buffer[3] = modulation_params.low_data_rate_optimize;
|
||||
|
||||
self.brd_write_command(OpCode::SetModulationParams, &buffer).await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RadioError::ModulationParamsMissing)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the packet parameters
|
||||
pub(super) async fn sub_set_packet_params(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
if self.packet_params.is_some() {
|
||||
let mut buffer = [0x00u8; 6];
|
||||
|
||||
// Since this driver only supports LoRa, ensure the packet type is set accordingly
|
||||
self.sub_set_packet_type(PacketType::LoRa).await?;
|
||||
|
||||
let packet_params = self.packet_params.unwrap();
|
||||
buffer[0] = ((packet_params.preamble_length >> 8) & 0xFF) as u8;
|
||||
buffer[1] = (packet_params.preamble_length & 0xFF) as u8;
|
||||
buffer[2] = packet_params.implicit_header as u8;
|
||||
buffer[3] = packet_params.payload_length;
|
||||
buffer[4] = packet_params.crc_on as u8;
|
||||
buffer[5] = packet_params.iq_inverted as u8;
|
||||
|
||||
self.brd_write_command(OpCode::SetPacketParams, &buffer).await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RadioError::PacketParamsMissing)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the channel activity detection (CAD) parameters
|
||||
// symbols number of symbols to use for CAD operations
|
||||
// det_peak limit for detection of SNR peak used in the CAD
|
||||
// det_min minimum symbol recognition for CAD
|
||||
// exit_mode operation to be done at the end of CAD action
|
||||
// timeout timeout value to abort the CAD activity
|
||||
|
||||
pub(super) async fn sub_set_cad_params(
|
||||
&mut self,
|
||||
symbols: CADSymbols,
|
||||
det_peak: u8,
|
||||
det_min: u8,
|
||||
exit_mode: CADExitMode,
|
||||
timeout: u32,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
let mut buffer = [0x00u8; 7];
|
||||
|
||||
buffer[0] = symbols.value();
|
||||
buffer[1] = det_peak;
|
||||
buffer[2] = det_min;
|
||||
buffer[3] = exit_mode.value();
|
||||
buffer[4] = Self::timeout_1(timeout);
|
||||
buffer[5] = Self::timeout_2(timeout);
|
||||
buffer[6] = Self::timeout_3(timeout);
|
||||
|
||||
self.brd_write_command(OpCode::SetCADParams, &buffer).await?;
|
||||
self.brd_set_operating_mode(RadioMode::ChannelActivityDetection);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the data buffer base address for transmission and reception
|
||||
pub(super) async fn sub_set_buffer_base_address(
|
||||
&mut self,
|
||||
tx_base_address: u8,
|
||||
rx_base_address: u8,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_command(OpCode::SetBufferBaseAddress, &[tx_base_address, rx_base_address])
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Get the current radio status
|
||||
pub(super) async fn sub_get_status(&mut self) -> Result<RadioStatus, RadioError<BUS>> {
|
||||
let status = self.brd_read_command(OpCode::GetStatus, &mut []).await?;
|
||||
Ok(RadioStatus {
|
||||
cmd_status: (status & (0x07 << 1)) >> 1,
|
||||
chip_mode: (status & (0x07 << 4)) >> 4,
|
||||
})
|
||||
}
|
||||
|
||||
// Get the instantaneous RSSI value for the last packet received
|
||||
pub(super) async fn sub_get_rssi_inst(&mut self) -> Result<i8, RadioError<BUS>> {
|
||||
let mut buffer = [0x00u8];
|
||||
self.brd_read_command(OpCode::GetRSSIInst, &mut buffer).await?;
|
||||
let rssi: i8 = ((-(buffer[0] as i32)) >> 1) as i8; // check this ???
|
||||
Ok(rssi)
|
||||
}
|
||||
|
||||
// Get the last received packet buffer status
|
||||
pub(super) async fn sub_get_rx_buffer_status(&mut self) -> Result<(u8, u8), RadioError<BUS>> {
|
||||
if self.packet_params.is_some() {
|
||||
let mut status = [0x00u8; 2];
|
||||
let mut payload_length_buffer = [0x00u8];
|
||||
|
||||
self.brd_read_command(OpCode::GetRxBufferStatus, &mut status).await?;
|
||||
if (self.sub_get_packet_type() == PacketType::LoRa) && self.packet_params.unwrap().implicit_header {
|
||||
self.brd_read_registers(Register::PayloadLength, &mut payload_length_buffer)
|
||||
.await?;
|
||||
} else {
|
||||
payload_length_buffer[0] = status[0];
|
||||
}
|
||||
|
||||
let payload_length = payload_length_buffer[0];
|
||||
let offset = status[1];
|
||||
|
||||
Ok((payload_length, offset))
|
||||
} else {
|
||||
Err(RadioError::PacketParamsMissing)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the last received packet payload status
|
||||
pub(super) async fn sub_get_packet_status(&mut self) -> Result<PacketStatus, RadioError<BUS>> {
|
||||
let mut status = [0x00u8; 3];
|
||||
self.brd_read_command(OpCode::GetPacketStatus, &mut status).await?;
|
||||
|
||||
// check this ???
|
||||
let rssi = ((-(status[0] as i32)) >> 1) as i8;
|
||||
let snr = ((status[1] as i8) + 2) >> 2;
|
||||
let signal_rssi = ((-(status[2] as i32)) >> 1) as i8;
|
||||
let freq_error = self.frequency_error;
|
||||
|
||||
Ok(PacketStatus {
|
||||
rssi,
|
||||
snr,
|
||||
signal_rssi,
|
||||
freq_error,
|
||||
})
|
||||
}
|
||||
|
||||
// Get the possible system errors
|
||||
pub(super) async fn sub_get_device_errors(&mut self) -> Result<RadioSystemError, RadioError<BUS>> {
|
||||
let mut errors = [0x00u8; 2];
|
||||
self.brd_read_command(OpCode::GetErrors, &mut errors).await?;
|
||||
|
||||
Ok(RadioSystemError {
|
||||
rc_64khz_calibration: (errors[1] & (1 << 0)) != 0,
|
||||
rc_13mhz_calibration: (errors[1] & (1 << 1)) != 0,
|
||||
pll_calibration: (errors[1] & (1 << 2)) != 0,
|
||||
adc_calibration: (errors[1] & (1 << 3)) != 0,
|
||||
image_calibration: (errors[1] & (1 << 4)) != 0,
|
||||
xosc_start: (errors[1] & (1 << 5)) != 0,
|
||||
pll_lock: (errors[1] & (1 << 6)) != 0,
|
||||
pa_ramp: (errors[0] & (1 << 0)) != 0,
|
||||
})
|
||||
}
|
||||
|
||||
// Clear all the errors in the device
|
||||
pub(super) async fn sub_clear_device_errors(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_command(OpCode::ClrErrors, &[0x00u8, 0x00u8]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Clear the IRQs
|
||||
pub(super) async fn sub_clear_irq_status(&mut self, irq: u16) -> Result<(), RadioError<BUS>> {
|
||||
let mut buffer = [0x00u8, 0x00u8];
|
||||
buffer[0] = ((irq >> 8) & 0xFF) as u8;
|
||||
buffer[1] = (irq & 0xFF) as u8;
|
||||
self.brd_write_command(OpCode::ClrIrqStatus, &buffer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
|
||||
fn timeout_1(timeout: u32) -> u8 {
|
||||
((timeout >> 16) & 0xFF) as u8
|
||||
}
|
||||
fn timeout_2(timeout: u32) -> u8 {
|
||||
((timeout >> 8) & 0xFF) as u8
|
||||
}
|
||||
fn timeout_3(timeout: u32) -> u8 {
|
||||
(timeout & 0xFF) as u8
|
||||
}
|
||||
|
||||
// check this ???
|
||||
fn convert_u8_buffer_to_u32(buffer: &[u8; 4]) -> u32 {
|
||||
let b0 = buffer[0] as u32;
|
||||
let b1 = buffer[1] as u32;
|
||||
let b2 = buffer[2] as u32;
|
||||
let b3 = buffer[3] as u32;
|
||||
(b0 << 24) | (b1 << 16) | (b2 << 8) | b3
|
||||
}
|
||||
|
||||
fn convert_freq_in_hz_to_pll_step(freq_in_hz: u32) -> u32 {
|
||||
// Get integer and fractional parts of the frequency computed with a PLL step scaled value
|
||||
let steps_int = freq_in_hz / SX126X_PLL_STEP_SCALED;
|
||||
let steps_frac = freq_in_hz - (steps_int * SX126X_PLL_STEP_SCALED);
|
||||
|
||||
(steps_int << SX126X_PLL_STEP_SHIFT_AMOUNT)
|
||||
+ (((steps_frac << SX126X_PLL_STEP_SHIFT_AMOUNT) + (SX126X_PLL_STEP_SCALED >> 1)) / SX126X_PLL_STEP_SCALED)
|
||||
}
|
||||
}
|
@ -1,217 +0,0 @@
|
||||
use core::future::Future;
|
||||
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use embedded_hal_async::digital::Wait;
|
||||
use embedded_hal_async::spi::*;
|
||||
use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig};
|
||||
use lorawan_device::async_device::Timings;
|
||||
|
||||
mod sx127x_lora;
|
||||
use sx127x_lora::{Error as RadioError, LoRa, RadioMode, IRQ};
|
||||
|
||||
/// Trait representing a radio switch for boards using the Sx127x radio. One some
|
||||
/// boards, this will be a dummy implementation that does nothing.
|
||||
pub trait RadioSwitch {
|
||||
fn set_tx(&mut self);
|
||||
fn set_rx(&mut self);
|
||||
}
|
||||
|
||||
/// Semtech Sx127x radio peripheral
|
||||
pub struct Sx127xRadio<SPI, CS, RESET, E, I, RFS>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = E> + 'static,
|
||||
E: 'static,
|
||||
CS: OutputPin + 'static,
|
||||
RESET: OutputPin + 'static,
|
||||
I: Wait + 'static,
|
||||
RFS: RadioSwitch + 'static,
|
||||
{
|
||||
radio: LoRa<SPI, CS, RESET>,
|
||||
rfs: RFS,
|
||||
irq: I,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum State {
|
||||
Idle,
|
||||
Txing,
|
||||
Rxing,
|
||||
}
|
||||
|
||||
impl<SPI, CS, RESET, E, I, RFS> Sx127xRadio<SPI, CS, RESET, E, I, RFS>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = E> + 'static,
|
||||
CS: OutputPin + 'static,
|
||||
RESET: OutputPin + 'static,
|
||||
I: Wait + 'static,
|
||||
RFS: RadioSwitch + 'static,
|
||||
E: 'static,
|
||||
{
|
||||
pub async fn new(
|
||||
spi: SPI,
|
||||
cs: CS,
|
||||
reset: RESET,
|
||||
irq: I,
|
||||
rfs: RFS,
|
||||
) -> Result<Self, RadioError<E, CS::Error, RESET::Error>> {
|
||||
let mut radio = LoRa::new(spi, cs, reset);
|
||||
radio.reset().await?;
|
||||
Ok(Self { radio, irq, rfs })
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, RESET, E, I, RFS> Timings for Sx127xRadio<SPI, CS, RESET, E, I, RFS>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = E> + 'static,
|
||||
CS: OutputPin + 'static,
|
||||
RESET: OutputPin + 'static,
|
||||
I: Wait + 'static,
|
||||
RFS: RadioSwitch + 'static,
|
||||
{
|
||||
fn get_rx_window_offset_ms(&self) -> i32 {
|
||||
-3
|
||||
}
|
||||
fn get_rx_window_duration_ms(&self) -> u32 {
|
||||
1003
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, RESET, E, I, RFS> PhyRxTx for Sx127xRadio<SPI, CS, RESET, E, I, RFS>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = E> + 'static,
|
||||
CS: OutputPin + 'static,
|
||||
E: 'static,
|
||||
RESET: OutputPin + 'static,
|
||||
I: Wait + 'static,
|
||||
RFS: RadioSwitch + 'static,
|
||||
{
|
||||
type PhyError = Sx127xError;
|
||||
|
||||
type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm
|
||||
where
|
||||
SPI: 'm,
|
||||
CS: 'm,
|
||||
RESET: 'm,
|
||||
E: 'm,
|
||||
I: 'm,
|
||||
RFS: 'm;
|
||||
|
||||
fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> {
|
||||
trace!("TX START");
|
||||
async move {
|
||||
self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap();
|
||||
self.rfs.set_tx();
|
||||
self.radio.set_tx_power(14, 0).await?;
|
||||
self.radio.set_frequency(config.rf.frequency).await?;
|
||||
// TODO: Modify radio to support other coding rates
|
||||
self.radio.set_coding_rate_4(5).await?;
|
||||
self.radio
|
||||
.set_signal_bandwidth(bandwidth_to_i64(config.rf.bandwidth))
|
||||
.await?;
|
||||
self.radio
|
||||
.set_spreading_factor(spreading_factor_to_u8(config.rf.spreading_factor))
|
||||
.await?;
|
||||
|
||||
self.radio.set_preamble_length(8).await?;
|
||||
self.radio.set_lora_pa_ramp().await?;
|
||||
self.radio.set_lora_sync_word().await?;
|
||||
self.radio.set_invert_iq(false).await?;
|
||||
self.radio.set_crc(true).await?;
|
||||
|
||||
self.radio.set_dio0_tx_done().await?;
|
||||
|
||||
self.radio.transmit_start(buf).await?;
|
||||
|
||||
loop {
|
||||
self.irq.wait_for_rising_edge().await.unwrap();
|
||||
self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap();
|
||||
let irq = self.radio.clear_irq().await.ok().unwrap();
|
||||
if (irq & IRQ::IrqTxDoneMask.addr()) != 0 {
|
||||
trace!("TX DONE");
|
||||
return Ok(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm
|
||||
where
|
||||
SPI: 'm,
|
||||
CS: 'm,
|
||||
RESET: 'm,
|
||||
E: 'm,
|
||||
I: 'm,
|
||||
RFS: 'm;
|
||||
|
||||
fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> {
|
||||
trace!("RX START");
|
||||
async move {
|
||||
self.rfs.set_rx();
|
||||
self.radio.reset_payload_length().await?;
|
||||
self.radio.set_frequency(config.frequency).await?;
|
||||
// TODO: Modify radio to support other coding rates
|
||||
self.radio.set_coding_rate_4(5).await?;
|
||||
self.radio
|
||||
.set_signal_bandwidth(bandwidth_to_i64(config.bandwidth))
|
||||
.await?;
|
||||
self.radio
|
||||
.set_spreading_factor(spreading_factor_to_u8(config.spreading_factor))
|
||||
.await?;
|
||||
|
||||
self.radio.set_preamble_length(8).await?;
|
||||
self.radio.set_lora_sync_word().await?;
|
||||
self.radio.set_invert_iq(true).await?;
|
||||
self.radio.set_crc(true).await?;
|
||||
|
||||
self.radio.set_dio0_rx_done().await?;
|
||||
self.radio.set_mode(RadioMode::RxContinuous).await?;
|
||||
|
||||
loop {
|
||||
self.irq.wait_for_rising_edge().await.unwrap();
|
||||
self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap();
|
||||
let irq = self.radio.clear_irq().await.ok().unwrap();
|
||||
if (irq & IRQ::IrqRxDoneMask.addr()) != 0 {
|
||||
let rssi = self.radio.get_packet_rssi().await.unwrap_or(0) as i16;
|
||||
let snr = self.radio.get_packet_snr().await.unwrap_or(0.0) as i8;
|
||||
let response = if let Ok(size) = self.radio.read_packet_size().await {
|
||||
self.radio.read_packet(buf).await?;
|
||||
Ok((size, RxQuality::new(rssi, snr)))
|
||||
} else {
|
||||
Ok((0, RxQuality::new(rssi, snr)))
|
||||
};
|
||||
trace!("RX DONE");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Sx127xError;
|
||||
|
||||
impl<A, B, C> From<sx127x_lora::Error<A, B, C>> for Sx127xError {
|
||||
fn from(_: sx127x_lora::Error<A, B, C>) -> Self {
|
||||
Sx127xError
|
||||
}
|
||||
}
|
||||
|
||||
fn spreading_factor_to_u8(sf: SpreadingFactor) -> u8 {
|
||||
match sf {
|
||||
SpreadingFactor::_7 => 7,
|
||||
SpreadingFactor::_8 => 8,
|
||||
SpreadingFactor::_9 => 9,
|
||||
SpreadingFactor::_10 => 10,
|
||||
SpreadingFactor::_11 => 11,
|
||||
SpreadingFactor::_12 => 12,
|
||||
}
|
||||
}
|
||||
|
||||
fn bandwidth_to_i64(bw: Bandwidth) -> i64 {
|
||||
match bw {
|
||||
Bandwidth::_125KHz => 125_000,
|
||||
Bandwidth::_250KHz => 250_000,
|
||||
Bandwidth::_500KHz => 500_000,
|
||||
}
|
||||
}
|
@ -1,539 +0,0 @@
|
||||
// Copyright Charles Wade (https://github.com/mr-glt/sx127x_lora). Licensed under the Apache 2.0
|
||||
// license
|
||||
//
|
||||
// Modifications made to make the driver work with the rust-lorawan link layer.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use bit_field::BitField;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use embedded_hal_async::spi::SpiBus;
|
||||
|
||||
mod register;
|
||||
pub use self::register::IRQ;
|
||||
use self::register::{PaConfig, Register};
|
||||
|
||||
/// Provides high-level access to Semtech SX1276/77/78/79 based boards connected to a Raspberry Pi
|
||||
pub struct LoRa<SPI, CS, RESET> {
|
||||
spi: SPI,
|
||||
cs: CS,
|
||||
reset: RESET,
|
||||
pub explicit_header: bool,
|
||||
pub mode: RadioMode,
|
||||
}
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Error<SPI, CS, RESET> {
|
||||
Uninformative,
|
||||
VersionMismatch(u8),
|
||||
CS(CS),
|
||||
Reset(RESET),
|
||||
SPI(SPI),
|
||||
Transmitting,
|
||||
}
|
||||
|
||||
use Error::*;
|
||||
|
||||
use super::sx127x_lora::register::{FskDataModulationShaping, FskRampUpRamDown};
|
||||
|
||||
#[cfg(not(feature = "version_0x09"))]
|
||||
const VERSION_CHECK: u8 = 0x12;
|
||||
|
||||
#[cfg(feature = "version_0x09")]
|
||||
const VERSION_CHECK: u8 = 0x09;
|
||||
|
||||
impl<SPI, CS, RESET, E> LoRa<SPI, CS, RESET>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = E>,
|
||||
CS: OutputPin,
|
||||
RESET: OutputPin,
|
||||
{
|
||||
/// Builds and returns a new instance of the radio. Only one instance of the radio should exist at a time.
|
||||
/// This also preforms a hardware reset of the module and then puts it in standby.
|
||||
pub fn new(spi: SPI, cs: CS, reset: RESET) -> Self {
|
||||
Self {
|
||||
spi,
|
||||
cs,
|
||||
reset,
|
||||
explicit_header: true,
|
||||
mode: RadioMode::Sleep,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn reset(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
self.reset.set_low().map_err(Reset)?;
|
||||
Timer::after(Duration::from_millis(10)).await;
|
||||
self.reset.set_high().map_err(Reset)?;
|
||||
Timer::after(Duration::from_millis(10)).await;
|
||||
let version = self.read_register(Register::RegVersion.addr()).await?;
|
||||
if version == VERSION_CHECK {
|
||||
self.set_mode(RadioMode::Sleep).await?;
|
||||
self.write_register(Register::RegFifoTxBaseAddr.addr(), 0).await?;
|
||||
self.write_register(Register::RegFifoRxBaseAddr.addr(), 0).await?;
|
||||
let lna = self.read_register(Register::RegLna.addr()).await?;
|
||||
self.write_register(Register::RegLna.addr(), lna | 0x03).await?;
|
||||
self.write_register(Register::RegModemConfig3.addr(), 0x04).await?;
|
||||
self.set_tcxo(true).await?;
|
||||
self.set_mode(RadioMode::Stdby).await?;
|
||||
self.cs.set_high().map_err(CS)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::VersionMismatch(version))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_dio0_tx_done(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
self.write_register(Register::RegIrqFlagsMask.addr(), 0b1111_0111)
|
||||
.await?;
|
||||
let mapping = self.read_register(Register::RegDioMapping1.addr()).await?;
|
||||
self.write_register(Register::RegDioMapping1.addr(), (mapping & 0x3F) | 0x40)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn set_dio0_rx_done(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
self.write_register(Register::RegIrqFlagsMask.addr(), 0b0001_1111)
|
||||
.await?;
|
||||
let mapping = self.read_register(Register::RegDioMapping1.addr()).await?;
|
||||
self.write_register(Register::RegDioMapping1.addr(), mapping & 0x3F)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn transmit_start(&mut self, buffer: &[u8]) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
assert!(buffer.len() < 255);
|
||||
if self.transmitting().await? {
|
||||
//trace!("ALREADY TRANSMNITTING");
|
||||
Err(Transmitting)
|
||||
} else {
|
||||
self.set_mode(RadioMode::Stdby).await?;
|
||||
if self.explicit_header {
|
||||
self.set_explicit_header_mode().await?;
|
||||
} else {
|
||||
self.set_implicit_header_mode().await?;
|
||||
}
|
||||
|
||||
self.write_register(Register::RegIrqFlags.addr(), 0).await?;
|
||||
self.write_register(Register::RegFifoAddrPtr.addr(), 0).await?;
|
||||
self.write_register(Register::RegPayloadLength.addr(), 0).await?;
|
||||
for byte in buffer.iter() {
|
||||
self.write_register(Register::RegFifo.addr(), *byte).await?;
|
||||
}
|
||||
self.write_register(Register::RegPayloadLength.addr(), buffer.len() as u8)
|
||||
.await?;
|
||||
self.set_mode(RadioMode::Tx).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn packet_ready(&mut self) -> Result<bool, Error<E, CS::Error, RESET::Error>> {
|
||||
Ok(self.read_register(Register::RegIrqFlags.addr()).await?.get_bit(6))
|
||||
}
|
||||
|
||||
pub async fn irq_flags_mask(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
|
||||
Ok(self.read_register(Register::RegIrqFlagsMask.addr()).await? as u8)
|
||||
}
|
||||
|
||||
pub async fn irq_flags(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
|
||||
Ok(self.read_register(Register::RegIrqFlags.addr()).await? as u8)
|
||||
}
|
||||
|
||||
pub async fn read_packet_size(&mut self) -> Result<usize, Error<E, CS::Error, RESET::Error>> {
|
||||
let size = self.read_register(Register::RegRxNbBytes.addr()).await?;
|
||||
Ok(size as usize)
|
||||
}
|
||||
|
||||
/// Returns the contents of the fifo as a fixed 255 u8 array. This should only be called is there is a
|
||||
/// new packet ready to be read.
|
||||
pub async fn read_packet(&mut self, buffer: &mut [u8]) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
self.clear_irq().await?;
|
||||
let size = self.read_register(Register::RegRxNbBytes.addr()).await?;
|
||||
assert!(size as usize <= buffer.len());
|
||||
let fifo_addr = self.read_register(Register::RegFifoRxCurrentAddr.addr()).await?;
|
||||
self.write_register(Register::RegFifoAddrPtr.addr(), fifo_addr).await?;
|
||||
for i in 0..size {
|
||||
let byte = self.read_register(Register::RegFifo.addr()).await?;
|
||||
buffer[i as usize] = byte;
|
||||
}
|
||||
self.write_register(Register::RegFifoAddrPtr.addr(), 0).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns true if the radio is currently transmitting a packet.
|
||||
pub async fn transmitting(&mut self) -> Result<bool, Error<E, CS::Error, RESET::Error>> {
|
||||
if (self.read_register(Register::RegOpMode.addr()).await?) & RadioMode::Tx.addr() == RadioMode::Tx.addr() {
|
||||
Ok(true)
|
||||
} else {
|
||||
if (self.read_register(Register::RegIrqFlags.addr()).await? & IRQ::IrqTxDoneMask.addr()) == 1 {
|
||||
self.write_register(Register::RegIrqFlags.addr(), IRQ::IrqTxDoneMask.addr())
|
||||
.await?;
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the radio's IRQ registers.
|
||||
pub async fn clear_irq(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
|
||||
let irq_flags = self.read_register(Register::RegIrqFlags.addr()).await?;
|
||||
self.write_register(Register::RegIrqFlags.addr(), 0xFF).await?;
|
||||
Ok(irq_flags)
|
||||
}
|
||||
|
||||
/// Sets the transmit power and pin. Levels can range from 0-14 when the output
|
||||
/// pin = 0(RFO), and form 0-20 when output pin = 1(PaBoost). Power is in dB.
|
||||
/// Default value is `17`.
|
||||
pub async fn set_tx_power(
|
||||
&mut self,
|
||||
mut level: i32,
|
||||
output_pin: u8,
|
||||
) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
if PaConfig::PaOutputRfoPin.addr() == output_pin {
|
||||
// RFO
|
||||
if level < 0 {
|
||||
level = 0;
|
||||
} else if level > 14 {
|
||||
level = 14;
|
||||
}
|
||||
self.write_register(Register::RegPaConfig.addr(), (0x70 | level) as u8)
|
||||
.await
|
||||
} else {
|
||||
// PA BOOST
|
||||
if level > 17 {
|
||||
if level > 20 {
|
||||
level = 20;
|
||||
}
|
||||
// subtract 3 from level, so 18 - 20 maps to 15 - 17
|
||||
level -= 3;
|
||||
|
||||
// High Power +20 dBm Operation (Semtech SX1276/77/78/79 5.4.3.)
|
||||
self.write_register(Register::RegPaDac.addr(), 0x87).await?;
|
||||
self.set_ocp(140).await?;
|
||||
} else {
|
||||
if level < 2 {
|
||||
level = 2;
|
||||
}
|
||||
//Default value PA_HF/LF or +17dBm
|
||||
self.write_register(Register::RegPaDac.addr(), 0x84).await?;
|
||||
self.set_ocp(100).await?;
|
||||
}
|
||||
level -= 2;
|
||||
self.write_register(Register::RegPaConfig.addr(), PaConfig::PaBoost.addr() | level as u8)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_modem_stat(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
|
||||
Ok(self.read_register(Register::RegModemStat.addr()).await? as u8)
|
||||
}
|
||||
|
||||
/// Sets the over current protection on the radio(mA).
|
||||
pub async fn set_ocp(&mut self, ma: u8) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
let mut ocp_trim: u8 = 27;
|
||||
|
||||
if ma <= 120 {
|
||||
ocp_trim = (ma - 45) / 5;
|
||||
} else if ma <= 240 {
|
||||
ocp_trim = (ma + 30) / 10;
|
||||
}
|
||||
self.write_register(Register::RegOcp.addr(), 0x20 | (0x1F & ocp_trim))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Sets the state of the radio. Default mode after initiation is `Standby`.
|
||||
pub async fn set_mode(&mut self, mode: RadioMode) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
if self.explicit_header {
|
||||
self.set_explicit_header_mode().await?;
|
||||
} else {
|
||||
self.set_implicit_header_mode().await?;
|
||||
}
|
||||
self.write_register(
|
||||
Register::RegOpMode.addr(),
|
||||
RadioMode::LongRangeMode.addr() | mode.addr(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.mode = mode;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn reset_payload_length(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
self.write_register(Register::RegPayloadLength.addr(), 0xFF).await
|
||||
}
|
||||
|
||||
/// Sets the frequency of the radio. Values are in megahertz.
|
||||
/// I.E. 915 MHz must be used for North America. Check regulation for your area.
|
||||
pub async fn set_frequency(&mut self, freq: u32) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
const FREQ_STEP: f64 = 61.03515625;
|
||||
// calculate register values
|
||||
let frf = (freq as f64 / FREQ_STEP) as u32;
|
||||
// write registers
|
||||
self.write_register(Register::RegFrfMsb.addr(), ((frf & 0x00FF_0000) >> 16) as u8)
|
||||
.await?;
|
||||
self.write_register(Register::RegFrfMid.addr(), ((frf & 0x0000_FF00) >> 8) as u8)
|
||||
.await?;
|
||||
self.write_register(Register::RegFrfLsb.addr(), (frf & 0x0000_00FF) as u8)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Sets the radio to use an explicit header. Default state is `ON`.
|
||||
async fn set_explicit_header_mode(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
let reg_modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?;
|
||||
self.write_register(Register::RegModemConfig1.addr(), reg_modem_config_1 & 0xfe)
|
||||
.await?;
|
||||
self.explicit_header = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the radio to use an implicit header. Default state is `OFF`.
|
||||
async fn set_implicit_header_mode(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
let reg_modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?;
|
||||
self.write_register(Register::RegModemConfig1.addr(), reg_modem_config_1 & 0x01)
|
||||
.await?;
|
||||
self.explicit_header = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the spreading factor of the radio. Supported values are between 6 and 12.
|
||||
/// If a spreading factor of 6 is set, implicit header mode must be used to transmit
|
||||
/// and receive packets. Default value is `7`.
|
||||
pub async fn set_spreading_factor(&mut self, mut sf: u8) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
if sf < 6 {
|
||||
sf = 6;
|
||||
} else if sf > 12 {
|
||||
sf = 12;
|
||||
}
|
||||
|
||||
if sf == 6 {
|
||||
self.write_register(Register::RegDetectionOptimize.addr(), 0xc5).await?;
|
||||
self.write_register(Register::RegDetectionThreshold.addr(), 0x0c)
|
||||
.await?;
|
||||
} else {
|
||||
self.write_register(Register::RegDetectionOptimize.addr(), 0xc3).await?;
|
||||
self.write_register(Register::RegDetectionThreshold.addr(), 0x0a)
|
||||
.await?;
|
||||
}
|
||||
let modem_config_2 = self.read_register(Register::RegModemConfig2.addr()).await?;
|
||||
self.write_register(
|
||||
Register::RegModemConfig2.addr(),
|
||||
(modem_config_2 & 0x0f) | ((sf << 4) & 0xf0),
|
||||
)
|
||||
.await?;
|
||||
self.set_ldo_flag().await?;
|
||||
|
||||
self.write_register(Register::RegSymbTimeoutLsb.addr(), 0x05).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_tcxo(&mut self, external: bool) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
if external {
|
||||
self.write_register(Register::RegTcxo.addr(), 0x10).await
|
||||
} else {
|
||||
self.write_register(Register::RegTcxo.addr(), 0x00).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the signal bandwidth of the radio. Supported values are: `7800 Hz`, `10400 Hz`,
|
||||
/// `15600 Hz`, `20800 Hz`, `31250 Hz`,`41700 Hz` ,`62500 Hz`,`125000 Hz` and `250000 Hz`
|
||||
/// Default value is `125000 Hz`
|
||||
pub async fn set_signal_bandwidth(&mut self, sbw: i64) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
let bw: i64 = match sbw {
|
||||
7_800 => 0,
|
||||
10_400 => 1,
|
||||
15_600 => 2,
|
||||
20_800 => 3,
|
||||
31_250 => 4,
|
||||
41_700 => 5,
|
||||
62_500 => 6,
|
||||
125_000 => 7,
|
||||
250_000 => 8,
|
||||
_ => 9,
|
||||
};
|
||||
let modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?;
|
||||
self.write_register(
|
||||
Register::RegModemConfig1.addr(),
|
||||
(modem_config_1 & 0x0f) | ((bw << 4) as u8),
|
||||
)
|
||||
.await?;
|
||||
self.set_ldo_flag().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the coding rate of the radio with the numerator fixed at 4. Supported values
|
||||
/// are between `5` and `8`, these correspond to coding rates of `4/5` and `4/8`.
|
||||
/// Default value is `5`.
|
||||
pub async fn set_coding_rate_4(&mut self, mut denominator: u8) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
if denominator < 5 {
|
||||
denominator = 5;
|
||||
} else if denominator > 8 {
|
||||
denominator = 8;
|
||||
}
|
||||
let cr = denominator - 4;
|
||||
let modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?;
|
||||
self.write_register(Register::RegModemConfig1.addr(), (modem_config_1 & 0xf1) | (cr << 1))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Sets the preamble length of the radio. Values are between 6 and 65535.
|
||||
/// Default value is `8`.
|
||||
pub async fn set_preamble_length(&mut self, length: i64) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
self.write_register(Register::RegPreambleMsb.addr(), (length >> 8) as u8)
|
||||
.await?;
|
||||
self.write_register(Register::RegPreambleLsb.addr(), length as u8).await
|
||||
}
|
||||
|
||||
/// Enables are disables the radio's CRC check. Default value is `false`.
|
||||
pub async fn set_crc(&mut self, value: bool) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
let modem_config_2 = self.read_register(Register::RegModemConfig2.addr()).await?;
|
||||
if value {
|
||||
self.write_register(Register::RegModemConfig2.addr(), modem_config_2 | 0x04)
|
||||
.await
|
||||
} else {
|
||||
self.write_register(Register::RegModemConfig2.addr(), modem_config_2 & 0xfb)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
/// Inverts the radio's IQ signals. Default value is `false`.
|
||||
pub async fn set_invert_iq(&mut self, value: bool) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
if value {
|
||||
self.write_register(Register::RegInvertiq.addr(), 0x66).await?;
|
||||
self.write_register(Register::RegInvertiq2.addr(), 0x19).await
|
||||
} else {
|
||||
self.write_register(Register::RegInvertiq.addr(), 0x27).await?;
|
||||
self.write_register(Register::RegInvertiq2.addr(), 0x1d).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the spreading factor of the radio.
|
||||
pub async fn get_spreading_factor(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
|
||||
Ok(self.read_register(Register::RegModemConfig2.addr()).await? >> 4)
|
||||
}
|
||||
|
||||
/// Returns the signal bandwidth of the radio.
|
||||
pub async fn get_signal_bandwidth(&mut self) -> Result<i64, Error<E, CS::Error, RESET::Error>> {
|
||||
let bw = self.read_register(Register::RegModemConfig1.addr()).await? >> 4;
|
||||
let bw = match bw {
|
||||
0 => 7_800,
|
||||
1 => 10_400,
|
||||
2 => 15_600,
|
||||
3 => 20_800,
|
||||
4 => 31_250,
|
||||
5 => 41_700,
|
||||
6 => 62_500,
|
||||
7 => 125_000,
|
||||
8 => 250_000,
|
||||
9 => 500_000,
|
||||
_ => -1,
|
||||
};
|
||||
Ok(bw)
|
||||
}
|
||||
|
||||
/// Returns the RSSI of the last received packet.
|
||||
pub async fn get_packet_rssi(&mut self) -> Result<i32, Error<E, CS::Error, RESET::Error>> {
|
||||
Ok(i32::from(self.read_register(Register::RegPktRssiValue.addr()).await?) - 157)
|
||||
}
|
||||
|
||||
/// Returns the signal to noise radio of the the last received packet.
|
||||
pub async fn get_packet_snr(&mut self) -> Result<f64, Error<E, CS::Error, RESET::Error>> {
|
||||
Ok(f64::from(self.read_register(Register::RegPktSnrValue.addr()).await?))
|
||||
}
|
||||
|
||||
/// Returns the frequency error of the last received packet in Hz.
|
||||
pub async fn get_packet_frequency_error(&mut self) -> Result<i64, Error<E, CS::Error, RESET::Error>> {
|
||||
let mut freq_error: i32;
|
||||
freq_error = i32::from(self.read_register(Register::RegFreqErrorMsb.addr()).await? & 0x7);
|
||||
freq_error <<= 8i64;
|
||||
freq_error += i32::from(self.read_register(Register::RegFreqErrorMid.addr()).await?);
|
||||
freq_error <<= 8i64;
|
||||
freq_error += i32::from(self.read_register(Register::RegFreqErrorLsb.addr()).await?);
|
||||
|
||||
let f_xtal = 32_000_000; // FXOSC: crystal oscillator (XTAL) frequency (2.5. Chip Specification, p. 14)
|
||||
let f_error = ((f64::from(freq_error) * (1i64 << 24) as f64) / f64::from(f_xtal))
|
||||
* (self.get_signal_bandwidth().await? as f64 / 500_000.0f64); // p. 37
|
||||
Ok(f_error as i64)
|
||||
}
|
||||
|
||||
async fn set_ldo_flag(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
let sw = self.get_signal_bandwidth().await?;
|
||||
// Section 4.1.1.5
|
||||
let symbol_duration = 1000 / (sw / ((1_i64) << self.get_spreading_factor().await?));
|
||||
|
||||
// Section 4.1.1.6
|
||||
let ldo_on = symbol_duration > 16;
|
||||
|
||||
let mut config_3 = self.read_register(Register::RegModemConfig3.addr()).await?;
|
||||
config_3.set_bit(3, ldo_on);
|
||||
//config_3.set_bit(2, true);
|
||||
self.write_register(Register::RegModemConfig3.addr(), config_3).await
|
||||
}
|
||||
|
||||
async fn read_register(&mut self, reg: u8) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
|
||||
let mut buffer = [reg & 0x7f, 0];
|
||||
self.cs.set_low().map_err(CS)?;
|
||||
|
||||
let _ = self.spi.transfer(&mut buffer, &[reg & 0x7f, 0]).await.map_err(SPI)?;
|
||||
|
||||
self.cs.set_high().map_err(CS)?;
|
||||
Ok(buffer[1])
|
||||
}
|
||||
|
||||
async fn write_register(&mut self, reg: u8, byte: u8) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
self.cs.set_low().map_err(CS)?;
|
||||
let buffer = [reg | 0x80, byte];
|
||||
self.spi.write(&buffer).await.map_err(SPI)?;
|
||||
self.cs.set_high().map_err(CS)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn put_in_fsk_mode(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
// Put in FSK mode
|
||||
let mut op_mode = 0;
|
||||
op_mode
|
||||
.set_bit(7, false) // FSK mode
|
||||
.set_bits(5..6, 0x00) // FSK modulation
|
||||
.set_bit(3, false) //Low freq registers
|
||||
.set_bits(0..2, 0b011); // Mode
|
||||
|
||||
self.write_register(Register::RegOpMode as u8, op_mode).await
|
||||
}
|
||||
|
||||
pub async fn set_fsk_pa_ramp(
|
||||
&mut self,
|
||||
modulation_shaping: FskDataModulationShaping,
|
||||
ramp: FskRampUpRamDown,
|
||||
) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
let mut pa_ramp = 0;
|
||||
pa_ramp
|
||||
.set_bits(5..6, modulation_shaping as u8)
|
||||
.set_bits(0..3, ramp as u8);
|
||||
|
||||
self.write_register(Register::RegPaRamp as u8, pa_ramp).await
|
||||
}
|
||||
|
||||
pub async fn set_lora_pa_ramp(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
self.write_register(Register::RegPaRamp as u8, 0b1000).await
|
||||
}
|
||||
|
||||
pub async fn set_lora_sync_word(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
self.write_register(Register::RegSyncWord as u8, 0x34).await
|
||||
}
|
||||
}
|
||||
/// Modes of the radio and their corresponding register values.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum RadioMode {
|
||||
LongRangeMode = 0x80,
|
||||
Sleep = 0x00,
|
||||
Stdby = 0x01,
|
||||
Tx = 0x03,
|
||||
RxContinuous = 0x05,
|
||||
RxSingle = 0x06,
|
||||
}
|
||||
|
||||
impl RadioMode {
|
||||
/// Returns the address of the mode.
|
||||
pub fn addr(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
// Copyright Charles Wade (https://github.com/mr-glt/sx127x_lora). Licensed under the Apache 2.0
|
||||
// license
|
||||
//
|
||||
// Modifications made to make the driver work with the rust-lorawan link layer.
|
||||
#![allow(dead_code, clippy::enum_variant_names)]
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Register {
|
||||
RegFifo = 0x00,
|
||||
RegOpMode = 0x01,
|
||||
RegFrfMsb = 0x06,
|
||||
RegFrfMid = 0x07,
|
||||
RegFrfLsb = 0x08,
|
||||
RegPaConfig = 0x09,
|
||||
RegPaRamp = 0x0a,
|
||||
RegOcp = 0x0b,
|
||||
RegLna = 0x0c,
|
||||
RegFifoAddrPtr = 0x0d,
|
||||
RegFifoTxBaseAddr = 0x0e,
|
||||
RegFifoRxBaseAddr = 0x0f,
|
||||
RegFifoRxCurrentAddr = 0x10,
|
||||
RegIrqFlagsMask = 0x11,
|
||||
RegIrqFlags = 0x12,
|
||||
RegRxNbBytes = 0x13,
|
||||
RegPktSnrValue = 0x19,
|
||||
RegModemStat = 0x18,
|
||||
RegPktRssiValue = 0x1a,
|
||||
RegModemConfig1 = 0x1d,
|
||||
RegModemConfig2 = 0x1e,
|
||||
RegSymbTimeoutLsb = 0x1f,
|
||||
RegPreambleMsb = 0x20,
|
||||
RegPreambleLsb = 0x21,
|
||||
RegPayloadLength = 0x22,
|
||||
RegMaxPayloadLength = 0x23,
|
||||
RegModemConfig3 = 0x26,
|
||||
RegFreqErrorMsb = 0x28,
|
||||
RegFreqErrorMid = 0x29,
|
||||
RegFreqErrorLsb = 0x2a,
|
||||
RegRssiWideband = 0x2c,
|
||||
RegDetectionOptimize = 0x31,
|
||||
RegInvertiq = 0x33,
|
||||
RegDetectionThreshold = 0x37,
|
||||
RegSyncWord = 0x39,
|
||||
RegInvertiq2 = 0x3b,
|
||||
RegDioMapping1 = 0x40,
|
||||
RegVersion = 0x42,
|
||||
RegTcxo = 0x4b,
|
||||
RegPaDac = 0x4d,
|
||||
}
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum PaConfig {
|
||||
PaBoost = 0x80,
|
||||
PaOutputRfoPin = 0,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum IRQ {
|
||||
IrqTxDoneMask = 0x08,
|
||||
IrqPayloadCrcErrorMask = 0x20,
|
||||
IrqRxDoneMask = 0x40,
|
||||
}
|
||||
|
||||
impl Register {
|
||||
pub fn addr(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl PaConfig {
|
||||
pub fn addr(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl IRQ {
|
||||
pub fn addr(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum FskDataModulationShaping {
|
||||
None = 1,
|
||||
GaussianBt1d0 = 2,
|
||||
GaussianBt0d5 = 10,
|
||||
GaussianBt0d3 = 11,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum FskRampUpRamDown {
|
||||
_3d4ms = 0b000,
|
||||
_2ms = 0b0001,
|
||||
_1ms = 0b0010,
|
||||
_500us = 0b0011,
|
||||
_250us = 0b0100,
|
||||
_125us = 0b0101,
|
||||
_100us = 0b0110,
|
||||
_62us = 0b0111,
|
||||
_50us = 0b1000,
|
||||
_40us = 0b1001,
|
||||
_31us = 0b1010,
|
||||
_25us = 0b1011,
|
||||
_20us = 0b1100,
|
||||
_15us = 0b1101,
|
||||
_12us = 0b1110,
|
||||
_10us = 0b1111,
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "embassy-macros"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "macros for creating the entry point and tasks for embassy-executor"
|
||||
|
@ -27,12 +27,10 @@ use embassy_sync::waitqueue::WakerRegistration;
|
||||
use embassy_time::{Instant, Timer};
|
||||
use futures::pin_mut;
|
||||
use heapless::Vec;
|
||||
use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage};
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
use smoltcp::iface::SocketHandle;
|
||||
use smoltcp::iface::{Interface, SocketSet, SocketStorage};
|
||||
use smoltcp::socket::dhcpv4::{self, RetryConfig};
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
use smoltcp::socket::dhcpv4;
|
||||
use smoltcp::socket::dhcpv4::RetryConfig;
|
||||
use smoltcp::time::Duration;
|
||||
// smoltcp reexports
|
||||
pub use smoltcp::time::{Duration as SmolDuration, Instant as SmolInstant};
|
||||
@ -76,6 +74,7 @@ pub struct StaticConfig {
|
||||
pub dns_servers: Vec<Ipv4Address, 3>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DhcpConfig {
|
||||
pub max_lease_duration: Option<Duration>,
|
||||
@ -88,6 +87,7 @@ pub struct DhcpConfig {
|
||||
pub client_port: u16,
|
||||
}
|
||||
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
impl Default for DhcpConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@ -384,6 +384,7 @@ impl<D: Driver + 'static> Inner<D> {
|
||||
self.config = Some(config)
|
||||
}
|
||||
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
fn apply_dhcp_config(&self, socket: &mut smoltcp::socket::dhcpv4::Socket, config: DhcpConfig) {
|
||||
socket.set_ignore_naks(config.ignore_naks);
|
||||
socket.set_max_lease_duration(config.max_lease_duration);
|
||||
|
@ -78,7 +78,7 @@ _dppi = []
|
||||
_gpio-p1 = []
|
||||
|
||||
[dependencies]
|
||||
embassy-executor = { version = "0.1.0", path = "../embassy-executor", optional = true }
|
||||
embassy-executor = { version = "0.2.0", path = "../embassy-executor", optional = true }
|
||||
embassy-time = { version = "0.1.0", path = "../embassy-time", optional = true }
|
||||
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
|
||||
embassy-cortex-m = { version = "0.1.0", path = "../embassy-cortex-m", features = ["prio-bits-3"]}
|
||||
|
@ -184,6 +184,34 @@ pub mod config {
|
||||
NotConfigured,
|
||||
}
|
||||
|
||||
/// Settings for enabling the built in DCDC converters.
|
||||
#[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))]
|
||||
pub struct DcdcConfig {
|
||||
/// Config for the first stage DCDC (VDDH -> VDD), if disabled LDO will be used.
|
||||
#[cfg(feature = "nrf52840")]
|
||||
pub reg0: bool,
|
||||
/// Config for the second stage DCDC (VDD -> DEC4), if disabled LDO will be used.
|
||||
pub reg1: bool,
|
||||
}
|
||||
|
||||
/// Settings for enabling the built in DCDC converters.
|
||||
#[cfg(feature = "_nrf5340-app")]
|
||||
pub struct DcdcConfig {
|
||||
/// Config for the high voltage stage, if disabled LDO will be used.
|
||||
pub regh: bool,
|
||||
/// Config for the main rail, if disabled LDO will be used.
|
||||
pub regmain: bool,
|
||||
/// Config for the radio rail, if disabled LDO will be used.
|
||||
pub regradio: bool,
|
||||
}
|
||||
|
||||
/// Settings for enabling the built in DCDC converter.
|
||||
#[cfg(feature = "_nrf9160")]
|
||||
pub struct DcdcConfig {
|
||||
/// Config for the main rail, if disabled LDO will be used.
|
||||
pub regmain: bool,
|
||||
}
|
||||
|
||||
/// Configuration for peripherals. Default configuration should work on any nRF chip.
|
||||
#[non_exhaustive]
|
||||
pub struct Config {
|
||||
@ -191,6 +219,9 @@ pub mod config {
|
||||
pub hfclk_source: HfclkSource,
|
||||
/// Low frequency clock source.
|
||||
pub lfclk_source: LfclkSource,
|
||||
#[cfg(not(feature = "_nrf5340-net"))]
|
||||
/// DCDC configuration.
|
||||
pub dcdc: DcdcConfig,
|
||||
/// GPIOTE interrupt priority. Should be lower priority than softdevice if used.
|
||||
#[cfg(feature = "gpiote")]
|
||||
pub gpiote_interrupt_priority: crate::interrupt::Priority,
|
||||
@ -209,6 +240,20 @@ pub mod config {
|
||||
// xtals if they know they have them.
|
||||
hfclk_source: HfclkSource::Internal,
|
||||
lfclk_source: LfclkSource::InternalRC,
|
||||
#[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))]
|
||||
dcdc: DcdcConfig {
|
||||
#[cfg(feature = "nrf52840")]
|
||||
reg0: false,
|
||||
reg1: false,
|
||||
},
|
||||
#[cfg(feature = "_nrf5340-app")]
|
||||
dcdc: DcdcConfig {
|
||||
regh: false,
|
||||
regmain: false,
|
||||
regradio: false,
|
||||
},
|
||||
#[cfg(feature = "_nrf9160")]
|
||||
dcdc: DcdcConfig { regmain: false },
|
||||
#[cfg(feature = "gpiote")]
|
||||
gpiote_interrupt_priority: crate::interrupt::Priority::P0,
|
||||
#[cfg(feature = "_time-driver")]
|
||||
@ -454,6 +499,41 @@ pub fn init(config: config::Config) -> Peripherals {
|
||||
r.tasks_lfclkstart.write(|w| unsafe { w.bits(1) });
|
||||
while r.events_lfclkstarted.read().bits() == 0 {}
|
||||
|
||||
#[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))]
|
||||
{
|
||||
// Setup DCDCs.
|
||||
let pwr = unsafe { &*pac::POWER::ptr() };
|
||||
#[cfg(feature = "nrf52840")]
|
||||
if config.dcdc.reg0 {
|
||||
pwr.dcdcen0.write(|w| w.dcdcen().set_bit());
|
||||
}
|
||||
if config.dcdc.reg1 {
|
||||
pwr.dcdcen.write(|w| w.dcdcen().set_bit());
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "_nrf9160")]
|
||||
{
|
||||
// Setup DCDC.
|
||||
let reg = unsafe { &*pac::REGULATORS::ptr() };
|
||||
if config.dcdc.regmain {
|
||||
reg.dcdcen.write(|w| w.dcdcen().set_bit());
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "_nrf5340-app")]
|
||||
{
|
||||
// Setup DCDC.
|
||||
let reg = unsafe { &*pac::REGULATORS::ptr() };
|
||||
if config.dcdc.regh {
|
||||
reg.vregh.dcdcen.write(|w| w.dcdcen().set_bit());
|
||||
}
|
||||
if config.dcdc.regmain {
|
||||
reg.vregmain.dcdcen.write(|w| w.dcdcen().set_bit());
|
||||
}
|
||||
if config.dcdc.regradio {
|
||||
reg.vregradio.dcdcen.write(|w| w.dcdcen().set_bit());
|
||||
}
|
||||
}
|
||||
|
||||
// Init GPIOTE
|
||||
#[cfg(feature = "gpiote")]
|
||||
gpiote::init(config.gpiote_interrupt_priority);
|
||||
|
@ -992,80 +992,3 @@ mod eh1 {
|
||||
type Error = Error;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
feature = "unstable-traits",
|
||||
feature = "nightly",
|
||||
feature = "_todo_embedded_hal_serial"
|
||||
))]
|
||||
mod eha {
|
||||
use core::future::Future;
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_async::serial::Read for Uarte<'d, T> {
|
||||
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn read<'a>(&'a mut self, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> {
|
||||
self.read(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_async::serial::Write for Uarte<'d, T> {
|
||||
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn write<'a>(&'a mut self, buffer: &'a [u8]) -> Self::WriteFuture<'a> {
|
||||
self.write(buffer)
|
||||
}
|
||||
|
||||
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
async move { Ok(()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_async::serial::Write for UarteTx<'d, T> {
|
||||
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn write<'a>(&'a mut self, buffer: &'a [u8]) -> Self::WriteFuture<'a> {
|
||||
self.write(buffer)
|
||||
}
|
||||
|
||||
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
async move { Ok(()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_async::serial::Read for UarteRx<'d, T> {
|
||||
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn read<'a>(&'a mut self, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> {
|
||||
self.read(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, U: Instance, T: TimerInstance> embedded_hal_async::serial::Read for UarteWithIdle<'d, U, T> {
|
||||
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn read<'a>(&'a mut self, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> {
|
||||
self.read(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, U: Instance, T: TimerInstance> embedded_hal_async::serial::Write for UarteWithIdle<'d, U, T> {
|
||||
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn write<'a>(&'a mut self, buffer: &'a [u8]) -> Self::WriteFuture<'a> {
|
||||
self.write(buffer)
|
||||
}
|
||||
|
||||
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
async move { Ok(()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ time-driver = []
|
||||
rom-func-cache = []
|
||||
intrinsics = []
|
||||
rom-v2-intrinsics = []
|
||||
pio = ["dep:pio", "dep:pio-proc"]
|
||||
|
||||
# Enable nightly-only features
|
||||
nightly = ["embassy-executor/nightly", "embedded-hal-1", "embedded-hal-async", "embassy-embedded-hal/nightly", "dep:embassy-usb-driver", "dep:embedded-io"]
|
||||
@ -40,7 +39,7 @@ unstable-traits = ["embedded-hal-1", "embedded-hal-nb"]
|
||||
|
||||
[dependencies]
|
||||
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
|
||||
embassy-executor = { version = "0.1.0", path = "../embassy-executor" }
|
||||
embassy-executor = { version = "0.2.0", path = "../embassy-executor" }
|
||||
embassy-time = { version = "0.1.0", path = "../embassy-time", features = [ "tick-hz-1_000_000" ] }
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
|
||||
embassy-cortex-m = { version = "0.1.0", path = "../embassy-cortex-m", features = ["prio-bits-2"]}
|
||||
@ -60,9 +59,9 @@ chrono = { version = "0.4", default-features = false, optional = true }
|
||||
embedded-io = { version = "0.4.0", features = ["async"], optional = true }
|
||||
embedded-storage = { version = "0.3" }
|
||||
rand_core = "0.6.4"
|
||||
fixed = "1.23.1"
|
||||
|
||||
rp2040-pac2 = { git = "https://github.com/embassy-rs/rp2040-pac2", rev="017e3c9007b2d3b6965f0d85b5bf8ce3fa6d7364", features = ["rt"] }
|
||||
#rp2040-pac2 = { path = "../../rp2040-pac2", features = ["rt"] }
|
||||
rp-pac = { version = "2", features = ["rt"] }
|
||||
|
||||
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
|
||||
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10", optional = true}
|
||||
@ -70,5 +69,5 @@ embedded-hal-async = { version = "=0.2.0-alpha.1", optional = true}
|
||||
embedded-hal-nb = { version = "=1.0.0-alpha.2", optional = true}
|
||||
|
||||
paste = "1.0"
|
||||
pio-proc = {version= "0.2", optional = true}
|
||||
pio = {version= "0.2.1", optional = true}
|
||||
pio-proc = {version= "0.2" }
|
||||
pio = {version= "0.2.1" }
|
||||
|
@ -155,8 +155,8 @@ unsafe fn configure_pll(p: pac::pll::Pll, refdiv: u32, vco_freq: u32, post_div1:
|
||||
let cs = p.cs().read();
|
||||
let prim = p.prim().read();
|
||||
if cs.lock()
|
||||
&& cs.refdiv() == refdiv as _
|
||||
&& p.fbdiv_int().read().fbdiv_int() == fbdiv as _
|
||||
&& cs.refdiv() == refdiv as u8
|
||||
&& p.fbdiv_int().read().fbdiv_int() == fbdiv as u16
|
||||
&& prim.postdiv1() == post_div1
|
||||
&& prim.postdiv2() == post_div2
|
||||
{
|
||||
|
@ -162,8 +162,6 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, FLASH_SIZE> {
|
||||
/// - interrupts must be disabled
|
||||
/// - DMA must not access flash memory
|
||||
unsafe fn in_ram(&mut self, operation: impl FnOnce()) -> Result<(), Error> {
|
||||
let dma_status = &mut [false; crate::dma::CHANNEL_COUNT];
|
||||
|
||||
// Make sure we're running on CORE0
|
||||
let core_id: u32 = unsafe { pac::SIO.cpuid().read() };
|
||||
if core_id != 0 {
|
||||
@ -174,25 +172,15 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, FLASH_SIZE> {
|
||||
crate::multicore::pause_core1();
|
||||
|
||||
critical_section::with(|_| {
|
||||
// Pause all DMA channels for the duration of the ram operation
|
||||
for (number, status) in dma_status.iter_mut().enumerate() {
|
||||
let ch = crate::pac::DMA.ch(number as _);
|
||||
*status = ch.ctrl_trig().read().en();
|
||||
if *status {
|
||||
ch.ctrl_trig().modify(|w| w.set_en(false));
|
||||
}
|
||||
// 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() {}
|
||||
}
|
||||
|
||||
// Run our flash operation in RAM
|
||||
operation();
|
||||
|
||||
// Re-enable previously enabled DMA channels
|
||||
for (number, status) in dma_status.iter().enumerate() {
|
||||
let ch = crate::pac::DMA.ch(number as _);
|
||||
if *status {
|
||||
ch.ctrl_trig().modify(|w| w.set_en(true));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Resume CORE1 execution
|
||||
|
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)
|
||||
}
|
||||
}
|
141
embassy-rp/src/float/div.rs
Normal file
141
embassy-rp/src/float/div.rs
Normal file
@ -0,0 +1,141 @@
|
||||
// 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;
|
||||
|
||||
unsafe {
|
||||
// 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 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 unsafe { !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)
|
||||
}
|
||||
}
|
||||
}
|
149
embassy-rp/src/float/mod.rs
Normal file
149
embassy-rp/src/float/mod.rs
Normal file
@ -0,0 +1,149 @@
|
||||
// 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
|
||||
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)
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
use crate::pac::common::{Reg, RW};
|
||||
use crate::pac::SIO;
|
||||
use crate::{interrupt, pac, peripherals, Peripheral};
|
||||
use crate::{interrupt, pac, peripherals, Peripheral, RegExt};
|
||||
|
||||
const PIN_COUNT: usize = 30;
|
||||
const NEW_AW: AtomicWaker = AtomicWaker::new();
|
||||
@ -136,16 +136,11 @@ pub enum InterruptTrigger {
|
||||
AnyEdge,
|
||||
}
|
||||
|
||||
impl InterruptTrigger {
|
||||
fn from_u32(value: u32) -> Option<InterruptTrigger> {
|
||||
match value {
|
||||
1 => Some(InterruptTrigger::LevelLow),
|
||||
2 => Some(InterruptTrigger::LevelHigh),
|
||||
3 => Some(InterruptTrigger::EdgeLow),
|
||||
4 => Some(InterruptTrigger::EdgeHigh),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub(crate) unsafe fn init() {
|
||||
let irq = interrupt::IO_IRQ_BANK0::steal();
|
||||
irq.disable();
|
||||
irq.set_priority(interrupt::Priority::P3);
|
||||
irq.enable();
|
||||
}
|
||||
|
||||
#[interrupt]
|
||||
@ -166,27 +161,15 @@ unsafe fn IO_IRQ_BANK0() {
|
||||
let pin_group = (pin % 8) as usize;
|
||||
let event = (intsx.read().0 >> pin_group * 4) & 0xf as u32;
|
||||
|
||||
if let Some(trigger) = InterruptTrigger::from_u32(event) {
|
||||
critical_section::with(|_| {
|
||||
proc_intx.inte(pin / 8).modify(|w| match trigger {
|
||||
InterruptTrigger::AnyEdge => {
|
||||
w.set_edge_high(pin_group, false);
|
||||
w.set_edge_low(pin_group, false);
|
||||
}
|
||||
InterruptTrigger::LevelHigh => {
|
||||
trace!("IO_IRQ_BANK0 pin {} LevelHigh triggered", pin);
|
||||
w.set_level_high(pin_group, false);
|
||||
}
|
||||
InterruptTrigger::LevelLow => {
|
||||
w.set_level_low(pin_group, false);
|
||||
}
|
||||
InterruptTrigger::EdgeHigh => {
|
||||
w.set_edge_high(pin_group, false);
|
||||
}
|
||||
InterruptTrigger::EdgeLow => {
|
||||
w.set_edge_low(pin_group, false);
|
||||
}
|
||||
});
|
||||
// no more than one event can be awaited per pin at any given time, so
|
||||
// we can just clear all interrupt enables for that pin without having
|
||||
// to check which event was signalled.
|
||||
if event != 0 {
|
||||
proc_intx.inte(pin / 8).write_clear(|w| {
|
||||
w.set_edge_high(pin_group, true);
|
||||
w.set_edge_low(pin_group, true);
|
||||
w.set_level_high(pin_group, true);
|
||||
w.set_level_low(pin_group, true);
|
||||
});
|
||||
INTERRUPT_WAKERS[pin as usize].wake();
|
||||
}
|
||||
@ -203,16 +186,26 @@ impl<'d, T: Pin> InputFuture<'d, T> {
|
||||
pub fn new(pin: impl Peripheral<P = T> + 'd, level: InterruptTrigger) -> Self {
|
||||
into_ref!(pin);
|
||||
unsafe {
|
||||
let irq = interrupt::IO_IRQ_BANK0::steal();
|
||||
irq.disable();
|
||||
irq.set_priority(interrupt::Priority::P3);
|
||||
let pin_group = (pin.pin() % 8) as usize;
|
||||
// first, clear the INTR register bits. without this INTR will still
|
||||
// contain reports of previous edges, causing the IRQ to fire early
|
||||
// on stale state. clearing these means that we can only detect edges
|
||||
// that occur *after* the clear happened, but since both this and the
|
||||
// alternative are fundamentally racy it's probably fine.
|
||||
// (the alternative being checking the current level and waiting for
|
||||
// its inverse, but that requires reading the current level and thus
|
||||
// missing anything that happened before the level was read.)
|
||||
pac::IO_BANK0.intr(pin.pin() as usize / 8).write(|w| {
|
||||
w.set_edge_high(pin_group, true);
|
||||
w.set_edge_low(pin_group, true);
|
||||
});
|
||||
|
||||
// Each INTR register is divided into 8 groups, one group for each
|
||||
// pin, and each group consists of LEVEL_LOW, LEVEL_HIGH, EDGE_LOW,
|
||||
// and EGDE_HIGH.
|
||||
let pin_group = (pin.pin() % 8) as usize;
|
||||
critical_section::with(|_| {
|
||||
pin.int_proc().inte((pin.pin() / 8) as usize).modify(|w| match level {
|
||||
pin.int_proc()
|
||||
.inte((pin.pin() / 8) as usize)
|
||||
.write_set(|w| match level {
|
||||
InterruptTrigger::LevelHigh => {
|
||||
trace!("InputFuture::new enable LevelHigh for pin {}", pin.pin());
|
||||
w.set_level_high(pin_group, true);
|
||||
@ -227,12 +220,10 @@ impl<'d, T: Pin> InputFuture<'d, T> {
|
||||
w.set_edge_low(pin_group, true);
|
||||
}
|
||||
InterruptTrigger::AnyEdge => {
|
||||
// noop
|
||||
w.set_edge_high(pin_group, true);
|
||||
w.set_edge_low(pin_group, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
irq.enable();
|
||||
}
|
||||
|
||||
Self { pin, level }
|
||||
@ -257,47 +248,21 @@ impl<'d, T: Pin> Future for InputFuture<'d, T> {
|
||||
// LEVEL_HIGH, EDGE_LOW, and EDGE_HIGH for each pin.
|
||||
let pin_group = (self.pin.pin() % 8) as usize;
|
||||
|
||||
// This should check the the level of the interrupt trigger level of
|
||||
// the pin and if it has been disabled that means it was done by the
|
||||
// interrupt service routine, so we then know that the event/trigger
|
||||
// happened and Poll::Ready will be returned.
|
||||
trace!("{:?} for pin {}", self.level, self.pin.pin());
|
||||
match self.level {
|
||||
InterruptTrigger::AnyEdge => {
|
||||
if !inte.edge_high(pin_group) && !inte.edge_low(pin_group) {
|
||||
#[rustfmt::skip]
|
||||
trace!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
|
||||
return Poll::Ready(());
|
||||
}
|
||||
}
|
||||
InterruptTrigger::LevelHigh => {
|
||||
if !inte.level_high(pin_group) {
|
||||
#[rustfmt::skip]
|
||||
trace!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
|
||||
return Poll::Ready(());
|
||||
}
|
||||
}
|
||||
InterruptTrigger::LevelLow => {
|
||||
if !inte.level_low(pin_group) {
|
||||
#[rustfmt::skip]
|
||||
trace!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
|
||||
return Poll::Ready(());
|
||||
}
|
||||
}
|
||||
InterruptTrigger::EdgeHigh => {
|
||||
if !inte.edge_high(pin_group) {
|
||||
#[rustfmt::skip]
|
||||
trace!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
|
||||
return Poll::Ready(());
|
||||
}
|
||||
}
|
||||
InterruptTrigger::EdgeLow => {
|
||||
if !inte.edge_low(pin_group) {
|
||||
#[rustfmt::skip]
|
||||
trace!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
|
||||
return Poll::Ready(());
|
||||
}
|
||||
}
|
||||
// since the interrupt handler clears all INTE flags we'll check that
|
||||
// all have been cleared and unconditionally return Ready(()) if so.
|
||||
// we don't need further handshaking since only a single event wait
|
||||
// is possible for any given pin at any given time.
|
||||
if !inte.edge_high(pin_group)
|
||||
&& !inte.edge_low(pin_group)
|
||||
&& !inte.level_high(pin_group)
|
||||
&& !inte.level_low(pin_group)
|
||||
{
|
||||
trace!(
|
||||
"{:?} for pin {} was cleared, return Poll::Ready",
|
||||
self.level,
|
||||
self.pin.pin()
|
||||
);
|
||||
return Poll::Ready(());
|
||||
}
|
||||
trace!("InputFuture::poll return Poll::Pending");
|
||||
Poll::Pending
|
||||
@ -644,23 +609,17 @@ impl<'d, T: Pin> Flex<'d, T> {
|
||||
|
||||
#[inline]
|
||||
pub async fn wait_for_rising_edge(&mut self) {
|
||||
self.wait_for_low().await;
|
||||
self.wait_for_high().await;
|
||||
InputFuture::new(&mut self.pin, InterruptTrigger::EdgeHigh).await;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn wait_for_falling_edge(&mut self) {
|
||||
self.wait_for_high().await;
|
||||
self.wait_for_low().await;
|
||||
InputFuture::new(&mut self.pin, InterruptTrigger::EdgeLow).await;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn wait_for_any_edge(&mut self) {
|
||||
if self.is_high() {
|
||||
self.wait_for_low().await;
|
||||
} else {
|
||||
self.wait_for_high().await;
|
||||
}
|
||||
InputFuture::new(&mut self.pin, InterruptTrigger::AnyEdge).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -617,6 +617,28 @@ mod eh02 {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-traits")]
|
||||
|
@ -274,3 +274,203 @@ macro_rules! intrinsics {
|
||||
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 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
|
||||
}
|
||||
}
|
||||
|
@ -11,18 +11,16 @@ mod critical_section_impl;
|
||||
mod intrinsics;
|
||||
|
||||
pub mod adc;
|
||||
pub mod clocks;
|
||||
pub mod dma;
|
||||
pub mod flash;
|
||||
mod float;
|
||||
pub mod gpio;
|
||||
pub mod i2c;
|
||||
pub mod interrupt;
|
||||
|
||||
#[cfg(feature = "pio")]
|
||||
pub mod pio;
|
||||
#[cfg(feature = "pio")]
|
||||
pub mod pio_instr_util;
|
||||
#[cfg(feature = "pio")]
|
||||
pub mod relocate;
|
||||
|
||||
pub mod multicore;
|
||||
pub mod pwm;
|
||||
mod reset;
|
||||
pub mod rom_data;
|
||||
pub mod rtc;
|
||||
pub mod spi;
|
||||
@ -31,22 +29,22 @@ pub mod timer;
|
||||
pub mod uart;
|
||||
#[cfg(feature = "nightly")]
|
||||
pub mod usb;
|
||||
|
||||
pub mod clocks;
|
||||
pub mod flash;
|
||||
pub mod multicore;
|
||||
mod reset;
|
||||
pub mod watchdog;
|
||||
|
||||
// Reexports
|
||||
// PIO
|
||||
// TODO: move `pio_instr_util` and `relocate` to inside `pio`
|
||||
pub mod pio;
|
||||
pub mod pio_instr_util;
|
||||
pub mod relocate;
|
||||
|
||||
// Reexports
|
||||
pub use embassy_cortex_m::executor;
|
||||
pub use embassy_cortex_m::interrupt::_export::interrupt;
|
||||
pub use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
|
||||
#[cfg(feature = "unstable-pac")]
|
||||
pub use rp2040_pac2 as pac;
|
||||
pub use rp_pac as pac;
|
||||
#[cfg(not(feature = "unstable-pac"))]
|
||||
pub(crate) use rp2040_pac2 as pac;
|
||||
pub(crate) use rp_pac as pac;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
PIN_0,
|
||||
@ -108,6 +106,15 @@ embassy_hal_common::peripherals! {
|
||||
DMA_CH10,
|
||||
DMA_CH11,
|
||||
|
||||
PWM_CH0,
|
||||
PWM_CH1,
|
||||
PWM_CH2,
|
||||
PWM_CH3,
|
||||
PWM_CH4,
|
||||
PWM_CH5,
|
||||
PWM_CH6,
|
||||
PWM_CH7,
|
||||
|
||||
USB,
|
||||
|
||||
RTC,
|
||||
@ -149,6 +156,8 @@ pub fn init(_config: config::Config) -> Peripherals {
|
||||
#[cfg(feature = "time-driver")]
|
||||
timer::init();
|
||||
dma::init();
|
||||
pio::init();
|
||||
gpio::init();
|
||||
}
|
||||
|
||||
peripherals
|
||||
|
@ -33,7 +33,7 @@ use core::sync::atomic::{compiler_fence, AtomicBool, Ordering};
|
||||
|
||||
use crate::interrupt::{Interrupt, InterruptExt};
|
||||
use crate::peripherals::CORE1;
|
||||
use crate::{interrupt, pac};
|
||||
use crate::{gpio, interrupt, pac};
|
||||
|
||||
const PAUSE_TOKEN: u32 = 0xDEADBEEF;
|
||||
const RESUME_TOKEN: u32 = !0xDEADBEEF;
|
||||
@ -68,6 +68,9 @@ fn install_stack_guard(stack_bottom: *mut usize) {
|
||||
#[inline(always)]
|
||||
fn core1_setup(stack_bottom: *mut usize) {
|
||||
install_stack_guard(stack_bottom);
|
||||
unsafe {
|
||||
gpio::init();
|
||||
}
|
||||
}
|
||||
|
||||
/// Data type for a properly aligned stack of N bytes
|
||||
|
File diff suppressed because it is too large
Load Diff
338
embassy-rp/src/pwm.rs
Normal file
338
embassy-rp/src/pwm.rs
Normal file
@ -0,0 +1,338 @@
|
||||
//! Pulse Width Modulation (PWM)
|
||||
|
||||
use embassy_embedded_hal::SetConfig;
|
||||
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
|
||||
use fixed::traits::ToFixed;
|
||||
use fixed::FixedU16;
|
||||
use pac::pwm::regs::{ChDiv, Intr};
|
||||
use pac::pwm::vals::Divmode;
|
||||
|
||||
use crate::gpio::sealed::Pin as _;
|
||||
use crate::gpio::{AnyPin, Pin as GpioPin};
|
||||
use crate::{pac, peripherals, RegExt};
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone)]
|
||||
pub struct Config {
|
||||
pub invert_a: bool,
|
||||
pub invert_b: bool,
|
||||
pub phase_correct: bool,
|
||||
pub enable: bool,
|
||||
pub divider: fixed::FixedU16<fixed::types::extra::U4>,
|
||||
pub compare_a: u16,
|
||||
pub compare_b: u16,
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum InputMode {
|
||||
Level,
|
||||
RisingEdge,
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Pwm<'d, T: Channel> {
|
||||
inner: PeripheralRef<'d, T>,
|
||||
pin_a: Option<PeripheralRef<'d, AnyPin>>,
|
||||
pin_b: Option<PeripheralRef<'d, AnyPin>>,
|
||||
}
|
||||
|
||||
impl<'d, T: Channel> Pwm<'d, T> {
|
||||
fn new_inner(
|
||||
inner: impl Peripheral<P = T> + 'd,
|
||||
a: Option<PeripheralRef<'d, AnyPin>>,
|
||||
b: Option<PeripheralRef<'d, AnyPin>>,
|
||||
config: Config,
|
||||
divmode: Divmode,
|
||||
) -> Self {
|
||||
into_ref!(inner);
|
||||
|
||||
let p = inner.regs();
|
||||
unsafe {
|
||||
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.io().ctrl().write(|w| w.set_funcsel(4));
|
||||
}
|
||||
if let Some(pin) = &b {
|
||||
pin.io().ctrl().write(|w| w.set_funcsel(4));
|
||||
}
|
||||
}
|
||||
Self {
|
||||
inner,
|
||||
pin_a: a.into(),
|
||||
pin_b: b.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_free(inner: impl Peripheral<P = T> + 'd, config: Config) -> Self {
|
||||
Self::new_inner(inner, None, None, config, Divmode::DIV)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_output_a(
|
||||
inner: impl Peripheral<P = T> + 'd,
|
||||
a: impl Peripheral<P = impl PwmPinA<T>> + 'd,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(a);
|
||||
Self::new_inner(inner, Some(a.map_into()), None, config, Divmode::DIV)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_output_b(
|
||||
inner: impl Peripheral<P = T> + 'd,
|
||||
b: impl Peripheral<P = impl PwmPinB<T>> + 'd,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(b);
|
||||
Self::new_inner(inner, None, Some(b.map_into()), config, Divmode::DIV)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_output_ab(
|
||||
inner: impl Peripheral<P = T> + 'd,
|
||||
a: impl Peripheral<P = impl PwmPinA<T>> + 'd,
|
||||
b: impl Peripheral<P = impl PwmPinB<T>> + 'd,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(a, b);
|
||||
Self::new_inner(inner, Some(a.map_into()), Some(b.map_into()), config, Divmode::DIV)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_input(
|
||||
inner: impl Peripheral<P = T> + 'd,
|
||||
b: impl Peripheral<P = impl PwmPinB<T>> + 'd,
|
||||
mode: InputMode,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(b);
|
||||
Self::new_inner(inner, None, Some(b.map_into()), config, mode.into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_output_input(
|
||||
inner: impl Peripheral<P = T> + 'd,
|
||||
a: impl Peripheral<P = impl PwmPinA<T>> + 'd,
|
||||
b: impl Peripheral<P = impl PwmPinB<T>> + 'd,
|
||||
mode: InputMode,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(a, b);
|
||||
Self::new_inner(inner, Some(a.map_into()), Some(b.map_into()), config, mode.into())
|
||||
}
|
||||
|
||||
fn configure(p: pac::pwm::Channel, config: &Config) {
|
||||
if config.divider > FixedU16::<fixed::types::extra::U4>::from_bits(0xFF_F) {
|
||||
panic!("Requested divider is too large");
|
||||
}
|
||||
|
||||
unsafe {
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn phase_advance(&mut self) {
|
||||
let p = self.inner.regs();
|
||||
p.csr().write_set(|w| w.set_ph_adv(true));
|
||||
while p.csr().read().ph_adv() {}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn phase_retard(&mut self) {
|
||||
let p = self.inner.regs();
|
||||
p.csr().write_set(|w| w.set_ph_ret(true));
|
||||
while p.csr().read().ph_ret() {}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn counter(&self) -> u16 {
|
||||
unsafe { self.inner.regs().ctr().read().ctr() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_counter(&self, ctr: u16) {
|
||||
unsafe { self.inner.regs().ctr().write(|w| w.set_ctr(ctr)) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn wait_for_wrap(&mut self) {
|
||||
while !self.wrapped() {}
|
||||
self.clear_wrapped();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn wrapped(&mut self) -> bool {
|
||||
unsafe { pac::PWM.intr().read().0 & self.bit() != 0 }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clear_wrapped(&mut self) {
|
||||
unsafe {
|
||||
pac::PWM.intr().write_value(Intr(self.bit() as _));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bit(&self) -> u32 {
|
||||
1 << self.inner.number() as usize
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PwmBatch(u32);
|
||||
|
||||
impl PwmBatch {
|
||||
#[inline]
|
||||
pub fn enable(&mut self, pwm: &Pwm<'_, impl Channel>) {
|
||||
self.0 |= pwm.bit();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_enabled(enabled: bool, batch: impl FnOnce(&mut PwmBatch)) {
|
||||
let mut en = PwmBatch(0);
|
||||
batch(&mut en);
|
||||
unsafe {
|
||||
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, T: Channel> Drop for Pwm<'d, T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.inner.regs().csr().write_clear(|w| w.set_en(false));
|
||||
if let Some(pin) = &self.pin_a {
|
||||
pin.io().ctrl().write(|w| w.set_funcsel(31));
|
||||
}
|
||||
if let Some(pin) = &self.pin_b {
|
||||
pin.io().ctrl().write(|w| w.set_funcsel(31));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod sealed {
|
||||
pub trait Channel {}
|
||||
}
|
||||
|
||||
pub trait Channel: Peripheral<P = Self> + sealed::Channel + Sized + 'static {
|
||||
fn number(&self) -> u8;
|
||||
|
||||
fn regs(&self) -> pac::pwm::Channel {
|
||||
pac::PWM.ch(self.number() as _)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! channel {
|
||||
($name:ident, $num:expr) => {
|
||||
impl sealed::Channel for peripherals::$name {}
|
||||
impl Channel for peripherals::$name {
|
||||
fn number(&self) -> u8 {
|
||||
$num
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
channel!(PWM_CH0, 0);
|
||||
channel!(PWM_CH1, 1);
|
||||
channel!(PWM_CH2, 2);
|
||||
channel!(PWM_CH3, 3);
|
||||
channel!(PWM_CH4, 4);
|
||||
channel!(PWM_CH5, 5);
|
||||
channel!(PWM_CH6, 6);
|
||||
channel!(PWM_CH7, 7);
|
||||
|
||||
pub trait PwmPinA<T: Channel>: GpioPin {}
|
||||
pub trait PwmPinB<T: Channel>: GpioPin {}
|
||||
|
||||
macro_rules! impl_pin {
|
||||
($pin:ident, $channel:ident, $kind:ident) => {
|
||||
impl $kind<peripherals::$channel> for peripherals::$pin {}
|
||||
};
|
||||
}
|
||||
|
||||
impl_pin!(PIN_0, PWM_CH0, PwmPinA);
|
||||
impl_pin!(PIN_1, PWM_CH0, PwmPinB);
|
||||
impl_pin!(PIN_2, PWM_CH1, PwmPinA);
|
||||
impl_pin!(PIN_3, PWM_CH1, PwmPinB);
|
||||
impl_pin!(PIN_4, PWM_CH2, PwmPinA);
|
||||
impl_pin!(PIN_5, PWM_CH2, PwmPinB);
|
||||
impl_pin!(PIN_6, PWM_CH3, PwmPinA);
|
||||
impl_pin!(PIN_7, PWM_CH3, PwmPinB);
|
||||
impl_pin!(PIN_8, PWM_CH4, PwmPinA);
|
||||
impl_pin!(PIN_9, PWM_CH4, PwmPinB);
|
||||
impl_pin!(PIN_10, PWM_CH5, PwmPinA);
|
||||
impl_pin!(PIN_11, PWM_CH5, PwmPinB);
|
||||
impl_pin!(PIN_12, PWM_CH6, PwmPinA);
|
||||
impl_pin!(PIN_13, PWM_CH6, PwmPinB);
|
||||
impl_pin!(PIN_14, PWM_CH7, PwmPinA);
|
||||
impl_pin!(PIN_15, PWM_CH7, PwmPinB);
|
||||
impl_pin!(PIN_16, PWM_CH0, PwmPinA);
|
||||
impl_pin!(PIN_17, PWM_CH0, PwmPinB);
|
||||
impl_pin!(PIN_18, PWM_CH1, PwmPinA);
|
||||
impl_pin!(PIN_19, PWM_CH1, PwmPinB);
|
||||
impl_pin!(PIN_20, PWM_CH2, PwmPinA);
|
||||
impl_pin!(PIN_21, PWM_CH2, PwmPinB);
|
||||
impl_pin!(PIN_22, PWM_CH3, PwmPinA);
|
||||
impl_pin!(PIN_23, PWM_CH3, PwmPinB);
|
||||
impl_pin!(PIN_24, PWM_CH4, PwmPinA);
|
||||
impl_pin!(PIN_25, PWM_CH4, PwmPinB);
|
||||
impl_pin!(PIN_26, PWM_CH5, PwmPinA);
|
||||
impl_pin!(PIN_27, PWM_CH5, PwmPinB);
|
||||
impl_pin!(PIN_28, PWM_CH6, PwmPinA);
|
||||
impl_pin!(PIN_29, PWM_CH6, PwmPinB);
|
||||
|
||||
impl<'d, T: Channel> SetConfig for Pwm<'d, T> {
|
||||
type Config = Config;
|
||||
fn set_config(&mut self, config: &Self::Config) {
|
||||
Self::configure(self.inner.regs(), config);
|
||||
}
|
||||
}
|
@ -56,50 +56,11 @@ macro_rules! declare_rom_function {
|
||||
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: *const u32 = $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: *const u32 = match CACHED_PTR.load(Ordering::Relaxed) {
|
||||
0 => {
|
||||
let raw: *const u32 = $lookup;
|
||||
CACHED_PTR.store(raw as u16, Ordering::Relaxed);
|
||||
raw
|
||||
},
|
||||
val => val as *const u32,
|
||||
};
|
||||
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),*)
|
||||
declare_rom_function!{
|
||||
__internal ,
|
||||
$(#[$outer])*
|
||||
fn $name( $($argname: $ty),* ) -> $ret
|
||||
$lookup
|
||||
}
|
||||
};
|
||||
|
||||
@ -107,6 +68,21 @@ macro_rules! declare_rom_function {
|
||||
$(#[$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)]
|
||||
@ -114,43 +90,58 @@ macro_rules! declare_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 {
|
||||
pub fn ptr() -> $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret {
|
||||
let p: *const u32 = $lookup;
|
||||
unsafe {
|
||||
let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p);
|
||||
let func : $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret
|
||||
= core::mem::transmute(p);
|
||||
func
|
||||
}
|
||||
}
|
||||
|
||||
#[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),*)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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};
|
||||
pub fn ptr() -> $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret {
|
||||
use core::sync::atomic::{compiler_fence, 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: *const u32 = match CACHED_PTR.load(Ordering::Relaxed) {
|
||||
0 => {
|
||||
let raw: *const u32 = $lookup;
|
||||
CACHED_PTR.store(raw as u16, Ordering::Relaxed);
|
||||
raw
|
||||
},
|
||||
val => val as *const u32,
|
||||
};
|
||||
//
|
||||
// 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 {
|
||||
let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p);
|
||||
func
|
||||
CACHE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(#[$outer])*
|
||||
pub unsafe extern "C" fn $name( $($argname: $ty),* ) -> $ret {
|
||||
pub $( $maybe_unsafe )? extern "C" fn $name( $($argname: $ty),* ) -> $ret {
|
||||
$name::ptr()($($argname),*)
|
||||
}
|
||||
};
|
||||
@ -369,6 +360,7 @@ pub fn fplib_start() -> *const u8 {
|
||||
}
|
||||
|
||||
/// 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")
|
||||
}
|
||||
@ -379,6 +371,7 @@ pub fn fplib_end() -> *const u8 {
|
||||
}
|
||||
|
||||
/// 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!(
|
||||
|
@ -2,11 +2,14 @@ use core::future::{poll_fn, Future};
|
||||
use core::slice;
|
||||
use core::task::Poll;
|
||||
|
||||
use atomic_polyfill::{AtomicU8, Ordering};
|
||||
use embassy_cortex_m::interrupt::{Interrupt, InterruptExt};
|
||||
use embassy_hal_common::atomic_ring_buffer::RingBuffer;
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use embassy_time::{Duration, Timer};
|
||||
|
||||
use super::*;
|
||||
use crate::clocks::clk_peri_freq;
|
||||
use crate::RegExt;
|
||||
|
||||
pub struct State {
|
||||
@ -14,8 +17,15 @@ pub struct State {
|
||||
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 {
|
||||
@ -23,6 +33,7 @@ impl State {
|
||||
tx_buf: RingBuffer::new(),
|
||||
rx_waker: AtomicWaker::new(),
|
||||
tx_waker: AtomicWaker::new(),
|
||||
rx_error: AtomicU8::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -45,7 +56,7 @@ pub(crate) fn init_buffers<'d, T: Instance + 'd>(
|
||||
tx_buffer: &'d mut [u8],
|
||||
rx_buffer: &'d mut [u8],
|
||||
) {
|
||||
let state = T::state();
|
||||
let state = T::buffered_state();
|
||||
let len = tx_buffer.len();
|
||||
unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), len) };
|
||||
let len = rx_buffer.len();
|
||||
@ -63,7 +74,7 @@ pub(crate) fn init_buffers<'d, T: Instance + 'd>(
|
||||
// to pend the ISR when we want data transmission to start.
|
||||
let regs = T::regs();
|
||||
unsafe {
|
||||
regs.uartimsc().write_set(|w| {
|
||||
regs.uartimsc().write(|w| {
|
||||
w.set_rxim(true);
|
||||
w.set_rtim(true);
|
||||
w.set_txim(true);
|
||||
@ -136,6 +147,14 @@ impl<'d, T: Instance> BufferedUart<'d, T> {
|
||||
self.rx.blocking_read(buffer)
|
||||
}
|
||||
|
||||
pub fn busy(&self) -> bool {
|
||||
self.tx.busy()
|
||||
}
|
||||
|
||||
pub async fn send_break(&mut self, bits: u32) {
|
||||
self.tx.send_break(bits).await
|
||||
}
|
||||
|
||||
pub fn split(self) -> (BufferedUartRx<'d, T>, BufferedUartTx<'d, T>) {
|
||||
(self.rx, self.tx)
|
||||
}
|
||||
@ -173,90 +192,113 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> {
|
||||
Self { phantom: PhantomData }
|
||||
}
|
||||
|
||||
fn read<'a>(buf: &'a mut [u8]) -> impl Future<Output = Result<usize, Error>> + 'a {
|
||||
fn read<'a>(buf: &'a mut [u8]) -> impl Future<Output = Result<usize, Error>> + 'a
|
||||
where
|
||||
T: 'd,
|
||||
{
|
||||
poll_fn(move |cx| {
|
||||
if buf.is_empty() {
|
||||
return Poll::Ready(Ok(0));
|
||||
if let Poll::Ready(r) = Self::try_read(buf) {
|
||||
return Poll::Ready(r);
|
||||
}
|
||||
|
||||
let state = T::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
|
||||
});
|
||||
if n == 0 {
|
||||
state.rx_waker.register(cx.waker());
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
// (Re-)Enable the interrupt to receive more data in case it was
|
||||
// disabled because the buffer was full.
|
||||
let regs = T::regs();
|
||||
unsafe {
|
||||
regs.uartimsc().write_set(|w| {
|
||||
w.set_rxim(true);
|
||||
w.set_rtim(true);
|
||||
});
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(n))
|
||||
T::buffered_state().rx_waker.register(cx.waker());
|
||||
Poll::Pending
|
||||
})
|
||||
}
|
||||
|
||||
pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
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 Ok(0);
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
|
||||
loop {
|
||||
let state = T::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 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();
|
||||
unsafe {
|
||||
regs.uartimsc().write_set(|w| {
|
||||
w.set_rxim(true);
|
||||
w.set_rtim(true);
|
||||
});
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
// (Re-)Enable the interrupt to receive more data in case it was
|
||||
// disabled because the buffer was full.
|
||||
let regs = T::regs();
|
||||
unsafe {
|
||||
regs.uartimsc().write_set(|w| {
|
||||
w.set_rxim(true);
|
||||
w.set_rtim(true);
|
||||
});
|
||||
}
|
||||
Poll::Ready(result)
|
||||
}
|
||||
|
||||
return Ok(n);
|
||||
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>> {
|
||||
fn fill_buf<'a>() -> impl Future<Output = Result<&'a [u8], Error>>
|
||||
where
|
||||
T: 'd,
|
||||
{
|
||||
poll_fn(move |cx| {
|
||||
let state = T::state();
|
||||
let state = T::buffered_state();
|
||||
let mut rx_reader = unsafe { state.rx_buf.reader() };
|
||||
let (p, n) = rx_reader.pop_buf();
|
||||
if n == 0 {
|
||||
state.rx_waker.register(cx.waker());
|
||||
return Poll::Pending;
|
||||
}
|
||||
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)
|
||||
};
|
||||
|
||||
let buf = unsafe { slice::from_raw_parts(p, n) };
|
||||
Poll::Ready(Ok(buf))
|
||||
Poll::Ready(result)
|
||||
})
|
||||
}
|
||||
|
||||
fn consume(amt: usize) {
|
||||
let state = T::state();
|
||||
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.
|
||||
// disabled because the buffer was full or errors were detected.
|
||||
let regs = T::regs();
|
||||
unsafe {
|
||||
regs.uartimsc().write_set(|w| {
|
||||
@ -305,7 +347,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> {
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
|
||||
let state = T::state();
|
||||
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());
|
||||
@ -328,7 +370,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> {
|
||||
|
||||
fn flush() -> impl Future<Output = Result<(), Error>> {
|
||||
poll_fn(move |cx| {
|
||||
let state = T::state();
|
||||
let state = T::buffered_state();
|
||||
if !state.tx_buf.is_empty() {
|
||||
state.tx_waker.register(cx.waker());
|
||||
return Poll::Pending;
|
||||
@ -344,7 +386,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> {
|
||||
}
|
||||
|
||||
loop {
|
||||
let state = T::state();
|
||||
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());
|
||||
@ -365,17 +407,54 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> {
|
||||
|
||||
pub fn blocking_flush(&mut self) -> Result<(), Error> {
|
||||
loop {
|
||||
let state = T::state();
|
||||
let state = T::buffered_state();
|
||||
if state.tx_buf.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn busy(&self) -> bool {
|
||||
unsafe { 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(unsafe {
|
||||
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 = unsafe {
|
||||
((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() {}
|
||||
unsafe {
|
||||
regs.uartlcr_h().write_set(|w| w.set_brk(true));
|
||||
}
|
||||
Timer::after(Duration::from_micros(wait_usecs)).await;
|
||||
unsafe {
|
||||
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::state();
|
||||
let state = T::buffered_state();
|
||||
unsafe {
|
||||
state.rx_buf.deinit();
|
||||
|
||||
@ -390,7 +469,7 @@ impl<'d, T: Instance> Drop for BufferedUartRx<'d, T> {
|
||||
|
||||
impl<'d, T: Instance> Drop for BufferedUartTx<'d, T> {
|
||||
fn drop(&mut self) {
|
||||
let state = T::state();
|
||||
let state = T::buffered_state();
|
||||
unsafe {
|
||||
state.tx_buf.deinit();
|
||||
|
||||
@ -405,7 +484,7 @@ impl<'d, T: Instance> Drop for BufferedUartTx<'d, T> {
|
||||
|
||||
pub(crate) unsafe fn on_interrupt<T: Instance>(_: *mut ()) {
|
||||
let r = T::regs();
|
||||
let s = T::state();
|
||||
let s = T::buffered_state();
|
||||
|
||||
unsafe {
|
||||
// Clear TX and error interrupt flags
|
||||
@ -439,19 +518,37 @@ pub(crate) unsafe fn on_interrupt<T: Instance>(_: *mut ()) {
|
||||
let mut rx_writer = 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;
|
||||
}
|
||||
*rx_byte = r.uartdr().read().data();
|
||||
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.
|
||||
if s.rx_buf.is_full() {
|
||||
// Disable any further RX interrupts when the buffer becomes full or
|
||||
// errors have occured. 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);
|
||||
@ -726,58 +823,3 @@ mod eh1 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
feature = "unstable-traits",
|
||||
feature = "nightly",
|
||||
feature = "_todo_embedded_hal_serial"
|
||||
))]
|
||||
mod eha {
|
||||
use core::future::Future;
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_async::serial::Write for BufferedUartTx<'d, T> {
|
||||
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
|
||||
Self::write(buf)
|
||||
}
|
||||
|
||||
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> {
|
||||
Self::flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_async::serial::Read for BufferedUartRx<'d, T> {
|
||||
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
|
||||
Self::read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_async::serial::Write for BufferedUart<'d, T> {
|
||||
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
|
||||
BufferedUartTx::<'d, T>::write(buf)
|
||||
}
|
||||
|
||||
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> {
|
||||
BufferedUartTx::<'d, T>::flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_async::serial::Read for BufferedUart<'d, T> {
|
||||
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
|
||||
BufferedUartRx::<'d, T>::read(buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,21 @@
|
||||
use core::future::poll_fn;
|
||||
use core::marker::PhantomData;
|
||||
use core::task::Poll;
|
||||
|
||||
use atomic_polyfill::{AtomicU16, Ordering};
|
||||
use embassy_cortex_m::interrupt::{Interrupt, InterruptExt};
|
||||
use embassy_futures::select::{select, Either};
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use pac::uart::regs::Uartris;
|
||||
|
||||
use crate::clocks::clk_peri_freq;
|
||||
use crate::dma::{AnyChannel, Channel};
|
||||
use crate::gpio::sealed::Pin;
|
||||
use crate::gpio::AnyPin;
|
||||
use crate::{pac, peripherals, Peripheral};
|
||||
use crate::pac::io::vals::{Inover, Outover};
|
||||
use crate::{pac, peripherals, Peripheral, RegExt};
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
mod buffered;
|
||||
@ -53,6 +63,14 @@ pub struct Config {
|
||||
pub data_bits: DataBits,
|
||||
pub stop_bits: StopBits,
|
||||
pub parity: Parity,
|
||||
/// Invert the tx pin output
|
||||
pub invert_tx: bool,
|
||||
/// Invert the rx pin input
|
||||
pub invert_rx: bool,
|
||||
// Invert the rts pin
|
||||
pub invert_rts: bool,
|
||||
// Invert the cts pin
|
||||
pub invert_cts: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@ -62,6 +80,10 @@ impl Default for Config {
|
||||
data_bits: DataBits::DataBits8,
|
||||
stop_bits: StopBits::STOP1,
|
||||
parity: Parity::ParityNone,
|
||||
invert_rx: false,
|
||||
invert_tx: false,
|
||||
invert_rts: false,
|
||||
invert_cts: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -82,6 +104,11 @@ pub enum Error {
|
||||
Framing,
|
||||
}
|
||||
|
||||
pub struct DmaState {
|
||||
rx_err_waker: AtomicWaker,
|
||||
rx_errs: AtomicU16,
|
||||
}
|
||||
|
||||
pub struct Uart<'d, T: Instance, M: Mode> {
|
||||
tx: UartTx<'d, T, M>,
|
||||
rx: UartRx<'d, T, M>,
|
||||
@ -133,6 +160,43 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> {
|
||||
unsafe { while !r.uartfr().read().txfe() {} }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn busy(&self) -> bool {
|
||||
unsafe { 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(unsafe {
|
||||
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 = unsafe {
|
||||
((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.blocking_flush().unwrap();
|
||||
while self.busy() {}
|
||||
unsafe {
|
||||
regs.uartlcr_h().write_set(|w| w.set_brk(true));
|
||||
}
|
||||
Timer::after(Duration::from_micros(wait_usecs)).await;
|
||||
unsafe {
|
||||
regs.uartlcr_h().write_clear(|w| w.set_brk(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> UartTx<'d, T, Blocking> {
|
||||
@ -154,7 +218,7 @@ impl<'d, T: Instance> UartTx<'d, T, Async> {
|
||||
pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> {
|
||||
let ch = self.tx_dma.as_mut().unwrap();
|
||||
let transfer = unsafe {
|
||||
T::regs().uartdmacr().modify(|reg| {
|
||||
T::regs().uartdmacr().write_set(|reg| {
|
||||
reg.set_txdmae(true);
|
||||
});
|
||||
// If we don't assign future to a variable, the data register pointer
|
||||
@ -167,55 +231,90 @@ impl<'d, T: Instance> UartTx<'d, T, Async> {
|
||||
}
|
||||
|
||||
impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> {
|
||||
/// Create a new DMA-enabled UART which can only send data
|
||||
/// Create a new DMA-enabled UART which can only recieve data
|
||||
pub fn new(
|
||||
_uart: impl Peripheral<P = T> + 'd,
|
||||
rx: impl Peripheral<P = impl RxPin<T>> + 'd,
|
||||
irq: impl Peripheral<P = T::Interrupt> + 'd,
|
||||
rx_dma: impl Peripheral<P = impl Channel> + 'd,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(rx, rx_dma);
|
||||
Uart::<T, M>::init(Some(rx.map_into()), None, None, None, config);
|
||||
Self::new_inner(Some(rx_dma.map_into()))
|
||||
into_ref!(rx, irq, rx_dma);
|
||||
Uart::<T, M>::init(None, Some(rx.map_into()), None, None, config);
|
||||
Self::new_inner(Some(irq), Some(rx_dma.map_into()))
|
||||
}
|
||||
|
||||
fn new_inner(rx_dma: Option<PeripheralRef<'d, AnyChannel>>) -> Self {
|
||||
fn new_inner(irq: Option<PeripheralRef<'d, T::Interrupt>>, rx_dma: Option<PeripheralRef<'d, AnyChannel>>) -> Self {
|
||||
debug_assert_eq!(irq.is_some(), rx_dma.is_some());
|
||||
if let Some(irq) = irq {
|
||||
unsafe {
|
||||
// disable all error interrupts initially
|
||||
T::regs().uartimsc().write(|w| w.0 = 0);
|
||||
}
|
||||
irq.set_handler(on_interrupt::<T>);
|
||||
irq.unpend();
|
||||
irq.enable();
|
||||
}
|
||||
Self {
|
||||
rx_dma,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
|
||||
let r = T::regs();
|
||||
unsafe {
|
||||
for b in buffer {
|
||||
*b = loop {
|
||||
if r.uartfr().read().rxfe() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dr = r.uartdr().read();
|
||||
|
||||
if dr.oe() {
|
||||
return Err(Error::Overrun);
|
||||
} else if dr.be() {
|
||||
return Err(Error::Break);
|
||||
} else if dr.pe() {
|
||||
return Err(Error::Parity);
|
||||
} else if dr.fe() {
|
||||
return Err(Error::Framing);
|
||||
} else {
|
||||
break dr.data();
|
||||
}
|
||||
};
|
||||
}
|
||||
pub fn blocking_read(&mut self, mut buffer: &mut [u8]) -> Result<(), Error> {
|
||||
while buffer.len() > 0 {
|
||||
let received = self.drain_fifo(buffer)?;
|
||||
buffer = &mut buffer[received..];
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn drain_fifo(&mut self, buffer: &mut [u8]) -> Result<usize, Error> {
|
||||
let r = T::regs();
|
||||
for (i, b) in buffer.iter_mut().enumerate() {
|
||||
if unsafe { r.uartfr().read().rxfe() } {
|
||||
return Ok(i);
|
||||
}
|
||||
|
||||
let dr = unsafe { r.uartdr().read() };
|
||||
|
||||
if dr.oe() {
|
||||
return Err(Error::Overrun);
|
||||
} else if dr.be() {
|
||||
return Err(Error::Break);
|
||||
} else if dr.pe() {
|
||||
return Err(Error::Parity);
|
||||
} else if dr.fe() {
|
||||
return Err(Error::Framing);
|
||||
} else {
|
||||
*b = dr.data();
|
||||
}
|
||||
}
|
||||
Ok(buffer.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance, M: Mode> Drop for UartRx<'d, T, M> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(_) = self.rx_dma {
|
||||
unsafe {
|
||||
T::Interrupt::steal().disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> UartRx<'d, T, Blocking> {
|
||||
pub fn new_blocking(
|
||||
_uart: impl Peripheral<P = T> + 'd,
|
||||
rx: impl Peripheral<P = impl RxPin<T>> + 'd,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(rx);
|
||||
Uart::<T, Blocking>::init(None, Some(rx.map_into()), None, None, config);
|
||||
Self::new_inner(None, None)
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
pub fn into_buffered(
|
||||
self,
|
||||
@ -230,19 +329,93 @@ impl<'d, T: Instance> UartRx<'d, T, Blocking> {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn on_interrupt<T: Instance>(_: *mut ()) {
|
||||
let uart = T::regs();
|
||||
let state = T::dma_state();
|
||||
let errs = uart.uartris().read();
|
||||
state.rx_errs.store(errs.0 as u16, Ordering::Relaxed);
|
||||
state.rx_err_waker.wake();
|
||||
// disable the error interrupts instead of clearing the flags. clearing the
|
||||
// flags would allow the dma transfer to continue, potentially signaling
|
||||
// completion before we can check for errors that happened *during* the transfer.
|
||||
uart.uartimsc().write_clear(|w| w.0 = errs.0);
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> UartRx<'d, T, Async> {
|
||||
pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
|
||||
// clear error flags before we drain the fifo. errors that have accumulated
|
||||
// in the flags will also be present in the fifo.
|
||||
T::dma_state().rx_errs.store(0, Ordering::Relaxed);
|
||||
unsafe {
|
||||
T::regs().uarticr().write(|w| {
|
||||
w.set_oeic(true);
|
||||
w.set_beic(true);
|
||||
w.set_peic(true);
|
||||
w.set_feic(true);
|
||||
});
|
||||
}
|
||||
|
||||
// then drain the fifo. we need to read at most 32 bytes. errors that apply
|
||||
// to fifo bytes will be reported directly.
|
||||
let buffer = match {
|
||||
let limit = buffer.len().min(32);
|
||||
self.drain_fifo(&mut buffer[0..limit])
|
||||
} {
|
||||
Ok(len) if len < buffer.len() => &mut buffer[len..],
|
||||
Ok(_) => return Ok(()),
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
// start a dma transfer. if errors have happened in the interim some error
|
||||
// interrupt flags will have been raised, and those will be picked up immediately
|
||||
// by the interrupt handler.
|
||||
let ch = self.rx_dma.as_mut().unwrap();
|
||||
let transfer = unsafe {
|
||||
T::regs().uartdmacr().modify(|reg| {
|
||||
T::regs().uartimsc().write_set(|w| {
|
||||
w.set_oeim(true);
|
||||
w.set_beim(true);
|
||||
w.set_peim(true);
|
||||
w.set_feim(true);
|
||||
});
|
||||
T::regs().uartdmacr().write_set(|reg| {
|
||||
reg.set_rxdmae(true);
|
||||
reg.set_dmaonerr(true);
|
||||
});
|
||||
// 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(ch, T::regs().uartdr().ptr() as *const _, buffer, T::RX_DREQ)
|
||||
};
|
||||
transfer.await;
|
||||
Ok(())
|
||||
|
||||
// wait for either the transfer to complete or an error to happen.
|
||||
let transfer_result = select(
|
||||
transfer,
|
||||
poll_fn(|cx| {
|
||||
T::dma_state().rx_err_waker.register(cx.waker());
|
||||
match T::dma_state().rx_errs.swap(0, Ordering::Relaxed) {
|
||||
0 => Poll::Pending,
|
||||
e => Poll::Ready(Uartris(e as u32)),
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let errors = match transfer_result {
|
||||
Either::First(()) => return Ok(()),
|
||||
Either::Second(e) => e,
|
||||
};
|
||||
|
||||
if errors.0 == 0 {
|
||||
return Ok(());
|
||||
} else if errors.oeris() {
|
||||
return Err(Error::Overrun);
|
||||
} else if errors.beris() {
|
||||
return Err(Error::Break);
|
||||
} else if errors.peris() {
|
||||
return Err(Error::Parity);
|
||||
} else if errors.feris() {
|
||||
return Err(Error::Framing);
|
||||
}
|
||||
unreachable!("unrecognized rx error");
|
||||
}
|
||||
}
|
||||
|
||||
@ -255,7 +428,7 @@ impl<'d, T: Instance> Uart<'d, T, Blocking> {
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(tx, rx);
|
||||
Self::new_inner(uart, tx.map_into(), rx.map_into(), None, None, None, None, config)
|
||||
Self::new_inner(uart, tx.map_into(), rx.map_into(), None, None, None, None, None, config)
|
||||
}
|
||||
|
||||
/// Create a new UART with hardware flow control (RTS/CTS)
|
||||
@ -276,6 +449,7 @@ impl<'d, T: Instance> Uart<'d, T, Blocking> {
|
||||
Some(cts.map_into()),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
config,
|
||||
)
|
||||
}
|
||||
@ -304,17 +478,19 @@ impl<'d, T: Instance> Uart<'d, T, Async> {
|
||||
uart: impl Peripheral<P = T> + 'd,
|
||||
tx: impl Peripheral<P = impl TxPin<T>> + 'd,
|
||||
rx: impl Peripheral<P = impl RxPin<T>> + 'd,
|
||||
irq: impl Peripheral<P = T::Interrupt> + 'd,
|
||||
tx_dma: impl Peripheral<P = impl Channel> + 'd,
|
||||
rx_dma: impl Peripheral<P = impl Channel> + 'd,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(tx, rx, tx_dma, rx_dma);
|
||||
into_ref!(tx, rx, irq, tx_dma, rx_dma);
|
||||
Self::new_inner(
|
||||
uart,
|
||||
tx.map_into(),
|
||||
rx.map_into(),
|
||||
None,
|
||||
None,
|
||||
Some(irq),
|
||||
Some(tx_dma.map_into()),
|
||||
Some(rx_dma.map_into()),
|
||||
config,
|
||||
@ -328,17 +504,19 @@ impl<'d, T: Instance> Uart<'d, T, Async> {
|
||||
rx: impl Peripheral<P = impl RxPin<T>> + 'd,
|
||||
rts: impl Peripheral<P = impl RtsPin<T>> + 'd,
|
||||
cts: impl Peripheral<P = impl CtsPin<T>> + 'd,
|
||||
irq: impl Peripheral<P = T::Interrupt> + 'd,
|
||||
tx_dma: impl Peripheral<P = impl Channel> + 'd,
|
||||
rx_dma: impl Peripheral<P = impl Channel> + 'd,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(tx, rx, cts, rts, tx_dma, rx_dma);
|
||||
into_ref!(tx, rx, cts, rts, irq, tx_dma, rx_dma);
|
||||
Self::new_inner(
|
||||
uart,
|
||||
tx.map_into(),
|
||||
rx.map_into(),
|
||||
Some(rts.map_into()),
|
||||
Some(cts.map_into()),
|
||||
Some(irq),
|
||||
Some(tx_dma.map_into()),
|
||||
Some(rx_dma.map_into()),
|
||||
config,
|
||||
@ -353,6 +531,7 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> {
|
||||
mut rx: PeripheralRef<'d, AnyPin>,
|
||||
mut rts: Option<PeripheralRef<'d, AnyPin>>,
|
||||
mut cts: Option<PeripheralRef<'d, AnyPin>>,
|
||||
irq: Option<PeripheralRef<'d, T::Interrupt>>,
|
||||
tx_dma: Option<PeripheralRef<'d, AnyChannel>>,
|
||||
rx_dma: Option<PeripheralRef<'d, AnyChannel>>,
|
||||
config: Config,
|
||||
@ -367,7 +546,7 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> {
|
||||
|
||||
Self {
|
||||
tx: UartTx::new_inner(tx_dma),
|
||||
rx: UartRx::new_inner(rx_dma),
|
||||
rx: UartRx::new_inner(irq, rx_dma),
|
||||
}
|
||||
}
|
||||
|
||||
@ -381,19 +560,47 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> {
|
||||
let r = T::regs();
|
||||
unsafe {
|
||||
if let Some(pin) = &tx {
|
||||
pin.io().ctrl().write(|w| w.set_funcsel(2));
|
||||
pin.io().ctrl().write(|w| {
|
||||
w.set_funcsel(2);
|
||||
w.set_outover(if config.invert_tx {
|
||||
Outover::INVERT
|
||||
} else {
|
||||
Outover::NORMAL
|
||||
});
|
||||
});
|
||||
pin.pad_ctrl().write(|w| w.set_ie(true));
|
||||
}
|
||||
if let Some(pin) = &rx {
|
||||
pin.io().ctrl().write(|w| w.set_funcsel(2));
|
||||
pin.io().ctrl().write(|w| {
|
||||
w.set_funcsel(2);
|
||||
w.set_inover(if config.invert_rx {
|
||||
Inover::INVERT
|
||||
} else {
|
||||
Inover::NORMAL
|
||||
});
|
||||
});
|
||||
pin.pad_ctrl().write(|w| w.set_ie(true));
|
||||
}
|
||||
if let Some(pin) = &cts {
|
||||
pin.io().ctrl().write(|w| w.set_funcsel(2));
|
||||
pin.io().ctrl().write(|w| {
|
||||
w.set_funcsel(2);
|
||||
w.set_inover(if config.invert_cts {
|
||||
Inover::INVERT
|
||||
} else {
|
||||
Inover::NORMAL
|
||||
});
|
||||
});
|
||||
pin.pad_ctrl().write(|w| w.set_ie(true));
|
||||
}
|
||||
if let Some(pin) = &rts {
|
||||
pin.io().ctrl().write(|w| w.set_funcsel(2));
|
||||
pin.io().ctrl().write(|w| {
|
||||
w.set_funcsel(2);
|
||||
w.set_outover(if config.invert_rts {
|
||||
Outover::INVERT
|
||||
} else {
|
||||
Outover::NORMAL
|
||||
});
|
||||
});
|
||||
pin.pad_ctrl().write(|w| w.set_ie(true));
|
||||
}
|
||||
|
||||
@ -475,6 +682,14 @@ impl<'d, T: Instance, M: Mode> Uart<'d, T, M> {
|
||||
self.rx.blocking_read(buffer)
|
||||
}
|
||||
|
||||
pub fn busy(&self) -> bool {
|
||||
self.tx.busy()
|
||||
}
|
||||
|
||||
pub async fn send_break(&mut self, bits: u32) {
|
||||
self.tx.send_break(bits).await
|
||||
}
|
||||
|
||||
/// Split the Uart into a transmitter and receiver, which is particuarly
|
||||
/// useful when having two tasks correlating to transmitting and receiving.
|
||||
pub fn split(self) -> (UartTx<'d, T, M>, UartRx<'d, T, M>) {
|
||||
@ -651,61 +866,6 @@ mod eh1 {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
feature = "unstable-traits",
|
||||
feature = "nightly",
|
||||
feature = "_todo_embedded_hal_serial"
|
||||
))]
|
||||
mod eha {
|
||||
use core::future::Future;
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<'d, T: Instance, M: Mode> embedded_hal_async::serial::Write for UartTx<'d, T, M> {
|
||||
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
|
||||
self.write(buf)
|
||||
}
|
||||
|
||||
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> {
|
||||
async move { Ok(()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance, M: Mode> embedded_hal_async::serial::Read for UartRx<'d, T, M> {
|
||||
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
|
||||
self.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance, M: Mode> embedded_hal_async::serial::Write for Uart<'d, T, M> {
|
||||
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
|
||||
self.write(buf)
|
||||
}
|
||||
|
||||
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> {
|
||||
async move { Ok(()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance, M: Mode> embedded_hal_async::serial::Read for Uart<'d, T, M> {
|
||||
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
|
||||
|
||||
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
|
||||
self.read(buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod sealed {
|
||||
use super::*;
|
||||
|
||||
@ -720,7 +880,9 @@ mod sealed {
|
||||
fn regs() -> pac::uart::Uart;
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
fn state() -> &'static buffered::State;
|
||||
fn buffered_state() -> &'static buffered::State;
|
||||
|
||||
fn dma_state() -> &'static DmaState;
|
||||
}
|
||||
pub trait TxPin<T: Instance> {}
|
||||
pub trait RxPin<T: Instance> {}
|
||||
@ -758,10 +920,18 @@ macro_rules! impl_instance {
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
fn state() -> &'static buffered::State {
|
||||
fn buffered_state() -> &'static buffered::State {
|
||||
static STATE: buffered::State = buffered::State::new();
|
||||
&STATE
|
||||
}
|
||||
|
||||
fn dma_state() -> &'static DmaState {
|
||||
static STATE: DmaState = DmaState {
|
||||
rx_err_waker: AtomicWaker::new(),
|
||||
rx_errs: AtomicU16::new(0),
|
||||
};
|
||||
&STATE
|
||||
}
|
||||
}
|
||||
impl Instance for peripherals::$inst {}
|
||||
};
|
||||
|
@ -231,7 +231,7 @@ impl<'d, T: Instance> Driver<'d, T> {
|
||||
let len = (max_packet_size + 63) / 64 * 64;
|
||||
|
||||
let addr = self.ep_mem_free;
|
||||
if addr + len > EP_MEMORY_SIZE as _ {
|
||||
if addr + len > EP_MEMORY_SIZE as u16 {
|
||||
warn!("Endpoint memory full");
|
||||
return Err(EndpointAllocError);
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ flavors = [
|
||||
|
||||
[dependencies]
|
||||
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
|
||||
embassy-executor = { version = "0.1.0", path = "../embassy-executor" }
|
||||
embassy-executor = { version = "0.2.0", path = "../embassy-executor" }
|
||||
embassy-time = { version = "0.1.0", path = "../embassy-time", optional = true }
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
|
||||
embassy-cortex-m = { version = "0.1.0", path = "../embassy-cortex-m", features = ["prio-bits-4"]}
|
||||
@ -55,10 +55,10 @@ cortex-m = "0.7.6"
|
||||
futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
|
||||
rand_core = "0.6.3"
|
||||
sdio-host = "0.5.0"
|
||||
embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "46d1b1c2ff13e31e282ec1e352421721694f126a", optional = true }
|
||||
embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "a4f293d3a6f72158385f79c98634cb8a14d0d2fc", optional = true }
|
||||
critical-section = "1.1"
|
||||
atomic-polyfill = "1.0.1"
|
||||
stm32-metapac = "6"
|
||||
stm32-metapac = "7"
|
||||
vcell = "0.1.3"
|
||||
bxcan = "0.7.0"
|
||||
nb = "1.0.0"
|
||||
@ -66,6 +66,7 @@ stm32-fmc = "0.2.4"
|
||||
seq-macro = "0.3.0"
|
||||
cfg-if = "1.0.0"
|
||||
embedded-io = { version = "0.4.0", features = ["async"], optional = true }
|
||||
chrono = { version = "^0.4", default-features = false, optional = true}
|
||||
|
||||
[dev-dependencies]
|
||||
critical-section = { version = "1.1", features = ["std"] }
|
||||
@ -73,7 +74,7 @@ critical-section = { version = "1.1", features = ["std"] }
|
||||
[build-dependencies]
|
||||
proc-macro2 = "1.0.36"
|
||||
quote = "1.0.15"
|
||||
stm32-metapac = { version = "6", default-features = false, features = ["metadata"]}
|
||||
stm32-metapac = { version = "7", default-features = false, features = ["metadata"]}
|
||||
|
||||
[features]
|
||||
default = ["stm32-metapac/rt"]
|
||||
|
@ -81,11 +81,74 @@ fn main() {
|
||||
singletons.push(c.name.to_string());
|
||||
}
|
||||
|
||||
// ========
|
||||
// Handle time-driver-XXXX features.
|
||||
|
||||
let time_driver = match env::vars()
|
||||
.map(|(a, _)| a)
|
||||
.filter(|x| x.starts_with("CARGO_FEATURE_TIME_DRIVER_"))
|
||||
.get_one()
|
||||
{
|
||||
Ok(x) => Some(
|
||||
x.strip_prefix("CARGO_FEATURE_TIME_DRIVER_")
|
||||
.unwrap()
|
||||
.to_ascii_lowercase(),
|
||||
),
|
||||
Err(GetOneError::None) => None,
|
||||
Err(GetOneError::Multiple) => panic!("Multiple stm32xx Cargo features enabled"),
|
||||
};
|
||||
|
||||
let time_driver_singleton = match time_driver.as_ref().map(|x| x.as_ref()) {
|
||||
None => "",
|
||||
Some("tim2") => "TIM2",
|
||||
Some("tim3") => "TIM3",
|
||||
Some("tim4") => "TIM4",
|
||||
Some("tim5") => "TIM5",
|
||||
Some("tim12") => "TIM12",
|
||||
Some("tim15") => "TIM15",
|
||||
Some("any") => {
|
||||
if singletons.contains(&"TIM2".to_string()) {
|
||||
"TIM2"
|
||||
} else if singletons.contains(&"TIM3".to_string()) {
|
||||
"TIM3"
|
||||
} else if singletons.contains(&"TIM4".to_string()) {
|
||||
"TIM4"
|
||||
} else if singletons.contains(&"TIM5".to_string()) {
|
||||
"TIM5"
|
||||
} else if singletons.contains(&"TIM12".to_string()) {
|
||||
"TIM12"
|
||||
} else if singletons.contains(&"TIM15".to_string()) {
|
||||
"TIM15"
|
||||
} else {
|
||||
panic!("time-driver-any requested, but the chip doesn't have TIM2, TIM3, TIM4, TIM5, TIM12 or TIM15.")
|
||||
}
|
||||
}
|
||||
_ => panic!("unknown time_driver {:?}", time_driver),
|
||||
};
|
||||
|
||||
if time_driver_singleton != "" {
|
||||
println!("cargo:rustc-cfg=time_driver_{}", time_driver_singleton.to_lowercase());
|
||||
}
|
||||
|
||||
// ========
|
||||
// Write singletons
|
||||
|
||||
let mut g = TokenStream::new();
|
||||
|
||||
let singleton_tokens: Vec<_> = singletons.iter().map(|s| format_ident!("{}", s)).collect();
|
||||
|
||||
g.extend(quote! {
|
||||
embassy_hal_common::peripherals!(#(#singleton_tokens),*);
|
||||
embassy_hal_common::peripherals_definition!(#(#singleton_tokens),*);
|
||||
});
|
||||
|
||||
let singleton_tokens: Vec<_> = singletons
|
||||
.iter()
|
||||
.filter(|s| *s != &time_driver_singleton.to_string())
|
||||
.map(|s| format_ident!("{}", s))
|
||||
.collect();
|
||||
|
||||
g.extend(quote! {
|
||||
embassy_hal_common::peripherals_struct!(#(#singleton_tokens),*);
|
||||
});
|
||||
|
||||
// ========
|
||||
@ -192,12 +255,20 @@ fn main() {
|
||||
];
|
||||
});
|
||||
|
||||
let max_erase_size = flash_memory_regions
|
||||
.iter()
|
||||
.map(|region| region.settings.as_ref().unwrap().erase_size)
|
||||
.max()
|
||||
.unwrap();
|
||||
|
||||
g.extend(quote! { pub const MAX_ERASE_SIZE: usize = #max_erase_size as usize; });
|
||||
|
||||
g.extend(quote! { pub mod flash_regions { #flash_regions } });
|
||||
|
||||
// ========
|
||||
// Generate DMA IRQs.
|
||||
|
||||
let mut dma_irqs: HashMap<&str, Vec<(&str, &str)>> = HashMap::new();
|
||||
let mut dma_irqs: HashMap<&str, Vec<(&str, &str, &str)>> = HashMap::new();
|
||||
|
||||
for p in METADATA.peripherals {
|
||||
if let Some(r) = &p.registers {
|
||||
@ -207,7 +278,10 @@ fn main() {
|
||||
continue;
|
||||
}
|
||||
for irq in p.interrupts {
|
||||
dma_irqs.entry(irq.interrupt).or_default().push((p.name, irq.signal));
|
||||
dma_irqs
|
||||
.entry(irq.interrupt)
|
||||
.or_default()
|
||||
.push((r.kind, p.name, irq.signal));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -216,13 +290,14 @@ fn main() {
|
||||
for (irq, channels) in dma_irqs {
|
||||
let irq = format_ident!("{}", irq);
|
||||
|
||||
let channels = channels.iter().map(|(dma, ch)| format_ident!("{}_{}", dma, ch));
|
||||
let xdma = format_ident!("{}", channels[0].0);
|
||||
let channels = channels.iter().map(|(_, dma, ch)| format_ident!("{}_{}", dma, ch));
|
||||
|
||||
g.extend(quote! {
|
||||
#[crate::interrupt]
|
||||
unsafe fn #irq () {
|
||||
#(
|
||||
<crate::peripherals::#channels as crate::dma::sealed::Channel>::on_irq();
|
||||
<crate::peripherals::#channels as crate::dma::#xdma::sealed::Channel>::on_irq();
|
||||
)*
|
||||
}
|
||||
});
|
||||
@ -838,51 +913,6 @@ fn main() {
|
||||
println!("cargo:rustc-cfg={}x", &chip_name[..8]); // stm32f42x
|
||||
println!("cargo:rustc-cfg={}x{}", &chip_name[..7], &chip_name[8..9]); // stm32f4x9
|
||||
|
||||
// ========
|
||||
// Handle time-driver-XXXX features.
|
||||
|
||||
let time_driver = match env::vars()
|
||||
.map(|(a, _)| a)
|
||||
.filter(|x| x.starts_with("CARGO_FEATURE_TIME_DRIVER_"))
|
||||
.get_one()
|
||||
{
|
||||
Ok(x) => Some(
|
||||
x.strip_prefix("CARGO_FEATURE_TIME_DRIVER_")
|
||||
.unwrap()
|
||||
.to_ascii_lowercase(),
|
||||
),
|
||||
Err(GetOneError::None) => None,
|
||||
Err(GetOneError::Multiple) => panic!("Multiple stm32xx Cargo features enabled"),
|
||||
};
|
||||
|
||||
match time_driver.as_ref().map(|x| x.as_ref()) {
|
||||
None => {}
|
||||
Some("tim2") => println!("cargo:rustc-cfg=time_driver_tim2"),
|
||||
Some("tim3") => println!("cargo:rustc-cfg=time_driver_tim3"),
|
||||
Some("tim4") => println!("cargo:rustc-cfg=time_driver_tim4"),
|
||||
Some("tim5") => println!("cargo:rustc-cfg=time_driver_tim5"),
|
||||
Some("tim12") => println!("cargo:rustc-cfg=time_driver_tim12"),
|
||||
Some("tim15") => println!("cargo:rustc-cfg=time_driver_tim15"),
|
||||
Some("any") => {
|
||||
if singletons.contains(&"TIM2".to_string()) {
|
||||
println!("cargo:rustc-cfg=time_driver_tim2");
|
||||
} else if singletons.contains(&"TIM3".to_string()) {
|
||||
println!("cargo:rustc-cfg=time_driver_tim3");
|
||||
} else if singletons.contains(&"TIM4".to_string()) {
|
||||
println!("cargo:rustc-cfg=time_driver_tim4");
|
||||
} else if singletons.contains(&"TIM5".to_string()) {
|
||||
println!("cargo:rustc-cfg=time_driver_tim5");
|
||||
} else if singletons.contains(&"TIM12".to_string()) {
|
||||
println!("cargo:rustc-cfg=time_driver_tim12");
|
||||
} else if singletons.contains(&"TIM15".to_string()) {
|
||||
println!("cargo:rustc-cfg=time_driver_tim15");
|
||||
} else {
|
||||
panic!("time-driver-any requested, but the chip doesn't have TIM2, TIM3, TIM4, TIM5, TIM12 or TIM15.")
|
||||
}
|
||||
}
|
||||
_ => panic!("unknown time_driver {:?}", time_driver),
|
||||
}
|
||||
|
||||
// Handle time-driver-XXXX features.
|
||||
if env::var("CARGO_FEATURE_TIME_DRIVER_ANY").is_ok() {}
|
||||
println!("cargo:rustc-cfg={}", &chip_name[..chip_name.len() - 2]);
|
||||
|
@ -4,6 +4,7 @@ use core::task::Poll;
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
use crate::dma::Transfer;
|
||||
use crate::gpio::sealed::AFType;
|
||||
use crate::gpio::Speed;
|
||||
use crate::interrupt::{Interrupt, InterruptExt};
|
||||
@ -385,14 +386,11 @@ where
|
||||
return self.capture_giant(buffer).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn capture_small(&mut self, buffer: &mut [u32]) -> Result<(), Error> {
|
||||
let channel = &mut self.dma;
|
||||
let request = channel.request();
|
||||
|
||||
let r = self.inner.regs();
|
||||
let src = r.dr().ptr() as *mut u32;
|
||||
let dma_read = crate::dma::read(channel, request, src, buffer);
|
||||
let request = self.dma.request();
|
||||
let dma_read = unsafe { Transfer::new_read(&mut self.dma, request, src, buffer, Default::default()) };
|
||||
|
||||
Self::clear_interrupt_flags();
|
||||
Self::enable_irqs();
|
||||
@ -436,6 +434,12 @@ where
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(not(dma))]
|
||||
async fn capture_giant(&mut self, _buffer: &mut [u32]) -> Result<(), Error> {
|
||||
panic!("capturing to buffers larger than 0xffff is only supported on DMA for now, not on BDMA or GPDMA.");
|
||||
}
|
||||
|
||||
#[cfg(dma)]
|
||||
async fn capture_giant(&mut self, buffer: &mut [u32]) -> Result<(), Error> {
|
||||
use crate::dma::TransferOptions;
|
||||
|
||||
@ -460,16 +464,24 @@ where
|
||||
let r = self.inner.regs();
|
||||
let src = r.dr().ptr() as *mut u32;
|
||||
|
||||
unsafe {
|
||||
channel.start_double_buffered_read(request, src, m0ar, m1ar, chunk_size, TransferOptions::default());
|
||||
}
|
||||
let mut transfer = unsafe {
|
||||
crate::dma::DoubleBuffered::new_read(
|
||||
&mut self.dma,
|
||||
request,
|
||||
src,
|
||||
m0ar,
|
||||
m1ar,
|
||||
chunk_size,
|
||||
TransferOptions::default(),
|
||||
)
|
||||
};
|
||||
|
||||
let mut last_chunk_set_for_transfer = false;
|
||||
let mut buffer0_last_accessible = false;
|
||||
let dma_result = poll_fn(|cx| {
|
||||
channel.set_waker(cx.waker());
|
||||
transfer.set_waker(cx.waker());
|
||||
|
||||
let buffer0_currently_accessible = unsafe { channel.is_buffer0_accessible() };
|
||||
let buffer0_currently_accessible = transfer.is_buffer0_accessible();
|
||||
|
||||
// check if the accessible buffer changed since last poll
|
||||
if buffer0_last_accessible == buffer0_currently_accessible {
|
||||
@ -480,21 +492,21 @@ where
|
||||
if remaining_chunks != 0 {
|
||||
if remaining_chunks % 2 == 0 && buffer0_currently_accessible {
|
||||
m0ar = unsafe { m0ar.add(2 * chunk_size) };
|
||||
unsafe { channel.set_buffer0(m0ar) }
|
||||
unsafe { transfer.set_buffer0(m0ar) }
|
||||
remaining_chunks -= 1;
|
||||
} else if !buffer0_currently_accessible {
|
||||
m1ar = unsafe { m1ar.add(2 * chunk_size) };
|
||||
unsafe { channel.set_buffer1(m1ar) };
|
||||
unsafe { transfer.set_buffer1(m1ar) };
|
||||
remaining_chunks -= 1;
|
||||
}
|
||||
} else {
|
||||
if buffer0_currently_accessible {
|
||||
unsafe { channel.set_buffer0(buffer.as_mut_ptr()) }
|
||||
unsafe { transfer.set_buffer0(buffer.as_mut_ptr()) }
|
||||
} else {
|
||||
unsafe { channel.set_buffer1(buffer.as_mut_ptr()) }
|
||||
unsafe { transfer.set_buffer1(buffer.as_mut_ptr()) }
|
||||
}
|
||||
if last_chunk_set_for_transfer {
|
||||
channel.request_stop();
|
||||
transfer.request_stop();
|
||||
return Poll::Ready(());
|
||||
}
|
||||
last_chunk_set_for_transfer = true;
|
||||
|
@ -1,17 +1,33 @@
|
||||
#![macro_use]
|
||||
|
||||
use core::future::Future;
|
||||
use core::pin::Pin;
|
||||
use core::sync::atomic::{fence, Ordering};
|
||||
use core::task::Waker;
|
||||
use core::task::{Context, Poll, Waker};
|
||||
|
||||
use atomic_polyfill::AtomicUsize;
|
||||
use embassy_cortex_m::interrupt::Priority;
|
||||
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
use super::{TransferOptions, Word, WordSize};
|
||||
use super::ringbuffer::{DmaCtrl, DmaRingBuffer, OverrunError};
|
||||
use super::word::{Word, WordSize};
|
||||
use super::Dir;
|
||||
use crate::_generated::BDMA_CHANNEL_COUNT;
|
||||
use crate::dma::Request;
|
||||
use crate::interrupt::{Interrupt, InterruptExt};
|
||||
use crate::pac;
|
||||
use crate::pac::bdma::vals;
|
||||
use crate::pac::bdma::{regs, vals};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub struct TransferOptions {}
|
||||
|
||||
impl Default for TransferOptions {
|
||||
fn default() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WordSize> for vals::Size {
|
||||
fn from(raw: WordSize) -> Self {
|
||||
@ -23,15 +39,27 @@ impl From<WordSize> for vals::Size {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Dir> for vals::Dir {
|
||||
fn from(raw: Dir) -> Self {
|
||||
match raw {
|
||||
Dir::MemoryToPeripheral => Self::FROMMEMORY,
|
||||
Dir::PeripheralToMemory => Self::FROMPERIPHERAL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct State {
|
||||
ch_wakers: [AtomicWaker; BDMA_CHANNEL_COUNT],
|
||||
complete_count: [AtomicUsize; BDMA_CHANNEL_COUNT],
|
||||
}
|
||||
|
||||
impl State {
|
||||
const fn new() -> Self {
|
||||
const ZERO: AtomicUsize = AtomicUsize::new(0);
|
||||
const AW: AtomicWaker = AtomicWaker::new();
|
||||
Self {
|
||||
ch_wakers: [AW; BDMA_CHANNEL_COUNT],
|
||||
complete_count: [ZERO; BDMA_CHANNEL_COUNT],
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -55,228 +83,443 @@ foreach_dma_channel! {
|
||||
// BDMA1 in H7 doesn't use DMAMUX, which breaks
|
||||
};
|
||||
($channel_peri:ident, $dma_peri:ident, bdma, $channel_num:expr, $index:expr, $dmamux:tt) => {
|
||||
impl crate::dma::sealed::Channel for crate::peripherals::$channel_peri {
|
||||
|
||||
unsafe fn start_write<W: Word>(&mut self, _request: Request, buf: *const[W], reg_addr: *mut W, options: TransferOptions) {
|
||||
let (ptr, len) = super::slice_ptr_parts(buf);
|
||||
low_level_api::start_transfer(
|
||||
pac::$dma_peri,
|
||||
$channel_num,
|
||||
#[cfg(any(bdma_v2, dmamux))]
|
||||
_request,
|
||||
vals::Dir::FROMMEMORY,
|
||||
reg_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
vals::Size::from(W::bits()),
|
||||
options,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_REGS,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_CH_NUM,
|
||||
);
|
||||
impl sealed::Channel for crate::peripherals::$channel_peri {
|
||||
fn regs(&self) -> pac::bdma::Dma {
|
||||
pac::$dma_peri
|
||||
}
|
||||
|
||||
unsafe fn start_write_repeated<W: Word>(&mut self, _request: Request, repeated: *const W, count: usize, reg_addr: *mut W, options: TransferOptions) {
|
||||
low_level_api::start_transfer(
|
||||
pac::$dma_peri,
|
||||
$channel_num,
|
||||
#[cfg(any(bdma_v2, dmamux))]
|
||||
_request,
|
||||
vals::Dir::FROMMEMORY,
|
||||
reg_addr as *const u32,
|
||||
repeated as *mut u32,
|
||||
count,
|
||||
false,
|
||||
vals::Size::from(W::bits()),
|
||||
options,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_REGS,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_CH_NUM,
|
||||
)
|
||||
fn num(&self) -> usize {
|
||||
$channel_num
|
||||
}
|
||||
|
||||
unsafe fn start_read<W: Word>(&mut self, _request: Request, reg_addr: *const W, buf: *mut [W], options: TransferOptions) {
|
||||
let (ptr, len) = super::slice_ptr_parts_mut(buf);
|
||||
low_level_api::start_transfer(
|
||||
pac::$dma_peri,
|
||||
$channel_num,
|
||||
#[cfg(any(bdma_v2, dmamux))]
|
||||
_request,
|
||||
vals::Dir::FROMPERIPHERAL,
|
||||
reg_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
vals::Size::from(W::bits()),
|
||||
options,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_REGS,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_CH_NUM,
|
||||
);
|
||||
fn index(&self) -> usize {
|
||||
$index
|
||||
}
|
||||
|
||||
unsafe fn start_double_buffered_read<W: super::Word>(
|
||||
&mut self,
|
||||
_request: Request,
|
||||
_reg_addr: *const W,
|
||||
_buffer0: *mut W,
|
||||
_buffer1: *mut W,
|
||||
_buffer_len: usize,
|
||||
_options: TransferOptions,
|
||||
) {
|
||||
panic!("Unsafe double buffered mode is unavailable on BDMA");
|
||||
}
|
||||
|
||||
unsafe fn set_buffer0<W: super::Word>(&mut self, _buffer: *mut W) {
|
||||
panic!("Unsafe double buffered mode is unavailable on BDMA");
|
||||
}
|
||||
|
||||
unsafe fn set_buffer1<W: super::Word>(&mut self, _buffer: *mut W) {
|
||||
panic!("Unsafe double buffered mode is unavailable on BDMA");
|
||||
}
|
||||
|
||||
unsafe fn is_buffer0_accessible(&mut self) -> bool {
|
||||
panic!("Unsafe double buffered mode is unavailable on BDMA");
|
||||
}
|
||||
|
||||
fn request_stop(&mut self){
|
||||
unsafe {low_level_api::request_stop(pac::$dma_peri, $channel_num);}
|
||||
}
|
||||
|
||||
fn is_running(&self) -> bool {
|
||||
unsafe {low_level_api::is_running(pac::$dma_peri, $channel_num)}
|
||||
}
|
||||
fn remaining_transfers(&mut self) -> u16 {
|
||||
unsafe {low_level_api::get_remaining_transfers(pac::$dma_peri, $channel_num)}
|
||||
}
|
||||
|
||||
fn set_waker(&mut self, waker: &Waker) {
|
||||
unsafe { low_level_api::set_waker($index, waker) }
|
||||
}
|
||||
|
||||
fn on_irq() {
|
||||
unsafe {
|
||||
low_level_api::on_irq_inner(pac::$dma_peri, $channel_num, $index);
|
||||
}
|
||||
unsafe { on_irq_inner(pac::$dma_peri, $channel_num, $index) }
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::dma::Channel for crate::peripherals::$channel_peri {}
|
||||
impl Channel for crate::peripherals::$channel_peri {}
|
||||
};
|
||||
}
|
||||
|
||||
mod low_level_api {
|
||||
/// Safety: Must be called with a matching set of parameters for a valid dma channel
|
||||
pub(crate) unsafe fn on_irq_inner(dma: pac::bdma::Dma, channel_num: usize, index: usize) {
|
||||
let isr = dma.isr().read();
|
||||
let cr = dma.ch(channel_num).cr();
|
||||
|
||||
if isr.teif(channel_num) {
|
||||
panic!("DMA: error on BDMA@{:08x} channel {}", dma.0 as u32, channel_num);
|
||||
}
|
||||
|
||||
let mut wake = false;
|
||||
|
||||
if isr.htif(channel_num) && cr.read().htie() {
|
||||
// Acknowledge half transfer complete interrupt
|
||||
dma.ifcr().write(|w| w.set_htif(channel_num, true));
|
||||
wake = true;
|
||||
}
|
||||
|
||||
if isr.tcif(channel_num) && cr.read().tcie() {
|
||||
// Acknowledge transfer complete interrupt
|
||||
dma.ifcr().write(|w| w.set_tcif(channel_num, true));
|
||||
STATE.complete_count[index].fetch_add(1, Ordering::Release);
|
||||
wake = true;
|
||||
}
|
||||
|
||||
if wake {
|
||||
STATE.ch_wakers[index].wake();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(bdma_v2, dmamux))]
|
||||
pub type Request = u8;
|
||||
#[cfg(not(any(bdma_v2, dmamux)))]
|
||||
pub type Request = ();
|
||||
|
||||
#[cfg(dmamux)]
|
||||
pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static + super::dmamux::MuxChannel {}
|
||||
#[cfg(not(dmamux))]
|
||||
pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static {}
|
||||
|
||||
pub(crate) mod sealed {
|
||||
use super::*;
|
||||
|
||||
pub unsafe fn start_transfer(
|
||||
dma: pac::bdma::Dma,
|
||||
channel_number: u8,
|
||||
#[cfg(any(bdma_v2, dmamux))] request: Request,
|
||||
dir: vals::Dir,
|
||||
pub trait Channel {
|
||||
fn regs(&self) -> pac::bdma::Dma;
|
||||
fn num(&self) -> usize;
|
||||
fn index(&self) -> usize;
|
||||
fn on_irq();
|
||||
}
|
||||
}
|
||||
|
||||
#[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 unsafe fn new_read<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
peri_addr: *mut W,
|
||||
buf: &'a mut [W],
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
Self::new_read_raw(channel, request, peri_addr, buf, options)
|
||||
}
|
||||
|
||||
pub unsafe fn new_read_raw<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
peri_addr: *mut W,
|
||||
buf: *mut [W],
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
into_ref!(channel);
|
||||
|
||||
let (ptr, len) = super::slice_ptr_parts_mut(buf);
|
||||
assert!(len > 0 && len <= 0xFFFF);
|
||||
|
||||
Self::new_inner(
|
||||
channel,
|
||||
request,
|
||||
Dir::PeripheralToMemory,
|
||||
peri_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
W::size(),
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
pub unsafe fn new_write<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
buf: &'a [W],
|
||||
peri_addr: *mut W,
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
Self::new_write_raw(channel, request, buf, peri_addr, options)
|
||||
}
|
||||
|
||||
pub unsafe fn new_write_raw<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
buf: *const [W],
|
||||
peri_addr: *mut W,
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
into_ref!(channel);
|
||||
|
||||
let (ptr, len) = super::slice_ptr_parts(buf);
|
||||
assert!(len > 0 && len <= 0xFFFF);
|
||||
|
||||
Self::new_inner(
|
||||
channel,
|
||||
request,
|
||||
Dir::MemoryToPeripheral,
|
||||
peri_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
W::size(),
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
pub unsafe fn new_write_repeated<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
repeated: &'a W,
|
||||
count: usize,
|
||||
peri_addr: *mut W,
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
into_ref!(channel);
|
||||
|
||||
Self::new_inner(
|
||||
channel,
|
||||
request,
|
||||
Dir::MemoryToPeripheral,
|
||||
peri_addr as *const u32,
|
||||
repeated as *const W as *mut u32,
|
||||
count,
|
||||
false,
|
||||
W::size(),
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
unsafe fn new_inner(
|
||||
channel: PeripheralRef<'a, C>,
|
||||
_request: Request,
|
||||
dir: Dir,
|
||||
peri_addr: *const u32,
|
||||
mem_addr: *mut u32,
|
||||
mem_len: usize,
|
||||
incr_mem: bool,
|
||||
data_size: vals::Size,
|
||||
options: TransferOptions,
|
||||
#[cfg(dmamux)] dmamux_regs: pac::dmamux::Dmamux,
|
||||
#[cfg(dmamux)] dmamux_ch_num: u8,
|
||||
) {
|
||||
assert!(options.mburst == crate::dma::Burst::Single, "Burst mode not supported");
|
||||
assert!(options.pburst == crate::dma::Burst::Single, "Burst mode not supported");
|
||||
assert!(
|
||||
options.flow_ctrl == crate::dma::FlowControl::Dma,
|
||||
"Peripheral flow control not supported"
|
||||
);
|
||||
assert!(options.fifo_threshold.is_none(), "FIFO mode not supported");
|
||||
|
||||
let ch = dma.ch(channel_number as _);
|
||||
|
||||
reset_status(dma, channel_number);
|
||||
|
||||
#[cfg(dmamux)]
|
||||
super::super::dmamux::configure_dmamux(dmamux_regs, dmamux_ch_num, request);
|
||||
|
||||
#[cfg(bdma_v2)]
|
||||
critical_section::with(|_| dma.cselr().modify(|w| w.set_cs(channel_number as _, request)));
|
||||
data_size: WordSize,
|
||||
_options: TransferOptions,
|
||||
) -> Self {
|
||||
let ch = channel.regs().ch(channel.num());
|
||||
|
||||
// "Preceding reads and writes cannot be moved past subsequent writes."
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
#[cfg(bdma_v2)]
|
||||
critical_section::with(|_| channel.regs().cselr().modify(|w| w.set_cs(channel.num(), _request)));
|
||||
|
||||
let mut this = Self { channel };
|
||||
this.clear_irqs();
|
||||
STATE.complete_count[this.channel.index()].store(0, Ordering::Release);
|
||||
|
||||
#[cfg(dmamux)]
|
||||
super::dmamux::configure_dmamux(&mut *this.channel, _request);
|
||||
|
||||
ch.par().write_value(peri_addr as u32);
|
||||
ch.mar().write_value(mem_addr as u32);
|
||||
ch.ndtr().write(|w| w.set_ndt(mem_len as u16));
|
||||
ch.cr().write(|w| {
|
||||
w.set_psize(data_size);
|
||||
w.set_msize(data_size);
|
||||
w.set_psize(data_size.into());
|
||||
w.set_msize(data_size.into());
|
||||
if incr_mem {
|
||||
w.set_minc(vals::Inc::ENABLED);
|
||||
} else {
|
||||
w.set_minc(vals::Inc::DISABLED);
|
||||
}
|
||||
w.set_dir(dir);
|
||||
w.set_dir(dir.into());
|
||||
w.set_teie(true);
|
||||
w.set_tcie(true);
|
||||
w.set_en(true);
|
||||
});
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
pub unsafe fn request_stop(dma: pac::bdma::Dma, channel_number: u8) {
|
||||
reset_status(dma, channel_number);
|
||||
|
||||
let ch = dma.ch(channel_number as _);
|
||||
|
||||
// Disable the channel and interrupts with the default value.
|
||||
ch.cr().write(|_| ());
|
||||
|
||||
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
|
||||
fence(Ordering::SeqCst);
|
||||
fn clear_irqs(&mut self) {
|
||||
unsafe {
|
||||
self.channel.regs().ifcr().write(|w| {
|
||||
w.set_tcif(self.channel.num(), true);
|
||||
w.set_teif(self.channel.num(), true);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn is_running(dma: pac::bdma::Dma, ch: u8) -> bool {
|
||||
let ch = dma.ch(ch as _);
|
||||
ch.cr().read().en()
|
||||
pub fn request_stop(&mut self) {
|
||||
let ch = self.channel.regs().ch(self.channel.num());
|
||||
|
||||
// Disable the channel. Keep the IEs enabled so the irqs still fire.
|
||||
unsafe {
|
||||
ch.cr().write(|w| {
|
||||
w.set_teie(true);
|
||||
w.set_tcie(true);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_running(&mut self) -> bool {
|
||||
let ch = self.channel.regs().ch(self.channel.num());
|
||||
let en = unsafe { ch.cr().read() }.en();
|
||||
let tcif = STATE.complete_count[self.channel.index()].load(Ordering::Acquire) != 0;
|
||||
en && !tcif
|
||||
}
|
||||
|
||||
/// Gets the total remaining transfers for the channel
|
||||
/// Note: this will be zero for transfers that completed without cancellation.
|
||||
pub unsafe fn get_remaining_transfers(dma: pac::bdma::Dma, ch: u8) -> u16 {
|
||||
// get a handle on the channel itself
|
||||
let ch = dma.ch(ch as _);
|
||||
// read the remaining transfer count. If this is zero, the transfer completed fully.
|
||||
ch.ndtr().read().ndt() as u16
|
||||
pub fn get_remaining_transfers(&self) -> u16 {
|
||||
let ch = self.channel.regs().ch(self.channel.num());
|
||||
unsafe { ch.ndtr().read() }.ndt()
|
||||
}
|
||||
|
||||
/// Sets the waker for the specified DMA channel
|
||||
pub unsafe fn set_waker(state_number: usize, waker: &Waker) {
|
||||
STATE.ch_wakers[state_number].register(waker);
|
||||
pub fn blocking_wait(mut self) {
|
||||
while self.is_running() {}
|
||||
|
||||
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
core::mem::forget(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn reset_status(dma: pac::bdma::Dma, channel_number: u8) {
|
||||
dma.ifcr().write(|w| {
|
||||
w.set_tcif(channel_number as _, true);
|
||||
w.set_teif(channel_number as _, true);
|
||||
});
|
||||
impl<'a, C: Channel> Drop for Transfer<'a, C> {
|
||||
fn drop(&mut self) {
|
||||
self.request_stop();
|
||||
while self.is_running() {}
|
||||
|
||||
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
|
||||
fence(Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
/// Safety: Must be called with a matching set of parameters for a valid dma channel
|
||||
pub unsafe fn on_irq_inner(dma: pac::bdma::Dma, channel_num: u8, index: u8) {
|
||||
let channel_num = channel_num as usize;
|
||||
let index = index as usize;
|
||||
impl<'a, C: Channel> Unpin for Transfer<'a, C> {}
|
||||
impl<'a, C: Channel> Future for Transfer<'a, C> {
|
||||
type Output = ();
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
STATE.ch_wakers[self.channel.index()].register(cx.waker());
|
||||
|
||||
let isr = dma.isr().read();
|
||||
let cr = dma.ch(channel_num).cr();
|
||||
|
||||
if isr.teif(channel_num) {
|
||||
panic!("DMA: error on BDMA@{:08x} channel {}", dma.0 as u32, channel_num);
|
||||
}
|
||||
if isr.tcif(channel_num) && cr.read().tcie() {
|
||||
cr.write(|_| ()); // Disable channel interrupts with the default value.
|
||||
STATE.ch_wakers[index].wake();
|
||||
if self.is_running() {
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================
|
||||
|
||||
struct DmaCtrlImpl<'a, C: Channel>(PeripheralRef<'a, C>);
|
||||
|
||||
impl<'a, C: Channel> DmaCtrl for DmaCtrlImpl<'a, C> {
|
||||
fn ndtr(&self) -> usize {
|
||||
let ch = self.0.regs().ch(self.0.num());
|
||||
unsafe { ch.ndtr().read() }.ndt() as usize
|
||||
}
|
||||
|
||||
fn get_complete_count(&self) -> usize {
|
||||
STATE.complete_count[self.0.index()].load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
fn reset_complete_count(&mut self) -> usize {
|
||||
STATE.complete_count[self.0.index()].swap(0, Ordering::AcqRel)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RingBuffer<'a, C: Channel, W: Word> {
|
||||
cr: regs::Cr,
|
||||
channel: PeripheralRef<'a, C>,
|
||||
ringbuf: DmaRingBuffer<'a, W>,
|
||||
}
|
||||
|
||||
impl<'a, C: Channel, W: Word> RingBuffer<'a, C, W> {
|
||||
pub unsafe fn new_read(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
_request: Request,
|
||||
peri_addr: *mut W,
|
||||
buffer: &'a mut [W],
|
||||
_options: TransferOptions,
|
||||
) -> Self {
|
||||
into_ref!(channel);
|
||||
|
||||
let len = buffer.len();
|
||||
assert!(len > 0 && len <= 0xFFFF);
|
||||
|
||||
let dir = Dir::PeripheralToMemory;
|
||||
let data_size = W::size();
|
||||
|
||||
let channel_number = channel.num();
|
||||
let dma = channel.regs();
|
||||
|
||||
// "Preceding reads and writes cannot be moved past subsequent writes."
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
#[cfg(bdma_v2)]
|
||||
critical_section::with(|_| channel.regs().cselr().modify(|w| w.set_cs(channel.num(), _request)));
|
||||
|
||||
let mut w = regs::Cr(0);
|
||||
w.set_psize(data_size.into());
|
||||
w.set_msize(data_size.into());
|
||||
w.set_minc(vals::Inc::ENABLED);
|
||||
w.set_dir(dir.into());
|
||||
w.set_teie(true);
|
||||
w.set_htie(true);
|
||||
w.set_tcie(true);
|
||||
w.set_circ(vals::Circ::ENABLED);
|
||||
w.set_pl(vals::Pl::VERYHIGH);
|
||||
w.set_en(true);
|
||||
|
||||
let buffer_ptr = buffer.as_mut_ptr();
|
||||
let mut this = Self {
|
||||
channel,
|
||||
cr: w,
|
||||
ringbuf: DmaRingBuffer::new(buffer),
|
||||
};
|
||||
this.clear_irqs();
|
||||
|
||||
#[cfg(dmamux)]
|
||||
super::dmamux::configure_dmamux(&mut *this.channel, _request);
|
||||
|
||||
let ch = dma.ch(channel_number);
|
||||
ch.par().write_value(peri_addr as u32);
|
||||
ch.mar().write_value(buffer_ptr as u32);
|
||||
ch.ndtr().write(|w| w.set_ndt(len as u16));
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
let ch = self.channel.regs().ch(self.channel.num());
|
||||
unsafe { ch.cr().write_value(self.cr) }
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.ringbuf.clear(DmaCtrlImpl(self.channel.reborrow()));
|
||||
}
|
||||
|
||||
/// Read bytes from the ring buffer
|
||||
/// OverrunError is returned if the portion to be read was overwritten by the DMA controller.
|
||||
pub fn read(&mut self, buf: &mut [W]) -> Result<usize, OverrunError> {
|
||||
self.ringbuf.read(DmaCtrlImpl(self.channel.reborrow()), buf)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.ringbuf.is_empty()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.ringbuf.len()
|
||||
}
|
||||
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.ringbuf.dma_buf.len()
|
||||
}
|
||||
|
||||
pub fn set_waker(&mut self, waker: &Waker) {
|
||||
STATE.ch_wakers[self.channel.index()].register(waker);
|
||||
}
|
||||
|
||||
fn clear_irqs(&mut self) {
|
||||
let dma = self.channel.regs();
|
||||
unsafe {
|
||||
dma.ifcr().write(|w| {
|
||||
w.set_htif(self.channel.num(), true);
|
||||
w.set_tcif(self.channel.num(), true);
|
||||
w.set_teif(self.channel.num(), true);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_stop(&mut self) {
|
||||
let ch = self.channel.regs().ch(self.channel.num());
|
||||
|
||||
// Disable the channel. Keep the IEs enabled so the irqs still fire.
|
||||
unsafe {
|
||||
ch.cr().write(|w| {
|
||||
w.set_teie(true);
|
||||
w.set_htie(true);
|
||||
w.set_tcie(true);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_running(&mut self) -> bool {
|
||||
let ch = self.channel.regs().ch(self.channel.num());
|
||||
unsafe { ch.cr().read() }.en()
|
||||
}
|
||||
|
||||
/// Synchronize the position of the ring buffer to the actual DMA controller position
|
||||
pub fn reload_position(&mut self) {
|
||||
let ch = self.channel.regs().ch(self.channel.num());
|
||||
self.ringbuf.ndtr = unsafe { ch.ndtr().read() }.ndt() as usize;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, C: Channel, W: Word> Drop for RingBuffer<'a, C, W> {
|
||||
fn drop(&mut self) {
|
||||
self.request_stop();
|
||||
while self.is_running() {}
|
||||
|
||||
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
|
||||
fence(Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2,8 +2,8 @@
|
||||
|
||||
use crate::{pac, peripherals};
|
||||
|
||||
pub(crate) unsafe fn configure_dmamux(dmamux_regs: pac::dmamux::Dmamux, dmamux_ch_num: u8, request: u8) {
|
||||
let ch_mux_regs = dmamux_regs.ccr(dmamux_ch_num as _);
|
||||
pub(crate) unsafe fn configure_dmamux<M: MuxChannel>(channel: &mut M, request: u8) {
|
||||
let ch_mux_regs = channel.mux_regs().ccr(channel.mux_num());
|
||||
ch_mux_regs.write(|reg| {
|
||||
reg.set_nbreq(0);
|
||||
reg.set_dmareq_id(request);
|
||||
@ -14,11 +14,11 @@ pub(crate) unsafe fn configure_dmamux(dmamux_regs: pac::dmamux::Dmamux, dmamux_c
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) mod sealed {
|
||||
pub(crate) mod dmamux_sealed {
|
||||
use super::*;
|
||||
pub trait MuxChannel {
|
||||
const DMAMUX_CH_NUM: u8;
|
||||
const DMAMUX_REGS: pac::dmamux::Dmamux;
|
||||
fn mux_regs(&self) -> pac::dmamux::Dmamux;
|
||||
fn mux_num(&self) -> usize;
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,15 +26,19 @@ pub struct DMAMUX1;
|
||||
#[cfg(stm32h7)]
|
||||
pub struct DMAMUX2;
|
||||
|
||||
pub trait MuxChannel: sealed::MuxChannel + super::Channel {
|
||||
pub trait MuxChannel: dmamux_sealed::MuxChannel {
|
||||
type Mux;
|
||||
}
|
||||
|
||||
foreach_dma_channel! {
|
||||
($channel_peri:ident, $dma_peri:ident, $version:ident, $channel_num:expr, $index:expr, {dmamux: $dmamux:ident, dmamux_channel: $dmamux_channel:expr}) => {
|
||||
impl sealed::MuxChannel for peripherals::$channel_peri {
|
||||
const DMAMUX_CH_NUM: u8 = $dmamux_channel;
|
||||
const DMAMUX_REGS: pac::dmamux::Dmamux = pac::$dmamux;
|
||||
impl dmamux_sealed::MuxChannel for peripherals::$channel_peri {
|
||||
fn mux_regs(&self) -> pac::dmamux::Dmamux {
|
||||
pac::$dmamux
|
||||
}
|
||||
fn mux_num(&self) -> usize {
|
||||
$dmamux_channel
|
||||
}
|
||||
}
|
||||
impl MuxChannel for peripherals::$channel_peri {
|
||||
type Mux = $dmamux;
|
||||
|
@ -1,13 +1,31 @@
|
||||
use core::sync::atomic::{fence, Ordering};
|
||||
use core::task::Waker;
|
||||
#![macro_use]
|
||||
|
||||
use core::future::Future;
|
||||
use core::pin::Pin;
|
||||
use core::sync::atomic::{fence, Ordering};
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
use embassy_cortex_m::interrupt::Priority;
|
||||
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
use super::{Request, TransferOptions, Word, WordSize};
|
||||
use super::word::{Word, WordSize};
|
||||
use super::Dir;
|
||||
use crate::_generated::GPDMA_CHANNEL_COUNT;
|
||||
use crate::interrupt::{Interrupt, InterruptExt};
|
||||
use crate::pac::gpdma::{vals, Gpdma};
|
||||
use crate::{interrupt, pac};
|
||||
use crate::pac;
|
||||
use crate::pac::gpdma::vals;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub struct TransferOptions {}
|
||||
|
||||
impl Default for TransferOptions {
|
||||
fn default() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WordSize> for vals::ChTr1Dw {
|
||||
fn from(raw: WordSize) -> Self {
|
||||
@ -19,27 +37,15 @@ impl From<WordSize> for vals::ChTr1Dw {
|
||||
}
|
||||
}
|
||||
|
||||
struct ChannelState {
|
||||
waker: AtomicWaker,
|
||||
}
|
||||
|
||||
impl ChannelState {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
waker: AtomicWaker::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct State {
|
||||
channels: [ChannelState; GPDMA_CHANNEL_COUNT],
|
||||
ch_wakers: [AtomicWaker; GPDMA_CHANNEL_COUNT],
|
||||
}
|
||||
|
||||
impl State {
|
||||
const fn new() -> Self {
|
||||
const CH: ChannelState = ChannelState::new();
|
||||
const AW: AtomicWaker = AtomicWaker::new();
|
||||
Self {
|
||||
channels: [CH; GPDMA_CHANNEL_COUNT],
|
||||
ch_wakers: [AW; GPDMA_CHANNEL_COUNT],
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,10 +53,12 @@ impl State {
|
||||
static STATE: State = State::new();
|
||||
|
||||
/// safety: must be called only once
|
||||
pub(crate) unsafe fn init() {
|
||||
pub(crate) unsafe fn init(irq_priority: Priority) {
|
||||
foreach_interrupt! {
|
||||
($peri:ident, gpdma, $block:ident, $signal_name:ident, $irq:ident) => {
|
||||
interrupt::$irq::steal().enable();
|
||||
let irq = crate::interrupt::$irq::steal();
|
||||
irq.set_priority(irq_priority);
|
||||
irq.enable();
|
||||
};
|
||||
}
|
||||
crate::_generated::init_gpdma();
|
||||
@ -58,117 +66,171 @@ pub(crate) unsafe fn init() {
|
||||
|
||||
foreach_dma_channel! {
|
||||
($channel_peri:ident, $dma_peri:ident, gpdma, $channel_num:expr, $index:expr, $dmamux:tt) => {
|
||||
impl crate::dma::sealed::Channel for crate::peripherals::$channel_peri {
|
||||
unsafe fn start_write<W: Word>(&mut self, request: Request, buf: *const [W], reg_addr: *mut W, options: TransferOptions) {
|
||||
let (ptr, len) = super::slice_ptr_parts(buf);
|
||||
low_level_api::start_transfer(
|
||||
pac::$dma_peri,
|
||||
$channel_num,
|
||||
request,
|
||||
low_level_api::Dir::MemoryToPeripheral,
|
||||
reg_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
W::bits(),
|
||||
options,
|
||||
)
|
||||
impl sealed::Channel for crate::peripherals::$channel_peri {
|
||||
fn regs(&self) -> pac::gpdma::Gpdma {
|
||||
pac::$dma_peri
|
||||
}
|
||||
|
||||
unsafe fn start_write_repeated<W: Word>(&mut self, request: Request, repeated: *const W, count: usize, reg_addr: *mut W, options: TransferOptions) {
|
||||
low_level_api::start_transfer(
|
||||
pac::$dma_peri,
|
||||
$channel_num,
|
||||
request,
|
||||
low_level_api::Dir::MemoryToPeripheral,
|
||||
reg_addr as *const u32,
|
||||
repeated as *mut u32,
|
||||
count,
|
||||
false,
|
||||
W::bits(),
|
||||
options,
|
||||
)
|
||||
fn num(&self) -> usize {
|
||||
$channel_num
|
||||
}
|
||||
|
||||
unsafe fn start_read<W: Word>(&mut self, request: Request, reg_addr: *const W, buf: *mut [W], options: TransferOptions) {
|
||||
let (ptr, len) = super::slice_ptr_parts_mut(buf);
|
||||
low_level_api::start_transfer(
|
||||
pac::$dma_peri,
|
||||
$channel_num,
|
||||
request,
|
||||
low_level_api::Dir::PeripheralToMemory,
|
||||
reg_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
W::bits(),
|
||||
options,
|
||||
);
|
||||
fn index(&self) -> usize {
|
||||
$index
|
||||
}
|
||||
|
||||
unsafe fn start_double_buffered_read<W: Word>(
|
||||
&mut self,
|
||||
_request: Request,
|
||||
_reg_addr: *const W,
|
||||
_buffer0: *mut W,
|
||||
_buffer1: *mut W,
|
||||
_buffer_len: usize,
|
||||
_options: TransferOptions,
|
||||
) {
|
||||
panic!("Unsafe double buffered mode is unavailable on GPBDMA");
|
||||
}
|
||||
|
||||
unsafe fn set_buffer0<W: Word>(&mut self, _buffer: *mut W) {
|
||||
panic!("Unsafe double buffered mode is unavailable on GPBDMA");
|
||||
}
|
||||
|
||||
unsafe fn set_buffer1<W: Word>(&mut self, _buffer: *mut W) {
|
||||
panic!("Unsafe double buffered mode is unavailable on GPBDMA");
|
||||
}
|
||||
|
||||
unsafe fn is_buffer0_accessible(&mut self) -> bool {
|
||||
panic!("Unsafe double buffered mode is unavailable on GPBDMA");
|
||||
}
|
||||
|
||||
fn request_stop(&mut self) {
|
||||
unsafe {low_level_api::request_stop(pac::$dma_peri, $channel_num);}
|
||||
}
|
||||
|
||||
fn is_running(&self) -> bool {
|
||||
unsafe {low_level_api::is_running(pac::$dma_peri, $channel_num)}
|
||||
}
|
||||
|
||||
fn remaining_transfers(&mut self) -> u16 {
|
||||
unsafe {low_level_api::get_remaining_transfers(pac::$dma_peri, $channel_num)}
|
||||
}
|
||||
|
||||
fn set_waker(&mut self, waker: &Waker) {
|
||||
unsafe {low_level_api::set_waker($index, waker )}
|
||||
}
|
||||
|
||||
fn on_irq() {
|
||||
unsafe {
|
||||
low_level_api::on_irq_inner(pac::$dma_peri, $channel_num, $index);
|
||||
}
|
||||
unsafe { on_irq_inner(pac::$dma_peri, $channel_num, $index) }
|
||||
}
|
||||
}
|
||||
impl crate::dma::Channel for crate::peripherals::$channel_peri { }
|
||||
|
||||
impl Channel for crate::peripherals::$channel_peri {}
|
||||
};
|
||||
}
|
||||
|
||||
mod low_level_api {
|
||||
use super::*;
|
||||
/// Safety: Must be called with a matching set of parameters for a valid dma channel
|
||||
pub(crate) unsafe fn on_irq_inner(dma: pac::gpdma::Gpdma, channel_num: usize, index: usize) {
|
||||
let ch = dma.ch(channel_num);
|
||||
let sr = ch.sr().read();
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Dir {
|
||||
MemoryToPeripheral,
|
||||
PeripheralToMemory,
|
||||
if sr.dtef() {
|
||||
panic!(
|
||||
"DMA: data transfer error on DMA@{:08x} channel {}",
|
||||
dma.0 as u32, channel_num
|
||||
);
|
||||
}
|
||||
if sr.usef() {
|
||||
panic!(
|
||||
"DMA: user settings error on DMA@{:08x} channel {}",
|
||||
dma.0 as u32, channel_num
|
||||
);
|
||||
}
|
||||
|
||||
pub unsafe fn start_transfer(
|
||||
dma: Gpdma,
|
||||
channel_number: u8,
|
||||
if sr.suspf() || sr.tcf() {
|
||||
// disable all xxIEs to prevent the irq from firing again.
|
||||
ch.cr().write(|_| {});
|
||||
|
||||
// Wake the future. It'll look at tcf and see it's set.
|
||||
STATE.ch_wakers[index].wake();
|
||||
}
|
||||
}
|
||||
|
||||
pub type Request = u8;
|
||||
|
||||
#[cfg(dmamux)]
|
||||
pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static + super::dmamux::MuxChannel {}
|
||||
#[cfg(not(dmamux))]
|
||||
pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static {}
|
||||
|
||||
pub(crate) mod sealed {
|
||||
use super::*;
|
||||
|
||||
pub trait Channel {
|
||||
fn regs(&self) -> pac::gpdma::Gpdma;
|
||||
fn num(&self) -> usize;
|
||||
fn index(&self) -> usize;
|
||||
fn on_irq();
|
||||
}
|
||||
}
|
||||
|
||||
#[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 unsafe fn new_read<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
peri_addr: *mut W,
|
||||
buf: &'a mut [W],
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
Self::new_read_raw(channel, request, peri_addr, buf, options)
|
||||
}
|
||||
|
||||
pub unsafe fn new_read_raw<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
peri_addr: *mut W,
|
||||
buf: *mut [W],
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
into_ref!(channel);
|
||||
|
||||
let (ptr, len) = super::slice_ptr_parts_mut(buf);
|
||||
assert!(len > 0 && len <= 0xFFFF);
|
||||
|
||||
Self::new_inner(
|
||||
channel,
|
||||
request,
|
||||
Dir::PeripheralToMemory,
|
||||
peri_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
W::size(),
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
pub unsafe fn new_write<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
buf: &'a [W],
|
||||
peri_addr: *mut W,
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
Self::new_write_raw(channel, request, buf, peri_addr, options)
|
||||
}
|
||||
|
||||
pub unsafe fn new_write_raw<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
buf: *const [W],
|
||||
peri_addr: *mut W,
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
into_ref!(channel);
|
||||
|
||||
let (ptr, len) = super::slice_ptr_parts(buf);
|
||||
assert!(len > 0 && len <= 0xFFFF);
|
||||
|
||||
Self::new_inner(
|
||||
channel,
|
||||
request,
|
||||
Dir::MemoryToPeripheral,
|
||||
peri_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
W::size(),
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
pub unsafe fn new_write_repeated<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
repeated: &'a W,
|
||||
count: usize,
|
||||
peri_addr: *mut W,
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
into_ref!(channel);
|
||||
|
||||
Self::new_inner(
|
||||
channel,
|
||||
request,
|
||||
Dir::MemoryToPeripheral,
|
||||
peri_addr as *const u32,
|
||||
repeated as *const W as *mut u32,
|
||||
count,
|
||||
false,
|
||||
W::size(),
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
unsafe fn new_inner(
|
||||
channel: PeripheralRef<'a, C>,
|
||||
request: Request,
|
||||
dir: Dir,
|
||||
peri_addr: *const u32,
|
||||
@ -176,24 +238,19 @@ mod low_level_api {
|
||||
mem_len: usize,
|
||||
incr_mem: bool,
|
||||
data_size: WordSize,
|
||||
options: TransferOptions,
|
||||
) {
|
||||
assert!(options.mburst == crate::dma::Burst::Single, "Burst mode not supported");
|
||||
assert!(options.pburst == crate::dma::Burst::Single, "Burst mode not supported");
|
||||
assert!(
|
||||
options.flow_ctrl == crate::dma::FlowControl::Dma,
|
||||
"Peripheral flow control not supported"
|
||||
);
|
||||
assert!(options.fifo_threshold.is_none(), "FIFO mode not supported");
|
||||
_options: TransferOptions,
|
||||
) -> Self {
|
||||
let ch = channel.regs().ch(channel.num());
|
||||
|
||||
// "Preceding reads and writes cannot be moved past subsequent writes."
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
let ch = dma.ch(channel_number as _);
|
||||
let this = Self { channel };
|
||||
|
||||
#[cfg(dmamux)]
|
||||
super::dmamux::configure_dmamux(&mut *this.channel, request);
|
||||
|
||||
// Reset ch
|
||||
ch.cr().write(|w| w.set_reset(true));
|
||||
|
||||
ch.llr().write(|_| {}); // no linked list
|
||||
ch.tr1().write(|w| {
|
||||
w.set_sdw(data_size.into());
|
||||
@ -234,72 +291,66 @@ mod low_level_api {
|
||||
// Start it
|
||||
w.set_en(true);
|
||||
});
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Stops the DMA channel.
|
||||
pub unsafe fn request_stop(dma: Gpdma, channel_number: u8) {
|
||||
// get a handle on the channel itself
|
||||
let ch = dma.ch(channel_number as _);
|
||||
pub fn request_stop(&mut self) {
|
||||
let ch = self.channel.regs().ch(self.channel.num());
|
||||
|
||||
// Disable the channel. Keep the IEs enabled so the irqs still fire.
|
||||
ch.cr().write(|w| {
|
||||
w.set_tcie(true);
|
||||
w.set_useie(true);
|
||||
w.set_dteie(true);
|
||||
w.set_suspie(true);
|
||||
});
|
||||
|
||||
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
|
||||
fence(Ordering::SeqCst);
|
||||
unsafe {
|
||||
ch.cr().write(|w| {
|
||||
w.set_tcie(true);
|
||||
w.set_useie(true);
|
||||
w.set_dteie(true);
|
||||
w.set_suspie(true);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the running status of the channel
|
||||
pub unsafe fn is_running(dma: Gpdma, ch: u8) -> bool {
|
||||
let ch = dma.ch(ch as _);
|
||||
!ch.sr().read().tcf()
|
||||
pub fn is_running(&mut self) -> bool {
|
||||
let ch = self.channel.regs().ch(self.channel.num());
|
||||
!unsafe { ch.sr().read() }.tcf()
|
||||
}
|
||||
|
||||
/// Gets the total remaining transfers for the channel
|
||||
/// Note: this will be zero for transfers that completed without cancellation.
|
||||
pub unsafe fn get_remaining_transfers(dma: Gpdma, ch: u8) -> u16 {
|
||||
// get a handle on the channel itself
|
||||
let ch = dma.ch(ch as _);
|
||||
// read the remaining transfer count. If this is zero, the transfer completed fully.
|
||||
ch.br1().read().bndt()
|
||||
pub fn get_remaining_transfers(&self) -> u16 {
|
||||
let ch = self.channel.regs().ch(self.channel.num());
|
||||
unsafe { ch.br1().read() }.bndt()
|
||||
}
|
||||
|
||||
/// Sets the waker for the specified DMA channel
|
||||
pub unsafe fn set_waker(state_number: usize, waker: &Waker) {
|
||||
STATE.channels[state_number].waker.register(waker);
|
||||
pub fn blocking_wait(mut self) {
|
||||
while self.is_running() {}
|
||||
|
||||
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
core::mem::forget(self);
|
||||
}
|
||||
}
|
||||
|
||||
/// Safety: Must be called with a matching set of parameters for a valid dma channel
|
||||
pub unsafe fn on_irq_inner(dma: Gpdma, channel_num: u8, state_index: u8) {
|
||||
let channel_num = channel_num as usize;
|
||||
let state_index = state_index as usize;
|
||||
impl<'a, C: Channel> Drop for Transfer<'a, C> {
|
||||
fn drop(&mut self) {
|
||||
self.request_stop();
|
||||
while self.is_running() {}
|
||||
|
||||
let ch = dma.ch(channel_num);
|
||||
let sr = ch.sr().read();
|
||||
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
|
||||
fence(Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
if sr.dtef() {
|
||||
panic!(
|
||||
"DMA: data transfer error on DMA@{:08x} channel {}",
|
||||
dma.0 as u32, channel_num
|
||||
);
|
||||
}
|
||||
if sr.usef() {
|
||||
panic!(
|
||||
"DMA: user settings error on DMA@{:08x} channel {}",
|
||||
dma.0 as u32, channel_num
|
||||
);
|
||||
}
|
||||
impl<'a, C: Channel> Unpin for Transfer<'a, C> {}
|
||||
impl<'a, C: Channel> Future for Transfer<'a, C> {
|
||||
type Output = ();
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
STATE.ch_wakers[self.channel.index()].register(cx.waker());
|
||||
|
||||
if sr.suspf() || sr.tcf() {
|
||||
// disable all xxIEs to prevent the irq from firing again.
|
||||
ch.cr().write(|_| {});
|
||||
|
||||
// Wake the future. It'll look at tcf and see it's set.
|
||||
STATE.channels[state_index].waker.wake();
|
||||
if self.is_running() {
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,329 +1,48 @@
|
||||
#[cfg(bdma)]
|
||||
pub(crate) mod bdma;
|
||||
#[cfg(dma)]
|
||||
pub(crate) mod dma;
|
||||
#[cfg(dma)]
|
||||
pub use dma::*;
|
||||
|
||||
// stm32h7 has both dma and bdma. In that case, we export dma as "main" dma,
|
||||
// and bdma as "secondary", under `embassy_stm32::dma::bdma`.
|
||||
#[cfg(all(bdma, dma))]
|
||||
pub mod bdma;
|
||||
|
||||
#[cfg(all(bdma, not(dma)))]
|
||||
pub(crate) mod bdma;
|
||||
#[cfg(all(bdma, not(dma)))]
|
||||
pub use bdma::*;
|
||||
|
||||
#[cfg(gpdma)]
|
||||
pub(crate) mod gpdma;
|
||||
#[cfg(gpdma)]
|
||||
pub use gpdma::*;
|
||||
|
||||
#[cfg(dmamux)]
|
||||
mod dmamux;
|
||||
#[cfg(gpdma)]
|
||||
mod gpdma;
|
||||
|
||||
use core::future::Future;
|
||||
pub(crate) mod ringbuffer;
|
||||
pub mod word;
|
||||
|
||||
use core::mem;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll, Waker};
|
||||
|
||||
#[cfg(any(dma, bdma))]
|
||||
use embassy_cortex_m::interrupt::Priority;
|
||||
use embassy_hal_common::{impl_peripheral, into_ref};
|
||||
use embassy_hal_common::impl_peripheral;
|
||||
|
||||
#[cfg(dmamux)]
|
||||
pub use self::dmamux::*;
|
||||
use crate::Peripheral;
|
||||
|
||||
#[cfg(feature = "unstable-pac")]
|
||||
pub mod low_level {
|
||||
pub use super::transfers::*;
|
||||
}
|
||||
|
||||
pub(crate) use transfers::*;
|
||||
|
||||
#[cfg(any(bdma_v2, dma_v2, dmamux, gpdma))]
|
||||
pub type Request = u8;
|
||||
#[cfg(not(any(bdma_v2, dma_v2, dmamux, gpdma)))]
|
||||
pub type Request = ();
|
||||
|
||||
pub(crate) mod sealed {
|
||||
use super::*;
|
||||
|
||||
pub trait Word {}
|
||||
|
||||
pub trait Channel {
|
||||
/// Starts this channel for writing a stream of words.
|
||||
///
|
||||
/// Safety:
|
||||
/// - `buf` must point to a valid buffer for DMA reading.
|
||||
/// - `buf` must be alive for the entire duration of the DMA transfer.
|
||||
/// - `reg_addr` must be a valid peripheral register address to write to.
|
||||
unsafe fn start_write<W: super::Word>(
|
||||
&mut self,
|
||||
request: Request,
|
||||
buf: *const [W],
|
||||
reg_addr: *mut W,
|
||||
options: TransferOptions,
|
||||
);
|
||||
|
||||
/// Starts this channel for writing a word repeatedly.
|
||||
///
|
||||
/// Safety:
|
||||
/// - `reg_addr` must be a valid peripheral register address to write to.
|
||||
unsafe fn start_write_repeated<W: super::Word>(
|
||||
&mut self,
|
||||
request: Request,
|
||||
repeated: *const W,
|
||||
count: usize,
|
||||
reg_addr: *mut W,
|
||||
options: TransferOptions,
|
||||
);
|
||||
|
||||
/// Starts this channel for reading a stream of words.
|
||||
///
|
||||
/// Safety:
|
||||
/// - `buf` must point to a valid buffer for DMA writing.
|
||||
/// - `buf` must be alive for the entire duration of the DMA transfer.
|
||||
/// - `reg_addr` must be a valid peripheral register address to read from.
|
||||
unsafe fn start_read<W: super::Word>(
|
||||
&mut self,
|
||||
request: Request,
|
||||
reg_addr: *const W,
|
||||
buf: *mut [W],
|
||||
options: TransferOptions,
|
||||
);
|
||||
|
||||
/// DMA double-buffered mode is unsafe as UB can happen when the hardware writes to a buffer currently owned by the software
|
||||
/// more information can be found here: https://github.com/embassy-rs/embassy/issues/702
|
||||
/// This feature is now used solely for the purposes of implementing giant DMA transfers required for DCMI
|
||||
unsafe fn start_double_buffered_read<W: super::Word>(
|
||||
&mut self,
|
||||
request: Request,
|
||||
reg_addr: *const W,
|
||||
buffer0: *mut W,
|
||||
buffer1: *mut W,
|
||||
buffer_len: usize,
|
||||
options: TransferOptions,
|
||||
);
|
||||
|
||||
unsafe fn set_buffer0<W: super::Word>(&mut self, buffer: *mut W);
|
||||
|
||||
unsafe fn set_buffer1<W: super::Word>(&mut self, buffer: *mut W);
|
||||
|
||||
unsafe fn is_buffer0_accessible(&mut self) -> bool;
|
||||
|
||||
/// Requests the channel to stop.
|
||||
/// NOTE: The channel does not immediately stop, you have to wait
|
||||
/// for `is_running() = false`.
|
||||
fn request_stop(&mut self);
|
||||
|
||||
/// Returns whether this channel is running or stopped.
|
||||
///
|
||||
/// The channel stops running when it either completes or is manually stopped.
|
||||
fn is_running(&self) -> bool;
|
||||
|
||||
/// Returns the total number of remaining transfers.
|
||||
fn remaining_transfers(&mut self) -> u16;
|
||||
|
||||
/// Sets the waker that is called when this channel stops (either completed or manually stopped)
|
||||
fn set_waker(&mut self, waker: &Waker);
|
||||
|
||||
/// This is called when this channel triggers an interrupt.
|
||||
/// Note: Because some channels share an interrupt, this function might be
|
||||
/// called for a channel that didn't trigger an interrupt.
|
||||
fn on_irq();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum WordSize {
|
||||
OneByte,
|
||||
TwoBytes,
|
||||
FourBytes,
|
||||
enum Dir {
|
||||
MemoryToPeripheral,
|
||||
PeripheralToMemory,
|
||||
}
|
||||
|
||||
impl WordSize {
|
||||
pub fn bytes(&self) -> usize {
|
||||
match self {
|
||||
Self::OneByte => 1,
|
||||
Self::TwoBytes => 2,
|
||||
Self::FourBytes => 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Word: sealed::Word {
|
||||
fn bits() -> WordSize;
|
||||
}
|
||||
|
||||
impl sealed::Word for u8 {}
|
||||
impl Word for u8 {
|
||||
fn bits() -> WordSize {
|
||||
WordSize::OneByte
|
||||
}
|
||||
}
|
||||
|
||||
impl sealed::Word for u16 {}
|
||||
impl Word for u16 {
|
||||
fn bits() -> WordSize {
|
||||
WordSize::TwoBytes
|
||||
}
|
||||
}
|
||||
|
||||
impl sealed::Word for u32 {}
|
||||
impl Word for u32 {
|
||||
fn bits() -> WordSize {
|
||||
WordSize::FourBytes
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Burst {
|
||||
/// Single transfer
|
||||
Single,
|
||||
/// Incremental burst of 4 beats
|
||||
Incr4,
|
||||
/// Incremental burst of 8 beats
|
||||
Incr8,
|
||||
/// Incremental burst of 16 beats
|
||||
Incr16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum FlowControl {
|
||||
/// Flow control by DMA
|
||||
Dma,
|
||||
/// Flow control by peripheral
|
||||
Peripheral,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum FifoThreshold {
|
||||
/// 1/4 full FIFO
|
||||
Quarter,
|
||||
/// 1/2 full FIFO
|
||||
Half,
|
||||
/// 3/4 full FIFO
|
||||
ThreeQuarters,
|
||||
/// Full FIFO
|
||||
Full,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct TransferOptions {
|
||||
/// Peripheral burst transfer configuration
|
||||
pub pburst: Burst,
|
||||
/// Memory burst transfer configuration
|
||||
pub mburst: Burst,
|
||||
/// Flow control configuration
|
||||
pub flow_ctrl: FlowControl,
|
||||
/// FIFO threshold for DMA FIFO mode. If none, direct mode is used.
|
||||
pub fifo_threshold: Option<FifoThreshold>,
|
||||
}
|
||||
|
||||
impl Default for TransferOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pburst: Burst::Single,
|
||||
mburst: Burst::Single,
|
||||
flow_ctrl: FlowControl::Dma,
|
||||
fifo_threshold: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod transfers {
|
||||
use embassy_hal_common::PeripheralRef;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn read<'a, W: Word>(
|
||||
channel: impl Peripheral<P = impl Channel> + 'a,
|
||||
request: Request,
|
||||
reg_addr: *mut W,
|
||||
buf: &'a mut [W],
|
||||
) -> impl Future<Output = ()> + 'a {
|
||||
assert!(buf.len() > 0 && buf.len() <= 0xFFFF);
|
||||
into_ref!(channel);
|
||||
|
||||
unsafe { channel.start_read::<W>(request, reg_addr, buf, Default::default()) };
|
||||
|
||||
Transfer::new(channel)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn write<'a, W: Word>(
|
||||
channel: impl Peripheral<P = impl Channel> + 'a,
|
||||
request: Request,
|
||||
buf: &'a [W],
|
||||
reg_addr: *mut W,
|
||||
) -> impl Future<Output = ()> + 'a {
|
||||
assert!(buf.len() > 0 && buf.len() <= 0xFFFF);
|
||||
into_ref!(channel);
|
||||
|
||||
unsafe { channel.start_write::<W>(request, buf, reg_addr, Default::default()) };
|
||||
|
||||
Transfer::new(channel)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn write_repeated<'a, W: Word>(
|
||||
channel: impl Peripheral<P = impl Channel> + 'a,
|
||||
request: Request,
|
||||
repeated: *const W,
|
||||
count: usize,
|
||||
reg_addr: *mut W,
|
||||
) -> impl Future<Output = ()> + 'a {
|
||||
into_ref!(channel);
|
||||
|
||||
unsafe { channel.start_write_repeated::<W>(request, repeated, count, reg_addr, Default::default()) };
|
||||
|
||||
Transfer::new(channel)
|
||||
}
|
||||
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub(crate) 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) {
|
||||
self.channel.request_stop();
|
||||
while self.channel.is_running() {}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, C: Channel> Unpin for Transfer<'a, C> {}
|
||||
impl<'a, C: Channel> Future for Transfer<'a, C> {
|
||||
type Output = ();
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
self.channel.set_waker(cx.waker());
|
||||
if self.channel.is_running() {
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static {}
|
||||
|
||||
pub struct NoDma;
|
||||
|
||||
impl_peripheral!(NoDma);
|
||||
|
||||
// safety: must be called only once at startup
|
||||
pub(crate) unsafe fn init(#[cfg(bdma)] bdma_priority: Priority, #[cfg(dma)] dma_priority: Priority) {
|
||||
#[cfg(bdma)]
|
||||
bdma::init(bdma_priority);
|
||||
#[cfg(dma)]
|
||||
dma::init(dma_priority);
|
||||
#[cfg(dmamux)]
|
||||
dmamux::init();
|
||||
#[cfg(gpdma)]
|
||||
gpdma::init();
|
||||
}
|
||||
|
||||
// TODO: replace transmutes with core::ptr::metadata once it's stable
|
||||
#[allow(unused)]
|
||||
pub(crate) fn slice_ptr_parts<T>(slice: *const [T]) -> (usize, usize) {
|
||||
@ -334,3 +53,19 @@ pub(crate) fn slice_ptr_parts<T>(slice: *const [T]) -> (usize, usize) {
|
||||
pub(crate) fn slice_ptr_parts_mut<T>(slice: *mut [T]) -> (usize, usize) {
|
||||
unsafe { mem::transmute(slice) }
|
||||
}
|
||||
|
||||
// safety: must be called only once at startup
|
||||
pub(crate) unsafe fn init(
|
||||
#[cfg(bdma)] bdma_priority: Priority,
|
||||
#[cfg(dma)] dma_priority: Priority,
|
||||
#[cfg(gpdma)] gpdma_priority: Priority,
|
||||
) {
|
||||
#[cfg(bdma)]
|
||||
bdma::init(bdma_priority);
|
||||
#[cfg(dma)]
|
||||
dma::init(dma_priority);
|
||||
#[cfg(gpdma)]
|
||||
gpdma::init(gpdma_priority);
|
||||
#[cfg(dmamux)]
|
||||
dmamux::init();
|
||||
}
|
||||
|
420
embassy-stm32/src/dma/ringbuffer.rs
Normal file
420
embassy-stm32/src/dma/ringbuffer.rs
Normal file
@ -0,0 +1,420 @@
|
||||
#![cfg_attr(gpdma, allow(unused))]
|
||||
|
||||
use core::ops::Range;
|
||||
use core::sync::atomic::{compiler_fence, Ordering};
|
||||
|
||||
use super::word::Word;
|
||||
|
||||
/// A "read-only" ring-buffer to be used together with the DMA controller which
|
||||
/// writes in a circular way, "uncontrolled" to the buffer.
|
||||
///
|
||||
/// A snapshot of the ring buffer state can be attained by setting the `ndtr` field
|
||||
/// to the current register value. `ndtr` describes the current position of the DMA
|
||||
/// write.
|
||||
///
|
||||
/// # Buffer layout
|
||||
///
|
||||
/// ```text
|
||||
/// Without wraparound: With wraparound:
|
||||
///
|
||||
/// + buf +--- NDTR ---+ + buf +---------- NDTR ----------+
|
||||
/// | | | | | |
|
||||
/// v v v v v v
|
||||
/// +-----------------------------------------+ +-----------------------------------------+
|
||||
/// |oooooooooooXXXXXXXXXXXXXXXXoooooooooooooo| |XXXXXXXXXXXXXooooooooooooXXXXXXXXXXXXXXXX|
|
||||
/// +-----------------------------------------+ +-----------------------------------------+
|
||||
/// ^ ^ ^ ^ ^ ^
|
||||
/// | | | | | |
|
||||
/// +- first --+ | +- end ------+ |
|
||||
/// | | | |
|
||||
/// +- end --------------------+ +- first ----------------+
|
||||
/// ```
|
||||
pub struct DmaRingBuffer<'a, W: Word> {
|
||||
pub(crate) dma_buf: &'a mut [W],
|
||||
first: usize,
|
||||
pub ndtr: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct OverrunError;
|
||||
|
||||
pub trait DmaCtrl {
|
||||
/// Get the NDTR register value, i.e. the space left in the underlying
|
||||
/// buffer until the dma writer wraps.
|
||||
fn ndtr(&self) -> usize;
|
||||
|
||||
/// Get the transfer completed counter.
|
||||
/// This counter is incremented by the dma controller when NDTR is reloaded,
|
||||
/// i.e. when the writing wraps.
|
||||
fn get_complete_count(&self) -> usize;
|
||||
|
||||
/// Reset the transfer completed counter to 0 and return the value just prior to the reset.
|
||||
fn reset_complete_count(&mut self) -> usize;
|
||||
}
|
||||
|
||||
impl<'a, W: Word> DmaRingBuffer<'a, W> {
|
||||
pub fn new(dma_buf: &'a mut [W]) -> Self {
|
||||
let ndtr = dma_buf.len();
|
||||
Self {
|
||||
dma_buf,
|
||||
first: 0,
|
||||
ndtr,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the ring buffer to its initial state
|
||||
pub fn clear(&mut self, mut dma: impl DmaCtrl) {
|
||||
self.first = 0;
|
||||
self.ndtr = self.dma_buf.len();
|
||||
dma.reset_complete_count();
|
||||
}
|
||||
|
||||
/// The buffer end position
|
||||
fn end(&self) -> usize {
|
||||
self.dma_buf.len() - self.ndtr
|
||||
}
|
||||
|
||||
/// Returns whether the buffer is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.first == self.end()
|
||||
}
|
||||
|
||||
/// The current number of bytes in the buffer
|
||||
/// This may change at any time if dma is currently active
|
||||
pub fn len(&self) -> usize {
|
||||
// Read out a stable end (the dma periheral can change it at anytime)
|
||||
let end = self.end();
|
||||
if self.first <= end {
|
||||
// No wrap
|
||||
end - self.first
|
||||
} else {
|
||||
self.dma_buf.len() - self.first + end
|
||||
}
|
||||
}
|
||||
|
||||
/// Read bytes from the ring buffer
|
||||
/// OverrunError is returned if the portion to be read was overwritten by the DMA controller.
|
||||
pub fn read(&mut self, mut dma: impl DmaCtrl, buf: &mut [W]) -> Result<usize, OverrunError> {
|
||||
let end = self.end();
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
if self.first == end {
|
||||
// The buffer is currently empty
|
||||
|
||||
if dma.get_complete_count() > 0 {
|
||||
// The DMA has written such that the ring buffer wraps at least once
|
||||
self.ndtr = dma.ndtr();
|
||||
if self.end() > self.first || dma.get_complete_count() > 1 {
|
||||
return Err(OverrunError);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(0)
|
||||
} else if self.first < end {
|
||||
// The available, unread portion in the ring buffer DOES NOT wrap
|
||||
|
||||
if dma.get_complete_count() > 1 {
|
||||
return Err(OverrunError);
|
||||
}
|
||||
|
||||
// Copy out the bytes from the dma buffer
|
||||
let len = self.copy_to(buf, self.first..end);
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
match dma.get_complete_count() {
|
||||
0 => {
|
||||
// The DMA writer has not wrapped before nor after the copy
|
||||
}
|
||||
1 => {
|
||||
// The DMA writer has written such that the ring buffer now wraps
|
||||
self.ndtr = dma.ndtr();
|
||||
if self.end() > self.first || dma.get_complete_count() > 1 {
|
||||
// The bytes that we have copied out have overflowed
|
||||
// as the writer has now both wrapped and is currently writing
|
||||
// within the region that we have just copied out
|
||||
return Err(OverrunError);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(OverrunError);
|
||||
}
|
||||
}
|
||||
|
||||
self.first = (self.first + len) % self.dma_buf.len();
|
||||
Ok(len)
|
||||
} else {
|
||||
// The available, unread portion in the ring buffer DOES wrap
|
||||
// The DMA writer has wrapped since we last read and is currently
|
||||
// writing (or the next byte added will be) in the beginning of the ring buffer.
|
||||
|
||||
let complete_count = dma.get_complete_count();
|
||||
if complete_count > 1 {
|
||||
return Err(OverrunError);
|
||||
}
|
||||
|
||||
// If the unread portion wraps then the writer must also have wrapped
|
||||
assert!(complete_count == 1);
|
||||
|
||||
if self.first + buf.len() < self.dma_buf.len() {
|
||||
// The provided read buffer is not large enough to include all bytes from the tail of the dma buffer.
|
||||
|
||||
// Copy out from the dma buffer
|
||||
let len = self.copy_to(buf, self.first..self.dma_buf.len());
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
// We have now copied out the data from dma_buf
|
||||
// Make sure that the just read part was not overwritten during the copy
|
||||
self.ndtr = dma.ndtr();
|
||||
if self.end() > self.first || dma.get_complete_count() > 1 {
|
||||
// The writer has entered the data that we have just read since we read out `end` in the beginning and until now.
|
||||
return Err(OverrunError);
|
||||
}
|
||||
|
||||
self.first = (self.first + len) % self.dma_buf.len();
|
||||
Ok(len)
|
||||
} else {
|
||||
// The provided read buffer is large enough to include all bytes from the tail of the dma buffer,
|
||||
// so the next read will not have any unread tail bytes in the ring buffer.
|
||||
|
||||
// Copy out from the dma buffer
|
||||
let tail = self.copy_to(buf, self.first..self.dma_buf.len());
|
||||
let head = self.copy_to(&mut buf[tail..], 0..end);
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
// We have now copied out the data from dma_buf
|
||||
// Reset complete counter and make sure that the just read part was not overwritten during the copy
|
||||
self.ndtr = dma.ndtr();
|
||||
let complete_count = dma.reset_complete_count();
|
||||
if self.end() > self.first || complete_count > 1 {
|
||||
return Err(OverrunError);
|
||||
}
|
||||
|
||||
self.first = head;
|
||||
Ok(tail + head)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy from the dma buffer at `data_range` into `buf`
|
||||
fn copy_to(&mut self, buf: &mut [W], data_range: Range<usize>) -> usize {
|
||||
// Limit the number of bytes that can be copied
|
||||
let length = usize::min(data_range.len(), buf.len());
|
||||
|
||||
// Copy from dma buffer into read buffer
|
||||
// We need to do it like this instead of a simple copy_from_slice() because
|
||||
// reading from a part of memory that may be simultaneously written to is unsafe
|
||||
unsafe {
|
||||
let dma_buf = self.dma_buf.as_ptr();
|
||||
|
||||
for i in 0..length {
|
||||
buf[i] = core::ptr::read_volatile(dma_buf.offset((data_range.start + i) as isize));
|
||||
}
|
||||
}
|
||||
|
||||
length
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::array;
|
||||
use core::cell::RefCell;
|
||||
|
||||
use super::*;
|
||||
|
||||
struct TestCtrl {
|
||||
next_ndtr: RefCell<Option<usize>>,
|
||||
complete_count: usize,
|
||||
}
|
||||
|
||||
impl TestCtrl {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
next_ndtr: RefCell::new(None),
|
||||
complete_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_next_ndtr(&mut self, ndtr: usize) {
|
||||
self.next_ndtr.borrow_mut().replace(ndtr);
|
||||
}
|
||||
}
|
||||
|
||||
impl DmaCtrl for &mut TestCtrl {
|
||||
fn ndtr(&self) -> usize {
|
||||
self.next_ndtr.borrow_mut().unwrap()
|
||||
}
|
||||
|
||||
fn get_complete_count(&self) -> usize {
|
||||
self.complete_count
|
||||
}
|
||||
|
||||
fn reset_complete_count(&mut self) -> usize {
|
||||
let old = self.complete_count;
|
||||
self.complete_count = 0;
|
||||
old
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
let mut dma_buf = [0u8; 16];
|
||||
let ringbuf = DmaRingBuffer::new(&mut dma_buf);
|
||||
|
||||
assert!(ringbuf.is_empty());
|
||||
assert_eq!(0, ringbuf.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_read() {
|
||||
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
|
||||
let mut ctrl = TestCtrl::new();
|
||||
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
|
||||
ringbuf.ndtr = 6;
|
||||
|
||||
assert!(!ringbuf.is_empty());
|
||||
assert_eq!(10, ringbuf.len());
|
||||
|
||||
let mut buf = [0; 2];
|
||||
assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap());
|
||||
assert_eq!([0, 1], buf);
|
||||
assert_eq!(8, ringbuf.len());
|
||||
|
||||
let mut buf = [0; 2];
|
||||
assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap());
|
||||
assert_eq!([2, 3], buf);
|
||||
assert_eq!(6, ringbuf.len());
|
||||
|
||||
let mut buf = [0; 8];
|
||||
assert_eq!(6, ringbuf.read(&mut ctrl, &mut buf).unwrap());
|
||||
assert_eq!([4, 5, 6, 7, 8, 9], buf[..6]);
|
||||
assert_eq!(0, ringbuf.len());
|
||||
|
||||
let mut buf = [0; 2];
|
||||
assert_eq!(0, ringbuf.read(&mut ctrl, &mut buf).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_read_with_wrap() {
|
||||
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
|
||||
let mut ctrl = TestCtrl::new();
|
||||
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
|
||||
ringbuf.first = 12;
|
||||
ringbuf.ndtr = 10;
|
||||
|
||||
// The dma controller has written 4 + 6 bytes and has reloaded NDTR
|
||||
ctrl.complete_count = 1;
|
||||
ctrl.set_next_ndtr(10);
|
||||
|
||||
assert!(!ringbuf.is_empty());
|
||||
assert_eq!(6 + 4, ringbuf.len());
|
||||
|
||||
let mut buf = [0; 2];
|
||||
assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap());
|
||||
assert_eq!([12, 13], buf);
|
||||
assert_eq!(6 + 2, ringbuf.len());
|
||||
|
||||
let mut buf = [0; 4];
|
||||
assert_eq!(4, ringbuf.read(&mut ctrl, &mut buf).unwrap());
|
||||
assert_eq!([14, 15, 0, 1], buf);
|
||||
assert_eq!(4, ringbuf.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_read_when_dma_writer_is_wrapped_and_read_does_not_wrap() {
|
||||
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
|
||||
let mut ctrl = TestCtrl::new();
|
||||
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
|
||||
ringbuf.first = 2;
|
||||
ringbuf.ndtr = 6;
|
||||
|
||||
// The dma controller has written 6 + 2 bytes and has reloaded NDTR
|
||||
ctrl.complete_count = 1;
|
||||
ctrl.set_next_ndtr(14);
|
||||
|
||||
let mut buf = [0; 2];
|
||||
assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap());
|
||||
assert_eq!([2, 3], buf);
|
||||
|
||||
assert_eq!(1, ctrl.complete_count); // The interrupt flag IS NOT cleared
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_read_when_dma_writer_is_wrapped_and_read_wraps() {
|
||||
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
|
||||
let mut ctrl = TestCtrl::new();
|
||||
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
|
||||
ringbuf.first = 12;
|
||||
ringbuf.ndtr = 10;
|
||||
|
||||
// The dma controller has written 6 + 2 bytes and has reloaded NDTR
|
||||
ctrl.complete_count = 1;
|
||||
ctrl.set_next_ndtr(14);
|
||||
|
||||
let mut buf = [0; 10];
|
||||
assert_eq!(10, ringbuf.read(&mut ctrl, &mut buf).unwrap());
|
||||
assert_eq!([12, 13, 14, 15, 0, 1, 2, 3, 4, 5], buf);
|
||||
|
||||
assert_eq!(0, ctrl.complete_count); // The interrupt flag IS cleared
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_read_when_dma_writer_wraps_with_same_ndtr() {
|
||||
let mut dma_buf = [0u8; 16];
|
||||
let mut ctrl = TestCtrl::new();
|
||||
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
|
||||
ringbuf.first = 6;
|
||||
ringbuf.ndtr = 10;
|
||||
ctrl.set_next_ndtr(9);
|
||||
|
||||
assert!(ringbuf.is_empty()); // The ring buffer thinks that it is empty
|
||||
|
||||
// The dma controller has written exactly 16 bytes
|
||||
ctrl.complete_count = 1;
|
||||
|
||||
let mut buf = [0; 2];
|
||||
assert_eq!(Err(OverrunError), ringbuf.read(&mut ctrl, &mut buf));
|
||||
|
||||
assert_eq!(1, ctrl.complete_count); // The complete counter is not reset
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_read_when_dma_writer_overwrites_during_not_wrapping_read() {
|
||||
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
|
||||
let mut ctrl = TestCtrl::new();
|
||||
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
|
||||
ringbuf.first = 2;
|
||||
ringbuf.ndtr = 6;
|
||||
|
||||
// The dma controller has written 6 + 3 bytes and has reloaded NDTR
|
||||
ctrl.complete_count = 1;
|
||||
ctrl.set_next_ndtr(13);
|
||||
|
||||
let mut buf = [0; 2];
|
||||
assert_eq!(Err(OverrunError), ringbuf.read(&mut ctrl, &mut buf));
|
||||
|
||||
assert_eq!(1, ctrl.complete_count); // The complete counter is not reset
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_read_when_dma_writer_overwrites_during_wrapping_read() {
|
||||
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
|
||||
let mut ctrl = TestCtrl::new();
|
||||
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
|
||||
ringbuf.first = 12;
|
||||
ringbuf.ndtr = 10;
|
||||
|
||||
// The dma controller has written 6 + 13 bytes and has reloaded NDTR
|
||||
ctrl.complete_count = 1;
|
||||
ctrl.set_next_ndtr(3);
|
||||
|
||||
let mut buf = [0; 2];
|
||||
assert_eq!(Err(OverrunError), ringbuf.read(&mut ctrl, &mut buf));
|
||||
|
||||
assert_eq!(1, ctrl.complete_count); // The complete counter is not reset
|
||||
}
|
||||
}
|
79
embassy-stm32/src/dma/word.rs
Normal file
79
embassy-stm32/src/dma/word.rs
Normal file
@ -0,0 +1,79 @@
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum WordSize {
|
||||
OneByte,
|
||||
TwoBytes,
|
||||
FourBytes,
|
||||
}
|
||||
|
||||
impl WordSize {
|
||||
pub fn bytes(&self) -> usize {
|
||||
match self {
|
||||
Self::OneByte => 1,
|
||||
Self::TwoBytes => 2,
|
||||
Self::FourBytes => 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod sealed {
|
||||
pub trait Word {}
|
||||
}
|
||||
|
||||
pub trait Word: sealed::Word + Default + Copy + 'static {
|
||||
fn size() -> WordSize;
|
||||
fn bits() -> usize;
|
||||
}
|
||||
|
||||
macro_rules! impl_word {
|
||||
(_, $T:ident, $bits:literal, $size:ident) => {
|
||||
impl sealed::Word for $T {}
|
||||
impl Word for $T {
|
||||
fn bits() -> usize {
|
||||
$bits
|
||||
}
|
||||
fn size() -> WordSize {
|
||||
WordSize::$size
|
||||
}
|
||||
}
|
||||
};
|
||||
($T:ident, $uX:ident, $bits:literal, $size:ident) => {
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct $T(pub $uX);
|
||||
impl_word!(_, $T, $bits, $size);
|
||||
};
|
||||
}
|
||||
|
||||
impl_word!(U1, u8, 1, OneByte);
|
||||
impl_word!(U2, u8, 2, OneByte);
|
||||
impl_word!(U3, u8, 3, OneByte);
|
||||
impl_word!(U4, u8, 4, OneByte);
|
||||
impl_word!(U5, u8, 5, OneByte);
|
||||
impl_word!(U6, u8, 6, OneByte);
|
||||
impl_word!(U7, u8, 7, OneByte);
|
||||
impl_word!(_, u8, 8, OneByte);
|
||||
impl_word!(U9, u16, 9, TwoBytes);
|
||||
impl_word!(U10, u16, 10, TwoBytes);
|
||||
impl_word!(U11, u16, 11, TwoBytes);
|
||||
impl_word!(U12, u16, 12, TwoBytes);
|
||||
impl_word!(U13, u16, 13, TwoBytes);
|
||||
impl_word!(U14, u16, 14, TwoBytes);
|
||||
impl_word!(U15, u16, 15, TwoBytes);
|
||||
impl_word!(_, u16, 16, TwoBytes);
|
||||
impl_word!(U17, u32, 17, FourBytes);
|
||||
impl_word!(U18, u32, 18, FourBytes);
|
||||
impl_word!(U19, u32, 19, FourBytes);
|
||||
impl_word!(U20, u32, 20, FourBytes);
|
||||
impl_word!(U21, u32, 21, FourBytes);
|
||||
impl_word!(U22, u32, 22, FourBytes);
|
||||
impl_word!(U23, u32, 23, FourBytes);
|
||||
impl_word!(U24, u32, 24, FourBytes);
|
||||
impl_word!(U25, u32, 25, FourBytes);
|
||||
impl_word!(U26, u32, 26, FourBytes);
|
||||
impl_word!(U27, u32, 27, FourBytes);
|
||||
impl_word!(U28, u32, 28, FourBytes);
|
||||
impl_word!(U29, u32, 29, FourBytes);
|
||||
impl_word!(U30, u32, 30, FourBytes);
|
||||
impl_word!(U31, u32, 31, FourBytes);
|
||||
impl_word!(_, u32, 32, FourBytes);
|
@ -2,7 +2,7 @@ use atomic_polyfill::{fence, Ordering};
|
||||
use embassy_hal_common::drop::OnDrop;
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
|
||||
use super::{family, Error, FlashLayout, FlashRegion, FlashSector, FLASH_BASE, FLASH_SIZE, WRITE_SIZE};
|
||||
use super::{family, Error, FlashLayout, FlashRegion, FlashSector, FLASH_BASE, FLASH_SIZE, MAX_ERASE_SIZE, WRITE_SIZE};
|
||||
use crate::flash::FlashBank;
|
||||
use crate::Peripheral;
|
||||
|
||||
@ -162,6 +162,35 @@ impl FlashRegion {
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_storage::nor_flash::ErrorType for Flash<'_> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl embedded_storage::nor_flash::ReadNorFlash for Flash<'_> {
|
||||
const READ_SIZE: usize = 1;
|
||||
|
||||
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_read(offset, bytes)
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
FLASH_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_storage::nor_flash::NorFlash for Flash<'_> {
|
||||
const WRITE_SIZE: usize = WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = MAX_ERASE_SIZE;
|
||||
|
||||
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_write(offset, bytes)
|
||||
}
|
||||
|
||||
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
self.blocking_erase(from, to)
|
||||
}
|
||||
}
|
||||
|
||||
foreach_flash_region! {
|
||||
($type_name:ident, $write_size:literal, $erase_size:literal) => {
|
||||
impl crate::_generated::flash_regions::$type_name<'_> {
|
||||
|
@ -7,6 +7,7 @@ mod common;
|
||||
pub use common::*;
|
||||
|
||||
pub use crate::_generated::flash_regions::*;
|
||||
pub use crate::_generated::MAX_ERASE_SIZE;
|
||||
pub use crate::pac::{FLASH_BASE, FLASH_SIZE, WRITE_SIZE};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -1,6 +1,5 @@
|
||||
use core::cmp;
|
||||
use core::future::poll_fn;
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_embedded_hal::SetConfig;
|
||||
@ -8,7 +7,7 @@ use embassy_hal_common::drop::OnDrop;
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
use crate::dma::NoDma;
|
||||
use crate::dma::{NoDma, Transfer};
|
||||
use crate::gpio::sealed::AFType;
|
||||
use crate::gpio::Pull;
|
||||
use crate::i2c::{Error, Instance, SclPin, SdaPin};
|
||||
@ -35,14 +34,12 @@ impl Default for Config {
|
||||
|
||||
pub struct State {
|
||||
waker: AtomicWaker,
|
||||
chunks_transferred: AtomicUsize,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub(crate) const fn new() -> Self {
|
||||
Self {
|
||||
waker: AtomicWaker::new(),
|
||||
chunks_transferred: AtomicUsize::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -130,10 +127,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
let isr = regs.isr().read();
|
||||
|
||||
if isr.tcr() || isr.tc() {
|
||||
let state = T::state();
|
||||
let transferred = state.chunks_transferred.load(Ordering::Relaxed);
|
||||
state.chunks_transferred.store(transferred + 1, Ordering::Relaxed);
|
||||
state.waker.wake();
|
||||
T::state().waker.wake();
|
||||
}
|
||||
// The flag can only be cleared by writting to nbytes, we won't do that here, so disable
|
||||
// the interrupt
|
||||
@ -457,12 +451,6 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
TXDMA: crate::i2c::TxDma<T>,
|
||||
{
|
||||
let total_len = write.len();
|
||||
let completed_chunks = total_len / 255;
|
||||
let total_chunks = if completed_chunks * 255 == total_len {
|
||||
completed_chunks
|
||||
} else {
|
||||
completed_chunks + 1
|
||||
};
|
||||
|
||||
let dma_transfer = unsafe {
|
||||
let regs = T::regs();
|
||||
@ -476,11 +464,10 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
|
||||
let ch = &mut self.tx_dma;
|
||||
let request = ch.request();
|
||||
crate::dma::write(ch, request, write, dst)
|
||||
Transfer::new_write(ch, request, write, dst, Default::default())
|
||||
};
|
||||
|
||||
let state = T::state();
|
||||
state.chunks_transferred.store(0, Ordering::Relaxed);
|
||||
let mut remaining_len = total_len;
|
||||
|
||||
let on_drop = OnDrop::new(|| {
|
||||
@ -495,33 +482,35 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
}
|
||||
});
|
||||
|
||||
// NOTE(unsafe) self.tx_dma does not fiddle with the i2c registers
|
||||
if first_slice {
|
||||
unsafe {
|
||||
Self::master_write(
|
||||
address,
|
||||
total_len.min(255),
|
||||
Stop::Software,
|
||||
(total_chunks != 1) || !last_slice,
|
||||
&check_timeout,
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
Self::master_continue(total_len.min(255), (total_chunks != 1) || !last_slice, &check_timeout)?;
|
||||
T::regs().cr1().modify(|w| w.set_tcie(true));
|
||||
}
|
||||
}
|
||||
|
||||
poll_fn(|cx| {
|
||||
state.waker.register(cx.waker());
|
||||
let chunks_transferred = state.chunks_transferred.load(Ordering::Relaxed);
|
||||
|
||||
if chunks_transferred == total_chunks {
|
||||
let isr = unsafe { T::regs().isr().read() };
|
||||
if remaining_len == total_len {
|
||||
// NOTE(unsafe) self.tx_dma does not fiddle with the i2c registers
|
||||
if first_slice {
|
||||
unsafe {
|
||||
Self::master_write(
|
||||
address,
|
||||
total_len.min(255),
|
||||
Stop::Software,
|
||||
(total_len > 255) || !last_slice,
|
||||
&check_timeout,
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
Self::master_continue(total_len.min(255), (total_len > 255) || !last_slice, &check_timeout)?;
|
||||
T::regs().cr1().modify(|w| w.set_tcie(true));
|
||||
}
|
||||
}
|
||||
} else if !(isr.tcr() || isr.tc()) {
|
||||
// poll_fn was woken without an interrupt present
|
||||
return Poll::Pending;
|
||||
} else if remaining_len == 0 {
|
||||
return Poll::Ready(Ok(()));
|
||||
} else if chunks_transferred != 0 {
|
||||
remaining_len = remaining_len.saturating_sub(255);
|
||||
let last_piece = (chunks_transferred + 1 == total_chunks) && last_slice;
|
||||
} else {
|
||||
let last_piece = (remaining_len <= 255) && last_slice;
|
||||
|
||||
// NOTE(unsafe) self.tx_dma does not fiddle with the i2c registers
|
||||
unsafe {
|
||||
@ -531,6 +520,8 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
T::regs().cr1().modify(|w| w.set_tcie(true));
|
||||
}
|
||||
}
|
||||
|
||||
remaining_len = remaining_len.saturating_sub(255);
|
||||
Poll::Pending
|
||||
})
|
||||
.await?;
|
||||
@ -559,12 +550,6 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
RXDMA: crate::i2c::RxDma<T>,
|
||||
{
|
||||
let total_len = buffer.len();
|
||||
let completed_chunks = total_len / 255;
|
||||
let total_chunks = if completed_chunks * 255 == total_len {
|
||||
completed_chunks
|
||||
} else {
|
||||
completed_chunks + 1
|
||||
};
|
||||
|
||||
let dma_transfer = unsafe {
|
||||
let regs = T::regs();
|
||||
@ -576,11 +561,10 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
|
||||
let ch = &mut self.rx_dma;
|
||||
let request = ch.request();
|
||||
crate::dma::read(ch, request, src, buffer)
|
||||
Transfer::new_read(ch, request, src, buffer, Default::default())
|
||||
};
|
||||
|
||||
let state = T::state();
|
||||
state.chunks_transferred.store(0, Ordering::Relaxed);
|
||||
let mut remaining_len = total_len;
|
||||
|
||||
let on_drop = OnDrop::new(|| {
|
||||
@ -593,27 +577,29 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
}
|
||||
});
|
||||
|
||||
// NOTE(unsafe) self.rx_dma does not fiddle with the i2c registers
|
||||
unsafe {
|
||||
Self::master_read(
|
||||
address,
|
||||
total_len.min(255),
|
||||
Stop::Software,
|
||||
total_chunks != 1,
|
||||
restart,
|
||||
&check_timeout,
|
||||
)?;
|
||||
}
|
||||
|
||||
poll_fn(|cx| {
|
||||
state.waker.register(cx.waker());
|
||||
let chunks_transferred = state.chunks_transferred.load(Ordering::Relaxed);
|
||||
|
||||
if chunks_transferred == total_chunks {
|
||||
let isr = unsafe { T::regs().isr().read() };
|
||||
if remaining_len == total_len {
|
||||
// NOTE(unsafe) self.rx_dma does not fiddle with the i2c registers
|
||||
unsafe {
|
||||
Self::master_read(
|
||||
address,
|
||||
total_len.min(255),
|
||||
Stop::Software,
|
||||
total_len > 255,
|
||||
restart,
|
||||
&check_timeout,
|
||||
)?;
|
||||
}
|
||||
} else if !(isr.tcr() || isr.tc()) {
|
||||
// poll_fn was woken without an interrupt present
|
||||
return Poll::Pending;
|
||||
} else if remaining_len == 0 {
|
||||
return Poll::Ready(Ok(()));
|
||||
} else if chunks_transferred != 0 {
|
||||
remaining_len = remaining_len.saturating_sub(255);
|
||||
let last_piece = chunks_transferred + 1 == total_chunks;
|
||||
} else {
|
||||
let last_piece = remaining_len <= 255;
|
||||
|
||||
// NOTE(unsafe) self.rx_dma does not fiddle with the i2c registers
|
||||
unsafe {
|
||||
@ -623,6 +609,8 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
T::regs().cr1().modify(|w| w.set_tcie(true));
|
||||
}
|
||||
}
|
||||
|
||||
remaining_len = remaining_len.saturating_sub(255);
|
||||
Poll::Pending
|
||||
})
|
||||
.await?;
|
||||
|
178
embassy-stm32/src/ipcc.rs
Normal file
178
embassy-stm32/src/ipcc.rs
Normal file
@ -0,0 +1,178 @@
|
||||
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
|
||||
|
||||
use crate::ipcc::sealed::Instance;
|
||||
use crate::peripherals::IPCC;
|
||||
use crate::rcc::sealed::RccPeripheral;
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct Config {
|
||||
// TODO: add IPCC peripheral configuration, if any, here
|
||||
// reserved for future use
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub enum IpccChannel {
|
||||
Channel1 = 0,
|
||||
Channel2 = 1,
|
||||
Channel3 = 2,
|
||||
Channel4 = 3,
|
||||
Channel5 = 4,
|
||||
Channel6 = 5,
|
||||
}
|
||||
|
||||
pub(crate) mod sealed {
|
||||
pub trait Instance: crate::rcc::RccPeripheral {
|
||||
fn regs() -> crate::pac::ipcc::Ipcc;
|
||||
fn set_cpu2(enabled: bool);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Ipcc<'d> {
|
||||
_peri: PeripheralRef<'d, IPCC>,
|
||||
}
|
||||
|
||||
impl<'d> Ipcc<'d> {
|
||||
pub fn new(peri: impl Peripheral<P = IPCC> + 'd, _config: Config) -> Self {
|
||||
into_ref!(peri);
|
||||
|
||||
Self { _peri: peri }
|
||||
}
|
||||
|
||||
pub fn init(&mut self) {
|
||||
IPCC::enable();
|
||||
IPCC::reset();
|
||||
IPCC::set_cpu2(true);
|
||||
|
||||
unsafe { _configure_pwr() };
|
||||
|
||||
let regs = IPCC::regs();
|
||||
|
||||
unsafe {
|
||||
regs.cpu(0).cr().modify(|w| {
|
||||
w.set_rxoie(true);
|
||||
w.set_txfie(true);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn c1_set_rx_channel(&mut self, channel: IpccChannel, enabled: bool) {
|
||||
let regs = IPCC::regs();
|
||||
|
||||
// If bit is set to 1 then interrupt is disabled
|
||||
unsafe { regs.cpu(0).mr().modify(|w| w.set_chom(channel as usize, !enabled)) }
|
||||
}
|
||||
|
||||
pub fn c1_get_rx_channel(&self, channel: IpccChannel) -> bool {
|
||||
let regs = IPCC::regs();
|
||||
|
||||
// If bit is set to 1 then interrupt is disabled
|
||||
unsafe { !regs.cpu(0).mr().read().chom(channel as usize) }
|
||||
}
|
||||
|
||||
pub fn c2_set_rx_channel(&mut self, channel: IpccChannel, enabled: bool) {
|
||||
let regs = IPCC::regs();
|
||||
|
||||
// If bit is set to 1 then interrupt is disabled
|
||||
unsafe { regs.cpu(1).mr().modify(|w| w.set_chom(channel as usize, !enabled)) }
|
||||
}
|
||||
|
||||
pub fn c2_get_rx_channel(&self, channel: IpccChannel) -> bool {
|
||||
let regs = IPCC::regs();
|
||||
|
||||
// If bit is set to 1 then interrupt is disabled
|
||||
unsafe { !regs.cpu(1).mr().read().chom(channel as usize) }
|
||||
}
|
||||
|
||||
pub fn c1_set_tx_channel(&mut self, channel: IpccChannel, enabled: bool) {
|
||||
let regs = IPCC::regs();
|
||||
|
||||
// If bit is set to 1 then interrupt is disabled
|
||||
unsafe { regs.cpu(0).mr().modify(|w| w.set_chfm(channel as usize, !enabled)) }
|
||||
}
|
||||
|
||||
pub fn c1_get_tx_channel(&self, channel: IpccChannel) -> bool {
|
||||
let regs = IPCC::regs();
|
||||
|
||||
// If bit is set to 1 then interrupt is disabled
|
||||
unsafe { !regs.cpu(0).mr().read().chfm(channel as usize) }
|
||||
}
|
||||
|
||||
pub fn c2_set_tx_channel(&mut self, channel: IpccChannel, enabled: bool) {
|
||||
let regs = IPCC::regs();
|
||||
|
||||
// If bit is set to 1 then interrupt is disabled
|
||||
unsafe { regs.cpu(1).mr().modify(|w| w.set_chfm(channel as usize, !enabled)) }
|
||||
}
|
||||
|
||||
pub fn c2_get_tx_channel(&self, channel: IpccChannel) -> bool {
|
||||
let regs = IPCC::regs();
|
||||
|
||||
// If bit is set to 1 then interrupt is disabled
|
||||
unsafe { !regs.cpu(1).mr().read().chfm(channel as usize) }
|
||||
}
|
||||
|
||||
/// clears IPCC receive channel status for CPU1
|
||||
pub fn c1_clear_flag_channel(&mut self, channel: IpccChannel) {
|
||||
let regs = IPCC::regs();
|
||||
|
||||
unsafe { regs.cpu(0).scr().write(|w| w.set_chc(channel as usize, true)) }
|
||||
}
|
||||
|
||||
/// clears IPCC receive channel status for CPU2
|
||||
pub fn c2_clear_flag_channel(&mut self, channel: IpccChannel) {
|
||||
let regs = IPCC::regs();
|
||||
|
||||
unsafe { regs.cpu(1).scr().write(|w| w.set_chc(channel as usize, true)) }
|
||||
}
|
||||
|
||||
pub fn c1_set_flag_channel(&mut self, channel: IpccChannel) {
|
||||
let regs = IPCC::regs();
|
||||
|
||||
unsafe { regs.cpu(0).scr().write(|w| w.set_chs(channel as usize, true)) }
|
||||
}
|
||||
|
||||
pub fn c2_set_flag_channel(&mut self, channel: IpccChannel) {
|
||||
let regs = IPCC::regs();
|
||||
|
||||
unsafe { regs.cpu(1).scr().write(|w| w.set_chs(channel as usize, true)) }
|
||||
}
|
||||
|
||||
pub fn c1_is_active_flag(&self, channel: IpccChannel) -> bool {
|
||||
let regs = IPCC::regs();
|
||||
|
||||
unsafe { regs.cpu(0).sr().read().chf(channel as usize) }
|
||||
}
|
||||
|
||||
pub fn c2_is_active_flag(&self, channel: IpccChannel) -> bool {
|
||||
let regs = IPCC::regs();
|
||||
|
||||
unsafe { regs.cpu(1).sr().read().chf(channel as usize) }
|
||||
}
|
||||
|
||||
pub fn is_tx_pending(&self, channel: IpccChannel) -> bool {
|
||||
!self.c1_is_active_flag(channel) && self.c1_get_tx_channel(channel)
|
||||
}
|
||||
|
||||
pub fn is_rx_pending(&self, channel: IpccChannel) -> bool {
|
||||
self.c2_is_active_flag(channel) && self.c1_get_rx_channel(channel)
|
||||
}
|
||||
}
|
||||
|
||||
impl sealed::Instance for crate::peripherals::IPCC {
|
||||
fn regs() -> crate::pac::ipcc::Ipcc {
|
||||
crate::pac::IPCC
|
||||
}
|
||||
|
||||
fn set_cpu2(enabled: bool) {
|
||||
unsafe { crate::pac::PWR.cr4().modify(|w| w.set_c2boot(enabled)) }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn _configure_pwr() {
|
||||
let rcc = crate::pac::RCC;
|
||||
|
||||
// set RF wake-up clock = LSE
|
||||
rcc.csr().modify(|w| w.set_rfwkpsel(0b01));
|
||||
}
|
@ -44,17 +44,19 @@ pub mod i2c;
|
||||
#[cfg(crc)]
|
||||
pub mod crc;
|
||||
pub mod flash;
|
||||
#[cfg(stm32wb)]
|
||||
pub mod ipcc;
|
||||
pub mod pwm;
|
||||
#[cfg(quadspi)]
|
||||
pub mod qspi;
|
||||
#[cfg(rng)]
|
||||
pub mod rng;
|
||||
#[cfg(all(rtc, not(rtc_v1)))]
|
||||
pub mod rtc;
|
||||
#[cfg(sdmmc)]
|
||||
pub mod sdmmc;
|
||||
#[cfg(spi)]
|
||||
pub mod spi;
|
||||
#[cfg(stm32wl)]
|
||||
pub mod subghz;
|
||||
#[cfg(usart)]
|
||||
pub mod usart;
|
||||
#[cfg(all(usb, feature = "time"))]
|
||||
@ -76,7 +78,6 @@ pub(crate) mod _generated {
|
||||
// Reexports
|
||||
pub use _generated::{peripherals, Peripherals};
|
||||
pub use embassy_cortex_m::executor;
|
||||
#[cfg(any(dma, bdma))]
|
||||
use embassy_cortex_m::interrupt::Priority;
|
||||
pub use embassy_cortex_m::interrupt::_export::interrupt;
|
||||
pub use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
|
||||
@ -94,6 +95,8 @@ pub struct Config {
|
||||
pub bdma_interrupt_priority: Priority,
|
||||
#[cfg(dma)]
|
||||
pub dma_interrupt_priority: Priority,
|
||||
#[cfg(gpdma)]
|
||||
pub gpdma_interrupt_priority: Priority,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@ -106,6 +109,8 @@ impl Default for Config {
|
||||
bdma_interrupt_priority: Priority::P0,
|
||||
#[cfg(dma)]
|
||||
dma_interrupt_priority: Priority::P0,
|
||||
#[cfg(gpdma)]
|
||||
gpdma_interrupt_priority: Priority::P0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -149,6 +154,8 @@ pub fn init(config: Config) -> Peripherals {
|
||||
config.bdma_interrupt_priority,
|
||||
#[cfg(dma)]
|
||||
config.dma_interrupt_priority,
|
||||
#[cfg(gpdma)]
|
||||
config.gpdma_interrupt_priority,
|
||||
);
|
||||
#[cfg(feature = "exti")]
|
||||
exti::init();
|
||||
|
@ -5,7 +5,7 @@ pub mod enums;
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use enums::*;
|
||||
|
||||
use crate::dma::TransferOptions;
|
||||
use crate::dma::Transfer;
|
||||
use crate::gpio::sealed::AFType;
|
||||
use crate::gpio::AnyPin;
|
||||
use crate::pac::quadspi::Quadspi as Regs;
|
||||
@ -230,9 +230,6 @@ impl<'d, T: Instance, Dma> Qspi<'d, T, Dma> {
|
||||
unsafe {
|
||||
self.setup_transaction(QspiMode::IndirectWrite, &transaction);
|
||||
|
||||
let request = self.dma.request();
|
||||
let options = TransferOptions::default();
|
||||
|
||||
T::REGS.ccr().modify(|v| {
|
||||
v.set_fmode(QspiMode::IndirectRead.into());
|
||||
});
|
||||
@ -241,12 +238,18 @@ impl<'d, T: Instance, Dma> Qspi<'d, T, Dma> {
|
||||
v.set_address(current_ar);
|
||||
});
|
||||
|
||||
self.dma
|
||||
.start_read(request, T::REGS.dr().ptr() as *mut u8, buf, options);
|
||||
let request = self.dma.request();
|
||||
let transfer = Transfer::new_read(
|
||||
&mut self.dma,
|
||||
request,
|
||||
T::REGS.dr().ptr() as *mut u8,
|
||||
buf,
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
T::REGS.cr().modify(|v| v.set_dmaen(true));
|
||||
|
||||
while self.dma.is_running() {}
|
||||
transfer.blocking_wait();
|
||||
}
|
||||
}
|
||||
|
||||
@ -257,19 +260,22 @@ impl<'d, T: Instance, Dma> Qspi<'d, T, Dma> {
|
||||
unsafe {
|
||||
self.setup_transaction(QspiMode::IndirectWrite, &transaction);
|
||||
|
||||
let request = self.dma.request();
|
||||
let options = TransferOptions::default();
|
||||
|
||||
T::REGS.ccr().modify(|v| {
|
||||
v.set_fmode(QspiMode::IndirectWrite.into());
|
||||
});
|
||||
|
||||
self.dma
|
||||
.start_write(request, buf, T::REGS.dr().ptr() as *mut u8, options);
|
||||
let request = self.dma.request();
|
||||
let transfer = Transfer::new_write(
|
||||
&mut self.dma,
|
||||
request,
|
||||
buf,
|
||||
T::REGS.dr().ptr() as *mut u8,
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
T::REGS.cr().modify(|v| v.set_dmaen(true));
|
||||
|
||||
while self.dma.is_running() {}
|
||||
transfer.blocking_wait();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,10 +29,66 @@ pub struct Config {
|
||||
pub pclk1: Option<Hertz>,
|
||||
pub pclk2: Option<Hertz>,
|
||||
|
||||
#[cfg(not(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446)))]
|
||||
pub plli2s: Option<Hertz>,
|
||||
|
||||
pub pll48: bool,
|
||||
}
|
||||
|
||||
unsafe fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option<u32>, pll48clk: bool) -> PllResults {
|
||||
#[cfg(stm32f410)]
|
||||
unsafe fn setup_i2s_pll(_vco_in: u32, _plli2s: Option<u32>) -> Option<u32> {
|
||||
None
|
||||
}
|
||||
|
||||
// Not currently implemented, but will be in the future
|
||||
#[cfg(any(stm32f411, stm32f412, stm32f413, stm32f423, stm32f446))]
|
||||
unsafe fn setup_i2s_pll(_vco_in: u32, _plli2s: Option<u32>) -> Option<u32> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446)))]
|
||||
unsafe fn setup_i2s_pll(vco_in: u32, plli2s: Option<u32>) -> Option<u32> {
|
||||
let min_div = 2;
|
||||
let max_div = 7;
|
||||
let target = match plli2s {
|
||||
Some(target) => target,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
// We loop through the possible divider values to find the best configuration. Looping
|
||||
// through all possible "N" values would result in more iterations.
|
||||
let (n, outdiv, output, _error) = (min_div..=max_div)
|
||||
.filter_map(|outdiv| {
|
||||
let target_vco_out = match target.checked_mul(outdiv) {
|
||||
Some(x) => x,
|
||||
None => return None,
|
||||
};
|
||||
let n = (target_vco_out + (vco_in >> 1)) / vco_in;
|
||||
let vco_out = vco_in * n;
|
||||
if !(100_000_000..=432_000_000).contains(&vco_out) {
|
||||
return None;
|
||||
}
|
||||
let output = vco_out / outdiv;
|
||||
let error = (output as i32 - target as i32).unsigned_abs();
|
||||
Some((n, outdiv, output, error))
|
||||
})
|
||||
.min_by_key(|(_, _, _, error)| *error)?;
|
||||
|
||||
RCC.plli2scfgr().modify(|w| {
|
||||
w.set_plli2sn(n as u16);
|
||||
w.set_plli2sr(outdiv as u8);
|
||||
});
|
||||
|
||||
Some(output)
|
||||
}
|
||||
|
||||
unsafe fn setup_pll(
|
||||
pllsrcclk: u32,
|
||||
use_hse: bool,
|
||||
pllsysclk: Option<u32>,
|
||||
plli2s: Option<u32>,
|
||||
pll48clk: bool,
|
||||
) -> PllResults {
|
||||
use crate::pac::rcc::vals::{Pllp, Pllsrc};
|
||||
|
||||
let sysclk = pllsysclk.unwrap_or(pllsrcclk);
|
||||
@ -43,6 +99,7 @@ unsafe fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option<u32>, pll48
|
||||
use_pll: false,
|
||||
pllsysclk: None,
|
||||
pll48clk: None,
|
||||
plli2sclk: None,
|
||||
};
|
||||
}
|
||||
// Input divisor from PLL source clock, must result to frequency in
|
||||
@ -101,6 +158,7 @@ unsafe fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option<u32>, pll48
|
||||
use_pll: true,
|
||||
pllsysclk: Some(real_pllsysclk),
|
||||
pll48clk: if pll48clk { Some(real_pll48clk) } else { None },
|
||||
plli2sclk: setup_i2s_pll(vco_in, plli2s),
|
||||
}
|
||||
}
|
||||
|
||||
@ -286,6 +344,10 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
pllsrcclk,
|
||||
config.hse.is_some(),
|
||||
if sysclk_on_pll { Some(sysclk) } else { None },
|
||||
#[cfg(not(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446)))]
|
||||
config.plli2s.map(|i2s| i2s.0),
|
||||
#[cfg(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446))]
|
||||
None,
|
||||
config.pll48,
|
||||
);
|
||||
|
||||
@ -376,6 +438,13 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
while !RCC.cr().read().pllrdy() {}
|
||||
}
|
||||
|
||||
#[cfg(not(stm32f410))]
|
||||
if plls.plli2sclk.is_some() {
|
||||
RCC.cr().modify(|w| w.set_plli2son(true));
|
||||
|
||||
while !RCC.cr().read().plli2srdy() {}
|
||||
}
|
||||
|
||||
RCC.cfgr().modify(|w| {
|
||||
w.set_ppre2(Ppre(ppre2_bits));
|
||||
w.set_ppre1(Ppre(ppre1_bits));
|
||||
@ -409,6 +478,12 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
ahb3: Hertz(hclk),
|
||||
|
||||
pll48: plls.pll48clk.map(Hertz),
|
||||
|
||||
#[cfg(not(stm32f410))]
|
||||
plli2s: plls.plli2sclk.map(Hertz),
|
||||
|
||||
#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))]
|
||||
pllsai: None,
|
||||
});
|
||||
}
|
||||
|
||||
@ -416,6 +491,8 @@ struct PllResults {
|
||||
use_pll: bool,
|
||||
pllsysclk: Option<u32>,
|
||||
pll48clk: Option<u32>,
|
||||
#[allow(dead_code)]
|
||||
plli2sclk: Option<u32>,
|
||||
}
|
||||
|
||||
mod max {
|
||||
|
@ -60,6 +60,12 @@ pub struct Clocks {
|
||||
#[cfg(any(rcc_f2, rcc_f4, rcc_f410, rcc_f7))]
|
||||
pub pll48: Option<Hertz>,
|
||||
|
||||
#[cfg(all(rcc_f4, not(stm32f410)))]
|
||||
pub plli2s: Option<Hertz>,
|
||||
|
||||
#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))]
|
||||
pub pllsai: Option<Hertz>,
|
||||
|
||||
#[cfg(stm32f1)]
|
||||
pub adc: Hertz,
|
||||
|
||||
|
201
embassy-stm32/src/rtc/datetime.rs
Normal file
201
embassy-stm32/src/rtc/datetime.rs
Normal file
@ -0,0 +1,201 @@
|
||||
#[cfg(feature = "chrono")]
|
||||
use core::convert::From;
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
use chrono::{self, Datelike, NaiveDate, Timelike, Weekday};
|
||||
|
||||
use super::byte_to_bcd2;
|
||||
use crate::pac::rtc::Rtc;
|
||||
|
||||
/// Errors regarding the [`DateTime`] struct.
|
||||
#[derive(Clone, 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
|
||||
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,
|
||||
}
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
impl From<chrono::NaiveDateTime> for DateTime {
|
||||
fn from(date_time: chrono::NaiveDateTime) -> Self {
|
||||
Self {
|
||||
year: date_time.year() as u16,
|
||||
month: date_time.month() as u8,
|
||||
day: date_time.day() as u8,
|
||||
day_of_week: date_time.weekday().into(),
|
||||
hour: date_time.hour() as u8,
|
||||
minute: date_time.minute() as u8,
|
||||
second: date_time.second() as u8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
impl From<DateTime> for chrono::NaiveDateTime {
|
||||
fn from(date_time: DateTime) -> Self {
|
||||
NaiveDate::from_ymd_opt(date_time.year as i32, date_time.month as u32, date_time.day as u32)
|
||||
.unwrap()
|
||||
.and_hms_opt(date_time.hour as u32, date_time.minute as u32, date_time.second as u32)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// A day of the week
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum DayOfWeek {
|
||||
Monday = 0,
|
||||
Tuesday = 1,
|
||||
Wednesday = 2,
|
||||
Thursday = 3,
|
||||
Friday = 4,
|
||||
Saturday = 5,
|
||||
Sunday = 6,
|
||||
}
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
impl From<chrono::Weekday> for DayOfWeek {
|
||||
fn from(weekday: Weekday) -> Self {
|
||||
day_of_week_from_u8(weekday.number_from_monday() as u8).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
impl From<DayOfWeek> for chrono::Weekday {
|
||||
fn from(weekday: DayOfWeek) -> Self {
|
||||
match weekday {
|
||||
DayOfWeek::Monday => Weekday::Mon,
|
||||
DayOfWeek::Tuesday => Weekday::Tue,
|
||||
DayOfWeek::Wednesday => Weekday::Wed,
|
||||
DayOfWeek::Thursday => Weekday::Thu,
|
||||
DayOfWeek::Friday => Weekday::Fri,
|
||||
DayOfWeek::Saturday => Weekday::Sat,
|
||||
DayOfWeek::Sunday => Weekday::Sun,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn day_of_week_from_u8(v: u8) -> Result<DayOfWeek, Error> {
|
||||
Ok(match v {
|
||||
0 => DayOfWeek::Monday,
|
||||
1 => DayOfWeek::Tuesday,
|
||||
2 => DayOfWeek::Wednesday,
|
||||
3 => DayOfWeek::Thursday,
|
||||
4 => DayOfWeek::Friday,
|
||||
5 => DayOfWeek::Saturday,
|
||||
6 => DayOfWeek::Sunday,
|
||||
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_date_time(rtc: &Rtc, t: DateTime) {
|
||||
let (ht, hu) = byte_to_bcd2(t.hour as u8);
|
||||
let (mnt, mnu) = byte_to_bcd2(t.minute as u8);
|
||||
let (st, su) = byte_to_bcd2(t.second as u8);
|
||||
|
||||
let (dt, du) = byte_to_bcd2(t.day as u8);
|
||||
let (mt, mu) = byte_to_bcd2(t.month as u8);
|
||||
let yr = t.year as u16;
|
||||
let yr_offset = (yr - 1970_u16) as u8;
|
||||
let (yt, yu) = byte_to_bcd2(yr_offset);
|
||||
|
||||
unsafe {
|
||||
use crate::pac::rtc::vals::Ampm;
|
||||
|
||||
rtc.tr().write(|w| {
|
||||
w.set_ht(ht);
|
||||
w.set_hu(hu);
|
||||
w.set_mnt(mnt);
|
||||
w.set_mnu(mnu);
|
||||
w.set_st(st);
|
||||
w.set_su(su);
|
||||
w.set_pm(Ampm::AM);
|
||||
});
|
||||
|
||||
rtc.dr().write(|w| {
|
||||
w.set_dt(dt);
|
||||
w.set_du(du);
|
||||
w.set_mt(mt > 0);
|
||||
w.set_mu(mu);
|
||||
w.set_yt(yt);
|
||||
w.set_yu(yu);
|
||||
w.set_wdu(day_of_week_to_u8(t.day_of_week));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn datetime(
|
||||
year: u16,
|
||||
month: u8,
|
||||
day: u8,
|
||||
day_of_week: u8,
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
) -> Result<DateTime, Error> {
|
||||
let day_of_week = day_of_week_from_u8(day_of_week)?;
|
||||
Ok(DateTime {
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
day_of_week,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
})
|
||||
}
|
249
embassy-stm32/src/rtc/mod.rs
Normal file
249
embassy-stm32/src/rtc/mod.rs
Normal file
@ -0,0 +1,249 @@
|
||||
//! RTC peripheral abstraction
|
||||
use core::marker::PhantomData;
|
||||
mod datetime;
|
||||
|
||||
pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError};
|
||||
|
||||
/// refer to AN4759 to compare features of RTC2 and RTC3
|
||||
#[cfg_attr(any(rtc_v1), path = "v1.rs")]
|
||||
#[cfg_attr(
|
||||
any(
|
||||
rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb
|
||||
),
|
||||
path = "v2.rs"
|
||||
)]
|
||||
#[cfg_attr(any(rtc_v3, rtc_v3u5), path = "v3.rs")]
|
||||
mod _version;
|
||||
pub use _version::*;
|
||||
use embassy_hal_common::Peripheral;
|
||||
|
||||
/// Errors that can occur on methods on [RtcClock]
|
||||
#[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,
|
||||
}
|
||||
|
||||
/// RTC Abstraction
|
||||
pub struct Rtc<'d, T: Instance> {
|
||||
phantom: PhantomData<&'d mut T>,
|
||||
rtc_config: RtcConfig,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum RtcClockSource {
|
||||
/// 00: No clock
|
||||
NoClock = 0b00,
|
||||
/// 01: LSE oscillator clock used as RTC clock
|
||||
LSE = 0b01,
|
||||
/// 10: LSI oscillator clock used as RTC clock
|
||||
LSI = 0b10,
|
||||
/// 11: HSE oscillator clock divided by 32 used as RTC clock
|
||||
HSE = 0b11,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub struct RtcConfig {
|
||||
/// RTC clock source
|
||||
clock_config: RtcClockSource,
|
||||
/// Asynchronous prescaler factor
|
||||
/// This is the asynchronous division factor:
|
||||
/// ck_apre frequency = RTCCLK frequency/(PREDIV_A+1)
|
||||
/// ck_apre drives the subsecond register
|
||||
async_prescaler: u8,
|
||||
/// Synchronous prescaler factor
|
||||
/// This is the synchronous division factor:
|
||||
/// ck_spre frequency = ck_apre frequency/(PREDIV_S+1)
|
||||
/// ck_spre must be 1Hz
|
||||
sync_prescaler: u16,
|
||||
}
|
||||
|
||||
impl Default for RtcConfig {
|
||||
/// LSI with prescalers assuming 32.768 kHz.
|
||||
/// Raw sub-seconds in 1/256.
|
||||
fn default() -> Self {
|
||||
RtcConfig {
|
||||
clock_config: RtcClockSource::LSI,
|
||||
async_prescaler: 127,
|
||||
sync_prescaler: 255,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RtcConfig {
|
||||
/// Sets the clock source of RTC config
|
||||
pub fn clock_config(mut self, cfg: RtcClockSource) -> Self {
|
||||
self.clock_config = cfg;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the asynchronous prescaler of RTC config
|
||||
pub fn async_prescaler(mut self, prescaler: u8) -> Self {
|
||||
self.async_prescaler = prescaler;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the synchronous prescaler of RTC config
|
||||
pub fn sync_prescaler(mut self, prescaler: u16) -> Self {
|
||||
self.sync_prescaler = prescaler;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum RtcCalibrationCyclePeriod {
|
||||
/// 8-second calibration period
|
||||
Seconds8,
|
||||
/// 16-second calibration period
|
||||
Seconds16,
|
||||
/// 32-second calibration period
|
||||
Seconds32,
|
||||
}
|
||||
|
||||
impl Default for RtcCalibrationCyclePeriod {
|
||||
fn default() -> Self {
|
||||
RtcCalibrationCyclePeriod::Seconds32
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> Rtc<'d, T> {
|
||||
pub fn new(_rtc: impl Peripheral<P = T> + 'd, rtc_config: RtcConfig) -> Self {
|
||||
unsafe { T::enable_peripheral_clk() };
|
||||
|
||||
let mut rtc_struct = Self {
|
||||
phantom: PhantomData,
|
||||
rtc_config,
|
||||
};
|
||||
|
||||
rtc_struct.apply_config(rtc_config);
|
||||
|
||||
rtc_struct
|
||||
}
|
||||
|
||||
/// 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)?;
|
||||
self.write(true, |rtc| self::datetime::write_date_time(rtc, t));
|
||||
|
||||
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> {
|
||||
let r = T::regs();
|
||||
unsafe {
|
||||
let tr = r.tr().read();
|
||||
let second = bcd2_to_byte((tr.st(), tr.su()));
|
||||
let minute = bcd2_to_byte((tr.mnt(), tr.mnu()));
|
||||
let hour = bcd2_to_byte((tr.ht(), tr.hu()));
|
||||
// Reading either RTC_SSR or RTC_TR locks the values in the higher-order
|
||||
// calendar shadow registers until RTC_DR is read.
|
||||
let dr = r.dr().read();
|
||||
|
||||
let weekday = dr.wdu();
|
||||
let day = bcd2_to_byte((dr.dt(), dr.du()));
|
||||
let month = bcd2_to_byte((dr.mt() as u8, dr.mu()));
|
||||
let year = bcd2_to_byte((dr.yt(), dr.yu())) as u16 + 1970_u16;
|
||||
|
||||
self::datetime::datetime(year, month, day, weekday, hour, minute, second).map_err(RtcError::InvalidDateTime)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if daylight savings time is active.
|
||||
pub fn get_daylight_savings(&self) -> bool {
|
||||
let cr = unsafe { T::regs().cr().read() };
|
||||
cr.bkp()
|
||||
}
|
||||
|
||||
/// Enable/disable daylight savings time.
|
||||
pub fn set_daylight_savings(&mut self, daylight_savings: bool) {
|
||||
self.write(true, |rtc| {
|
||||
unsafe { rtc.cr().modify(|w| w.set_bkp(daylight_savings)) };
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_config(&self) -> RtcConfig {
|
||||
self.rtc_config
|
||||
}
|
||||
|
||||
pub const BACKUP_REGISTER_COUNT: usize = T::BACKUP_REGISTER_COUNT;
|
||||
|
||||
/// Read content of the backup register.
|
||||
///
|
||||
/// The registers retain their values during wakes from standby mode or system resets. They also
|
||||
/// retain their value when Vdd is switched off as long as V_BAT is powered.
|
||||
pub fn read_backup_register(&self, register: usize) -> Option<u32> {
|
||||
T::read_backup_register(&T::regs(), register)
|
||||
}
|
||||
|
||||
/// Set content of the backup register.
|
||||
///
|
||||
/// The registers retain their values during wakes from standby mode or system resets. They also
|
||||
/// retain their value when Vdd is switched off as long as V_BAT is powered.
|
||||
pub fn write_backup_register(&self, register: usize, value: u32) {
|
||||
T::write_backup_register(&T::regs(), register, value)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn byte_to_bcd2(byte: u8) -> (u8, u8) {
|
||||
let mut bcd_high: u8 = 0;
|
||||
let mut value = byte;
|
||||
|
||||
while value >= 10 {
|
||||
bcd_high += 1;
|
||||
value -= 10;
|
||||
}
|
||||
|
||||
(bcd_high, ((bcd_high << 4) | value) as u8)
|
||||
}
|
||||
|
||||
pub(crate) fn bcd2_to_byte(bcd: (u8, u8)) -> u8 {
|
||||
let value = bcd.1 | bcd.0 << 4;
|
||||
|
||||
let tmp = ((value & 0xF0) >> 0x4) * 10;
|
||||
|
||||
tmp + (value & 0x0F)
|
||||
}
|
||||
|
||||
pub(crate) mod sealed {
|
||||
use crate::pac::rtc::Rtc;
|
||||
|
||||
pub trait Instance {
|
||||
const BACKUP_REGISTER_COUNT: usize;
|
||||
|
||||
fn regs() -> Rtc {
|
||||
crate::pac::RTC
|
||||
}
|
||||
|
||||
unsafe fn enable_peripheral_clk() {}
|
||||
|
||||
/// Read content of the backup register.
|
||||
///
|
||||
/// The registers retain their values during wakes from standby mode or system resets. They also
|
||||
/// retain their value when Vdd is switched off as long as V_BAT is powered.
|
||||
fn read_backup_register(rtc: &Rtc, register: usize) -> Option<u32>;
|
||||
|
||||
/// Set content of the backup register.
|
||||
///
|
||||
/// The registers retain their values during wakes from standby mode or system resets. They also
|
||||
/// retain their value when Vdd is switched off as long as V_BAT is powered.
|
||||
fn write_backup_register(rtc: &Rtc, register: usize, value: u32);
|
||||
|
||||
// fn apply_config(&mut self, rtc_config: RtcConfig);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Instance: sealed::Instance + 'static {}
|
229
embassy-stm32/src/rtc/v2.rs
Normal file
229
embassy-stm32/src/rtc/v2.rs
Normal file
@ -0,0 +1,229 @@
|
||||
use stm32_metapac::rtc::vals::{Init, Osel, Pol};
|
||||
|
||||
use super::{sealed, Instance, RtcConfig};
|
||||
use crate::pac::rtc::Rtc;
|
||||
|
||||
impl<'d, T: Instance> super::Rtc<'d, T> {
|
||||
/// Applies the RTC config
|
||||
/// It this changes the RTC clock source the time will be reset
|
||||
pub(super) fn apply_config(&mut self, rtc_config: RtcConfig) {
|
||||
// Unlock the backup domain
|
||||
unsafe {
|
||||
let clock_config = rtc_config.clock_config as u8;
|
||||
|
||||
#[cfg(not(rtc_v2wb))]
|
||||
use stm32_metapac::rcc::vals::Rtcsel;
|
||||
|
||||
#[cfg(any(rtc_v2f2, rtc_v2f3, rtc_v2l1))]
|
||||
let cr = crate::pac::PWR.cr();
|
||||
#[cfg(any(rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l4, rtc_v2wb))]
|
||||
let cr = crate::pac::PWR.cr1();
|
||||
|
||||
// TODO: Missing from PAC for l0 and f0?
|
||||
#[cfg(not(any(rtc_v2f0, rtc_v2l0)))]
|
||||
{
|
||||
cr.modify(|w| w.set_dbp(true));
|
||||
while !cr.read().dbp() {}
|
||||
}
|
||||
|
||||
#[cfg(not(any(rtc_v2l0, rtc_v2l1)))]
|
||||
let reg = crate::pac::RCC.bdcr().read();
|
||||
#[cfg(any(rtc_v2l0, rtc_v2l1))]
|
||||
let reg = crate::pac::RCC.csr().read();
|
||||
|
||||
#[cfg(any(rtc_v2h7, rtc_v2l4, rtc_v2wb))]
|
||||
assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet.");
|
||||
|
||||
#[cfg(rtc_v2wb)]
|
||||
let rtcsel = reg.rtcsel();
|
||||
#[cfg(not(rtc_v2wb))]
|
||||
let rtcsel = reg.rtcsel().0;
|
||||
|
||||
if !reg.rtcen() || rtcsel != clock_config {
|
||||
#[cfg(not(any(rtc_v2l0, rtc_v2l1)))]
|
||||
crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true));
|
||||
|
||||
#[cfg(not(any(rtc_v2l0, rtc_v2l1)))]
|
||||
let cr = crate::pac::RCC.bdcr();
|
||||
#[cfg(any(rtc_v2l0, rtc_v2l1))]
|
||||
let cr = crate::pac::RCC.csr();
|
||||
|
||||
cr.modify(|w| {
|
||||
// Reset
|
||||
#[cfg(not(any(rtc_v2l0, rtc_v2l1)))]
|
||||
w.set_bdrst(false);
|
||||
|
||||
// Select RTC source
|
||||
#[cfg(not(rtc_v2wb))]
|
||||
w.set_rtcsel(Rtcsel(clock_config));
|
||||
#[cfg(rtc_v2wb)]
|
||||
w.set_rtcsel(clock_config);
|
||||
w.set_rtcen(true);
|
||||
|
||||
// Restore bcdr
|
||||
#[cfg(any(rtc_v2l4, rtc_v2wb))]
|
||||
w.set_lscosel(reg.lscosel());
|
||||
#[cfg(any(rtc_v2l4, rtc_v2wb))]
|
||||
w.set_lscoen(reg.lscoen());
|
||||
|
||||
w.set_lseon(reg.lseon());
|
||||
|
||||
#[cfg(any(rtc_v2f0, rtc_v2f7, rtc_v2h7, rtc_v2l4, rtc_v2wb))]
|
||||
w.set_lsedrv(reg.lsedrv());
|
||||
w.set_lsebyp(reg.lsebyp());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.write(true, |rtc| unsafe {
|
||||
rtc.cr().modify(|w| {
|
||||
#[cfg(rtc_v2f2)]
|
||||
w.set_fmt(false);
|
||||
#[cfg(not(rtc_v2f2))]
|
||||
w.set_fmt(stm32_metapac::rtc::vals::Fmt::TWENTY_FOUR_HOUR);
|
||||
w.set_osel(Osel::DISABLED);
|
||||
w.set_pol(Pol::HIGH);
|
||||
});
|
||||
|
||||
rtc.prer().modify(|w| {
|
||||
w.set_prediv_s(rtc_config.sync_prescaler);
|
||||
w.set_prediv_a(rtc_config.async_prescaler);
|
||||
});
|
||||
});
|
||||
|
||||
self.rtc_config = rtc_config;
|
||||
}
|
||||
|
||||
/// Calibrate the clock drift.
|
||||
///
|
||||
/// `clock_drift` can be adjusted from -487.1 ppm to 488.5 ppm and is clamped to this range.
|
||||
///
|
||||
/// ### Note
|
||||
///
|
||||
/// To perform a calibration when `async_prescaler` is less then 3, `sync_prescaler`
|
||||
/// has to be reduced accordingly (see RM0351 Rev 9, sec 38.3.12).
|
||||
#[cfg(not(rtc_v2f2))]
|
||||
pub fn calibrate(&mut self, mut clock_drift: f32, period: super::RtcCalibrationCyclePeriod) {
|
||||
const RTC_CALR_MIN_PPM: f32 = -487.1;
|
||||
const RTC_CALR_MAX_PPM: f32 = 488.5;
|
||||
const RTC_CALR_RESOLUTION_PPM: f32 = 0.9537;
|
||||
|
||||
if clock_drift < RTC_CALR_MIN_PPM {
|
||||
clock_drift = RTC_CALR_MIN_PPM;
|
||||
} else if clock_drift > RTC_CALR_MAX_PPM {
|
||||
clock_drift = RTC_CALR_MAX_PPM;
|
||||
}
|
||||
|
||||
clock_drift = clock_drift / RTC_CALR_RESOLUTION_PPM;
|
||||
|
||||
self.write(false, |rtc| {
|
||||
unsafe {
|
||||
rtc.calr().write(|w| {
|
||||
match period {
|
||||
super::RtcCalibrationCyclePeriod::Seconds8 => {
|
||||
w.set_calw8(stm32_metapac::rtc::vals::Calw8::EIGHT_SECOND);
|
||||
}
|
||||
super::RtcCalibrationCyclePeriod::Seconds16 => {
|
||||
w.set_calw16(stm32_metapac::rtc::vals::Calw16::SIXTEEN_SECOND);
|
||||
}
|
||||
super::RtcCalibrationCyclePeriod::Seconds32 => {
|
||||
// Set neither `calw8` nor `calw16` to use 32 seconds
|
||||
}
|
||||
}
|
||||
|
||||
// Extra pulses during calibration cycle period: CALP * 512 - CALM
|
||||
//
|
||||
// CALP sets whether pulses are added or omitted.
|
||||
//
|
||||
// CALM contains how many pulses (out of 512) are masked in a
|
||||
// given calibration cycle period.
|
||||
if clock_drift > 0.0 {
|
||||
// Maximum (about 512.2) rounds to 512.
|
||||
clock_drift += 0.5;
|
||||
|
||||
// When the offset is positive (0 to 512), the opposite of
|
||||
// the offset (512 - offset) is masked, i.e. for the
|
||||
// maximum offset (512), 0 pulses are masked.
|
||||
w.set_calp(stm32_metapac::rtc::vals::Calp::INCREASEFREQ);
|
||||
w.set_calm(512 - clock_drift as u16);
|
||||
} else {
|
||||
// Minimum (about -510.7) rounds to -511.
|
||||
clock_drift -= 0.5;
|
||||
|
||||
// When the offset is negative or zero (-511 to 0),
|
||||
// the absolute offset is masked, i.e. for the minimum
|
||||
// offset (-511), 511 pulses are masked.
|
||||
w.set_calp(stm32_metapac::rtc::vals::Calp::NOCHANGE);
|
||||
w.set_calm((clock_drift * -1.0) as u16);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn write<F, R>(&mut self, init_mode: bool, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&crate::pac::rtc::Rtc) -> R,
|
||||
{
|
||||
let r = T::regs();
|
||||
// Disable write protection.
|
||||
// This is safe, as we're only writin the correct and expected values.
|
||||
unsafe {
|
||||
r.wpr().write(|w| w.set_key(0xca));
|
||||
r.wpr().write(|w| w.set_key(0x53));
|
||||
|
||||
// true if initf bit indicates RTC peripheral is in init mode
|
||||
if init_mode && !r.isr().read().initf() {
|
||||
// to update calendar date/time, time format, and prescaler configuration, RTC must be in init mode
|
||||
r.isr().modify(|w| w.set_init(Init::INITMODE));
|
||||
// wait till init state entered
|
||||
// ~2 RTCCLK cycles
|
||||
while !r.isr().read().initf() {}
|
||||
}
|
||||
}
|
||||
|
||||
let result = f(&r);
|
||||
|
||||
unsafe {
|
||||
if init_mode {
|
||||
r.isr().modify(|w| w.set_init(Init::FREERUNNINGMODE)); // Exits init mode
|
||||
}
|
||||
|
||||
// Re-enable write protection.
|
||||
// This is safe, as the field accepts the full range of 8-bit values.
|
||||
r.wpr().write(|w| w.set_key(0xff));
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl sealed::Instance for crate::peripherals::RTC {
|
||||
const BACKUP_REGISTER_COUNT: usize = 20;
|
||||
|
||||
unsafe fn enable_peripheral_clk() {
|
||||
#[cfg(any(rtc_v2l4, rtc_v2wb))]
|
||||
{
|
||||
// enable peripheral clock for communication
|
||||
crate::pac::RCC.apb1enr1().modify(|w| w.set_rtcapben(true));
|
||||
|
||||
// read to allow the pwr clock to enable
|
||||
crate::pac::PWR.cr1().read();
|
||||
}
|
||||
}
|
||||
|
||||
fn read_backup_register(rtc: &Rtc, register: usize) -> Option<u32> {
|
||||
if register < Self::BACKUP_REGISTER_COUNT {
|
||||
Some(unsafe { rtc.bkpr(register).read().bkp() })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn write_backup_register(rtc: &Rtc, register: usize, value: u32) {
|
||||
if register < Self::BACKUP_REGISTER_COUNT {
|
||||
unsafe { rtc.bkpr(register).write(|w| w.set_bkp(value)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Instance for crate::peripherals::RTC {}
|
205
embassy-stm32/src/rtc/v3.rs
Normal file
205
embassy-stm32/src/rtc/v3.rs
Normal file
@ -0,0 +1,205 @@
|
||||
use stm32_metapac::rtc::vals::{Calp, Calw16, Calw8, Fmt, Init, Key, Osel, Pol, TampalrmPu, TampalrmType};
|
||||
|
||||
use super::{sealed, Instance, RtcCalibrationCyclePeriod, RtcConfig};
|
||||
use crate::pac::rtc::Rtc;
|
||||
|
||||
impl<'d, T: Instance> super::Rtc<'d, T> {
|
||||
/// Applies the RTC config
|
||||
/// It this changes the RTC clock source the time will be reset
|
||||
pub(super) fn apply_config(&mut self, rtc_config: RtcConfig) {
|
||||
// Unlock the backup domain
|
||||
unsafe {
|
||||
#[cfg(any(rtc_v3u5, rcc_g0))]
|
||||
use crate::pac::rcc::vals::Rtcsel;
|
||||
#[cfg(not(any(rtc_v3u5, rcc_g0, rcc_g4, rcc_wl5, rcc_wle)))]
|
||||
use crate::pac::rtc::vals::Rtcsel;
|
||||
|
||||
#[cfg(not(any(rtc_v3u5, rcc_wl5, rcc_wle)))]
|
||||
{
|
||||
crate::pac::PWR.cr1().modify(|w| w.set_dbp(true));
|
||||
while !crate::pac::PWR.cr1().read().dbp() {}
|
||||
}
|
||||
#[cfg(any(rcc_wl5, rcc_wle))]
|
||||
{
|
||||
use crate::pac::pwr::vals::Dbp;
|
||||
|
||||
crate::pac::PWR.cr1().modify(|w| w.set_dbp(Dbp::ENABLED));
|
||||
while crate::pac::PWR.cr1().read().dbp() != Dbp::ENABLED {}
|
||||
}
|
||||
|
||||
let reg = crate::pac::RCC.bdcr().read();
|
||||
assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet.");
|
||||
|
||||
let config_rtcsel = rtc_config.clock_config as u8;
|
||||
#[cfg(not(any(rcc_wl5, rcc_wle, rcc_g4)))]
|
||||
let config_rtcsel = Rtcsel(config_rtcsel);
|
||||
|
||||
if !reg.rtcen() || reg.rtcsel() != config_rtcsel {
|
||||
crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true));
|
||||
|
||||
crate::pac::RCC.bdcr().modify(|w| {
|
||||
// Reset
|
||||
w.set_bdrst(false);
|
||||
|
||||
// Select RTC source
|
||||
w.set_rtcsel(config_rtcsel);
|
||||
|
||||
w.set_rtcen(true);
|
||||
|
||||
// Restore bcdr
|
||||
w.set_lscosel(reg.lscosel());
|
||||
w.set_lscoen(reg.lscoen());
|
||||
|
||||
w.set_lseon(reg.lseon());
|
||||
w.set_lsedrv(reg.lsedrv());
|
||||
w.set_lsebyp(reg.lsebyp());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.write(true, |rtc| {
|
||||
unsafe {
|
||||
rtc.cr().modify(|w| {
|
||||
w.set_fmt(Fmt::TWENTYFOURHOUR);
|
||||
w.set_osel(Osel::DISABLED);
|
||||
w.set_pol(Pol::HIGH);
|
||||
});
|
||||
|
||||
rtc.prer().modify(|w| {
|
||||
w.set_prediv_s(rtc_config.sync_prescaler);
|
||||
w.set_prediv_a(rtc_config.async_prescaler);
|
||||
});
|
||||
|
||||
// TODO: configuration for output pins
|
||||
rtc.cr().modify(|w| {
|
||||
w.set_out2en(false);
|
||||
w.set_tampalrm_type(TampalrmType::PUSHPULL);
|
||||
w.set_tampalrm_pu(TampalrmPu::NOPULLUP);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
self.rtc_config = rtc_config;
|
||||
}
|
||||
|
||||
const RTC_CALR_MIN_PPM: f32 = -487.1;
|
||||
const RTC_CALR_MAX_PPM: f32 = 488.5;
|
||||
const RTC_CALR_RESOLUTION_PPM: f32 = 0.9537;
|
||||
|
||||
/// Calibrate the clock drift.
|
||||
///
|
||||
/// `clock_drift` can be adjusted from -487.1 ppm to 488.5 ppm and is clamped to this range.
|
||||
///
|
||||
/// ### Note
|
||||
///
|
||||
/// To perform a calibration when `async_prescaler` is less then 3, `sync_prescaler`
|
||||
/// has to be reduced accordingly (see RM0351 Rev 9, sec 38.3.12).
|
||||
pub fn calibrate(&mut self, mut clock_drift: f32, period: RtcCalibrationCyclePeriod) {
|
||||
if clock_drift < Self::RTC_CALR_MIN_PPM {
|
||||
clock_drift = Self::RTC_CALR_MIN_PPM;
|
||||
} else if clock_drift > Self::RTC_CALR_MAX_PPM {
|
||||
clock_drift = Self::RTC_CALR_MAX_PPM;
|
||||
}
|
||||
|
||||
clock_drift = clock_drift / Self::RTC_CALR_RESOLUTION_PPM;
|
||||
|
||||
self.write(false, |rtc| {
|
||||
unsafe {
|
||||
rtc.calr().write(|w| {
|
||||
match period {
|
||||
RtcCalibrationCyclePeriod::Seconds8 => {
|
||||
w.set_calw8(Calw8::EIGHTSECONDS);
|
||||
}
|
||||
RtcCalibrationCyclePeriod::Seconds16 => {
|
||||
w.set_calw16(Calw16::SIXTEENSECONDS);
|
||||
}
|
||||
RtcCalibrationCyclePeriod::Seconds32 => {
|
||||
// Set neither `calw8` nor `calw16` to use 32 seconds
|
||||
}
|
||||
}
|
||||
|
||||
// Extra pulses during calibration cycle period: CALP * 512 - CALM
|
||||
//
|
||||
// CALP sets whether pulses are added or omitted.
|
||||
//
|
||||
// CALM contains how many pulses (out of 512) are masked in a
|
||||
// given calibration cycle period.
|
||||
if clock_drift > 0.0 {
|
||||
// Maximum (about 512.2) rounds to 512.
|
||||
clock_drift += 0.5;
|
||||
|
||||
// When the offset is positive (0 to 512), the opposite of
|
||||
// the offset (512 - offset) is masked, i.e. for the
|
||||
// maximum offset (512), 0 pulses are masked.
|
||||
w.set_calp(Calp::INCREASEFREQ);
|
||||
w.set_calm(512 - clock_drift as u16);
|
||||
} else {
|
||||
// Minimum (about -510.7) rounds to -511.
|
||||
clock_drift -= 0.5;
|
||||
|
||||
// When the offset is negative or zero (-511 to 0),
|
||||
// the absolute offset is masked, i.e. for the minimum
|
||||
// offset (-511), 511 pulses are masked.
|
||||
w.set_calp(Calp::NOCHANGE);
|
||||
w.set_calm((clock_drift * -1.0) as u16);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn write<F, R>(&mut self, init_mode: bool, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&crate::pac::rtc::Rtc) -> R,
|
||||
{
|
||||
let r = T::regs();
|
||||
// Disable write protection.
|
||||
// This is safe, as we're only writin the correct and expected values.
|
||||
unsafe {
|
||||
r.wpr().write(|w| w.set_key(Key::DEACTIVATE1));
|
||||
r.wpr().write(|w| w.set_key(Key::DEACTIVATE2));
|
||||
|
||||
if init_mode && !r.icsr().read().initf() {
|
||||
r.icsr().modify(|w| w.set_init(Init::INITMODE));
|
||||
// wait till init state entered
|
||||
// ~2 RTCCLK cycles
|
||||
while !r.icsr().read().initf() {}
|
||||
}
|
||||
}
|
||||
|
||||
let result = f(&r);
|
||||
|
||||
unsafe {
|
||||
if init_mode {
|
||||
r.icsr().modify(|w| w.set_init(Init::FREERUNNINGMODE)); // Exits init mode
|
||||
}
|
||||
|
||||
// Re-enable write protection.
|
||||
// This is safe, as the field accepts the full range of 8-bit values.
|
||||
r.wpr().write(|w| w.set_key(Key::ACTIVATE));
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl sealed::Instance for crate::peripherals::RTC {
|
||||
const BACKUP_REGISTER_COUNT: usize = 32;
|
||||
|
||||
fn read_backup_register(_rtc: &Rtc, register: usize) -> Option<u32> {
|
||||
if register < Self::BACKUP_REGISTER_COUNT {
|
||||
//Some(rtc.bkpr()[register].read().bits())
|
||||
None // RTC3 backup registers come from the TAMP peripe=heral, not RTC. Not() even in the L412 PAC
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn write_backup_register(_rtc: &Rtc, register: usize, _value: u32) {
|
||||
if register < Self::BACKUP_REGISTER_COUNT {
|
||||
// RTC3 backup registers come from the TAMP peripe=heral, not RTC. Not() even in the L412 PAC
|
||||
//unsafe { self.rtc.bkpr()[register].write(|w| w.bits(value)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Instance for crate::peripherals::RTC {}
|
File diff suppressed because it is too large
Load Diff
@ -7,8 +7,7 @@ use embassy_futures::join::join;
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3};
|
||||
|
||||
use self::sealed::WordSize;
|
||||
use crate::dma::{slice_ptr_parts, Transfer};
|
||||
use crate::dma::{slice_ptr_parts, word, Transfer};
|
||||
use crate::gpio::sealed::{AFType, Pin as _};
|
||||
use crate::gpio::{AnyPin, Pull};
|
||||
use crate::pac::spi::{regs, vals, Spi as Regs};
|
||||
@ -78,7 +77,7 @@ pub struct Spi<'d, T: Instance, Tx, Rx> {
|
||||
miso: Option<PeripheralRef<'d, AnyPin>>,
|
||||
txdma: PeripheralRef<'d, Tx>,
|
||||
rxdma: PeripheralRef<'d, Rx>,
|
||||
current_word_size: WordSize,
|
||||
current_word_size: word_impl::Config,
|
||||
}
|
||||
|
||||
impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
@ -178,10 +177,27 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_txonly_nosck(
|
||||
peri: impl Peripheral<P = T> + 'd,
|
||||
mosi: impl Peripheral<P = impl MosiPin<T>> + 'd,
|
||||
txdma: impl Peripheral<P = Tx> + 'd,
|
||||
rxdma: impl Peripheral<P = Rx> + 'd, // TODO: remove
|
||||
freq: Hertz,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(mosi);
|
||||
unsafe {
|
||||
mosi.set_as_af_pull(mosi.af_num(), AFType::OutputPushPull, Pull::Down);
|
||||
mosi.set_speed(crate::gpio::Speed::Medium);
|
||||
}
|
||||
|
||||
Self::new_inner(peri, None, Some(mosi.map_into()), None, txdma, rxdma, freq, config)
|
||||
}
|
||||
|
||||
/// Useful for on chip peripherals like SUBGHZ which are hardwired.
|
||||
/// The bus can optionally be exposed externally with `Spi::new()` still.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn new_internal(
|
||||
pub fn new_subghz(
|
||||
peri: impl Peripheral<P = T> + 'd,
|
||||
txdma: impl Peripheral<P = Tx> + 'd,
|
||||
rxdma: impl Peripheral<P = Rx> + 'd,
|
||||
@ -234,14 +250,15 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
if mosi.is_none() {
|
||||
w.set_rxonly(vals::Rxonly::OUTPUTDISABLED);
|
||||
}
|
||||
w.set_dff(WordSize::EightBit.dff())
|
||||
w.set_dff(<u8 as sealed::Word>::CONFIG)
|
||||
});
|
||||
}
|
||||
#[cfg(spi_v2)]
|
||||
unsafe {
|
||||
T::REGS.cr2().modify(|w| {
|
||||
w.set_frxth(WordSize::EightBit.frxth());
|
||||
w.set_ds(WordSize::EightBit.ds());
|
||||
let (ds, frxth) = <u8 as sealed::Word>::CONFIG;
|
||||
w.set_frxth(frxth);
|
||||
w.set_ds(ds);
|
||||
w.set_ssoe(false);
|
||||
});
|
||||
T::REGS.cr1().modify(|w| {
|
||||
@ -279,7 +296,8 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
T::REGS.cfg1().modify(|w| {
|
||||
w.set_crcen(false);
|
||||
w.set_mbr(br);
|
||||
w.set_dsize(WordSize::EightBit.dsize());
|
||||
w.set_dsize(<u8 as sealed::Word>::CONFIG);
|
||||
w.set_fthlv(vals::Fthlv::ONEFRAME);
|
||||
});
|
||||
T::REGS.cr2().modify(|w| {
|
||||
w.set_tsize(0);
|
||||
@ -297,7 +315,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
miso,
|
||||
txdma,
|
||||
rxdma,
|
||||
current_word_size: WordSize::EightBit,
|
||||
current_word_size: <u8 as sealed::Word>::CONFIG,
|
||||
}
|
||||
}
|
||||
|
||||
@ -355,7 +373,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_word_size(&mut self, word_size: WordSize) {
|
||||
fn set_word_size(&mut self, word_size: word_impl::Config) {
|
||||
if self.current_word_size == word_size {
|
||||
return;
|
||||
}
|
||||
@ -364,7 +382,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
unsafe {
|
||||
T::REGS.cr1().modify(|reg| {
|
||||
reg.set_spe(false);
|
||||
reg.set_dff(word_size.dff())
|
||||
reg.set_dff(word_size)
|
||||
});
|
||||
T::REGS.cr1().modify(|reg| {
|
||||
reg.set_spe(true);
|
||||
@ -376,8 +394,8 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
w.set_spe(false);
|
||||
});
|
||||
T::REGS.cr2().modify(|w| {
|
||||
w.set_frxth(word_size.frxth());
|
||||
w.set_ds(word_size.ds());
|
||||
w.set_frxth(word_size.1);
|
||||
w.set_ds(word_size.0);
|
||||
});
|
||||
T::REGS.cr1().modify(|w| {
|
||||
w.set_spe(true);
|
||||
@ -393,7 +411,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
w.set_spe(false);
|
||||
});
|
||||
T::REGS.cfg1().modify(|w| {
|
||||
w.set_dsize(word_size.dsize());
|
||||
w.set_dsize(word_size);
|
||||
});
|
||||
T::REGS.cr1().modify(|w| {
|
||||
w.set_csusp(false);
|
||||
@ -412,7 +430,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.set_word_size(W::WORDSIZE);
|
||||
self.set_word_size(W::CONFIG);
|
||||
unsafe {
|
||||
T::REGS.cr1().modify(|w| {
|
||||
w.set_spe(false);
|
||||
@ -421,8 +439,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
|
||||
let tx_request = self.txdma.request();
|
||||
let tx_dst = T::REGS.tx_ptr();
|
||||
unsafe { self.txdma.start_write(tx_request, data, tx_dst, Default::default()) }
|
||||
let tx_f = Transfer::new(&mut self.txdma);
|
||||
let tx_f = unsafe { Transfer::new_write(&mut self.txdma, tx_request, data, tx_dst, Default::default()) };
|
||||
|
||||
unsafe {
|
||||
set_txdmaen(T::REGS, true);
|
||||
@ -451,7 +468,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.set_word_size(W::WORDSIZE);
|
||||
self.set_word_size(W::CONFIG);
|
||||
unsafe {
|
||||
T::REGS.cr1().modify(|w| {
|
||||
w.set_spe(false);
|
||||
@ -468,13 +485,21 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
|
||||
let rx_request = self.rxdma.request();
|
||||
let rx_src = T::REGS.rx_ptr();
|
||||
unsafe { self.rxdma.start_read(rx_request, rx_src, data, Default::default()) };
|
||||
let rx_f = Transfer::new(&mut self.rxdma);
|
||||
let rx_f = unsafe { Transfer::new_read(&mut self.rxdma, rx_request, rx_src, data, Default::default()) };
|
||||
|
||||
let tx_request = self.txdma.request();
|
||||
let tx_dst = T::REGS.tx_ptr();
|
||||
let clock_byte = 0x00u8;
|
||||
let tx_f = crate::dma::write_repeated(&mut self.txdma, tx_request, &clock_byte, clock_byte_count, tx_dst);
|
||||
let tx_f = unsafe {
|
||||
Transfer::new_write_repeated(
|
||||
&mut self.txdma,
|
||||
tx_request,
|
||||
&clock_byte,
|
||||
clock_byte_count,
|
||||
tx_dst,
|
||||
Default::default(),
|
||||
)
|
||||
};
|
||||
|
||||
unsafe {
|
||||
set_txdmaen(T::REGS, true);
|
||||
@ -506,7 +531,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.set_word_size(W::WORDSIZE);
|
||||
self.set_word_size(W::CONFIG);
|
||||
unsafe {
|
||||
T::REGS.cr1().modify(|w| {
|
||||
w.set_spe(false);
|
||||
@ -521,13 +546,11 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
|
||||
let rx_request = self.rxdma.request();
|
||||
let rx_src = T::REGS.rx_ptr();
|
||||
unsafe { self.rxdma.start_read(rx_request, rx_src, read, Default::default()) };
|
||||
let rx_f = Transfer::new(&mut self.rxdma);
|
||||
let rx_f = unsafe { Transfer::new_read_raw(&mut self.rxdma, rx_request, rx_src, read, Default::default()) };
|
||||
|
||||
let tx_request = self.txdma.request();
|
||||
let tx_dst = T::REGS.tx_ptr();
|
||||
unsafe { self.txdma.start_write(tx_request, write, tx_dst, Default::default()) }
|
||||
let tx_f = Transfer::new(&mut self.txdma);
|
||||
let tx_f = unsafe { Transfer::new_write_raw(&mut self.txdma, tx_request, write, tx_dst, Default::default()) };
|
||||
|
||||
unsafe {
|
||||
set_txdmaen(T::REGS, true);
|
||||
@ -566,7 +589,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
pub fn blocking_write<W: Word>(&mut self, words: &[W]) -> Result<(), Error> {
|
||||
unsafe { T::REGS.cr1().modify(|w| w.set_spe(true)) }
|
||||
flush_rx_fifo(T::REGS);
|
||||
self.set_word_size(W::WORDSIZE);
|
||||
self.set_word_size(W::CONFIG);
|
||||
for word in words.iter() {
|
||||
let _ = transfer_word(T::REGS, *word)?;
|
||||
}
|
||||
@ -576,7 +599,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
pub fn blocking_read<W: Word>(&mut self, words: &mut [W]) -> Result<(), Error> {
|
||||
unsafe { T::REGS.cr1().modify(|w| w.set_spe(true)) }
|
||||
flush_rx_fifo(T::REGS);
|
||||
self.set_word_size(W::WORDSIZE);
|
||||
self.set_word_size(W::CONFIG);
|
||||
for word in words.iter_mut() {
|
||||
*word = transfer_word(T::REGS, W::default())?;
|
||||
}
|
||||
@ -586,7 +609,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
pub fn blocking_transfer_in_place<W: Word>(&mut self, words: &mut [W]) -> Result<(), Error> {
|
||||
unsafe { T::REGS.cr1().modify(|w| w.set_spe(true)) }
|
||||
flush_rx_fifo(T::REGS);
|
||||
self.set_word_size(W::WORDSIZE);
|
||||
self.set_word_size(W::CONFIG);
|
||||
for word in words.iter_mut() {
|
||||
*word = transfer_word(T::REGS, *word)?;
|
||||
}
|
||||
@ -596,7 +619,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
pub fn blocking_transfer<W: Word>(&mut self, read: &mut [W], write: &[W]) -> Result<(), Error> {
|
||||
unsafe { T::REGS.cr1().modify(|w| w.set_spe(true)) }
|
||||
flush_rx_fifo(T::REGS);
|
||||
self.set_word_size(W::WORDSIZE);
|
||||
self.set_word_size(W::CONFIG);
|
||||
let len = read.len().max(write.len());
|
||||
for i in 0..len {
|
||||
let wb = write.get(i).copied().unwrap_or_default();
|
||||
@ -928,70 +951,89 @@ pub(crate) mod sealed {
|
||||
const REGS: Regs;
|
||||
}
|
||||
|
||||
pub trait Word: Copy + 'static {
|
||||
const WORDSIZE: WordSize;
|
||||
}
|
||||
|
||||
impl Word for u8 {
|
||||
const WORDSIZE: WordSize = WordSize::EightBit;
|
||||
}
|
||||
impl Word for u16 {
|
||||
const WORDSIZE: WordSize = WordSize::SixteenBit;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialOrd, PartialEq)]
|
||||
pub enum WordSize {
|
||||
EightBit,
|
||||
SixteenBit,
|
||||
}
|
||||
|
||||
impl WordSize {
|
||||
#[cfg(any(spi_v1, spi_f1))]
|
||||
pub fn dff(&self) -> vals::Dff {
|
||||
match self {
|
||||
WordSize::EightBit => vals::Dff::EIGHTBIT,
|
||||
WordSize::SixteenBit => vals::Dff::SIXTEENBIT,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(spi_v2)]
|
||||
pub fn ds(&self) -> vals::Ds {
|
||||
match self {
|
||||
WordSize::EightBit => vals::Ds::EIGHTBIT,
|
||||
WordSize::SixteenBit => vals::Ds::SIXTEENBIT,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(spi_v2)]
|
||||
pub fn frxth(&self) -> vals::Frxth {
|
||||
match self {
|
||||
WordSize::EightBit => vals::Frxth::QUARTER,
|
||||
WordSize::SixteenBit => vals::Frxth::HALF,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(spi_v3, spi_v4, spi_v5))]
|
||||
pub fn dsize(&self) -> u8 {
|
||||
match self {
|
||||
WordSize::EightBit => 0b0111,
|
||||
WordSize::SixteenBit => 0b1111,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(spi_v3, spi_v4, spi_v5))]
|
||||
pub fn _frxth(&self) -> vals::Fthlv {
|
||||
match self {
|
||||
WordSize::EightBit => vals::Fthlv::ONEFRAME,
|
||||
WordSize::SixteenBit => vals::Fthlv::ONEFRAME,
|
||||
}
|
||||
}
|
||||
pub trait Word {
|
||||
const CONFIG: word_impl::Config;
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Word: Copy + 'static + sealed::Word + Default + crate::dma::Word {}
|
||||
pub trait Word: word::Word + sealed::Word {}
|
||||
|
||||
impl Word for u8 {}
|
||||
impl Word for u16 {}
|
||||
macro_rules! impl_word {
|
||||
($T:ty, $config:expr) => {
|
||||
impl sealed::Word for $T {
|
||||
const CONFIG: Config = $config;
|
||||
}
|
||||
impl Word for $T {}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(any(spi_v1, spi_f1))]
|
||||
mod word_impl {
|
||||
use super::*;
|
||||
|
||||
pub type Config = vals::Dff;
|
||||
|
||||
impl_word!(u8, vals::Dff::EIGHTBIT);
|
||||
impl_word!(u16, vals::Dff::SIXTEENBIT);
|
||||
}
|
||||
|
||||
#[cfg(any(spi_v2))]
|
||||
mod word_impl {
|
||||
use super::*;
|
||||
|
||||
pub type Config = (vals::Ds, vals::Frxth);
|
||||
|
||||
impl_word!(word::U4, (vals::Ds::FOURBIT, vals::Frxth::QUARTER));
|
||||
impl_word!(word::U5, (vals::Ds::FIVEBIT, vals::Frxth::QUARTER));
|
||||
impl_word!(word::U6, (vals::Ds::SIXBIT, vals::Frxth::QUARTER));
|
||||
impl_word!(word::U7, (vals::Ds::SEVENBIT, vals::Frxth::QUARTER));
|
||||
impl_word!(u8, (vals::Ds::EIGHTBIT, vals::Frxth::QUARTER));
|
||||
impl_word!(word::U9, (vals::Ds::NINEBIT, vals::Frxth::HALF));
|
||||
impl_word!(word::U10, (vals::Ds::TENBIT, vals::Frxth::HALF));
|
||||
impl_word!(word::U11, (vals::Ds::ELEVENBIT, vals::Frxth::HALF));
|
||||
impl_word!(word::U12, (vals::Ds::TWELVEBIT, vals::Frxth::HALF));
|
||||
impl_word!(word::U13, (vals::Ds::THIRTEENBIT, vals::Frxth::HALF));
|
||||
impl_word!(word::U14, (vals::Ds::FOURTEENBIT, vals::Frxth::HALF));
|
||||
impl_word!(word::U15, (vals::Ds::FIFTEENBIT, vals::Frxth::HALF));
|
||||
impl_word!(u16, (vals::Ds::SIXTEENBIT, vals::Frxth::HALF));
|
||||
}
|
||||
|
||||
#[cfg(any(spi_v3, spi_v4, spi_v5))]
|
||||
mod word_impl {
|
||||
use super::*;
|
||||
|
||||
pub type Config = u8;
|
||||
|
||||
impl_word!(word::U4, 4 - 1);
|
||||
impl_word!(word::U5, 5 - 1);
|
||||
impl_word!(word::U6, 6 - 1);
|
||||
impl_word!(word::U7, 7 - 1);
|
||||
impl_word!(u8, 8 - 1);
|
||||
impl_word!(word::U9, 9 - 1);
|
||||
impl_word!(word::U10, 10 - 1);
|
||||
impl_word!(word::U11, 11 - 1);
|
||||
impl_word!(word::U12, 12 - 1);
|
||||
impl_word!(word::U13, 13 - 1);
|
||||
impl_word!(word::U14, 14 - 1);
|
||||
impl_word!(word::U15, 15 - 1);
|
||||
impl_word!(u16, 16 - 1);
|
||||
impl_word!(word::U17, 17 - 1);
|
||||
impl_word!(word::U18, 18 - 1);
|
||||
impl_word!(word::U19, 19 - 1);
|
||||
impl_word!(word::U20, 20 - 1);
|
||||
impl_word!(word::U21, 21 - 1);
|
||||
impl_word!(word::U22, 22 - 1);
|
||||
impl_word!(word::U23, 23 - 1);
|
||||
impl_word!(word::U24, 24 - 1);
|
||||
impl_word!(word::U25, 25 - 1);
|
||||
impl_word!(word::U26, 26 - 1);
|
||||
impl_word!(word::U27, 27 - 1);
|
||||
impl_word!(word::U28, 28 - 1);
|
||||
impl_word!(word::U29, 29 - 1);
|
||||
impl_word!(word::U30, 30 - 1);
|
||||
impl_word!(word::U31, 31 - 1);
|
||||
impl_word!(u32, 32 - 1);
|
||||
}
|
||||
|
||||
pub trait Instance: Peripheral<P = Self> + sealed::Instance + RccPeripheral {}
|
||||
pin_trait!(SckPin, Instance);
|
||||
|
@ -1,160 +0,0 @@
|
||||
/// Bit synchronization.
|
||||
///
|
||||
/// This must be cleared to `0x00` (the reset value) when using packet types
|
||||
/// other than LoRa.
|
||||
///
|
||||
/// Argument of [`set_bit_sync`](crate::subghz::SubGhz::set_bit_sync).
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct BitSync {
|
||||
val: u8,
|
||||
}
|
||||
|
||||
impl BitSync {
|
||||
/// Bit synchronization register reset value.
|
||||
pub const RESET: BitSync = BitSync { val: 0x00 };
|
||||
|
||||
/// Create a new [`BitSync`] structure from a raw value.
|
||||
///
|
||||
/// Reserved bits will be masked.
|
||||
pub const fn from_raw(raw: u8) -> Self {
|
||||
Self { val: raw & 0x70 }
|
||||
}
|
||||
|
||||
/// Get the raw value of the [`BitSync`] register.
|
||||
pub const fn as_bits(&self) -> u8 {
|
||||
self.val
|
||||
}
|
||||
|
||||
/// LoRa simple bit synchronization enable.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Enable simple bit synchronization.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::BitSync;
|
||||
///
|
||||
/// const BIT_SYNC: BitSync = BitSync::RESET.set_simple_bit_sync_en(true);
|
||||
/// # assert_eq!(u8::from(BIT_SYNC), 0x40u8);
|
||||
/// ```
|
||||
#[must_use = "set_simple_bit_sync_en returns a modified BitSync"]
|
||||
pub const fn set_simple_bit_sync_en(mut self, en: bool) -> BitSync {
|
||||
if en {
|
||||
self.val |= 1 << 6;
|
||||
} else {
|
||||
self.val &= !(1 << 6);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns `true` if simple bit synchronization is enabled.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::BitSync;
|
||||
///
|
||||
/// let bs: BitSync = BitSync::RESET;
|
||||
/// assert_eq!(bs.simple_bit_sync_en(), false);
|
||||
/// let bs: BitSync = bs.set_simple_bit_sync_en(true);
|
||||
/// assert_eq!(bs.simple_bit_sync_en(), true);
|
||||
/// let bs: BitSync = bs.set_simple_bit_sync_en(false);
|
||||
/// assert_eq!(bs.simple_bit_sync_en(), false);
|
||||
/// ```
|
||||
pub const fn simple_bit_sync_en(&self) -> bool {
|
||||
self.val & (1 << 6) != 0
|
||||
}
|
||||
|
||||
/// LoRa RX data inversion.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Invert receive data.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::BitSync;
|
||||
///
|
||||
/// const BIT_SYNC: BitSync = BitSync::RESET.set_rx_data_inv(true);
|
||||
/// # assert_eq!(u8::from(BIT_SYNC), 0x20u8);
|
||||
/// ```
|
||||
#[must_use = "set_rx_data_inv returns a modified BitSync"]
|
||||
pub const fn set_rx_data_inv(mut self, inv: bool) -> BitSync {
|
||||
if inv {
|
||||
self.val |= 1 << 5;
|
||||
} else {
|
||||
self.val &= !(1 << 5);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns `true` if LoRa RX data is inverted.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::BitSync;
|
||||
///
|
||||
/// let bs: BitSync = BitSync::RESET;
|
||||
/// assert_eq!(bs.rx_data_inv(), false);
|
||||
/// let bs: BitSync = bs.set_rx_data_inv(true);
|
||||
/// assert_eq!(bs.rx_data_inv(), true);
|
||||
/// let bs: BitSync = bs.set_rx_data_inv(false);
|
||||
/// assert_eq!(bs.rx_data_inv(), false);
|
||||
/// ```
|
||||
pub const fn rx_data_inv(&self) -> bool {
|
||||
self.val & (1 << 5) != 0
|
||||
}
|
||||
|
||||
/// LoRa normal bit synchronization enable.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Enable normal bit synchronization.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::BitSync;
|
||||
///
|
||||
/// const BIT_SYNC: BitSync = BitSync::RESET.set_norm_bit_sync_en(true);
|
||||
/// # assert_eq!(u8::from(BIT_SYNC), 0x10u8);
|
||||
/// ```
|
||||
#[must_use = "set_norm_bit_sync_en returns a modified BitSync"]
|
||||
pub const fn set_norm_bit_sync_en(mut self, en: bool) -> BitSync {
|
||||
if en {
|
||||
self.val |= 1 << 4;
|
||||
} else {
|
||||
self.val &= !(1 << 4);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns `true` if normal bit synchronization is enabled.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::BitSync;
|
||||
///
|
||||
/// let bs: BitSync = BitSync::RESET;
|
||||
/// assert_eq!(bs.norm_bit_sync_en(), false);
|
||||
/// let bs: BitSync = bs.set_norm_bit_sync_en(true);
|
||||
/// assert_eq!(bs.norm_bit_sync_en(), true);
|
||||
/// let bs: BitSync = bs.set_norm_bit_sync_en(false);
|
||||
/// assert_eq!(bs.norm_bit_sync_en(), false);
|
||||
/// ```
|
||||
pub const fn norm_bit_sync_en(&self) -> bool {
|
||||
self.val & (1 << 4) != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BitSync> for u8 {
|
||||
fn from(bs: BitSync) -> Self {
|
||||
bs.val
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BitSync {
|
||||
fn default() -> Self {
|
||||
Self::RESET
|
||||
}
|
||||
}
|
@ -1,230 +0,0 @@
|
||||
use super::Timeout;
|
||||
|
||||
/// Number of symbols used for channel activity detection scans.
|
||||
///
|
||||
/// Argument of [`CadParams::set_num_symbol`].
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum NbCadSymbol {
|
||||
/// 1 symbol.
|
||||
S1 = 0x0,
|
||||
/// 2 symbols.
|
||||
S2 = 0x1,
|
||||
/// 4 symbols.
|
||||
S4 = 0x2,
|
||||
/// 8 symbols.
|
||||
S8 = 0x3,
|
||||
/// 16 symbols.
|
||||
S16 = 0x4,
|
||||
}
|
||||
|
||||
/// Mode to enter after a channel activity detection scan is finished.
|
||||
///
|
||||
/// Argument of [`CadParams::set_exit_mode`].
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum ExitMode {
|
||||
/// Standby with RC 13 MHz mode entry after CAD.
|
||||
Standby = 0,
|
||||
/// Standby with RC 13 MHz mode after CAD if no LoRa symbol is detected
|
||||
/// during the CAD scan.
|
||||
/// If a LoRa symbol is detected, the sub-GHz radio stays in RX mode
|
||||
/// until a packet is received or until the CAD timeout is reached.
|
||||
StandbyLoRa = 1,
|
||||
}
|
||||
|
||||
/// Channel activity detection (CAD) parameters.
|
||||
///
|
||||
/// Argument of [`set_cad_params`].
|
||||
///
|
||||
/// # Recommended CAD settings
|
||||
///
|
||||
/// This is taken directly from the datasheet.
|
||||
///
|
||||
/// "The correct values selected in the table below must be carefully tested to
|
||||
/// ensure a good detection at sensitivity level and to limit the number of
|
||||
/// false detections"
|
||||
///
|
||||
/// | SF (Spreading Factor) | [`set_det_peak`] | [`set_det_min`] |
|
||||
/// |-----------------------|------------------|-----------------|
|
||||
/// | 5 | 0x18 | 0x10 |
|
||||
/// | 6 | 0x19 | 0x10 |
|
||||
/// | 7 | 0x20 | 0x10 |
|
||||
/// | 8 | 0x21 | 0x10 |
|
||||
/// | 9 | 0x22 | 0x10 |
|
||||
/// | 10 | 0x23 | 0x10 |
|
||||
/// | 11 | 0x24 | 0x10 |
|
||||
/// | 12 | 0x25 | 0x10 |
|
||||
///
|
||||
/// [`set_cad_params`]: crate::subghz::SubGhz::set_cad_params
|
||||
/// [`set_det_peak`]: crate::subghz::CadParams::set_det_peak
|
||||
/// [`set_det_min`]: crate::subghz::CadParams::set_det_min
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct CadParams {
|
||||
buf: [u8; 8],
|
||||
}
|
||||
|
||||
impl CadParams {
|
||||
/// Create a new `CadParams`.
|
||||
///
|
||||
/// This is the same as `default`, but in a `const` function.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::CadParams;
|
||||
///
|
||||
/// const CAD_PARAMS: CadParams = CadParams::new();
|
||||
/// assert_eq!(CAD_PARAMS, CadParams::default());
|
||||
/// ```
|
||||
pub const fn new() -> CadParams {
|
||||
CadParams {
|
||||
buf: [super::OpCode::SetCadParams as u8, 0, 0, 0, 0, 0, 0, 0],
|
||||
}
|
||||
.set_num_symbol(NbCadSymbol::S1)
|
||||
.set_det_peak(0x18)
|
||||
.set_det_min(0x10)
|
||||
.set_exit_mode(ExitMode::Standby)
|
||||
}
|
||||
|
||||
/// Number of symbols used for a CAD scan.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Set the number of symbols to 4.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::{CadParams, NbCadSymbol};
|
||||
///
|
||||
/// const CAD_PARAMS: CadParams = CadParams::new().set_num_symbol(NbCadSymbol::S4);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[1], 0x2);
|
||||
/// ```
|
||||
#[must_use = "set_num_symbol returns a modified CadParams"]
|
||||
pub const fn set_num_symbol(mut self, nb: NbCadSymbol) -> CadParams {
|
||||
self.buf[1] = nb as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Used with [`set_det_min`] to correlate the LoRa symbol.
|
||||
///
|
||||
/// See the table in [`CadParams`] docs for recommended values.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Setting the recommended value for a spreading factor of 7.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::CadParams;
|
||||
///
|
||||
/// const CAD_PARAMS: CadParams = CadParams::new().set_det_peak(0x20).set_det_min(0x10);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[2], 0x20);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[3], 0x10);
|
||||
/// ```
|
||||
///
|
||||
/// [`set_det_min`]: crate::subghz::CadParams::set_det_min
|
||||
#[must_use = "set_det_peak returns a modified CadParams"]
|
||||
pub const fn set_det_peak(mut self, peak: u8) -> CadParams {
|
||||
self.buf[2] = peak;
|
||||
self
|
||||
}
|
||||
|
||||
/// Used with [`set_det_peak`] to correlate the LoRa symbol.
|
||||
///
|
||||
/// See the table in [`CadParams`] docs for recommended values.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Setting the recommended value for a spreading factor of 6.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::CadParams;
|
||||
///
|
||||
/// const CAD_PARAMS: CadParams = CadParams::new().set_det_peak(0x18).set_det_min(0x10);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[2], 0x18);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[3], 0x10);
|
||||
/// ```
|
||||
///
|
||||
/// [`set_det_peak`]: crate::subghz::CadParams::set_det_peak
|
||||
#[must_use = "set_det_min returns a modified CadParams"]
|
||||
pub const fn set_det_min(mut self, min: u8) -> CadParams {
|
||||
self.buf[3] = min;
|
||||
self
|
||||
}
|
||||
|
||||
/// Mode to enter after a channel activity detection scan is finished.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::{CadParams, ExitMode};
|
||||
///
|
||||
/// const CAD_PARAMS: CadParams = CadParams::new().set_exit_mode(ExitMode::Standby);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[4], 0x00);
|
||||
/// # assert_eq!(CAD_PARAMS.set_exit_mode(ExitMode::StandbyLoRa).as_slice()[4], 0x01);
|
||||
/// ```
|
||||
#[must_use = "set_exit_mode returns a modified CadParams"]
|
||||
pub const fn set_exit_mode(mut self, mode: ExitMode) -> CadParams {
|
||||
self.buf[4] = mode as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the timeout.
|
||||
///
|
||||
/// This is only used with [`ExitMode::StandbyLoRa`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::{CadParams, ExitMode, Timeout};
|
||||
///
|
||||
/// const TIMEOUT: Timeout = Timeout::from_raw(0x123456);
|
||||
/// const CAD_PARAMS: CadParams = CadParams::new()
|
||||
/// .set_exit_mode(ExitMode::StandbyLoRa)
|
||||
/// .set_timeout(TIMEOUT);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[4], 0x01);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[5], 0x12);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[6], 0x34);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[7], 0x56);
|
||||
/// ```
|
||||
#[must_use = "set_timeout returns a modified CadParams"]
|
||||
pub const fn set_timeout(mut self, to: Timeout) -> CadParams {
|
||||
let to_bytes: [u8; 3] = to.as_bytes();
|
||||
self.buf[5] = to_bytes[0];
|
||||
self.buf[6] = to_bytes[1];
|
||||
self.buf[7] = to_bytes[2];
|
||||
self
|
||||
}
|
||||
|
||||
/// Extracts a slice containing the packet.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::{CadParams, ExitMode, NbCadSymbol, Timeout};
|
||||
///
|
||||
/// const TIMEOUT: Timeout = Timeout::from_raw(0x123456);
|
||||
/// const CAD_PARAMS: CadParams = CadParams::new()
|
||||
/// .set_num_symbol(NbCadSymbol::S4)
|
||||
/// .set_det_peak(0x18)
|
||||
/// .set_det_min(0x10)
|
||||
/// .set_exit_mode(ExitMode::StandbyLoRa)
|
||||
/// .set_timeout(TIMEOUT);
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// CAD_PARAMS.as_slice(),
|
||||
/// &[0x88, 0x02, 0x18, 0x10, 0x01, 0x12, 0x34, 0x56]
|
||||
/// );
|
||||
/// ```
|
||||
pub const fn as_slice(&self) -> &[u8] {
|
||||
&self.buf
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CadParams {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
/// Image calibration.
|
||||
///
|
||||
/// Argument of [`calibrate_image`].
|
||||
///
|
||||
/// [`calibrate_image`]: crate::subghz::SubGhz::calibrate_image
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct CalibrateImage(pub(crate) u8, pub(crate) u8);
|
||||
|
||||
impl CalibrateImage {
|
||||
/// Image calibration for the 430 - 440 MHz ISM band.
|
||||
pub const ISM_430_440: CalibrateImage = CalibrateImage(0x6B, 0x6F);
|
||||
|
||||
/// Image calibration for the 470 - 510 MHz ISM band.
|
||||
pub const ISM_470_510: CalibrateImage = CalibrateImage(0x75, 0x81);
|
||||
|
||||
/// Image calibration for the 779 - 787 MHz ISM band.
|
||||
pub const ISM_779_787: CalibrateImage = CalibrateImage(0xC1, 0xC5);
|
||||
|
||||
/// Image calibration for the 863 - 870 MHz ISM band.
|
||||
pub const ISM_863_870: CalibrateImage = CalibrateImage(0xD7, 0xDB);
|
||||
|
||||
/// Image calibration for the 902 - 928 MHz ISM band.
|
||||
pub const ISM_902_928: CalibrateImage = CalibrateImage(0xE1, 0xE9);
|
||||
|
||||
/// Create a new `CalibrateImage` structure from raw values.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::CalibrateImage;
|
||||
///
|
||||
/// const CAL: CalibrateImage = CalibrateImage::new(0xE1, 0xE9);
|
||||
/// assert_eq!(CAL, CalibrateImage::ISM_902_928);
|
||||
/// ```
|
||||
pub const fn new(f1: u8, f2: u8) -> CalibrateImage {
|
||||
CalibrateImage(f1, f2)
|
||||
}
|
||||
|
||||
/// Create a new `CalibrateImage` structure from two frequencies.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// The units for `freq1` and `freq2` are in MHz.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// * Panics if `freq1` is less than `freq2`.
|
||||
/// * Panics if `freq1` or `freq2` is not a multiple of 4MHz.
|
||||
/// * Panics if `freq1` or `freq2` is greater than `1020`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Create an image calibration for the 430 - 440 MHz ISM band.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::CalibrateImage;
|
||||
///
|
||||
/// let cal: CalibrateImage = CalibrateImage::from_freq(428, 444);
|
||||
/// assert_eq!(cal, CalibrateImage::ISM_430_440);
|
||||
/// ```
|
||||
pub fn from_freq(freq1: u16, freq2: u16) -> CalibrateImage {
|
||||
assert!(freq2 >= freq1);
|
||||
assert_eq!(freq1 % 4, 0);
|
||||
assert_eq!(freq2 % 4, 0);
|
||||
assert!(freq1 <= 1020);
|
||||
assert!(freq2 <= 1020);
|
||||
CalibrateImage((freq1 / 4) as u8, (freq2 / 4) as u8)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CalibrateImage {
|
||||
fn default() -> Self {
|
||||
CalibrateImage::new(0xE1, 0xE9)
|
||||
}
|
||||
}
|
||||
|
||||
/// Block calibration.
|
||||
///
|
||||
/// Argument of [`calibrate`].
|
||||
///
|
||||
/// [`calibrate`]: crate::subghz::SubGhz::calibrate
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum Calibrate {
|
||||
/// Image calibration
|
||||
Image = 1 << 6,
|
||||
/// RF-ADC bulk P calibration
|
||||
AdcBulkP = 1 << 5,
|
||||
/// RF-ADC bulk N calibration
|
||||
AdcBulkN = 1 << 4,
|
||||
/// RF-ADC pulse calibration
|
||||
AdcPulse = 1 << 3,
|
||||
/// RF-PLL calibration
|
||||
Pll = 1 << 2,
|
||||
/// Sub-GHz radio RC 13 MHz calibration
|
||||
Rc13M = 1 << 1,
|
||||
/// Sub-GHz radio RC 64 kHz calibration
|
||||
Rc64K = 1,
|
||||
}
|
||||
|
||||
impl Calibrate {
|
||||
/// Get the bitmask for the block calibration.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::Calibrate;
|
||||
///
|
||||
/// assert_eq!(Calibrate::Image.mask(), 0b0100_0000);
|
||||
/// assert_eq!(Calibrate::AdcBulkP.mask(), 0b0010_0000);
|
||||
/// assert_eq!(Calibrate::AdcBulkN.mask(), 0b0001_0000);
|
||||
/// assert_eq!(Calibrate::AdcPulse.mask(), 0b0000_1000);
|
||||
/// assert_eq!(Calibrate::Pll.mask(), 0b0000_0100);
|
||||
/// assert_eq!(Calibrate::Rc13M.mask(), 0b0000_0010);
|
||||
/// assert_eq!(Calibrate::Rc64K.mask(), 0b0000_0001);
|
||||
/// ```
|
||||
pub const fn mask(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
/// Fallback mode after successful packet transmission or packet reception.
|
||||
///
|
||||
/// Argument of [`set_tx_rx_fallback_mode`].
|
||||
///
|
||||
/// [`set_tx_rx_fallback_mode`]: crate::subghz::SubGhz::set_tx_rx_fallback_mode.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum FallbackMode {
|
||||
/// Standby mode entry.
|
||||
Standby = 0x20,
|
||||
/// Standby with HSE32 enabled.
|
||||
StandbyHse = 0x30,
|
||||
/// Frequency synthesizer entry.
|
||||
Fs = 0x40,
|
||||
}
|
||||
|
||||
impl From<FallbackMode> for u8 {
|
||||
fn from(fm: FallbackMode) -> Self {
|
||||
fm as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FallbackMode {
|
||||
/// Default fallback mode after power-on reset.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::FallbackMode;
|
||||
///
|
||||
/// assert_eq!(FallbackMode::default(), FallbackMode::Standby);
|
||||
/// ```
|
||||
fn default() -> Self {
|
||||
FallbackMode::Standby
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
use super::ValueError;
|
||||
|
||||
/// HSE32 load capacitor trimming.
|
||||
///
|
||||
/// Argument of [`set_hse_in_trim`] and [`set_hse_out_trim`].
|
||||
///
|
||||
/// [`set_hse_in_trim`]: crate::subghz::SubGhz::set_hse_in_trim
|
||||
/// [`set_hse_out_trim`]: crate::subghz::SubGhz::set_hse_out_trim
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct HseTrim {
|
||||
val: u8,
|
||||
}
|
||||
|
||||
impl HseTrim {
|
||||
/// Maximum capacitor value, ~33.4 pF
|
||||
pub const MAX: HseTrim = HseTrim::from_raw(0x2F);
|
||||
|
||||
/// Minimum capacitor value, ~11.3 pF
|
||||
pub const MIN: HseTrim = HseTrim::from_raw(0x00);
|
||||
|
||||
/// Power-on-reset capacitor value, ~20.3 pF
|
||||
///
|
||||
/// This is the same as `default`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::HseTrim;
|
||||
///
|
||||
/// assert_eq!(HseTrim::POR, HseTrim::default());
|
||||
/// ```
|
||||
pub const POR: HseTrim = HseTrim::from_raw(0x12);
|
||||
|
||||
/// Create a new [`HseTrim`] structure from a raw value.
|
||||
///
|
||||
/// Values greater than the maximum of `0x2F` will be set to the maximum.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::HseTrim;
|
||||
///
|
||||
/// assert_eq!(HseTrim::from_raw(0xFF), HseTrim::MAX);
|
||||
/// assert_eq!(HseTrim::from_raw(0x2F), HseTrim::MAX);
|
||||
/// assert_eq!(HseTrim::from_raw(0x00), HseTrim::MIN);
|
||||
/// ```
|
||||
pub const fn from_raw(raw: u8) -> HseTrim {
|
||||
if raw > 0x2F {
|
||||
HseTrim { val: 0x2F }
|
||||
} else {
|
||||
HseTrim { val: raw }
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a HSE trim value from farads.
|
||||
///
|
||||
/// Values greater than the maximum of 33.4 pF will be set to the maximum.
|
||||
/// Values less than the minimum of 11.3 pF will be set to the minimum.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::HseTrim;
|
||||
///
|
||||
/// assert!(HseTrim::from_farads(1.0).is_err());
|
||||
/// assert!(HseTrim::from_farads(1e-12).is_err());
|
||||
/// assert_eq!(HseTrim::from_farads(20.2e-12), Ok(HseTrim::default()));
|
||||
/// ```
|
||||
pub fn from_farads(farads: f32) -> Result<HseTrim, ValueError<f32>> {
|
||||
const MAX: f32 = 33.4E-12;
|
||||
const MIN: f32 = 11.3E-12;
|
||||
if farads > MAX {
|
||||
Err(ValueError::too_high(farads, MAX))
|
||||
} else if farads < MIN {
|
||||
Err(ValueError::too_low(farads, MIN))
|
||||
} else {
|
||||
Ok(HseTrim::from_raw(((farads - 11.3e-12) / 0.47e-12) as u8))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the capacitance as farads.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::HseTrim;
|
||||
///
|
||||
/// assert_eq!((HseTrim::MAX.as_farads() * 10e11) as u8, 33);
|
||||
/// assert_eq!((HseTrim::MIN.as_farads() * 10e11) as u8, 11);
|
||||
/// ```
|
||||
pub fn as_farads(&self) -> f32 {
|
||||
(self.val as f32) * 0.47E-12 + 11.3E-12
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HseTrim> for u8 {
|
||||
fn from(ht: HseTrim) -> Self {
|
||||
ht.val
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HseTrim {
|
||||
fn default() -> Self {
|
||||
Self::POR
|
||||
}
|
||||
}
|
@ -1,292 +0,0 @@
|
||||
/// Interrupt lines.
|
||||
///
|
||||
/// Argument of [`CfgIrq::irq_enable`] and [`CfgIrq::irq_disable`].
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum IrqLine {
|
||||
/// Global interrupt.
|
||||
Global,
|
||||
/// Interrupt line 1.
|
||||
///
|
||||
/// This will output to the [`RfIrq0`](crate::gpio::RfIrq0) pin.
|
||||
Line1,
|
||||
/// Interrupt line 2.
|
||||
///
|
||||
/// This will output to the [`RfIrq1`](crate::gpio::RfIrq1) pin.
|
||||
Line2,
|
||||
/// Interrupt line 3.
|
||||
///
|
||||
/// This will output to the [`RfIrq2`](crate::gpio::RfIrq2) pin.
|
||||
Line3,
|
||||
}
|
||||
|
||||
impl IrqLine {
|
||||
pub(super) const fn offset(&self) -> usize {
|
||||
match self {
|
||||
IrqLine::Global => 1,
|
||||
IrqLine::Line1 => 3,
|
||||
IrqLine::Line2 => 5,
|
||||
IrqLine::Line3 => 7,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// IRQ bit mapping
|
||||
///
|
||||
/// See table 37 "IRQ bit mapping and definition" in the reference manual for
|
||||
/// more information.
|
||||
#[repr(u16)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Irq {
|
||||
/// Packet transmission finished.
|
||||
///
|
||||
/// * Packet type: LoRa and GFSK
|
||||
/// * Operation: TX
|
||||
TxDone = (1 << 0),
|
||||
/// Packet reception finished.
|
||||
///
|
||||
/// * Packet type: LoRa and GFSK
|
||||
/// * Operation: RX
|
||||
RxDone = (1 << 1),
|
||||
/// Preamble detected.
|
||||
///
|
||||
/// * Packet type: LoRa and GFSK
|
||||
/// * Operation: RX
|
||||
PreambleDetected = (1 << 2),
|
||||
/// Synchronization word valid.
|
||||
///
|
||||
/// * Packet type: GFSK
|
||||
/// * Operation: RX
|
||||
SyncDetected = (1 << 3),
|
||||
/// Header valid.
|
||||
///
|
||||
/// * Packet type: LoRa
|
||||
/// * Operation: RX
|
||||
HeaderValid = (1 << 4),
|
||||
/// Header CRC error.
|
||||
///
|
||||
/// * Packet type: LoRa
|
||||
/// * Operation: RX
|
||||
HeaderErr = (1 << 5),
|
||||
/// Dual meaning error.
|
||||
///
|
||||
/// For GFSK RX this indicates a preamble, syncword, address, CRC, or length
|
||||
/// error.
|
||||
///
|
||||
/// For LoRa RX this indicates a CRC error.
|
||||
Err = (1 << 6),
|
||||
/// Channel activity detection finished.
|
||||
///
|
||||
/// * Packet type: LoRa
|
||||
/// * Operation: CAD
|
||||
CadDone = (1 << 7),
|
||||
/// Channel activity detected.
|
||||
///
|
||||
/// * Packet type: LoRa
|
||||
/// * Operation: CAD
|
||||
CadDetected = (1 << 8),
|
||||
/// RX or TX timeout.
|
||||
///
|
||||
/// * Packet type: LoRa and GFSK
|
||||
/// * Operation: RX and TX
|
||||
Timeout = (1 << 9),
|
||||
}
|
||||
|
||||
impl Irq {
|
||||
/// Get the bitmask for an IRQ.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::Irq;
|
||||
///
|
||||
/// assert_eq!(Irq::TxDone.mask(), 0x0001);
|
||||
/// assert_eq!(Irq::Timeout.mask(), 0x0200);
|
||||
/// ```
|
||||
pub const fn mask(self) -> u16 {
|
||||
self as u16
|
||||
}
|
||||
}
|
||||
|
||||
/// Argument for [`set_irq_cfg`].
|
||||
///
|
||||
/// [`set_irq_cfg`]: crate::subghz::SubGhz::set_irq_cfg
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct CfgIrq {
|
||||
buf: [u8; 9],
|
||||
}
|
||||
|
||||
impl CfgIrq {
|
||||
/// Create a new `CfgIrq`.
|
||||
///
|
||||
/// This is the same as `default`, but in a `const` function.
|
||||
///
|
||||
/// The default value has all interrupts disabled on all lines.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::CfgIrq;
|
||||
///
|
||||
/// const IRQ_CFG: CfgIrq = CfgIrq::new();
|
||||
/// ```
|
||||
pub const fn new() -> CfgIrq {
|
||||
CfgIrq {
|
||||
buf: [
|
||||
super::OpCode::CfgDioIrq as u8,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable an interrupt.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::{CfgIrq, Irq, IrqLine};
|
||||
///
|
||||
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
|
||||
/// .irq_enable(IrqLine::Global, Irq::TxDone)
|
||||
/// .irq_enable(IrqLine::Global, Irq::Timeout);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[1], 0x02);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[2], 0x01);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[3], 0x00);
|
||||
/// ```
|
||||
#[must_use = "irq_enable returns a modified CfgIrq"]
|
||||
pub const fn irq_enable(mut self, line: IrqLine, irq: Irq) -> CfgIrq {
|
||||
let mask: u16 = irq as u16;
|
||||
let offset: usize = line.offset();
|
||||
self.buf[offset] |= ((mask >> 8) & 0xFF) as u8;
|
||||
self.buf[offset + 1] |= (mask & 0xFF) as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable an interrupt on all lines.
|
||||
///
|
||||
/// As far as I can tell with empirical testing all IRQ lines need to be
|
||||
/// enabled for the internal interrupt to be pending in the NVIC.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::{CfgIrq, Irq};
|
||||
///
|
||||
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
|
||||
/// .irq_enable_all(Irq::TxDone)
|
||||
/// .irq_enable_all(Irq::Timeout);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[1], 0x02);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[2], 0x01);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[3], 0x02);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[4], 0x01);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[5], 0x02);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[6], 0x01);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[7], 0x02);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[8], 0x01);
|
||||
/// ```
|
||||
#[must_use = "irq_enable_all returns a modified CfgIrq"]
|
||||
pub const fn irq_enable_all(mut self, irq: Irq) -> CfgIrq {
|
||||
let mask: [u8; 2] = irq.mask().to_be_bytes();
|
||||
|
||||
self.buf[1] |= mask[0];
|
||||
self.buf[2] |= mask[1];
|
||||
self.buf[3] |= mask[0];
|
||||
self.buf[4] |= mask[1];
|
||||
self.buf[5] |= mask[0];
|
||||
self.buf[6] |= mask[1];
|
||||
self.buf[7] |= mask[0];
|
||||
self.buf[8] |= mask[1];
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable an interrupt.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::{CfgIrq, Irq, IrqLine};
|
||||
///
|
||||
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
|
||||
/// .irq_enable(IrqLine::Global, Irq::TxDone)
|
||||
/// .irq_enable(IrqLine::Global, Irq::Timeout)
|
||||
/// .irq_disable(IrqLine::Global, Irq::TxDone)
|
||||
/// .irq_disable(IrqLine::Global, Irq::Timeout);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[1], 0x00);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[2], 0x00);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[3], 0x00);
|
||||
/// ```
|
||||
#[must_use = "irq_disable returns a modified CfgIrq"]
|
||||
pub const fn irq_disable(mut self, line: IrqLine, irq: Irq) -> CfgIrq {
|
||||
let mask: u16 = !(irq as u16);
|
||||
let offset: usize = line.offset();
|
||||
self.buf[offset] &= ((mask >> 8) & 0xFF) as u8;
|
||||
self.buf[offset + 1] &= (mask & 0xFF) as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable an interrupt on all lines.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::{CfgIrq, Irq};
|
||||
///
|
||||
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
|
||||
/// .irq_enable_all(Irq::TxDone)
|
||||
/// .irq_enable_all(Irq::Timeout)
|
||||
/// .irq_disable_all(Irq::TxDone)
|
||||
/// .irq_disable_all(Irq::Timeout);
|
||||
/// # assert_eq!(IRQ_CFG, CfgIrq::new());
|
||||
/// ```
|
||||
#[must_use = "irq_disable_all returns a modified CfgIrq"]
|
||||
pub const fn irq_disable_all(mut self, irq: Irq) -> CfgIrq {
|
||||
let mask: [u8; 2] = (!irq.mask()).to_be_bytes();
|
||||
|
||||
self.buf[1] &= mask[0];
|
||||
self.buf[2] &= mask[1];
|
||||
self.buf[3] &= mask[0];
|
||||
self.buf[4] &= mask[1];
|
||||
self.buf[5] &= mask[0];
|
||||
self.buf[6] &= mask[1];
|
||||
self.buf[7] &= mask[0];
|
||||
self.buf[8] &= mask[1];
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Extracts a slice containing the packet.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wlxx_hal::subghz::{CfgIrq, Irq};
|
||||
///
|
||||
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
|
||||
/// .irq_enable_all(Irq::TxDone)
|
||||
/// .irq_enable_all(Irq::Timeout);
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// IRQ_CFG.as_slice(),
|
||||
/// &[0x08, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01]
|
||||
/// );
|
||||
/// ```
|
||||
pub const fn as_slice(&self) -> &[u8] {
|
||||
&self.buf
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CfgIrq {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
/// LoRa synchronization word.
|
||||
///
|
||||
/// Argument of [`set_lora_sync_word`][crate::subghz::SubGhz::set_lora_sync_word].
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum LoRaSyncWord {
|
||||
/// LoRa private network.
|
||||
Private,
|
||||
/// LoRa public network.
|
||||
Public,
|
||||
}
|
||||
|
||||
impl LoRaSyncWord {
|
||||
pub(crate) const fn bytes(self) -> [u8; 2] {
|
||||
match self {
|
||||
LoRaSyncWord::Private => [0x14, 0x24],
|
||||
LoRaSyncWord::Public => [0x34, 0x44],
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user