Rebase on master

This commit is contained in:
Mathias 2023-02-13 14:55:15 +01:00
commit 218b44652c
410 changed files with 25743 additions and 7098 deletions

View file

@ -17,7 +17,7 @@ jobs:
strategy:
matrix:
crates:
- stm32
#- stm32 # runs out of disk space...
- rest
# This will ensure at most one doc build job is running at a time
@ -46,7 +46,7 @@ jobs:
- name: Install docserver
run: |
wget -q -O /usr/local/bin/builder "https://github.com/embassy-rs/docserver/releases/download/v0.3/builder"
wget -q -O /usr/local/bin/builder "https://github.com/embassy-rs/docserver/releases/download/v0.4/builder"
chmod +x /usr/local/bin/builder
- name: build-stm32
@ -69,12 +69,15 @@ jobs:
builder ./embassy-futures crates/embassy-futures/git.zup
builder ./embassy-lora crates/embassy-lora/git.zup
builder ./embassy-net crates/embassy-net/git.zup
builder ./embassy-net-driver crates/embassy-net-driver/git.zup
builder ./embassy-net-driver-channel crates/embassy-net-driver-channel/git.zup
builder ./embassy-nrf crates/embassy-nrf/git.zup
builder ./embassy-rp crates/embassy-rp/git.zup
builder ./embassy-sync crates/embassy-sync/git.zup
builder ./embassy-time crates/embassy-time/git.zup
builder ./embassy-usb crates/embassy-usb/git.zup
builder ./embassy-usb-driver crates/embassy-usb-driver/git.zup
builder ./embassy-usb-logger crates/embassy-usb-logger/git.zup
- name: upload
run: |

View file

@ -68,5 +68,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Test
run: cd embassy-sync && cargo test
- name: Test boot
working-directory: ./embassy-boot/boot
run: cargo test && cargo test --features "ed25519-dalek" && cargo test --features "ed25519-salty"
- name: Test sync
working-directory: ./embassy-sync
run: cargo test

25
.vscode/settings.json vendored
View file

@ -1,29 +1,27 @@
{
"editor.formatOnSave": true,
"rust-analyzer.checkOnSave.allTargets": false,
"rust-analyzer.checkOnSave.noDefaultFeatures": true,
"rust-analyzer.check.allTargets": false,
"rust-analyzer.check.noDefaultFeatures": true,
"rust-analyzer.cargo.noDefaultFeatures": true,
"rust-analyzer.procMacro.enable": true,
"rust-analyzer.cargo.target": "thumbv7em-none-eabi",
//"rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf",
"rust-analyzer.cargo.features": [
// These are needed to prevent embassy-net from failing to build
//"embassy-net/medium-ethernet",
//"embassy-net/tcp",
//"embassy-net/pool-16",
//"time-tick-16mhz",
//"defmt-timestamp-uptime",
"nightly",
//"unstable-traits",
],
"rust-analyzer.linkedProjects": [
// Declare for the target you wish to develop
//"embassy-executor/Cargo.toml",
//"embassy-sync/Cargo.toml",
"examples/nrf/Cargo.toml",
// "embassy-executor/Cargo.toml",
// "embassy-sync/Cargo.toml",
"examples/nrf52840/Cargo.toml",
//"examples/nrf5340/Cargo.toml",
// "examples/nrf-rtos-trace/Cargo.toml",
// "examples/rp/Cargo.toml",
// "examples/std/Cargo.toml",
// "examples/stm32f0/Cargo.toml",
// "examples/stm32f1/Cargo.toml",
// "examples/stm32f2/Cargo.toml",
// "examples/stm32f3/Cargo.toml",
// "examples/stm32f4/Cargo.toml",
// "examples/stm32f7/Cargo.toml",
// "examples/stm32g0/Cargo.toml",
@ -32,8 +30,11 @@
// "examples/stm32l0/Cargo.toml",
// "examples/stm32l1/Cargo.toml",
// "examples/stm32l4/Cargo.toml",
// "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",
],

View file

@ -19,7 +19,7 @@ Rust's <a href="https://rust-lang.github.io/async-book/">async/await</a> allows
No more messing with hardware timers. <a href="https://docs.embassy.dev/embassy-time">embassy_time</a> provides Instant, Duration and Timer types that are globally available and never overflow.
- **Real-time ready** -
Tasks on the same async executor run cooperatively, but you can create multiple executors with different priorities, so that higher priority tasks preempt lower priority ones. See the <a href="https://github.com/embassy-rs/embassy/blob/master/examples/nrf/src/bin/multiprio.rs">example</a>.
Tasks on the same async executor run cooperatively, but you can create multiple executors with different priorities, so that higher priority tasks preempt lower priority ones. See the <a href="https://github.com/embassy-rs/embassy/blob/master/examples/nrf52840/src/bin/multiprio.rs">example</a>.
- **Low-power ready** -
Easily build devices with years of battery life. The async executor automatically puts the core to sleep when there's no work to do. Tasks are woken by interrupts, there is no busy-loop polling while waiting.
@ -31,7 +31,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 SX127x transceivers.
<a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking on STM32WL wireless microcontrollers and Semtech SX126x and SX127x transceivers.
- **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.
@ -87,7 +87,8 @@ async fn main(spawner: Spawner) {
Examples are found in the `examples/` folder seperated by the chip manufacturer they are designed to run on. For example:
* `examples/nrf` run on the `nrf52840-dk` board (PCA10056) but should be easily adaptable to other nRF52 chips and boards.
* `examples/nrf52840` run on the `nrf52840-dk` board (PCA10056) but should be easily adaptable to other nRF52 chips and boards.
* `examples/nrf5340` run on the `nrf5340-dk` board (PCA10095).
* `examples/stm32xx` for the various STM32 families.
* `examples/rp` are for the RP2040 chip.
* `examples/std` are designed to run locally on your PC.
@ -110,7 +111,7 @@ cargo install probe-run
- Change directory to the sample's base directory. For example:
```bash
cd examples/nrf
cd examples/nrf52840
```
- Run the example

27
ci.sh
View file

@ -36,10 +36,12 @@ cargo batch \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,log \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,defmt \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,pool-16 \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,pool-16,unstable-traits \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,pool-16,nightly \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,pool-16,unstable-traits,nightly \
--- 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,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 \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits,nightly \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52805,gpiote,time-driver-rtc1 \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52810,gpiote,time-driver-rtc1 \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52811,gpiote,time-driver-rtc1 \
@ -61,7 +63,8 @@ cargo batch \
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,intrinsics \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f410tb,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f411ce,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f429zi,log,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f413vh,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f429zi,log,exti,time-driver-any,unstable-traits,embedded-sdmmc \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h755zi-cm7,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h7b3ai,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32l476vg,defmt,exti,time-driver-any,unstable-traits \
@ -74,18 +77,21 @@ cargo batch \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f217zg,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,stm32l552ze,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32wl54jc-cm0p,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32wle5ub,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32wle5jb,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f107vc,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f103re,defmt,exti,time-driver-any,unstable-traits \
--- 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-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 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 \
--- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-irq/Cargo.toml --target thumbv7em-none-eabi \
--- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml --target thumbv7em-none-eabi \
--- build --release --manifest-path examples/nrf/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf \
--- build --release --manifest-path examples/nrf52840/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52840 \
--- build --release --manifest-path examples/nrf5340/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf5340 \
--- build --release --manifest-path examples/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/rp \
--- build --release --manifest-path examples/stm32f0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32f0 \
--- build --release --manifest-path examples/stm32f1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f1 \
@ -93,6 +99,7 @@ cargo batch \
--- build --release --manifest-path examples/stm32f3/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f3 \
--- build --release --manifest-path examples/stm32f4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f4 \
--- build --release --manifest-path examples/stm32f7/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f7 \
--- build --release --manifest-path examples/stm32c0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32c0 \
--- build --release --manifest-path examples/stm32g0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32g0 \
--- build --release --manifest-path examples/stm32g4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32g4 \
--- build --release --manifest-path examples/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7 \
@ -103,7 +110,9 @@ cargo batch \
--- build --release --manifest-path examples/stm32u5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32u5 \
--- build --release --manifest-path examples/stm32wb/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32wb \
--- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32wl \
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/nrf --bin b \
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 --out-dir out/examples/boot/nrf --bin b \
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns --out-dir out/examples/boot/nrf --bin b \
--- build --release --manifest-path examples/boot/application/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/boot/rp --bin b \
--- build --release --manifest-path examples/boot/application/stm32f3/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32f3 --bin b \
--- build --release --manifest-path examples/boot/application/stm32f7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32f7 --bin b \
--- build --release --manifest-path examples/boot/application/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32h7 --bin b \
@ -112,6 +121,8 @@ cargo batch \
--- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32l4 --bin b \
--- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/boot/stm32wl --bin b \
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
--- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \
--- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \
--- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --out-dir out/examples/wasm \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/bluepill-stm32f103c8 \

View file

@ -13,8 +13,8 @@ cargo batch \
--- 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,dhcpv4,medium-ethernet,pool-16 \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,pool-16,unstable-traits \
--- 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 \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52810,gpiote,time-driver-rtc1 \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52811,gpiote,time-driver-rtc1 \
@ -36,7 +36,7 @@ cargo batch \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585zi,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55vy,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wl55uc-cm4,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wl55cc-cm4,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4r9zi,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f303vc,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f411ce,defmt,time-driver-any \
@ -65,5 +65,5 @@ cargo batch \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l151cb-a,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f217zg,defmt,exti,time-driver-any \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f217zg,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path examples/nrf/Cargo.toml --target thumbv7em-none-eabi --no-default-features --out-dir out/examples/nrf --bin raw_spawn \
--- build --release --manifest-path examples/nrf52840/Cargo.toml --target thumbv7em-none-eabi --no-default-features --out-dir out/examples/nrf52840 --bin raw_spawn \
--- build --release --manifest-path examples/stm32l0/Cargo.toml --target thumbv6m-none-eabi --no-default-features --out-dir out/examples/stm32l0 --bin raw_spawn \

View file

@ -6,14 +6,13 @@ version = "0.1.0"
license = "MIT OR Apache-2.0"
[dependencies]
embassy-executor = { version = "0.1.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly"] }
embassy-executor = { version = "0.1.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly", "integrated-timers"] }
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"] }
defmt = "0.3"
defmt-rtt = "0.3"
cortex-m = "0.7.3"
cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7.0"
embedded-hal = "0.2.6"
panic-probe = { version = "0.3", features = ["print-defmt"] }

View file

@ -21,7 +21,7 @@ Then, what follows are some declarations on how to deal with panics and faults.
[source,rust]
----
include::example$basic/src/main.rs[lines="11..12"]
include::example$basic/src/main.rs[lines="10"]
----
=== Task declaration
@ -30,7 +30,7 @@ After a bit of import declaration, the tasks run by the application should be de
[source,rust]
----
include::example$basic/src/main.rs[lines="13..22"]
include::example$basic/src/main.rs[lines="12..20"]
----
An embassy task must be declared `async`, and may NOT take generic arguments. In this case, we are handed the LED that should be blinked and the interval of the blinking.
@ -45,23 +45,10 @@ The `Spawner` is the way the main application spawns other tasks. The `Periphera
[source,rust]
----
include::example$basic/src/main.rs[lines="23..-1"]
include::example$basic/src/main.rs[lines="22..-1"]
----
`#[embassy_executor::main]` takes an optional `config` parameter specifying a function that returns an instance of HAL's `Config` struct. For example:
```rust
fn embassy_config() -> embassy_nrf::config::Config {
embassy_nrf::config::Config::default()
}
#[embassy_executor::main(config = "embassy_config()")]
async fn main(_spawner: Spawner, p: embassy_nrf::Peripherals) {
// ...
}
```
What happens when the `blinker` task have been spawned and main returns? Well, the main entry point is actually just like any other task, except that you can only have one and it takes some specific type arguments. The magic lies within the `#[embassy::main]` macro. The macro does the following:
What happens when the `blinker` task has been spawned and main returns? Well, the main entry point is actually just like any other task, except that you can only have one and it takes some specific type arguments. The magic lies within the `#[embassy::main]` macro. The macro does the following:
. Creates an Embassy Executor
. Initializes the microcontroller HAL to get the `Peripherals`
@ -76,7 +63,7 @@ The project definition needs to contain the embassy dependencies:
[source,toml]
----
include::example$basic/Cargo.toml[lines="8..9"]
include::example$basic/Cargo.toml[lines="9..11"]
----
Depending on your microcontroller, you may need to replace `embassy-nrf` with something else (`embassy-stm32` for STM32. Remember to update feature flags as well).

View file

@ -6,7 +6,7 @@ The bootloader can be used either as a library or be flashed directly if you are
By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself.
The bootloader supports both internal and external flash by relying on the `embedded-storage` traits.
The bootloader supports both internal and external flash by relying on the `embedded-storage` traits. The bootloader optionally supports the verification of firmware that has been digitally signed (recommended).
== Hardware support
@ -15,6 +15,7 @@ The bootloader supports
* nRF52 with and without softdevice
* STM32 L4, WB, WL, L1, L0, F3, F7 and H7
* Raspberry Pi: RP2040
In general, the bootloader works on any platform that implements the `embedded-storage` traits for its internal flash, but may require custom initialization code to work.
@ -25,12 +26,69 @@ image::bootloader_flash.png[Bootloader flash layout]
The bootloader divides the storage into 4 main partitions, configurable when creating the bootloader
instance or via linker scripts:
* BOOTLOADER - Where the bootloader is placed. The bootloader itself consumes about 8kB of flash.
* ACTIVE - Where the main application is placed. The bootloader will attempt to load the application at the start of this partition. This partition is only written to by the bootloader.
* DFU - Where the application-to-be-swapped is placed. This partition is written to by the application.
* BOOTLOADER STATE - Where the bootloader stores the current state describing if the active and dfu partitions need to be swapped. When the new firmware has been written to the DFU partition, a flag is set to instruct the bootloader that the partitions should be swapped.
* BOOTLOADER - Where the bootloader is placed. The bootloader itself consumes about 8kB of flash, but if you need to debug it and have space available, increasing this to 24kB will allow you to run the bootloader with probe-rs.
* ACTIVE - Where the main application is placed. The bootloader will attempt to load the application at the start of this partition. This partition is only written to by the bootloader. The size required for this partition depends on the size of your application.
* DFU - Where the application-to-be-swapped is placed. This partition is written to by the application. This partition must be at least 1 page bigger than the ACTIVE partition, since the swap algorithm uses the extra space to ensure power safe copy of data:
+
Partition Size~dfu~= Partition Size~active~+ Page Size~active~
+
All values are specified in bytes.
* BOOTLOADER STATE - Where the bootloader stores the current state describing if the active and dfu partitions need to be swapped. When the new firmware has been written to the DFU partition, a magic field is written to instruct the bootloader that the partitions should be swapped. This partition must be able to store a magic field as well as the partition swap progress. The partition size given by:
+
Partition Size~state~ = Write Size~state~ + (2 × Partition Size~active~ / Page Size~active~)
+
All values are specified in bytes.
The partitions for ACTIVE (+BOOTLOADER), DFU and BOOTLOADER_STATE may be placed in separate flash. The page size used by the bootloader is determined by the lowest common multiple of the ACTIVE and DFU page sizes.
The BOOTLOADER_STATE partition must be big enough to store one word per page in the ACTIVE and DFU partitions combined.
The bootloader has a platform-agnostic part, which implements the power fail safe swapping algorithm given the boundaries set by the partitions. The platform-specific part is a minimal shim that provides additional functionality such as watchdogs or supporting the nRF52 softdevice.
=== FirmwareUpdater
The `FirmwareUpdater` is an object for conveniently flashing firmware to the DFU partition and subsequently marking it as being ready for swapping with the active partition on the next reset. Its principle methods are `write_firmware`, which is called once per the size of the flash "write block" (typically 4KiB), and `mark_updated`, which is the final call.
=== Verification
The bootloader supports the verification of firmware that has been flashed to the DFU partition. Verification requires that firmware has been signed digitally using link:https://ed25519.cr.yp.to/[`ed25519`] signatures. With verification enabled, the `FirmwareUpdater::verify_and_mark_updated` method is called in place of `mark_updated`. A public key and signature are required, along with the actual length of the firmware that has been flashed. If verification fails then the firmware will not be marked as updated and therefore be rejected.
Signatures are normally conveyed with the firmware to be updated and not written to flash. How signatures are provided is a firmware responsibility.
To enable verification use either the `ed25519-dalek` or `ed25519-salty` features when depending on the `embassy-boot` crate. We recommend `ed25519-salty` at this time due to its small size.
==== Tips on keys and signing with ed25519
Ed25519 is a public key signature system where you are responsible for keeping the private key secure. We recommend embedding the *public* key in your program so that it can be easily passed to `verify_and_mark_updated`. An example declaration of the public key in your firmware:
[source, rust]
----
static PUBLIC_SIGNING_KEY: &[u8] = include_bytes!("key.pub");
----
Signatures are often conveyed along with firmware by appending them.
Ed25519 keys can be generated by a variety of tools. We recommend link:https://man.openbsd.org/signify[`signify`] as it is in wide use to sign and verify OpenBSD distributions, and is straightforward to use.
The following set of Bash commands can be used to generate public and private keys on Unix platforms, and also generate a local `key.pub` file with the `signify` file headers removed. Declare a `SECRETS_DIR` environment variable in a secure location.
[source, bash]
----
signify -G -n -p $SECRETS_DIR/key.pub -s $SECRETS_DIR/key.sec
tail -n1 $SECRETS_DIR/key.pub | base64 -d -i - | dd ibs=10 skip=1 > key.pub
chmod 700 $SECRETS_DIR/key.sec
export SECRET_SIGNING_KEY=$(tail -n1 $SECRETS_DIR/key.sec)
----
Then, to sign your firmware given a declaration of `FIRMWARE_DIR` and a firmware filename of `myfirmware`:
[source, bash]
----
shasum -a 512 -b $FIRMWARE_DIR/myfirmware > $SECRETS_DIR/message.txt
cat $SECRETS_DIR/message.txt | dd ibs=128 count=1 | xxd -p -r > $SECRETS_DIR/message.txt
signify -S -s $SECRETS_DIR/key.sec -m $SECRETS_DIR/message.txt -x $SECRETS_DIR/message.txt.sig
cp $FIRMWARE_DIR/myfirmware $FIRMWARE_DIR/myfirmware+signed
tail -n1 $SECRETS_DIR/message.txt.sig | base64 -d -i - | dd ibs=10 skip=1 >> $FIRMWARE_DIR/myfirmware+signed
----
Remember, guard the `$SECRETS_DIR/key.sec` key as compromising it means that another party can sign your firmware.

View file

@ -45,7 +45,7 @@ You can run an example by opening a terminal and entering the following commands
[source, bash]
----
cd examples/nrf
cd examples/nrf52840
cargo run --bin blinky --release
----

View file

@ -8,7 +8,7 @@ The application we'll write is a simple 'push button, blink led' application, wh
== PAC version
The PAC is the lowest API for accessing peripherals and registers, if you don't count reading/writing directly to memory addresses. It provide distinct types
The PAC is the lowest API for accessing peripherals and registers, if you don't count reading/writing directly to memory addresses. It provides distinct types
to make accessing peripheral registers easier, but it does not prevent you from writing unsafe code.
Writing an application using the PAC directly is therefore not recommended, but if the functionality you want to use is not exposed in the upper layers, that's what you need to use.
@ -20,13 +20,13 @@ The blinky app using PAC is shown below:
include::example$layer-by-layer/blinky-pac/src/main.rs[]
----
As you can see, there are a lot of code needed to enable the peripheral clocks, configuring the input pins and the output pins of the application.
As you can see, a lot of code is needed to enable the peripheral clocks and to configure the input pins and the output pins of the application.
Another downside of this application is that it is busy-looping while polling the button state. This prevents the microcontroller from utilizing any sleep mode to save power.
== HAL version
To simplify our application, we can use the HAL instead. The HAL exposes higher level APIs that handle details such
To simplify our application, we can use the HAL instead. The HAL exposes higher level APIs that handle details such as:
* Automatically enabling the peripheral clock when you're using the peripheral
* Deriving and applying register configuration from higher level types
@ -39,7 +39,7 @@ The HAL example is shown below:
include::example$layer-by-layer/blinky-hal/src/main.rs[]
----
As you can see, the application becomes a lot simpler, even without using any async code. The `Input` and `Output` hides all the details accessing the GPIO registers, and allow you to use a much simpler API to query the state of the button and toggle the LED output accordingly.
As you can see, the application becomes a lot simpler, even without using any async code. The `Input` and `Output` types hide all the details of accessing the GPIO registers and allow you to use a much simpler API for querying the state of the button and toggling the LED output.
The same downside from the PAC example still applies though: the application is busy looping and consuming more power than necessary.

View file

@ -4,9 +4,9 @@ The link:https://github.com/embassy-rs/embassy/tree/master/embassy-stm32[Embassy
== The infinite variant problem
STM32 microcontrollers comes in many families and flavors, and supporting all of them is a big undertaking. Embassy has taken advantage of the fact
STM32 microcontrollers come in many families, and flavors and supporting all of them is a big undertaking. Embassy has taken advantage of the fact
that the STM32 peripheral versions are shared across chip families. Instead of re-implementing the SPI
peripheral for every STM32 chip family, embassy have a single SPI implementation that depends on
peripheral for every STM32 chip family, embassy has a single SPI implementation that depends on
code-generated register types that are identical for STM32 families with the same version of a given peripheral.
=== The metapac

View file

@ -1,26 +1,50 @@
[package]
edition = "2021"
name = "embassy-boot"
version = "0.1.0"
description = "Bootloader using Embassy"
version = "0.1.1"
description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks."
license = "MIT OR Apache-2.0"
repository = "https://github.com/embassy-rs/embassy"
categories = [
"embedded",
"no-std",
"asynchronous",
]
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-v$VERSION/embassy-boot/boot/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/boot/src/"
target = "thumbv7em-none-eabi"
features = ["defmt"]
[package.metadata.docs.rs]
features = ["defmt"]
[lib]
[dependencies]
defmt = { version = "0.3", optional = true }
log = { version = "0.4", optional = true }
ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true }
embassy-sync = { version = "0.1.0", path = "../../embassy-sync" }
embedded-storage = "0.3.0"
embedded-storage-async = "0.3.0"
salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true }
signature = { version = "1.6.4", default-features = false }
[dev-dependencies]
log = "0.4"
env_logger = "0.9"
rand = "0.8"
rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version
futures = { version = "0.3", features = ["executor"] }
[dev-dependencies.ed25519-dalek]
default_features = false
features = ["rand", "std", "u32_backend"]
[features]
ed25519-dalek = ["dep:ed25519-dalek", "_verify"]
ed25519-salty = ["dep:salty", "_verify"]
#Internal features
_verify = []

View file

@ -1,7 +1,7 @@
#![feature(type_alias_impl_trait)]
#![no_std]
#![warn(missing_docs)]
#![doc = include_str!("../../README.md")]
#![doc = include_str!("../README.md")]
mod fmt;
use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
@ -52,6 +52,16 @@ pub enum BootError {
BadMagic,
}
#[cfg(feature = "defmt")]
impl defmt::Format for BootError {
fn format(&self, fmt: defmt::Formatter) {
match self {
BootError::Flash(_) => defmt::write!(fmt, "BootError::Flash(_)"),
BootError::BadMagic => defmt::write!(fmt, "BootError::BadMagic"),
}
}
}
impl<E> From<E> for BootError
where
E: NorFlashError,
@ -150,7 +160,7 @@ impl BootLoader {
/// +-----------+------------+--------+--------+--------+--------+
/// | Active | 0 | 1 | 2 | 3 | - |
/// | DFU | 0 | 3 | 2 | 1 | X |
/// +-----------+-------+--------+--------+--------+--------+
/// +-----------+------------+--------+--------+--------+--------+
///
/// The algorithm starts by copying 'backwards', and after the first step, the layout is
/// as follows:
@ -557,6 +567,33 @@ where
self.state
}
}
/// Errors returned by FirmwareUpdater
#[derive(Debug)]
pub enum FirmwareUpdaterError {
/// Error from flash.
Flash(NorFlashErrorKind),
/// Signature errors.
Signature(signature::Error),
}
#[cfg(feature = "defmt")]
impl defmt::Format for FirmwareUpdaterError {
fn format(&self, fmt: defmt::Formatter) {
match self {
FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"),
FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"),
}
}
}
impl<E> From<E> for FirmwareUpdaterError
where
E: NorFlashError,
{
fn from(error: E) -> Self {
FirmwareUpdaterError::Flash(error.kind())
}
}
/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
/// 'mess up' the internal bootloader state
@ -609,7 +646,11 @@ 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`.
pub async fn get_state<F: AsyncNorFlash>(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<State, F::Error> {
pub async fn get_state<F: AsyncNorFlash>(
&mut self,
flash: &mut F,
aligned: &mut [u8],
) -> Result<State, FirmwareUpdaterError> {
flash.read(self.state.from as u32, aligned).await?;
if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
@ -619,12 +660,126 @@ impl FirmwareUpdater {
}
}
/// Verify the DFU given a public key. If there is an error then DO NOT
/// proceed with updating the firmware as it must be signed with a
/// corresponding private key (otherwise it could be malicious firmware).
///
/// Mark to trigger firmware swap on next boot if verify suceeds.
///
/// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
/// been generated from a SHA-512 digest of the firmware bytes.
///
/// If no signature feature is set then this method will always return a
/// signature error.
///
/// # Safety
///
/// 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")]
pub async fn verify_and_mark_updated<F: AsyncNorFlash>(
&mut self,
_flash: &mut F,
_public_key: &[u8],
_signature: &[u8],
_update_len: usize,
_aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
let _end = self.dfu.from + _update_len;
let _read_size = _aligned.len();
assert_eq!(_aligned.len(), F::WRITE_SIZE);
assert!(_end <= self.dfu.to);
#[cfg(feature = "ed25519-dalek")]
{
use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier};
let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
let mut digest = Sha512::new();
let mut offset = self.dfu.from;
let last_offset = _end / _read_size * _read_size;
while offset < last_offset {
_flash.read(offset as u32, _aligned).await?;
digest.update(&_aligned);
offset += _read_size;
}
let remaining = _end % _read_size;
if remaining > 0 {
_flash.read(last_offset as u32, _aligned).await?;
digest.update(&_aligned[0..remaining]);
}
public_key
.verify(&digest.finalize(), &signature)
.map_err(into_signature_error)?
}
#[cfg(feature = "ed25519-salty")]
{
use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
use salty::{PublicKey, Sha512, Signature};
fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
FirmwareUpdaterError::Signature(signature::Error::default())
}
let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
let mut digest = Sha512::new();
let mut offset = self.dfu.from;
let last_offset = _end / _read_size * _read_size;
while offset < last_offset {
_flash.read(offset as u32, _aligned).await?;
digest.update(&_aligned);
offset += _read_size;
}
let remaining = _end % _read_size;
if remaining > 0 {
_flash.read(last_offset as u32, _aligned).await?;
digest.update(&_aligned[0..remaining]);
}
let message = digest.finalize();
let r = public_key.verify(&message, &signature);
trace!(
"Verifying with public key {}, signature {} and message {} yields ok: {}",
public_key.to_bytes(),
signature.to_bytes(),
message,
r.is_ok()
);
r.map_err(into_signature_error)?
}
self.set_magic(_aligned, SWAP_MAGIC, _flash).await
}
/// Mark to trigger firmware swap on next boot.
///
/// # Safety
///
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
pub async fn mark_updated<F: AsyncNorFlash>(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> {
#[cfg(not(feature = "_verify"))]
pub async fn mark_updated<F: AsyncNorFlash>(
&mut self,
flash: &mut F,
aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), F::WRITE_SIZE);
self.set_magic(aligned, SWAP_MAGIC, flash).await
}
@ -634,7 +789,11 @@ 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.
pub async fn mark_booted<F: AsyncNorFlash>(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> {
pub async fn mark_booted<F: AsyncNorFlash>(
&mut self,
flash: &mut F,
aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), F::WRITE_SIZE);
self.set_magic(aligned, BOOT_MAGIC, flash).await
}
@ -644,7 +803,7 @@ impl FirmwareUpdater {
aligned: &mut [u8],
magic: u8,
flash: &mut F,
) -> Result<(), F::Error> {
) -> Result<(), FirmwareUpdaterError> {
flash.read(self.state.from as u32, aligned).await?;
if aligned.iter().any(|&b| b != magic) {
@ -672,7 +831,7 @@ impl FirmwareUpdater {
data: &[u8],
flash: &mut F,
block_size: usize,
) -> Result<(), F::Error> {
) -> Result<(), FirmwareUpdaterError> {
assert!(data.len() >= F::ERASE_SIZE);
flash
@ -700,7 +859,10 @@ impl FirmwareUpdater {
///
/// Using this instead of `write_firmware` allows for an optimized API in
/// exchange for added complexity.
pub async fn prepare_update<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<FirmwareWriter, F::Error> {
pub async fn prepare_update<F: AsyncNorFlash>(
&mut self,
flash: &mut F,
) -> Result<FirmwareWriter, FirmwareUpdaterError> {
flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32).await?;
trace!("Erased from {} to {}", self.dfu.from, self.dfu.to);
@ -717,7 +879,11 @@ 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`.
pub fn get_state_blocking<F: NorFlash>(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<State, F::Error> {
pub fn get_state_blocking<F: NorFlash>(
&mut self,
flash: &mut F,
aligned: &mut [u8],
) -> Result<State, FirmwareUpdaterError> {
flash.read(self.state.from as u32, aligned)?;
if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
@ -727,12 +893,126 @@ impl FirmwareUpdater {
}
}
/// Verify the DFU given a public key. If there is an error then DO NOT
/// proceed with updating the firmware as it must be signed with a
/// corresponding private key (otherwise it could be malicious firmware).
///
/// Mark to trigger firmware swap on next boot if verify suceeds.
///
/// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
/// been generated from a SHA-512 digest of the firmware bytes.
///
/// If no signature feature is set then this method will always return a
/// signature error.
///
/// # Safety
///
/// 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")]
pub fn verify_and_mark_updated_blocking<F: NorFlash>(
&mut self,
_flash: &mut F,
_public_key: &[u8],
_signature: &[u8],
_update_len: usize,
_aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
let _end = self.dfu.from + _update_len;
let _read_size = _aligned.len();
assert_eq!(_aligned.len(), F::WRITE_SIZE);
assert!(_end <= self.dfu.to);
#[cfg(feature = "ed25519-dalek")]
{
use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier};
let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
let mut digest = Sha512::new();
let mut offset = self.dfu.from;
let last_offset = _end / _read_size * _read_size;
while offset < last_offset {
_flash.read(offset as u32, _aligned)?;
digest.update(&_aligned);
offset += _read_size;
}
let remaining = _end % _read_size;
if remaining > 0 {
_flash.read(last_offset as u32, _aligned)?;
digest.update(&_aligned[0..remaining]);
}
public_key
.verify(&digest.finalize(), &signature)
.map_err(into_signature_error)?
}
#[cfg(feature = "ed25519-salty")]
{
use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
use salty::{PublicKey, Sha512, Signature};
fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
FirmwareUpdaterError::Signature(signature::Error::default())
}
let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
let mut digest = Sha512::new();
let mut offset = self.dfu.from;
let last_offset = _end / _read_size * _read_size;
while offset < last_offset {
_flash.read(offset as u32, _aligned)?;
digest.update(&_aligned);
offset += _read_size;
}
let remaining = _end % _read_size;
if remaining > 0 {
_flash.read(last_offset as u32, _aligned)?;
digest.update(&_aligned[0..remaining]);
}
let message = digest.finalize();
let r = public_key.verify(&message, &signature);
trace!(
"Verifying with public key {}, signature {} and message {} yields ok: {}",
public_key.to_bytes(),
signature.to_bytes(),
message,
r.is_ok()
);
r.map_err(into_signature_error)?
}
self.set_magic_blocking(_aligned, SWAP_MAGIC, _flash)
}
/// Mark to trigger firmware swap on next boot.
///
/// # Safety
///
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
pub fn mark_updated_blocking<F: NorFlash>(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> {
#[cfg(not(feature = "_verify"))]
pub fn mark_updated_blocking<F: NorFlash>(
&mut self,
flash: &mut F,
aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), F::WRITE_SIZE);
self.set_magic_blocking(aligned, SWAP_MAGIC, flash)
}
@ -742,7 +1022,11 @@ 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.
pub fn mark_booted_blocking<F: NorFlash>(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> {
pub fn mark_booted_blocking<F: NorFlash>(
&mut self,
flash: &mut F,
aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), F::WRITE_SIZE);
self.set_magic_blocking(aligned, BOOT_MAGIC, flash)
}
@ -752,7 +1036,7 @@ impl FirmwareUpdater {
aligned: &mut [u8],
magic: u8,
flash: &mut F,
) -> Result<(), F::Error> {
) -> Result<(), FirmwareUpdaterError> {
flash.read(self.state.from as u32, aligned)?;
if aligned.iter().any(|&b| b != magic) {
@ -780,7 +1064,7 @@ impl FirmwareUpdater {
data: &[u8],
flash: &mut F,
block_size: usize,
) -> Result<(), F::Error> {
) -> Result<(), FirmwareUpdaterError> {
assert!(data.len() >= F::ERASE_SIZE);
flash.erase(
@ -804,7 +1088,10 @@ impl FirmwareUpdater {
///
/// Using this instead of `write_firmware_blocking` allows for an optimized
/// API in exchange for added complexity.
pub fn prepare_update_blocking<F: NorFlash>(&mut self, flash: &mut F) -> Result<FirmwareWriter, F::Error> {
pub fn prepare_update_blocking<F: NorFlash>(
&mut self,
flash: &mut F,
) -> Result<FirmwareWriter, FirmwareUpdaterError> {
flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32)?;
trace!("Erased from {} to {}", self.dfu.from, self.dfu.to);
@ -953,6 +1240,7 @@ mod tests {
}
#[test]
#[cfg(not(feature = "_verify"))]
fn test_swap_state() {
const STATE: Partition = Partition::new(0, 4096);
const ACTIVE: Partition = Partition::new(4096, 61440);
@ -1022,6 +1310,7 @@ mod tests {
}
#[test]
#[cfg(not(feature = "_verify"))]
fn test_separate_flash_active_page_biggest() {
const STATE: Partition = Partition::new(2048, 4096);
const ACTIVE: Partition = Partition::new(4096, 16384);
@ -1074,6 +1363,7 @@ mod tests {
}
#[test]
#[cfg(not(feature = "_verify"))]
fn test_separate_flash_dfu_page_biggest() {
const STATE: Partition = Partition::new(2048, 4096);
const ACTIVE: Partition = Partition::new(4096, 16384);
@ -1133,6 +1423,55 @@ mod tests {
assert_partitions(ACTIVE, DFU, STATE, 4096, 4);
}
#[test]
#[cfg(feature = "_verify")]
fn test_verify() {
// The following key setup is based on:
// https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example
use ed25519_dalek::Keypair;
use rand::rngs::OsRng;
let mut csprng = OsRng {};
let keypair: Keypair = Keypair::generate(&mut csprng);
use ed25519_dalek::{Digest, Sha512, Signature, Signer};
let firmware: &[u8] = b"This are bytes that would otherwise be firmware bytes for DFU.";
let mut digest = Sha512::new();
digest.update(&firmware);
let message = digest.finalize();
let signature: Signature = keypair.sign(&message);
use ed25519_dalek::PublicKey;
let public_key: PublicKey = keypair.public;
// Setup flash
const STATE: Partition = Partition::new(0, 4096);
const DFU: Partition = Partition::new(4096, 8192);
let mut flash = MemFlash::<8192, 4096, 4>([0xff; 8192]);
let firmware_len = firmware.len();
let mut write_buf = [0; 4096];
write_buf[0..firmware_len].copy_from_slice(firmware);
NorFlash::write(&mut flash, DFU.from as u32, &write_buf).unwrap();
// On with the test
let mut updater = FirmwareUpdater::new(DFU, STATE);
let mut aligned = [0; 4];
assert!(block_on(updater.verify_and_mark_updated(
&mut flash,
&public_key.to_bytes(),
&signature.to_bytes(),
firmware_len,
&mut aligned,
))
.is_ok());
}
struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize>([u8; SIZE]);
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFlash
@ -1171,7 +1510,7 @@ mod tests {
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNorFlash
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
{
const READ_SIZE: usize = 4;
const READ_SIZE: usize = 1;
fn read(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), Self::Error> {
let len = buf.len();
@ -1194,7 +1533,7 @@ mod tests {
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
{
const READ_SIZE: usize = 4;
const READ_SIZE: usize = 1;
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a;
fn read<'a>(&'a mut self, offset: u32, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {

View file

@ -0,0 +1,26 @@
# embassy-boot-nrf
An [Embassy](https://embassy.dev) project.
An adaptation of `embassy-boot` for nRF.
## Features
* Load applications with our without the softdevice.
* Configure bootloader partitions based on linker script.
* Using watchdog timer to detect application failure.
## 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.
## License
This work is licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
<http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.

View file

@ -1,7 +1,7 @@
#![no_std]
#![feature(type_alias_impl_trait)]
#![warn(missing_docs)]
#![doc = include_str!("../../README.md")]
#![doc = include_str!("../README.md")]
mod fmt;
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig};
@ -17,9 +17,9 @@ pub struct BootLoader {
page: AlignedBuffer<PAGE_SIZE>,
}
impl BootLoader {
impl Default for BootLoader {
/// Create a new bootloader instance using parameters from linker script
pub fn default() -> Self {
fn default() -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
@ -54,7 +54,9 @@ impl BootLoader {
Self::new(active, dfu, state)
}
}
impl BootLoader {
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
Self {
@ -147,11 +149,7 @@ pub struct WatchdogFlash<'d> {
impl<'d> WatchdogFlash<'d> {
/// Start a new watchdog with a given flash and WDT peripheral and a timeout
pub fn start(flash: Nvmc<'d>, wdt: WDT, timeout: u32) -> Self {
let mut config = wdt::Config::default();
config.timeout_ticks = 32768 * timeout; // timeout seconds
config.run_during_sleep = true;
config.run_during_debug_halt = false;
pub fn start(flash: Nvmc<'d>, wdt: WDT, config: wdt::Config) -> Self {
let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) {
Ok(x) => x,
Err(_) => {

View file

@ -0,0 +1,73 @@
[package]
edition = "2021"
name = "embassy-boot-rp"
version = "0.1.0"
description = "Bootloader lib for RP2040 chips"
license = "MIT OR Apache-2.0"
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-rp-v$VERSION/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/rp/src/"
target = "thumbv6m-none-eabi"
[lib]
[dependencies]
defmt = { version = "0.3", optional = true }
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-boot = { path = "../boot", default-features = false }
embassy-time = { path = "../../embassy-time", features = ["nightly"] }
cortex-m = { version = "0.7.6" }
cortex-m-rt = { version = "0.7" }
embedded-storage = "0.3.0"
embedded-storage-async = "0.3.0"
cfg-if = "1.0.0"
[features]
defmt = [
"dep:defmt",
"embassy-boot/defmt",
"embassy-rp/defmt",
]
log = [
"dep:log",
"embassy-boot/log",
"embassy-rp/log",
]
debug = ["defmt-rtt"]
[profile.dev]
debug = 2
debug-assertions = true
incremental = false
opt-level = 'z'
overflow-checks = true
[profile.release]
codegen-units = 1
debug = 2
debug-assertions = false
incremental = false
lto = 'fat'
opt-level = 'z'
overflow-checks = false
# do not optimize proc-macro crates = faster builds from scratch
[profile.dev.build-override]
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
overflow-checks = false
[profile.release.build-override]
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
overflow-checks = false

26
embassy-boot/rp/README.md Normal file
View file

@ -0,0 +1,26 @@
# embassy-boot-rp
An [Embassy](https://embassy.dev) project.
An adaptation of `embassy-boot` for RP2040.
NOTE: The applications using this bootloader should not link with the `link-rp.x` linker script.
## Features
* Configure bootloader partitions based on linker script.
* Load applications from active partition.
## 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.
## License
This work is licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
<http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.

8
embassy-boot/rp/build.rs Normal file
View file

@ -0,0 +1,8 @@
use std::env;
fn main() {
let target = env::var("TARGET").unwrap();
if target.starts_with("thumbv6m-") {
println!("cargo:rustc-cfg=armv6m");
}
}

225
embassy-boot/rp/src/fmt.rs Normal file
View file

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

139
embassy-boot/rp/src/lib.rs Normal file
View file

@ -0,0 +1,139 @@
#![no_std]
#![feature(type_alias_impl_trait)]
#![warn(missing_docs)]
#![doc = include_str!("../README.md")]
mod fmt;
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State};
use embassy_rp::flash::{Flash, ERASE_SIZE, WRITE_SIZE};
use embassy_rp::peripherals::{FLASH, WATCHDOG};
use embassy_rp::watchdog::Watchdog;
use embassy_time::Duration;
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
/// A bootloader for RP2040 devices.
pub struct BootLoader {
boot: embassy_boot::BootLoader,
magic: AlignedBuffer<WRITE_SIZE>,
page: AlignedBuffer<ERASE_SIZE>,
}
impl BootLoader {
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
Self {
boot: embassy_boot::BootLoader::new(active, dfu, state),
magic: AlignedBuffer([0; WRITE_SIZE]),
page: AlignedBuffer([0; ERASE_SIZE]),
}
}
/// Inspect the bootloader state and perform actions required before booting, such as swapping
/// firmware.
pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize {
match self.boot.prepare_boot(flash, self.magic.as_mut(), self.page.as_mut()) {
Ok(_) => embassy_rp::flash::FLASH_BASE + self.boot.boot_address(),
Err(_) => panic!("boot prepare error!"),
}
}
/// Boots the application.
///
/// # Safety
///
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
pub unsafe fn load(&mut self, start: usize) -> ! {
trace!("Loading app at 0x{:x}", start);
#[allow(unused_mut)]
let mut p = cortex_m::Peripherals::steal();
#[cfg(not(armv6m))]
p.SCB.invalidate_icache();
p.SCB.vtor.write(start as u32);
cortex_m::asm::bootload(start as *const u32)
}
}
impl Default for BootLoader {
/// Create a new bootloader instance using parameters from linker script
fn default() -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_active_start: u32;
static __bootloader_active_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
let active = unsafe {
Partition::new(
&__bootloader_active_start as *const u32 as usize,
&__bootloader_active_end as *const u32 as usize,
)
};
let dfu = unsafe {
Partition::new(
&__bootloader_dfu_start as *const u32 as usize,
&__bootloader_dfu_end as *const u32 as usize,
)
};
let state = unsafe {
Partition::new(
&__bootloader_state_start as *const u32 as usize,
&__bootloader_state_end as *const u32 as usize,
)
};
trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to);
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
Self::new(active, dfu, state)
}
}
/// A flash implementation that will feed a watchdog when touching flash.
pub struct WatchdogFlash<'d, const SIZE: usize> {
flash: Flash<'d, FLASH, SIZE>,
watchdog: Watchdog,
}
impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> {
/// Start a new watchdog with a given flash and watchdog peripheral and a timeout
pub fn start(flash: FLASH, watchdog: WATCHDOG, timeout: Duration) -> Self {
let flash: Flash<'_, FLASH, SIZE> = Flash::new(flash);
let mut watchdog = Watchdog::new(watchdog);
watchdog.start(timeout);
Self { flash, watchdog }
}
}
impl<'d, const SIZE: usize> ErrorType for WatchdogFlash<'d, SIZE> {
type Error = <Flash<'d, FLASH, SIZE> as ErrorType>::Error;
}
impl<'d, const SIZE: usize> NorFlash for WatchdogFlash<'d, SIZE> {
const WRITE_SIZE: usize = <Flash<'d, FLASH, SIZE> as NorFlash>::WRITE_SIZE;
const ERASE_SIZE: usize = <Flash<'d, FLASH, SIZE> as NorFlash>::ERASE_SIZE;
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
self.watchdog.feed();
self.flash.erase(from, to)
}
fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> {
self.watchdog.feed();
self.flash.write(offset, data)
}
}
impl<'d, const SIZE: usize> ReadNorFlash for WatchdogFlash<'d, SIZE> {
const READ_SIZE: usize = <Flash<'d, FLASH, SIZE> as ReadNorFlash>::READ_SIZE;
fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> {
self.watchdog.feed();
self.flash.read(offset, data)
}
fn capacity(&self) -> usize {
self.flash.capacity()
}
}

View file

@ -15,7 +15,7 @@ target = "thumbv7em-none-eabi"
[dependencies]
defmt = { version = "0.3", optional = true }
defmt-rtt = { version = "0.3", optional = true }
defmt-rtt = { version = "0.4", optional = true }
log = { version = "0.4", optional = true }
embassy-sync = { path = "../../embassy-sync" }

View file

@ -1,11 +1,24 @@
# Bootloader for STM32
# embassy-boot-stm32
The bootloader uses `embassy-boot` to interact with the flash.
An [Embassy](https://embassy.dev) project.
# Usage
An adaptation of `embassy-boot` for STM32.
Flash the bootloader
## Features
```
cargo flash --features embassy-stm32/stm32wl55jc-cm4 --release --chip STM32WLE5JCIx
```
* Configure bootloader partitions based on linker script.
* Load applications from active partition.
## 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.
## License
This work is licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
<http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.

View file

@ -1,7 +1,7 @@
#![no_std]
#![feature(type_alias_impl_trait)]
#![warn(missing_docs)]
#![doc = include_str!("../../README.md")]
#![doc = include_str!("../README.md")]
mod fmt;
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State};
@ -14,43 +14,6 @@ pub struct BootLoader<const PAGE_SIZE: usize, const WRITE_SIZE: usize> {
}
impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize> BootLoader<PAGE_SIZE, WRITE_SIZE> {
/// Create a new bootloader instance using parameters from linker script
pub fn default() -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_active_start: u32;
static __bootloader_active_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
let active = unsafe {
Partition::new(
&__bootloader_active_start as *const u32 as usize,
&__bootloader_active_end as *const u32 as usize,
)
};
let dfu = unsafe {
Partition::new(
&__bootloader_dfu_start as *const u32 as usize,
&__bootloader_dfu_end as *const u32 as usize,
)
};
let state = unsafe {
Partition::new(
&__bootloader_state_start as *const u32 as usize,
&__bootloader_state_end as *const u32 as usize,
)
};
trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to);
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
Self::new(active, dfu, state)
}
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
Self {
@ -85,3 +48,42 @@ impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize> BootLoader<PAGE_SIZE, WRIT
cortex_m::asm::bootload(start as *const u32)
}
}
impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize> Default for BootLoader<PAGE_SIZE, WRITE_SIZE> {
/// Create a new bootloader instance using parameters from linker script
fn default() -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_active_start: u32;
static __bootloader_active_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
let active = unsafe {
Partition::new(
&__bootloader_active_start as *const u32 as usize,
&__bootloader_active_end as *const u32 as usize,
)
};
let dfu = unsafe {
Partition::new(
&__bootloader_dfu_start as *const u32 as usize,
&__bootloader_dfu_end as *const u32 as usize,
)
};
let state = unsafe {
Partition::new(
&__bootloader_state_start as *const u32 as usize,
&__bootloader_state_end as *const u32 as usize,
)
};
trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to);
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
Self::new(active, dfu, state)
}
}

View file

@ -20,7 +20,7 @@ nightly = ["embedded-hal-async", "embedded-storage-async"]
embassy-sync = { version = "0.1.0", path = "../embassy-sync" }
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9" }
embedded-hal-async = { version = "=0.1.0-alpha.2", optional = true }
embedded-hal-async = { version = "=0.2.0-alpha.0", optional = true }
embedded-storage = "0.3.0"
embedded-storage-async = { version = "0.3.0", optional = true }
nb = "1.0.0"

View file

@ -38,32 +38,31 @@ where
E: embedded_hal_1::i2c::Error + 'static,
T: blocking::i2c::WriteRead<Error = E> + blocking::i2c::Read<Error = E> + blocking::i2c::Write<Error = E>,
{
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
type WriteReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> {
async move { self.wrapped.read(address, buffer) }
async fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Result<(), Self::Error> {
self.wrapped.read(address, buffer)
}
fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> {
async move { self.wrapped.write(address, bytes) }
async fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Result<(), Self::Error> {
self.wrapped.write(address, bytes)
}
fn write_read<'a>(&'a mut self, address: u8, bytes: &'a [u8], buffer: &'a mut [u8]) -> Self::WriteReadFuture<'a> {
async move { self.wrapped.write_read(address, bytes, buffer) }
async fn write_read<'a>(
&'a mut self,
address: u8,
bytes: &'a [u8],
buffer: &'a mut [u8],
) -> Result<(), Self::Error> {
self.wrapped.write_read(address, bytes, buffer)
}
type TransactionFuture<'a, 'b> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a, 'b: 'a;
fn transaction<'a, 'b>(
async fn transaction<'a, 'b>(
&'a mut self,
address: u8,
operations: &'a mut [embedded_hal_async::i2c::Operation<'b>],
) -> Self::TransactionFuture<'a, 'b> {
) -> Result<(), Self::Error> {
let _ = address;
let _ = operations;
async move { todo!() }
todo!()
}
}
@ -84,23 +83,17 @@ where
E: embedded_hal_1::spi::Error + 'static,
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
{
type TransferFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn transfer<'a>(&'a mut self, read: &'a mut [u8], write: &'a [u8]) -> Self::TransferFuture<'a> {
async move {
// Ensure we write the expected bytes
for i in 0..core::cmp::min(read.len(), write.len()) {
read[i] = write[i].clone();
}
self.wrapped.transfer(read)?;
Ok(())
async fn transfer<'a>(&'a mut self, read: &'a mut [u8], write: &'a [u8]) -> Result<(), Self::Error> {
// Ensure we write the expected bytes
for i in 0..core::cmp::min(read.len(), write.len()) {
read[i] = write[i].clone();
}
self.wrapped.transfer(read)?;
Ok(())
}
type TransferInPlaceFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn transfer_in_place<'a>(&'a mut self, _: &'a mut [u8]) -> Self::TransferInPlaceFuture<'a> {
async move { todo!() }
async fn transfer_in_place<'a>(&'a mut self, _: &'a mut [u8]) -> Result<(), Self::Error> {
todo!()
}
}
@ -109,10 +102,8 @@ where
E: embedded_hal_1::spi::Error + 'static,
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
{
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(()) }
async fn flush(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}
@ -121,13 +112,9 @@ where
E: embedded_hal_1::spi::Error + 'static,
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
{
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn write<'a>(&'a mut self, data: &'a [u8]) -> Self::WriteFuture<'a> {
async move {
self.wrapped.write(data)?;
Ok(())
}
async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> {
self.wrapped.write(data)?;
Ok(())
}
}
@ -136,13 +123,9 @@ where
E: embedded_hal_1::spi::Error + 'static,
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
{
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn read<'a>(&'a mut self, data: &'a mut [u8]) -> Self::ReadFuture<'a> {
async move {
self.wrapped.transfer(data)?;
Ok(())
}
async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
self.wrapped.transfer(data)?;
Ok(())
}
}
@ -192,7 +175,7 @@ where
}
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where T: 'a;
fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> {
fn flush(&mut self) -> Result<(), Self::Error> {
async move { self.wrapped.bflush() }
}
}

View file

@ -1,5 +1,9 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))]
#![cfg_attr(
feature = "nightly",
feature(type_alias_impl_trait, async_fn_in_trait, impl_trait_projections)
)]
#![cfg_attr(feature = "nightly", allow(incomplete_features))]
#![warn(missing_docs)]
//! Utilities to use `embedded-hal` traits with Embassy.

View file

@ -22,7 +22,6 @@
//! let i2c_dev2 = I2cDevice::new(i2c_bus);
//! let mpu = Mpu6050::new(i2c_dev2);
//! ```
use core::future::Future;
use embassy_sync::blocking_mutex::raw::RawMutex;
use embassy_sync::mutex::Mutex;
@ -55,53 +54,39 @@ where
M: RawMutex + 'static,
BUS: i2c::I2c + 'static,
{
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> {
async move {
let mut bus = self.bus.lock().await;
bus.read(address, buffer).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
async fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Result<(), I2cDeviceError<BUS::Error>> {
let mut bus = self.bus.lock().await;
bus.read(address, buffer).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> {
async move {
let mut bus = self.bus.lock().await;
bus.write(address, bytes).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
async fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Result<(), I2cDeviceError<BUS::Error>> {
let mut bus = self.bus.lock().await;
bus.write(address, bytes).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
type WriteReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn write_read<'a>(
async fn write_read<'a>(
&'a mut self,
address: u8,
wr_buffer: &'a [u8],
rd_buffer: &'a mut [u8],
) -> Self::WriteReadFuture<'a> {
async move {
let mut bus = self.bus.lock().await;
bus.write_read(address, wr_buffer, rd_buffer)
.await
.map_err(I2cDeviceError::I2c)?;
Ok(())
}
) -> Result<(), I2cDeviceError<BUS::Error>> {
let mut bus = self.bus.lock().await;
bus.write_read(address, wr_buffer, rd_buffer)
.await
.map_err(I2cDeviceError::I2c)?;
Ok(())
}
type TransactionFuture<'a, 'b> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a, 'b: 'a;
fn transaction<'a, 'b>(
async fn transaction<'a, 'b>(
&'a mut self,
address: u8,
operations: &'a mut [embedded_hal_async::i2c::Operation<'b>],
) -> Self::TransactionFuture<'a, 'b> {
) -> Result<(), I2cDeviceError<BUS::Error>> {
let _ = address;
let _ = operations;
async move { todo!() }
todo!()
}
}
@ -136,55 +121,41 @@ where
M: RawMutex + 'static,
BUS: i2c::I2c + SetConfig + 'static,
{
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> {
async move {
let mut bus = self.bus.lock().await;
bus.set_config(&self.config);
bus.read(address, buffer).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
async fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Result<(), I2cDeviceError<BUS::Error>> {
let mut bus = self.bus.lock().await;
bus.set_config(&self.config);
bus.read(address, buffer).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> {
async move {
let mut bus = self.bus.lock().await;
bus.set_config(&self.config);
bus.write(address, bytes).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
async fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Result<(), I2cDeviceError<BUS::Error>> {
let mut bus = self.bus.lock().await;
bus.set_config(&self.config);
bus.write(address, bytes).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
type WriteReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn write_read<'a>(
async fn write_read<'a>(
&'a mut self,
address: u8,
wr_buffer: &'a [u8],
rd_buffer: &'a mut [u8],
) -> Self::WriteReadFuture<'a> {
async move {
let mut bus = self.bus.lock().await;
bus.set_config(&self.config);
bus.write_read(address, wr_buffer, rd_buffer)
.await
.map_err(I2cDeviceError::I2c)?;
Ok(())
}
) -> Result<(), I2cDeviceError<BUS::Error>> {
let mut bus = self.bus.lock().await;
bus.set_config(&self.config);
bus.write_read(address, wr_buffer, rd_buffer)
.await
.map_err(I2cDeviceError::I2c)?;
Ok(())
}
type TransactionFuture<'a, 'b> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a, 'b: 'a;
fn transaction<'a, 'b>(
async fn transaction<'a, 'b>(
&'a mut self,
address: u8,
operations: &'a mut [embedded_hal_async::i2c::Operation<'b>],
) -> Self::TransactionFuture<'a, 'b> {
) -> Result<(), I2cDeviceError<BUS::Error>> {
let _ = address;
let _ = operations;
async move { todo!() }
todo!()
}
}

View file

@ -65,33 +65,25 @@ where
{
type Bus = BUS;
type TransactionFuture<'a, R, F, Fut> = impl Future<Output = Result<R, Self::Error>> + 'a
async fn transaction<R, F, Fut>(&mut self, f: F) -> Result<R, Self::Error>
where
Self: 'a, R: 'a, F: FnOnce(*mut Self::Bus) -> Fut + 'a,
Fut: Future<Output = Result<R, <Self::Bus as ErrorType>::Error>> + 'a;
fn transaction<'a, R, F, Fut>(&'a mut self, f: F) -> Self::TransactionFuture<'a, R, F, Fut>
where
R: 'a,
F: FnOnce(*mut Self::Bus) -> Fut + 'a,
Fut: Future<Output = Result<R, <Self::Bus as ErrorType>::Error>> + 'a,
F: FnOnce(*mut Self::Bus) -> Fut,
Fut: Future<Output = Result<R, <Self::Bus as ErrorType>::Error>>,
{
async move {
let mut bus = self.bus.lock().await;
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
let mut bus = self.bus.lock().await;
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
let f_res = f(&mut *bus).await;
let f_res = f(&mut *bus).await;
// On failure, it's important to still flush and deassert CS.
let flush_res = bus.flush().await;
let cs_res = self.cs.set_high();
// On failure, it's important to still flush and deassert CS.
let flush_res = bus.flush().await;
let cs_res = self.cs.set_high();
let f_res = f_res.map_err(SpiDeviceError::Spi)?;
flush_res.map_err(SpiDeviceError::Spi)?;
cs_res.map_err(SpiDeviceError::Cs)?;
let f_res = f_res.map_err(SpiDeviceError::Spi)?;
flush_res.map_err(SpiDeviceError::Spi)?;
cs_res.map_err(SpiDeviceError::Cs)?;
Ok(f_res)
}
Ok(f_res)
}
}
@ -130,33 +122,25 @@ where
{
type Bus = BUS;
type TransactionFuture<'a, R, F, Fut> = impl Future<Output = Result<R, Self::Error>> + 'a
async fn transaction<R, F, Fut>(&mut self, f: F) -> Result<R, Self::Error>
where
Self: 'a, R: 'a, F: FnOnce(*mut Self::Bus) -> Fut + 'a,
Fut: Future<Output = Result<R, <Self::Bus as ErrorType>::Error>> + 'a;
fn transaction<'a, R, F, Fut>(&'a mut self, f: F) -> Self::TransactionFuture<'a, R, F, Fut>
where
R: 'a,
F: FnOnce(*mut Self::Bus) -> Fut + 'a,
Fut: Future<Output = Result<R, <Self::Bus as ErrorType>::Error>> + 'a,
F: FnOnce(*mut Self::Bus) -> Fut,
Fut: Future<Output = Result<R, <Self::Bus as ErrorType>::Error>>,
{
async move {
let mut bus = self.bus.lock().await;
bus.set_config(&self.config);
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
let mut bus = self.bus.lock().await;
bus.set_config(&self.config);
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
let f_res = f(&mut *bus).await;
let f_res = f(&mut *bus).await;
// On failure, it's important to still flush and deassert CS.
let flush_res = bus.flush().await;
let cs_res = self.cs.set_high();
// On failure, it's important to still flush and deassert CS.
let flush_res = bus.flush().await;
let cs_res = self.cs.set_high();
let f_res = f_res.map_err(SpiDeviceError::Spi)?;
flush_res.map_err(SpiDeviceError::Spi)?;
cs_res.map_err(SpiDeviceError::Cs)?;
let f_res = f_res.map_err(SpiDeviceError::Spi)?;
flush_res.map_err(SpiDeviceError::Spi)?;
cs_res.map_err(SpiDeviceError::Cs)?;
Ok(f_res)
}
Ok(f_res)
}
}

View file

@ -1,9 +1,15 @@
[package]
name = "embassy-executor"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "async/await executor designed for embedded usage"
repository = "https://github.com/embassy-rs/embassy"
categories = [
"embedded",
"no-std",
"asynchronous",
]
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-v$VERSION/embassy-executor/src/"
@ -21,10 +27,13 @@ flavors = [
{ name = "thumbv8m.main-none-eabihf", target = "thumbv8m.main-none-eabihf", features = [] },
]
[package.metadata.docs.rs]
features = ["std", "nightly", "defmt"]
[features]
default = []
std = ["embassy-macros/std", "critical-section/std"]
wasm = ["dep:wasm-bindgen", "dep:js-sys", "embassy-macros/wasm"]
std = ["critical-section/std"]
wasm = ["dep:wasm-bindgen", "dep:js-sys"]
# Enable nightly-only features
nightly = []

View file

@ -1,7 +1,6 @@
use core::marker::PhantomData;
use core::ptr;
use atomic_polyfill::{AtomicBool, Ordering};
use core::sync::atomic::{AtomicBool, Ordering};
use super::{raw, Spawner};

View file

@ -1,7 +1,6 @@
use core::marker::PhantomData;
use core::ptr;
use atomic_polyfill::{AtomicBool, Ordering};
use core::sync::atomic::{AtomicBool, Ordering};
use super::{raw, Spawner};

View file

@ -8,18 +8,22 @@
pub(crate) mod fmt;
#[cfg(feature = "nightly")]
pub use embassy_macros::{main, task};
pub use embassy_macros::task;
cfg_if::cfg_if! {
if #[cfg(cortex_m)] {
#[path="arch/cortex_m.rs"]
mod arch;
pub use arch::*;
#[cfg(feature = "nightly")]
pub use embassy_macros::main_cortex_m as main;
}
else if #[cfg(target_arch="riscv32")] {
#[path="arch/riscv32.rs"]
mod arch;
pub use arch::*;
#[cfg(feature = "nightly")]
pub use embassy_macros::main_riscv as main;
}
else if #[cfg(all(target_arch="xtensa", feature = "nightly"))] {
#[path="arch/xtensa.rs"]
@ -30,11 +34,15 @@ cfg_if::cfg_if! {
#[path="arch/wasm.rs"]
mod arch;
pub use arch::*;
#[cfg(feature = "nightly")]
pub use embassy_macros::main_wasm as main;
}
else if #[cfg(feature="std")] {
#[path="arch/std.rs"]
mod arch;
pub use arch::*;
#[cfg(feature = "nightly")]
pub use embassy_macros::main_std as main;
}
}

View file

@ -15,10 +15,10 @@ mod waker;
use core::cell::Cell;
use core::future::Future;
use core::mem;
use core::pin::Pin;
use core::ptr::NonNull;
use core::task::{Context, Poll};
use core::{mem, ptr};
use atomic_polyfill::{AtomicU32, Ordering};
use critical_section::CriticalSection;
@ -43,14 +43,11 @@ pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1;
pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2;
/// Raw task header for use in task pointers.
///
/// This is an opaque struct, used for raw pointers to tasks, for use
/// with funtions like [`wake_task`] and [`task_from_waker`].
pub struct TaskHeader {
pub(crate) struct TaskHeader {
pub(crate) state: AtomicU32,
pub(crate) run_queue_item: RunQueueItem,
pub(crate) executor: Cell<*const Executor>, // Valid if state != 0
pub(crate) poll_fn: UninitCell<unsafe fn(NonNull<TaskHeader>)>, // Valid if STATE_SPAWNED
pub(crate) executor: Cell<Option<&'static Executor>>,
poll_fn: Cell<Option<unsafe fn(TaskRef)>>,
#[cfg(feature = "integrated-timers")]
pub(crate) expires_at: Cell<Instant>,
@ -58,20 +55,34 @@ pub struct TaskHeader {
pub(crate) timer_queue_item: timer_queue::TimerQueueItem,
}
impl TaskHeader {
pub(crate) const fn new() -> Self {
Self {
state: AtomicU32::new(0),
run_queue_item: RunQueueItem::new(),
executor: Cell::new(ptr::null()),
poll_fn: UninitCell::uninit(),
/// This is essentially a `&'static TaskStorage<F>` where the type of the future has been erased.
#[derive(Clone, Copy)]
pub struct TaskRef {
ptr: NonNull<TaskHeader>,
}
#[cfg(feature = "integrated-timers")]
expires_at: Cell::new(Instant::from_ticks(0)),
#[cfg(feature = "integrated-timers")]
timer_queue_item: timer_queue::TimerQueueItem::new(),
impl TaskRef {
fn new<F: Future + 'static>(task: &'static TaskStorage<F>) -> Self {
Self {
ptr: NonNull::from(task).cast(),
}
}
/// Safety: The pointer must have been obtained with `Task::as_ptr`
pub(crate) unsafe fn from_ptr(ptr: *const TaskHeader) -> Self {
Self {
ptr: NonNull::new_unchecked(ptr as *mut TaskHeader),
}
}
pub(crate) fn header(self) -> &'static TaskHeader {
unsafe { self.ptr.as_ref() }
}
/// The returned pointer is valid for the entire TaskStorage.
pub(crate) fn as_ptr(self) -> *const TaskHeader {
self.ptr.as_ptr()
}
}
/// Raw storage in which a task can be spawned.
@ -101,7 +112,18 @@ impl<F: Future + 'static> TaskStorage<F> {
/// Create a new TaskStorage, in not-spawned state.
pub const fn new() -> Self {
Self {
raw: TaskHeader::new(),
raw: TaskHeader {
state: AtomicU32::new(0),
run_queue_item: RunQueueItem::new(),
executor: Cell::new(None),
// Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss`
poll_fn: Cell::new(None),
#[cfg(feature = "integrated-timers")]
expires_at: Cell::new(Instant::from_ticks(0)),
#[cfg(feature = "integrated-timers")]
timer_queue_item: timer_queue::TimerQueueItem::new(),
},
future: UninitCell::uninit(),
}
}
@ -120,29 +142,17 @@ impl<F: Future + 'static> TaskStorage<F> {
/// Once the task has finished running, you may spawn it again. It is allowed to spawn it
/// on a different executor.
pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> {
if self.spawn_mark_used() {
return unsafe { SpawnToken::<F>::new(self.spawn_initialize(future)) };
let task = AvailableTask::claim(self);
match task {
Some(task) => {
let task = task.initialize(future);
unsafe { SpawnToken::<F>::new(task) }
}
None => SpawnToken::new_failed(),
}
SpawnToken::<F>::new_failed()
}
fn spawn_mark_used(&'static self) -> bool {
let state = STATE_SPAWNED | STATE_RUN_QUEUED;
self.raw
.state
.compare_exchange(0, state, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
}
unsafe fn spawn_initialize(&'static self, future: impl FnOnce() -> F) -> NonNull<TaskHeader> {
// Initialize the task
self.raw.poll_fn.write(Self::poll);
self.future.write(future());
NonNull::new_unchecked(self as *const TaskStorage<F> as *const TaskHeader as *mut TaskHeader)
}
unsafe fn poll(p: NonNull<TaskHeader>) {
unsafe fn poll(p: TaskRef) {
let this = &*(p.as_ptr() as *const TaskStorage<F>);
let future = Pin::new_unchecked(this.future.as_mut());
@ -164,6 +174,28 @@ impl<F: Future + 'static> TaskStorage<F> {
unsafe impl<F: Future + 'static> Sync for TaskStorage<F> {}
struct AvailableTask<F: Future + 'static> {
task: &'static TaskStorage<F>,
}
impl<F: Future + 'static> AvailableTask<F> {
fn claim(task: &'static TaskStorage<F>) -> Option<Self> {
task.raw
.state
.compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire)
.ok()
.map(|_| Self { task })
}
fn initialize(self, future: impl FnOnce() -> F) -> TaskRef {
unsafe {
self.task.raw.poll_fn.set(Some(TaskStorage::<F>::poll));
self.task.future.write(future());
}
TaskRef::new(self.task)
}
}
/// Raw storage that can hold up to N tasks of the same type.
///
/// This is essentially a `[TaskStorage<F>; N]`.
@ -187,13 +219,14 @@ impl<F: Future + 'static, const N: usize> TaskPool<F, N> {
/// is currently free. If none is free, a "poisoned" SpawnToken is returned,
/// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error.
pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> {
for task in &self.pool {
if task.spawn_mark_used() {
return unsafe { SpawnToken::<F>::new(task.spawn_initialize(future)) };
let task = self.pool.iter().find_map(AvailableTask::claim);
match task {
Some(task) => {
let task = task.initialize(future);
unsafe { SpawnToken::<F>::new(task) }
}
None => SpawnToken::new_failed(),
}
SpawnToken::<F>::new_failed()
}
/// Like spawn(), but allows the task to be send-spawned if the args are Send even if
@ -235,13 +268,14 @@ impl<F: Future + 'static, const N: usize> TaskPool<F, N> {
// This ONLY holds for `async fn` futures. The other `spawn` methods can be called directly
// by the user, with arbitrary hand-implemented futures. This is why these return `SpawnToken<F>`.
for task in &self.pool {
if task.spawn_mark_used() {
return SpawnToken::<FutFn>::new(task.spawn_initialize(future));
let task = self.pool.iter().find_map(AvailableTask::claim);
match task {
Some(task) => {
let task = task.initialize(future);
unsafe { SpawnToken::<FutFn>::new(task) }
}
None => SpawnToken::new_failed(),
}
SpawnToken::<FutFn>::new_failed()
}
}
@ -307,7 +341,7 @@ impl Executor {
/// - `task` must be set up to run in this executor.
/// - `task` must NOT be already enqueued (in this executor or another one).
#[inline(always)]
unsafe fn enqueue(&self, cs: CriticalSection, task: NonNull<TaskHeader>) {
unsafe fn enqueue(&self, cs: CriticalSection, task: TaskRef) {
#[cfg(feature = "rtos-trace")]
trace::task_ready_begin(task.as_ptr() as u32);
@ -325,8 +359,8 @@ impl Executor {
/// It is OK to use `unsafe` to call this from a thread that's not the executor thread.
/// In this case, the task's Future must be Send. This is because this is effectively
/// sending the task to the executor thread.
pub(super) unsafe fn spawn(&'static self, task: NonNull<TaskHeader>) {
task.as_ref().executor.set(self);
pub(super) unsafe fn spawn(&'static self, task: TaskRef) {
task.header().executor.set(Some(self));
#[cfg(feature = "rtos-trace")]
trace::task_new(task.as_ptr() as u32);
@ -354,46 +388,54 @@ impl Executor {
/// somehow schedule for `poll()` to be called later, at a time you know for sure there's
/// no `poll()` already running.
pub unsafe fn poll(&'static self) {
#[cfg(feature = "integrated-timers")]
self.timer_queue.dequeue_expired(Instant::now(), |task| wake_task(task));
loop {
#[cfg(feature = "integrated-timers")]
self.timer_queue.dequeue_expired(Instant::now(), |task| wake_task(task));
self.run_queue.dequeue_all(|p| {
let task = p.as_ref();
self.run_queue.dequeue_all(|p| {
let task = p.header();
#[cfg(feature = "integrated-timers")]
task.expires_at.set(Instant::MAX);
let state = task.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel);
if state & STATE_SPAWNED == 0 {
// If task is not running, ignore it. This can happen in the following scenario:
// - Task gets dequeued, poll starts
// - While task is being polled, it gets woken. It gets placed in the queue.
// - Task poll finishes, returning done=true
// - RUNNING bit is cleared, but the task is already in the queue.
return;
}
#[cfg(feature = "rtos-trace")]
trace::task_exec_begin(p.as_ptr() as u32);
// Run the task
task.poll_fn.get().unwrap_unchecked()(p);
#[cfg(feature = "rtos-trace")]
trace::task_exec_end();
// Enqueue or update into timer_queue
#[cfg(feature = "integrated-timers")]
self.timer_queue.update(p);
});
#[cfg(feature = "integrated-timers")]
task.expires_at.set(Instant::MAX);
let state = task.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel);
if state & STATE_SPAWNED == 0 {
// If task is not running, ignore it. This can happen in the following scenario:
// - Task gets dequeued, poll starts
// - While task is being polled, it gets woken. It gets placed in the queue.
// - Task poll finishes, returning done=true
// - RUNNING bit is cleared, but the task is already in the queue.
return;
{
// If this is already in the past, set_alarm might return false
// In that case do another poll loop iteration.
let next_expiration = self.timer_queue.next_expiration();
if driver::set_alarm(self.alarm, next_expiration.as_ticks()) {
break;
}
}
#[cfg(feature = "rtos-trace")]
trace::task_exec_begin(p.as_ptr() as u32);
// Run the task
task.poll_fn.read()(p as _);
#[cfg(feature = "rtos-trace")]
trace::task_exec_end();
// Enqueue or update into timer_queue
#[cfg(feature = "integrated-timers")]
self.timer_queue.update(p);
});
#[cfg(feature = "integrated-timers")]
{
// If this is already in the past, set_alarm will immediately trigger the alarm.
// This will cause `signal_fn` to be called, which will cause `poll()` to be called again,
// so we immediately do another poll loop iteration.
let next_expiration = self.timer_queue.next_expiration();
driver::set_alarm(self.alarm, next_expiration.as_ticks());
#[cfg(not(feature = "integrated-timers"))]
{
break;
}
}
#[cfg(feature = "rtos-trace")]
@ -409,16 +451,12 @@ impl Executor {
}
}
/// Wake a task by raw pointer.
/// Wake a task by `TaskRef`.
///
/// You can obtain task pointers from `Waker`s using [`task_from_waker`].
///
/// # Safety
///
/// `task` must be a valid task pointer obtained from [`task_from_waker`].
pub unsafe fn wake_task(task: NonNull<TaskHeader>) {
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
pub fn wake_task(task: TaskRef) {
critical_section::with(|cs| {
let header = task.as_ref();
let header = task.header();
let state = header.state.load(Ordering::Relaxed);
// If already scheduled, or if not started,
@ -430,20 +468,29 @@ pub unsafe fn wake_task(task: NonNull<TaskHeader>) {
header.state.store(state | STATE_RUN_QUEUED, Ordering::Relaxed);
// We have just marked the task as scheduled, so enqueue it.
let executor = &*header.executor.get();
executor.enqueue(cs, task);
unsafe {
let executor = header.executor.get().unwrap_unchecked();
executor.enqueue(cs, task);
}
})
}
#[cfg(feature = "integrated-timers")]
#[no_mangle]
unsafe fn _embassy_time_schedule_wake(at: Instant, waker: &core::task::Waker) {
let task = waker::task_from_waker(waker);
let task = task.as_ref();
let expires_at = task.expires_at.get();
task.expires_at.set(expires_at.min(at));
struct TimerQueue;
#[cfg(feature = "integrated-timers")]
impl embassy_time::queue::TimerQueue for TimerQueue {
fn schedule_wake(&'static self, at: Instant, waker: &core::task::Waker) {
let task = waker::task_from_waker(waker);
let task = task.header();
let expires_at = task.expires_at.get();
task.expires_at.set(expires_at.min(at));
}
}
#[cfg(feature = "integrated-timers")]
embassy_time::timer_queue_impl!(static TIMER_QUEUE: TimerQueue = TimerQueue);
#[cfg(feature = "rtos-trace")]
impl rtos_trace::RtosTraceOSCallbacks for Executor {
fn task_list() {

View file

@ -4,7 +4,7 @@ use core::ptr::NonNull;
use atomic_polyfill::{AtomicPtr, Ordering};
use critical_section::CriticalSection;
use super::TaskHeader;
use super::{TaskHeader, TaskRef};
pub(crate) struct RunQueueItem {
next: AtomicPtr<TaskHeader>,
@ -46,25 +46,26 @@ impl RunQueue {
///
/// `item` must NOT be already enqueued in any queue.
#[inline(always)]
pub(crate) unsafe fn enqueue(&self, _cs: CriticalSection, task: NonNull<TaskHeader>) -> bool {
pub(crate) unsafe fn enqueue(&self, _cs: CriticalSection, task: TaskRef) -> bool {
let prev = self.head.load(Ordering::Relaxed);
task.as_ref().run_queue_item.next.store(prev, Ordering::Relaxed);
self.head.store(task.as_ptr(), Ordering::Relaxed);
task.header().run_queue_item.next.store(prev, Ordering::Relaxed);
self.head.store(task.as_ptr() as _, Ordering::Relaxed);
prev.is_null()
}
/// Empty the queue, then call `on_task` for each task that was in the queue.
/// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue
/// 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(NonNull<TaskHeader>)) {
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);
// 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()) };
// 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 = unsafe { task.as_ref() }.run_queue_item.next.load(Ordering::Relaxed);
let next = task.header().run_queue_item.next.load(Ordering::Relaxed);
on_task(task);

View file

@ -1,45 +1,39 @@
use core::cell::Cell;
use core::cmp::min;
use core::ptr;
use core::ptr::NonNull;
use atomic_polyfill::Ordering;
use embassy_time::Instant;
use super::{TaskHeader, STATE_TIMER_QUEUED};
use super::{TaskRef, STATE_TIMER_QUEUED};
pub(crate) struct TimerQueueItem {
next: Cell<*mut TaskHeader>,
next: Cell<Option<TaskRef>>,
}
impl TimerQueueItem {
pub const fn new() -> Self {
Self {
next: Cell::new(ptr::null_mut()),
}
Self { next: Cell::new(None) }
}
}
pub(crate) struct TimerQueue {
head: Cell<*mut TaskHeader>,
head: Cell<Option<TaskRef>>,
}
impl TimerQueue {
pub const fn new() -> Self {
Self {
head: Cell::new(ptr::null_mut()),
}
Self { head: Cell::new(None) }
}
pub(crate) unsafe fn update(&self, p: NonNull<TaskHeader>) {
let task = p.as_ref();
pub(crate) unsafe fn update(&self, p: TaskRef) {
let task = p.header();
if task.expires_at.get() != Instant::MAX {
let old_state = task.state.fetch_or(STATE_TIMER_QUEUED, Ordering::AcqRel);
let is_new = old_state & STATE_TIMER_QUEUED == 0;
if is_new {
task.timer_queue_item.next.set(self.head.get());
self.head.set(p.as_ptr());
self.head.set(Some(p));
}
}
}
@ -47,7 +41,7 @@ impl TimerQueue {
pub(crate) unsafe fn next_expiration(&self) -> Instant {
let mut res = Instant::MAX;
self.retain(|p| {
let task = p.as_ref();
let task = p.header();
let expires = task.expires_at.get();
res = min(res, expires);
expires != Instant::MAX
@ -55,9 +49,9 @@ impl TimerQueue {
res
}
pub(crate) unsafe fn dequeue_expired(&self, now: Instant, on_task: impl Fn(NonNull<TaskHeader>)) {
pub(crate) unsafe fn dequeue_expired(&self, now: Instant, on_task: impl Fn(TaskRef)) {
self.retain(|p| {
let task = p.as_ref();
let task = p.header();
if task.expires_at.get() <= now {
on_task(p);
false
@ -67,11 +61,10 @@ impl TimerQueue {
});
}
pub(crate) unsafe fn retain(&self, mut f: impl FnMut(NonNull<TaskHeader>) -> bool) {
pub(crate) unsafe fn retain(&self, mut f: impl FnMut(TaskRef) -> bool) {
let mut prev = &self.head;
while !prev.get().is_null() {
let p = NonNull::new_unchecked(prev.get());
let task = &*p.as_ptr();
while let Some(p) = prev.get() {
let task = p.header();
if f(p) {
// Skip to next
prev = &task.timer_queue_item.next;

View file

@ -25,9 +25,3 @@ impl<T> UninitCell<T> {
ptr::drop_in_place(self.as_mut_ptr())
}
}
impl<T: Copy> UninitCell<T> {
pub unsafe fn read(&self) -> T {
ptr::read(self.as_mut_ptr())
}
}

View file

@ -1,8 +1,7 @@
use core::mem;
use core::ptr::NonNull;
use core::task::{RawWaker, RawWakerVTable, Waker};
use super::{wake_task, TaskHeader};
use super::{wake_task, TaskHeader, TaskRef};
const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake, drop);
@ -11,14 +10,14 @@ unsafe fn clone(p: *const ()) -> RawWaker {
}
unsafe fn wake(p: *const ()) {
wake_task(NonNull::new_unchecked(p as *mut TaskHeader))
wake_task(TaskRef::from_ptr(p as *const TaskHeader))
}
unsafe fn drop(_: *const ()) {
// nop
}
pub(crate) unsafe fn from_task(p: NonNull<TaskHeader>) -> Waker {
pub(crate) unsafe fn from_task(p: TaskRef) -> Waker {
Waker::from_raw(RawWaker::new(p.as_ptr() as _, &VTABLE))
}
@ -33,7 +32,7 @@ pub(crate) unsafe fn from_task(p: NonNull<TaskHeader>) -> Waker {
/// # Panics
///
/// Panics if the waker is not created by the Embassy executor.
pub fn task_from_waker(waker: &Waker) -> NonNull<TaskHeader> {
pub fn task_from_waker(waker: &Waker) -> TaskRef {
// safety: OK because WakerHack has the same layout as Waker.
// This is not really guaranteed because the structs are `repr(Rust)`, it is
// indeed the case in the current implementation.
@ -43,8 +42,8 @@ pub fn task_from_waker(waker: &Waker) -> NonNull<TaskHeader> {
panic!("Found waker not created by the Embassy executor. `embassy_time::Timer` only works with the Embassy executor.")
}
// safety: we never create a waker with a null data pointer.
unsafe { NonNull::new_unchecked(hack.data as *mut TaskHeader) }
// safety: our wakers are always created with `TaskRef::as_ptr`
unsafe { TaskRef::from_ptr(hack.data as *const TaskHeader) }
}
struct WakerHack {

View file

@ -1,7 +1,6 @@
use core::future::poll_fn;
use core::marker::PhantomData;
use core::mem;
use core::ptr::NonNull;
use core::task::Poll;
use super::raw;
@ -22,12 +21,12 @@ use super::raw;
/// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it.
#[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"]
pub struct SpawnToken<S> {
raw_task: Option<NonNull<raw::TaskHeader>>,
raw_task: Option<raw::TaskRef>,
phantom: PhantomData<*mut S>,
}
impl<S> SpawnToken<S> {
pub(crate) unsafe fn new(raw_task: NonNull<raw::TaskHeader>) -> Self {
pub(crate) unsafe fn new(raw_task: raw::TaskRef) -> Self {
Self {
raw_task: Some(raw_task),
phantom: PhantomData,
@ -90,10 +89,10 @@ impl Spawner {
///
/// Panics if the current executor is not an Embassy executor.
pub async fn for_current_executor() -> Self {
poll_fn(|cx| unsafe {
poll_fn(|cx| {
let task = raw::task_from_waker(cx.waker());
let executor = (*task.as_ptr()).executor.get();
Poll::Ready(Self::new(&*executor))
let executor = unsafe { task.header().executor.get().unwrap_unchecked() };
Poll::Ready(Self::new(executor))
})
.await
}
@ -166,10 +165,10 @@ impl SendSpawner {
///
/// Panics if the current executor is not an Embassy executor.
pub async fn for_current_executor() -> Self {
poll_fn(|cx| unsafe {
poll_fn(|cx| {
let task = raw::task_from_waker(cx.waker());
let executor = (*task.as_ptr()).executor.get();
Poll::Ready(Self::new(&*executor))
let executor = unsafe { task.header().executor.get().unwrap_unchecked() };
Poll::Ready(Self::new(executor))
})
.await
}

View file

@ -0,0 +1,371 @@
use core::slice;
use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
/// Atomic reusable ringbuffer
///
/// This ringbuffer implementation is designed to be stored in a `static`,
/// therefore all methods take `&self` and not `&mut self`.
///
/// It is "reusable": when created it has no backing buffer, you can give it
/// one with `init` and take it back with `deinit`, and init it again in the
/// future if needed. This is very non-idiomatic, but helps a lot when storing
/// it in a `static`.
///
/// One concurrent writer and one concurrent reader are supported, even at
/// different execution priorities (like main and irq).
pub struct RingBuffer {
buf: AtomicPtr<u8>,
len: AtomicUsize,
start: AtomicUsize,
end: AtomicUsize,
}
pub struct Reader<'a>(&'a RingBuffer);
pub struct Writer<'a>(&'a RingBuffer);
impl RingBuffer {
/// Create a new empty ringbuffer.
pub const fn new() -> Self {
Self {
buf: AtomicPtr::new(core::ptr::null_mut()),
len: AtomicUsize::new(0),
start: AtomicUsize::new(0),
end: AtomicUsize::new(0),
}
}
/// Initialize the ring buffer with a buffer.
///
/// # Safety
/// - The buffer (`buf .. buf+len`) must be valid memory until `deinit` is called.
/// - Must not be called concurrently with any other methods.
pub unsafe fn init(&self, buf: *mut u8, len: usize) {
// Ordering: it's OK to use `Relaxed` because this is not called
// concurrently with other methods.
self.buf.store(buf, Ordering::Relaxed);
self.len.store(len, Ordering::Relaxed);
self.start.store(0, Ordering::Relaxed);
self.end.store(0, Ordering::Relaxed);
}
/// Deinitialize the ringbuffer.
///
/// After calling this, the ringbuffer becomes empty, as if it was
/// just created with `new()`.
///
/// # Safety
/// - Must not be called concurrently with any other methods.
pub unsafe fn deinit(&self) {
// Ordering: it's OK to use `Relaxed` because this is not called
// concurrently with other methods.
self.len.store(0, Ordering::Relaxed);
self.start.store(0, Ordering::Relaxed);
self.end.store(0, Ordering::Relaxed);
}
/// Create a reader.
///
/// # Safety
///
/// Only one reader can exist at a time.
pub unsafe fn reader(&self) -> Reader<'_> {
Reader(self)
}
/// Create a writer.
///
/// # Safety
///
/// Only one writer can exist at a time.
pub unsafe fn writer(&self) -> Writer<'_> {
Writer(self)
}
pub fn len(&self) -> usize {
self.len.load(Ordering::Relaxed)
}
pub fn is_full(&self) -> bool {
let len = self.len.load(Ordering::Relaxed);
let start = self.start.load(Ordering::Relaxed);
let end = self.end.load(Ordering::Relaxed);
len == 0 || self.wrap(end + 1) == start
}
pub fn is_empty(&self) -> bool {
let start = self.start.load(Ordering::Relaxed);
let end = self.end.load(Ordering::Relaxed);
start == end
}
fn wrap(&self, n: usize) -> usize {
let len = self.len.load(Ordering::Relaxed);
assert!(n <= len);
if n == len {
0
} else {
n
}
}
}
impl<'a> Writer<'a> {
/// Push data into the buffer in-place.
///
/// The closure `f` is called with a free part of the buffer, it must write
/// some data to it and return the amount of bytes written.
pub fn push(&mut self, f: impl FnOnce(&mut [u8]) -> usize) -> usize {
let (p, n) = self.push_buf();
let buf = unsafe { slice::from_raw_parts_mut(p, n) };
let n = f(buf);
self.push_done(n);
n
}
/// Push one data byte.
///
/// Returns true if pushed succesfully.
pub fn push_one(&mut self, val: u8) -> bool {
let n = self.push(|f| match f {
[] => 0,
[x, ..] => {
*x = val;
1
}
});
n != 0
}
/// Get a buffer where data can be pushed to.
///
/// Equivalent to [`Self::push_buf`] but returns a slice.
pub fn push_slice(&mut self) -> &mut [u8] {
let (data, len) = self.push_buf();
unsafe { slice::from_raw_parts_mut(data, len) }
}
/// Get a buffer where data can be pushed to.
///
/// Write data to the start of the buffer, then call `push_done` with
/// however many bytes you've pushed.
///
/// The buffer is suitable to DMA to.
///
/// If the ringbuf is full, size=0 will be returned.
///
/// The buffer stays valid as long as no other `Writer` method is called
/// and `init`/`deinit` aren't called on the ringbuf.
pub fn push_buf(&mut self) -> (*mut u8, usize) {
// Ordering: popping writes `start` last, so we read `start` first.
// Read it with Acquire ordering, so that the next accesses can't be reordered up past it.
let start = self.0.start.load(Ordering::Acquire);
let buf = self.0.buf.load(Ordering::Relaxed);
let len = self.0.len.load(Ordering::Relaxed);
let end = self.0.end.load(Ordering::Relaxed);
let n = if start <= end {
len - end - (start == 0 && len != 0) as usize
} else {
start - end - 1
};
trace!(" ringbuf: push_buf {:?}..{:?}", end, end + n);
(unsafe { buf.add(end) }, n)
}
pub fn push_done(&mut self, n: usize) {
trace!(" ringbuf: push {:?}", n);
let end = self.0.end.load(Ordering::Relaxed);
// Ordering: write `end` last, with Release ordering.
// The ordering ensures no preceding memory accesses (such as writing
// the actual data in the buffer) can be reordered down past it, which
// will guarantee the reader sees them after reading from `end`.
self.0.end.store(self.0.wrap(end + n), Ordering::Release);
}
}
impl<'a> Reader<'a> {
/// Pop data from the buffer in-place.
///
/// The closure `f` is called with the next data, it must process
/// some data from it and return the amount of bytes processed.
pub fn pop(&mut self, f: impl FnOnce(&[u8]) -> usize) -> usize {
let (p, n) = self.pop_buf();
let buf = unsafe { slice::from_raw_parts(p, n) };
let n = f(buf);
self.pop_done(n);
n
}
/// Pop one data byte.
///
/// Returns true if popped succesfully.
pub fn pop_one(&mut self) -> Option<u8> {
let mut res = None;
self.pop(|f| match f {
&[] => 0,
&[x, ..] => {
res = Some(x);
1
}
});
res
}
/// Get a buffer where data can be popped from.
///
/// Equivalent to [`Self::pop_buf`] but returns a slice.
pub fn pop_slice(&mut self) -> &mut [u8] {
let (data, len) = self.pop_buf();
unsafe { slice::from_raw_parts_mut(data, len) }
}
/// Get a buffer where data can be popped from.
///
/// Read data from the start of the buffer, then call `pop_done` with
/// however many bytes you've processed.
///
/// The buffer is suitable to DMA from.
///
/// If the ringbuf is empty, size=0 will be returned.
///
/// The buffer stays valid as long as no other `Reader` method is called
/// and `init`/`deinit` aren't called on the ringbuf.
pub fn pop_buf(&mut self) -> (*mut u8, usize) {
// Ordering: pushing writes `end` last, so we read `end` first.
// Read it with Acquire ordering, so that the next accesses can't be reordered up past it.
// This is needed to guarantee we "see" the data written by the writer.
let end = self.0.end.load(Ordering::Acquire);
let buf = self.0.buf.load(Ordering::Relaxed);
let len = self.0.len.load(Ordering::Relaxed);
let start = self.0.start.load(Ordering::Relaxed);
let n = if end < start { len - start } else { end - start };
trace!(" ringbuf: pop_buf {:?}..{:?}", start, start + n);
(unsafe { buf.add(start) }, n)
}
pub fn pop_done(&mut self, n: usize) {
trace!(" ringbuf: pop {:?}", n);
let start = self.0.start.load(Ordering::Relaxed);
// Ordering: write `start` last, with Release ordering.
// The ordering ensures no preceding memory accesses (such as reading
// the actual data) can be reordered down past it. This is necessary
// because writing to `start` is effectively freeing the read part of the
// buffer, which "gives permission" to the writer to write to it again.
// Therefore, all buffer accesses must be completed before this.
self.0.start.store(self.0.wrap(start + n), Ordering::Release);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn push_pop() {
let mut b = [0; 4];
let rb = RingBuffer::new();
unsafe {
rb.init(b.as_mut_ptr(), 4);
assert_eq!(rb.is_empty(), true);
assert_eq!(rb.is_full(), false);
rb.writer().push(|buf| {
// If capacity is 4, we can fill it up to 3.
assert_eq!(3, buf.len());
buf[0] = 1;
buf[1] = 2;
buf[2] = 3;
3
});
assert_eq!(rb.is_empty(), false);
assert_eq!(rb.is_full(), true);
rb.writer().push(|buf| {
// If it's full, we can push 0 bytes.
assert_eq!(0, buf.len());
0
});
assert_eq!(rb.is_empty(), false);
assert_eq!(rb.is_full(), true);
rb.reader().pop(|buf| {
assert_eq!(3, buf.len());
assert_eq!(1, buf[0]);
1
});
assert_eq!(rb.is_empty(), false);
assert_eq!(rb.is_full(), false);
rb.reader().pop(|buf| {
assert_eq!(2, buf.len());
0
});
assert_eq!(rb.is_empty(), false);
assert_eq!(rb.is_full(), false);
rb.reader().pop(|buf| {
assert_eq!(2, buf.len());
assert_eq!(2, buf[0]);
assert_eq!(3, buf[1]);
2
});
assert_eq!(rb.is_empty(), true);
assert_eq!(rb.is_full(), false);
rb.reader().pop(|buf| {
assert_eq!(0, buf.len());
0
});
rb.writer().push(|buf| {
assert_eq!(1, buf.len());
buf[0] = 10;
1
});
rb.writer().push(|buf| {
assert_eq!(2, buf.len());
buf[0] = 11;
buf[1] = 12;
2
});
assert_eq!(rb.is_empty(), false);
assert_eq!(rb.is_full(), true);
}
}
#[test]
fn zero_len() {
let rb = RingBuffer::new();
unsafe {
assert_eq!(rb.is_empty(), true);
assert_eq!(rb.is_full(), true);
rb.writer().push(|buf| {
assert_eq!(0, buf.len());
0
});
rb.reader().pop(|buf| {
assert_eq!(0, buf.len());
0
});
}
}
}

View file

@ -4,6 +4,7 @@
// This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt;
pub mod atomic_ring_buffer;
pub mod drop;
mod macros;
mod peripheral;

View file

@ -1,10 +1,12 @@
#[macro_export]
macro_rules! peripherals {
($($(#[$cfg:meta])? $name:ident),*$(,)?) => {
/// Types for the peripheral singletons.
pub mod peripherals {
$(
$(#[$cfg])?
#[allow(non_camel_case_types)]
#[doc = concat!(stringify!($name), " peripheral")]
pub struct $name { _private: () }
$(#[$cfg])?
@ -25,9 +27,13 @@ macro_rules! peripherals {
)*
}
/// Struct containing all the peripheral singletons.
///
/// To obtain the peripherals, you must initialize the HAL, by calling [`crate::init`].
#[allow(non_snake_case)]
pub struct Peripherals {
$(
#[doc = concat!(stringify!($name), " peripheral")]
$(#[$cfg])?
pub $name: peripherals::$name,
)*

View file

@ -3,16 +3,17 @@ use core::ops::{Deref, DerefMut};
/// An exclusive reference to a peripheral.
///
/// This is functionally the same as a `&'a mut T`. The reason for having a
/// dedicated struct is memory efficiency:
/// This is functionally the same as a `&'a mut T`. There's a few advantages in having
/// a dedicated struct instead:
///
/// Peripheral singletons are typically either zero-sized (for concrete peripherals
/// like `PA9` or `Spi4`) or very small (for example `AnyPin` which is 1 byte).
/// However `&mut T` is always 4 bytes for 32-bit targets, even if T is zero-sized.
/// PeripheralRef stores a copy of `T` instead, so it's the same size.
///
/// but it is the size of `T` not the size
/// of a pointer. This is useful if T is a zero sized type.
/// - Memory efficiency: Peripheral singletons are typically either zero-sized (for concrete
/// peripherals like `PA9` or `SPI4`) or very small (for example `AnyPin`, which is 1 byte).
/// However `&mut T` is always 4 bytes for 32-bit targets, even if T is zero-sized.
/// PeripheralRef stores a copy of `T` instead, so it's the same size.
/// - Code size efficiency. If the user uses the same driver with both `SPI4` and `&mut SPI4`,
/// the driver code would be monomorphized two times. With PeripheralRef, the driver is generic
/// over a lifetime only. `SPI4` becomes `PeripheralRef<'static, SPI4>`, and `&mut SPI4` becomes
/// `PeripheralRef<'a, SPI4>`. Lifetimes don't cause monomorphization.
pub struct PeripheralRef<'a, T> {
inner: T,
_lifetime: PhantomData<&'a mut T>,

View file

@ -9,6 +9,7 @@ src_base = "https://github.com/embassy-rs/embassy/blob/embassy-lora-v$VERSION/em
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", "embassy-stm32/stm32wl55jc-cm4", "embassy-stm32/time-driver-any"] },
{ name = "stm32wl", target = "thumbv7em-none-eabihf", features = ["stm32wl", "embassy-stm32/stm32wl55jc-cm4", "embassy-stm32/time-driver-any"] },
]
@ -16,6 +17,7 @@ flavors = [
[lib]
[features]
sx126x = []
sx127x = []
stm32wl = ["embassy-stm32", "embassy-stm32/subghz"]
time = []
@ -30,7 +32,7 @@ embassy-time = { version = "0.1.0", path = "../embassy-time" }
embassy-sync = { version = "0.1.0", path = "../embassy-sync" }
embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false, optional = true }
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9" }
embedded-hal-async = { version = "=0.1.0-alpha.2" }
embedded-hal-async = { version = "=0.2.0-alpha.0" }
embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common", default-features = false }
futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] }
embedded-hal = { version = "0.2", features = ["unproven"] }

View file

@ -7,6 +7,8 @@ pub(crate) mod fmt;
#[cfg(feature = "stm32wl")]
pub mod stm32wl;
#[cfg(feature = "sx126x")]
pub mod sx126x;
#[cfg(feature = "sx127x")]
pub mod sx127x;

View file

@ -70,7 +70,7 @@ impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> {
/// 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);
trace!("TX request: {:?}", config);
self.switch.set_tx();
self.radio
@ -130,7 +130,7 @@ impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> {
/// 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);
trace!("RX request: {:?}", config);
self.switch.set_rx();
self.radio.set_rf_frequency(&RfFreq::from_frequency(config.frequency))?;
@ -172,7 +172,11 @@ impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> {
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)));
}
@ -193,7 +197,7 @@ impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> {
.clear_irq_status(irq_status)
.expect("error clearing irq status");
trace!("SUGHZ IRQ 0b{=u16:b}, {:?}", irq_status, status);
trace!("SUGHZ IRQ 0b{:016b}, {:?}", irq_status, status);
if irq_status == 0 {
Poll::Pending
@ -256,10 +260,10 @@ impl From<embassy_stm32::spi::Error> for RadioError {
impl<'d, RS> Timings for SubGhzRadio<'d, RS> {
fn get_rx_window_offset_ms(&self) -> i32 {
-500
-3
}
fn get_rx_window_duration_ms(&self) -> u32 {
3000
1003
}
}

View file

@ -0,0 +1,153 @@
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 {
-3
}
fn get_rx_window_duration_ms(&self) -> u32 {
1003
}
}
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)))
}
}
}

View file

@ -0,0 +1,256 @@
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;
}
}

View file

@ -0,0 +1,732 @@
#![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
}
}

View file

@ -0,0 +1,469 @@
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
}
}

View file

@ -0,0 +1,674 @@
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, &reg_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, &reg_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, &reg_ana_lna_buffer_original)
.await?;
self.brd_write_registers(Register::AnaMixer, &reg_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)
}
}

View file

@ -70,10 +70,10 @@ where
RFS: RadioSwitch + 'static,
{
fn get_rx_window_offset_ms(&self) -> i32 {
-500
-3
}
fn get_rx_window_duration_ms(&self) -> u32 {
800
1003
}
}

View file

@ -3,6 +3,13 @@ name = "embassy-macros"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "macros for creating the entry point and tasks for embassy-executor"
repository = "https://github.com/embassy-rs/embassy"
categories = [
"embedded",
"no-std",
"asynchronous",
]
[dependencies]
syn = { version = "1.0.76", features = ["full", "extra-traits"] }
@ -14,8 +21,5 @@ proc-macro2 = "1.0.29"
proc-macro = true
[features]
std = []
wasm = []
# Enabling this cause interrupt::take! to require embassy-executor
rtos-trace-interrupt = []

21
embassy-macros/README.md Normal file
View file

@ -0,0 +1,21 @@
# embassy-macros
An [Embassy](https://embassy.dev) project.
Macros for creating the main entry point and tasks that can be spawned by `embassy-executor`.
NOTE: The macros are re-exported by the `embassy-executor` crate which should be used instead of adding a direct dependency on the `embassy-macros` crate.
## Minimum supported Rust version (MSRV)
The `task` and `main` macros require the type alias impl trait (TAIT) nightly feature in order to compile.
## License
This work is licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
<http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.

View file

@ -1,3 +1,4 @@
#![doc = include_str!("../README.md")]
extern crate proc_macro;
use proc_macro::TokenStream;
@ -6,6 +7,36 @@ mod macros;
mod util;
use macros::*;
/// Declares an async task that can be run by `embassy-executor`. The optional `pool_size` parameter can be used to specify how
/// many concurrent tasks can be spawned (default is 1) for the function.
///
///
/// The following restrictions apply:
///
/// * The function must be declared `async`.
/// * The function must not use generics.
/// * The optional `pool_size` attribute must be 1 or greater.
///
///
/// ## Examples
///
/// Declaring a task taking no arguments:
///
/// ``` rust
/// #[embassy_executor::task]
/// async fn mytask() {
/// // Function body
/// }
/// ```
///
/// Declaring a task with a given pool size:
///
/// ``` rust
/// #[embassy_executor::task(pool_size = 4)]
/// async fn mytask() {
/// // Function body
/// }
/// ```
#[proc_macro_attribute]
pub fn task(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
@ -14,11 +45,104 @@ pub fn task(args: TokenStream, item: TokenStream) -> TokenStream {
task::run(args, f).unwrap_or_else(|x| x).into()
}
/// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task.
///
/// The following restrictions apply:
///
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
/// * The function must be declared `async`.
/// * The function must not use generics.
/// * Only a single `main` task may be declared.
///
/// ## Examples
/// Spawning a task:
///
/// ``` rust
/// #[embassy_executor::main]
/// async fn main(_s: embassy_executor::Spawner) {
/// // Function body
/// }
/// ```
#[proc_macro_attribute]
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(args, f).unwrap_or_else(|x| x).into()
main::run(args, f, main::cortex_m()).unwrap_or_else(|x| x).into()
}
/// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task.
///
/// The following restrictions apply:
///
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
/// * The function must be declared `async`.
/// * The function must not use generics.
/// * Only a single `main` task may be declared.
///
/// ## Examples
/// Spawning a task:
///
/// ``` rust
/// #[embassy_executor::main]
/// async fn main(_s: embassy_executor::Spawner) {
/// // Function body
/// }
/// ```
#[proc_macro_attribute]
pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(args, f, main::riscv()).unwrap_or_else(|x| x).into()
}
/// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task.
///
/// The following restrictions apply:
///
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
/// * The function must be declared `async`.
/// * The function must not use generics.
/// * Only a single `main` task may be declared.
///
/// ## Examples
/// Spawning a task:
///
/// ``` rust
/// #[embassy_executor::main]
/// async fn main(_s: embassy_executor::Spawner) {
/// // Function body
/// }
/// ```
#[proc_macro_attribute]
pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(args, f, main::std()).unwrap_or_else(|x| x).into()
}
/// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task.
///
/// The following restrictions apply:
///
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
/// * The function must be declared `async`.
/// * The function must not use generics.
/// * Only a single `main` task may be declared.
///
/// ## Examples
/// Spawning a task:
///
/// ``` rust
/// #[embassy_executor::main]
/// async fn main(_s: embassy_executor::Spawner) {
/// // Function body
/// }
/// ```
#[proc_macro_attribute]
pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(args, f, main::wasm()).unwrap_or_else(|x| x).into()
}
#[proc_macro_attribute]

View file

@ -6,7 +6,10 @@ pub fn run(name: syn::Ident) -> Result<TokenStream, TokenStream> {
let name_interrupt = format_ident!("{}", name);
let name_handler = format!("__EMBASSY_{}_HANDLER", name);
let doc = format!("{} interrupt singleton.", name);
let result = quote! {
#[doc = #doc]
#[allow(non_camel_case_types)]
pub struct #name_interrupt(());
unsafe impl ::embassy_cortex_m::interrupt::Interrupt for #name_interrupt {

View file

@ -7,7 +7,62 @@ use crate::util::ctxt::Ctxt;
#[derive(Debug, FromMeta)]
struct Args {}
pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result<TokenStream, TokenStream> {
pub fn riscv() -> TokenStream {
quote! {
#[riscv_rt::entry]
fn main() -> ! {
let mut executor = ::embassy_executor::Executor::new();
let executor = unsafe { __make_static(&mut executor) };
executor.run(|spawner| {
spawner.must_spawn(__embassy_main(spawner));
})
}
}
}
pub fn cortex_m() -> TokenStream {
quote! {
#[cortex_m_rt::entry]
fn main() -> ! {
let mut executor = ::embassy_executor::Executor::new();
let executor = unsafe { __make_static(&mut executor) };
executor.run(|spawner| {
spawner.must_spawn(__embassy_main(spawner));
})
}
}
}
pub fn wasm() -> TokenStream {
quote! {
#[wasm_bindgen::prelude::wasm_bindgen(start)]
pub fn main() -> Result<(), wasm_bindgen::JsValue> {
static EXECUTOR: ::embassy_executor::_export::StaticCell<::embassy_executor::Executor> = ::embassy_executor::_export::StaticCell::new();
let executor = EXECUTOR.init(::embassy_executor::Executor::new());
executor.start(|spawner| {
spawner.spawn(__embassy_main(spawner)).unwrap();
});
Ok(())
}
}
}
pub fn std() -> TokenStream {
quote! {
fn main() -> ! {
let mut executor = ::embassy_executor::Executor::new();
let executor = unsafe { __make_static(&mut executor) };
executor.run(|spawner| {
spawner.must_spawn(__embassy_main(spawner));
})
}
}
}
pub fn run(args: syn::AttributeArgs, f: syn::ItemFn, main: TokenStream) -> Result<TokenStream, TokenStream> {
#[allow(unused_variables)]
let args = Args::from_list(&args).map_err(|e| e.write_errors())?;
@ -30,46 +85,6 @@ pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result<TokenStream, Toke
let f_body = f.block;
#[cfg(feature = "wasm")]
let main = quote! {
#[wasm_bindgen::prelude::wasm_bindgen(start)]
pub fn main() -> Result<(), wasm_bindgen::JsValue> {
static EXECUTOR: ::embassy_executor::_export::StaticCell<::embassy_executor::Executor> = ::embassy_executor::_export::StaticCell::new();
let executor = EXECUTOR.init(::embassy_executor::Executor::new());
executor.start(|spawner| {
spawner.spawn(__embassy_main(spawner)).unwrap();
});
Ok(())
}
};
#[cfg(all(feature = "std", not(feature = "wasm")))]
let main = quote! {
fn main() -> ! {
let mut executor = ::embassy_executor::Executor::new();
let executor = unsafe { __make_static(&mut executor) };
executor.run(|spawner| {
spawner.must_spawn(__embassy_main(spawner));
})
}
};
#[cfg(all(not(feature = "std"), not(feature = "wasm")))]
let main = quote! {
#[cortex_m_rt::entry]
fn main() -> ! {
let mut executor = ::embassy_executor::Executor::new();
let executor = unsafe { __make_static(&mut executor) };
executor.run(|spawner| {
spawner.must_spawn(__embassy_main(spawner));
})
}
};
let result = quote! {
#[::embassy_executor::task()]
async fn __embassy_main(#fargs) {

View file

@ -1,6 +1,7 @@
use darling::FromMeta;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_quote, ItemFn};
use crate::util::ctxt::Ctxt;
@ -57,13 +58,7 @@ pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result<TokenStream, Toke
task_inner.vis = syn::Visibility::Inherited;
task_inner.sig.ident = task_inner_ident.clone();
let result = quote! {
// This is the user's task function, renamed.
// We put it outside the #task_ident fn below, because otherwise
// the items defined there (such as POOL) would be in scope
// in the user's code.
#task_inner
let mut task_outer: ItemFn = parse_quote! {
#visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> {
type Fut = impl ::core::future::Future + 'static;
static POOL: ::embassy_executor::raw::TaskPool<Fut, #pool_size> = ::embassy_executor::raw::TaskPool::new();
@ -71,5 +66,18 @@ pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result<TokenStream, Toke
}
};
task_outer.attrs.append(&mut task_inner.attrs.clone());
let result = quote! {
// This is the user's task function, renamed.
// We put it outside the #task_ident fn below, because otherwise
// the items defined there (such as POOL) would be in scope
// in the user's code.
#[doc(hidden)]
#task_inner
#task_outer
};
Ok(result)
}

View file

@ -0,0 +1,18 @@
[package]
name = "embassy-net-driver-channel"
version = "0.1.0"
edition = "2021"
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-channel-v$VERSION/embassy-net-driver-channel/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-driver-channel/src/"
features = ["defmt"]
target = "thumbv7em-none-eabi"
[dependencies]
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
embassy-sync = { version = "0.1.0", path = "../embassy-sync" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" }

View file

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

View file

@ -0,0 +1,549 @@
#![no_std]
// must go first!
mod fmt;
use core::cell::RefCell;
use core::mem::MaybeUninit;
use core::task::{Context, Poll};
pub use embassy_net_driver as driver;
use embassy_net_driver::{Capabilities, LinkState, Medium};
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::blocking_mutex::Mutex;
use embassy_sync::waitqueue::WakerRegistration;
pub struct State<const MTU: usize, const N_RX: usize, const N_TX: usize> {
rx: [PacketBuf<MTU>; N_RX],
tx: [PacketBuf<MTU>; N_TX],
inner: MaybeUninit<StateInner<'static, MTU>>,
}
impl<const MTU: usize, const N_RX: usize, const N_TX: usize> State<MTU, N_RX, N_TX> {
const NEW_PACKET: PacketBuf<MTU> = PacketBuf::new();
pub const fn new() -> Self {
Self {
rx: [Self::NEW_PACKET; N_RX],
tx: [Self::NEW_PACKET; N_TX],
inner: MaybeUninit::uninit(),
}
}
}
struct StateInner<'d, const MTU: usize> {
rx: zerocopy_channel::Channel<'d, NoopRawMutex, PacketBuf<MTU>>,
tx: zerocopy_channel::Channel<'d, NoopRawMutex, PacketBuf<MTU>>,
shared: Mutex<NoopRawMutex, RefCell<Shared>>,
}
/// State of the LinkState
struct Shared {
link_state: LinkState,
waker: WakerRegistration,
ethernet_address: [u8; 6],
}
pub struct Runner<'d, const MTU: usize> {
tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf<MTU>>,
rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf<MTU>>,
shared: &'d Mutex<NoopRawMutex, RefCell<Shared>>,
}
#[derive(Clone, Copy)]
pub struct StateRunner<'d> {
shared: &'d Mutex<NoopRawMutex, RefCell<Shared>>,
}
pub struct RxRunner<'d, const MTU: usize> {
rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf<MTU>>,
}
pub struct TxRunner<'d, const MTU: usize> {
tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf<MTU>>,
}
impl<'d, const MTU: usize> Runner<'d, MTU> {
pub fn split(self) -> (StateRunner<'d>, RxRunner<'d, MTU>, TxRunner<'d, MTU>) {
(
StateRunner { shared: self.shared },
RxRunner { rx_chan: self.rx_chan },
TxRunner { tx_chan: self.tx_chan },
)
}
pub fn state_runner(&self) -> StateRunner<'d> {
StateRunner { shared: self.shared }
}
pub fn set_link_state(&mut self, state: LinkState) {
self.shared.lock(|s| {
let s = &mut *s.borrow_mut();
s.link_state = state;
s.waker.wake();
});
}
pub fn set_ethernet_address(&mut self, address: [u8; 6]) {
self.shared.lock(|s| {
let s = &mut *s.borrow_mut();
s.ethernet_address = address;
s.waker.wake();
});
}
pub async fn rx_buf(&mut self) -> &mut [u8] {
let p = self.rx_chan.send().await;
&mut p.buf
}
pub fn try_rx_buf(&mut self) -> Option<&mut [u8]> {
let p = self.rx_chan.try_send()?;
Some(&mut p.buf)
}
pub fn poll_rx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> {
match self.rx_chan.poll_send(cx) {
Poll::Ready(p) => Poll::Ready(&mut p.buf),
Poll::Pending => Poll::Pending,
}
}
pub fn rx_done(&mut self, len: usize) {
let p = self.rx_chan.try_send().unwrap();
p.len = len;
self.rx_chan.send_done();
}
pub async fn tx_buf(&mut self) -> &mut [u8] {
let p = self.tx_chan.recv().await;
&mut p.buf[..p.len]
}
pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> {
let p = self.tx_chan.try_recv()?;
Some(&mut p.buf[..p.len])
}
pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> {
match self.tx_chan.poll_recv(cx) {
Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]),
Poll::Pending => Poll::Pending,
}
}
pub fn tx_done(&mut self) {
self.tx_chan.recv_done();
}
}
impl<'d> StateRunner<'d> {
pub fn set_link_state(&self, state: LinkState) {
self.shared.lock(|s| {
let s = &mut *s.borrow_mut();
s.link_state = state;
s.waker.wake();
});
}
pub fn set_ethernet_address(&self, address: [u8; 6]) {
self.shared.lock(|s| {
let s = &mut *s.borrow_mut();
s.ethernet_address = address;
s.waker.wake();
});
}
}
impl<'d, const MTU: usize> RxRunner<'d, MTU> {
pub async fn rx_buf(&mut self) -> &mut [u8] {
let p = self.rx_chan.send().await;
&mut p.buf
}
pub fn try_rx_buf(&mut self) -> Option<&mut [u8]> {
let p = self.rx_chan.try_send()?;
Some(&mut p.buf)
}
pub fn poll_rx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> {
match self.rx_chan.poll_send(cx) {
Poll::Ready(p) => Poll::Ready(&mut p.buf),
Poll::Pending => Poll::Pending,
}
}
pub fn rx_done(&mut self, len: usize) {
let p = self.rx_chan.try_send().unwrap();
p.len = len;
self.rx_chan.send_done();
}
}
impl<'d, const MTU: usize> TxRunner<'d, MTU> {
pub async fn tx_buf(&mut self) -> &mut [u8] {
let p = self.tx_chan.recv().await;
&mut p.buf[..p.len]
}
pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> {
let p = self.tx_chan.try_recv()?;
Some(&mut p.buf[..p.len])
}
pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> {
match self.tx_chan.poll_recv(cx) {
Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]),
Poll::Pending => Poll::Pending,
}
}
pub fn tx_done(&mut self) {
self.tx_chan.recv_done();
}
}
pub fn new<'d, const MTU: usize, const N_RX: usize, const N_TX: usize>(
state: &'d mut State<MTU, N_RX, N_TX>,
ethernet_address: [u8; 6],
) -> (Runner<'d, MTU>, Device<'d, MTU>) {
let mut caps = Capabilities::default();
caps.max_transmission_unit = MTU;
caps.medium = Medium::Ethernet;
// safety: this is a self-referential struct, however:
// - it can't move while the `'d` borrow is active.
// - when the borrow ends, the dangling references inside the MaybeUninit will never be used again.
let state_uninit: *mut MaybeUninit<StateInner<'d, MTU>> =
(&mut state.inner as *mut MaybeUninit<StateInner<'static, MTU>>).cast();
let state = unsafe { &mut *state_uninit }.write(StateInner {
rx: zerocopy_channel::Channel::new(&mut state.rx[..]),
tx: zerocopy_channel::Channel::new(&mut state.tx[..]),
shared: Mutex::new(RefCell::new(Shared {
link_state: LinkState::Down,
ethernet_address,
waker: WakerRegistration::new(),
})),
});
let (rx_sender, rx_receiver) = state.rx.split();
let (tx_sender, tx_receiver) = state.tx.split();
(
Runner {
tx_chan: tx_receiver,
rx_chan: rx_sender,
shared: &state.shared,
},
Device {
caps,
shared: &state.shared,
rx: rx_receiver,
tx: tx_sender,
},
)
}
pub struct PacketBuf<const MTU: usize> {
len: usize,
buf: [u8; MTU],
}
impl<const MTU: usize> PacketBuf<MTU> {
pub const fn new() -> Self {
Self { len: 0, buf: [0; MTU] }
}
}
pub struct Device<'d, const MTU: usize> {
rx: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf<MTU>>,
tx: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf<MTU>>,
shared: &'d Mutex<NoopRawMutex, RefCell<Shared>>,
caps: Capabilities,
}
impl<'d, const MTU: usize> embassy_net_driver::Driver for Device<'d, MTU> {
type RxToken<'a> = RxToken<'a, MTU> where Self: 'a ;
type TxToken<'a> = TxToken<'a, MTU> where Self: 'a ;
fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
if self.rx.poll_recv(cx).is_ready() && self.tx.poll_send(cx).is_ready() {
Some((RxToken { rx: self.rx.borrow() }, TxToken { tx: self.tx.borrow() }))
} else {
None
}
}
/// Construct a transmit token.
fn transmit(&mut self, cx: &mut Context) -> Option<Self::TxToken<'_>> {
if self.tx.poll_send(cx).is_ready() {
Some(TxToken { tx: self.tx.borrow() })
} else {
None
}
}
/// Get a description of device capabilities.
fn capabilities(&self) -> Capabilities {
self.caps.clone()
}
fn ethernet_address(&self) -> [u8; 6] {
self.shared.lock(|s| s.borrow().ethernet_address)
}
fn link_state(&mut self, cx: &mut Context) -> LinkState {
self.shared.lock(|s| {
let s = &mut *s.borrow_mut();
s.waker.register(cx.waker());
s.link_state
})
}
}
pub struct RxToken<'a, const MTU: usize> {
rx: zerocopy_channel::Receiver<'a, NoopRawMutex, PacketBuf<MTU>>,
}
impl<'a, const MTU: usize> embassy_net_driver::RxToken for RxToken<'a, MTU> {
fn consume<R, F>(mut self, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
// NOTE(unwrap): we checked the queue wasn't full when creating the token.
let pkt = unwrap!(self.rx.try_recv());
let r = f(&mut pkt.buf[..pkt.len]);
self.rx.recv_done();
r
}
}
pub struct TxToken<'a, const MTU: usize> {
tx: zerocopy_channel::Sender<'a, NoopRawMutex, PacketBuf<MTU>>,
}
impl<'a, const MTU: usize> embassy_net_driver::TxToken for TxToken<'a, MTU> {
fn consume<R, F>(mut self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
// NOTE(unwrap): we checked the queue wasn't full when creating the token.
let pkt = unwrap!(self.tx.try_send());
let r = f(&mut pkt.buf[..len]);
pkt.len = len;
self.tx.send_done();
r
}
}
mod zerocopy_channel {
use core::cell::RefCell;
use core::future::poll_fn;
use core::marker::PhantomData;
use core::task::{Context, Poll};
use embassy_sync::blocking_mutex::raw::RawMutex;
use embassy_sync::blocking_mutex::Mutex;
use embassy_sync::waitqueue::WakerRegistration;
pub struct Channel<'a, M: RawMutex, T> {
buf: *mut T,
phantom: PhantomData<&'a mut T>,
state: Mutex<M, RefCell<State>>,
}
impl<'a, M: RawMutex, T> Channel<'a, M, T> {
pub fn new(buf: &'a mut [T]) -> Self {
let len = buf.len();
assert!(len != 0);
Self {
buf: buf.as_mut_ptr(),
phantom: PhantomData,
state: Mutex::new(RefCell::new(State {
len,
front: 0,
back: 0,
full: false,
send_waker: WakerRegistration::new(),
recv_waker: WakerRegistration::new(),
})),
}
}
pub fn split(&mut self) -> (Sender<'_, M, T>, Receiver<'_, M, T>) {
(Sender { channel: self }, Receiver { channel: self })
}
}
pub struct Sender<'a, M: RawMutex, T> {
channel: &'a Channel<'a, M, T>,
}
impl<'a, M: RawMutex, T> Sender<'a, M, T> {
pub fn borrow(&mut self) -> Sender<'_, M, T> {
Sender { channel: self.channel }
}
pub fn try_send(&mut self) -> Option<&mut T> {
self.channel.state.lock(|s| {
let s = &mut *s.borrow_mut();
match s.push_index() {
Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }),
None => None,
}
})
}
pub fn poll_send(&mut self, cx: &mut Context) -> Poll<&mut T> {
self.channel.state.lock(|s| {
let s = &mut *s.borrow_mut();
match s.push_index() {
Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }),
None => {
s.recv_waker.register(cx.waker());
Poll::Pending
}
}
})
}
pub async fn send(&mut self) -> &mut T {
let i = poll_fn(|cx| {
self.channel.state.lock(|s| {
let s = &mut *s.borrow_mut();
match s.push_index() {
Some(i) => Poll::Ready(i),
None => {
s.recv_waker.register(cx.waker());
Poll::Pending
}
}
})
})
.await;
unsafe { &mut *self.channel.buf.add(i) }
}
pub fn send_done(&mut self) {
self.channel.state.lock(|s| s.borrow_mut().push_done())
}
}
pub struct Receiver<'a, M: RawMutex, T> {
channel: &'a Channel<'a, M, T>,
}
impl<'a, M: RawMutex, T> Receiver<'a, M, T> {
pub fn borrow(&mut self) -> Receiver<'_, M, T> {
Receiver { channel: self.channel }
}
pub fn try_recv(&mut self) -> Option<&mut T> {
self.channel.state.lock(|s| {
let s = &mut *s.borrow_mut();
match s.pop_index() {
Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }),
None => None,
}
})
}
pub fn poll_recv(&mut self, cx: &mut Context) -> Poll<&mut T> {
self.channel.state.lock(|s| {
let s = &mut *s.borrow_mut();
match s.pop_index() {
Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }),
None => {
s.send_waker.register(cx.waker());
Poll::Pending
}
}
})
}
pub async fn recv(&mut self) -> &mut T {
let i = poll_fn(|cx| {
self.channel.state.lock(|s| {
let s = &mut *s.borrow_mut();
match s.pop_index() {
Some(i) => Poll::Ready(i),
None => {
s.send_waker.register(cx.waker());
Poll::Pending
}
}
})
})
.await;
unsafe { &mut *self.channel.buf.add(i) }
}
pub fn recv_done(&mut self) {
self.channel.state.lock(|s| s.borrow_mut().pop_done())
}
}
struct State {
len: usize,
/// Front index. Always 0..=(N-1)
front: usize,
/// Back index. Always 0..=(N-1).
back: usize,
/// Used to distinguish "empty" and "full" cases when `front == back`.
/// May only be `true` if `front == back`, always `false` otherwise.
full: bool,
send_waker: WakerRegistration,
recv_waker: WakerRegistration,
}
impl State {
fn increment(&self, i: usize) -> usize {
if i + 1 == self.len {
0
} else {
i + 1
}
}
fn is_full(&self) -> bool {
self.full
}
fn is_empty(&self) -> bool {
self.front == self.back && !self.full
}
fn push_index(&mut self) -> Option<usize> {
match self.is_full() {
true => None,
false => Some(self.back),
}
}
fn push_done(&mut self) {
assert!(!self.is_full());
self.back = self.increment(self.back);
if self.back == self.front {
self.full = true;
}
self.send_waker.wake();
}
fn pop_index(&mut self) -> Option<usize> {
match self.is_empty() {
true => None,
false => Some(self.front),
}
}
fn pop_done(&mut self) {
assert!(!self.is_empty());
self.front = self.increment(self.front);
self.full = false;
self.recv_waker.wake();
}
}
}

View file

@ -0,0 +1,15 @@
[package]
name = "embassy-net-driver"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-v$VERSION/embassy-net-driver/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-driver/src/"
features = ["defmt"]
target = "thumbv7em-none-eabi"
[dependencies]
defmt = { version = "0.3", optional = true }

View file

@ -0,0 +1,175 @@
#![no_std]
use core::task::Context;
pub trait Driver {
type RxToken<'a>: RxToken
where
Self: 'a;
type TxToken<'a>: TxToken
where
Self: 'a;
fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)>;
fn transmit(&mut self, cx: &mut Context) -> Option<Self::TxToken<'_>>;
fn link_state(&mut self, cx: &mut Context) -> LinkState;
fn capabilities(&self) -> Capabilities;
fn ethernet_address(&self) -> [u8; 6];
}
impl<T: ?Sized + Driver> Driver for &mut T {
type RxToken<'a> = T::RxToken<'a>
where
Self: 'a;
type TxToken<'a> = T::TxToken<'a>
where
Self: 'a;
fn transmit(&mut self, cx: &mut Context) -> Option<Self::TxToken<'_>> {
T::transmit(self, cx)
}
fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
T::receive(self, cx)
}
fn capabilities(&self) -> Capabilities {
T::capabilities(self)
}
fn link_state(&mut self, cx: &mut Context) -> LinkState {
T::link_state(self, cx)
}
fn ethernet_address(&self) -> [u8; 6] {
T::ethernet_address(self)
}
}
/// A token to receive a single network packet.
pub trait RxToken {
/// Consumes the token to receive a single network packet.
///
/// This method receives a packet and then calls the given closure `f` with the raw
/// packet bytes as argument.
fn consume<R, F>(self, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R;
}
/// A token to transmit a single network packet.
pub trait TxToken {
/// Consumes the token to send a single network packet.
///
/// This method constructs a transmit buffer of size `len` and calls the passed
/// closure `f` with a mutable reference to that buffer. The closure should construct
/// a valid network packet (e.g. an ethernet packet) in the buffer. When the closure
/// returns, the transmit buffer is sent out.
fn consume<R, F>(self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R;
}
/// A description of device capabilities.
///
/// Higher-level protocols may achieve higher throughput or lower latency if they consider
/// the bandwidth or packet size limitations.
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct Capabilities {
/// Medium of the device.
///
/// This indicates what kind of packet the sent/received bytes are, and determines
/// some behaviors of Interface. For example, ARP/NDISC address resolution is only done
/// for Ethernet mediums.
pub medium: Medium,
/// Maximum transmission unit.
///
/// The network device is unable to send or receive frames larger than the value returned
/// by this function.
///
/// For Ethernet devices, this is the maximum Ethernet frame size, including the Ethernet header (14 octets), but
/// *not* including the Ethernet FCS (4 octets). Therefore, Ethernet MTU = IP MTU + 14.
///
/// Note that in Linux and other OSes, "MTU" is the IP MTU, not the Ethernet MTU, even for Ethernet
/// devices. This is a common source of confusion.
///
/// Most common IP MTU is 1500. Minimum is 576 (for IPv4) or 1280 (for IPv6). Maximum is 9216 octets.
pub max_transmission_unit: usize,
/// Maximum burst size, in terms of MTU.
///
/// The network device is unable to send or receive bursts large than the value returned
/// by this function.
///
/// If `None`, there is no fixed limit on burst size, e.g. if network buffers are
/// dynamically allocated.
pub max_burst_size: Option<usize>,
/// Checksum behavior.
///
/// If the network device is capable of verifying or computing checksums for some protocols,
/// it can request that the stack not do so in software to improve performance.
pub checksum: ChecksumCapabilities,
}
/// Type of medium of a device.
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Medium {
/// Ethernet medium. Devices of this type send and receive Ethernet frames,
/// and interfaces using it must do neighbor discovery via ARP or NDISC.
///
/// Examples of devices of this type are Ethernet, WiFi (802.11), Linux `tap`, and VPNs in tap (layer 2) mode.
Ethernet,
/// IP medium. Devices of this type send and receive IP frames, without an
/// Ethernet header. MAC addresses are not used, and no neighbor discovery (ARP, NDISC) is done.
///
/// Examples of devices of this type are the Linux `tun`, PPP interfaces, VPNs in tun (layer 3) mode.
Ip,
}
impl Default for Medium {
fn default() -> Medium {
Medium::Ethernet
}
}
/// A description of checksum behavior for every supported protocol.
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct ChecksumCapabilities {
pub ipv4: Checksum,
pub udp: Checksum,
pub tcp: Checksum,
pub icmpv4: Checksum,
pub icmpv6: Checksum,
}
/// A description of checksum behavior for a particular protocol.
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Checksum {
/// Verify checksum when receiving and compute checksum when sending.
Both,
/// Verify checksum when receiving.
Rx,
/// Compute checksum before sending.
Tx,
/// Ignore checksum completely.
None,
}
impl Default for Checksum {
fn default() -> Checksum {
Checksum::Both
}
}
#[derive(PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum LinkState {
Down,
Up,
}

View file

@ -8,41 +8,42 @@ license = "MIT OR Apache-2.0"
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-v$VERSION/embassy-net/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net/src/"
features = ["pool-4", "defmt", "tcp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip"]
features = ["nightly", "unstable-traits", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip"]
target = "thumbv7em-none-eabi"
[features]
default = []
std = []
defmt = ["dep:defmt", "smoltcp/defmt"]
defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt"]
nightly = ["dep:embedded-io", "embedded-io?/async", "dep:embedded-nal-async"]
unstable-traits = []
udp = ["smoltcp/socket-udp"]
tcp = ["smoltcp/socket-tcp"]
dns = ["smoltcp/socket-dns"]
dns = ["smoltcp/socket-dns", "smoltcp/proto-dns"]
dhcpv4 = ["medium-ethernet", "smoltcp/socket-dhcpv4"]
proto-ipv6 = ["smoltcp/proto-ipv6"]
medium-ethernet = ["smoltcp/medium-ethernet"]
medium-ip = ["smoltcp/medium-ip"]
pool-4 = []
pool-8 = []
pool-16 = []
pool-32 = []
pool-64 = []
pool-128 = []
[dependencies]
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
smoltcp = { version = "0.9.0", default-features = false, features = [
"proto-ipv4",
"socket",
"async",
]}
embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" }
embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common" }
embassy-time = { version = "0.1.0", path = "../embassy-time" }
embassy-sync = { version = "0.1.0", path = "../embassy-sync" }
embedded-io = { version = "0.3.0", optional = true }
embedded-io = { version = "0.4.0", optional = true }
managed = { version = "0.8.0", default-features = false, features = [ "map" ] }
heapless = { version = "0.7.5", default-features = false }
@ -51,16 +52,5 @@ generic-array = { version = "0.14.4", default-features = false }
stable_deref_trait = { version = "1.2.0", default-features = false }
futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] }
atomic-pool = "1.0"
atomic-polyfill = "1.0.1"
embedded-nal-async = { version = "0.2.0", optional = true }
[dependencies.smoltcp]
version = "0.8.0"
git = "https://github.com/smoltcp-rs/smoltcp"
rev = "ed0cf16750a42f30e31fcaf5347915592924b1e3"
default-features = false
features = [
"proto-ipv4",
"socket",
"async",
]
embedded-nal-async = { version = "0.4.0", optional = true }
atomic-polyfill = { version = "1.0" }

View file

@ -17,11 +17,13 @@ sudo ip -6 route add fe80::/64 dev tap0
sudo ip -6 route add fdaa::/64 dev tap0
```
Second, have something listening there. For example `nc -l 8000`
Then run the example located in the `examples` folder:
```sh
cd $EMBASSY_ROOT/examples/std/
cargo run --bin net
cargo run --bin net -- --static-ip
```
## License

View file

@ -1,129 +1,102 @@
use core::task::Waker;
use core::task::Context;
use smoltcp::phy::{Device as SmolDevice, DeviceCapabilities};
use smoltcp::time::Instant as SmolInstant;
use embassy_net_driver::{Capabilities, Checksum, Driver, Medium, RxToken, TxToken};
use smoltcp::phy;
use smoltcp::time::Instant;
use crate::packet_pool::PacketBoxExt;
use crate::{Packet, PacketBox, PacketBuf};
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum LinkState {
Down,
Up,
pub(crate) struct DriverAdapter<'d, 'c, T>
where
T: Driver,
{
// must be Some when actually using this to rx/tx
pub cx: Option<&'d mut Context<'c>>,
pub inner: &'d mut T,
}
// 'static required due to the "fake GAT" in smoltcp::phy::Device.
// https://github.com/smoltcp-rs/smoltcp/pull/572
pub trait Device {
fn is_transmit_ready(&mut self) -> bool;
fn transmit(&mut self, pkt: PacketBuf);
fn receive(&mut self) -> Option<PacketBuf>;
impl<'d, 'c, T> phy::Device for DriverAdapter<'d, 'c, T>
where
T: Driver,
{
type RxToken<'a> = RxTokenAdapter<T::RxToken<'a>> where Self: 'a;
type TxToken<'a> = TxTokenAdapter<T::TxToken<'a>> where Self: 'a;
fn register_waker(&mut self, waker: &Waker);
fn capabilities(&self) -> DeviceCapabilities;
fn link_state(&mut self) -> LinkState;
fn ethernet_address(&self) -> [u8; 6];
}
impl<T: ?Sized + Device> Device for &'static mut T {
fn is_transmit_ready(&mut self) -> bool {
T::is_transmit_ready(self)
}
fn transmit(&mut self, pkt: PacketBuf) {
T::transmit(self, pkt)
}
fn receive(&mut self) -> Option<PacketBuf> {
T::receive(self)
}
fn register_waker(&mut self, waker: &Waker) {
T::register_waker(self, waker)
}
fn capabilities(&self) -> DeviceCapabilities {
T::capabilities(self)
}
fn link_state(&mut self) -> LinkState {
T::link_state(self)
}
fn ethernet_address(&self) -> [u8; 6] {
T::ethernet_address(self)
}
}
pub struct DeviceAdapter<D: Device> {
pub device: D,
caps: DeviceCapabilities,
}
impl<D: Device> DeviceAdapter<D> {
pub(crate) fn new(device: D) -> Self {
Self {
caps: device.capabilities(),
device,
}
}
}
impl<'a, D: Device + 'static> SmolDevice<'a> for DeviceAdapter<D> {
type RxToken = RxToken;
type TxToken = TxToken<'a, D>;
fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> {
let tx_pkt = PacketBox::new(Packet::new())?;
let rx_pkt = self.device.receive()?;
let rx_token = RxToken { pkt: rx_pkt };
let tx_token = TxToken {
device: &mut self.device,
pkt: tx_pkt,
};
Some((rx_token, tx_token))
fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
self.inner
.receive(self.cx.as_deref_mut().unwrap())
.map(|(rx, tx)| (RxTokenAdapter(rx), TxTokenAdapter(tx)))
}
/// Construct a transmit token.
fn transmit(&'a mut self) -> Option<Self::TxToken> {
if !self.device.is_transmit_ready() {
return None;
}
let tx_pkt = PacketBox::new(Packet::new())?;
Some(TxToken {
device: &mut self.device,
pkt: tx_pkt,
})
fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
self.inner.transmit(self.cx.as_deref_mut().unwrap()).map(TxTokenAdapter)
}
/// Get a description of device capabilities.
fn capabilities(&self) -> DeviceCapabilities {
self.caps.clone()
fn capabilities(&self) -> phy::DeviceCapabilities {
fn convert(c: Checksum) -> phy::Checksum {
match c {
Checksum::Both => phy::Checksum::Both,
Checksum::Tx => phy::Checksum::Tx,
Checksum::Rx => phy::Checksum::Rx,
Checksum::None => phy::Checksum::None,
}
}
let caps: Capabilities = self.inner.capabilities();
let mut smolcaps = phy::DeviceCapabilities::default();
smolcaps.max_transmission_unit = caps.max_transmission_unit;
smolcaps.max_burst_size = caps.max_burst_size;
smolcaps.medium = match caps.medium {
#[cfg(feature = "medium-ethernet")]
Medium::Ethernet => phy::Medium::Ethernet,
#[cfg(feature = "medium-ip")]
Medium::Ip => phy::Medium::Ip,
_ => panic!(
"Unsupported medium {:?}. MAke sure to enable it in embassy-net's Cargo features.",
caps.medium
),
};
smolcaps.checksum.ipv4 = convert(caps.checksum.ipv4);
smolcaps.checksum.tcp = convert(caps.checksum.tcp);
smolcaps.checksum.udp = convert(caps.checksum.udp);
smolcaps.checksum.icmpv4 = convert(caps.checksum.icmpv4);
#[cfg(feature = "proto-ipv6")]
{
smolcaps.checksum.icmpv6 = convert(caps.checksum.icmpv6);
}
smolcaps
}
}
pub struct RxToken {
pkt: PacketBuf,
}
pub(crate) struct RxTokenAdapter<T>(T)
where
T: RxToken;
impl smoltcp::phy::RxToken for RxToken {
fn consume<R, F>(mut self, _timestamp: SmolInstant, f: F) -> smoltcp::Result<R>
impl<T> phy::RxToken for RxTokenAdapter<T>
where
T: RxToken,
{
fn consume<R, F>(self, f: F) -> R
where
F: FnOnce(&mut [u8]) -> smoltcp::Result<R>,
F: FnOnce(&mut [u8]) -> R,
{
f(&mut self.pkt)
self.0.consume(|buf| f(buf))
}
}
pub struct TxToken<'a, D: Device> {
device: &'a mut D,
pkt: PacketBox,
}
pub(crate) struct TxTokenAdapter<T>(T)
where
T: TxToken;
impl<'a, D: Device> smoltcp::phy::TxToken for TxToken<'a, D> {
fn consume<R, F>(self, _timestamp: SmolInstant, len: usize, f: F) -> smoltcp::Result<R>
impl<T> phy::TxToken for TxTokenAdapter<T>
where
T: TxToken,
{
fn consume<R, F>(self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> smoltcp::Result<R>,
F: FnOnce(&mut [u8]) -> R,
{
let mut buf = self.pkt.slice(0..len);
let r = f(&mut buf)?;
self.device.transmit(buf);
Ok(r)
self.0.consume(len, |buf| f(buf))
}
}

97
embassy-net/src/dns.rs Normal file
View file

@ -0,0 +1,97 @@
//! DNS socket with async support.
use heapless::Vec;
pub use smoltcp::socket::dns::{DnsQuery, Socket};
pub(crate) use smoltcp::socket::dns::{GetQueryResultError, StartQueryError};
pub use smoltcp::wire::{DnsQueryType, IpAddress};
use crate::{Driver, Stack};
/// Errors returned by DnsSocket.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
/// Invalid name
InvalidName,
/// Name too long
NameTooLong,
/// Name lookup failed
Failed,
}
impl From<GetQueryResultError> for Error {
fn from(_: GetQueryResultError) -> Self {
Self::Failed
}
}
impl From<StartQueryError> for Error {
fn from(e: StartQueryError) -> Self {
match e {
StartQueryError::NoFreeSlot => Self::Failed,
StartQueryError::InvalidName => Self::InvalidName,
StartQueryError::NameTooLong => Self::NameTooLong,
}
}
}
/// Async socket for making DNS queries.
pub struct DnsSocket<'a, D>
where
D: Driver + 'static,
{
stack: &'a Stack<D>,
}
impl<'a, D> DnsSocket<'a, D>
where
D: Driver + 'static,
{
/// Create a new DNS socket using the provided stack.
///
/// NOTE: If using DHCP, make sure it has reconfigured the stack to ensure the DNS servers are updated.
pub fn new(stack: &'a Stack<D>) -> Self {
Self { stack }
}
/// Make a query for a given name and return the corresponding IP addresses.
pub async fn query(&self, name: &str, qtype: DnsQueryType) -> Result<Vec<IpAddress, 1>, Error> {
self.stack.dns_query(name, qtype).await
}
}
#[cfg(all(feature = "unstable-traits", feature = "nightly"))]
impl<'a, D> embedded_nal_async::Dns for DnsSocket<'a, D>
where
D: Driver + 'static,
{
type Error = Error;
async fn get_host_by_name(
&self,
host: &str,
addr_type: embedded_nal_async::AddrType,
) -> Result<embedded_nal_async::IpAddr, Self::Error> {
use embedded_nal_async::{AddrType, IpAddr};
let qtype = match addr_type {
AddrType::IPv6 => DnsQueryType::Aaaa,
_ => DnsQueryType::A,
};
let addrs = self.query(host, qtype).await?;
if let Some(first) = addrs.get(0) {
Ok(match first {
IpAddress::Ipv4(addr) => IpAddr::V4(addr.0.into()),
#[cfg(feature = "proto-ipv6")]
IpAddress::Ipv6(addr) => IpAddr::V6(addr.0.into()),
})
} else {
Err(Error::Failed)
}
}
async fn get_host_by_address(
&self,
_addr: embedded_nal_async::IpAddr,
) -> Result<heapless::String<256>, Self::Error> {
todo!()
}
}

View file

@ -1,25 +1,40 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))]
#![cfg_attr(
feature = "nightly",
feature(type_alias_impl_trait, async_fn_in_trait, impl_trait_projections)
)]
#![cfg_attr(feature = "nightly", allow(incomplete_features))]
// This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt;
pub use embassy_net_driver as driver;
mod device;
mod packet_pool;
mod stack;
pub use device::{Device, LinkState};
pub use packet_pool::{Packet, PacketBox, PacketBoxExt, PacketBuf, MTU};
pub use stack::{Config, ConfigStrategy, Stack, StackResources};
#[cfg(feature = "dns")]
pub mod dns;
#[cfg(feature = "tcp")]
pub mod tcp;
#[cfg(feature = "udp")]
pub mod udp;
use core::cell::RefCell;
use core::future::{poll_fn, Future};
use core::task::{Context, Poll};
use embassy_net_driver::{Driver, LinkState, Medium};
use embassy_sync::waitqueue::WakerRegistration;
use embassy_time::{Instant, Timer};
use futures::pin_mut;
use heapless::Vec;
#[cfg(feature = "dhcpv4")]
use smoltcp::iface::SocketHandle;
use smoltcp::iface::{Interface, SocketSet, SocketStorage};
#[cfg(feature = "dhcpv4")]
use smoltcp::socket::dhcpv4;
use smoltcp::socket::dhcpv4::RetryConfig;
use smoltcp::time::Duration;
// smoltcp reexports
pub use smoltcp::phy::{DeviceCapabilities, Medium};
pub use smoltcp::time::{Duration as SmolDuration, Instant as SmolInstant};
#[cfg(feature = "medium-ethernet")]
pub use smoltcp::wire::{EthernetAddress, HardwareAddress};
@ -28,3 +43,388 @@ pub use smoltcp::wire::{IpAddress, IpCidr, Ipv4Address, Ipv4Cidr};
pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr};
#[cfg(feature = "udp")]
pub use smoltcp::{socket::udp::PacketMetadata, wire::IpListenEndpoint};
use crate::device::DriverAdapter;
const LOCAL_PORT_MIN: u16 = 1025;
const LOCAL_PORT_MAX: u16 = 65535;
#[cfg(feature = "dns")]
const MAX_QUERIES: usize = 4;
pub struct StackResources<const SOCK: usize> {
sockets: [SocketStorage<'static>; SOCK],
#[cfg(feature = "dns")]
queries: [Option<dns::DnsQuery>; MAX_QUERIES],
}
impl<const SOCK: usize> StackResources<SOCK> {
pub fn new() -> Self {
#[cfg(feature = "dns")]
const INIT: Option<dns::DnsQuery> = None;
Self {
sockets: [SocketStorage::EMPTY; SOCK],
#[cfg(feature = "dns")]
queries: [INIT; MAX_QUERIES],
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StaticConfig {
pub address: Ipv4Cidr,
pub gateway: Option<Ipv4Address>,
pub dns_servers: Vec<Ipv4Address, 3>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DhcpConfig {
pub max_lease_duration: Option<Duration>,
pub retry_config: RetryConfig,
/// Ignore NAKs.
pub ignore_naks: bool,
/// Server port config
pub server_port: u16,
/// Client port config
pub client_port: u16,
}
impl Default for DhcpConfig {
fn default() -> Self {
Self {
max_lease_duration: Default::default(),
retry_config: Default::default(),
ignore_naks: Default::default(),
server_port: smoltcp::wire::DHCP_SERVER_PORT,
client_port: smoltcp::wire::DHCP_CLIENT_PORT,
}
}
}
pub enum Config {
Static(StaticConfig),
#[cfg(feature = "dhcpv4")]
Dhcp(DhcpConfig),
}
pub struct Stack<D: Driver> {
pub(crate) socket: RefCell<SocketStack>,
inner: RefCell<Inner<D>>,
}
struct Inner<D: Driver> {
device: D,
link_up: bool,
config: Option<StaticConfig>,
#[cfg(feature = "dhcpv4")]
dhcp_socket: Option<SocketHandle>,
#[cfg(feature = "dns")]
dns_socket: SocketHandle,
#[cfg(feature = "dns")]
dns_waker: WakerRegistration,
}
pub(crate) struct SocketStack {
pub(crate) sockets: SocketSet<'static>,
pub(crate) iface: Interface,
pub(crate) waker: WakerRegistration,
next_local_port: u16,
}
impl<D: Driver + 'static> Stack<D> {
pub fn new<const SOCK: usize>(
mut device: D,
config: Config,
resources: &'static mut StackResources<SOCK>,
random_seed: u64,
) -> Self {
#[cfg(feature = "medium-ethernet")]
let medium = device.capabilities().medium;
let mut iface_cfg = smoltcp::iface::Config::new();
iface_cfg.random_seed = random_seed;
#[cfg(feature = "medium-ethernet")]
if medium == Medium::Ethernet {
iface_cfg.hardware_addr = Some(HardwareAddress::Ethernet(EthernetAddress(device.ethernet_address())));
}
let iface = Interface::new(
iface_cfg,
&mut DriverAdapter {
inner: &mut device,
cx: None,
},
);
let sockets = SocketSet::new(&mut resources.sockets[..]);
let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN;
let mut socket = SocketStack {
sockets,
iface,
waker: WakerRegistration::new(),
next_local_port,
};
let mut inner = Inner {
device,
link_up: false,
config: None,
#[cfg(feature = "dhcpv4")]
dhcp_socket: None,
#[cfg(feature = "dns")]
dns_socket: socket.sockets.add(dns::Socket::new(
&[],
managed::ManagedSlice::Borrowed(&mut resources.queries),
)),
#[cfg(feature = "dns")]
dns_waker: WakerRegistration::new(),
};
match config {
Config::Static(config) => {
inner.apply_config(&mut socket, config);
}
#[cfg(feature = "dhcpv4")]
Config::Dhcp(config) => {
let mut dhcp_socket = smoltcp::socket::dhcpv4::Socket::new();
inner.apply_dhcp_config(&mut dhcp_socket, config);
let handle = socket.sockets.add(dhcp_socket);
inner.dhcp_socket = Some(handle);
}
}
Self {
socket: RefCell::new(socket),
inner: RefCell::new(inner),
}
}
fn with<R>(&self, f: impl FnOnce(&SocketStack, &Inner<D>) -> R) -> R {
f(&*self.socket.borrow(), &*self.inner.borrow())
}
fn with_mut<R>(&self, f: impl FnOnce(&mut SocketStack, &mut Inner<D>) -> R) -> R {
f(&mut *self.socket.borrow_mut(), &mut *self.inner.borrow_mut())
}
pub fn ethernet_address(&self) -> [u8; 6] {
self.with(|_s, i| i.device.ethernet_address())
}
pub fn is_link_up(&self) -> bool {
self.with(|_s, i| i.link_up)
}
pub fn is_config_up(&self) -> bool {
self.with(|_s, i| i.config.is_some())
}
pub fn config(&self) -> Option<StaticConfig> {
self.with(|_s, i| i.config.clone())
}
pub async fn run(&self) -> ! {
poll_fn(|cx| {
self.with_mut(|s, i| i.poll(cx, s));
Poll::<()>::Pending
})
.await;
unreachable!()
}
/// Make a query for a given name and return the corresponding IP addresses.
#[cfg(feature = "dns")]
pub async fn dns_query(&self, name: &str, qtype: dns::DnsQueryType) -> Result<Vec<IpAddress, 1>, dns::Error> {
let query = poll_fn(|cx| {
self.with_mut(|s, i| {
let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);
match socket.start_query(s.iface.context(), name, qtype) {
Ok(handle) => Poll::Ready(Ok(handle)),
Err(dns::StartQueryError::NoFreeSlot) => {
i.dns_waker.register(cx.waker());
Poll::Pending
}
Err(e) => Poll::Ready(Err(e)),
}
})
})
.await?;
use embassy_hal_common::drop::OnDrop;
let drop = OnDrop::new(|| {
self.with_mut(|s, i| {
let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);
socket.cancel_query(query);
s.waker.wake();
i.dns_waker.wake();
})
});
let res = poll_fn(|cx| {
self.with_mut(|s, i| {
let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);
match socket.get_query_result(query) {
Ok(addrs) => {
i.dns_waker.wake();
Poll::Ready(Ok(addrs))
}
Err(dns::GetQueryResultError::Pending) => {
socket.register_query_waker(query, cx.waker());
Poll::Pending
}
Err(e) => {
i.dns_waker.wake();
Poll::Ready(Err(e.into()))
}
}
})
})
.await;
drop.defuse();
res
}
}
impl SocketStack {
#[allow(clippy::absurd_extreme_comparisons, dead_code)]
pub fn get_local_port(&mut self) -> u16 {
let res = self.next_local_port;
self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 };
res
}
}
impl<D: Driver + 'static> Inner<D> {
fn apply_config(&mut self, s: &mut SocketStack, config: StaticConfig) {
#[cfg(feature = "medium-ethernet")]
let medium = self.device.capabilities().medium;
debug!("Acquired IP configuration:");
debug!(" IP address: {}", config.address);
s.iface.update_ip_addrs(|addrs| {
if addrs.is_empty() {
addrs.push(IpCidr::Ipv4(config.address)).unwrap();
} else {
addrs[0] = IpCidr::Ipv4(config.address);
}
});
#[cfg(feature = "medium-ethernet")]
if medium == Medium::Ethernet {
if let Some(gateway) = config.gateway {
debug!(" Default gateway: {}", gateway);
s.iface.routes_mut().add_default_ipv4_route(gateway).unwrap();
} else {
debug!(" Default gateway: None");
s.iface.routes_mut().remove_default_ipv4_route();
}
}
for (i, s) in config.dns_servers.iter().enumerate() {
debug!(" DNS server {}: {}", i, s);
}
#[cfg(feature = "dns")]
{
let socket = s.sockets.get_mut::<smoltcp::socket::dns::Socket>(self.dns_socket);
let servers: Vec<IpAddress, 3> = config.dns_servers.iter().map(|c| IpAddress::Ipv4(*c)).collect();
socket.update_servers(&servers[..]);
}
self.config = Some(config)
}
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);
socket.set_ports(config.server_port, config.client_port);
socket.set_retry_config(config.retry_config);
}
#[allow(unused)] // used only with dhcp
fn unapply_config(&mut self, s: &mut SocketStack) {
#[cfg(feature = "medium-ethernet")]
let medium = self.device.capabilities().medium;
debug!("Lost IP configuration");
s.iface.update_ip_addrs(|ip_addrs| ip_addrs.clear());
#[cfg(feature = "medium-ethernet")]
if medium == Medium::Ethernet {
s.iface.routes_mut().remove_default_ipv4_route();
}
self.config = None
}
fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) {
s.waker.register(cx.waker());
#[cfg(feature = "medium-ethernet")]
if self.device.capabilities().medium == Medium::Ethernet {
s.iface.set_hardware_addr(HardwareAddress::Ethernet(EthernetAddress(
self.device.ethernet_address(),
)));
}
let timestamp = instant_to_smoltcp(Instant::now());
let mut smoldev = DriverAdapter {
cx: Some(cx),
inner: &mut self.device,
};
s.iface.poll(timestamp, &mut smoldev, &mut s.sockets);
// Update link up
let old_link_up = self.link_up;
self.link_up = self.device.link_state(cx) == LinkState::Up;
// Print when changed
if old_link_up != self.link_up {
info!("link_up = {:?}", self.link_up);
}
#[cfg(feature = "dhcpv4")]
if let Some(dhcp_handle) = self.dhcp_socket {
let socket = s.sockets.get_mut::<dhcpv4::Socket>(dhcp_handle);
if self.link_up {
match socket.poll() {
None => {}
Some(dhcpv4::Event::Deconfigured) => self.unapply_config(s),
Some(dhcpv4::Event::Configured(config)) => {
let config = StaticConfig {
address: config.address,
gateway: config.router,
dns_servers: config.dns_servers,
};
self.apply_config(s, config)
}
}
} else if old_link_up {
socket.reset();
self.unapply_config(s);
}
}
//if old_link_up || self.link_up {
// self.poll_configurator(timestamp)
//}
//
if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) {
let t = Timer::at(instant_from_smoltcp(poll_at));
pin_mut!(t);
if t.poll(cx).is_ready() {
cx.waker().wake_by_ref();
}
}
}
}
fn instant_to_smoltcp(instant: Instant) -> SmolInstant {
SmolInstant::from_millis(instant.as_millis() as i64)
}
fn instant_from_smoltcp(instant: SmolInstant) -> Instant {
Instant::from_millis(instant.total_millis() as u64)
}

View file

@ -1,107 +0,0 @@
use core::ops::{Deref, DerefMut, Range};
use as_slice::{AsMutSlice, AsSlice};
use atomic_pool::{pool, Box};
pub const MTU: usize = 1516;
#[cfg(feature = "pool-4")]
pub const PACKET_POOL_SIZE: usize = 4;
#[cfg(feature = "pool-8")]
pub const PACKET_POOL_SIZE: usize = 8;
#[cfg(feature = "pool-16")]
pub const PACKET_POOL_SIZE: usize = 16;
#[cfg(feature = "pool-32")]
pub const PACKET_POOL_SIZE: usize = 32;
#[cfg(feature = "pool-64")]
pub const PACKET_POOL_SIZE: usize = 64;
#[cfg(feature = "pool-128")]
pub const PACKET_POOL_SIZE: usize = 128;
pool!(pub PacketPool: [Packet; PACKET_POOL_SIZE]);
pub type PacketBox = Box<PacketPool>;
#[repr(align(4))]
pub struct Packet(pub [u8; MTU]);
impl Packet {
pub const fn new() -> Self {
Self([0; MTU])
}
}
pub trait PacketBoxExt {
fn slice(self, range: Range<usize>) -> PacketBuf;
}
impl PacketBoxExt for PacketBox {
fn slice(self, range: Range<usize>) -> PacketBuf {
PacketBuf { packet: self, range }
}
}
impl AsSlice for Packet {
type Element = u8;
fn as_slice(&self) -> &[Self::Element] {
&self.deref()[..]
}
}
impl AsMutSlice for Packet {
fn as_mut_slice(&mut self) -> &mut [Self::Element] {
&mut self.deref_mut()[..]
}
}
impl Deref for Packet {
type Target = [u8; MTU];
fn deref(&self) -> &[u8; MTU] {
&self.0
}
}
impl DerefMut for Packet {
fn deref_mut(&mut self) -> &mut [u8; MTU] {
&mut self.0
}
}
pub struct PacketBuf {
packet: PacketBox,
range: Range<usize>,
}
impl AsSlice for PacketBuf {
type Element = u8;
fn as_slice(&self) -> &[Self::Element] {
&self.packet[self.range.clone()]
}
}
impl AsMutSlice for PacketBuf {
fn as_mut_slice(&mut self) -> &mut [Self::Element] {
&mut self.packet[self.range.clone()]
}
}
impl Deref for PacketBuf {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.packet[self.range.clone()]
}
}
impl DerefMut for PacketBuf {
fn deref_mut(&mut self) -> &mut [u8] {
&mut self.packet[self.range.clone()]
}
}

View file

@ -1,315 +0,0 @@
use core::cell::UnsafeCell;
use core::future::{poll_fn, Future};
use core::task::{Context, Poll};
use embassy_sync::waitqueue::WakerRegistration;
use embassy_time::{Instant, Timer};
use futures::pin_mut;
use heapless::Vec;
#[cfg(feature = "dhcpv4")]
use smoltcp::iface::SocketHandle;
use smoltcp::iface::{Interface, InterfaceBuilder, SocketSet, SocketStorage};
#[cfg(feature = "medium-ethernet")]
use smoltcp::iface::{Neighbor, NeighborCache, Route, Routes};
#[cfg(feature = "medium-ethernet")]
use smoltcp::phy::{Device as _, Medium};
#[cfg(feature = "dhcpv4")]
use smoltcp::socket::dhcpv4;
use smoltcp::time::Instant as SmolInstant;
#[cfg(feature = "medium-ethernet")]
use smoltcp::wire::{EthernetAddress, HardwareAddress, IpAddress};
use smoltcp::wire::{IpCidr, Ipv4Address, Ipv4Cidr};
use crate::device::{Device, DeviceAdapter, LinkState};
const LOCAL_PORT_MIN: u16 = 1025;
const LOCAL_PORT_MAX: u16 = 65535;
pub struct StackResources<const ADDR: usize, const SOCK: usize, const NEIGHBOR: usize> {
addresses: [IpCidr; ADDR],
sockets: [SocketStorage<'static>; SOCK],
#[cfg(feature = "medium-ethernet")]
routes: [Option<(IpCidr, Route)>; 1],
#[cfg(feature = "medium-ethernet")]
neighbor_cache: [Option<(IpAddress, Neighbor)>; NEIGHBOR],
}
impl<const ADDR: usize, const SOCK: usize, const NEIGHBOR: usize> StackResources<ADDR, SOCK, NEIGHBOR> {
pub fn new() -> Self {
Self {
addresses: [IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 32); ADDR],
sockets: [SocketStorage::EMPTY; SOCK],
#[cfg(feature = "medium-ethernet")]
routes: [None; 1],
#[cfg(feature = "medium-ethernet")]
neighbor_cache: [None; NEIGHBOR],
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Config {
pub address: Ipv4Cidr,
pub gateway: Option<Ipv4Address>,
pub dns_servers: Vec<Ipv4Address, 3>,
}
pub enum ConfigStrategy {
Static(Config),
#[cfg(feature = "dhcpv4")]
Dhcp,
}
pub struct Stack<D: Device> {
pub(crate) socket: UnsafeCell<SocketStack>,
inner: UnsafeCell<Inner<D>>,
}
struct Inner<D: Device> {
device: DeviceAdapter<D>,
link_up: bool,
config: Option<Config>,
#[cfg(feature = "dhcpv4")]
dhcp_socket: Option<SocketHandle>,
}
pub(crate) struct SocketStack {
pub(crate) sockets: SocketSet<'static>,
pub(crate) iface: Interface<'static>,
pub(crate) waker: WakerRegistration,
next_local_port: u16,
}
unsafe impl<D: Device> Send for Stack<D> {}
impl<D: Device + 'static> Stack<D> {
pub fn new<const ADDR: usize, const SOCK: usize, const NEIGH: usize>(
device: D,
config: ConfigStrategy,
resources: &'static mut StackResources<ADDR, SOCK, NEIGH>,
random_seed: u64,
) -> Self {
#[cfg(feature = "medium-ethernet")]
let medium = device.capabilities().medium;
#[cfg(feature = "medium-ethernet")]
let ethernet_addr = if medium == Medium::Ethernet {
device.ethernet_address()
} else {
[0, 0, 0, 0, 0, 0]
};
let mut device = DeviceAdapter::new(device);
let mut b = InterfaceBuilder::new();
b = b.ip_addrs(&mut resources.addresses[..]);
b = b.random_seed(random_seed);
#[cfg(feature = "medium-ethernet")]
if medium == Medium::Ethernet {
b = b.hardware_addr(HardwareAddress::Ethernet(EthernetAddress(ethernet_addr)));
b = b.neighbor_cache(NeighborCache::new(&mut resources.neighbor_cache[..]));
b = b.routes(Routes::new(&mut resources.routes[..]));
}
let iface = b.finalize(&mut device);
let sockets = SocketSet::new(&mut resources.sockets[..]);
let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN;
let mut inner = Inner {
device,
link_up: false,
config: None,
#[cfg(feature = "dhcpv4")]
dhcp_socket: None,
};
let mut socket = SocketStack {
sockets,
iface,
waker: WakerRegistration::new(),
next_local_port,
};
match config {
ConfigStrategy::Static(config) => inner.apply_config(&mut socket, config),
#[cfg(feature = "dhcpv4")]
ConfigStrategy::Dhcp => {
let handle = socket.sockets.add(smoltcp::socket::dhcpv4::Socket::new());
inner.dhcp_socket = Some(handle);
}
}
Self {
socket: UnsafeCell::new(socket),
inner: UnsafeCell::new(inner),
}
}
/// SAFETY: must not call reentrantly.
unsafe fn with<R>(&self, f: impl FnOnce(&SocketStack, &Inner<D>) -> R) -> R {
f(&*self.socket.get(), &*self.inner.get())
}
/// SAFETY: must not call reentrantly.
unsafe fn with_mut<R>(&self, f: impl FnOnce(&mut SocketStack, &mut Inner<D>) -> R) -> R {
f(&mut *self.socket.get(), &mut *self.inner.get())
}
pub fn ethernet_address(&self) -> [u8; 6] {
unsafe { self.with(|_s, i| i.device.device.ethernet_address()) }
}
pub fn is_link_up(&self) -> bool {
unsafe { self.with(|_s, i| i.link_up) }
}
pub fn is_config_up(&self) -> bool {
unsafe { self.with(|_s, i| i.config.is_some()) }
}
pub fn config(&self) -> Option<Config> {
unsafe { self.with(|_s, i| i.config.clone()) }
}
pub async fn run(&self) -> ! {
poll_fn(|cx| {
unsafe { self.with_mut(|s, i| i.poll(cx, s)) }
Poll::<()>::Pending
})
.await;
unreachable!()
}
}
impl SocketStack {
#[allow(clippy::absurd_extreme_comparisons)]
pub fn get_local_port(&mut self) -> u16 {
let res = self.next_local_port;
self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 };
res
}
}
impl<D: Device + 'static> Inner<D> {
fn apply_config(&mut self, s: &mut SocketStack, config: Config) {
#[cfg(feature = "medium-ethernet")]
let medium = self.device.capabilities().medium;
debug!("Acquired IP configuration:");
debug!(" IP address: {}", config.address);
self.set_ipv4_addr(s, config.address);
#[cfg(feature = "medium-ethernet")]
if medium == Medium::Ethernet {
if let Some(gateway) = config.gateway {
debug!(" Default gateway: {}", gateway);
s.iface.routes_mut().add_default_ipv4_route(gateway).unwrap();
} else {
debug!(" Default gateway: None");
s.iface.routes_mut().remove_default_ipv4_route();
}
}
for (i, s) in config.dns_servers.iter().enumerate() {
debug!(" DNS server {}: {}", i, s);
}
self.config = Some(config)
}
#[allow(unused)] // used only with dhcp
fn unapply_config(&mut self, s: &mut SocketStack) {
#[cfg(feature = "medium-ethernet")]
let medium = self.device.capabilities().medium;
debug!("Lost IP configuration");
self.set_ipv4_addr(s, Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0));
#[cfg(feature = "medium-ethernet")]
if medium == Medium::Ethernet {
s.iface.routes_mut().remove_default_ipv4_route();
}
self.config = None
}
fn set_ipv4_addr(&mut self, s: &mut SocketStack, cidr: Ipv4Cidr) {
s.iface.update_ip_addrs(|addrs| {
let dest = addrs.iter_mut().next().unwrap();
*dest = IpCidr::Ipv4(cidr);
});
}
fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) {
self.device.device.register_waker(cx.waker());
s.waker.register(cx.waker());
let timestamp = instant_to_smoltcp(Instant::now());
if s.iface.poll(timestamp, &mut self.device, &mut s.sockets).is_err() {
// If poll() returns error, it may not be done yet, so poll again later.
cx.waker().wake_by_ref();
return;
}
// Update link up
let old_link_up = self.link_up;
self.link_up = self.device.device.link_state() == LinkState::Up;
// Print when changed
if old_link_up != self.link_up {
info!("link_up = {:?}", self.link_up);
}
#[cfg(feature = "dhcpv4")]
if let Some(dhcp_handle) = self.dhcp_socket {
let socket = s.sockets.get_mut::<dhcpv4::Socket>(dhcp_handle);
if self.link_up {
match socket.poll() {
None => {}
Some(dhcpv4::Event::Deconfigured) => self.unapply_config(s),
Some(dhcpv4::Event::Configured(config)) => {
let mut dns_servers = Vec::new();
for s in &config.dns_servers {
if let Some(addr) = s {
dns_servers.push(addr.clone()).unwrap();
}
}
self.apply_config(
s,
Config {
address: config.address,
gateway: config.router,
dns_servers,
},
)
}
}
} else if old_link_up {
socket.reset();
self.unapply_config(s);
}
}
//if old_link_up || self.link_up {
// self.poll_configurator(timestamp)
//}
if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) {
let t = Timer::at(instant_from_smoltcp(poll_at));
pin_mut!(t);
if t.poll(cx).is_ready() {
cx.waker().wake_by_ref();
}
}
}
}
fn instant_to_smoltcp(instant: Instant) -> SmolInstant {
SmolInstant::from_millis(instant.as_millis() as i64)
}
fn instant_from_smoltcp(instant: SmolInstant) -> Instant {
Instant::from_millis(instant.total_millis() as u64)
}

View file

@ -1,16 +1,15 @@
use core::cell::UnsafeCell;
use core::cell::RefCell;
use core::future::poll_fn;
use core::mem;
use core::task::Poll;
use embassy_net_driver::Driver;
use smoltcp::iface::{Interface, SocketHandle};
use smoltcp::socket::tcp;
use smoltcp::time::Duration;
use smoltcp::wire::{IpEndpoint, IpListenEndpoint};
use super::stack::Stack;
use crate::stack::SocketStack;
use crate::Device;
use crate::{SocketStack, Stack};
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
@ -67,9 +66,8 @@ impl<'a> TcpWriter<'a> {
}
impl<'a> TcpSocket<'a> {
pub fn new<D: Device>(stack: &'a Stack<D>, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self {
// safety: not accessed reentrantly.
let s = unsafe { &mut *stack.socket.get() };
pub fn new<D: Driver>(stack: &'a Stack<D>, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self {
let s = &mut *stack.socket.borrow_mut();
let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) };
let handle = s.sockets.add(tcp::Socket::new(
@ -93,17 +91,18 @@ impl<'a> TcpSocket<'a> {
where
T: Into<IpEndpoint>,
{
// safety: not accessed reentrantly.
let local_port = unsafe { &mut *self.io.stack.get() }.get_local_port();
let local_port = self.io.stack.borrow_mut().get_local_port();
// safety: not accessed reentrantly.
match unsafe { self.io.with_mut(|s, i| s.connect(i, remote_endpoint, local_port)) } {
match {
self.io
.with_mut(|s, i| s.connect(i.context(), remote_endpoint, local_port))
} {
Ok(()) => {}
Err(tcp::ConnectError::InvalidState) => return Err(ConnectError::InvalidState),
Err(tcp::ConnectError::Unaddressable) => return Err(ConnectError::NoRoute),
}
poll_fn(|cx| unsafe {
poll_fn(|cx| {
self.io.with_mut(|s, _| match s.state() {
tcp::State::Closed | tcp::State::TimeWait => Poll::Ready(Err(ConnectError::ConnectionReset)),
tcp::State::Listen => unreachable!(),
@ -121,14 +120,13 @@ impl<'a> TcpSocket<'a> {
where
T: Into<IpListenEndpoint>,
{
// safety: not accessed reentrantly.
match unsafe { self.io.with_mut(|s, _| s.listen(local_endpoint)) } {
match self.io.with_mut(|s, _| s.listen(local_endpoint)) {
Ok(()) => {}
Err(tcp::ListenError::InvalidState) => return Err(AcceptError::InvalidState),
Err(tcp::ListenError::Unaddressable) => return Err(AcceptError::InvalidPort),
}
poll_fn(|cx| unsafe {
poll_fn(|cx| {
self.io.with_mut(|s, _| match s.state() {
tcp::State::Listen | tcp::State::SynSent | tcp::State::SynReceived => {
s.register_send_waker(cx.waker());
@ -149,51 +147,49 @@ impl<'a> TcpSocket<'a> {
}
pub fn set_timeout(&mut self, duration: Option<Duration>) {
unsafe { self.io.with_mut(|s, _| s.set_timeout(duration)) }
self.io.with_mut(|s, _| s.set_timeout(duration))
}
pub fn set_keep_alive(&mut self, interval: Option<Duration>) {
unsafe { self.io.with_mut(|s, _| s.set_keep_alive(interval)) }
self.io.with_mut(|s, _| s.set_keep_alive(interval))
}
pub fn set_hop_limit(&mut self, hop_limit: Option<u8>) {
unsafe { self.io.with_mut(|s, _| s.set_hop_limit(hop_limit)) }
self.io.with_mut(|s, _| s.set_hop_limit(hop_limit))
}
pub fn local_endpoint(&self) -> Option<IpEndpoint> {
unsafe { self.io.with(|s, _| s.local_endpoint()) }
self.io.with(|s, _| s.local_endpoint())
}
pub fn remote_endpoint(&self) -> Option<IpEndpoint> {
unsafe { self.io.with(|s, _| s.remote_endpoint()) }
self.io.with(|s, _| s.remote_endpoint())
}
pub fn state(&self) -> tcp::State {
unsafe { self.io.with(|s, _| s.state()) }
self.io.with(|s, _| s.state())
}
pub fn close(&mut self) {
unsafe { self.io.with_mut(|s, _| s.close()) }
self.io.with_mut(|s, _| s.close())
}
pub fn abort(&mut self) {
unsafe { self.io.with_mut(|s, _| s.abort()) }
self.io.with_mut(|s, _| s.abort())
}
pub fn may_send(&self) -> bool {
unsafe { self.io.with(|s, _| s.may_send()) }
self.io.with(|s, _| s.may_send())
}
pub fn may_recv(&self) -> bool {
unsafe { self.io.with(|s, _| s.may_recv()) }
self.io.with(|s, _| s.may_recv())
}
}
impl<'a> Drop for TcpSocket<'a> {
fn drop(&mut self) {
// safety: not accessed reentrantly.
let s = unsafe { &mut *self.io.stack.get() };
s.sockets.remove(self.io.handle);
self.io.stack.borrow_mut().sockets.remove(self.io.handle);
}
}
@ -201,21 +197,19 @@ impl<'a> Drop for TcpSocket<'a> {
#[derive(Copy, Clone)]
struct TcpIo<'a> {
stack: &'a UnsafeCell<SocketStack>,
stack: &'a RefCell<SocketStack>,
handle: SocketHandle,
}
impl<'d> TcpIo<'d> {
/// SAFETY: must not call reentrantly.
unsafe fn with<R>(&self, f: impl FnOnce(&tcp::Socket, &Interface) -> R) -> R {
let s = &*self.stack.get();
fn with<R>(&self, f: impl FnOnce(&tcp::Socket, &Interface) -> R) -> R {
let s = &*self.stack.borrow();
let socket = s.sockets.get::<tcp::Socket>(self.handle);
f(socket, &s.iface)
}
/// SAFETY: must not call reentrantly.
unsafe fn with_mut<R>(&mut self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R {
let s = &mut *self.stack.get();
fn with_mut<R>(&mut self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R {
let s = &mut *self.stack.borrow_mut();
let socket = s.sockets.get_mut::<tcp::Socket>(self.handle);
let res = f(socket, &mut s.iface);
s.waker.wake();
@ -223,7 +217,7 @@ impl<'d> TcpIo<'d> {
}
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
poll_fn(move |cx| unsafe {
poll_fn(move |cx| {
// CAUTION: smoltcp semantics around EOF are different to what you'd expect
// from posix-like IO, so we have to tweak things here.
self.with_mut(|s, _| match s.recv_slice(buf) {
@ -244,7 +238,7 @@ impl<'d> TcpIo<'d> {
}
async fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
poll_fn(move |cx| unsafe {
poll_fn(move |cx| {
self.with_mut(|s, _| match s.send_slice(buf) {
// Not ready to send (no space in the tx buffer)
Ok(0) => {
@ -271,8 +265,6 @@ impl<'d> TcpIo<'d> {
#[cfg(feature = "nightly")]
mod embedded_io_impls {
use core::future::Future;
use super::*;
impl embedded_io::Error for ConnectError {
@ -292,30 +284,18 @@ mod embedded_io_impls {
}
impl<'d> embedded_io::asynch::Read for TcpSocket<'d> {
type ReadFuture<'a> = impl Future<Output = Result<usize, Self::Error>>
where
Self: 'a;
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
self.io.read(buf)
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.io.read(buf).await
}
}
impl<'d> embedded_io::asynch::Write for TcpSocket<'d> {
type WriteFuture<'a> = impl Future<Output = Result<usize, Self::Error>>
where
Self: 'a;
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
self.io.write(buf)
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.io.write(buf).await
}
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>>
where
Self: 'a;
fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> {
self.io.flush()
async fn flush(&mut self) -> Result<(), Self::Error> {
self.io.flush().await
}
}
@ -324,12 +304,8 @@ mod embedded_io_impls {
}
impl<'d> embedded_io::asynch::Read for TcpReader<'d> {
type ReadFuture<'a> = impl Future<Output = Result<usize, Self::Error>>
where
Self: 'a;
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
self.io.read(buf)
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.io.read(buf).await
}
}
@ -338,27 +314,19 @@ mod embedded_io_impls {
}
impl<'d> embedded_io::asynch::Write for TcpWriter<'d> {
type WriteFuture<'a> = impl Future<Output = Result<usize, Self::Error>>
where
Self: 'a;
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
self.io.write(buf)
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.io.write(buf).await
}
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>>
where
Self: 'a;
fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> {
self.io.flush()
async fn flush(&mut self) -> Result<(), Self::Error> {
self.io.flush().await
}
}
}
#[cfg(all(feature = "unstable-traits", feature = "nightly"))]
pub mod client {
use core::future::Future;
use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use core::ptr::NonNull;
@ -368,45 +336,46 @@ pub mod client {
use super::*;
/// TCP client capable of creating up to N multiple connections with tx and rx buffers according to TX_SZ and RX_SZ.
pub struct TcpClient<'d, D: Device, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> {
pub struct TcpClient<'d, D: Driver, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> {
stack: &'d Stack<D>,
state: &'d TcpClientState<N, TX_SZ, RX_SZ>,
}
impl<'d, D: Device, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, D, N, TX_SZ, RX_SZ> {
impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, D, N, TX_SZ, RX_SZ> {
/// Create a new TcpClient
pub fn new(stack: &'d Stack<D>, state: &'d TcpClientState<N, TX_SZ, RX_SZ>) -> Self {
Self { stack, state }
}
}
impl<'d, D: Device, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect
impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect
for TcpClient<'d, D, N, TX_SZ, RX_SZ>
{
type Error = Error;
type Connection<'m> = TcpConnection<'m, N, TX_SZ, RX_SZ> where Self: 'm;
type ConnectFuture<'m> = impl Future<Output = Result<Self::Connection<'m>, Self::Error>> + 'm
where
Self: 'm;
fn connect<'m>(&'m self, remote: embedded_nal_async::SocketAddr) -> Self::ConnectFuture<'m> {
async move {
let addr: crate::IpAddress = match remote.ip() {
IpAddr::V4(addr) => crate::IpAddress::Ipv4(crate::Ipv4Address::from_bytes(&addr.octets())),
#[cfg(feature = "proto-ipv6")]
IpAddr::V6(addr) => crate::IpAddress::Ipv6(crate::Ipv6Address::from_bytes(&addr.octets())),
#[cfg(not(feature = "proto-ipv6"))]
IpAddr::V6(_) => panic!("ipv6 support not enabled"),
};
let remote_endpoint = (addr, remote.port());
let mut socket = TcpConnection::new(&self.stack, self.state)?;
socket
.socket
.connect(remote_endpoint)
.await
.map_err(|_| Error::ConnectionReset)?;
Ok(socket)
}
async fn connect<'a>(
&'a self,
remote: embedded_nal_async::SocketAddr,
) -> Result<Self::Connection<'a>, Self::Error>
where
Self: 'a,
{
let addr: crate::IpAddress = match remote.ip() {
IpAddr::V4(addr) => crate::IpAddress::Ipv4(crate::Ipv4Address::from_bytes(&addr.octets())),
#[cfg(feature = "proto-ipv6")]
IpAddr::V6(addr) => crate::IpAddress::Ipv6(crate::Ipv6Address::from_bytes(&addr.octets())),
#[cfg(not(feature = "proto-ipv6"))]
IpAddr::V6(_) => panic!("ipv6 support not enabled"),
};
let remote_endpoint = (addr, remote.port());
let mut socket = TcpConnection::new(&self.stack, self.state)?;
socket
.socket
.connect(remote_endpoint)
.await
.map_err(|_| Error::ConnectionReset)?;
Ok(socket)
}
}
@ -417,10 +386,10 @@ pub mod client {
}
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpConnection<'d, N, TX_SZ, RX_SZ> {
fn new<D: Device>(stack: &'d Stack<D>, state: &'d TcpClientState<N, TX_SZ, RX_SZ>) -> Result<Self, Error> {
fn new<D: Driver>(stack: &'d Stack<D>, state: &'d TcpClientState<N, TX_SZ, RX_SZ>) -> Result<Self, Error> {
let mut bufs = state.pool.alloc().ok_or(Error::ConnectionReset)?;
Ok(Self {
socket: unsafe { TcpSocket::new(stack, &mut bufs.as_mut().0, &mut bufs.as_mut().1) },
socket: unsafe { TcpSocket::new(stack, &mut bufs.as_mut().1, &mut bufs.as_mut().0) },
state,
bufs,
})
@ -445,32 +414,20 @@ pub mod client {
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::asynch::Read
for TcpConnection<'d, N, TX_SZ, RX_SZ>
{
type ReadFuture<'a> = impl Future<Output = Result<usize, Self::Error>>
where
Self: 'a;
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
self.socket.read(buf)
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.socket.read(buf).await
}
}
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::asynch::Write
for TcpConnection<'d, N, TX_SZ, RX_SZ>
{
type WriteFuture<'a> = impl Future<Output = Result<usize, Self::Error>>
where
Self: 'a;
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
self.socket.write(buf)
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.socket.write(buf).await
}
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>>
where
Self: 'a;
fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> {
self.socket.flush()
async fn flush(&mut self) -> Result<(), Self::Error> {
self.socket.flush().await
}
}

View file

@ -1,14 +1,14 @@
use core::cell::UnsafeCell;
use core::cell::RefCell;
use core::future::poll_fn;
use core::mem;
use core::task::Poll;
use embassy_net_driver::Driver;
use smoltcp::iface::{Interface, SocketHandle};
use smoltcp::socket::udp::{self, PacketMetadata};
use smoltcp::wire::{IpEndpoint, IpListenEndpoint};
use super::stack::SocketStack;
use crate::{Device, Stack};
use crate::{SocketStack, Stack};
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
@ -27,20 +27,19 @@ pub enum Error {
}
pub struct UdpSocket<'a> {
stack: &'a UnsafeCell<SocketStack>,
stack: &'a RefCell<SocketStack>,
handle: SocketHandle,
}
impl<'a> UdpSocket<'a> {
pub fn new<D: Device>(
pub fn new<D: Driver>(
stack: &'a Stack<D>,
rx_meta: &'a mut [PacketMetadata],
rx_buffer: &'a mut [u8],
tx_meta: &'a mut [PacketMetadata],
tx_buffer: &'a mut [u8],
) -> Self {
// safety: not accessed reentrantly.
let s = unsafe { &mut *stack.socket.get() };
let s = &mut *stack.socket.borrow_mut();
let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) };
let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
@ -63,30 +62,26 @@ impl<'a> UdpSocket<'a> {
{
let mut endpoint = endpoint.into();
// safety: not accessed reentrantly.
if endpoint.port == 0 {
// If user didn't specify port allocate a dynamic port.
endpoint.port = unsafe { &mut *self.stack.get() }.get_local_port();
endpoint.port = self.stack.borrow_mut().get_local_port();
}
// safety: not accessed reentrantly.
match unsafe { self.with_mut(|s, _| s.bind(endpoint)) } {
match self.with_mut(|s, _| s.bind(endpoint)) {
Ok(()) => Ok(()),
Err(udp::BindError::InvalidState) => Err(BindError::InvalidState),
Err(udp::BindError::Unaddressable) => Err(BindError::NoRoute),
}
}
/// SAFETY: must not call reentrantly.
unsafe fn with<R>(&self, f: impl FnOnce(&udp::Socket, &Interface) -> R) -> R {
let s = &*self.stack.get();
fn with<R>(&self, f: impl FnOnce(&udp::Socket, &Interface) -> R) -> R {
let s = &*self.stack.borrow();
let socket = s.sockets.get::<udp::Socket>(self.handle);
f(socket, &s.iface)
}
/// SAFETY: must not call reentrantly.
unsafe fn with_mut<R>(&self, f: impl FnOnce(&mut udp::Socket, &mut Interface) -> R) -> R {
let s = &mut *self.stack.get();
fn with_mut<R>(&self, f: impl FnOnce(&mut udp::Socket, &mut Interface) -> R) -> R {
let s = &mut *self.stack.borrow_mut();
let socket = s.sockets.get_mut::<udp::Socket>(self.handle);
let res = f(socket, &mut s.iface);
s.waker.wake();
@ -94,7 +89,7 @@ impl<'a> UdpSocket<'a> {
}
pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, IpEndpoint), Error> {
poll_fn(move |cx| unsafe {
poll_fn(move |cx| {
self.with_mut(|s, _| match s.recv_slice(buf) {
Ok(x) => Poll::Ready(Ok(x)),
// No data ready
@ -113,7 +108,7 @@ impl<'a> UdpSocket<'a> {
T: Into<IpEndpoint>,
{
let remote_endpoint = remote_endpoint.into();
poll_fn(move |cx| unsafe {
poll_fn(move |cx| {
self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint) {
// Entire datagram has been sent
Ok(()) => Poll::Ready(Ok(())),
@ -128,30 +123,28 @@ impl<'a> UdpSocket<'a> {
}
pub fn endpoint(&self) -> IpListenEndpoint {
unsafe { self.with(|s, _| s.endpoint()) }
self.with(|s, _| s.endpoint())
}
pub fn is_open(&self) -> bool {
unsafe { self.with(|s, _| s.is_open()) }
self.with(|s, _| s.is_open())
}
pub fn close(&mut self) {
unsafe { self.with_mut(|s, _| s.close()) }
self.with_mut(|s, _| s.close())
}
pub fn may_send(&self) -> bool {
unsafe { self.with(|s, _| s.can_send()) }
self.with(|s, _| s.can_send())
}
pub fn may_recv(&self) -> bool {
unsafe { self.with(|s, _| s.can_recv()) }
self.with(|s, _| s.can_recv())
}
}
impl Drop for UdpSocket<'_> {
fn drop(&mut self) {
// safety: not accessed reentrantly.
let s = unsafe { &mut *self.stack.get() };
s.sockets.remove(self.handle);
self.stack.borrow_mut().sockets.remove(self.handle);
}
}

View file

@ -75,8 +75,8 @@ embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver", optiona
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9", optional = true}
embedded-hal-async = { version = "=0.1.0-alpha.2", optional = true}
embedded-io = { version = "0.3.0", features = ["async"], optional = true }
embedded-hal-async = { version = "=0.2.0-alpha.0", optional = true}
embedded-io = { version = "0.4.0", features = ["async"], optional = true }
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
@ -90,13 +90,13 @@ embedded-storage = "0.3.0"
embedded-storage-async = { version = "0.3.0", optional = true }
cfg-if = "1.0.0"
nrf52805-pac = { version = "0.11.0", optional = true, features = [ "rt" ] }
nrf52810-pac = { version = "0.11.0", optional = true, features = [ "rt" ] }
nrf52811-pac = { version = "0.11.0", optional = true, features = [ "rt" ] }
nrf52820-pac = { version = "0.11.0", optional = true, features = [ "rt" ] }
nrf52832-pac = { version = "0.11.0", optional = true, features = [ "rt" ] }
nrf52833-pac = { version = "0.11.0", optional = true, features = [ "rt" ] }
nrf52840-pac = { version = "0.11.0", optional = true, features = [ "rt" ] }
nrf5340-app-pac = { version = "0.11.0", optional = true, features = [ "rt" ] }
nrf5340-net-pac = { version = "0.11.0", optional = true, features = [ "rt" ] }
nrf9160-pac = { version = "0.11.0", optional = true, features = [ "rt" ] }
nrf52805-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }
nrf52810-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }
nrf52811-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }
nrf52820-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }
nrf52832-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }
nrf52833-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }
nrf52840-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }
nrf5340-app-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }
nrf5340-net-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }
nrf9160-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }

58
embassy-nrf/README.md Normal file
View file

@ -0,0 +1,58 @@
# Embassy nRF HAL
HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed.
The Embassy nRF HAL targets the Nordic Semiconductor nRF family of hardware. The HAL implements both blocking and async APIs
for many peripherals. The benefit of using the async APIs is that the HAL takes care of waiting for peripherals to
complete operations in low power mod and handling interrupts, so that applications can focus on more important matters.
## EasyDMA considerations
On nRF chips, peripherals can use the so called EasyDMA feature to offload the task of interacting
with peripherals. It takes care of sending/receiving data over a variety of bus protocols (TWI/I2C, UART, SPI).
However, EasyDMA requires the buffers used to transmit and receive data to reside in RAM. Unfortunately, Rust
slices will not always do so. The following example using the SPI peripheral shows a common situation where this might happen:
```no_run
// As we pass a slice to the function whose contents will not ever change,
// the compiler writes it into the flash and thus the pointer to it will
// reference static memory. Since EasyDMA requires slices to reside in RAM,
// this function call will fail.
let result = spim.write_from_ram(&[1, 2, 3]);
assert_eq!(result, Err(Error::BufferNotInRAM));
// The data is still static and located in flash. However, since we are assigning
// it to a variable, the compiler will load it into memory. Passing a reference to the
// variable will yield a pointer that references dynamic memory, thus making EasyDMA happy.
// This function call succeeds.
let data = [1, 2, 3];
let result = spim.write_from_ram(&data);
assert!(result.is_ok());
```
Each peripheral struct which uses EasyDMA ([`Spim`](spim::Spim), [`Uarte`](uarte::Uarte), [`Twim`](twim::Twim)) has two variants of their mutating functions:
- Functions with the suffix (e.g. [`write_from_ram`](spim::Spim::write_from_ram), [`transfer_from_ram`](spim::Spim::transfer_from_ram)) will return an error if the passed slice does not reside in RAM.
- Functions without the suffix (e.g. [`write`](spim::Spim::write), [`transfer`](spim::Spim::transfer)) will check whether the data is in RAM and copy it into memory prior to transmission.
Since copying incurs a overhead, you are given the option to choose from `_from_ram` variants which will
fail and notify you, or the more convenient versions without the suffix which are potentially a little bit
more inefficient. Be aware that this overhead is not only in terms of instruction count but also in terms of memory usage
as the methods without the suffix will be allocating a statically sized buffer (up to 512 bytes for the nRF52840).
Note that the methods that read data like [`read`](spim::Spim::read) and [`transfer_in_place`](spim::Spim::transfer_in_place) do not have the corresponding `_from_ram` variants as
mutable slices always reside in RAM.
## Minimum supported Rust version (MSRV)
Embassy 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
This work is licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
<http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.

View file

@ -1,4 +1,4 @@
//! Async buffered UART
//! Async buffered UART driver.
//!
//! WARNING!!! The functionality provided here is intended to be used only
//! in situations where hardware flow control are available i.e. CTS and RTS.
@ -15,7 +15,7 @@
use core::cell::RefCell;
use core::cmp::min;
use core::future::{poll_fn, Future};
use core::future::poll_fn;
use core::sync::atomic::{compiler_fence, Ordering};
use core::task::Poll;
@ -69,7 +69,7 @@ struct StateInner<'d, U: UarteInstance, T: TimerInstance> {
tx_waker: WakerRegistration,
}
/// Interface to a UARTE instance
/// Buffered UARTE driver.
pub struct BufferedUarte<'d, U: UarteInstance, T: TimerInstance> {
inner: RefCell<PeripheralMutex<'d, StateInner<'d, U, T>>>,
}
@ -199,6 +199,9 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> {
});
}
/// Split the UART in reader and writer parts.
///
/// This allows reading and writing concurrently from independent tasks.
pub fn split<'u>(&'u mut self) -> (BufferedUarteRx<'u, 'd, U, T>, BufferedUarteTx<'u, 'd, U, T>) {
(BufferedUarteRx { inner: self }, BufferedUarteTx { inner: self })
}
@ -320,10 +323,12 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> {
}
}
/// Reader part of the buffered UARTE driver.
pub struct BufferedUarteTx<'u, 'd, U: UarteInstance, T: TimerInstance> {
inner: &'u BufferedUarte<'d, U, T>,
}
/// Writer part of the buffered UARTE driver.
pub struct BufferedUarteRx<'u, 'd, U: UarteInstance, T: TimerInstance> {
inner: &'u BufferedUarte<'d, U, T>,
}
@ -341,32 +346,20 @@ impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUar
}
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Read for BufferedUarte<'d, U, T> {
type ReadFuture<'a> = impl Future<Output = Result<usize, Self::Error>>
where
Self: 'a;
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
self.inner_read(buf)
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.inner_read(buf).await
}
}
impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Read for BufferedUarteRx<'u, 'd, U, T> {
type ReadFuture<'a> = impl Future<Output = Result<usize, Self::Error>>
where
Self: 'a;
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
self.inner.inner_read(buf)
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.inner.inner_read(buf).await
}
}
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::BufRead for BufferedUarte<'d, U, T> {
type FillBufFuture<'a> = impl Future<Output = Result<&'a [u8], Self::Error>>
where
Self: 'a;
fn fill_buf<'a>(&'a mut self) -> Self::FillBufFuture<'a> {
self.inner_fill_buf()
async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> {
self.inner_fill_buf().await
}
fn consume(&mut self, amt: usize) {
@ -375,12 +368,8 @@ impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::BufRead for Bu
}
impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::BufRead for BufferedUarteRx<'u, 'd, U, T> {
type FillBufFuture<'a> = impl Future<Output = Result<&'a [u8], Self::Error>>
where
Self: 'a;
fn fill_buf<'a>(&'a mut self) -> Self::FillBufFuture<'a> {
self.inner.inner_fill_buf()
async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> {
self.inner.inner_fill_buf().await
}
fn consume(&mut self, amt: usize) {
@ -389,38 +378,22 @@ impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::BufRea
}
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Write for BufferedUarte<'d, U, T> {
type WriteFuture<'a> = impl Future<Output = Result<usize, Self::Error>>
where
Self: 'a;
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
self.inner_write(buf)
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.inner_write(buf).await
}
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>>
where
Self: 'a;
fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> {
self.inner_flush()
async fn flush(&mut self) -> Result<(), Self::Error> {
self.inner_flush().await
}
}
impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Write for BufferedUarteTx<'u, 'd, U, T> {
type WriteFuture<'a> = impl Future<Output = Result<usize, Self::Error>>
where
Self: 'a;
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
self.inner.inner_write(buf)
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.inner.inner_write(buf).await
}
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>>
where
Self: 'a;
fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> {
self.inner.inner_flush()
async fn flush(&mut self) -> Result<(), Self::Error> {
self.inner.inner_flush().await
}
}

View file

@ -131,8 +131,12 @@ impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
impl_spim!(SPI0, SPIM0, SPIM0_SPIS0_SPI0);
impl_spis!(SPI0, SPIS0, SPIM0_SPIS0_SPI0);
impl_twim!(TWI0, TWIM0, TWIM0_TWIS0_TWI0);
impl_twis!(TWI0, TWIS0, TWIM0_TWIS0_TWI0);
impl_timer!(TIMER0, TIMER0, TIMER0);
impl_timer!(TIMER1, TIMER1, TIMER1);
impl_timer!(TIMER2, TIMER2, TIMER2);
@ -193,8 +197,8 @@ impl_ppi_channel!(PPI_CH29, 29 => static);
impl_ppi_channel!(PPI_CH30, 30 => static);
impl_ppi_channel!(PPI_CH31, 31 => static);
impl_saadc_input!(P0_04, ANALOGINPUT2);
impl_saadc_input!(P0_05, ANALOGINPUT3);
impl_saadc_input!(P0_04, ANALOG_INPUT2);
impl_saadc_input!(P0_05, ANALOG_INPUT3);
pub mod irqs {
use embassy_cortex_m::interrupt::_export::declare;

View file

@ -128,14 +128,21 @@ embassy_hal_common::peripherals! {
// QDEC
QDEC,
// PDM
PDM,
}
impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
impl_spim!(SPI0, SPIM0, SPIM0_SPIS0_SPI0);
impl_spis!(SPI0, SPIS0, SPIM0_SPIS0_SPI0);
impl_twim!(TWI0, TWIM0, TWIM0_TWIS0_TWI0);
impl_twis!(TWI0, TWIS0, TWIM0_TWIS0_TWI0);
impl_pwm!(PWM0, PWM0, PWM0);
impl_timer!(TIMER0, TIMER0, TIMER0);
@ -208,14 +215,14 @@ impl_ppi_channel!(PPI_CH29, 29 => static);
impl_ppi_channel!(PPI_CH30, 30 => static);
impl_ppi_channel!(PPI_CH31, 31 => static);
impl_saadc_input!(P0_02, ANALOGINPUT0);
impl_saadc_input!(P0_03, ANALOGINPUT1);
impl_saadc_input!(P0_04, ANALOGINPUT2);
impl_saadc_input!(P0_05, ANALOGINPUT3);
impl_saadc_input!(P0_28, ANALOGINPUT4);
impl_saadc_input!(P0_29, ANALOGINPUT5);
impl_saadc_input!(P0_30, ANALOGINPUT6);
impl_saadc_input!(P0_31, ANALOGINPUT7);
impl_saadc_input!(P0_02, ANALOG_INPUT0);
impl_saadc_input!(P0_03, ANALOG_INPUT1);
impl_saadc_input!(P0_04, ANALOG_INPUT2);
impl_saadc_input!(P0_05, ANALOG_INPUT3);
impl_saadc_input!(P0_28, ANALOG_INPUT4);
impl_saadc_input!(P0_29, ANALOG_INPUT5);
impl_saadc_input!(P0_30, ANALOG_INPUT6);
impl_saadc_input!(P0_31, ANALOG_INPUT7);
pub mod irqs {
use embassy_cortex_m::interrupt::_export::declare;

View file

@ -128,6 +128,9 @@ embassy_hal_common::peripherals! {
// QDEC
QDEC,
// PDM
PDM,
}
impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
@ -135,8 +138,13 @@ impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
impl_spim!(TWISPI0, SPIM0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0);
impl_spim!(SPI1, SPIM1, SPIM1_SPIS1_SPI1);
impl_spis!(TWISPI0, SPIS0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0);
impl_spis!(SPI1, SPIS1, SPIM1_SPIS1_SPI1);
impl_twim!(TWISPI0, TWIM0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0);
impl_twis!(TWISPI0, TWIS0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0);
impl_pwm!(PWM0, PWM0, PWM0);
impl_timer!(TIMER0, TIMER0, TIMER0);
@ -209,14 +217,14 @@ impl_ppi_channel!(PPI_CH29, 29 => static);
impl_ppi_channel!(PPI_CH30, 30 => static);
impl_ppi_channel!(PPI_CH31, 31 => static);
impl_saadc_input!(P0_02, ANALOGINPUT0);
impl_saadc_input!(P0_03, ANALOGINPUT1);
impl_saadc_input!(P0_04, ANALOGINPUT2);
impl_saadc_input!(P0_05, ANALOGINPUT3);
impl_saadc_input!(P0_28, ANALOGINPUT4);
impl_saadc_input!(P0_29, ANALOGINPUT5);
impl_saadc_input!(P0_30, ANALOGINPUT6);
impl_saadc_input!(P0_31, ANALOGINPUT7);
impl_saadc_input!(P0_02, ANALOG_INPUT0);
impl_saadc_input!(P0_03, ANALOG_INPUT1);
impl_saadc_input!(P0_04, ANALOG_INPUT2);
impl_saadc_input!(P0_05, ANALOG_INPUT3);
impl_saadc_input!(P0_28, ANALOG_INPUT4);
impl_saadc_input!(P0_29, ANALOG_INPUT5);
impl_saadc_input!(P0_30, ANALOG_INPUT6);
impl_saadc_input!(P0_31, ANALOG_INPUT7);
pub mod irqs {
use embassy_cortex_m::interrupt::_export::declare;

View file

@ -136,9 +136,15 @@ impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
impl_spim!(TWISPI0, SPIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_timer!(TIMER0, TIMER0, TIMER0);
impl_timer!(TIMER1, TIMER1, TIMER1);
impl_timer!(TIMER2, TIMER2, TIMER2);

View file

@ -138,6 +138,9 @@ embassy_hal_common::peripherals! {
// QDEC
QDEC,
// I2S
I2S,
}
impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
@ -146,9 +149,16 @@ impl_spim!(TWISPI0, SPIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2);
impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_spis!(SPI2, SPIS2, SPIM2_SPIS2_SPI2);
impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_pwm!(PWM0, PWM0, PWM0);
impl_pwm!(PWM1, PWM1, PWM1);
impl_pwm!(PWM2, PWM2, PWM2);
@ -225,14 +235,16 @@ impl_ppi_channel!(PPI_CH29, 29 => static);
impl_ppi_channel!(PPI_CH30, 30 => static);
impl_ppi_channel!(PPI_CH31, 31 => static);
impl_saadc_input!(P0_02, ANALOGINPUT0);
impl_saadc_input!(P0_03, ANALOGINPUT1);
impl_saadc_input!(P0_04, ANALOGINPUT2);
impl_saadc_input!(P0_05, ANALOGINPUT3);
impl_saadc_input!(P0_28, ANALOGINPUT4);
impl_saadc_input!(P0_29, ANALOGINPUT5);
impl_saadc_input!(P0_30, ANALOGINPUT6);
impl_saadc_input!(P0_31, ANALOGINPUT7);
impl_saadc_input!(P0_02, ANALOG_INPUT0);
impl_saadc_input!(P0_03, ANALOG_INPUT1);
impl_saadc_input!(P0_04, ANALOG_INPUT2);
impl_saadc_input!(P0_05, ANALOG_INPUT3);
impl_saadc_input!(P0_28, ANALOG_INPUT4);
impl_saadc_input!(P0_29, ANALOG_INPUT5);
impl_saadc_input!(P0_30, ANALOG_INPUT6);
impl_saadc_input!(P0_31, ANALOG_INPUT7);
impl_i2s!(I2S, I2S, I2S);
pub mod irqs {
use embassy_cortex_m::interrupt::_export::declare;
@ -274,6 +286,6 @@ pub mod irqs {
declare!(PWM2);
declare!(SPIM2_SPIS2_SPI2);
declare!(RTC2);
declare!(I2S);
declare!(FPU);
declare!(I2S);
}

View file

@ -158,6 +158,12 @@ embassy_hal_common::peripherals! {
// QDEC
QDEC,
// PDM
PDM,
// I2S
I2S,
}
#[cfg(feature = "nightly")]
@ -171,9 +177,16 @@ impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2);
impl_spim!(SPI3, SPIM3, SPIM3);
impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_spis!(SPI2, SPIS2, SPIM2_SPIS2_SPI2);
impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_pwm!(PWM0, PWM0, PWM0);
impl_pwm!(PWM1, PWM1, PWM1);
impl_pwm!(PWM2, PWM2, PWM2);
@ -268,14 +281,16 @@ impl_ppi_channel!(PPI_CH29, 29 => static);
impl_ppi_channel!(PPI_CH30, 30 => static);
impl_ppi_channel!(PPI_CH31, 31 => static);
impl_saadc_input!(P0_02, ANALOGINPUT0);
impl_saadc_input!(P0_03, ANALOGINPUT1);
impl_saadc_input!(P0_04, ANALOGINPUT2);
impl_saadc_input!(P0_05, ANALOGINPUT3);
impl_saadc_input!(P0_28, ANALOGINPUT4);
impl_saadc_input!(P0_29, ANALOGINPUT5);
impl_saadc_input!(P0_30, ANALOGINPUT6);
impl_saadc_input!(P0_31, ANALOGINPUT7);
impl_saadc_input!(P0_02, ANALOG_INPUT0);
impl_saadc_input!(P0_03, ANALOG_INPUT1);
impl_saadc_input!(P0_04, ANALOG_INPUT2);
impl_saadc_input!(P0_05, ANALOG_INPUT3);
impl_saadc_input!(P0_28, ANALOG_INPUT4);
impl_saadc_input!(P0_29, ANALOG_INPUT5);
impl_saadc_input!(P0_30, ANALOG_INPUT6);
impl_saadc_input!(P0_31, ANALOG_INPUT7);
impl_i2s!(I2S, I2S, I2S);
pub mod irqs {
use embassy_cortex_m::interrupt::_export::declare;
@ -317,10 +332,10 @@ pub mod irqs {
declare!(PWM2);
declare!(SPIM2_SPIS2_SPI2);
declare!(RTC2);
declare!(I2S);
declare!(FPU);
declare!(USBD);
declare!(UARTE1);
declare!(PWM3);
declare!(SPIM3);
declare!(I2S);
}

View file

@ -161,6 +161,12 @@ embassy_hal_common::peripherals! {
// TEMP
TEMP,
// PDM
PDM,
// I2S
I2S,
}
#[cfg(feature = "nightly")]
@ -174,9 +180,16 @@ impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2);
impl_spim!(SPI3, SPIM3, SPIM3);
impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_spis!(SPI2, SPIS2, SPIM2_SPIS2_SPI2);
impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
impl_pwm!(PWM0, PWM0, PWM0);
impl_pwm!(PWM1, PWM1, PWM1);
impl_pwm!(PWM2, PWM2, PWM2);
@ -273,14 +286,16 @@ impl_ppi_channel!(PPI_CH29, 29 => static);
impl_ppi_channel!(PPI_CH30, 30 => static);
impl_ppi_channel!(PPI_CH31, 31 => static);
impl_saadc_input!(P0_02, ANALOGINPUT0);
impl_saadc_input!(P0_03, ANALOGINPUT1);
impl_saadc_input!(P0_04, ANALOGINPUT2);
impl_saadc_input!(P0_05, ANALOGINPUT3);
impl_saadc_input!(P0_28, ANALOGINPUT4);
impl_saadc_input!(P0_29, ANALOGINPUT5);
impl_saadc_input!(P0_30, ANALOGINPUT6);
impl_saadc_input!(P0_31, ANALOGINPUT7);
impl_saadc_input!(P0_02, ANALOG_INPUT0);
impl_saadc_input!(P0_03, ANALOG_INPUT1);
impl_saadc_input!(P0_04, ANALOG_INPUT2);
impl_saadc_input!(P0_05, ANALOG_INPUT3);
impl_saadc_input!(P0_28, ANALOG_INPUT4);
impl_saadc_input!(P0_29, ANALOG_INPUT5);
impl_saadc_input!(P0_30, ANALOG_INPUT6);
impl_saadc_input!(P0_31, ANALOG_INPUT7);
impl_i2s!(I2S, I2S, I2S);
pub mod irqs {
use embassy_cortex_m::interrupt::_export::declare;
@ -322,7 +337,6 @@ pub mod irqs {
declare!(PWM2);
declare!(SPIM2_SPIS2_SPI2);
declare!(RTC2);
declare!(I2S);
declare!(FPU);
declare!(USBD);
declare!(UARTE1);
@ -330,4 +344,5 @@ pub mod irqs {
declare!(CRYPTOCELL);
declare!(PWM3);
declare!(SPIM3);
declare!(I2S);
}

View file

@ -1,3 +1,4 @@
/// Peripheral Access Crate
#[allow(unused_imports)]
#[rustfmt::skip]
pub mod pac {
@ -213,6 +214,8 @@ pub mod pac {
pub const EASY_DMA_SIZE: usize = (1 << 16) - 1;
pub const FORCE_COPY_BUFFER_SIZE: usize = 1024;
pub const FLASH_SIZE: usize = 1024 * 1024;
embassy_hal_common::peripherals! {
// USB
USBD,
@ -224,6 +227,9 @@ embassy_hal_common::peripherals! {
// WDT
WDT,
// NVMC
NVMC,
// UARTE, TWI & SPI
UARTETWISPI0,
UARTETWISPI1,
@ -361,11 +367,21 @@ impl_spim!(UARTETWISPI1, SPIM1, SERIAL1);
impl_spim!(UARTETWISPI2, SPIM2, SERIAL2);
impl_spim!(UARTETWISPI3, SPIM3, SERIAL3);
impl_spis!(UARTETWISPI0, SPIS0, SERIAL0);
impl_spis!(UARTETWISPI1, SPIS1, SERIAL1);
impl_spis!(UARTETWISPI2, SPIS2, SERIAL2);
impl_spis!(UARTETWISPI3, SPIS3, SERIAL3);
impl_twim!(UARTETWISPI0, TWIM0, SERIAL0);
impl_twim!(UARTETWISPI1, TWIM1, SERIAL1);
impl_twim!(UARTETWISPI2, TWIM2, SERIAL2);
impl_twim!(UARTETWISPI3, TWIM3, SERIAL3);
impl_twis!(UARTETWISPI0, TWIS0, SERIAL0);
impl_twis!(UARTETWISPI1, TWIS1, SERIAL1);
impl_twis!(UARTETWISPI2, TWIS2, SERIAL2);
impl_twis!(UARTETWISPI3, TWIS3, SERIAL3);
impl_pwm!(PWM0, PWM0, PWM0);
impl_pwm!(PWM1, PWM1, PWM1);
impl_pwm!(PWM2, PWM2, PWM2);
@ -458,14 +474,14 @@ impl_ppi_channel!(PPI_CH29, 29 => configurable);
impl_ppi_channel!(PPI_CH30, 30 => configurable);
impl_ppi_channel!(PPI_CH31, 31 => configurable);
impl_saadc_input!(P0_13, ANALOGINPUT0);
impl_saadc_input!(P0_14, ANALOGINPUT1);
impl_saadc_input!(P0_15, ANALOGINPUT2);
impl_saadc_input!(P0_16, ANALOGINPUT3);
impl_saadc_input!(P0_17, ANALOGINPUT4);
impl_saadc_input!(P0_18, ANALOGINPUT5);
impl_saadc_input!(P0_19, ANALOGINPUT6);
impl_saadc_input!(P0_20, ANALOGINPUT7);
impl_saadc_input!(P0_13, ANALOG_INPUT0);
impl_saadc_input!(P0_14, ANALOG_INPUT1);
impl_saadc_input!(P0_15, ANALOG_INPUT2);
impl_saadc_input!(P0_16, ANALOG_INPUT3);
impl_saadc_input!(P0_17, ANALOG_INPUT4);
impl_saadc_input!(P0_18, ANALOG_INPUT5);
impl_saadc_input!(P0_19, ANALOG_INPUT6);
impl_saadc_input!(P0_20, ANALOG_INPUT7);
pub mod irqs {
use embassy_cortex_m::interrupt::_export::declare;

View file

@ -1,3 +1,4 @@
/// Peripheral Access Crate
#[allow(unused_imports)]
#[rustfmt::skip]
pub mod pac {
@ -104,6 +105,8 @@ pub mod pac {
pub const EASY_DMA_SIZE: usize = (1 << 16) - 1;
pub const FORCE_COPY_BUFFER_SIZE: usize = 1024;
pub const FLASH_SIZE: usize = 256 * 1024;
embassy_hal_common::peripherals! {
// RTC
RTC0,
@ -112,6 +115,9 @@ embassy_hal_common::peripherals! {
// WDT
WDT,
// NVMC
NVMC,
// UARTE, TWI & SPI
UARTETWISPI0,
UARTETWISPI1,
@ -238,7 +244,9 @@ embassy_hal_common::peripherals! {
impl_uarte!(UARTETWISPI0, UARTE0, SERIAL0);
impl_spim!(UARTETWISPI0, SPIM0, SERIAL0);
impl_spis!(UARTETWISPI0, SPIS0, SERIAL0);
impl_twim!(UARTETWISPI0, TWIM0, SERIAL0);
impl_twis!(UARTETWISPI0, TWIS0, SERIAL0);
impl_timer!(TIMER0, TIMER0, TIMER0);
impl_timer!(TIMER1, TIMER1, TIMER1);

View file

@ -1,3 +1,4 @@
/// Peripheral Access Crate
#[allow(unused_imports)]
#[rustfmt::skip]
pub mod pac {
@ -164,6 +165,8 @@ pub mod pac {
pub const EASY_DMA_SIZE: usize = (1 << 13) - 1;
pub const FORCE_COPY_BUFFER_SIZE: usize = 1024;
pub const FLASH_SIZE: usize = 1024 * 1024;
embassy_hal_common::peripherals! {
// RTC
RTC0,
@ -172,6 +175,9 @@ embassy_hal_common::peripherals! {
// WDT
WDT,
// NVMC
NVMC,
// UARTE, TWI & SPI
UARTETWISPI0,
UARTETWISPI1,
@ -260,6 +266,9 @@ embassy_hal_common::peripherals! {
P0_29,
P0_30,
P0_31,
// PDM
PDM,
}
impl_uarte!(UARTETWISPI0, UARTE0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0);
@ -272,11 +281,21 @@ impl_spim!(UARTETWISPI1, SPIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1);
impl_spim!(UARTETWISPI2, SPIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2);
impl_spim!(UARTETWISPI3, SPIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3);
impl_spis!(UARTETWISPI0, SPIS0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0);
impl_spis!(UARTETWISPI1, SPIS1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1);
impl_spis!(UARTETWISPI2, SPIS2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2);
impl_spis!(UARTETWISPI3, SPIS3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3);
impl_twim!(UARTETWISPI0, TWIM0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0);
impl_twim!(UARTETWISPI1, TWIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1);
impl_twim!(UARTETWISPI2, TWIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2);
impl_twim!(UARTETWISPI3, TWIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3);
impl_twis!(UARTETWISPI0, TWIS0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0);
impl_twis!(UARTETWISPI1, TWIS1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1);
impl_twis!(UARTETWISPI2, TWIS2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2);
impl_twis!(UARTETWISPI3, TWIS3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3);
impl_pwm!(PWM0, PWM0, PWM0);
impl_pwm!(PWM1, PWM1, PWM1);
impl_pwm!(PWM2, PWM2, PWM2);
@ -336,14 +355,14 @@ impl_ppi_channel!(PPI_CH13, 13 => configurable);
impl_ppi_channel!(PPI_CH14, 14 => configurable);
impl_ppi_channel!(PPI_CH15, 15 => configurable);
impl_saadc_input!(P0_13, ANALOGINPUT0);
impl_saadc_input!(P0_14, ANALOGINPUT1);
impl_saadc_input!(P0_15, ANALOGINPUT2);
impl_saadc_input!(P0_16, ANALOGINPUT3);
impl_saadc_input!(P0_17, ANALOGINPUT4);
impl_saadc_input!(P0_18, ANALOGINPUT5);
impl_saadc_input!(P0_19, ANALOGINPUT6);
impl_saadc_input!(P0_20, ANALOGINPUT7);
impl_saadc_input!(P0_13, ANALOG_INPUT0);
impl_saadc_input!(P0_14, ANALOG_INPUT1);
impl_saadc_input!(P0_15, ANALOG_INPUT2);
impl_saadc_input!(P0_16, ANALOG_INPUT3);
impl_saadc_input!(P0_17, ANALOG_INPUT4);
impl_saadc_input!(P0_18, ANALOG_INPUT5);
impl_saadc_input!(P0_19, ANALOG_INPUT6);
impl_saadc_input!(P0_20, ANALOG_INPUT7);
pub mod irqs {
use embassy_cortex_m::interrupt::_export::declare;

View file

@ -1,4 +1,4 @@
//! General purpose input/output for nRF.
//! General purpose input/output (GPIO) driver.
#![macro_use]
use core::convert::Infallible;
@ -70,7 +70,7 @@ impl<'d, T: Pin> Input<'d, T> {
}
/// Digital input or output level.
#[derive(Debug, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Level {
/// Logical low.
@ -88,9 +88,9 @@ impl From<bool> for Level {
}
}
impl Into<bool> for Level {
fn into(self) -> bool {
match self {
impl From<Level> for bool {
fn from(level: Level) -> bool {
match level {
Level::Low => false,
Level::High => true,
}

View file

@ -1,8 +1,10 @@
//! GPIO task/event (GPIOTE) driver.
use core::convert::Infallible;
use core::future::{poll_fn, Future};
use core::task::{Context, Poll};
use embassy_hal_common::{impl_peripheral, Peripheral, PeripheralRef};
use embassy_hal_common::{impl_peripheral, into_ref, Peripheral, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
use crate::gpio::sealed::Pin as _;
@ -11,29 +13,38 @@ use crate::interrupt::{Interrupt, InterruptExt};
use crate::ppi::{Event, Task};
use crate::{interrupt, pac, peripherals};
pub const CHANNEL_COUNT: usize = 8;
/// Amount of GPIOTE channels in the chip.
const CHANNEL_COUNT: usize = 8;
#[cfg(any(feature = "nrf52833", feature = "nrf52840"))]
pub const PIN_COUNT: usize = 48;
const PIN_COUNT: usize = 48;
#[cfg(not(any(feature = "nrf52833", feature = "nrf52840")))]
pub const PIN_COUNT: usize = 32;
const PIN_COUNT: usize = 32;
#[allow(clippy::declare_interior_mutable_const)]
const NEW_AW: AtomicWaker = AtomicWaker::new();
static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [NEW_AW; CHANNEL_COUNT];
static PORT_WAKERS: [AtomicWaker; PIN_COUNT] = [NEW_AW; PIN_COUNT];
/// Polarity for listening to events for GPIOTE input channels.
pub enum InputChannelPolarity {
/// Don't listen for any pin changes.
None,
/// Listen for high to low changes.
HiToLo,
/// Listen for low to high changes.
LoToHi,
/// Listen for any change, either low to high or high to low.
Toggle,
}
/// Polarity of the `task out` operation.
/// Polarity of the OUT task operation for GPIOTE output channels.
pub enum OutputChannelPolarity {
/// Set the pin high.
Set,
/// Set the pin low.
Clear,
/// Toggle the pin.
Toggle,
}
@ -148,7 +159,7 @@ impl Iterator for BitIter {
/// GPIOTE channel driver in input mode
pub struct InputChannel<'d, C: Channel, T: GpioPin> {
ch: C,
ch: PeripheralRef<'d, C>,
pin: Input<'d, T>,
}
@ -162,7 +173,10 @@ impl<'d, C: Channel, T: GpioPin> Drop for InputChannel<'d, C, T> {
}
impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> {
pub fn new(ch: C, pin: Input<'d, T>, polarity: InputChannelPolarity) -> Self {
/// Create a new GPIOTE input channel driver.
pub fn new(ch: impl Peripheral<P = C> + 'd, pin: Input<'d, T>, polarity: InputChannelPolarity) -> Self {
into_ref!(ch);
let g = regs();
let num = ch.number();
@ -186,6 +200,7 @@ impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> {
InputChannel { ch, pin }
}
/// Asynchronously wait for an event in this channel.
pub async fn wait(&self) {
let g = regs();
let num = self.ch.number();
@ -215,7 +230,7 @@ impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> {
/// GPIOTE channel driver in output mode
pub struct OutputChannel<'d, C: Channel, T: GpioPin> {
ch: C,
ch: PeripheralRef<'d, C>,
_pin: Output<'d, T>,
}
@ -229,7 +244,9 @@ impl<'d, C: Channel, T: GpioPin> Drop for OutputChannel<'d, C, T> {
}
impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> {
pub fn new(ch: C, pin: Output<'d, T>, polarity: OutputChannelPolarity) -> Self {
/// Create a new GPIOTE output channel driver.
pub fn new(ch: impl Peripheral<P = C> + 'd, pin: Output<'d, T>, polarity: OutputChannelPolarity) -> Self {
into_ref!(ch);
let g = regs();
let num = ch.number();
@ -255,20 +272,20 @@ impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> {
OutputChannel { ch, _pin: pin }
}
/// Triggers `task out` (as configured with task_out_polarity, defaults to Toggle).
/// Triggers the OUT task (does the action as configured with task_out_polarity, defaults to Toggle).
pub fn out(&self) {
let g = regs();
g.tasks_out[self.ch.number()].write(|w| unsafe { w.bits(1) });
}
/// Triggers `task set` (set associated pin high).
/// Triggers the SET task (set associated pin high).
#[cfg(not(feature = "nrf51"))]
pub fn set(&self) {
let g = regs();
g.tasks_set[self.ch.number()].write(|w| unsafe { w.bits(1) });
}
/// Triggers `task clear` (set associated pin low).
/// Triggers the CLEAR task (set associated pin low).
#[cfg(not(feature = "nrf51"))]
pub fn clear(&self) {
let g = regs();
@ -333,48 +350,58 @@ impl<'a> Future for PortInputFuture<'a> {
}
impl<'d, T: GpioPin> Input<'d, T> {
/// Wait until the pin is high. If it is already high, return immediately.
pub async fn wait_for_high(&mut self) {
self.pin.wait_for_high().await
}
/// Wait until the pin is low. If it is already low, return immediately.
pub async fn wait_for_low(&mut self) {
self.pin.wait_for_low().await
}
/// Wait for the pin to undergo a transition from low to high.
pub async fn wait_for_rising_edge(&mut self) {
self.pin.wait_for_rising_edge().await
}
/// Wait for the pin to undergo a transition from high to low.
pub async fn wait_for_falling_edge(&mut self) {
self.pin.wait_for_falling_edge().await
}
/// Wait for the pin to undergo any transition, i.e low to high OR high to low.
pub async fn wait_for_any_edge(&mut self) {
self.pin.wait_for_any_edge().await
}
}
impl<'d, T: GpioPin> Flex<'d, T> {
/// Wait until the pin is high. If it is already high, return immediately.
pub async fn wait_for_high(&mut self) {
self.pin.conf().modify(|_, w| w.sense().high());
PortInputFuture::new(&mut self.pin).await
}
/// Wait until the pin is low. If it is already low, return immediately.
pub async fn wait_for_low(&mut self) {
self.pin.conf().modify(|_, w| w.sense().low());
PortInputFuture::new(&mut self.pin).await
}
/// Wait for the pin to undergo a transition from low to high.
pub async fn wait_for_rising_edge(&mut self) {
self.wait_for_low().await;
self.wait_for_high().await;
}
/// Wait for the pin to undergo a transition from high to low.
pub async fn wait_for_falling_edge(&mut self) {
self.wait_for_high().await;
self.wait_for_low().await;
}
/// Wait for the pin to undergo any transition, i.e low to high OR high to low.
pub async fn wait_for_any_edge(&mut self) {
if self.is_high() {
self.pin.conf().modify(|_, w| w.sense().low());
@ -391,8 +418,17 @@ mod sealed {
pub trait Channel {}
}
/// GPIOTE channel trait.
///
/// Implemented by all GPIOTE channels.
pub trait Channel: sealed::Channel + Sized {
/// Get the channel number.
fn number(&self) -> usize;
/// Convert this channel to a type-erased `AnyChannel`.
///
/// This allows using several channels in situations that might require
/// them to be the same type, like putting them in an array.
fn degrade(self) -> AnyChannel {
AnyChannel {
number: self.number() as u8,
@ -400,6 +436,12 @@ pub trait Channel: sealed::Channel + Sized {
}
}
/// Type-erased channel.
///
/// Obtained by calling `Channel::degrade`.
///
/// This allows using several channels in situations that might require
/// them to be the same type, like putting them in an array.
pub struct AnyChannel {
number: u8,
}
@ -470,71 +512,49 @@ mod eh1 {
#[cfg(all(feature = "unstable-traits", feature = "nightly"))]
mod eha {
use futures::FutureExt;
use super::*;
impl<'d, T: GpioPin> embedded_hal_async::digital::Wait for Input<'d, T> {
type WaitForHighFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn wait_for_high<'a>(&'a mut self) -> Self::WaitForHighFuture<'a> {
self.wait_for_high().map(Ok)
async fn wait_for_high(&mut self) -> Result<(), Self::Error> {
Ok(self.wait_for_high().await)
}
type WaitForLowFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn wait_for_low<'a>(&'a mut self) -> Self::WaitForLowFuture<'a> {
self.wait_for_low().map(Ok)
async fn wait_for_low(&mut self) -> Result<(), Self::Error> {
Ok(self.wait_for_low().await)
}
type WaitForRisingEdgeFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn wait_for_rising_edge<'a>(&'a mut self) -> Self::WaitForRisingEdgeFuture<'a> {
self.wait_for_rising_edge().map(Ok)
async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> {
Ok(self.wait_for_rising_edge().await)
}
type WaitForFallingEdgeFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn wait_for_falling_edge<'a>(&'a mut self) -> Self::WaitForFallingEdgeFuture<'a> {
self.wait_for_falling_edge().map(Ok)
async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> {
Ok(self.wait_for_falling_edge().await)
}
type WaitForAnyEdgeFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn wait_for_any_edge<'a>(&'a mut self) -> Self::WaitForAnyEdgeFuture<'a> {
self.wait_for_any_edge().map(Ok)
async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> {
Ok(self.wait_for_any_edge().await)
}
}
impl<'d, T: GpioPin> embedded_hal_async::digital::Wait for Flex<'d, T> {
type WaitForHighFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn wait_for_high<'a>(&'a mut self) -> Self::WaitForHighFuture<'a> {
self.wait_for_high().map(Ok)
async fn wait_for_high(&mut self) -> Result<(), Self::Error> {
Ok(self.wait_for_high().await)
}
type WaitForLowFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn wait_for_low<'a>(&'a mut self) -> Self::WaitForLowFuture<'a> {
self.wait_for_low().map(Ok)
async fn wait_for_low(&mut self) -> Result<(), Self::Error> {
Ok(self.wait_for_low().await)
}
type WaitForRisingEdgeFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn wait_for_rising_edge<'a>(&'a mut self) -> Self::WaitForRisingEdgeFuture<'a> {
self.wait_for_rising_edge().map(Ok)
async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> {
Ok(self.wait_for_rising_edge().await)
}
type WaitForFallingEdgeFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn wait_for_falling_edge<'a>(&'a mut self) -> Self::WaitForFallingEdgeFuture<'a> {
self.wait_for_falling_edge().map(Ok)
async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> {
Ok(self.wait_for_falling_edge().await)
}
type WaitForAnyEdgeFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
fn wait_for_any_edge<'a>(&'a mut self) -> Self::WaitForAnyEdgeFuture<'a> {
self.wait_for_any_edge().map(Ok)
async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> {
Ok(self.wait_for_any_edge().await)
}
}
}

1192
embassy-nrf/src/i2s.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,49 +1,11 @@
//! # Embassy nRF HAL
//!
//! HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed.
//!
//! The Embassy nRF HAL targets the Nordic Semiconductor nRF family of hardware. The HAL implements both blocking and async APIs
//! for many peripherals. The benefit of using the async APIs is that the HAL takes care of waiting for peripherals to
//! complete operations in low power mod and handling interrupts, so that applications can focus on more important matters.
//!
//! ## EasyDMA considerations
//!
//! On nRF chips, peripherals can use the so called EasyDMA feature to offload the task of interacting
//! with peripherals. It takes care of sending/receiving data over a variety of bus protocols (TWI/I2C, UART, SPI).
//! However, EasyDMA requires the buffers used to transmit and receive data to reside in RAM. Unfortunately, Rust
//! slices will not always do so. The following example using the SPI peripheral shows a common situation where this might happen:
//!
//! ```no_run
//! // As we pass a slice to the function whose contents will not ever change,
//! // the compiler writes it into the flash and thus the pointer to it will
//! // reference static memory. Since EasyDMA requires slices to reside in RAM,
//! // this function call will fail.
//! let result = spim.write_from_ram(&[1, 2, 3]);
//! assert_eq!(result, Err(Error::DMABufferNotInDataMemory));
//!
//! // The data is still static and located in flash. However, since we are assigning
//! // it to a variable, the compiler will load it into memory. Passing a reference to the
//! // variable will yield a pointer that references dynamic memory, thus making EasyDMA happy.
//! // This function call succeeds.
//! let data = [1, 2, 3];
//! let result = spim.write_from_ram(&data);
//! assert!(result.is_ok());
//! ```
//!
//! Each peripheral struct which uses EasyDMA ([`Spim`](spim::Spim), [`Uarte`](uarte::Uarte), [`Twim`](twim::Twim)) has two variants of their mutating functions:
//! - Functions with the suffix (e.g. [`write_from_ram`](spim::Spim::write_from_ram), [`transfer_from_ram`](spim::Spim::transfer_from_ram)) will return an error if the passed slice does not reside in RAM.
//! - Functions without the suffix (e.g. [`write`](spim::Spim::write), [`transfer`](spim::Spim::transfer)) will check whether the data is in RAM and copy it into memory prior to transmission.
//!
//! Since copying incurs a overhead, you are given the option to choose from `_from_ram` variants which will
//! fail and notify you, or the more convenient versions without the suffix which are potentially a little bit
//! more inefficient. Be aware that this overhead is not only in terms of instruction count but also in terms of memory usage
//! as the methods without the suffix will be allocating a statically sized buffer (up to 512 bytes for the nRF52840).
//!
//! Note that the methods that read data like [`read`](spim::Spim::read) and [`transfer_in_place`](spim::Spim::transfer_in_place) do not have the corresponding `_from_ram` variants as
//! mutable slices always reside in RAM.
#![no_std]
#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))]
#![cfg_attr(
feature = "nightly",
feature(type_alias_impl_trait, async_fn_in_trait, impl_trait_projections)
)]
#![cfg_attr(feature = "nightly", allow(incomplete_features))]
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
#[cfg(not(any(
feature = "nrf51",
@ -74,8 +36,17 @@ pub mod buffered_uarte;
pub mod gpio;
#[cfg(feature = "gpiote")]
pub mod gpiote;
#[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))]
#[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))]
pub mod i2s;
pub mod nvmc;
#[cfg(any(
feature = "nrf52810",
feature = "nrf52811",
feature = "nrf52833",
feature = "nrf52840",
feature = "_nrf9160"
))]
pub mod pdm;
pub mod ppi;
#[cfg(not(any(feature = "nrf52805", feature = "nrf52820", feature = "_nrf5340-net")))]
pub mod pwm;
@ -88,10 +59,12 @@ pub mod rng;
#[cfg(not(any(feature = "nrf52820", feature = "_nrf5340-net")))]
pub mod saadc;
pub mod spim;
pub mod spis;
#[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))]
pub mod temp;
pub mod timer;
pub mod twim;
pub mod twis;
pub mod uarte;
#[cfg(any(
feature = "_nrf5340-app",
@ -259,5 +232,12 @@ pub fn init(config: config::Config) -> Peripherals {
#[cfg(feature = "_time-driver")]
time_driver::init(config.time_interrupt_priority);
// Disable UARTE (enabled by default for some reason)
#[cfg(feature = "_nrf9160")]
unsafe {
(*pac::UARTE0::ptr()).enable.write(|w| w.enable().disabled());
(*pac::UARTE1::ptr()).enable.write(|w| w.enable().disabled());
}
peripherals
}

View file

@ -1,4 +1,4 @@
//! Non-Volatile Memory Controller (NVMC) module.
//! Non-Volatile Memory Controller (NVMC, AKA internal flash) driver.
use core::{ptr, slice};
@ -10,8 +10,12 @@ use embedded_storage::nor_flash::{
use crate::peripherals::NVMC;
use crate::{pac, Peripheral};
#[cfg(not(feature = "_nrf5340-net"))]
/// Erase size of NVMC flash in bytes.
pub const PAGE_SIZE: usize = 4096;
#[cfg(feature = "_nrf5340-net")]
/// Erase size of NVMC flash in bytes.
pub const PAGE_SIZE: usize = 2048;
/// Size of NVMC flash in bytes.
pub const FLASH_SIZE: usize = crate::chip::FLASH_SIZE;
@ -55,6 +59,51 @@ impl<'d> Nvmc<'d> {
let p = Self::regs();
while p.ready.read().ready().is_busy() {}
}
#[cfg(not(any(feature = "_nrf9160", feature = "_nrf5340")))]
fn wait_ready_write(&mut self) {
self.wait_ready();
}
#[cfg(any(feature = "_nrf9160", feature = "_nrf5340"))]
fn wait_ready_write(&mut self) {
let p = Self::regs();
while p.readynext.read().readynext().is_busy() {}
}
#[cfg(not(any(feature = "_nrf9160", feature = "_nrf5340")))]
fn erase_page(&mut self, page_addr: u32) {
Self::regs().erasepage().write(|w| unsafe { w.bits(page_addr) });
}
#[cfg(any(feature = "_nrf9160", feature = "_nrf5340"))]
fn erase_page(&mut self, page_addr: u32) {
let first_page_word = page_addr as *mut u32;
unsafe {
first_page_word.write_volatile(0xFFFF_FFFF);
}
}
fn enable_erase(&self) {
#[cfg(not(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns")))]
Self::regs().config.write(|w| w.wen().een());
#[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns"))]
Self::regs().configns.write(|w| w.wen().een());
}
fn enable_read(&self) {
#[cfg(not(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns")))]
Self::regs().config.write(|w| w.wen().ren());
#[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns"))]
Self::regs().configns.write(|w| w.wen().ren());
}
fn enable_write(&self) {
#[cfg(not(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns")))]
Self::regs().config.write(|w| w.wen().wen());
#[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns"))]
Self::regs().configns.write(|w| w.wen().wen());
}
}
impl<'d> MultiwriteNorFlash for Nvmc<'d> {}
@ -93,17 +142,15 @@ impl<'d> NorFlash for Nvmc<'d> {
return Err(Error::Unaligned);
}
let p = Self::regs();
p.config.write(|w| w.wen().een());
self.enable_erase();
self.wait_ready();
for page in (from..to).step_by(PAGE_SIZE) {
p.erasepage().write(|w| unsafe { w.bits(page) });
for page_addr in (from..to).step_by(PAGE_SIZE) {
self.erase_page(page_addr);
self.wait_ready();
}
p.config.reset();
self.enable_read();
self.wait_ready();
Ok(())
@ -117,9 +164,7 @@ impl<'d> NorFlash for Nvmc<'d> {
return Err(Error::Unaligned);
}
let p = Self::regs();
p.config.write(|w| w.wen().wen());
self.enable_write();
self.wait_ready();
unsafe {
@ -129,11 +174,11 @@ impl<'d> NorFlash for Nvmc<'d> {
for i in 0..words {
let w = ptr::read_unaligned(p_src.add(i));
ptr::write_volatile(p_dst.add(i), w);
self.wait_ready();
self.wait_ready_write();
}
}
p.config.reset();
self.enable_read();
self.wait_ready();
Ok(())

254
embassy-nrf/src/pdm.rs Normal file
View file

@ -0,0 +1,254 @@
//! Pulse Density Modulation (PDM) mirophone driver.
use core::marker::PhantomData;
use core::sync::atomic::{compiler_fence, Ordering};
use core::task::Poll;
use embassy_hal_common::drop::OnDrop;
use embassy_hal_common::{into_ref, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
use futures::future::poll_fn;
use crate::chip::EASY_DMA_SIZE;
use crate::gpio::sealed::Pin;
use crate::gpio::{AnyPin, Pin as GpioPin};
use crate::interrupt::{self, InterruptExt};
use crate::peripherals::PDM;
use crate::{pac, Peripheral};
/// PDM microphone interface
pub struct Pdm<'d> {
irq: PeripheralRef<'d, interrupt::PDM>,
phantom: PhantomData<&'d PDM>,
}
/// PDM error.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Error {
/// Buffer is too long.
BufferTooLong,
/// Buffer is empty
BufferZeroLength,
/// PDM is not running
NotRunning,
}
static WAKER: AtomicWaker = AtomicWaker::new();
static DUMMY_BUFFER: [i16; 1] = [0; 1];
impl<'d> Pdm<'d> {
/// Create PDM driver
pub fn new(
pdm: impl Peripheral<P = PDM> + 'd,
irq: impl Peripheral<P = interrupt::PDM> + 'd,
clk: impl Peripheral<P = impl GpioPin> + 'd,
din: impl Peripheral<P = impl GpioPin> + 'd,
config: Config,
) -> Self {
into_ref!(clk, din);
Self::new_inner(pdm, irq, clk.map_into(), din.map_into(), config)
}
fn new_inner(
_pdm: impl Peripheral<P = PDM> + 'd,
irq: impl Peripheral<P = interrupt::PDM> + 'd,
clk: PeripheralRef<'d, AnyPin>,
din: PeripheralRef<'d, AnyPin>,
config: Config,
) -> Self {
into_ref!(irq);
let r = Self::regs();
// setup gpio pins
din.conf().write(|w| w.input().set_bit());
r.psel.din.write(|w| unsafe { w.bits(din.psel_bits()) });
clk.set_low();
clk.conf().write(|w| w.dir().output());
r.psel.clk.write(|w| unsafe { w.bits(clk.psel_bits()) });
// configure
// use default for
// - gain right
// - gain left
// - clk
// - ratio
r.mode.write(|w| {
w.edge().bit(config.edge == Edge::LeftRising);
w.operation().bit(config.operation_mode == OperationMode::Mono);
w
});
r.gainl.write(|w| w.gainl().default_gain());
r.gainr.write(|w| w.gainr().default_gain());
// IRQ
irq.disable();
irq.set_handler(|_| {
let r = Self::regs();
r.intenclr.write(|w| w.end().clear());
WAKER.wake();
});
irq.enable();
r.enable.write(|w| w.enable().set_bit());
Self {
phantom: PhantomData,
irq,
}
}
/// Start sampling microphon data into a dummy buffer
/// Usefull to start the microphon and keep it active between recording samples
pub async fn start(&mut self) {
let r = Self::regs();
// start dummy sampling because microphon needs some setup time
r.sample
.ptr
.write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) });
r.sample
.maxcnt
.write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) });
r.tasks_start.write(|w| w.tasks_start().set_bit());
}
/// Stop sampling microphon data inta a dummy buffer
pub async fn stop(&mut self) {
let r = Self::regs();
r.tasks_stop.write(|w| w.tasks_stop().set_bit());
r.events_started.reset();
}
/// Sample data into the given buffer.
pub async fn sample(&mut self, buffer: &mut [i16]) -> Result<(), Error> {
if buffer.len() == 0 {
return Err(Error::BufferZeroLength);
}
if buffer.len() > EASY_DMA_SIZE {
return Err(Error::BufferTooLong);
}
let r = Self::regs();
if r.events_started.read().events_started().bit_is_clear() {
return Err(Error::NotRunning);
}
let drop = OnDrop::new(move || {
r.intenclr.write(|w| w.end().clear());
r.events_stopped.reset();
// reset to dummy buffer
r.sample
.ptr
.write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) });
r.sample
.maxcnt
.write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) });
while r.events_stopped.read().bits() == 0 {}
});
// setup user buffer
let ptr = buffer.as_ptr();
let len = buffer.len();
r.sample.ptr.write(|w| unsafe { w.sampleptr().bits(ptr as u32) });
r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(len as _) });
// wait till the current sample is finished and the user buffer sample is started
Self::wait_for_sample().await;
// reset the buffer back to the dummy buffer
r.sample
.ptr
.write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) });
r.sample
.maxcnt
.write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) });
// wait till the user buffer is sampled
Self::wait_for_sample().await;
drop.defuse();
Ok(())
}
async fn wait_for_sample() {
let r = Self::regs();
r.events_end.reset();
r.intenset.write(|w| w.end().set());
compiler_fence(Ordering::SeqCst);
poll_fn(|cx| {
WAKER.register(cx.waker());
if r.events_end.read().events_end().bit_is_set() {
return Poll::Ready(());
}
Poll::Pending
})
.await;
compiler_fence(Ordering::SeqCst);
}
fn regs() -> &'static pac::pdm::RegisterBlock {
unsafe { &*pac::PDM::ptr() }
}
}
/// PDM microphone driver Config
pub struct Config {
/// Use stero or mono operation
pub operation_mode: OperationMode,
/// On which edge the left channel should be samples
pub edge: Edge,
}
impl Default for Config {
fn default() -> Self {
Self {
operation_mode: OperationMode::Mono,
edge: Edge::LeftFalling,
}
}
}
/// PDM operation mode.
#[derive(PartialEq)]
pub enum OperationMode {
/// Mono (1 channel)
Mono,
/// Stereo (2 channels)
Stereo,
}
/// PDM edge polarity
#[derive(PartialEq)]
pub enum Edge {
/// Left edge is rising
LeftRising,
/// Left edge is falling
LeftFalling,
}
impl<'d> Drop for Pdm<'d> {
fn drop(&mut self) {
let r = Self::regs();
r.tasks_stop.write(|w| w.tasks_stop().set_bit());
self.irq.disable();
r.enable.write(|w| w.enable().disabled());
r.psel.din.reset();
r.psel.clk.reset();
}
}

View file

@ -11,12 +11,14 @@ fn regs() -> &'static pac::dppic::RegisterBlock {
}
impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> {
/// Configure PPI channel to trigger `task` on `event`.
pub fn new_one_to_one(ch: impl Peripheral<P = C> + 'd, event: Event, task: Task) -> Self {
Ppi::new_many_to_many(ch, [event], [task])
}
}
impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> {
/// Configure PPI channel to trigger both `task1` and `task2` on `event`.
pub fn new_one_to_two(ch: impl Peripheral<P = C> + 'd, event: Event, task1: Task, task2: Task) -> Self {
Ppi::new_many_to_many(ch, [event], [task1, task2])
}
@ -25,6 +27,7 @@ impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> {
impl<'d, C: ConfigurableChannel, const EVENT_COUNT: usize, const TASK_COUNT: usize>
Ppi<'d, C, EVENT_COUNT, TASK_COUNT>
{
/// Configure a DPPI channel to trigger all `tasks` when any of the `events` fires.
pub fn new_many_to_many(
ch: impl Peripheral<P = C> + 'd,
events: [Event; EVENT_COUNT],

View file

@ -1,6 +1,6 @@
#![macro_use]
//! HAL interface for the PPI and DPPI peripheral.
//! Programmable Peripheral Interconnect (PPI/DPPI) driver.
//!
//! The (Distributed) Programmable Peripheral Interconnect interface allows for an autonomous interoperability
//! between peripherals through their events and tasks. There are fixed PPI channels and fully

View file

@ -48,7 +48,7 @@ impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> {
#[cfg(not(feature = "nrf51"))] // Not for nrf51 because of the fork task
impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> {
/// Configure PPI channel to trigger `task1` and `task2` on `event`.
/// Configure PPI channel to trigger both `task1` and `task2` on `event`.
pub fn new_one_to_two(ch: impl Peripheral<P = C> + 'd, event: Event, task1: Task, task2: Task) -> Self {
into_ref!(ch);

View file

@ -1,3 +1,5 @@
//! Pulse Width Modulation (PWM) driver.
#![macro_use]
use core::sync::atomic::{compiler_fence, Ordering};
@ -32,6 +34,7 @@ pub struct SequencePwm<'d, T: Instance> {
ch3: Option<PeripheralRef<'d, AnyPin>>,
}
/// PWM error
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
@ -41,7 +44,7 @@ pub enum Error {
/// Min Sequence count is 1
SequenceTimesAtLeastOne,
/// EasyDMA can only read from data memory, read only buffers in flash will fail.
DMABufferNotInDataMemory,
BufferNotInRAM,
}
const MAX_SEQUENCE_LEN: usize = 32767;
@ -358,6 +361,7 @@ pub struct Sequence<'s> {
}
impl<'s> Sequence<'s> {
/// Create a new `Sequence`
pub fn new(words: &'s [u16], config: SequenceConfig) -> Self {
Self { words, config }
}
@ -367,7 +371,7 @@ impl<'s> Sequence<'s> {
/// Takes at one sequence along with its configuration.
#[non_exhaustive]
pub struct SingleSequencer<'d, 's, T: Instance> {
pub sequencer: Sequencer<'d, 's, T>,
sequencer: Sequencer<'d, 's, T>,
}
impl<'d, 's, T: Instance> SingleSequencer<'d, 's, T> {
@ -428,8 +432,8 @@ impl<'d, 's, T: Instance> Sequencer<'d, 's, T> {
let sequence0 = &self.sequence0;
let alt_sequence = self.sequence1.as_ref().unwrap_or(&self.sequence0);
slice_in_ram_or(sequence0.words, Error::DMABufferNotInDataMemory)?;
slice_in_ram_or(alt_sequence.words, Error::DMABufferNotInDataMemory)?;
slice_in_ram_or(sequence0.words, Error::BufferNotInRAM)?;
slice_in_ram_or(alt_sequence.words, Error::BufferNotInRAM)?;
if sequence0.words.len() > MAX_SEQUENCE_LEN || alt_sequence.words.len() > MAX_SEQUENCE_LEN {
return Err(Error::SequenceTooLong);
@ -536,13 +540,21 @@ pub enum SequenceMode {
/// PWM Base clock is system clock (16MHz) divided by prescaler
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Prescaler {
/// Divide by 1
Div1,
/// Divide by 2
Div2,
/// Divide by 4
Div4,
/// Divide by 8
Div8,
/// Divide by 16
Div16,
/// Divide by 32
Div32,
/// Divide by 64
Div64,
/// Divide by 128
Div128,
}
@ -828,7 +840,9 @@ pub(crate) mod sealed {
}
}
/// PWM peripheral instance.
pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static {
/// Interrupt for this peripheral.
type Interrupt: Interrupt;
}

View file

@ -1,4 +1,4 @@
//! Quadrature decoder interface
//! Quadrature decoder (QDEC) driver.
use core::future::poll_fn;
use core::task::Poll;
@ -12,17 +12,23 @@ use crate::interrupt::InterruptExt;
use crate::peripherals::QDEC;
use crate::{interrupt, pac, Peripheral};
/// Quadrature decoder
/// Quadrature decoder driver.
pub struct Qdec<'d> {
_p: PeripheralRef<'d, QDEC>,
}
/// QDEC config
#[non_exhaustive]
pub struct Config {
/// Number of samples
pub num_samples: NumSamples,
/// Sample period
pub period: SamplePeriod,
/// Set LED output pin polarity
pub led_polarity: LedPolarity,
/// Enable/disable input debounce filters
pub debounce: bool,
/// Time period the LED is switched ON prior to sampling (0..511 us).
pub led_pre_usecs: u16,
}
@ -41,6 +47,7 @@ impl Default for Config {
static WAKER: AtomicWaker = AtomicWaker::new();
impl<'d> Qdec<'d> {
/// Create a new QDEC.
pub fn new(
qdec: impl Peripheral<P = QDEC> + 'd,
irq: impl Peripheral<P = interrupt::QDEC> + 'd,
@ -52,6 +59,7 @@ impl<'d> Qdec<'d> {
Self::new_inner(qdec, irq, a.map_into(), b.map_into(), None, config)
}
/// Create a new QDEC, with a pin for LED output.
pub fn new_with_led(
qdec: impl Peripheral<P = QDEC> + 'd,
irq: impl Peripheral<P = interrupt::QDEC> + 'd,
@ -170,36 +178,61 @@ impl<'d> Qdec<'d> {
}
}
/// Sample period
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum SamplePeriod {
/// 128 us
_128us,
/// 256 us
_256us,
/// 512 us
_512us,
/// 1024 us
_1024us,
/// 2048 us
_2048us,
/// 4096 us
_4096us,
/// 8192 us
_8192us,
/// 16384 us
_16384us,
/// 32 ms
_32ms,
/// 65 ms
_65ms,
/// 131 ms
_131ms,
}
/// Number of samples taken.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum NumSamples {
/// 10 samples
_10smpl,
/// 40 samples
_40smpl,
/// 80 samples
_80smpl,
/// 120 samples
_120smpl,
/// 160 samples
_160smpl,
/// 200 samples
_200smpl,
/// 240 samples
_240smpl,
/// 280 samples
_280smpl,
/// 1 sample
_1smpl,
}
/// LED polarity
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum LedPolarity {
/// Active high (a high output turns on the LED).
ActiveHigh,
/// Active low (a low output turns on the LED).
ActiveLow,
}

View file

@ -1,10 +1,12 @@
//! Quad Serial Peripheral Interface (QSPI) flash driver.
#![macro_use]
use core::future::poll_fn;
use core::ptr;
use core::task::Poll;
use embassy_hal_common::drop::DropBomb;
use embassy_hal_common::drop::OnDrop;
use embassy_hal_common::{into_ref, PeripheralRef};
use crate::gpio::{self, Pin as GpioPin};
@ -15,6 +17,7 @@ pub use crate::pac::qspi::ifconfig0::{
pub use crate::pac::qspi::ifconfig1::SPIMODE_A as SpiMode;
use crate::{pac, Peripheral};
/// Deep power-down config.
pub struct DeepPowerDownConfig {
/// Time required for entering DPM, in units of 16us
pub enter_time: u16,
@ -22,37 +25,62 @@ pub struct DeepPowerDownConfig {
pub exit_time: u16,
}
/// QSPI bus frequency.
pub enum Frequency {
/// 32 Mhz
M32 = 0,
/// 16 Mhz
M16 = 1,
/// 10.7 Mhz
M10_7 = 2,
/// 8 Mhz
M8 = 3,
/// 6.4 Mhz
M6_4 = 4,
/// 5.3 Mhz
M5_3 = 5,
/// 4.6 Mhz
M4_6 = 6,
/// 4 Mhz
M4 = 7,
/// 3.6 Mhz
M3_6 = 8,
/// 3.2 Mhz
M3_2 = 9,
/// 2.9 Mhz
M2_9 = 10,
/// 2.7 Mhz
M2_7 = 11,
/// 2.5 Mhz
M2_5 = 12,
/// 2.3 Mhz
M2_3 = 13,
/// 2.1 Mhz
M2_1 = 14,
/// 2 Mhz
M2 = 15,
}
/// QSPI config.
#[non_exhaustive]
pub struct Config {
/// XIP offset.
pub xip_offset: u32,
/// Opcode used for read operations.
pub read_opcode: ReadOpcode,
/// Opcode used for write operations.
pub write_opcode: WriteOpcode,
/// Page size for write operations.
pub write_page_size: WritePageSize,
/// Configuration for deep power down. If None, deep power down is disabled.
pub deep_power_down: Option<DeepPowerDownConfig>,
/// QSPI bus frequency.
pub frequency: Frequency,
/// Value is specified in number of 16 MHz periods (62.5 ns)
pub sck_delay: u8,
/// Whether data is captured on the clock rising edge and data is output on a falling edge (MODE0) or vice-versa (MODE3)
pub spi_mode: SpiMode,
/// Addressing mode (24-bit or 32-bit)
pub address_mode: AddressMode,
}
@ -72,20 +100,24 @@ impl Default for Config {
}
}
/// Error
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Error {
/// Operation address was out of bounds.
OutOfBounds,
// TODO add "not in data memory" error and check for it
}
/// QSPI flash driver.
pub struct Qspi<'d, T: Instance, const FLASH_SIZE: usize> {
irq: PeripheralRef<'d, T::Interrupt>,
dpm_enabled: bool,
}
impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> {
/// Create a new QSPI driver.
pub fn new(
_qspi: impl Peripheral<P = T> + 'd,
irq: impl Peripheral<P = T::Interrupt> + 'd,
@ -158,7 +190,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> {
// Enable it
r.enable.write(|w| w.enable().enabled());
let mut res = Self {
let res = Self {
dpm_enabled: config.deep_power_down.is_some(),
irq,
};
@ -168,7 +200,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> {
r.tasks_activate.write(|w| w.tasks_activate().bit(true));
res.blocking_wait_ready();
Self::blocking_wait_ready();
res
}
@ -183,8 +215,9 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> {
}
}
/// Do a custom QSPI instruction.
pub async fn custom_instruction(&mut self, opcode: u8, req: &[u8], resp: &mut [u8]) -> Result<(), Error> {
let bomb = DropBomb::new();
let ondrop = OnDrop::new(Self::blocking_wait_ready);
let len = core::cmp::max(req.len(), resp.len()) as u8;
self.custom_instruction_start(opcode, req, len)?;
@ -193,16 +226,17 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> {
self.custom_instruction_finish(resp)?;
bomb.defuse();
ondrop.defuse();
Ok(())
}
/// Do a custom QSPI instruction, blocking version.
pub fn blocking_custom_instruction(&mut self, opcode: u8, req: &[u8], resp: &mut [u8]) -> Result<(), Error> {
let len = core::cmp::max(req.len(), resp.len()) as u8;
self.custom_instruction_start(opcode, req, len)?;
self.blocking_wait_ready();
Self::blocking_wait_ready();
self.custom_instruction_finish(resp)?;
@ -278,7 +312,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> {
.await
}
fn blocking_wait_ready(&mut self) {
fn blocking_wait_ready() {
loop {
let r = T::regs();
if r.events_ready.read().bits() != 0 {
@ -346,54 +380,60 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> {
Ok(())
}
/// Read data from the flash memory.
pub async fn read(&mut self, address: usize, data: &mut [u8]) -> Result<(), Error> {
let bomb = DropBomb::new();
let ondrop = OnDrop::new(Self::blocking_wait_ready);
self.start_read(address, data)?;
self.wait_ready().await;
bomb.defuse();
ondrop.defuse();
Ok(())
}
/// Write data to the flash memory.
pub async fn write(&mut self, address: usize, data: &[u8]) -> Result<(), Error> {
let bomb = DropBomb::new();
let ondrop = OnDrop::new(Self::blocking_wait_ready);
self.start_write(address, data)?;
self.wait_ready().await;
bomb.defuse();
ondrop.defuse();
Ok(())
}
/// Erase a sector on the flash memory.
pub async fn erase(&mut self, address: usize) -> Result<(), Error> {
let bomb = DropBomb::new();
let ondrop = OnDrop::new(Self::blocking_wait_ready);
self.start_erase(address)?;
self.wait_ready().await;
bomb.defuse();
ondrop.defuse();
Ok(())
}
/// Read data from the flash memory, blocking version.
pub fn blocking_read(&mut self, address: usize, data: &mut [u8]) -> Result<(), Error> {
self.start_read(address, data)?;
self.blocking_wait_ready();
Self::blocking_wait_ready();
Ok(())
}
/// Write data to the flash memory, blocking version.
pub fn blocking_write(&mut self, address: usize, data: &[u8]) -> Result<(), Error> {
self.start_write(address, data)?;
self.blocking_wait_ready();
Self::blocking_wait_ready();
Ok(())
}
/// Erase a sector on the flash memory, blocking version.
pub fn blocking_erase(&mut self, address: usize) -> Result<(), Error> {
self.start_erase(address)?;
self.blocking_wait_ready();
Self::blocking_wait_ready();
Ok(())
}
}
@ -547,7 +587,9 @@ pub(crate) mod sealed {
}
}
/// QSPI peripheral instance.
pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static {
/// Interrupt for this peripheral.
type Interrupt: Interrupt;
}

Some files were not shown because too many files have changed in this diff Show more