Merge pull request #7 from embassy-rs/main

Merge latest from main repo
This commit is contained in:
Tyler 2023-11-26 09:11:02 -06:00 committed by GitHub
commit a306c43633
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
457 changed files with 9608 additions and 6042 deletions

View File

@ -12,9 +12,19 @@ export CARGO_TARGET_DIR=/ci/cache/target
# used when pointing stm32-metapac to a CI-built one. # used when pointing stm32-metapac to a CI-built one.
export CARGO_NET_GIT_FETCH_WITH_CLI=true export CARGO_NET_GIT_FETCH_WITH_CLI=true
# Restore lockfiles
if [ -f /ci/cache/lockfiles.tar ]; then
echo Restoring lockfiles...
tar xf /ci/cache/lockfiles.tar
fi
hashtime restore /ci/cache/filetime.json || true hashtime restore /ci/cache/filetime.json || true
hashtime save /ci/cache/filetime.json hashtime save /ci/cache/filetime.json
sed -i 's/channel.*/channel = "stable"/g' rust-toolchain.toml sed -i 's/channel.*/channel = "stable"/g' rust-toolchain.toml
./ci_stable.sh ./ci_stable.sh
# Save lockfiles
echo Saving lockfiles...
find . -type f -name Cargo.lock -exec tar -cf /ci/cache/lockfiles.tar '{}' \+

10
.github/ci/build.sh vendored
View File

@ -18,7 +18,17 @@ fi
# used when pointing stm32-metapac to a CI-built one. # used when pointing stm32-metapac to a CI-built one.
export CARGO_NET_GIT_FETCH_WITH_CLI=true export CARGO_NET_GIT_FETCH_WITH_CLI=true
# Restore lockfiles
if [ -f /ci/cache/lockfiles.tar ]; then
echo Restoring lockfiles...
tar xf /ci/cache/lockfiles.tar
fi
hashtime restore /ci/cache/filetime.json || true hashtime restore /ci/cache/filetime.json || true
hashtime save /ci/cache/filetime.json hashtime save /ci/cache/filetime.json
./ci.sh ./ci.sh
# Save lockfiles
echo Saving lockfiles...
find . -type f -name Cargo.lock -exec tar -cf /ci/cache/lockfiles.tar '{}' \+

3
.github/ci/test.sh vendored
View File

@ -15,6 +15,9 @@ export CARGO_NET_GIT_FETCH_WITH_CLI=true
hashtime restore /ci/cache/filetime.json || true hashtime restore /ci/cache/filetime.json || true
hashtime save /ci/cache/filetime.json hashtime save /ci/cache/filetime.json
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml --features nightly
cargo test --manifest-path ./embassy-sync/Cargo.toml cargo test --manifest-path ./embassy-sync/Cargo.toml
cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml
cargo test --manifest-path ./embassy-hal-internal/Cargo.toml cargo test --manifest-path ./embassy-hal-internal/Cargo.toml

View File

@ -62,9 +62,9 @@ async fn blink(pin: AnyPin) {
loop { loop {
// Timekeeping is globally available, no need to mess with hardware timers. // Timekeeping is globally available, no need to mess with hardware timers.
led.set_high(); led.set_high();
Timer::after(Duration::from_millis(150)).await; Timer::after_millis(150).await;
led.set_low(); led.set_low();
Timer::after(Duration::from_millis(150)).await; Timer::after_millis(150).await;
} }
} }

13
ci.sh
View File

@ -38,6 +38,7 @@ cargo batch \
--- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \ --- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \
--- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt,defmt-timestamp-uptime,tick-hz-32_768,generic-queue-8 \ --- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features nightly,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,proto-ipv4,medium-ethernet \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,igmp,medium-ethernet \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,nightly,dhcpv4-hostname \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,nightly,dhcpv4-hostname \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \
@ -110,6 +111,7 @@ cargo batch \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h753zi,defmt,exti,time-driver-any,unstable-traits,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h753zi,defmt,exti,time-driver-any,unstable-traits,time \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h735zg,defmt,exti,time-driver-any,unstable-traits,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h735zg,defmt,exti,time-driver-any,unstable-traits,time \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h755zi-cm7,defmt,exti,time-driver-any,unstable-traits,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h755zi-cm7,defmt,exti,time-driver-any,unstable-traits,time \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h725re,defmt,exti,time-driver-any,unstable-traits,time \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h7b3ai,defmt,exti,time-driver-any,unstable-traits,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h7b3ai,defmt,exti,time-driver-any,unstable-traits,time \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32l476vg,defmt,exti,time-driver-any,unstable-traits,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32l476vg,defmt,exti,time-driver-any,unstable-traits,time \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32l422cb,defmt,exti,time-driver-any,unstable-traits,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32l422cb,defmt,exti,time-driver-any,unstable-traits,time \
@ -192,9 +194,13 @@ cargo batch \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g071rb --out-dir out/tests/stm32g071rb \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g071rb --out-dir out/tests/stm32g071rb \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32c031c6 --out-dir out/tests/stm32c031c6 \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32c031c6 --out-dir out/tests/stm32c031c6 \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi --out-dir out/tests/stm32h755zi \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi --out-dir out/tests/stm32h755zi \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h753zi --out-dir out/tests/stm32h753zi \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7a3zi --out-dir out/tests/stm32h7a3zi \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --out-dir out/tests/stm32wb55rg \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --out-dir out/tests/stm32wb55rg \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h563zi --out-dir out/tests/stm32h563zi \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h563zi --out-dir out/tests/stm32h563zi \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585ai --out-dir out/tests/stm32u585ai \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585ai --out-dir out/tests/stm32u585ai \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u5a5zj --out-dir out/tests/stm32u5a5zj \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wba52cg --out-dir out/tests/stm32wba52cg \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l073rz --out-dir out/tests/stm32l073rz \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l073rz --out-dir out/tests/stm32l073rz \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l152re --out-dir out/tests/stm32l152re \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l152re --out-dir out/tests/stm32l152re \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4a6zg --out-dir out/tests/stm32l4a6zg \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4a6zg --out-dir out/tests/stm32l4a6zg \
@ -204,6 +210,7 @@ cargo batch \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f207zg --out-dir out/tests/stm32f207zg \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f207zg --out-dir out/tests/stm32f207zg \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f303ze --out-dir out/tests/stm32f303ze \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f303ze --out-dir out/tests/stm32f303ze \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l496zg --out-dir out/tests/stm32l496zg \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l496zg --out-dir out/tests/stm32l496zg \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wl55jc --out-dir out/tests/stm32wl55jc \
--- build --release --manifest-path tests/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/tests/rpi-pico \ --- build --release --manifest-path tests/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/tests/rpi-pico \
--- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --out-dir out/tests/nrf52840-dk \ --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --out-dir out/tests/nrf52840-dk \
--- build --release --manifest-path tests/riscv32/Cargo.toml --target riscv32imac-unknown-none-elf \ --- build --release --manifest-path tests/riscv32/Cargo.toml --target riscv32imac-unknown-none-elf \
@ -212,8 +219,14 @@ cargo batch \
rm out/tests/stm32wb55rg/wpan_mac rm out/tests/stm32wb55rg/wpan_mac
rm out/tests/stm32wb55rg/wpan_ble rm out/tests/stm32wb55rg/wpan_ble
# unstable, I think it's running out of RAM?
rm out/tests/stm32f207zg/eth rm out/tests/stm32f207zg/eth
# doesn't work, gives "noise error", no idea why. usart_dma does pass.
rm out/tests/stm32u5a5zj/usart
if [[ -z "${TELEPROBE_TOKEN-}" ]]; then if [[ -z "${TELEPROBE_TOKEN-}" ]]; then
echo No teleprobe token found, skipping running HIL tests echo No teleprobe token found, skipping running HIL tests
exit exit

View File

@ -1,6 +1,6 @@
#![no_std] #![no_std]
#![allow(incomplete_features)]
#![feature(async_fn_in_trait)] #![feature(async_fn_in_trait)]
#![allow(stable_features, unknown_lints, async_fn_in_trait)]
use core::slice; use core::slice;

View File

@ -11,10 +11,10 @@ log = ["dep:log"]
firmware-logs = [] firmware-logs = []
[dependencies] [dependencies]
embassy-time = { version = "0.1.4", path = "../embassy-time"} embassy-time = { version = "0.1.5", path = "../embassy-time"}
embassy-sync = { version = "0.3.0", path = "../embassy-sync"} embassy-sync = { version = "0.4.0", path = "../embassy-sync"}
embassy-futures = { version = "0.1.0", path = "../embassy-futures"} embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"} embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel"}
defmt = { version = "0.3", optional = true } defmt = { version = "0.3", optional = true }
log = { version = "0.4.17", optional = true } log = { version = "0.4.17", optional = true }

View File

@ -1,5 +1,5 @@
use embassy_futures::yield_now; use embassy_futures::yield_now;
use embassy_time::{Duration, Timer}; use embassy_time::Timer;
use embedded_hal_1::digital::OutputPin; use embedded_hal_1::digital::OutputPin;
use futures::FutureExt; use futures::FutureExt;
@ -51,9 +51,9 @@ where
pub async fn init(&mut self) { pub async fn init(&mut self) {
// Reset // Reset
self.pwr.set_low().unwrap(); self.pwr.set_low().unwrap();
Timer::after(Duration::from_millis(20)).await; Timer::after_millis(20).await;
self.pwr.set_high().unwrap(); self.pwr.set_high().unwrap();
Timer::after(Duration::from_millis(250)).await; Timer::after_millis(250).await;
while self while self
.read32_swapped(REG_BUS_TEST_RO) .read32_swapped(REG_BUS_TEST_RO)

View File

@ -1,10 +1,10 @@
use core::cmp::{max, min}; use core::cmp::{max, min};
use core::iter::zip;
use ch::driver::LinkState;
use embassy_net_driver_channel as ch; use embassy_net_driver_channel as ch;
use embassy_time::{Duration, Timer}; use embassy_net_driver_channel::driver::{HardwareAddress, LinkState};
use embassy_time::Timer;
pub use crate::bus::SpiBusCyw43;
use crate::consts::*; use crate::consts::*;
use crate::events::{Event, EventSubscriber, Events}; use crate::events::{Event, EventSubscriber, Events};
use crate::fmt::Bytes; use crate::fmt::Bytes;
@ -17,6 +17,12 @@ pub struct Error {
pub status: u32, pub status: u32,
} }
#[derive(Debug)]
pub enum AddMulticastAddressError {
NotMulticast,
NoFreeSlots,
}
pub struct Control<'a> { pub struct Control<'a> {
state_ch: ch::StateRunner<'a>, state_ch: ch::StateRunner<'a>,
events: &'a Events, events: &'a Events,
@ -87,22 +93,22 @@ impl<'a> Control<'a> {
self.set_iovar("country", &country_info.to_bytes()).await; self.set_iovar("country", &country_info.to_bytes()).await;
// set country takes some time, next ioctls fail if we don't wait. // set country takes some time, next ioctls fail if we don't wait.
Timer::after(Duration::from_millis(100)).await; Timer::after_millis(100).await;
// Set antenna to chip antenna // Set antenna to chip antenna
self.ioctl_set_u32(IOCTL_CMD_ANTDIV, 0, 0).await; self.ioctl_set_u32(IOCTL_CMD_ANTDIV, 0, 0).await;
self.set_iovar_u32("bus:txglom", 0).await; self.set_iovar_u32("bus:txglom", 0).await;
Timer::after(Duration::from_millis(100)).await; Timer::after_millis(100).await;
//self.set_iovar_u32("apsta", 1).await; // this crashes, also we already did it before...?? //self.set_iovar_u32("apsta", 1).await; // this crashes, also we already did it before...??
//Timer::after(Duration::from_millis(100)).await; //Timer::after_millis(100).await;
self.set_iovar_u32("ampdu_ba_wsize", 8).await; self.set_iovar_u32("ampdu_ba_wsize", 8).await;
Timer::after(Duration::from_millis(100)).await; Timer::after_millis(100).await;
self.set_iovar_u32("ampdu_mpdu", 4).await; self.set_iovar_u32("ampdu_mpdu", 4).await;
Timer::after(Duration::from_millis(100)).await; Timer::after_millis(100).await;
//self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes //self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes
//Timer::after(Duration::from_millis(100)).await; //Timer::after_millis(100).await;
// evts // evts
let mut evts = EventMask { let mut evts = EventMask {
@ -121,19 +127,19 @@ impl<'a> Control<'a> {
self.set_iovar("bsscfg:event_msgs", &evts.to_bytes()).await; self.set_iovar("bsscfg:event_msgs", &evts.to_bytes()).await;
Timer::after(Duration::from_millis(100)).await; Timer::after_millis(100).await;
// set wifi up // set wifi up
self.up().await; self.up().await;
Timer::after(Duration::from_millis(100)).await; Timer::after_millis(100).await;
self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto
self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any
Timer::after(Duration::from_millis(100)).await; Timer::after_millis(100).await;
self.state_ch.set_ethernet_address(mac_addr); self.state_ch.set_hardware_address(HardwareAddress::Ethernet(mac_addr));
debug!("INIT DONE"); debug!("INIT DONE");
} }
@ -185,7 +191,7 @@ impl<'a> Control<'a> {
self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await; self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await;
self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await; self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await;
Timer::after(Duration::from_millis(100)).await; Timer::after_millis(100).await;
let mut pfi = PassphraseInfo { let mut pfi = PassphraseInfo {
len: passphrase.len() as _, len: passphrase.len() as _,
@ -297,7 +303,7 @@ impl<'a> Control<'a> {
if security != Security::OPEN { if security != Security::OPEN {
self.set_iovar_u32x2("bsscfg:wpa_auth", 0, 0x0084).await; // wpa_auth = WPA2_AUTH_PSK | WPA_AUTH_PSK self.set_iovar_u32x2("bsscfg:wpa_auth", 0, 0x0084).await; // wpa_auth = WPA2_AUTH_PSK | WPA_AUTH_PSK
Timer::after(Duration::from_millis(100)).await; Timer::after_millis(100).await;
// Set passphrase // Set passphrase
let mut pfi = PassphraseInfo { let mut pfi = PassphraseInfo {
@ -317,6 +323,54 @@ impl<'a> Control<'a> {
self.set_iovar_u32x2("bss", 0, 1).await; // bss = BSS_UP self.set_iovar_u32x2("bss", 0, 1).await; // bss = BSS_UP
} }
/// Add specified address to the list of hardware addresses the device
/// listens on. The address must be a Group address (I/G bit set). Up
/// to 10 addresses are supported by the firmware. Returns the number of
/// address slots filled after adding, or an error.
pub async fn add_multicast_address(&mut self, address: [u8; 6]) -> Result<usize, AddMulticastAddressError> {
// The firmware seems to ignore non-multicast addresses, so let's
// prevent the user from adding them and wasting space.
if address[0] & 0x01 != 1 {
return Err(AddMulticastAddressError::NotMulticast);
}
let mut buf = [0; 64];
self.get_iovar("mcast_list", &mut buf).await;
let n = u32::from_le_bytes(buf[..4].try_into().unwrap()) as usize;
let (used, free) = buf[4..].split_at_mut(n * 6);
if used.chunks(6).any(|a| a == address) {
return Ok(n);
}
if free.len() < 6 {
return Err(AddMulticastAddressError::NoFreeSlots);
}
free[..6].copy_from_slice(&address);
let n = n + 1;
buf[..4].copy_from_slice(&(n as u32).to_le_bytes());
self.set_iovar_v::<80>("mcast_list", &buf).await;
Ok(n)
}
/// Retrieve the list of configured multicast hardware addresses.
pub async fn list_mulistcast_addresses(&mut self, result: &mut [[u8; 6]; 10]) -> usize {
let mut buf = [0; 64];
self.get_iovar("mcast_list", &mut buf).await;
let n = u32::from_le_bytes(buf[..4].try_into().unwrap()) as usize;
let used = &buf[4..][..n * 6];
for (addr, output) in zip(used.chunks(6), result.iter_mut()) {
output.copy_from_slice(addr)
}
n
}
async fn set_iovar_u32x2(&mut self, name: &str, val1: u32, val2: u32) { async fn set_iovar_u32x2(&mut self, name: &str, val1: u32, val2: u32) {
let mut buf = [0; 8]; let mut buf = [0; 8];
buf[0..4].copy_from_slice(&val1.to_le_bytes()); buf[0..4].copy_from_slice(&val1.to_le_bytes());

View File

@ -1,7 +1,7 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
#![allow(incomplete_features)] #![feature(async_fn_in_trait, type_alias_impl_trait)]
#![feature(async_fn_in_trait, type_alias_impl_trait, concat_bytes)] #![allow(stable_features, unknown_lints, async_fn_in_trait)]
#![deny(unused_must_use)] #![deny(unused_must_use)]
// This mod MUST go first, so that the others see its macros. // This mod MUST go first, so that the others see its macros.
@ -27,7 +27,7 @@ use ioctl::IoctlState;
use crate::bus::Bus; use crate::bus::Bus;
pub use crate::bus::SpiBusCyw43; pub use crate::bus::SpiBusCyw43;
pub use crate::control::{Control, Error as ControlError, Scanner}; pub use crate::control::{AddMulticastAddressError, Control, Error as ControlError, Scanner};
pub use crate::runner::Runner; pub use crate::runner::Runner;
pub use crate::structs::BssInfo; pub use crate::structs::BssInfo;

View File

@ -1,54 +1,48 @@
macro_rules! nvram { pub static NVRAM: &'static [u8] = b"
($($s:literal,)*) => { NVRAMRev=$Rev$\x00\
concat_bytes!($($s, b"\x00",)* b"\x00\x00") manfid=0x2d0\x00\
}; prodid=0x0727\x00\
} vendid=0x14e4\x00\
devid=0x43e2\x00\
pub static NVRAM: &'static [u8] = &*nvram!( boardtype=0x0887\x00\
b"NVRAMRev=$Rev$", boardrev=0x1100\x00\
b"manfid=0x2d0", boardnum=22\x00\
b"prodid=0x0727", macaddr=00:A0:50:b5:59:5e\x00\
b"vendid=0x14e4", sromrev=11\x00\
b"devid=0x43e2", boardflags=0x00404001\x00\
b"boardtype=0x0887", boardflags3=0x04000000\x00\
b"boardrev=0x1100", xtalfreq=37400\x00\
b"boardnum=22", nocrc=1\x00\
b"macaddr=00:A0:50:b5:59:5e", ag0=255\x00\
b"sromrev=11", aa2g=1\x00\
b"boardflags=0x00404001", ccode=ALL\x00\
b"boardflags3=0x04000000", pa0itssit=0x20\x00\
b"xtalfreq=37400", extpagain2g=0\x00\
b"nocrc=1", pa2ga0=-168,6649,-778\x00\
b"ag0=255", AvVmid_c0=0x0,0xc8\x00\
b"aa2g=1", cckpwroffset0=5\x00\
b"ccode=ALL", maxp2ga0=84\x00\
b"pa0itssit=0x20", txpwrbckof=6\x00\
b"extpagain2g=0", cckbw202gpo=0\x00\
b"pa2ga0=-168,6649,-778", legofdmbw202gpo=0x66111111\x00\
b"AvVmid_c0=0x0,0xc8", mcsbw202gpo=0x77711111\x00\
b"cckpwroffset0=5", propbw202gpo=0xdd\x00\
b"maxp2ga0=84", ofdmdigfilttype=18\x00\
b"txpwrbckof=6", ofdmdigfilttypebe=18\x00\
b"cckbw202gpo=0", papdmode=1\x00\
b"legofdmbw202gpo=0x66111111", papdvalidtest=1\x00\
b"mcsbw202gpo=0x77711111", pacalidx2g=45\x00\
b"propbw202gpo=0xdd", papdepsoffset=-30\x00\
b"ofdmdigfilttype=18", papdendidx=58\x00\
b"ofdmdigfilttypebe=18", ltecxmux=0\x00\
b"papdmode=1", ltecxpadnum=0x0102\x00\
b"papdvalidtest=1", ltecxfnsel=0x44\x00\
b"pacalidx2g=45", ltecxgcigpio=0x01\x00\
b"papdepsoffset=-30", il0macaddr=00:90:4c:c5:12:38\x00\
b"papdendidx=58", wl0id=0x431b\x00\
b"ltecxmux=0", deadman_to=0xffffffff\x00\
b"ltecxpadnum=0x0102", muxenab=0x100\x00\
b"ltecxfnsel=0x44", spurconfig=0x3\x00\
b"ltecxgcigpio=0x01", glitch_based_crsmin=1\x00\
b"il0macaddr=00:90:4c:c5:12:38", btc_mode=1\x00\
b"wl0id=0x431b", \x00";
b"deadman_to=0xffffffff",
b"muxenab=0x100",
b"spurconfig=0x3",
b"glitch_based_crsmin=1",
b"btc_mode=1",
);

View File

@ -555,14 +555,14 @@ where
self.bus.bp_write8(base + AI_RESETCTRL_OFFSET, 0).await; self.bus.bp_write8(base + AI_RESETCTRL_OFFSET, 0).await;
Timer::after(Duration::from_millis(1)).await; Timer::after_millis(1).await;
self.bus self.bus
.bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_CLOCK_EN) .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_CLOCK_EN)
.await; .await;
let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await;
Timer::after(Duration::from_millis(1)).await; Timer::after_millis(1).await;
} }
async fn core_is_up(&mut self, core: Core) -> bool { async fn core_is_up(&mut self, core: Core) -> bool {

View File

@ -10,3 +10,4 @@
* xref:examples.adoc[Examples] * xref:examples.adoc[Examples]
* xref:developer.adoc[Developer] * xref:developer.adoc[Developer]
** xref:developer_stm32.adoc[Developer: STM32] ** xref:developer_stm32.adoc[Developer: STM32]
* xref:faq.adoc[Frequently Asked Questions]

View File

@ -48,7 +48,7 @@ The `Spawner` is the way the main application spawns other tasks. The `Periphera
include::example$basic/src/main.rs[lines="22..-1"] include::example$basic/src/main.rs[lines="22..-1"]
---- ----
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: 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_executor::main]` macro. The macro does the following:
. Creates an Embassy Executor . Creates an Embassy Executor
. Initializes the microcontroller HAL to get the `Peripherals` . Initializes the microcontroller HAL to get the `Peripherals`

View File

@ -0,0 +1,38 @@
= Frequently Asked Questions
These are a list of unsorted, commonly asked questions and answers.
Please feel free to add items to link:https://github.com/embassy-rs/embassy/edit/main/docs/modules/ROOT/pages/faq.adoc[this page], especially if someone in the chat answered a question for you!
== How to deploy to RP2040 without a debugging probe.
Install link:https://github.com/JoNil/elf2uf2-rs[elf2uf2-rs] for converting the generated elf binary into a uf2 file.
Configure the runner to use this tool, add this to `.cargo/config.toml`:
[source,toml]
----
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "elf2uf2-rs --deploy --serial --verbose"
----
The command-line parameters `--deploy` will detect your device and upload the binary, `--serial` starts a serial connection. See the documentation for more info.
== Missing main macro
If you see an error like this:
[source,rust]
----
#[embassy_executor::main]
| ^^^^ could not find `main` in `embassy_executor`
----
You are likely missing some features of the `embassy-executor` crate.
For Cortex-M targets, consider making sure that ALL of the following features are active in your `Cargo.toml` for the `embassy-executor` crate:
* `arch-cortex-m`
* `executor-thread`
* `nightly`
For Xtensa ESP32, consider using the executors and `#[main]` macro provided by your appropriate link:https://crates.io/crates/esp-hal-common[HAL crate].

View File

@ -6,7 +6,7 @@ The Embassy executor is an async/await executor designed for embedded usage alon
* No `alloc`, no heap needed. Task are statically allocated. * No `alloc`, no heap needed. Task are statically allocated.
* No "fixed capacity" data structures, executor works with 1 or 1000 tasks without needing config/tuning. * No "fixed capacity" data structures, executor works with 1 or 1000 tasks without needing config/tuning.
* Integrated timer queue: sleeping is easy, just do `Timer::after(Duration::from_secs(1)).await;`. * Integrated timer queue: sleeping is easy, just do `Timer::after_secs(1).await;`.
* No busy-loop polling: CPU sleeps when there's no work to do, using interrupts or `WFE/SEV`. * No busy-loop polling: CPU sleeps when there's no work to do, using interrupts or `WFE/SEV`.
* Efficient polling: a wake will only poll the woken task, not all of them. * Efficient polling: a wake will only poll the woken task, not all of them.
* Fair: a task can't monopolize CPU time even if it's constantly being woken. All other tasks get a chance to run before a given task gets polled for the second time. * Fair: a task can't monopolize CPU time even if it's constantly being woken. All other tasks get a chance to run before a given task gets polled for the second time.

View File

@ -28,7 +28,7 @@ digest = "0.10"
log = { version = "0.4", optional = true } log = { version = "0.4", optional = true }
ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true } ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true }
embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" }
embassy-sync = { version = "0.3.0", path = "../../embassy-sync" } embassy-sync = { version = "0.4.0", path = "../../embassy-sync" }
embedded-storage = "0.3.0" embedded-storage = "0.3.0"
embedded-storage-async = { version = "0.4.0", optional = true } embedded-storage-async = { version = "0.4.0", optional = true }
salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true } salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true }

View File

@ -1,4 +1,5 @@
#![cfg_attr(feature = "nightly", feature(async_fn_in_trait))] #![cfg_attr(feature = "nightly", feature(async_fn_in_trait))]
#![cfg_attr(feature = "nightly", allow(stable_features, unknown_lints, async_fn_in_trait))]
#![no_std] #![no_std]
#![warn(missing_docs)] #![warn(missing_docs)]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]

View File

@ -16,7 +16,7 @@ target = "thumbv7em-none-eabi"
[dependencies] [dependencies]
defmt = { version = "0.3", optional = true } defmt = { version = "0.3", optional = true }
embassy-sync = { path = "../../embassy-sync" } embassy-sync = { version = "0.4.0", path = "../../embassy-sync" }
embassy-nrf = { path = "../../embassy-nrf" } embassy-nrf = { path = "../../embassy-nrf" }
embassy-boot = { path = "../boot", default-features = false } embassy-boot = { path = "../boot", default-features = false }
cortex-m = { version = "0.7.6" } cortex-m = { version = "0.7.6" }

View File

@ -17,7 +17,7 @@ defmt = { version = "0.3", optional = true }
defmt-rtt = { version = "0.4", optional = true } defmt-rtt = { version = "0.4", optional = true }
log = { version = "0.4", optional = true } log = { version = "0.4", optional = true }
embassy-sync = { path = "../../embassy-sync" } embassy-sync = { version = "0.4.0", path = "../../embassy-sync" }
embassy-rp = { path = "../../embassy-rp", default-features = false } embassy-rp = { path = "../../embassy-rp", default-features = false }
embassy-boot = { path = "../boot", default-features = false } embassy-boot = { path = "../boot", default-features = false }
embassy-time = { path = "../../embassy-time" } embassy-time = { path = "../../embassy-time" }

View File

@ -18,7 +18,7 @@ defmt = { version = "0.3", optional = true }
defmt-rtt = { version = "0.4", optional = true } defmt-rtt = { version = "0.4", optional = true }
log = { version = "0.4", optional = true } log = { version = "0.4", optional = true }
embassy-sync = { path = "../../embassy-sync" } embassy-sync = { version = "0.4.0", path = "../../embassy-sync" }
embassy-stm32 = { path = "../../embassy-stm32", default-features = false } embassy-stm32 = { path = "../../embassy-stm32", default-features = false }
embassy-boot = { path = "../boot", default-features = false } embassy-boot = { path = "../boot", default-features = false }
cortex-m = { version = "0.7.6" } cortex-m = { version = "0.7.6" }

View File

@ -20,8 +20,8 @@ default = ["time"]
[dependencies] [dependencies]
embassy-futures = { version = "0.1.0", path = "../embassy-futures", optional = true } embassy-futures = { version = "0.1.0", path = "../embassy-futures", optional = true }
embassy-sync = { version = "0.3.0", path = "../embassy-sync" } embassy-sync = { version = "0.4.0", path = "../embassy-sync" }
embassy-time = { version = "0.1.4", path = "../embassy-time", optional = true } embassy-time = { version = "0.1.5", path = "../embassy-time", optional = true }
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [ embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [
"unproven", "unproven",
] } ] }

View File

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

View File

@ -66,21 +66,29 @@ where
let mut bus = self.bus.lock().await; let mut bus = self.bus.lock().await;
self.cs.set_low().map_err(SpiDeviceError::Cs)?; self.cs.set_low().map_err(SpiDeviceError::Cs)?;
let op_res: Result<(), BUS::Error> = try { let op_res = 'ops: {
for op in operations { for op in operations {
match op { let res = match op {
Operation::Read(buf) => bus.read(buf).await?, Operation::Read(buf) => bus.read(buf).await,
Operation::Write(buf) => bus.write(buf).await?, Operation::Write(buf) => bus.write(buf).await,
Operation::Transfer(read, write) => bus.transfer(read, write).await?, Operation::Transfer(read, write) => bus.transfer(read, write).await,
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await?, Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await,
#[cfg(not(feature = "time"))] #[cfg(not(feature = "time"))]
Operation::DelayUs(_) => return Err(SpiDeviceError::DelayUsNotSupported), Operation::DelayUs(us) => return Err(SpiDeviceError::DelayUsNotSupported),
#[cfg(feature = "time")] #[cfg(feature = "time")]
Operation::DelayUs(us) => { Operation::DelayUs(us) => match bus.flush().await {
embassy_time::Timer::after(embassy_time::Duration::from_micros(*us as _)).await Err(e) => Err(e),
} Ok(()) => {
embassy_time::Timer::after_micros(*us as _).await;
Ok(())
}
},
};
if let Err(e) = res {
break 'ops Err(e);
} }
} }
Ok(())
}; };
// On failure, it's important to still flush and deassert CS. // On failure, it's important to still flush and deassert CS.
@ -133,21 +141,29 @@ where
bus.set_config(&self.config).map_err(|_| SpiDeviceError::Config)?; bus.set_config(&self.config).map_err(|_| SpiDeviceError::Config)?;
self.cs.set_low().map_err(SpiDeviceError::Cs)?; self.cs.set_low().map_err(SpiDeviceError::Cs)?;
let op_res: Result<(), BUS::Error> = try { let op_res = 'ops: {
for op in operations { for op in operations {
match op { let res = match op {
Operation::Read(buf) => bus.read(buf).await?, Operation::Read(buf) => bus.read(buf).await,
Operation::Write(buf) => bus.write(buf).await?, Operation::Write(buf) => bus.write(buf).await,
Operation::Transfer(read, write) => bus.transfer(read, write).await?, Operation::Transfer(read, write) => bus.transfer(read, write).await,
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await?, Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await,
#[cfg(not(feature = "time"))] #[cfg(not(feature = "time"))]
Operation::DelayUs(_) => return Err(SpiDeviceError::DelayUsNotSupported), Operation::DelayUs(us) => return Err(SpiDeviceError::DelayUsNotSupported),
#[cfg(feature = "time")] #[cfg(feature = "time")]
Operation::DelayUs(us) => { Operation::DelayUs(us) => match bus.flush().await {
embassy_time::Timer::after(embassy_time::Duration::from_micros(*us as _)).await Err(e) => Err(e),
} Ok(()) => {
embassy_time::Timer::after_micros(*us as _).await;
Ok(())
}
},
};
if let Err(e) = res {
break 'ops Err(e);
} }
} }
Ok(())
}; };
// On failure, it's important to still flush and deassert CS. // On failure, it's important to still flush and deassert CS.

View File

@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 0.3.3 - 2023-11-15
- Add `main` macro reexport for Xtensa arch.
- Remove use of `atomic-polyfill`. The executor now has multiple implementations of its internal data structures for cases where the target supports atomics or doesn't.
## 0.3.2 - 2023-11-06
- Use `atomic-polyfill` for `riscv32`
- Removed unused dependencies (static_cell, futures-util)
## 0.3.1 - 2023-11-01
- Fix spurious "Found waker not created by the Embassy executor" error in recent nightlies.
## 0.3.0 - 2023-08-25 ## 0.3.0 - 2023-08-25
- Replaced Pender. Implementations now must define an extern function called `__pender`. - Replaced Pender. Implementations now must define an extern function called `__pender`.

View File

@ -1,6 +1,6 @@
[package] [package]
name = "embassy-executor" name = "embassy-executor"
version = "0.3.0" version = "0.3.3"
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "async/await executor designed for embedded usage" description = "async/await executor designed for embedded usage"
@ -27,6 +27,30 @@ default-target = "thumbv7em-none-eabi"
targets = ["thumbv7em-none-eabi"] targets = ["thumbv7em-none-eabi"]
features = ["nightly", "defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"] features = ["nightly", "defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"]
[dependencies]
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
rtos-trace = { version = "0.1.2", optional = true }
embassy-macros = { version = "0.2.1", path = "../embassy-macros" }
embassy-time = { version = "0.1.5", path = "../embassy-time", optional = true}
critical-section = "1.1"
# needed for riscv
# remove when https://github.com/rust-lang/rust/pull/114499 is merged
portable-atomic = { version = "1.5", optional = true }
# arch-cortex-m dependencies
cortex-m = { version = "0.7.6", optional = true }
# arch-wasm dependencies
wasm-bindgen = { version = "0.2.82", optional = true }
js-sys = { version = "0.3", optional = true }
[dev-dependencies]
critical-section = { version = "1.1", features = ["std"] }
[features] [features]
# Architecture # Architecture
@ -34,7 +58,7 @@ _arch = [] # some arch was picked
arch-std = ["_arch", "critical-section/std"] arch-std = ["_arch", "critical-section/std"]
arch-cortex-m = ["_arch", "dep:cortex-m"] arch-cortex-m = ["_arch", "dep:cortex-m"]
arch-xtensa = ["_arch"] arch-xtensa = ["_arch"]
arch-riscv32 = ["_arch"] arch-riscv32 = ["_arch", "dep:portable-atomic"]
arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"] arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"]
# Enable the thread-mode executor (using WFE/SEV in Cortex-M, WFI in other embedded archs) # Enable the thread-mode executor (using WFE/SEV in Cortex-M, WFI in other embedded archs)
@ -43,30 +67,53 @@ executor-thread = []
executor-interrupt = [] executor-interrupt = []
# Enable nightly-only features # Enable nightly-only features
nightly = [] nightly = ["embassy-macros/nightly"]
turbowakers = [] turbowakers = []
integrated-timers = ["dep:embassy-time"] integrated-timers = ["dep:embassy-time"]
# Trace interrupt invocations with rtos-trace. # BEGIN AUTOGENERATED CONFIG FEATURES
rtos-trace-interrupt = ["rtos-trace", "embassy-macros/rtos-trace-interrupt"] # Generated by gen_config.py. DO NOT EDIT.
task-arena-size-64 = []
task-arena-size-128 = []
task-arena-size-192 = []
task-arena-size-256 = []
task-arena-size-320 = []
task-arena-size-384 = []
task-arena-size-512 = []
task-arena-size-640 = []
task-arena-size-768 = []
task-arena-size-1024 = []
task-arena-size-1280 = []
task-arena-size-1536 = []
task-arena-size-2048 = []
task-arena-size-2560 = []
task-arena-size-3072 = []
task-arena-size-4096 = [] # Default
task-arena-size-5120 = []
task-arena-size-6144 = []
task-arena-size-8192 = []
task-arena-size-10240 = []
task-arena-size-12288 = []
task-arena-size-16384 = []
task-arena-size-20480 = []
task-arena-size-24576 = []
task-arena-size-32768 = []
task-arena-size-40960 = []
task-arena-size-49152 = []
task-arena-size-65536 = []
task-arena-size-81920 = []
task-arena-size-98304 = []
task-arena-size-131072 = []
task-arena-size-163840 = []
task-arena-size-196608 = []
task-arena-size-262144 = []
task-arena-size-327680 = []
task-arena-size-393216 = []
task-arena-size-524288 = []
task-arena-size-655360 = []
task-arena-size-786432 = []
task-arena-size-1048576 = []
[dependencies] # END AUTOGENERATED CONFIG FEATURES
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
rtos-trace = { version = "0.1.2", optional = true }
futures-util = { version = "0.3.17", default-features = false }
embassy-macros = { version = "0.2.1", path = "../embassy-macros" }
embassy-time = { version = "0.1.4", path = "../embassy-time", optional = true}
atomic-polyfill = "1.0.1"
critical-section = "1.1"
static_cell = "1.1"
# arch-cortex-m dependencies
cortex-m = { version = "0.7.6", optional = true }
# arch-wasm dependencies
wasm-bindgen = { version = "0.2.82", optional = true }
js-sys = { version = "0.3", optional = true }

View File

@ -2,10 +2,36 @@
An async/await executor designed for embedded usage. An async/await executor designed for embedded usage.
- No `alloc`, no heap needed. Task futures are statically allocated. - No `alloc`, no heap needed.
- With nightly Rust, task futures can be fully statically allocated.
- No "fixed capacity" data structures, executor works with 1 or 1000 tasks without needing config/tuning. - No "fixed capacity" data structures, executor works with 1 or 1000 tasks without needing config/tuning.
- Integrated timer queue: sleeping is easy, just do `Timer::after(Duration::from_secs(1)).await;`. - Integrated timer queue: sleeping is easy, just do `Timer::after_secs(1).await;`.
- No busy-loop polling: CPU sleeps when there's no work to do, using interrupts or `WFE/SEV`. - No busy-loop polling: CPU sleeps when there's no work to do, using interrupts or `WFE/SEV`.
- Efficient polling: a wake will only poll the woken task, not all of them. - Efficient polling: a wake will only poll the woken task, not all of them.
- Fair: a task can't monopolize CPU time even if it's constantly being woken. All other tasks get a chance to run before a given task gets polled for the second time. - Fair: a task can't monopolize CPU time even if it's constantly being woken. All other tasks get a chance to run before a given task gets polled for the second time.
- Creating multiple executor instances is supported, to run tasks with multiple priority levels. This allows higher-priority tasks to preempt lower-priority tasks. - Creating multiple executor instances is supported, to run tasks with multiple priority levels. This allows higher-priority tasks to preempt lower-priority tasks.
## Task arena
When the `nightly` Cargo feature is not enabled, `embassy-executor` allocates tasks out of an arena (a very simple bump allocator).
If the task arena gets full, the program will panic at runtime. To guarantee this doesn't happen, you must set the size to the sum of sizes of all tasks.
Tasks are allocated from the arena when spawned for the first time. If the task exists, the allocation is not released to the arena, but can be reused to spawn the task again. For multiple-instance tasks (like `#[embassy_executor::task(pool_size = 4)]`), the first spawn will allocate memory for all instances. This is done for performance and to increase predictability (for example, spawning at least 1 instance of every task at boot guarantees an immediate panic if the arena is too small, while allocating instances on-demand could delay the panic to only when the program is under load).
The arena size can be configured in two ways:
- Via Cargo features: enable a Cargo feature like `task-arena-size-8192`. Only a selection of values
is available, check `Cargo.toml` for the list.
- Via environment variables at build time: set the variable named `EMBASSY_EXECUTOR_TASK_ARENA_SIZE`. For example
`EMBASSY_EXECUTOR_TASK_ARENA_SIZE=4321 cargo build`. You can also set them in the `[env]` section of `.cargo/config.toml`.
Any value can be set, unlike with Cargo features.
Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting
with different values, compilation fails.
## Statically allocating tasks
When using nightly Rust, enable the `nightly` Cargo feature. This will make `embassy-executor` use the `type_alias_impl_trait` feature to allocate all tasks in `static`s. Each task gets its own `static`, with the exact size to hold the task (or multiple instances of it, if using `pool_size`) calculated automatically at compile time. If tasks don't fit in RAM, this is detected at compile time by the linker. Runtime panics due to running out of memory are not possible.
The configured arena size is ignored, no arena is used at all.

View File

@ -1,6 +1,97 @@
use std::env; use std::collections::HashMap;
use std::fmt::Write;
use std::path::PathBuf;
use std::{env, fs};
static CONFIGS: &[(&str, usize)] = &[
// BEGIN AUTOGENERATED CONFIG FEATURES
// Generated by gen_config.py. DO NOT EDIT.
("TASK_ARENA_SIZE", 4096),
// END AUTOGENERATED CONFIG FEATURES
];
struct ConfigState {
value: usize,
seen_feature: bool,
seen_env: bool,
}
fn main() { fn main() {
let crate_name = env::var("CARGO_PKG_NAME")
.unwrap()
.to_ascii_uppercase()
.replace('-', "_");
// only rebuild if build.rs changed. Otherwise Cargo will rebuild if any
// other file changed.
println!("cargo:rerun-if-changed=build.rs");
// Rebuild if config envvar changed.
for (name, _) in CONFIGS {
println!("cargo:rerun-if-env-changed={crate_name}_{name}");
}
let mut configs = HashMap::new();
for (name, default) in CONFIGS {
configs.insert(
*name,
ConfigState {
value: *default,
seen_env: false,
seen_feature: false,
},
);
}
let prefix = format!("{crate_name}_");
for (var, value) in env::vars() {
if let Some(name) = var.strip_prefix(&prefix) {
let Some(cfg) = configs.get_mut(name) else {
panic!("Unknown env var {name}")
};
let Ok(value) = value.parse::<usize>() else {
panic!("Invalid value for env var {name}: {value}")
};
cfg.value = value;
cfg.seen_env = true;
}
if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") {
if let Some(i) = feature.rfind('_') {
let name = &feature[..i];
let value = &feature[i + 1..];
if let Some(cfg) = configs.get_mut(name) {
let Ok(value) = value.parse::<usize>() else {
panic!("Invalid value for feature {name}: {value}")
};
// envvars take priority.
if !cfg.seen_env {
if cfg.seen_feature {
panic!("multiple values set for feature {}: {} and {}", name, cfg.value, value);
}
cfg.value = value;
cfg.seen_feature = true;
}
}
}
}
}
let mut data = String::new();
for (name, cfg) in &configs {
writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap();
}
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let out_file = out_dir.join("config.rs").to_string_lossy().to_string();
fs::write(out_file, data).unwrap();
// cortex-m targets
let target = env::var("TARGET").unwrap(); let target = env::var("TARGET").unwrap();
if target.starts_with("thumbv6m-") { if target.starts_with("thumbv6m-") {

View File

@ -0,0 +1,82 @@
import os
abspath = os.path.abspath(__file__)
dname = os.path.dirname(abspath)
os.chdir(dname)
features = []
def feature(name, default, min=None, max=None, pow2=None, vals=None, factors=[]):
if vals is None:
assert min is not None
assert max is not None
vals = set()
val = min
while val <= max:
vals.add(val)
for f in factors:
if val * f <= max:
vals.add(val * f)
if (pow2 == True or (isinstance(pow2, int) and val >= pow2)) and val > 0:
val *= 2
else:
val += 1
vals.add(default)
vals = sorted(list(vals))
features.append(
{
"name": name,
"default": default,
"vals": vals,
}
)
feature(
"task_arena_size", default=4096, min=64, max=1024 * 1024, pow2=True, factors=[3, 5]
)
# ========= Update Cargo.toml
things = ""
for f in features:
name = f["name"].replace("_", "-")
for val in f["vals"]:
things += f"{name}-{val} = []"
if val == f["default"]:
things += " # Default"
things += "\n"
things += "\n"
SEPARATOR_START = "# BEGIN AUTOGENERATED CONFIG FEATURES\n"
SEPARATOR_END = "# END AUTOGENERATED CONFIG FEATURES\n"
HELP = "# Generated by gen_config.py. DO NOT EDIT.\n"
with open("Cargo.toml", "r") as f:
data = f.read()
before, data = data.split(SEPARATOR_START, maxsplit=1)
_, after = data.split(SEPARATOR_END, maxsplit=1)
data = before + SEPARATOR_START + HELP + things + SEPARATOR_END + after
with open("Cargo.toml", "w") as f:
f.write(data)
# ========= Update build.rs
things = ""
for f in features:
name = f["name"].upper()
things += f' ("{name}", {f["default"]}),\n'
SEPARATOR_START = "// BEGIN AUTOGENERATED CONFIG FEATURES\n"
SEPARATOR_END = "// END AUTOGENERATED CONFIG FEATURES\n"
HELP = " // Generated by gen_config.py. DO NOT EDIT.\n"
with open("build.rs", "r") as f:
data = f.read()
before, data = data.split(SEPARATOR_START, maxsplit=1)
_, after = data.split(SEPARATOR_END, maxsplit=1)
data = before + SEPARATOR_START + HELP + things + " " + SEPARATOR_END + after
with open("build.rs", "w") as f:
f.write(data)

View File

@ -51,7 +51,6 @@ mod thread {
use core::arch::asm; use core::arch::asm;
use core::marker::PhantomData; use core::marker::PhantomData;
#[cfg(feature = "nightly")]
pub use embassy_macros::main_cortex_m as main; pub use embassy_macros::main_cortex_m as main;
use crate::{raw, Spawner}; use crate::{raw, Spawner};
@ -115,12 +114,12 @@ mod thread {
pub use interrupt::*; pub use interrupt::*;
#[cfg(feature = "executor-interrupt")] #[cfg(feature = "executor-interrupt")]
mod interrupt { mod interrupt {
use core::cell::UnsafeCell; use core::cell::{Cell, UnsafeCell};
use core::mem::MaybeUninit; use core::mem::MaybeUninit;
use atomic_polyfill::{AtomicBool, Ordering};
use cortex_m::interrupt::InterruptNumber; use cortex_m::interrupt::InterruptNumber;
use cortex_m::peripheral::NVIC; use cortex_m::peripheral::NVIC;
use critical_section::Mutex;
use crate::raw; use crate::raw;
@ -146,7 +145,7 @@ mod interrupt {
/// It is somewhat more complex to use, it's recommended to use the thread-mode /// It is somewhat more complex to use, it's recommended to use the thread-mode
/// [`Executor`] instead, if it works for your use case. /// [`Executor`] instead, if it works for your use case.
pub struct InterruptExecutor { pub struct InterruptExecutor {
started: AtomicBool, started: Mutex<Cell<bool>>,
executor: UnsafeCell<MaybeUninit<raw::Executor>>, executor: UnsafeCell<MaybeUninit<raw::Executor>>,
} }
@ -158,7 +157,7 @@ mod interrupt {
#[inline] #[inline]
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
started: AtomicBool::new(false), started: Mutex::new(Cell::new(false)),
executor: UnsafeCell::new(MaybeUninit::uninit()), executor: UnsafeCell::new(MaybeUninit::uninit()),
} }
} }
@ -167,7 +166,8 @@ mod interrupt {
/// ///
/// # Safety /// # Safety
/// ///
/// You MUST call this from the interrupt handler, and from nowhere else. /// - You MUST call this from the interrupt handler, and from nowhere else.
/// - You must not call this before calling `start()`.
pub unsafe fn on_interrupt(&'static self) { pub unsafe fn on_interrupt(&'static self) {
let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
executor.poll(); executor.poll();
@ -196,11 +196,7 @@ mod interrupt {
/// do it after. /// do it after.
/// ///
pub fn start(&'static self, irq: impl InterruptNumber) -> crate::SendSpawner { pub fn start(&'static self, irq: impl InterruptNumber) -> crate::SendSpawner {
if self if critical_section::with(|cs| self.started.borrow(cs).replace(true)) {
.started
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
panic!("InterruptExecutor::start() called multiple times on the same executor."); panic!("InterruptExecutor::start() called multiple times on the same executor.");
} }
@ -222,10 +218,10 @@ mod interrupt {
/// This returns a [`SendSpawner`] you can use to spawn tasks on this /// This returns a [`SendSpawner`] you can use to spawn tasks on this
/// executor. /// executor.
/// ///
/// This MUST only be called on an executor that has already been spawned. /// This MUST only be called on an executor that has already been started.
/// The function will panic otherwise. /// The function will panic otherwise.
pub fn spawner(&'static self) -> crate::SendSpawner { pub fn spawner(&'static self) -> crate::SendSpawner {
if !self.started.load(Ordering::Acquire) { if !critical_section::with(|cs| self.started.borrow(cs).get()) {
panic!("InterruptExecutor::spawner() called on uninitialized executor."); panic!("InterruptExecutor::spawner() called on uninitialized executor.");
} }
let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; let executor = unsafe { (&*self.executor.get()).assume_init_ref() };

View File

@ -6,10 +6,9 @@ pub use thread::*;
#[cfg(feature = "executor-thread")] #[cfg(feature = "executor-thread")]
mod thread { mod thread {
use core::marker::PhantomData; use core::marker::PhantomData;
use core::sync::atomic::{AtomicBool, Ordering};
#[cfg(feature = "nightly")]
pub use embassy_macros::main_riscv as main; pub use embassy_macros::main_riscv as main;
use portable_atomic::{AtomicBool, Ordering};
use crate::{raw, Spawner}; use crate::{raw, Spawner};

View File

@ -8,7 +8,6 @@ mod thread {
use std::marker::PhantomData; use std::marker::PhantomData;
use std::sync::{Condvar, Mutex}; use std::sync::{Condvar, Mutex};
#[cfg(feature = "nightly")]
pub use embassy_macros::main_std as main; pub use embassy_macros::main_std as main;
use crate::{raw, Spawner}; use crate::{raw, Spawner};

View File

@ -8,7 +8,6 @@ mod thread {
use core::marker::PhantomData; use core::marker::PhantomData;
#[cfg(feature = "nightly")]
pub use embassy_macros::main_wasm as main; pub use embassy_macros::main_wasm as main;
use js_sys::Promise; use js_sys::Promise;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;

View File

@ -8,6 +8,8 @@ mod thread {
use core::marker::PhantomData; use core::marker::PhantomData;
use core::sync::atomic::{AtomicBool, Ordering}; use core::sync::atomic::{AtomicBool, Ordering};
pub use embassy_macros::main_riscv as main;
use crate::{raw, Spawner}; use crate::{raw, Spawner};
/// global atomic used to keep track of whether there is work to do since sev() is not available on Xtensa /// global atomic used to keep track of whether there is work to do since sev() is not available on Xtensa

View File

@ -1,5 +1,5 @@
#![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)] #![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)]
#![cfg_attr(all(feature = "nightly", feature = "arch-xtensa"), feature(asm_experimental_arch))] #![cfg_attr(feature = "arch-xtensa", feature(asm_experimental_arch))]
#![allow(clippy::new_without_default)] #![allow(clippy::new_without_default)]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
#![warn(missing_docs)] #![warn(missing_docs)]
@ -7,7 +7,6 @@
// This mod MUST go first, so that the others see its macros. // This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt; pub(crate) mod fmt;
#[cfg(feature = "nightly")]
pub use embassy_macros::task; pub use embassy_macros::task;
macro_rules! check_at_most_one { macro_rules! check_at_most_one {
@ -33,6 +32,7 @@ check_at_most_one!("arch-cortex-m", "arch-riscv32", "arch-xtensa", "arch-std", "
mod arch; mod arch;
#[cfg(feature = "_arch")] #[cfg(feature = "_arch")]
#[allow(unused_imports)] // don't warn if the module is empty.
pub use arch::*; pub use arch::*;
pub mod raw; pub mod raw;
@ -40,29 +40,101 @@ pub mod raw;
mod spawner; mod spawner;
pub use spawner::*; pub use spawner::*;
mod config {
#![allow(unused)]
include!(concat!(env!("OUT_DIR"), "/config.rs"));
}
/// Implementation details for embassy macros. /// Implementation details for embassy macros.
/// Do not use. Used for macros and HALs only. Not covered by semver guarantees. /// Do not use. Used for macros and HALs only. Not covered by semver guarantees.
#[doc(hidden)] #[doc(hidden)]
#[cfg(not(feature = "nightly"))]
pub mod _export { pub mod _export {
#[cfg(feature = "rtos-trace")] use core::alloc::Layout;
pub use rtos_trace::trace; use core::cell::{Cell, UnsafeCell};
pub use static_cell::StaticCell; use core::future::Future;
use core::mem::MaybeUninit;
use core::ptr::null_mut;
/// Expands the given block of code when `embassy-executor` is compiled with use critical_section::{CriticalSection, Mutex};
/// the `rtos-trace-interrupt` feature.
#[doc(hidden)] use crate::raw::TaskPool;
#[macro_export]
#[cfg(feature = "rtos-trace-interrupt")] struct Arena<const N: usize> {
macro_rules! rtos_trace_interrupt { buf: UnsafeCell<MaybeUninit<[u8; N]>>,
($($tt:tt)*) => { $($tt)* }; ptr: Mutex<Cell<*mut u8>>,
} }
/// Does not expand the given block of code when `embassy-executor` is unsafe impl<const N: usize> Sync for Arena<N> {}
/// compiled without the `rtos-trace-interrupt` feature. unsafe impl<const N: usize> Send for Arena<N> {}
#[doc(hidden)]
#[macro_export] impl<const N: usize> Arena<N> {
#[cfg(not(feature = "rtos-trace-interrupt"))] const fn new() -> Self {
macro_rules! rtos_trace_interrupt { Self {
($($tt:tt)*) => {}; buf: UnsafeCell::new(MaybeUninit::uninit()),
ptr: Mutex::new(Cell::new(null_mut())),
}
}
fn alloc<T>(&'static self, cs: CriticalSection) -> &'static mut MaybeUninit<T> {
let layout = Layout::new::<T>();
let start = self.buf.get().cast::<u8>();
let end = unsafe { start.add(N) };
let mut ptr = self.ptr.borrow(cs).get();
if ptr.is_null() {
ptr = self.buf.get().cast::<u8>();
}
let bytes_left = (end as usize) - (ptr as usize);
let align_offset = (ptr as usize).next_multiple_of(layout.align()) - (ptr as usize);
if align_offset + layout.size() > bytes_left {
panic!("embassy-executor: task arena is full. You must increase the arena size, see the documentation for details: https://docs.embassy.dev/embassy-executor/");
}
let res = unsafe { ptr.add(align_offset) };
let ptr = unsafe { ptr.add(align_offset + layout.size()) };
self.ptr.borrow(cs).set(ptr);
unsafe { &mut *(res as *mut MaybeUninit<T>) }
}
}
static ARENA: Arena<{ crate::config::TASK_ARENA_SIZE }> = Arena::new();
pub struct TaskPoolRef {
// type-erased `&'static mut TaskPool<F, N>`
// Needed because statics can't have generics.
ptr: Mutex<Cell<*mut ()>>,
}
unsafe impl Sync for TaskPoolRef {}
unsafe impl Send for TaskPoolRef {}
impl TaskPoolRef {
pub const fn new() -> Self {
Self {
ptr: Mutex::new(Cell::new(null_mut())),
}
}
/// Get the pool for this ref, allocating it from the arena the first time.
///
/// safety: for a given TaskPoolRef instance, must always call with the exact
/// same generic params.
pub unsafe fn get<F: Future, const N: usize>(&'static self) -> &'static TaskPool<F, N> {
critical_section::with(|cs| {
let ptr = self.ptr.borrow(cs);
if ptr.get().is_null() {
let pool = ARENA.alloc::<TaskPool<F, N>>(cs);
pool.write(TaskPool::new());
ptr.set(pool as *mut _ as _);
}
unsafe { &*(ptr.get() as *const _) }
})
}
} }
} }

View File

@ -7,7 +7,15 @@
//! Using this module requires respecting subtle safety contracts. If you can, prefer using the safe //! Using this module requires respecting subtle safety contracts. If you can, prefer using the safe
//! [executor wrappers](crate::Executor) and the [`embassy_executor::task`](embassy_macros::task) macro, which are fully safe. //! [executor wrappers](crate::Executor) and the [`embassy_executor::task`](embassy_macros::task) macro, which are fully safe.
#[cfg_attr(target_has_atomic = "ptr", path = "run_queue_atomics.rs")]
#[cfg_attr(not(target_has_atomic = "ptr"), path = "run_queue_critical_section.rs")]
mod run_queue; mod run_queue;
#[cfg_attr(all(cortex_m, target_has_atomic = "8"), path = "state_atomics_arm.rs")]
#[cfg_attr(all(not(cortex_m), target_has_atomic = "8"), path = "state_atomics.rs")]
#[cfg_attr(not(target_has_atomic = "8"), path = "state_critical_section.rs")]
mod state;
#[cfg(feature = "integrated-timers")] #[cfg(feature = "integrated-timers")]
mod timer_queue; mod timer_queue;
pub(crate) mod util; pub(crate) mod util;
@ -21,7 +29,6 @@ use core::pin::Pin;
use core::ptr::NonNull; use core::ptr::NonNull;
use core::task::{Context, Poll}; use core::task::{Context, Poll};
use atomic_polyfill::{AtomicU32, Ordering};
#[cfg(feature = "integrated-timers")] #[cfg(feature = "integrated-timers")]
use embassy_time::driver::{self, AlarmHandle}; use embassy_time::driver::{self, AlarmHandle};
#[cfg(feature = "integrated-timers")] #[cfg(feature = "integrated-timers")]
@ -30,21 +37,14 @@ use embassy_time::Instant;
use rtos_trace::trace; use rtos_trace::trace;
use self::run_queue::{RunQueue, RunQueueItem}; use self::run_queue::{RunQueue, RunQueueItem};
use self::state::State;
use self::util::{SyncUnsafeCell, UninitCell}; use self::util::{SyncUnsafeCell, UninitCell};
pub use self::waker::task_from_waker; pub use self::waker::task_from_waker;
use super::SpawnToken; use super::SpawnToken;
/// Task is spawned (has a future)
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
/// Task is in the executor run queue
pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1;
/// Task is in the executor timer queue
#[cfg(feature = "integrated-timers")]
pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2;
/// Raw task header for use in task pointers. /// Raw task header for use in task pointers.
pub(crate) struct TaskHeader { pub(crate) struct TaskHeader {
pub(crate) state: AtomicU32, pub(crate) state: State,
pub(crate) run_queue_item: RunQueueItem, pub(crate) run_queue_item: RunQueueItem,
pub(crate) executor: SyncUnsafeCell<Option<&'static SyncExecutor>>, pub(crate) executor: SyncUnsafeCell<Option<&'static SyncExecutor>>,
poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>, poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>,
@ -116,7 +116,7 @@ impl<F: Future + 'static> TaskStorage<F> {
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
raw: TaskHeader { raw: TaskHeader {
state: AtomicU32::new(0), state: State::new(),
run_queue_item: RunQueueItem::new(), run_queue_item: RunQueueItem::new(),
executor: SyncUnsafeCell::new(None), executor: SyncUnsafeCell::new(None),
// Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss` // Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss`
@ -161,7 +161,7 @@ impl<F: Future + 'static> TaskStorage<F> {
match future.poll(&mut cx) { match future.poll(&mut cx) {
Poll::Ready(_) => { Poll::Ready(_) => {
this.future.drop_in_place(); this.future.drop_in_place();
this.raw.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel); this.raw.state.despawn();
#[cfg(feature = "integrated-timers")] #[cfg(feature = "integrated-timers")]
this.raw.expires_at.set(Instant::MAX); this.raw.expires_at.set(Instant::MAX);
@ -193,11 +193,7 @@ impl<F: Future + 'static> AvailableTask<F> {
/// ///
/// This function returns `None` if a task has already been spawned and has not finished running. /// This function returns `None` if a task has already been spawned and has not finished running.
pub fn claim(task: &'static TaskStorage<F>) -> Option<Self> { pub fn claim(task: &'static TaskStorage<F>) -> Option<Self> {
task.raw task.raw.state.spawn().then(|| Self { task })
.state
.compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire)
.ok()
.map(|_| Self { task })
} }
fn initialize_impl<S>(self, future: impl FnOnce() -> F) -> SpawnToken<S> { fn initialize_impl<S>(self, future: impl FnOnce() -> F) -> SpawnToken<S> {
@ -394,8 +390,7 @@ impl SyncExecutor {
#[cfg(feature = "integrated-timers")] #[cfg(feature = "integrated-timers")]
task.expires_at.set(Instant::MAX); task.expires_at.set(Instant::MAX);
let state = task.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel); if !task.state.run_dequeue() {
if state & STATE_SPAWNED == 0 {
// If task is not running, ignore it. This can happen in the following scenario: // If task is not running, ignore it. This can happen in the following scenario:
// - Task gets dequeued, poll starts // - Task gets dequeued, poll starts
// - While task is being polled, it gets woken. It gets placed in the queue. // - While task is being polled, it gets woken. It gets placed in the queue.
@ -546,18 +541,7 @@ impl Executor {
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`]. /// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
pub fn wake_task(task: TaskRef) { pub fn wake_task(task: TaskRef) {
let header = task.header(); let header = task.header();
if header.state.run_enqueue() {
let res = header.state.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| {
// If already scheduled, or if not started,
if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) {
None
} else {
// Mark it as scheduled
Some(state | STATE_RUN_QUEUED)
}
});
if res.is_ok() {
// We have just marked the task as scheduled, so enqueue it. // We have just marked the task as scheduled, so enqueue it.
unsafe { unsafe {
let executor = header.executor.get().unwrap_unchecked(); let executor = header.executor.get().unwrap_unchecked();
@ -571,18 +555,7 @@ pub fn wake_task(task: TaskRef) {
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`]. /// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
pub fn wake_task_no_pend(task: TaskRef) { pub fn wake_task_no_pend(task: TaskRef) {
let header = task.header(); let header = task.header();
if header.state.run_enqueue() {
let res = header.state.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| {
// If already scheduled, or if not started,
if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) {
None
} else {
// Mark it as scheduled
Some(state | STATE_RUN_QUEUED)
}
});
if res.is_ok() {
// We have just marked the task as scheduled, so enqueue it. // We have just marked the task as scheduled, so enqueue it.
unsafe { unsafe {
let executor = header.executor.get().unwrap_unchecked(); let executor = header.executor.get().unwrap_unchecked();

View File

@ -1,7 +1,6 @@
use core::ptr; use core::ptr;
use core::ptr::NonNull; use core::ptr::NonNull;
use core::sync::atomic::{AtomicPtr, Ordering};
use atomic_polyfill::{AtomicPtr, Ordering};
use super::{TaskHeader, TaskRef}; use super::{TaskHeader, TaskRef};
use crate::raw::util::SyncUnsafeCell; use crate::raw::util::SyncUnsafeCell;

View File

@ -0,0 +1,75 @@
use core::cell::Cell;
use critical_section::{CriticalSection, Mutex};
use super::TaskRef;
pub(crate) struct RunQueueItem {
next: Mutex<Cell<Option<TaskRef>>>,
}
impl RunQueueItem {
pub const fn new() -> Self {
Self {
next: Mutex::new(Cell::new(None)),
}
}
}
/// Atomic task queue using a very, very simple lock-free linked-list queue:
///
/// To enqueue a task, task.next is set to the old head, and head is atomically set to task.
///
/// Dequeuing is done in batches: the queue is emptied by atomically replacing head with
/// null. Then the batch is iterated following the next pointers until null is reached.
///
/// Note that batches will be iterated in the reverse order as they were enqueued. This is OK
/// for our purposes: it can't create fairness problems since the next batch won't run until the
/// current batch is completely processed, so even if a task enqueues itself instantly (for example
/// by waking its own waker) can't prevent other tasks from running.
pub(crate) struct RunQueue {
head: Mutex<Cell<Option<TaskRef>>>,
}
impl RunQueue {
pub const fn new() -> Self {
Self {
head: Mutex::new(Cell::new(None)),
}
}
/// Enqueues an item. Returns true if the queue was empty.
///
/// # Safety
///
/// `item` must NOT be already enqueued in any queue.
#[inline(always)]
pub(crate) unsafe fn enqueue(&self, task: TaskRef) -> bool {
critical_section::with(|cs| {
let prev = self.head.borrow(cs).replace(Some(task));
task.header().run_queue_item.next.borrow(cs).set(prev);
prev.is_none()
})
}
/// 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(TaskRef)) {
// Atomically empty the queue.
let mut next = critical_section::with(|cs| self.head.borrow(cs).take());
// Iterate the linked list of tasks that were previously in the queue.
while let Some(task) = next {
// If the task re-enqueues itself, the `next` pointer will get overwritten.
// Therefore, first read the next pointer, and only then process the task.
// safety: we know if the task is enqueued, no one else will touch the `next` pointer.
let cs = unsafe { CriticalSection::new() };
next = task.header().run_queue_item.next.borrow(cs).get();
on_task(task);
}
}
}

View File

@ -0,0 +1,73 @@
use core::sync::atomic::{AtomicU32, Ordering};
/// Task is spawned (has a future)
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
/// Task is in the executor run queue
pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1;
/// Task is in the executor timer queue
#[cfg(feature = "integrated-timers")]
pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2;
pub(crate) struct State {
state: AtomicU32,
}
impl State {
pub const fn new() -> State {
Self {
state: AtomicU32::new(0),
}
}
/// If task is idle, mark it as spawned + run_queued and return true.
#[inline(always)]
pub fn spawn(&self) -> bool {
self.state
.compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
}
/// Unmark the task as spawned.
#[inline(always)]
pub fn despawn(&self) {
self.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel);
}
/// Mark the task as run-queued if it's spawned and isn't already run-queued. Return true on success.
#[inline(always)]
pub fn run_enqueue(&self) -> bool {
self.state
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| {
// If already scheduled, or if not started,
if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) {
None
} else {
// Mark it as scheduled
Some(state | STATE_RUN_QUEUED)
}
})
.is_ok()
}
/// Unmark the task as run-queued. Return whether the task is spawned.
#[inline(always)]
pub fn run_dequeue(&self) -> bool {
let state = self.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel);
state & STATE_SPAWNED != 0
}
/// Mark the task as timer-queued. Return whether it was newly queued (i.e. not queued before)
#[cfg(feature = "integrated-timers")]
#[inline(always)]
pub fn timer_enqueue(&self) -> bool {
let old_state = self.state.fetch_or(STATE_TIMER_QUEUED, Ordering::AcqRel);
old_state & STATE_TIMER_QUEUED == 0
}
/// Unmark the task as timer-queued.
#[cfg(feature = "integrated-timers")]
#[inline(always)]
pub fn timer_dequeue(&self) {
self.state.fetch_and(!STATE_TIMER_QUEUED, Ordering::AcqRel);
}
}

View File

@ -0,0 +1,103 @@
use core::arch::asm;
use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU32, Ordering};
// Must be kept in sync with the layout of `State`!
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 8;
#[repr(C, align(4))]
pub(crate) struct State {
/// Task is spawned (has a future)
spawned: AtomicBool,
/// Task is in the executor run queue
run_queued: AtomicBool,
/// Task is in the executor timer queue
timer_queued: AtomicBool,
pad: AtomicBool,
}
impl State {
pub const fn new() -> State {
Self {
spawned: AtomicBool::new(false),
run_queued: AtomicBool::new(false),
timer_queued: AtomicBool::new(false),
pad: AtomicBool::new(false),
}
}
fn as_u32(&self) -> &AtomicU32 {
unsafe { &*(self as *const _ as *const AtomicU32) }
}
/// If task is idle, mark it as spawned + run_queued and return true.
#[inline(always)]
pub fn spawn(&self) -> bool {
compiler_fence(Ordering::Release);
let r = self
.as_u32()
.compare_exchange(
0,
STATE_SPAWNED | STATE_RUN_QUEUED,
Ordering::Relaxed,
Ordering::Relaxed,
)
.is_ok();
compiler_fence(Ordering::Acquire);
r
}
/// Unmark the task as spawned.
#[inline(always)]
pub fn despawn(&self) {
compiler_fence(Ordering::Release);
self.spawned.store(false, Ordering::Relaxed);
}
/// Mark the task as run-queued if it's spawned and isn't already run-queued. Return true on success.
#[inline(always)]
pub fn run_enqueue(&self) -> bool {
unsafe {
loop {
let state: u32;
asm!("ldrex {}, [{}]", out(reg) state, in(reg) self, options(nostack));
if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) {
asm!("clrex", options(nomem, nostack));
return false;
}
let outcome: usize;
let new_state = state | STATE_RUN_QUEUED;
asm!("strex {}, {}, [{}]", out(reg) outcome, in(reg) new_state, in(reg) self, options(nostack));
if outcome == 0 {
return true;
}
}
}
}
/// Unmark the task as run-queued. Return whether the task is spawned.
#[inline(always)]
pub fn run_dequeue(&self) -> bool {
compiler_fence(Ordering::Release);
let r = self.spawned.load(Ordering::Relaxed);
self.run_queued.store(false, Ordering::Relaxed);
r
}
/// Mark the task as timer-queued. Return whether it was newly queued (i.e. not queued before)
#[cfg(feature = "integrated-timers")]
#[inline(always)]
pub fn timer_enqueue(&self) -> bool {
!self.timer_queued.swap(true, Ordering::Relaxed)
}
/// Unmark the task as timer-queued.
#[cfg(feature = "integrated-timers")]
#[inline(always)]
pub fn timer_dequeue(&self) {
self.timer_queued.store(false, Ordering::Relaxed);
}
}

View File

@ -0,0 +1,93 @@
use core::cell::Cell;
use critical_section::Mutex;
/// Task is spawned (has a future)
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
/// Task is in the executor run queue
pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1;
/// Task is in the executor timer queue
#[cfg(feature = "integrated-timers")]
pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2;
pub(crate) struct State {
state: Mutex<Cell<u32>>,
}
impl State {
pub const fn new() -> State {
Self {
state: Mutex::new(Cell::new(0)),
}
}
fn update<R>(&self, f: impl FnOnce(&mut u32) -> R) -> R {
critical_section::with(|cs| {
let s = self.state.borrow(cs);
let mut val = s.get();
let r = f(&mut val);
s.set(val);
r
})
}
/// If task is idle, mark it as spawned + run_queued and return true.
#[inline(always)]
pub fn spawn(&self) -> bool {
self.update(|s| {
if *s == 0 {
*s = STATE_SPAWNED | STATE_RUN_QUEUED;
true
} else {
false
}
})
}
/// Unmark the task as spawned.
#[inline(always)]
pub fn despawn(&self) {
self.update(|s| *s &= !STATE_SPAWNED);
}
/// Mark the task as run-queued if it's spawned and isn't already run-queued. Return true on success.
#[inline(always)]
pub fn run_enqueue(&self) -> bool {
self.update(|s| {
if (*s & STATE_RUN_QUEUED != 0) || (*s & STATE_SPAWNED == 0) {
false
} else {
*s |= STATE_RUN_QUEUED;
true
}
})
}
/// Unmark the task as run-queued. Return whether the task is spawned.
#[inline(always)]
pub fn run_dequeue(&self) -> bool {
self.update(|s| {
let ok = *s & STATE_SPAWNED != 0;
*s &= !STATE_RUN_QUEUED;
ok
})
}
/// Mark the task as timer-queued. Return whether it was newly queued (i.e. not queued before)
#[cfg(feature = "integrated-timers")]
#[inline(always)]
pub fn timer_enqueue(&self) -> bool {
self.update(|s| {
let ok = *s & STATE_TIMER_QUEUED == 0;
*s |= STATE_TIMER_QUEUED;
ok
})
}
/// Unmark the task as timer-queued.
#[cfg(feature = "integrated-timers")]
#[inline(always)]
pub fn timer_dequeue(&self) {
self.update(|s| *s &= !STATE_TIMER_QUEUED);
}
}

View File

@ -1,9 +1,8 @@
use core::cmp::min; use core::cmp::min;
use atomic_polyfill::Ordering;
use embassy_time::Instant; use embassy_time::Instant;
use super::{TaskRef, STATE_TIMER_QUEUED}; use super::TaskRef;
use crate::raw::util::SyncUnsafeCell; use crate::raw::util::SyncUnsafeCell;
pub(crate) struct TimerQueueItem { pub(crate) struct TimerQueueItem {
@ -32,10 +31,7 @@ impl TimerQueue {
pub(crate) unsafe fn update(&self, p: TaskRef) { pub(crate) unsafe fn update(&self, p: TaskRef) {
let task = p.header(); let task = p.header();
if task.expires_at.get() != Instant::MAX { if task.expires_at.get() != Instant::MAX {
let old_state = task.state.fetch_or(STATE_TIMER_QUEUED, Ordering::AcqRel); if task.state.timer_enqueue() {
let is_new = old_state & STATE_TIMER_QUEUED == 0;
if is_new {
task.timer_queue_item.next.set(self.head.get()); task.timer_queue_item.next.set(self.head.get());
self.head.set(Some(p)); self.head.set(Some(p));
} }
@ -75,7 +71,7 @@ impl TimerQueue {
} else { } else {
// Remove it // Remove it
prev.set(task.timer_queue_item.next.get()); prev.set(task.timer_queue_item.next.get());
task.state.fetch_and(!STATE_TIMER_QUEUED, Ordering::AcqRel); task.state.timer_dequeue();
} }
} }
} }

View File

@ -3,7 +3,7 @@ use core::task::{RawWaker, RawWakerVTable, Waker};
use super::{wake_task, TaskHeader, TaskRef}; use super::{wake_task, TaskHeader, TaskRef};
const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake, drop); static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake, drop);
unsafe fn clone(p: *const ()) -> RawWaker { unsafe fn clone(p: *const ()) -> RawWaker {
RawWaker::new(p, &VTABLE) RawWaker::new(p, &VTABLE)

View File

@ -0,0 +1,137 @@
#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))]
use std::boxed::Box;
use std::future::poll_fn;
use std::sync::{Arc, Mutex};
use std::task::Poll;
use embassy_executor::raw::Executor;
use embassy_executor::task;
#[export_name = "__pender"]
fn __pender(context: *mut ()) {
unsafe {
let trace = &*(context as *const Trace);
trace.push("pend");
}
}
#[derive(Clone)]
struct Trace {
trace: Arc<Mutex<Vec<&'static str>>>,
}
impl Trace {
fn new() -> Self {
Self {
trace: Arc::new(Mutex::new(Vec::new())),
}
}
fn push(&self, value: &'static str) {
self.trace.lock().unwrap().push(value)
}
fn get(&self) -> Vec<&'static str> {
self.trace.lock().unwrap().clone()
}
}
fn setup() -> (&'static Executor, Trace) {
let trace = Trace::new();
let context = Box::leak(Box::new(trace.clone())) as *mut _ as *mut ();
let executor = &*Box::leak(Box::new(Executor::new(context)));
(executor, trace)
}
#[test]
fn executor_noop() {
let (executor, trace) = setup();
unsafe { executor.poll() };
assert!(trace.get().is_empty())
}
#[test]
fn executor_task() {
#[task]
async fn task1(trace: Trace) {
trace.push("poll task1")
}
let (executor, trace) = setup();
executor.spawner().spawn(task1(trace.clone())).unwrap();
unsafe { executor.poll() };
unsafe { executor.poll() };
assert_eq!(
trace.get(),
&[
"pend", // spawning a task pends the executor
"poll task1", // poll only once.
]
)
}
#[test]
fn executor_task_self_wake() {
#[task]
async fn task1(trace: Trace) {
poll_fn(|cx| {
trace.push("poll task1");
cx.waker().wake_by_ref();
Poll::Pending
})
.await
}
let (executor, trace) = setup();
executor.spawner().spawn(task1(trace.clone())).unwrap();
unsafe { executor.poll() };
unsafe { executor.poll() };
assert_eq!(
trace.get(),
&[
"pend", // spawning a task pends the executor
"poll task1", //
"pend", // task self-wakes
"poll task1", //
"pend", // task self-wakes
]
)
}
#[test]
fn executor_task_self_wake_twice() {
#[task]
async fn task1(trace: Trace) {
poll_fn(|cx| {
trace.push("poll task1");
cx.waker().wake_by_ref();
trace.push("poll task1 wake 2");
cx.waker().wake_by_ref();
Poll::Pending
})
.await
}
let (executor, trace) = setup();
executor.spawner().spawn(task1(trace.clone())).unwrap();
unsafe { executor.poll() };
unsafe { executor.poll() };
assert_eq!(
trace.get(),
&[
"pend", // spawning a task pends the executor
"poll task1", //
"pend", // task self-wakes
"poll task1 wake 2", // task self-wakes again, shouldn't pend
"poll task1", //
"pend", // task self-wakes
"poll task1 wake 2", // task self-wakes again, shouldn't pend
]
)
}

View File

@ -1,6 +1,6 @@
[package] [package]
name = "embassy-futures" name = "embassy-futures"
version = "0.1.0" version = "0.1.1"
edition = "2021" edition = "2021"
description = "no-std, no-alloc utilities for working with futures" description = "no-std, no-alloc utilities for working with futures"
repository = "https://github.com/embassy-rs/embassy" repository = "https://github.com/embassy-rs/embassy"

View File

@ -20,8 +20,8 @@ defmt = ["dep:defmt", "lorawan-device/defmt"]
defmt = { version = "0.3", optional = true } defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true } log = { version = "0.4.14", optional = true }
embassy-time = { version = "0.1.4", path = "../embassy-time", optional = true } embassy-time = { version = "0.1.5", path = "../embassy-time", optional = true }
embassy-sync = { version = "0.3.0", path = "../embassy-sync" } embassy-sync = { version = "0.4.0", path = "../embassy-sync" }
embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false, optional = true } embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false, optional = true }
embedded-hal-async = { version = "=1.0.0-rc.1" } embedded-hal-async = { version = "=1.0.0-rc.1" }
embedded-hal = { version = "0.2", features = ["unproven"] } embedded-hal = { version = "0.2", features = ["unproven"] }

View File

@ -1,5 +1,6 @@
#![no_std] #![no_std]
#![feature(async_fn_in_trait)] #![feature(async_fn_in_trait, impl_trait_projections)]
#![allow(stable_features, unknown_lints, async_fn_in_trait)]
//! embassy-lora holds LoRa-specific functionality. //! embassy-lora holds LoRa-specific functionality.
pub(crate) mod fmt; pub(crate) mod fmt;
@ -34,6 +35,6 @@ impl lorawan_device::async_device::radio::Timer for LoraTimer {
} }
async fn delay_ms(&mut self, millis: u64) { async fn delay_ms(&mut self, millis: u64) {
Timer::after(Duration::from_millis(millis)).await Timer::after_millis(millis).await
} }
} }

View File

@ -21,5 +21,4 @@ proc-macro2 = "1.0.29"
proc-macro = true proc-macro = true
[features] [features]
# Enabling this cause interrupt::take! to require embassy-executor nightly = []
rtos-trace-interrupt = []

View File

@ -53,8 +53,7 @@ pub fn wasm() -> TokenStream {
quote! { quote! {
#[wasm_bindgen::prelude::wasm_bindgen(start)] #[wasm_bindgen::prelude::wasm_bindgen(start)]
pub fn main() -> Result<(), wasm_bindgen::JsValue> { pub fn main() -> Result<(), wasm_bindgen::JsValue> {
static EXECUTOR: ::embassy_executor::_export::StaticCell<::embassy_executor::Executor> = ::embassy_executor::_export::StaticCell::new(); let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new()));
let executor = EXECUTOR.init(::embassy_executor::Executor::new());
executor.start(|spawner| { executor.start(|spawner| {
spawner.spawn(__embassy_main(spawner)).unwrap(); spawner.spawn(__embassy_main(spawner)).unwrap();

View File

@ -79,6 +79,7 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
task_inner.vis = syn::Visibility::Inherited; task_inner.vis = syn::Visibility::Inherited;
task_inner.sig.ident = task_inner_ident.clone(); task_inner.sig.ident = task_inner_ident.clone();
#[cfg(feature = "nightly")]
let mut task_outer: ItemFn = parse_quote! { let mut task_outer: ItemFn = parse_quote! {
#visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> { #visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> {
type Fut = impl ::core::future::Future + 'static; type Fut = impl ::core::future::Future + 'static;
@ -87,6 +88,14 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
unsafe { POOL._spawn_async_fn(move || #task_inner_ident(#(#arg_names,)*)) } unsafe { POOL._spawn_async_fn(move || #task_inner_ident(#(#arg_names,)*)) }
} }
}; };
#[cfg(not(feature = "nightly"))]
let mut task_outer: ItemFn = parse_quote! {
#visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> {
const POOL_SIZE: usize = #pool_size;
static POOL: ::embassy_executor::_export::TaskPoolRef = ::embassy_executor::_export::TaskPoolRef::new();
unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#arg_names,)*)) }
}
};
task_outer.attrs.append(&mut task_inner.attrs.clone()); task_outer.attrs.append(&mut task_inner.attrs.clone());

View File

@ -10,21 +10,19 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
heapless = "0.7.16" heapless = "0.8"
defmt = { version = "0.3", optional = true } defmt = { version = "0.3", optional = true }
log = { version = "0.4", default-features = false, optional = true } log = { version = "0.4", default-features = false, optional = true }
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" } embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" }
embedded-hal-async = { version = "=1.0.0-rc.1" } embedded-hal-async = { version = "=1.0.0-rc.1" }
embedded-hal-bus = { version = "=0.1.0-rc.1", features = ["async"] } embedded-hal-bus = { version = "=0.1.0-rc.1", features = ["async"] }
embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" } embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel" }
embassy-time = { version = "0.1.4", path = "../embassy-time" } embassy-time = { version = "0.1.5", path = "../embassy-time" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
bitfield = "0.14.0" bitfield = "0.14.0"
[dev-dependencies] [dev-dependencies]
# reenable when https://github.com/dbrgn/embedded-hal-mock/pull/86 is merged. embedded-hal-mock = { version = "=0.10.0-rc.1", features = ["embedded-hal-async", "eh1"] }
#embedded-hal-mock = { git = "https://github.com/dbrgn/embedded-hal-mock", branch = "1-alpha", features = ["embedded-hal-async", "eh1"] }] }
embedded-hal-mock = { git = "https://github.com/newAM/embedded-hal-mock", branch = "eh1-rc.1", features = ["embedded-hal-async", "eh1"] }
crc = "3.0.1" crc = "3.0.1"
env_logger = "0.10" env_logger = "0.10"
critical-section = { version = "1.1.2", features = ["std"] } critical-section = { version = "1.1.2", features = ["std"] }

View File

@ -1,5 +1,6 @@
#![deny(clippy::pedantic)] #![deny(clippy::pedantic)]
#![feature(async_fn_in_trait)] #![feature(async_fn_in_trait)]
#![allow(stable_features, unknown_lints, async_fn_in_trait)]
#![cfg_attr(not(any(test, feature = "std")), no_std)] #![cfg_attr(not(any(test, feature = "std")), no_std)]
#![allow(clippy::module_name_repetitions)] #![allow(clippy::module_name_repetitions)]
#![allow(clippy::missing_errors_doc)] #![allow(clippy::missing_errors_doc)]
@ -20,7 +21,7 @@ pub use crc32::ETH_FCS;
use crc8::crc8; use crc8::crc8;
use embassy_futures::select::{select, Either}; use embassy_futures::select::{select, Either};
use embassy_net_driver_channel as ch; use embassy_net_driver_channel as ch;
use embassy_time::{Duration, Timer}; use embassy_time::Timer;
use embedded_hal_1::digital::OutputPin; use embedded_hal_1::digital::OutputPin;
use embedded_hal_async::digital::Wait; use embedded_hal_async::digital::Wait;
use embedded_hal_async::spi::{Error, Operation, SpiDevice}; use embedded_hal_async::spi::{Error, Operation, SpiDevice};
@ -609,12 +610,12 @@ pub async fn new<const N_RX: usize, const N_TX: usize, SPI: SpiDevice, INT: Wait
reset.set_low().unwrap(); reset.set_low().unwrap();
// Wait t1: 20-43mS // Wait t1: 20-43mS
Timer::after(Duration::from_millis(30)).await; Timer::after_millis(30).await;
reset.set_high().unwrap(); reset.set_high().unwrap();
// Wait t3: 50mS // Wait t3: 50mS
Timer::after(Duration::from_millis(50)).await; Timer::after_millis(50).await;
// Create device // Create device
let mut mac = ADIN1110::new(spi_dev, spi_crc, append_fcs_on_tx); let mut mac = ADIN1110::new(spi_dev, spi_crc, append_fcs_on_tx);

View File

@ -0,0 +1,16 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 0.2.0 - 2023-10-18
- Update `embassy-net-driver` to v0.2
- `Runner::new` now takes an `embassy_net_driver::HardwareAddress` parameter.
- `Runner::set_ethernet_address` is now `set_hardware_address`.
## 0.1.0 - 2023-06-29
- First release

View File

@ -1,6 +1,6 @@
[package] [package]
name = "embassy-net-driver-channel" name = "embassy-net-driver-channel"
version = "0.1.0" version = "0.2.0"
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "High-level channel-based driver for the `embassy-net` async TCP/IP network stack." description = "High-level channel-based driver for the `embassy-net` async TCP/IP network stack."
@ -24,6 +24,6 @@ features = ["defmt"]
defmt = { version = "0.3", optional = true } defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true } log = { version = "0.4.14", optional = true }
embassy-sync = { version = "0.3.0", path = "../embassy-sync" } embassy-sync = { version = "0.4.0", path = "../embassy-sync" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" }

View File

@ -7,7 +7,9 @@ The `embassy-net-driver` trait is polling-based. To implement it, you must write
hand, and hook up the `Waker`s provided by `embassy-net` to the right interrupt handlers so that `embassy-net` hand, and hook up the `Waker`s provided by `embassy-net` to the right interrupt handlers so that `embassy-net`
knows when to poll your driver again to make more progress. knows when to poll your driver again to make more progress.
With `embassy-net-driver-channel` With `embassy-net-driver-channel` you get a "channel-like" interface instead, where you can send/receive packets
to/from embassy-net. The intended usage is to spawn a "driver task" in the background that does this, passing
packets between the hardware and the channel.
## A note about deadlocks ## A note about deadlocks
@ -18,19 +20,19 @@ loop {
// Wait for either.. // Wait for either..
match select( match select(
// ... the chip signaling an interrupt, indicating a packet is available to receive, or // ... the chip signaling an interrupt, indicating a packet is available to receive, or
irq_pin.wait_for_low(), irq_pin.wait_for_low(),
// ... a TX buffer becoming available, i.e. embassy-net wants to send a packet // ... a TX buffer becoming available, i.e. embassy-net wants to send a packet
tx_chan.tx_buf(), tx_chan.tx_buf(),
).await { ).await {
Either::First(_) => { Either::First(_) => {
// a packet is ready to be received! // a packet is ready to be received!
let buf = rx_chan.rx_buf().await; // allocate a rx buf from the packet queue let buf = rx_chan.rx_buf().await; // allocate a rx buf from the packet queue
let n = receive_packet_over_spi(buf).await; let n = receive_packet_over_spi(buf).await;
rx_chan.rx_done(n); rx_chan.rx_done(n);
} }
Either::Second(buf) => { Either::Second(buf) => {
// a packet is ready to be sent! // a packet is ready to be sent!
send_packet_over_spi(buf).await; send_packet_over_spi(buf).await;
tx_chan.tx_done(); tx_chan.tx_done();
} }
} }
@ -41,7 +43,7 @@ However, this code has a latent deadlock bug. The symptom is it can hang at `rx_
The reason is that, under load, both the TX and RX queues can get full at the same time. When this happens, the `embassy-net` task stalls trying to send because the TX queue is full, therefore it stops processing packets in the RX queue. Your driver task also stalls because the RX queue is full, therefore it stops processing packets in the TX queue. The reason is that, under load, both the TX and RX queues can get full at the same time. When this happens, the `embassy-net` task stalls trying to send because the TX queue is full, therefore it stops processing packets in the RX queue. Your driver task also stalls because the RX queue is full, therefore it stops processing packets in the TX queue.
The fix is to make sure to always service the TX queue while you're waiting for space to become available in the TX queue. For example, select on either "tx_chan.tx_buf() available" or "INT is low AND rx_chan.rx_buf() available": The fix is to make sure to always service the TX queue while you're waiting for space to become available in the RX queue. For example, select on either "tx_chan.tx_buf() available" or "INT is low AND rx_chan.rx_buf() available":
```rust,ignore ```rust,ignore
loop { loop {
@ -58,12 +60,12 @@ loop {
).await { ).await {
Either::First(buf) => { Either::First(buf) => {
// a packet is ready to be received! // a packet is ready to be received!
let n = receive_packet_over_spi(buf).await; let n = receive_packet_over_spi(buf).await;
rx_chan.rx_done(n); rx_chan.rx_done(n);
} }
Either::Second(buf) => { Either::Second(buf) => {
// a packet is ready to be sent! // a packet is ready to be sent!
send_packet_over_spi(buf).await; send_packet_over_spi(buf).await;
tx_chan.tx_done(); tx_chan.tx_done();
} }
} }
@ -79,12 +81,10 @@ These `embassy-net` drivers are implemented using this crate. You can look at th
- [`embassy-net-wiznet`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-wiznet) for Wiznet SPI Ethernet MAC+PHY chips. - [`embassy-net-wiznet`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-wiznet) for Wiznet SPI Ethernet MAC+PHY chips.
- [`embassy-net-esp-hosted`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-esp-hosted) for using ESP32 chips with the [`esp-hosted`](https://github.com/espressif/esp-hosted) firmware as WiFi adapters for another non-ESP32 MCU. - [`embassy-net-esp-hosted`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-esp-hosted) for using ESP32 chips with the [`esp-hosted`](https://github.com/espressif/esp-hosted) firmware as WiFi adapters for another non-ESP32 MCU.
## Interoperability ## Interoperability
This crate can run on any executor. This crate can run on any executor.
## License ## License
This work is licensed under either of This work is licensed under either of

View File

@ -8,9 +8,8 @@ use core::cell::RefCell;
use core::mem::MaybeUninit; use core::mem::MaybeUninit;
use core::task::{Context, Poll}; use core::task::{Context, Poll};
use driver::HardwareAddress;
pub use embassy_net_driver as driver; pub use embassy_net_driver as driver;
use embassy_net_driver::{Capabilities, LinkState, Medium}; use embassy_net_driver::{Capabilities, LinkState};
use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::blocking_mutex::Mutex; use embassy_sync::blocking_mutex::Mutex;
use embassy_sync::waitqueue::WakerRegistration; use embassy_sync::waitqueue::WakerRegistration;
@ -161,18 +160,10 @@ impl<'d> StateRunner<'d> {
}); });
} }
pub fn set_ethernet_address(&self, address: [u8; 6]) { pub fn set_hardware_address(&self, address: driver::HardwareAddress) {
self.shared.lock(|s| { self.shared.lock(|s| {
let s = &mut *s.borrow_mut(); let s = &mut *s.borrow_mut();
s.hardware_address = driver::HardwareAddress::Ethernet(address); s.hardware_address = address;
s.waker.wake();
});
}
pub fn set_ieee802154_address(&self, address: [u8; 8]) {
self.shared.lock(|s| {
let s = &mut *s.borrow_mut();
s.hardware_address = driver::HardwareAddress::Ieee802154(address);
s.waker.wake(); s.waker.wake();
}); });
} }
@ -232,11 +223,6 @@ pub fn new<'d, const MTU: usize, const N_RX: usize, const N_TX: usize>(
) -> (Runner<'d, MTU>, Device<'d, MTU>) { ) -> (Runner<'d, MTU>, Device<'d, MTU>) {
let mut caps = Capabilities::default(); let mut caps = Capabilities::default();
caps.max_transmission_unit = MTU; caps.max_transmission_unit = MTU;
caps.medium = match &hardware_address {
HardwareAddress::Ethernet(_) => Medium::Ethernet,
HardwareAddress::Ieee802154(_) => Medium::Ieee802154,
HardwareAddress::Ip => Medium::Ip,
};
// safety: this is a self-referential struct, however: // safety: this is a self-referential struct, however:
// - it can't move while the `'d` borrow is active. // - it can't move while the `'d` borrow is active.

View File

@ -0,0 +1,17 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 0.2.0 - 2023-10-18
- Added support for IEEE 802.15.4 mediums.
- Added `Driver::hardware_address()`, `HardwareAddress`.
- Removed `Medium` enum. The medium is deduced out of the hardware address.
- Removed `Driver::ethernet_address()`. Replacement is `hardware_address()`.
## 0.1.0 - 2023-06-29
- First release

View File

@ -1,6 +1,6 @@
[package] [package]
name = "embassy-net-driver" name = "embassy-net-driver"
version = "0.1.0" version = "0.2.0"
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "Driver trait for the `embassy-net` async TCP/IP network stack." description = "Driver trait for the `embassy-net` async TCP/IP network stack."

View File

@ -7,12 +7,23 @@ use core::task::Context;
/// Representation of an hardware address, such as an Ethernet address or an IEEE802.15.4 address. /// Representation of an hardware address, such as an Ethernet address or an IEEE802.15.4 address.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum HardwareAddress { pub enum HardwareAddress {
/// A six-octet Ethernet address /// Ethernet medium, with a A six-octet Ethernet address.
///
/// 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([u8; 6]), Ethernet([u8; 6]),
/// An eight-octet IEEE802.15.4 address /// 6LoWPAN over IEEE802.15.4, with an eight-octet address.
Ieee802154([u8; 8]), Ieee802154([u8; 8]),
/// Indicates that a Driver is IP-native, and has no hardware address /// Indicates that a Driver is IP-native, and has no hardware address.
///
/// 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, Ip,
} }
@ -64,6 +75,10 @@ pub trait Driver {
fn capabilities(&self) -> Capabilities; fn capabilities(&self) -> Capabilities;
/// Get the device's hardware address. /// Get the device's hardware address.
///
/// The returned hardware address also determines the "medium" of this driver. This indicates
/// what kind of packet the sent/received bytes are, and determines some behaviors of
/// the interface. For example, ARP/NDISC address resolution is only done for Ethernet mediums.
fn hardware_address(&self) -> HardwareAddress; fn hardware_address(&self) -> HardwareAddress;
} }
@ -124,13 +139,6 @@ pub trait TxToken {
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive] #[non_exhaustive]
pub struct Capabilities { 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. /// Maximum transmission unit.
/// ///
/// The network device is unable to send or receive frames larger than the value returned /// The network device is unable to send or receive frames larger than the value returned
@ -161,32 +169,6 @@ pub struct Capabilities {
pub checksum: ChecksumCapabilities, 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,
/// IEEE 802_15_4 medium
Ieee802154,
}
impl Default for Medium {
fn default() -> Medium {
Medium::Ethernet
}
}
/// A description of checksum behavior for every supported protocol. /// A description of checksum behavior for every supported protocol.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]

View File

@ -10,8 +10,8 @@ edition = "2021"
[dependencies] [dependencies]
embedded-hal = { version = "1.0.0-rc.1" } embedded-hal = { version = "1.0.0-rc.1" }
embedded-hal-async = { version = "=1.0.0-rc.1" } embedded-hal-async = { version = "=1.0.0-rc.1" }
embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" }
embassy-time = { version = "0.1.4", path = "../embassy-time" } embassy-time = { version = "0.1.5", path = "../embassy-time" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
defmt = { version = "0.3", optional = true } defmt = { version = "0.3", optional = true }

View File

@ -19,7 +19,7 @@ mod traits;
use core::cmp; use core::cmp;
use core::convert::TryInto; use core::convert::TryInto;
use embassy_net_driver::{Capabilities, HardwareAddress, LinkState, Medium}; use embassy_net_driver::{Capabilities, HardwareAddress, LinkState};
use embassy_time::Duration; use embassy_time::Duration;
use embedded_hal::digital::OutputPin; use embedded_hal::digital::OutputPin;
use embedded_hal::spi::{Operation, SpiDevice}; use embedded_hal::spi::{Operation, SpiDevice};
@ -671,7 +671,6 @@ where
fn capabilities(&self) -> Capabilities { fn capabilities(&self) -> Capabilities {
let mut caps = Capabilities::default(); let mut caps = Capabilities::default();
caps.max_transmission_unit = MTU; caps.max_transmission_unit = MTU;
caps.medium = Medium::Ethernet;
caps caps
} }

View File

@ -7,17 +7,17 @@ edition = "2021"
defmt = { version = "0.3", optional = true } defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true } log = { version = "0.4.14", optional = true }
embassy-time = { version = "0.1.4", path = "../embassy-time" } embassy-time = { version = "0.1.5", path = "../embassy-time" }
embassy-sync = { version = "0.3.0", path = "../embassy-sync"} embassy-sync = { version = "0.4.0", path = "../embassy-sync"}
embassy-futures = { version = "0.1.0", path = "../embassy-futures"} embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"} embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel"}
embedded-hal = { version = "1.0.0-rc.1" } embedded-hal = { version = "1.0.0-rc.1" }
embedded-hal-async = { version = "=1.0.0-rc.1" } embedded-hal-async = { version = "=1.0.0-rc.1" }
noproto = { git="https://github.com/embassy-rs/noproto", default-features = false, features = ["derive"] } noproto = { git="https://github.com/embassy-rs/noproto", rev = "f5e6d1f325b6ad4e344f60452b09576e24671f62", default-features = false, features = ["derive"] }
#noproto = { version = "0.1", path = "/home/dirbaio/noproto", default-features = false, features = ["derive"] } #noproto = { version = "0.1", path = "/home/dirbaio/noproto", default-features = false, features = ["derive"] }
heapless = "0.7.16" heapless = "0.8"
[package.metadata.embassy_docs] [package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-esp-hosted-v$VERSION/embassy-net-esp-hosted/src/" src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-esp-hosted-v$VERSION/embassy-net-esp-hosted/src/"

View File

@ -1,5 +1,5 @@
use ch::driver::LinkState;
use embassy_net_driver_channel as ch; use embassy_net_driver_channel as ch;
use embassy_net_driver_channel::driver::{HardwareAddress, LinkState};
use heapless::String; use heapless::String;
use crate::ioctl::Shared; use crate::ioctl::Shared;
@ -77,7 +77,7 @@ impl<'a> Control<'a> {
let mac_addr = self.get_mac_addr().await?; let mac_addr = self.get_mac_addr().await?;
debug!("mac addr: {:02x}", mac_addr); debug!("mac addr: {:02x}", mac_addr);
self.state_ch.set_ethernet_address(mac_addr); self.state_ch.set_hardware_address(HardwareAddress::Ethernet(mac_addr));
Ok(()) Ok(())
} }
@ -97,8 +97,8 @@ impl<'a> Control<'a> {
pub async fn connect(&mut self, ssid: &str, password: &str) -> Result<(), Error> { pub async fn connect(&mut self, ssid: &str, password: &str) -> Result<(), Error> {
let req = proto::CtrlMsgReqConnectAp { let req = proto::CtrlMsgReqConnectAp {
ssid: String::from(ssid), ssid: unwrap!(String::try_from(ssid)),
pwd: String::from(password), pwd: unwrap!(String::try_from(password)),
bssid: String::new(), bssid: String::new(),
listen_interval: 3, listen_interval: 3,
is_wpa3_supported: false, is_wpa3_supported: false,

View File

@ -169,9 +169,9 @@ where
pub async fn run(mut self) -> ! { pub async fn run(mut self) -> ! {
debug!("resetting..."); debug!("resetting...");
self.reset.set_low().unwrap(); self.reset.set_low().unwrap();
Timer::after(Duration::from_millis(100)).await; Timer::after_millis(100).await;
self.reset.set_high().unwrap(); self.reset.set_high().unwrap();
Timer::after(Duration::from_millis(1000)).await; Timer::after_millis(1000).await;
let mut tx_buf = [0u8; MAX_SPI_BUFFER_SIZE]; let mut tx_buf = [0u8; MAX_SPI_BUFFER_SIZE];
let mut rx_buf = [0u8; MAX_SPI_BUFFER_SIZE]; let mut rx_buf = [0u8; MAX_SPI_BUFFER_SIZE];

View File

@ -16,10 +16,10 @@ defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true } log = { version = "0.4.14", optional = true }
embedded-io-async = { version = "0.6.0" } embedded-io-async = { version = "0.6.0" }
embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" } embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
ppproto = { version = "0.1.2"} ppproto = { version = "0.1.2"}
embassy-sync = { version = "0.3.0", path = "../embassy-sync" } embassy-sync = { version = "0.4.0", path = "../embassy-sync" }
[package.metadata.embassy_docs] [package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-ppp-v$VERSION/embassy-net-ppp/src/" src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-ppp-v$VERSION/embassy-net-ppp/src/"

View File

@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" }
async-io = "1.6.0" async-io = "1.6.0"
log = "0.4.14" log = "0.4.14"
libc = "0.2.101" libc = "0.2.101"

View File

@ -10,8 +10,8 @@ edition = "2021"
[dependencies] [dependencies]
embedded-hal = { version = "1.0.0-rc.1" } embedded-hal = { version = "1.0.0-rc.1" }
embedded-hal-async = { version = "=1.0.0-rc.1" } embedded-hal-async = { version = "=1.0.0-rc.1" }
embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" } embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel" }
embassy-time = { version = "0.1.4", path = "../embassy-time" } embassy-time = { version = "0.1.5", path = "../embassy-time" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
defmt = { version = "0.3", optional = true } defmt = { version = "0.3", optional = true }

View File

@ -1,14 +1,15 @@
//! [`embassy-net`](https://crates.io/crates/embassy-net) driver for WIZnet ethernet chips.
#![no_std] #![no_std]
#![feature(async_fn_in_trait)] #![feature(async_fn_in_trait)]
#![allow(stable_features, unknown_lints, async_fn_in_trait)]
#![doc = include_str!("../README.md")]
pub mod chip; pub mod chip;
mod device; mod device;
use embassy_futures::select::{select, Either}; use embassy_futures::select::{select3, Either3};
use embassy_net_driver_channel as ch; use embassy_net_driver_channel as ch;
use embassy_net_driver_channel::driver::LinkState; use embassy_net_driver_channel::driver::LinkState;
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Ticker, Timer};
use embedded_hal::digital::OutputPin; use embedded_hal::digital::OutputPin;
use embedded_hal_async::digital::Wait; use embedded_hal_async::digital::Wait;
use embedded_hal_async::spi::SpiDevice; use embedded_hal_async::spi::SpiDevice;
@ -49,32 +50,34 @@ pub struct Runner<'d, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin> {
impl<'d, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, C, SPI, INT, RST> { impl<'d, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, C, SPI, INT, RST> {
pub async fn run(mut self) -> ! { pub async fn run(mut self) -> ! {
let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split();
let mut tick = Ticker::every(Duration::from_millis(500));
loop { loop {
if self.mac.is_link_up().await { match select3(
state_chan.set_link_state(LinkState::Up); async {
loop { self.int.wait_for_low().await.ok();
match select( rx_chan.rx_buf().await
async { },
self.int.wait_for_low().await.ok(); tx_chan.tx_buf(),
rx_chan.rx_buf().await tick.next(),
}, )
tx_chan.tx_buf(), .await
) {
.await Either3::First(p) => {
{ if let Ok(n) = self.mac.read_frame(p).await {
Either::First(p) => { rx_chan.rx_done(n);
if let Ok(n) = self.mac.read_frame(p).await { }
rx_chan.rx_done(n); }
} Either3::Second(p) => {
} self.mac.write_frame(p).await.ok();
Either::Second(p) => { tx_chan.tx_done();
self.mac.write_frame(p).await.ok(); }
tx_chan.tx_done(); Either3::Third(()) => {
} if self.mac.is_link_up().await {
state_chan.set_link_state(LinkState::Up);
} else {
state_chan.set_link_state(LinkState::Down);
} }
} }
} else {
state_chan.set_link_state(LinkState::Down);
} }
} }
} }
@ -95,12 +98,12 @@ pub async fn new<'a, const N_RX: usize, const N_TX: usize, C: Chip, SPI: SpiDevi
// Reset the chip. // Reset the chip.
reset.set_low().ok(); reset.set_low().ok();
// Ensure the reset is registered. // Ensure the reset is registered.
Timer::after(Duration::from_millis(1)).await; Timer::after_millis(1).await;
reset.set_high().ok(); reset.set_high().ok();
// Wait for PLL lock. Some chips are slower than others. // Wait for PLL lock. Some chips are slower than others.
// Slowest is w5100s which is 100ms, so let's just wait that. // Slowest is w5100s which is 100ms, so let's just wait that.
Timer::after(Duration::from_millis(100)).await; Timer::after_millis(100).await;
let mac = WiznetDevice::new(spi_dev, mac_addr).await.unwrap(); let mac = WiznetDevice::new(spi_dev, mac_addr).await.unwrap();

40
embassy-net/CHANGELOG.md Normal file
View File

@ -0,0 +1,40 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
- Avoid never resolving `TcpIo::read` when the output buffer is empty.
- Update to `smoltcp` git.
- Forward constants from `smoltcp` in DNS query results so changing DNS result size in `smoltcp` properly propagates.
## 0.2.1 - 2023-10-31
- Re-add impl_trait_projections
- Fix: Reset DHCP socket when the link up is detected
## 0.2.0 - 2023-10-18
- Re-export `smoltcp::wire::IpEndpoint`
- Add poll functions on UdpSocket
- Make dual-stack work in embassy-net
- Fix multicast support
- Allow ethernet and 802.15.4 to coexist
- Add IEEE802.15.4 address to embassy net Stack
- Use HardwareAddress in Driver
- Add async versions of smoltcp's `send` and `recv` closure based API
- add error translation to tcp errors
- Forward TCP/UDP socket capacity impls
- allow changing IP config at runtime
- allow non-'static drivers
- Remove impl_trait_projections
- update embedded-io, embedded-nal-async
- add support for dhcp hostname option
- Wake stack's task after queueing a DNS query
## 0.1.0 - 2023-06-29
- First release

View File

@ -1,6 +1,6 @@
[package] [package]
name = "embassy-net" name = "embassy-net"
version = "0.1.0" version = "0.2.1"
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "Async TCP/IP network stack for embedded systems" description = "Async TCP/IP network stack for embedded systems"
@ -25,7 +25,7 @@ features = ["nightly", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "me
default = [] default = []
std = [] std = []
defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt"] defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt", "heapless/defmt-03"]
nightly = ["dep:embedded-io-async", "dep:embedded-nal-async"] nightly = ["dep:embedded-io-async", "dep:embedded-nal-async"]
@ -46,21 +46,21 @@ igmp = ["smoltcp/proto-igmp"]
defmt = { version = "0.3", optional = true } defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true } log = { version = "0.4.14", optional = true }
smoltcp = { version = "0.10.0", default-features = false, features = [ smoltcp = { git = "https://github.com/smoltcp-rs/smoltcp.git", rev = "b57e2f9e70e82a13f31d5ea17e55232c11cc2b2d", default-features = false, features = [
"socket", "socket",
"async", "async",
] } ] }
embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" }
embassy-time = { version = "0.1.4", path = "../embassy-time" } embassy-time = { version = "0.1.5", path = "../embassy-time" }
embassy-sync = { version = "0.3.0", path = "../embassy-sync" } embassy-sync = { version = "0.4.0", path = "../embassy-sync" }
embedded-io-async = { version = "0.6.0", optional = true } embedded-io-async = { version = "0.6.0", optional = true }
managed = { version = "0.8.0", default-features = false, features = [ "map" ] } managed = { version = "0.8.0", default-features = false, features = [ "map" ] }
heapless = { version = "0.7.5", default-features = false } heapless = { version = "0.8", default-features = false }
as-slice = "0.2.1" as-slice = "0.2.1"
generic-array = { version = "0.14.4", default-features = false } generic-array = { version = "0.14.4", default-features = false }
stable_deref_trait = { version = "1.2.0", default-features = false } stable_deref_trait = { version = "1.2.0", default-features = false }
futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] } futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] }
atomic-pool = "1.0" atomic-pool = "1.0"
embedded-nal-async = { version = "0.6.0", optional = true } embedded-nal-async = { version = "0.7", optional = true }

View File

@ -4,7 +4,7 @@
It builds on [`smoltcp`](https://github.com/smoltcp-rs/smoltcp). It provides a higher-level and more opinionated It builds on [`smoltcp`](https://github.com/smoltcp-rs/smoltcp). It provides a higher-level and more opinionated
API. It glues together the components provided by `smoltcp`, handling the low-level details with defaults and API. It glues together the components provided by `smoltcp`, handling the low-level details with defaults and
memory management designed to work well for embedded systems, aiiming for a more "Just Works" experience. memory management designed to work well for embedded systems, aiming for a more "Just Works" experience.
## Features ## Features

View File

@ -1,7 +1,7 @@
use core::task::Context; use core::task::Context;
use embassy_net_driver::{Capabilities, Checksum, Driver, Medium, RxToken, TxToken}; use embassy_net_driver::{Capabilities, Checksum, Driver, RxToken, TxToken};
use smoltcp::phy; use smoltcp::phy::{self, Medium};
use smoltcp::time::Instant; use smoltcp::time::Instant;
pub(crate) struct DriverAdapter<'d, 'c, T> pub(crate) struct DriverAdapter<'d, 'c, T>
@ -11,6 +11,7 @@ where
// must be Some when actually using this to rx/tx // must be Some when actually using this to rx/tx
pub cx: Option<&'d mut Context<'c>>, pub cx: Option<&'d mut Context<'c>>,
pub inner: &'d mut T, pub inner: &'d mut T,
pub medium: Medium,
} }
impl<'d, 'c, T> phy::Device for DriverAdapter<'d, 'c, T> impl<'d, 'c, T> phy::Device for DriverAdapter<'d, 'c, T>
@ -46,19 +47,7 @@ where
smolcaps.max_transmission_unit = caps.max_transmission_unit; smolcaps.max_transmission_unit = caps.max_transmission_unit;
smolcaps.max_burst_size = caps.max_burst_size; smolcaps.max_burst_size = caps.max_burst_size;
smolcaps.medium = match caps.medium { smolcaps.medium = self.medium;
#[cfg(feature = "medium-ethernet")]
Medium::Ethernet => phy::Medium::Ethernet,
#[cfg(feature = "medium-ip")]
Medium::Ip => phy::Medium::Ip,
#[cfg(feature = "medium-ieee802154")]
Medium::Ieee802154 => phy::Medium::Ieee802154,
#[allow(unreachable_patterns)]
_ => 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.ipv4 = convert(caps.checksum.ipv4);
smolcaps.checksum.tcp = convert(caps.checksum.tcp); smolcaps.checksum.tcp = convert(caps.checksum.tcp);
smolcaps.checksum.udp = convert(caps.checksum.udp); smolcaps.checksum.udp = convert(caps.checksum.udp);

View File

@ -63,7 +63,11 @@ where
} }
/// Make a query for a given name and return the corresponding IP addresses. /// 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> { pub async fn query(
&self,
name: &str,
qtype: DnsQueryType,
) -> Result<Vec<IpAddress, { smoltcp::config::DNS_MAX_RESULT_COUNT }>, Error> {
self.stack.dns_query(name, qtype).await self.stack.dns_query(name, qtype).await
} }
} }
@ -101,7 +105,8 @@ where
async fn get_host_by_address( async fn get_host_by_address(
&self, &self,
_addr: embedded_nal_async::IpAddr, _addr: embedded_nal_async::IpAddr,
) -> Result<heapless::String<256>, Self::Error> { _result: &mut [u8],
) -> Result<usize, Self::Error> {
todo!() todo!()
} }
} }

View File

@ -1,5 +1,6 @@
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(feature = "nightly", feature(async_fn_in_trait))] #![cfg_attr(feature = "nightly", feature(async_fn_in_trait, impl_trait_projections))]
#![cfg_attr(feature = "nightly", allow(stable_features, unknown_lints, async_fn_in_trait))]
#![warn(missing_docs)] #![warn(missing_docs)]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
@ -33,6 +34,7 @@ use heapless::Vec;
pub use smoltcp::iface::MulticastError; pub use smoltcp::iface::MulticastError;
#[allow(unused_imports)] #[allow(unused_imports)]
use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage}; use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage};
use smoltcp::phy::Medium;
#[cfg(feature = "dhcpv4")] #[cfg(feature = "dhcpv4")]
use smoltcp::socket::dhcpv4::{self, RetryConfig}; use smoltcp::socket::dhcpv4::{self, RetryConfig};
#[cfg(feature = "medium-ethernet")] #[cfg(feature = "medium-ethernet")]
@ -264,14 +266,17 @@ pub(crate) struct SocketStack {
next_local_port: u16, next_local_port: u16,
} }
fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> HardwareAddress { fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> (HardwareAddress, Medium) {
match addr { match addr {
#[cfg(feature = "medium-ethernet")] #[cfg(feature = "medium-ethernet")]
driver::HardwareAddress::Ethernet(eth) => HardwareAddress::Ethernet(EthernetAddress(eth)), driver::HardwareAddress::Ethernet(eth) => (HardwareAddress::Ethernet(EthernetAddress(eth)), Medium::Ethernet),
#[cfg(feature = "medium-ieee802154")] #[cfg(feature = "medium-ieee802154")]
driver::HardwareAddress::Ieee802154(ieee) => HardwareAddress::Ieee802154(Ieee802154Address::Extended(ieee)), driver::HardwareAddress::Ieee802154(ieee) => (
HardwareAddress::Ieee802154(Ieee802154Address::Extended(ieee)),
Medium::Ieee802154,
),
#[cfg(feature = "medium-ip")] #[cfg(feature = "medium-ip")]
driver::HardwareAddress::Ip => HardwareAddress::Ip, driver::HardwareAddress::Ip => (HardwareAddress::Ip, Medium::Ip),
#[allow(unreachable_patterns)] #[allow(unreachable_patterns)]
_ => panic!( _ => panic!(
@ -289,7 +294,8 @@ impl<D: Driver> Stack<D> {
resources: &'static mut StackResources<SOCK>, resources: &'static mut StackResources<SOCK>,
random_seed: u64, random_seed: u64,
) -> Self { ) -> Self {
let mut iface_cfg = smoltcp::iface::Config::new(to_smoltcp_hardware_address(device.hardware_address())); let (hardware_addr, medium) = to_smoltcp_hardware_address(device.hardware_address());
let mut iface_cfg = smoltcp::iface::Config::new(hardware_addr);
iface_cfg.random_seed = random_seed; iface_cfg.random_seed = random_seed;
let iface = Interface::new( let iface = Interface::new(
@ -297,6 +303,7 @@ impl<D: Driver> Stack<D> {
&mut DriverAdapter { &mut DriverAdapter {
inner: &mut device, inner: &mut device,
cx: None, cx: None,
medium,
}, },
instant_to_smoltcp(Instant::now()), instant_to_smoltcp(Instant::now()),
); );
@ -356,7 +363,7 @@ impl<D: Driver> Stack<D> {
/// Get the hardware address of the network interface. /// Get the hardware address of the network interface.
pub fn hardware_address(&self) -> HardwareAddress { pub fn hardware_address(&self) -> HardwareAddress {
self.with(|_s, i| to_smoltcp_hardware_address(i.device.hardware_address())) self.with(|_s, i| to_smoltcp_hardware_address(i.device.hardware_address()).0)
} }
/// Get whether the link is up. /// Get whether the link is up.
@ -487,7 +494,11 @@ impl<D: Driver> Stack<D> {
/// Make a query for a given name and return the corresponding IP addresses. /// Make a query for a given name and return the corresponding IP addresses.
#[cfg(feature = "dns")] #[cfg(feature = "dns")]
pub async fn dns_query(&self, name: &str, qtype: dns::DnsQueryType) -> Result<Vec<IpAddress, 1>, dns::Error> { pub async fn dns_query(
&self,
name: &str,
qtype: dns::DnsQueryType,
) -> Result<Vec<IpAddress, { smoltcp::config::DNS_MAX_RESULT_COUNT }>, dns::Error> {
// For A and AAAA queries we try detect whether `name` is just an IP address // For A and AAAA queries we try detect whether `name` is just an IP address
match qtype { match qtype {
#[cfg(feature = "proto-ipv4")] #[cfg(feature = "proto-ipv4")]
@ -605,9 +616,11 @@ impl<D: Driver> Stack<D> {
let addr = addr.into(); let addr = addr.into();
self.with_mut(|s, i| { self.with_mut(|s, i| {
let (_hardware_addr, medium) = to_smoltcp_hardware_address(i.device.hardware_address());
let mut smoldev = DriverAdapter { let mut smoldev = DriverAdapter {
cx: Some(cx), cx: Some(cx),
inner: &mut i.device, inner: &mut i.device,
medium,
}; };
match s match s
@ -642,9 +655,11 @@ impl<D: Driver> Stack<D> {
let addr = addr.into(); let addr = addr.into();
self.with_mut(|s, i| { self.with_mut(|s, i| {
let (_hardware_addr, medium) = to_smoltcp_hardware_address(i.device.hardware_address());
let mut smoldev = DriverAdapter { let mut smoldev = DriverAdapter {
cx: Some(cx), cx: Some(cx),
inner: &mut i.device, inner: &mut i.device,
medium,
}; };
match s match s
@ -812,18 +827,28 @@ impl<D: Driver> Inner<D> {
fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) { fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) {
s.waker.register(cx.waker()); s.waker.register(cx.waker());
let (_hardware_addr, medium) = to_smoltcp_hardware_address(self.device.hardware_address());
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
if self.device.capabilities().medium == embassy_net_driver::Medium::Ethernet
|| self.device.capabilities().medium == embassy_net_driver::Medium::Ieee802154
{ {
s.iface let do_set = match medium {
.set_hardware_addr(to_smoltcp_hardware_address(self.device.hardware_address())); #[cfg(feature = "medium-ethernet")]
Medium::Ethernet => true,
#[cfg(feature = "medium-ieee802154")]
Medium::Ieee802154 => true,
#[allow(unreachable_patterns)]
_ => false,
};
if do_set {
s.iface.set_hardware_addr(_hardware_addr);
}
} }
let timestamp = instant_to_smoltcp(Instant::now()); let timestamp = instant_to_smoltcp(Instant::now());
let mut smoldev = DriverAdapter { let mut smoldev = DriverAdapter {
cx: Some(cx), cx: Some(cx),
inner: &mut self.device, inner: &mut self.device,
medium,
}; };
s.iface.poll(timestamp, &mut smoldev, &mut s.sockets); s.iface.poll(timestamp, &mut smoldev, &mut s.sockets);
@ -844,6 +869,9 @@ impl<D: Driver> Inner<D> {
let socket = s.sockets.get_mut::<dhcpv4::Socket>(dhcp_handle); let socket = s.sockets.get_mut::<dhcpv4::Socket>(dhcp_handle);
if self.link_up { if self.link_up {
if old_link_up != self.link_up {
socket.reset();
}
match socket.poll() { match socket.poll() {
None => {} None => {}
Some(dhcpv4::Event::Deconfigured) => { Some(dhcpv4::Event::Deconfigured) => {

View File

@ -390,6 +390,13 @@ impl<'d> TcpIo<'d> {
// CAUTION: smoltcp semantics around EOF are different to what you'd expect // CAUTION: smoltcp semantics around EOF are different to what you'd expect
// from posix-like IO, so we have to tweak things here. // from posix-like IO, so we have to tweak things here.
self.with_mut(|s, _| match s.recv_slice(buf) { self.with_mut(|s, _| match s.recv_slice(buf) {
// Reading into empty buffer
Ok(0) if buf.is_empty() => {
// embedded_io_async::Read's contract is to not block if buf is empty. While
// this function is not a direct implementor of the trait method, we still don't
// want our future to never resolve.
Poll::Ready(Ok(0))
}
// No data ready // No data ready
Ok(0) => { Ok(0) => {
s.register_recv_waker(cx.waker()); s.register_recv_waker(cx.waker());
@ -611,10 +618,7 @@ pub mod client {
async fn connect<'a>( async fn connect<'a>(
&'a self, &'a self,
remote: embedded_nal_async::SocketAddr, remote: embedded_nal_async::SocketAddr,
) -> Result<Self::Connection<'a>, Self::Error> ) -> Result<Self::Connection<'a>, Self::Error> {
where
Self: 'a,
{
let addr: crate::IpAddress = match remote.ip() { let addr: crate::IpAddress = match remote.ip() {
#[cfg(feature = "proto-ipv4")] #[cfg(feature = "proto-ipv4")]
IpAddr::V4(addr) => crate::IpAddress::Ipv4(crate::Ipv4Address::from_bytes(&addr.octets())), IpAddr::V4(addr) => crate::IpAddress::Ipv4(crate::Ipv4Address::from_bytes(&addr.octets())),

View File

@ -94,8 +94,8 @@ _gpio-p1 = []
_nrf52832_anomaly_109 = [] _nrf52832_anomaly_109 = []
[dependencies] [dependencies]
embassy-time = { version = "0.1.4", path = "../embassy-time", optional = true } embassy-time = { version = "0.1.5", path = "../embassy-time", optional = true }
embassy-sync = { version = "0.3.0", path = "../embassy-sync" } embassy-sync = { version = "0.4.0", path = "../embassy-sync" }
embassy-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] } embassy-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] }
embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" } embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" }
embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver", optional=true } embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver", optional=true }
@ -110,7 +110,6 @@ defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true } log = { version = "0.4.14", optional = true }
cortex-m-rt = ">=0.6.15,<0.8" cortex-m-rt = ">=0.6.15,<0.8"
cortex-m = "0.7.6" cortex-m = "0.7.6"
futures = { version = "0.3.17", default-features = false }
critical-section = "1.1" critical-section = "1.1"
rand_core = "0.6.3" rand_core = "0.6.3"
fixed = "1.10.0" fixed = "1.10.0"

View File

@ -12,7 +12,7 @@ use core::cmp::min;
use core::future::poll_fn; use core::future::poll_fn;
use core::marker::PhantomData; use core::marker::PhantomData;
use core::slice; use core::slice;
use core::sync::atomic::{compiler_fence, AtomicU8, AtomicUsize, Ordering}; use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU8, AtomicUsize, Ordering};
use core::task::Poll; use core::task::Poll;
use embassy_hal_internal::atomic_ring_buffer::RingBuffer; use embassy_hal_internal::atomic_ring_buffer::RingBuffer;
@ -41,7 +41,9 @@ mod sealed {
pub rx_waker: AtomicWaker, pub rx_waker: AtomicWaker,
pub rx_buf: RingBuffer, pub rx_buf: RingBuffer,
pub rx_bufs: AtomicU8, pub rx_started: AtomicBool,
pub rx_started_count: AtomicU8,
pub rx_ended_count: AtomicU8,
pub rx_ppi_ch: AtomicU8, pub rx_ppi_ch: AtomicU8,
} }
} }
@ -65,7 +67,9 @@ impl State {
rx_waker: AtomicWaker::new(), rx_waker: AtomicWaker::new(),
rx_buf: RingBuffer::new(), rx_buf: RingBuffer::new(),
rx_bufs: AtomicU8::new(0), rx_started: AtomicBool::new(false),
rx_started_count: AtomicU8::new(0),
rx_ended_count: AtomicU8::new(0),
rx_ppi_ch: AtomicU8::new(0), rx_ppi_ch: AtomicU8::new(0),
} }
} }
@ -104,28 +108,20 @@ impl<U: UarteInstance> interrupt::typelevel::Handler<U::Interrupt> for Interrupt
s.rx_waker.wake(); s.rx_waker.wake();
} }
// If not RXing, start. if r.events_endrx.read().bits() != 0 {
if s.rx_bufs.load(Ordering::Relaxed) == 0 { //trace!(" irq_rx: endrx");
let (ptr, len) = rx.push_buf(); r.events_endrx.reset();
if len >= half_len {
//trace!(" irq_rx: starting {:?}", half_len);
s.rx_bufs.store(1, Ordering::Relaxed);
// Set up the DMA read let val = s.rx_ended_count.load(Ordering::Relaxed);
r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); s.rx_ended_count.store(val.wrapping_add(1), Ordering::Relaxed);
r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(half_len as _) });
// Start UARTE Receive transaction
r.tasks_startrx.write(|w| unsafe { w.bits(1) });
rx.push_done(half_len);
r.intenset.write(|w| w.rxstarted().set());
}
} }
if r.events_rxstarted.read().bits() != 0 { if r.events_rxstarted.read().bits() != 0 || !s.rx_started.load(Ordering::Relaxed) {
//trace!(" irq_rx: rxstarted"); //trace!(" irq_rx: rxstarted");
let (ptr, len) = rx.push_buf(); let (ptr, len) = rx.push_buf();
if len >= half_len { if len >= half_len {
r.events_rxstarted.reset();
//trace!(" irq_rx: starting second {:?}", half_len); //trace!(" irq_rx: starting second {:?}", half_len);
// Set up the DMA read // Set up the DMA read
@ -134,11 +130,50 @@ impl<U: UarteInstance> interrupt::typelevel::Handler<U::Interrupt> for Interrupt
let chn = s.rx_ppi_ch.load(Ordering::Relaxed); let chn = s.rx_ppi_ch.load(Ordering::Relaxed);
// Enable endrx -> startrx PPI channel.
// From this point on, if endrx happens, startrx is automatically fired.
ppi::regs().chenset.write(|w| unsafe { w.bits(1 << chn) }); ppi::regs().chenset.write(|w| unsafe { w.bits(1 << chn) });
// It is possible that endrx happened BEFORE enabling the PPI. In this case
// the PPI channel doesn't trigger, and we'd hang. We have to detect this
// and manually start.
// check again in case endrx has happened between the last check and now.
if r.events_endrx.read().bits() != 0 {
//trace!(" irq_rx: endrx");
r.events_endrx.reset();
let val = s.rx_ended_count.load(Ordering::Relaxed);
s.rx_ended_count.store(val.wrapping_add(1), Ordering::Relaxed);
}
let rx_ended = s.rx_ended_count.load(Ordering::Relaxed);
let rx_started = s.rx_started_count.load(Ordering::Relaxed);
// If we started the same amount of transfers as ended, the last rxend has
// already occured.
let rxend_happened = rx_started == rx_ended;
// Check if the PPI channel is still enabled. The PPI channel disables itself
// when it fires, so if it's still enabled it hasn't fired.
let ppi_ch_enabled = ppi::regs().chen.read().bits() & (1 << chn) != 0;
// if rxend happened, and the ppi channel hasn't fired yet, the rxend got missed.
// this condition also naturally matches if `!started`, needed to kickstart the DMA.
if rxend_happened && ppi_ch_enabled {
//trace!("manually starting.");
// disable the ppi ch, it's of no use anymore.
ppi::regs().chenclr.write(|w| unsafe { w.bits(1 << chn) });
// manually start
r.tasks_startrx.write(|w| unsafe { w.bits(1) });
}
rx.push_done(half_len); rx.push_done(half_len);
r.events_rxstarted.reset(); s.rx_started_count.store(rx_started.wrapping_add(1), Ordering::Relaxed);
s.rx_started.store(true, Ordering::Relaxed);
} else { } else {
//trace!(" irq_rx: rxstarted no buf"); //trace!(" irq_rx: rxstarted no buf");
r.intenclr.write(|w| w.rxstarted().clear()); r.intenclr.write(|w| w.rxstarted().clear());
@ -282,6 +317,8 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> {
let r = U::regs(); let r = U::regs();
let hwfc = cts.is_some();
rxd.conf().write(|w| w.input().connect().drive().h0h1()); rxd.conf().write(|w| w.input().connect().drive().h0h1());
r.psel.rxd.write(|w| unsafe { w.bits(rxd.psel_bits()) }); r.psel.rxd.write(|w| unsafe { w.bits(rxd.psel_bits()) });
@ -303,7 +340,8 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> {
// Initialize state // Initialize state
let s = U::buffered_state(); let s = U::buffered_state();
s.tx_count.store(0, Ordering::Relaxed); s.tx_count.store(0, Ordering::Relaxed);
s.rx_bufs.store(0, Ordering::Relaxed); s.rx_started_count.store(0, Ordering::Relaxed);
s.rx_ended_count.store(0, Ordering::Relaxed);
let len = tx_buffer.len(); let len = tx_buffer.len();
unsafe { s.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; unsafe { s.tx_buf.init(tx_buffer.as_mut_ptr(), len) };
let len = rx_buffer.len(); let len = rx_buffer.len();
@ -311,7 +349,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> {
// Configure // Configure
r.config.write(|w| { r.config.write(|w| {
w.hwfc().bit(false); w.hwfc().bit(hwfc);
w.parity().variant(config.parity); w.parity().variant(config.parity);
w w
}); });
@ -333,6 +371,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> {
w.endtx().set(); w.endtx().set();
w.rxstarted().set(); w.rxstarted().set();
w.error().set(); w.error().set();
w.endrx().set();
w w
}); });

View File

@ -6,10 +6,13 @@ pub mod pac {
// To avoid cfg spam, we remove _ns or _s suffixes here. // To avoid cfg spam, we remove _ns or _s suffixes here.
pub use nrf5340_app_pac::NVIC_PRIO_BITS; pub use nrf5340_app_pac::NVIC_PRIO_BITS;
#[cfg(feature="rt")]
#[doc(no_inline)]
pub use nrf5340_app_pac::interrupt;
#[doc(no_inline)] #[doc(no_inline)]
pub use nrf5340_app_pac::{ pub use nrf5340_app_pac::{
interrupt,
Interrupt, Interrupt,
Peripherals, Peripherals,
@ -60,156 +63,167 @@ pub mod pac {
wdt0_ns as wdt0, wdt0_ns as wdt0,
}; };
#[cfg(feature = "nrf5340-app-ns")] /// Non-Secure mode (NS) peripherals
#[doc(no_inline)] pub mod ns {
pub use nrf5340_app_pac::{ #[cfg(feature = "nrf5340-app-ns")]
CLOCK_NS as CLOCK, #[doc(no_inline)]
COMP_NS as COMP, pub use nrf5340_app_pac::{
CTRLAP_NS as CTRLAP, CLOCK_NS as CLOCK,
DCNF_NS as DCNF, COMP_NS as COMP,
DPPIC_NS as DPPIC, CTRLAP_NS as CTRLAP,
EGU0_NS as EGU0, DCNF_NS as DCNF,
EGU1_NS as EGU1, DPPIC_NS as DPPIC,
EGU2_NS as EGU2, EGU0_NS as EGU0,
EGU3_NS as EGU3, EGU1_NS as EGU1,
EGU4_NS as EGU4, EGU2_NS as EGU2,
EGU5_NS as EGU5, EGU3_NS as EGU3,
FPU_NS as FPU, EGU4_NS as EGU4,
GPIOTE1_NS as GPIOTE1, EGU5_NS as EGU5,
I2S0_NS as I2S0, FPU_NS as FPU,
IPC_NS as IPC, GPIOTE1_NS as GPIOTE1,
KMU_NS as KMU, I2S0_NS as I2S0,
LPCOMP_NS as LPCOMP, IPC_NS as IPC,
MUTEX_NS as MUTEX, KMU_NS as KMU,
NFCT_NS as NFCT, LPCOMP_NS as LPCOMP,
NVMC_NS as NVMC, MUTEX_NS as MUTEX,
OSCILLATORS_NS as OSCILLATORS, NFCT_NS as NFCT,
P0_NS as P0, NVMC_NS as NVMC,
P1_NS as P1, OSCILLATORS_NS as OSCILLATORS,
PDM0_NS as PDM0, P0_NS as P0,
POWER_NS as POWER, P1_NS as P1,
PWM0_NS as PWM0, PDM0_NS as PDM0,
PWM1_NS as PWM1, POWER_NS as POWER,
PWM2_NS as PWM2, PWM0_NS as PWM0,
PWM3_NS as PWM3, PWM1_NS as PWM1,
QDEC0_NS as QDEC0, PWM2_NS as PWM2,
QDEC1_NS as QDEC1, PWM3_NS as PWM3,
QSPI_NS as QSPI, QDEC0_NS as QDEC0,
REGULATORS_NS as REGULATORS, QDEC1_NS as QDEC1,
RESET_NS as RESET, QSPI_NS as QSPI,
RTC0_NS as RTC0, REGULATORS_NS as REGULATORS,
RTC1_NS as RTC1, RESET_NS as RESET,
SAADC_NS as SAADC, RTC0_NS as RTC0,
SPIM0_NS as SPIM0, RTC1_NS as RTC1,
SPIM1_NS as SPIM1, SAADC_NS as SAADC,
SPIM2_NS as SPIM2, SPIM0_NS as SPIM0,
SPIM3_NS as SPIM3, SPIM1_NS as SPIM1,
SPIM4_NS as SPIM4, SPIM2_NS as SPIM2,
SPIS0_NS as SPIS0, SPIM3_NS as SPIM3,
SPIS1_NS as SPIS1, SPIM4_NS as SPIM4,
SPIS2_NS as SPIS2, SPIS0_NS as SPIS0,
SPIS3_NS as SPIS3, SPIS1_NS as SPIS1,
TIMER0_NS as TIMER0, SPIS2_NS as SPIS2,
TIMER1_NS as TIMER1, SPIS3_NS as SPIS3,
TIMER2_NS as TIMER2, TIMER0_NS as TIMER0,
TWIM0_NS as TWIM0, TIMER1_NS as TIMER1,
TWIM1_NS as TWIM1, TIMER2_NS as TIMER2,
TWIM2_NS as TWIM2, TWIM0_NS as TWIM0,
TWIM3_NS as TWIM3, TWIM1_NS as TWIM1,
TWIS0_NS as TWIS0, TWIM2_NS as TWIM2,
TWIS1_NS as TWIS1, TWIM3_NS as TWIM3,
TWIS2_NS as TWIS2, TWIS0_NS as TWIS0,
TWIS3_NS as TWIS3, TWIS1_NS as TWIS1,
UARTE0_NS as UARTE0, TWIS2_NS as TWIS2,
UARTE1_NS as UARTE1, TWIS3_NS as TWIS3,
UARTE2_NS as UARTE2, UARTE0_NS as UARTE0,
UARTE3_NS as UARTE3, UARTE1_NS as UARTE1,
USBD_NS as USBD, UARTE2_NS as UARTE2,
USBREGULATOR_NS as USBREGULATOR, UARTE3_NS as UARTE3,
VMC_NS as VMC, USBD_NS as USBD,
WDT0_NS as WDT0, USBREGULATOR_NS as USBREGULATOR,
WDT1_NS as WDT1, VMC_NS as VMC,
}; WDT0_NS as WDT0,
WDT1_NS as WDT1,
};
}
#[cfg(feature = "nrf5340-app-s")] /// Secure mode (S) peripherals
#[doc(no_inline)] pub mod s {
pub use nrf5340_app_pac::{ #[cfg(feature = "nrf5340-app-s")]
CACHEDATA_S as CACHEDATA, #[doc(no_inline)]
CACHEINFO_S as CACHEINFO, pub use nrf5340_app_pac::{
CACHE_S as CACHE, CACHEDATA_S as CACHEDATA,
CLOCK_S as CLOCK, CACHEINFO_S as CACHEINFO,
COMP_S as COMP, CACHE_S as CACHE,
CRYPTOCELL_S as CRYPTOCELL, CLOCK_S as CLOCK,
CTI_S as CTI, COMP_S as COMP,
CTRLAP_S as CTRLAP, CRYPTOCELL_S as CRYPTOCELL,
DCNF_S as DCNF, CTI_S as CTI,
DPPIC_S as DPPIC, CTRLAP_S as CTRLAP,
EGU0_S as EGU0, DCNF_S as DCNF,
EGU1_S as EGU1, DPPIC_S as DPPIC,
EGU2_S as EGU2, EGU0_S as EGU0,
EGU3_S as EGU3, EGU1_S as EGU1,
EGU4_S as EGU4, EGU2_S as EGU2,
EGU5_S as EGU5, EGU3_S as EGU3,
FICR_S as FICR, EGU4_S as EGU4,
FPU_S as FPU, EGU5_S as EGU5,
GPIOTE0_S as GPIOTE0, FICR_S as FICR,
I2S0_S as I2S0, FPU_S as FPU,
IPC_S as IPC, GPIOTE0_S as GPIOTE0,
KMU_S as KMU, I2S0_S as I2S0,
LPCOMP_S as LPCOMP, IPC_S as IPC,
MUTEX_S as MUTEX, KMU_S as KMU,
NFCT_S as NFCT, LPCOMP_S as LPCOMP,
NVMC_S as NVMC, MUTEX_S as MUTEX,
OSCILLATORS_S as OSCILLATORS, NFCT_S as NFCT,
P0_S as P0, NVMC_S as NVMC,
P1_S as P1, OSCILLATORS_S as OSCILLATORS,
PDM0_S as PDM0, P0_S as P0,
POWER_S as POWER, P1_S as P1,
PWM0_S as PWM0, PDM0_S as PDM0,
PWM1_S as PWM1, POWER_S as POWER,
PWM2_S as PWM2, PWM0_S as PWM0,
PWM3_S as PWM3, PWM1_S as PWM1,
QDEC0_S as QDEC0, PWM2_S as PWM2,
QDEC1_S as QDEC1, PWM3_S as PWM3,
QSPI_S as QSPI, QDEC0_S as QDEC0,
REGULATORS_S as REGULATORS, QDEC1_S as QDEC1,
RESET_S as RESET, QSPI_S as QSPI,
RTC0_S as RTC0, REGULATORS_S as REGULATORS,
RTC1_S as RTC1, RESET_S as RESET,
SAADC_S as SAADC, RTC0_S as RTC0,
SPIM0_S as SPIM0, RTC1_S as RTC1,
SPIM1_S as SPIM1, SAADC_S as SAADC,
SPIM2_S as SPIM2, SPIM0_S as SPIM0,
SPIM3_S as SPIM3, SPIM1_S as SPIM1,
SPIM4_S as SPIM4, SPIM2_S as SPIM2,
SPIS0_S as SPIS0, SPIM3_S as SPIM3,
SPIS1_S as SPIS1, SPIM4_S as SPIM4,
SPIS2_S as SPIS2, SPIS0_S as SPIS0,
SPIS3_S as SPIS3, SPIS1_S as SPIS1,
SPU_S as SPU, SPIS2_S as SPIS2,
TAD_S as TAD, SPIS3_S as SPIS3,
TIMER0_S as TIMER0, SPU_S as SPU,
TIMER1_S as TIMER1, TAD_S as TAD,
TIMER2_S as TIMER2, TIMER0_S as TIMER0,
TWIM0_S as TWIM0, TIMER1_S as TIMER1,
TWIM1_S as TWIM1, TIMER2_S as TIMER2,
TWIM2_S as TWIM2, TWIM0_S as TWIM0,
TWIM3_S as TWIM3, TWIM1_S as TWIM1,
TWIS0_S as TWIS0, TWIM2_S as TWIM2,
TWIS1_S as TWIS1, TWIM3_S as TWIM3,
TWIS2_S as TWIS2, TWIS0_S as TWIS0,
TWIS3_S as TWIS3, TWIS1_S as TWIS1,
UARTE0_S as UARTE0, TWIS2_S as TWIS2,
UARTE1_S as UARTE1, TWIS3_S as TWIS3,
UARTE2_S as UARTE2, UARTE0_S as UARTE0,
UARTE3_S as UARTE3, UARTE1_S as UARTE1,
UICR_S as UICR, UARTE2_S as UARTE2,
USBD_S as USBD, UARTE3_S as UARTE3,
USBREGULATOR_S as USBREGULATOR, UICR_S as UICR,
VMC_S as VMC, USBD_S as USBD,
WDT0_S as WDT0, USBREGULATOR_S as USBREGULATOR,
WDT1_S as WDT1, VMC_S as VMC,
}; WDT0_S as WDT0,
WDT1_S as WDT1,
};
}
#[cfg(feature = "_ns")]
pub use ns::*;
#[cfg(feature = "_s")]
pub use s::*;
} }
/// The maximum buffer size that the EasyDMA can send/recv in one operation. /// The maximum buffer size that the EasyDMA can send/recv in one operation.

View File

@ -7,9 +7,12 @@ pub mod pac {
pub use nrf5340_net_pac::NVIC_PRIO_BITS; pub use nrf5340_net_pac::NVIC_PRIO_BITS;
#[cfg(feature="rt")]
#[doc(no_inline)]
pub use nrf5340_net_pac::interrupt;
#[doc(no_inline)] #[doc(no_inline)]
pub use nrf5340_net_pac::{ pub use nrf5340_net_pac::{
interrupt,
Interrupt, Interrupt,
Peripherals, Peripherals,

View File

@ -7,9 +7,12 @@ pub mod pac {
pub use nrf9160_pac::NVIC_PRIO_BITS; pub use nrf9160_pac::NVIC_PRIO_BITS;
#[cfg(feature="rt")]
#[doc(no_inline)]
pub use nrf9160_pac::interrupt;
#[doc(no_inline)] #[doc(no_inline)]
pub use nrf9160_pac::{ pub use nrf9160_pac::{
interrupt,
Interrupt, Interrupt,
cc_host_rgf_s as cc_host_rgf, cc_host_rgf_s as cc_host_rgf,
@ -45,122 +48,131 @@ pub mod pac {
wdt_ns as wdt, wdt_ns as wdt,
}; };
#[cfg(feature = "nrf9160-ns")] /// Non-Secure mode (NS) peripherals
#[doc(no_inline)] pub mod ns {
pub use nrf9160_pac::{ #[doc(no_inline)]
CLOCK_NS as CLOCK, pub use nrf9160_pac::{
DPPIC_NS as DPPIC, CLOCK_NS as CLOCK,
EGU0_NS as EGU0, DPPIC_NS as DPPIC,
EGU1_NS as EGU1, EGU0_NS as EGU0,
EGU2_NS as EGU2, EGU1_NS as EGU1,
EGU3_NS as EGU3, EGU2_NS as EGU2,
EGU4_NS as EGU4, EGU3_NS as EGU3,
EGU5_NS as EGU5, EGU4_NS as EGU4,
FPU_NS as FPU, EGU5_NS as EGU5,
GPIOTE1_NS as GPIOTE1, FPU_NS as FPU,
I2S_NS as I2S, GPIOTE1_NS as GPIOTE1,
IPC_NS as IPC, I2S_NS as I2S,
KMU_NS as KMU, IPC_NS as IPC,
NVMC_NS as NVMC, KMU_NS as KMU,
P0_NS as P0, NVMC_NS as NVMC,
PDM_NS as PDM, P0_NS as P0,
POWER_NS as POWER, PDM_NS as PDM,
PWM0_NS as PWM0, POWER_NS as POWER,
PWM1_NS as PWM1, PWM0_NS as PWM0,
PWM2_NS as PWM2, PWM1_NS as PWM1,
PWM3_NS as PWM3, PWM2_NS as PWM2,
REGULATORS_NS as REGULATORS, PWM3_NS as PWM3,
RTC0_NS as RTC0, REGULATORS_NS as REGULATORS,
RTC1_NS as RTC1, RTC0_NS as RTC0,
SAADC_NS as SAADC, RTC1_NS as RTC1,
SPIM0_NS as SPIM0, SAADC_NS as SAADC,
SPIM1_NS as SPIM1, SPIM0_NS as SPIM0,
SPIM2_NS as SPIM2, SPIM1_NS as SPIM1,
SPIM3_NS as SPIM3, SPIM2_NS as SPIM2,
SPIS0_NS as SPIS0, SPIM3_NS as SPIM3,
SPIS1_NS as SPIS1, SPIS0_NS as SPIS0,
SPIS2_NS as SPIS2, SPIS1_NS as SPIS1,
SPIS3_NS as SPIS3, SPIS2_NS as SPIS2,
TIMER0_NS as TIMER0, SPIS3_NS as SPIS3,
TIMER1_NS as TIMER1, TIMER0_NS as TIMER0,
TIMER2_NS as TIMER2, TIMER1_NS as TIMER1,
TWIM0_NS as TWIM0, TIMER2_NS as TIMER2,
TWIM1_NS as TWIM1, TWIM0_NS as TWIM0,
TWIM2_NS as TWIM2, TWIM1_NS as TWIM1,
TWIM3_NS as TWIM3, TWIM2_NS as TWIM2,
TWIS0_NS as TWIS0, TWIM3_NS as TWIM3,
TWIS1_NS as TWIS1, TWIS0_NS as TWIS0,
TWIS2_NS as TWIS2, TWIS1_NS as TWIS1,
TWIS3_NS as TWIS3, TWIS2_NS as TWIS2,
UARTE0_NS as UARTE0, TWIS3_NS as TWIS3,
UARTE1_NS as UARTE1, UARTE0_NS as UARTE0,
UARTE2_NS as UARTE2, UARTE1_NS as UARTE1,
UARTE3_NS as UARTE3, UARTE2_NS as UARTE2,
VMC_NS as VMC, UARTE3_NS as UARTE3,
WDT_NS as WDT, VMC_NS as VMC,
}; WDT_NS as WDT,
};
}
#[cfg(feature = "nrf9160-s")] /// Secure mode (S) peripherals
#[doc(no_inline)] pub mod s {
pub use nrf9160_pac::{ #[doc(no_inline)]
CC_HOST_RGF_S as CC_HOST_RGF, pub use nrf9160_pac::{
CLOCK_S as CLOCK, CC_HOST_RGF_S as CC_HOST_RGF,
CRYPTOCELL_S as CRYPTOCELL, CLOCK_S as CLOCK,
CTRL_AP_PERI_S as CTRL_AP_PERI, CRYPTOCELL_S as CRYPTOCELL,
DPPIC_S as DPPIC, CTRL_AP_PERI_S as CTRL_AP_PERI,
EGU0_S as EGU0, DPPIC_S as DPPIC,
EGU1_S as EGU1, EGU0_S as EGU0,
EGU2_S as EGU2, EGU1_S as EGU1,
EGU3_S as EGU3, EGU2_S as EGU2,
EGU4_S as EGU4, EGU3_S as EGU3,
EGU5_S as EGU5, EGU4_S as EGU4,
FICR_S as FICR, EGU5_S as EGU5,
FPU_S as FPU, FICR_S as FICR,
GPIOTE0_S as GPIOTE0, FPU_S as FPU,
I2S_S as I2S, GPIOTE0_S as GPIOTE0,
IPC_S as IPC, I2S_S as I2S,
KMU_S as KMU, IPC_S as IPC,
NVMC_S as NVMC, KMU_S as KMU,
P0_S as P0, NVMC_S as NVMC,
PDM_S as PDM, P0_S as P0,
POWER_S as POWER, PDM_S as PDM,
PWM0_S as PWM0, POWER_S as POWER,
PWM1_S as PWM1, PWM0_S as PWM0,
PWM2_S as PWM2, PWM1_S as PWM1,
PWM3_S as PWM3, PWM2_S as PWM2,
REGULATORS_S as REGULATORS, PWM3_S as PWM3,
RTC0_S as RTC0, REGULATORS_S as REGULATORS,
RTC1_S as RTC1, RTC0_S as RTC0,
SAADC_S as SAADC, RTC1_S as RTC1,
SPIM0_S as SPIM0, SAADC_S as SAADC,
SPIM1_S as SPIM1, SPIM0_S as SPIM0,
SPIM2_S as SPIM2, SPIM1_S as SPIM1,
SPIM3_S as SPIM3, SPIM2_S as SPIM2,
SPIS0_S as SPIS0, SPIM3_S as SPIM3,
SPIS1_S as SPIS1, SPIS0_S as SPIS0,
SPIS2_S as SPIS2, SPIS1_S as SPIS1,
SPIS3_S as SPIS3, SPIS2_S as SPIS2,
SPU_S as SPU, SPIS3_S as SPIS3,
TAD_S as TAD, SPU_S as SPU,
TIMER0_S as TIMER0, TAD_S as TAD,
TIMER1_S as TIMER1, TIMER0_S as TIMER0,
TIMER2_S as TIMER2, TIMER1_S as TIMER1,
TWIM0_S as TWIM0, TIMER2_S as TIMER2,
TWIM1_S as TWIM1, TWIM0_S as TWIM0,
TWIM2_S as TWIM2, TWIM1_S as TWIM1,
TWIM3_S as TWIM3, TWIM2_S as TWIM2,
TWIS0_S as TWIS0, TWIM3_S as TWIM3,
TWIS1_S as TWIS1, TWIS0_S as TWIS0,
TWIS2_S as TWIS2, TWIS1_S as TWIS1,
TWIS3_S as TWIS3, TWIS2_S as TWIS2,
UARTE0_S as UARTE0, TWIS3_S as TWIS3,
UARTE1_S as UARTE1, UARTE0_S as UARTE0,
UARTE2_S as UARTE2, UARTE1_S as UARTE1,
UARTE3_S as UARTE3, UARTE2_S as UARTE2,
UICR_S as UICR, UARTE3_S as UARTE3,
VMC_S as VMC, UICR_S as UICR,
WDT_S as WDT, VMC_S as VMC,
}; WDT_S as WDT,
};
}
#[cfg(feature = "_ns")]
pub use ns::*;
#[cfg(feature = "_s")]
pub use s::*;
} }
/// The maximum buffer size that the EasyDMA can send/recv in one operation. /// The maximum buffer size that the EasyDMA can send/recv in one operation.

View File

@ -1,5 +1,6 @@
#![no_std] #![no_std]
#![cfg_attr(feature = "nightly", feature(async_fn_in_trait))] #![cfg_attr(feature = "nightly", feature(async_fn_in_trait, impl_trait_projections))]
#![cfg_attr(feature = "nightly", allow(stable_features, unknown_lints, async_fn_in_trait))]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
#![warn(missing_docs)] #![warn(missing_docs)]

View File

@ -2,6 +2,7 @@
#![macro_use] #![macro_use]
use core::future::poll_fn;
use core::marker::PhantomData; use core::marker::PhantomData;
use core::sync::atomic::{compiler_fence, Ordering}; use core::sync::atomic::{compiler_fence, Ordering};
use core::task::Poll; use core::task::Poll;
@ -9,7 +10,6 @@ use core::task::Poll;
use embassy_hal_internal::drop::OnDrop; use embassy_hal_internal::drop::OnDrop;
use embassy_hal_internal::{into_ref, PeripheralRef}; use embassy_hal_internal::{into_ref, PeripheralRef};
use fixed::types::I7F1; use fixed::types::I7F1;
use futures::future::poll_fn;
use crate::chip::EASY_DMA_SIZE; use crate::chip::EASY_DMA_SIZE;
use crate::gpio::sealed::Pin; use crate::gpio::sealed::Pin;

View File

@ -59,8 +59,8 @@ nightly = ["embedded-hal-1", "embedded-hal-async", "embedded-storage-async", "em
unstable-traits = ["embedded-hal-1", "embedded-hal-nb"] unstable-traits = ["embedded-hal-1", "embedded-hal-nb"]
[dependencies] [dependencies]
embassy-sync = { version = "0.3.0", path = "../embassy-sync" } embassy-sync = { version = "0.4.0", path = "../embassy-sync" }
embassy-time = { version = "0.1.4", path = "../embassy-time", features = [ "tick-hz-1_000_000" ] } embassy-time = { version = "0.1.5", path = "../embassy-time", features = [ "tick-hz-1_000_000" ] }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } embassy-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] }
embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" } embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" }
@ -94,5 +94,5 @@ pio = {version= "0.2.1" }
rp2040-boot2 = "0.3" rp2040-boot2 = "0.3"
[dev-dependencies] [dev-dependencies]
embassy-executor = { version = "0.3.0", path = "../embassy-executor", features = ["nightly", "arch-std", "executor-thread"] } embassy-executor = { version = "0.3.3", path = "../embassy-executor", features = ["nightly", "arch-std", "executor-thread"] }
static_cell = "1.1" static_cell = { version = "2" }

View File

@ -213,6 +213,7 @@ impl<'d> Adc<'d, Async> {
ch: &mut Channel<'_>, ch: &mut Channel<'_>,
buf: &mut [W], buf: &mut [W],
fcs_err: bool, fcs_err: bool,
div: u16,
dma: impl Peripheral<P = impl dma::Channel>, dma: impl Peripheral<P = impl dma::Channel>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let r = Self::regs(); let r = Self::regs();
@ -258,6 +259,7 @@ impl<'d> Adc<'d, Async> {
// start conversions and wait for dma to finish. we can't report errors early // start conversions and wait for dma to finish. we can't report errors early
// because there's no interrupt to signal them, and inspecting every element // because there's no interrupt to signal them, and inspecting every element
// of the fifo is too costly to do here. // of the fifo is too costly to do here.
r.div().write_set(|w| w.set_int(div));
r.cs().write_set(|w| w.set_start_many(true)); r.cs().write_set(|w| w.set_start_many(true));
dma.await; dma.await;
mem::drop(auto_reset); mem::drop(auto_reset);
@ -275,9 +277,10 @@ impl<'d> Adc<'d, Async> {
&mut self, &mut self,
ch: &mut Channel<'_>, ch: &mut Channel<'_>,
buf: &mut [S], buf: &mut [S],
div: u16,
dma: impl Peripheral<P = impl dma::Channel>, dma: impl Peripheral<P = impl dma::Channel>,
) -> Result<(), Error> { ) -> Result<(), Error> {
self.read_many_inner(ch, buf, false, dma).await self.read_many_inner(ch, buf, false, div, dma).await
} }
#[inline] #[inline]
@ -285,11 +288,12 @@ impl<'d> Adc<'d, Async> {
&mut self, &mut self,
ch: &mut Channel<'_>, ch: &mut Channel<'_>,
buf: &mut [Sample], buf: &mut [Sample],
div: u16,
dma: impl Peripheral<P = impl dma::Channel>, dma: impl Peripheral<P = impl dma::Channel>,
) { ) {
// errors are reported in individual samples // errors are reported in individual samples
let _ = self let _ = self
.read_many_inner(ch, unsafe { mem::transmute::<_, &mut [u16]>(buf) }, true, dma) .read_many_inner(ch, unsafe { mem::transmute::<_, &mut [u16]>(buf) }, true, div, dma)
.await; .await;
} }
} }

View File

@ -1,5 +1,6 @@
#![no_std] #![no_std]
#![cfg_attr(feature = "nightly", feature(async_fn_in_trait))] #![cfg_attr(feature = "nightly", feature(async_fn_in_trait, impl_trait_projections))]
#![cfg_attr(feature = "nightly", allow(stable_features, unknown_lints, async_fn_in_trait))]
// This mod MUST go first, so that the others see its macros. // This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt; pub(crate) mod fmt;

View File

@ -10,16 +10,39 @@ use crate::gpio::sealed::Pin as _;
use crate::gpio::{AnyPin, Pin as GpioPin}; use crate::gpio::{AnyPin, Pin as GpioPin};
use crate::{pac, peripherals, RegExt}; use crate::{pac, peripherals, RegExt};
/// The configuration of a PWM slice.
/// Note the period in clock cycles of a slice can be computed as:
/// `(top + 1) * (phase_correct ? 1 : 2) * divider`
#[non_exhaustive] #[non_exhaustive]
#[derive(Clone)] #[derive(Clone)]
pub struct Config { pub struct Config {
/// Inverts the PWM output signal on channel A.
pub invert_a: bool, pub invert_a: bool,
/// Inverts the PWM output signal on channel B.
pub invert_b: bool, pub invert_b: bool,
/// Enables phase-correct mode for PWM operation.
/// In phase-correct mode, the PWM signal is generated in such a way that
/// the pulse is always centered regardless of the duty cycle.
/// The output frequency is halved when phase-correct mode is enabled.
pub phase_correct: bool, pub phase_correct: bool,
/// Enables the PWM slice, allowing it to generate an output.
pub enable: bool, pub enable: bool,
/// A fractional clock divider, represented as a fixed-point number with
/// 8 integer bits and 4 fractional bits. It allows precise control over
/// the PWM output frequency by gating the PWM counter increment.
/// A higher value will result in a slower output frequency.
pub divider: fixed::FixedU16<fixed::types::extra::U4>, pub divider: fixed::FixedU16<fixed::types::extra::U4>,
/// The output on channel A goes high when `compare_a` is higher than the
/// counter. A compare of 0 will produce an always low output, while a
/// compare of `top + 1` will produce an always high output.
pub compare_a: u16, pub compare_a: u16,
/// The output on channel B goes high when `compare_b` is higher than the
/// counter. A compare of 0 will produce an always low output, while a
/// compare of `top + 1` will produce an always high output.
pub compare_b: u16, pub compare_b: u16,
/// The point at which the counter wraps, representing the maximum possible
/// period. The counter will either wrap to 0 or reverse depending on the
/// setting of `phase_correct`.
pub top: u16, pub top: u16,
} }
@ -173,6 +196,9 @@ impl<'d, T: Channel> Pwm<'d, T> {
}); });
} }
/// Advances a slices output phase by one count while it is running
/// by inserting a pulse into the clock enable. The counter
/// will not count faster than once per cycle.
#[inline] #[inline]
pub fn phase_advance(&mut self) { pub fn phase_advance(&mut self) {
let p = self.inner.regs(); let p = self.inner.regs();
@ -180,6 +206,9 @@ impl<'d, T: Channel> Pwm<'d, T> {
while p.csr().read().ph_adv() {} while p.csr().read().ph_adv() {}
} }
/// Retards a slices output phase by one count while it is running
/// by deleting a pulse from the clock enable. The counter will not
/// count backward when clock enable is permenantly low.
#[inline] #[inline]
pub fn phase_retard(&mut self) { pub fn phase_retard(&mut self) {
let p = self.inner.regs(); let p = self.inner.regs();

View File

@ -5,7 +5,7 @@ use core::task::Poll;
use atomic_polyfill::{AtomicU8, Ordering}; use atomic_polyfill::{AtomicU8, Ordering};
use embassy_hal_internal::atomic_ring_buffer::RingBuffer; use embassy_hal_internal::atomic_ring_buffer::RingBuffer;
use embassy_sync::waitqueue::AtomicWaker; use embassy_sync::waitqueue::AtomicWaker;
use embassy_time::{Duration, Timer}; use embassy_time::Timer;
use super::*; use super::*;
use crate::clocks::clk_peri_freq; use crate::clocks::clk_peri_freq;
@ -435,7 +435,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> {
Self::flush().await.unwrap(); Self::flush().await.unwrap();
while self.busy() {} while self.busy() {}
regs.uartlcr_h().write_set(|w| w.set_brk(true)); regs.uartlcr_h().write_set(|w| w.set_brk(true));
Timer::after(Duration::from_micros(wait_usecs)).await; Timer::after_micros(wait_usecs).await;
regs.uartlcr_h().write_clear(|w| w.set_brk(true)); regs.uartlcr_h().write_clear(|w| w.set_brk(true));
} }
} }

View File

@ -6,7 +6,7 @@ use atomic_polyfill::{AtomicU16, Ordering};
use embassy_futures::select::{select, Either}; use embassy_futures::select::{select, Either};
use embassy_hal_internal::{into_ref, PeripheralRef}; use embassy_hal_internal::{into_ref, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker; use embassy_sync::waitqueue::AtomicWaker;
use embassy_time::{Duration, Timer}; use embassy_time::Timer;
use pac::uart::regs::Uartris; use pac::uart::regs::Uartris;
use crate::clocks::clk_peri_freq; use crate::clocks::clk_peri_freq;
@ -187,7 +187,7 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> {
self.blocking_flush().unwrap(); self.blocking_flush().unwrap();
while self.busy() {} while self.busy() {}
regs.uartlcr_h().write_set(|w| w.set_brk(true)); regs.uartlcr_h().write_set(|w| w.set_brk(true));
Timer::after(Duration::from_micros(wait_usecs)).await; Timer::after_micros(wait_usecs).await;
regs.uartlcr_h().write_clear(|w| w.set_brk(true)); regs.uartlcr_h().write_clear(|w| w.set_brk(true));
} }
} }

View File

@ -12,16 +12,16 @@ features = ["stm32wb55rg"]
[dependencies] [dependencies]
embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32" } embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32" }
embassy-sync = { version = "0.3.0", path = "../embassy-sync" } embassy-sync = { version = "0.4.0", path = "../embassy-sync" }
embassy-time = { version = "0.1.4", path = "../embassy-time", optional = true } embassy-time = { version = "0.1.5", path = "../embassy-time", optional = true }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-hal-internal = { version = "0.1.0", path = "../embassy-hal-internal" } embassy-hal-internal = { version = "0.1.0", path = "../embassy-hal-internal" }
embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" } embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" }
embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver", optional=true } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver", optional=true }
defmt = { version = "0.3", optional = true } defmt = { version = "0.3", optional = true }
cortex-m = "0.7.6" cortex-m = "0.7.6"
heapless = "0.7.16" heapless = "0.8"
aligned = "0.4.1" aligned = "0.4.1"
bit_field = "0.10.2" bit_field = "0.10.2"

View File

@ -1,6 +1,9 @@
#![no_std] #![no_std]
#![cfg_attr(any(feature = "ble", feature = "mac"), feature(async_fn_in_trait))] #![cfg_attr(any(feature = "ble", feature = "mac"), feature(async_fn_in_trait))]
#![cfg_attr(feature = "mac", feature(type_alias_impl_trait, concat_bytes))] #![cfg_attr(
any(feature = "ble", feature = "mac"),
allow(stable_features, unknown_lints, async_fn_in_trait)
)]
// This must go FIRST so that all the other modules see its macros. // This must go FIRST so that all the other modules see its macros.
mod fmt; mod fmt;

View File

@ -1,9 +1,8 @@
#![allow(incomplete_features)]
#![deny(unused_must_use)] #![deny(unused_must_use)]
use core::task::Context; use core::task::Context;
use embassy_net_driver::{Capabilities, HardwareAddress, LinkState, Medium}; use embassy_net_driver::{Capabilities, HardwareAddress, LinkState};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::channel::Channel; use embassy_sync::channel::Channel;
@ -60,24 +59,15 @@ impl<'d> embassy_net_driver::Driver for Driver<'d> {
let mut caps = Capabilities::default(); let mut caps = Capabilities::default();
caps.max_transmission_unit = MTU; caps.max_transmission_unit = MTU;
// caps.max_burst_size = Some(self.tx.len()); // caps.max_burst_size = Some(self.tx.len());
caps.medium = Medium::Ieee802154;
caps caps
} }
fn link_state(&mut self, _cx: &mut Context) -> LinkState { fn link_state(&mut self, _cx: &mut Context) -> LinkState {
// if self.phy.poll_link(&mut self.station_management, cx) {
// LinkState::Up
// } else {
// LinkState::Down
// }
LinkState::Down LinkState::Down
} }
fn hardware_address(&self) -> HardwareAddress { fn hardware_address(&self) -> HardwareAddress {
// self.mac_addr // self.mac_addr
HardwareAddress::Ieee802154([0; 8]) HardwareAddress::Ieee802154([0; 8])
} }
} }

View File

@ -18,7 +18,7 @@ flavors = [
{ regex_feature = "stm32f7.*", target = "thumbv7em-none-eabi" }, { regex_feature = "stm32f7.*", target = "thumbv7em-none-eabi" },
{ regex_feature = "stm32c0.*", target = "thumbv6m-none-eabi" }, { regex_feature = "stm32c0.*", target = "thumbv6m-none-eabi" },
{ regex_feature = "stm32g0.*", target = "thumbv6m-none-eabi" }, { regex_feature = "stm32g0.*", target = "thumbv6m-none-eabi" },
{ regex_feature = "stm32g4.*", target = "thumbv7em-none-eabi" }, { regex_feature = "stm32g4.*", target = "thumbv7em-none-eabi", features = ["low-power"] },
{ regex_feature = "stm32h5.*", target = "thumbv8m.main-none-eabihf" }, { regex_feature = "stm32h5.*", target = "thumbv8m.main-none-eabihf" },
{ regex_feature = "stm32h7.*", target = "thumbv7em-none-eabi" }, { regex_feature = "stm32h7.*", target = "thumbv7em-none-eabi" },
{ regex_feature = "stm32l0.*", target = "thumbv6m-none-eabi", features = ["low-power"] }, { regex_feature = "stm32l0.*", target = "thumbv6m-none-eabi", features = ["low-power"] },
@ -32,14 +32,14 @@ flavors = [
] ]
[dependencies] [dependencies]
embassy-sync = { version = "0.3.0", path = "../embassy-sync" } embassy-sync = { version = "0.4.0", path = "../embassy-sync" }
embassy-time = { version = "0.1.4", path = "../embassy-time", optional = true } embassy-time = { version = "0.1.5", path = "../embassy-time", optional = true }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-4"] } embassy-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-4"] }
embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" } embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" }
embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" }
embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver", optional = true } embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver", optional = true }
embassy-executor = { version = "0.3.0", path = "../embassy-executor", optional = true } embassy-executor = { version = "0.3.3", path = "../embassy-executor", optional = true }
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1", optional = true} embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1", optional = true}
@ -58,7 +58,7 @@ rand_core = "0.6.3"
sdio-host = "0.5.0" sdio-host = "0.5.0"
embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "a4f293d3a6f72158385f79c98634cb8a14d0d2fc", optional = true } embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "a4f293d3a6f72158385f79c98634cb8a14d0d2fc", optional = true }
critical-section = "1.1" critical-section = "1.1"
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-e6e51db6cdd7d533e52ca7a3237f7816a0486cd4" } stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-7117ad49c06fa00c388130a34977e029910083bd" }
vcell = "0.1.3" vcell = "0.1.3"
bxcan = "0.7.0" bxcan = "0.7.0"
nb = "1.0.0" nb = "1.0.0"
@ -76,7 +76,7 @@ critical-section = { version = "1.1", features = ["std"] }
[build-dependencies] [build-dependencies]
proc-macro2 = "1.0.36" proc-macro2 = "1.0.36"
quote = "1.0.15" quote = "1.0.15"
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-e6e51db6cdd7d533e52ca7a3237f7816a0486cd4", default-features = false, features = ["metadata"]} stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-7117ad49c06fa00c388130a34977e029910083bd", default-features = false, features = ["metadata"]}
[features] [features]
@ -90,6 +90,7 @@ defmt = ["dep:defmt", "bxcan/unstable-defmt", "embassy-sync/defmt", "embassy-emb
exti = [] exti = []
low-power = [ "dep:embassy-executor", "embassy-executor/arch-cortex-m" ] low-power = [ "dep:embassy-executor", "embassy-executor/arch-cortex-m" ]
low-power-debug-with-sleep = []
embassy-executor = [] embassy-executor = []
## Automatically generate `memory.x` file using [`stm32-metapac`](https://docs.rs/stm32-metapac/) ## Automatically generate `memory.x` file using [`stm32-metapac`](https://docs.rs/stm32-metapac/)

View File

@ -1,4 +1,4 @@
use std::collections::{HashMap, HashSet}; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::fmt::Write as _; use std::fmt::Write as _;
use std::path::PathBuf; use std::path::PathBuf;
use std::{env, fs}; use std::{env, fs};
@ -6,7 +6,7 @@ use std::{env, fs};
use proc_macro2::{Ident, TokenStream}; use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use stm32_metapac::metadata::ir::{BlockItemInner, Enum, FieldSet}; use stm32_metapac::metadata::ir::{BlockItemInner, Enum, FieldSet};
use stm32_metapac::metadata::{MemoryRegionKind, PeripheralRccRegister, METADATA}; use stm32_metapac::metadata::{MemoryRegionKind, PeripheralRccRegister, StopMode, METADATA};
fn main() { fn main() {
let target = env::var("TARGET").unwrap(); let target = env::var("TARGET").unwrap();
@ -61,10 +61,10 @@ fn main() {
let mut singletons: Vec<String> = Vec::new(); let mut singletons: Vec<String> = Vec::new();
for p in METADATA.peripherals { for p in METADATA.peripherals {
if let Some(r) = &p.registers { if let Some(r) = &p.registers {
println!("cargo:rustc-cfg=peri_{}", p.name.to_ascii_lowercase());
match r.kind { match r.kind {
// Generate singletons per pin, not per port // Generate singletons per pin, not per port
"gpio" => { "gpio" => {
println!("{}", p.name);
let port_letter = p.name.strip_prefix("GPIO").unwrap(); let port_letter = p.name.strip_prefix("GPIO").unwrap();
for pin_num in 0..16 { for pin_num in 0..16 {
singletons.push(format!("P{}{}", port_letter, pin_num)); singletons.push(format!("P{}{}", port_letter, pin_num));
@ -352,7 +352,7 @@ fn main() {
// ======== // ========
// Generate DMA IRQs. // Generate DMA IRQs.
let mut dma_irqs: HashMap<&str, Vec<(&str, &str, &str)>> = HashMap::new(); let mut dma_irqs: BTreeMap<&str, Vec<(&str, &str, &str)>> = BTreeMap::new();
for p in METADATA.peripherals { for p in METADATA.peripherals {
if let Some(r) = &p.registers { if let Some(r) = &p.registers {
@ -371,37 +371,43 @@ fn main() {
} }
} }
for (irq, channels) in dma_irqs { let dma_irqs: TokenStream = dma_irqs
let irq = format_ident!("{}", irq); .iter()
.map(|(irq, channels)| {
let irq = format_ident!("{}", irq);
let xdma = format_ident!("{}", channels[0].0); let xdma = format_ident!("{}", channels[0].0);
let channels = channels.iter().map(|(_, dma, ch)| format_ident!("{}_{}", dma, ch)); let channels = channels.iter().map(|(_, dma, ch)| format_ident!("{}_{}", dma, ch));
g.extend(quote! { quote! {
#[cfg(feature = "rt")] #[cfg(feature = "rt")]
#[crate::interrupt] #[crate::interrupt]
unsafe fn #irq () { unsafe fn #irq () {
#( #(
<crate::peripherals::#channels as crate::dma::#xdma::sealed::Channel>::on_irq(); <crate::peripherals::#channels as crate::dma::#xdma::sealed::Channel>::on_irq();
)* )*
}
} }
}); })
} .collect();
g.extend(dma_irqs);
// ========
// Extract the rcc registers
let rcc_registers = METADATA
.peripherals
.iter()
.filter_map(|p| p.registers.as_ref())
.find(|r| r.kind == "rcc")
.unwrap();
// ======== // ========
// Generate rcc fieldset and enum maps // Generate rcc fieldset and enum maps
let rcc_enum_map: HashMap<&str, HashMap<&str, &Enum>> = { let rcc_enum_map: HashMap<&str, HashMap<&str, &Enum>> = {
let rcc_registers = METADATA let rcc_blocks = rcc_registers.ir.blocks.iter().find(|b| b.name == "Rcc").unwrap().items;
.peripherals let rcc_fieldsets: HashMap<&str, &FieldSet> = rcc_registers.ir.fieldsets.iter().map(|f| (f.name, f)).collect();
.iter() let rcc_enums: HashMap<&str, &Enum> = rcc_registers.ir.enums.iter().map(|e| (e.name, e)).collect();
.filter_map(|p| p.registers.as_ref())
.find(|r| r.kind == "rcc")
.unwrap()
.ir;
let rcc_blocks = rcc_registers.blocks.iter().find(|b| b.name == "Rcc").unwrap().items;
let rcc_fieldsets: HashMap<&str, &FieldSet> = rcc_registers.fieldsets.iter().map(|f| (f.name, f)).collect();
let rcc_enums: HashMap<&str, &Enum> = rcc_registers.enums.iter().map(|e| (e.name, e)).collect();
rcc_blocks rcc_blocks
.iter() .iter()
@ -432,7 +438,7 @@ fn main() {
// Generate RccPeripheral impls // Generate RccPeripheral impls
let refcounted_peripherals = HashSet::from(["usart", "adc"]); let refcounted_peripherals = HashSet::from(["usart", "adc"]);
let mut refcount_statics = HashSet::new(); let mut refcount_statics = BTreeSet::new();
for p in METADATA.peripherals { for p in METADATA.peripherals {
if !singletons.contains(&p.name.to_string()) { if !singletons.contains(&p.name.to_string()) {
@ -465,9 +471,9 @@ fn main() {
let ptype = if let Some(reg) = &p.registers { reg.kind } else { "" }; let ptype = if let Some(reg) = &p.registers { reg.kind } else { "" };
let pname = format_ident!("{}", p.name); let pname = format_ident!("{}", p.name);
let clk = format_ident!("{}", rcc.clock.to_ascii_lowercase()); let clk = format_ident!("{}", rcc.clock);
let en_reg = format_ident!("{}", en.register.to_ascii_lowercase()); let en_reg = format_ident!("{}", en.register);
let set_en_field = format_ident!("set_{}", en.field.to_ascii_lowercase()); let set_en_field = format_ident!("set_{}", en.field);
let (before_enable, before_disable) = if refcounted_peripherals.contains(ptype) { let (before_enable, before_disable) = if refcounted_peripherals.contains(ptype) {
let refcount_static = let refcount_static =
@ -493,9 +499,11 @@ fn main() {
(TokenStream::new(), TokenStream::new()) (TokenStream::new(), TokenStream::new())
}; };
let mux_supported = HashSet::from(["c0", "h5", "h50", "h7", "h7ab", "h7rm0433", "g4", "l4"])
.contains(rcc_registers.version);
let mux_for = |mux: Option<&'static PeripheralRccRegister>| { let mux_for = |mux: Option<&'static PeripheralRccRegister>| {
// temporary hack to restrict the scope of the implementation to h5 // restrict mux implementation to supported versions
if !&chip_name.starts_with("stm32h5") { if !mux_supported {
return None; return None;
} }
@ -518,11 +526,9 @@ fn main() {
.filter(|v| v.name != "DISABLE") .filter(|v| v.name != "DISABLE")
.map(|v| { .map(|v| {
let variant_name = format_ident!("{}", v.name); let variant_name = format_ident!("{}", v.name);
// temporary hack to restrict the scope of the implementation until clock names can be stabilized
let clock_name = format_ident!("{}", v.name.to_ascii_lowercase()); let clock_name = format_ident!("{}", v.name.to_ascii_lowercase());
if v.name.starts_with("AHB") || v.name.starts_with("APB") { if v.name.starts_with("HCLK") || v.name.starts_with("PCLK") || v.name == "SYS" {
quote! { quote! {
#enum_name::#variant_name => unsafe { crate::rcc::get_freqs().#clock_name }, #enum_name::#variant_name => unsafe { crate::rcc::get_freqs().#clock_name },
} }
@ -550,6 +556,31 @@ fn main() {
}, },
}; };
/*
A refcount leak can result if the same field is shared by peripherals with different stop modes
This condition should be checked in stm32-data
*/
let stop_refcount = match rcc.stop_mode {
StopMode::Standby => None,
StopMode::Stop2 => Some(quote! { REFCOUNT_STOP2 }),
StopMode::Stop1 => Some(quote! { REFCOUNT_STOP1 }),
};
let (incr_stop_refcount, decr_stop_refcount) = match stop_refcount {
Some(stop_refcount) => (
quote! {
#[cfg(feature = "low-power")]
unsafe { crate::rcc::#stop_refcount += 1 };
},
quote! {
#[cfg(feature = "low-power")]
unsafe { crate::rcc::#stop_refcount -= 1 };
},
),
None => (TokenStream::new(), TokenStream::new()),
};
g.extend(quote! { g.extend(quote! {
impl crate::rcc::sealed::RccPeripheral for peripherals::#pname { impl crate::rcc::sealed::RccPeripheral for peripherals::#pname {
fn frequency() -> crate::time::Hertz { fn frequency() -> crate::time::Hertz {
@ -557,8 +588,7 @@ fn main() {
} }
fn enable_and_reset_with_cs(_cs: critical_section::CriticalSection) { fn enable_and_reset_with_cs(_cs: critical_section::CriticalSection) {
#before_enable #before_enable
#[cfg(feature = "low-power")] #incr_stop_refcount
crate::rcc::clock_refcount_add(_cs);
crate::pac::RCC.#en_reg().modify(|w| w.#set_en_field(true)); crate::pac::RCC.#en_reg().modify(|w| w.#set_en_field(true));
#after_enable #after_enable
#rst #rst
@ -566,8 +596,7 @@ fn main() {
fn disable_with_cs(_cs: critical_section::CriticalSection) { fn disable_with_cs(_cs: critical_section::CriticalSection) {
#before_disable #before_disable
crate::pac::RCC.#en_reg().modify(|w| w.#set_en_field(false)); crate::pac::RCC.#en_reg().modify(|w| w.#set_en_field(false));
#[cfg(feature = "low-power")] #decr_stop_refcount
crate::rcc::clock_refcount_sub(_cs);
} }
} }
@ -798,7 +827,7 @@ fn main() {
(("fmc", "NCE"), quote!(crate::fmc::NCEPin)), (("fmc", "NCE"), quote!(crate::fmc::NCEPin)),
(("fmc", "NOE"), quote!(crate::fmc::NOEPin)), (("fmc", "NOE"), quote!(crate::fmc::NOEPin)),
(("fmc", "NWE"), quote!(crate::fmc::NWEPin)), (("fmc", "NWE"), quote!(crate::fmc::NWEPin)),
(("fmc", "Clk"), quote!(crate::fmc::ClkPin)), (("fmc", "CLK"), quote!(crate::fmc::ClkPin)),
(("fmc", "BA0"), quote!(crate::fmc::BA0Pin)), (("fmc", "BA0"), quote!(crate::fmc::BA0Pin)),
(("fmc", "BA1"), quote!(crate::fmc::BA1Pin)), (("fmc", "BA1"), quote!(crate::fmc::BA1Pin)),
(("timer", "CH1"), quote!(crate::timer::Channel1Pin)), (("timer", "CH1"), quote!(crate::timer::Channel1Pin)),
@ -914,17 +943,23 @@ fn main() {
} }
if regs.kind == "opamp" { if regs.kind == "opamp" {
if !pin.signal.starts_with("VP") { if pin.signal.starts_with("VP") {
continue; // Impl NonInvertingPin for the VP* signals (VP0, VP1, VP2, etc)
let peri = format_ident!("{}", p.name);
let pin_name = format_ident!("{}", pin.pin);
let ch: u8 = pin.signal.strip_prefix("VP").unwrap().parse().unwrap();
g.extend(quote! {
impl_opamp_vp_pin!( #peri, #pin_name, #ch);
})
} else if pin.signal == "VOUT" {
// Impl OutputPin for the VOUT pin
let peri = format_ident!("{}", p.name);
let pin_name = format_ident!("{}", pin.pin);
g.extend(quote! {
impl_opamp_vout_pin!( #peri, #pin_name );
})
} }
let peri = format_ident!("{}", p.name);
let pin_name = format_ident!("{}", pin.pin);
let ch: u8 = pin.signal.strip_prefix("VP").unwrap().parse().unwrap();
g.extend(quote! {
impl_opamp_pin!( #peri, #pin_name, #ch);
})
} }
// DAC is special // DAC is special
@ -961,8 +996,8 @@ fn main() {
// SDMMCv1 uses the same channel for both directions, so just implement for RX // SDMMCv1 uses the same channel for both directions, so just implement for RX
(("sdmmc", "RX"), quote!(crate::sdmmc::SdmmcDma)), (("sdmmc", "RX"), quote!(crate::sdmmc::SdmmcDma)),
(("quadspi", "QUADSPI"), quote!(crate::qspi::QuadDma)), (("quadspi", "QUADSPI"), quote!(crate::qspi::QuadDma)),
(("dac", "CH1"), quote!(crate::dac::DmaCh1)), (("dac", "CH1"), quote!(crate::dac::DacDma1)),
(("dac", "CH2"), quote!(crate::dac::DmaCh2)), (("dac", "CH2"), quote!(crate::dac::DacDma2)),
] ]
.into(); .into();
@ -1013,15 +1048,7 @@ fn main() {
// ======== // ========
// Generate Div/Mul impls for RCC prescalers/dividers/multipliers. // Generate Div/Mul impls for RCC prescalers/dividers/multipliers.
let rcc_registers = METADATA for e in rcc_registers.ir.enums {
.peripherals
.iter()
.filter_map(|p| p.registers.as_ref())
.find(|r| r.kind == "rcc")
.unwrap()
.ir;
for e in rcc_registers.enums {
fn is_rcc_name(e: &str) -> bool { fn is_rcc_name(e: &str) -> bool {
match e { match e {
"Pllp" | "Pllq" | "Pllr" | "Pllm" | "Plln" => true, "Pllp" | "Pllq" | "Pllr" | "Pllm" | "Plln" => true,
@ -1110,6 +1137,23 @@ fn main() {
} }
} }
// ========
// Write peripheral_interrupts module.
let mut mt = TokenStream::new();
for p in METADATA.peripherals {
let mut pt = TokenStream::new();
for irq in p.interrupts {
let iname = format_ident!("{}", irq.interrupt);
let sname = format_ident!("{}", irq.signal);
pt.extend(quote!(pub type #sname = crate::interrupt::typelevel::#iname;));
}
let pname = format_ident!("{}", p.name);
mt.extend(quote!(pub mod #pname { #pt }));
}
g.extend(quote!(#[allow(non_camel_case_types)] pub mod peripheral_interrupts { #mt }));
// ======== // ========
// Write foreach_foo! macrotables // Write foreach_foo! macrotables
@ -1268,6 +1312,9 @@ fn main() {
let mut m = String::new(); let mut m = String::new();
// DO NOT ADD more macros like these.
// These turned to be a bad idea!
// Instead, make build.rs generate the final code.
make_table(&mut m, "foreach_flash_region", &flash_regions_table); make_table(&mut m, "foreach_flash_region", &flash_regions_table);
make_table(&mut m, "foreach_interrupt", &interrupts_table); make_table(&mut m, "foreach_interrupt", &interrupts_table);
make_table(&mut m, "foreach_peripheral", &peripherals_table); make_table(&mut m, "foreach_peripheral", &peripherals_table);
@ -1304,15 +1351,6 @@ fn main() {
if let Some(core) = core_name { if let Some(core) = core_name {
println!("cargo:rustc-cfg={}_{}", &chip_name[..chip_name.len() - 2], core); println!("cargo:rustc-cfg={}_{}", &chip_name[..chip_name.len() - 2], core);
} else {
println!("cargo:rustc-cfg={}", &chip_name[..chip_name.len() - 2]);
}
// ========
// stm32f3 wildcard features used in RCC
if chip_name.starts_with("stm32f3") {
println!("cargo:rustc-cfg={}x{}", &chip_name[..9], &chip_name[10..11]);
} }
// ======= // =======
@ -1327,16 +1365,25 @@ fn main() {
if &chip_name[..8] == "stm32wba" { if &chip_name[..8] == "stm32wba" {
println!("cargo:rustc-cfg={}", &chip_name[..8]); // stm32wba println!("cargo:rustc-cfg={}", &chip_name[..8]); // stm32wba
println!("cargo:rustc-cfg={}", &chip_name[..10]); // stm32wba52 println!("cargo:rustc-cfg={}", &chip_name[..10]); // stm32wba52
println!("cargo:rustc-cfg=package_{}", &chip_name[10..11]);
println!("cargo:rustc-cfg=flashsize_{}", &chip_name[11..12]);
} else { } else {
println!("cargo:rustc-cfg={}", &chip_name[..7]); // stm32f4 println!("cargo:rustc-cfg={}", &chip_name[..7]); // stm32f4
println!("cargo:rustc-cfg={}", &chip_name[..9]); // stm32f429 println!("cargo:rustc-cfg={}", &chip_name[..9]); // stm32f429
println!("cargo:rustc-cfg={}x", &chip_name[..8]); // stm32f42x println!("cargo:rustc-cfg={}x", &chip_name[..8]); // stm32f42x
println!("cargo:rustc-cfg={}x{}", &chip_name[..7], &chip_name[8..9]); // stm32f4x9 println!("cargo:rustc-cfg={}x{}", &chip_name[..7], &chip_name[8..9]); // stm32f4x9
println!("cargo:rustc-cfg=package_{}", &chip_name[9..10]);
println!("cargo:rustc-cfg=flashsize_{}", &chip_name[10..11]);
} }
// Handle time-driver-XXXX features. // Mark the L4+ chips as they have many differences to regular L4.
if env::var("CARGO_FEATURE_TIME_DRIVER_ANY").is_ok() {} if &chip_name[..7] == "stm32l4" {
println!("cargo:rustc-cfg={}", &chip_name[..chip_name.len() - 2]); if "pqrs".contains(&chip_name[7..8]) {
println!("cargo:rustc-cfg=stm32l4_plus");
} else {
println!("cargo:rustc-cfg=stm32l4_nonplus");
}
}
println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=build.rs");
} }

View File

@ -1,4 +1,3 @@
use core::cell::{RefCell, RefMut};
use core::future::poll_fn; use core::future::poll_fn;
use core::marker::PhantomData; use core::marker::PhantomData;
use core::ops::{Deref, DerefMut}; use core::ops::{Deref, DerefMut};
@ -84,7 +83,7 @@ impl<T: Instance> interrupt::typelevel::Handler<T::SCEInterrupt> for SceInterrup
} }
pub struct Can<'d, T: Instance> { pub struct Can<'d, T: Instance> {
pub can: RefCell<bxcan::Can<BxcanInstance<'d, T>>>, pub can: bxcan::Can<BxcanInstance<'d, T>>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -175,17 +174,12 @@ impl<'d, T: Instance> Can<'d, T> {
tx.set_as_af(tx.af_num(), AFType::OutputPushPull); tx.set_as_af(tx.af_num(), AFType::OutputPushPull);
let can = bxcan::Can::builder(BxcanInstance(peri)).leave_disabled(); let can = bxcan::Can::builder(BxcanInstance(peri)).leave_disabled();
let can_ref_cell = RefCell::new(can); Self { can }
Self { can: can_ref_cell }
} }
pub fn set_bitrate(&mut self, bitrate: u32) { pub fn set_bitrate(&mut self, bitrate: u32) {
let bit_timing = Self::calc_bxcan_timings(T::frequency(), bitrate).unwrap(); let bit_timing = Self::calc_bxcan_timings(T::frequency(), bitrate).unwrap();
self.can self.can.modify_config().set_bit_timing(bit_timing).leave_disabled();
.borrow_mut()
.modify_config()
.set_bit_timing(bit_timing)
.leave_disabled();
} }
/// Enables the peripheral and synchronizes with the bus. /// Enables the peripheral and synchronizes with the bus.
@ -193,7 +187,7 @@ impl<'d, T: Instance> Can<'d, T> {
/// This will wait for 11 consecutive recessive bits (bus idle state). /// This will wait for 11 consecutive recessive bits (bus idle state).
/// Contrary to enable method from bxcan library, this will not freeze the executor while waiting. /// Contrary to enable method from bxcan library, this will not freeze the executor while waiting.
pub async fn enable(&mut self) { pub async fn enable(&mut self) {
while self.borrow_mut().enable_non_blocking().is_err() { while self.enable_non_blocking().is_err() {
// SCE interrupt is only generated for entering sleep mode, but not leaving. // SCE interrupt is only generated for entering sleep mode, but not leaving.
// Yield to allow other tasks to execute while can bus is initializing. // Yield to allow other tasks to execute while can bus is initializing.
embassy_futures::yield_now().await; embassy_futures::yield_now().await;
@ -202,46 +196,46 @@ impl<'d, T: Instance> Can<'d, T> {
/// Queues the message to be sent but exerts backpressure /// Queues the message to be sent but exerts backpressure
pub async fn write(&mut self, frame: &Frame) -> bxcan::TransmitStatus { pub async fn write(&mut self, frame: &Frame) -> bxcan::TransmitStatus {
CanTx { can: &self.can }.write(frame).await self.split().0.write(frame).await
} }
/// Attempts to transmit a frame without blocking. /// Attempts to transmit a frame without blocking.
/// ///
/// Returns [Err(TryWriteError::Full)] if all transmit mailboxes are full. /// Returns [Err(TryWriteError::Full)] if all transmit mailboxes are full.
pub fn try_write(&mut self, frame: &Frame) -> Result<bxcan::TransmitStatus, TryWriteError> { pub fn try_write(&mut self, frame: &Frame) -> Result<bxcan::TransmitStatus, TryWriteError> {
CanTx { can: &self.can }.try_write(frame) self.split().0.try_write(frame)
} }
/// Waits for a specific transmit mailbox to become empty /// Waits for a specific transmit mailbox to become empty
pub async fn flush(&self, mb: bxcan::Mailbox) { pub async fn flush(&self, mb: bxcan::Mailbox) {
CanTx { can: &self.can }.flush(mb).await CanTx::<T>::flush_inner(mb).await
} }
/// Waits until any of the transmit mailboxes become empty /// Waits until any of the transmit mailboxes become empty
pub async fn flush_any(&self) { pub async fn flush_any(&self) {
CanTx { can: &self.can }.flush_any().await CanTx::<T>::flush_any_inner().await
} }
/// Waits until all of the transmit mailboxes become empty /// Waits until all of the transmit mailboxes become empty
pub async fn flush_all(&self) { pub async fn flush_all(&self) {
CanTx { can: &self.can }.flush_all().await CanTx::<T>::flush_all_inner().await
} }
/// Returns a tuple of the time the message was received and the message frame /// Returns a tuple of the time the message was received and the message frame
pub async fn read(&mut self) -> Result<Envelope, BusError> { pub async fn read(&mut self) -> Result<Envelope, BusError> {
CanRx { can: &self.can }.read().await self.split().1.read().await
} }
/// Attempts to read a can frame without blocking. /// Attempts to read a can frame without blocking.
/// ///
/// Returns [Err(TryReadError::Empty)] if there are no frames in the rx queue. /// Returns [Err(TryReadError::Empty)] if there are no frames in the rx queue.
pub fn try_read(&mut self) -> Result<Envelope, TryReadError> { pub fn try_read(&mut self) -> Result<Envelope, TryReadError> {
CanRx { can: &self.can }.try_read() self.split().1.try_read()
} }
/// Waits while receive queue is empty. /// Waits while receive queue is empty.
pub async fn wait_not_empty(&mut self) { pub async fn wait_not_empty(&mut self) {
CanRx { can: &self.can }.wait_not_empty().await self.split().1.wait_not_empty().await
} }
unsafe fn receive_fifo(fifo: RxFifo) { unsafe fn receive_fifo(fifo: RxFifo) {
@ -385,24 +379,25 @@ impl<'d, T: Instance> Can<'d, T> {
Some((sjw - 1) << 24 | (bs1 as u32 - 1) << 16 | (bs2 as u32 - 1) << 20 | (prescaler - 1)) Some((sjw - 1) << 24 | (bs1 as u32 - 1) << 16 | (bs2 as u32 - 1) << 20 | (prescaler - 1))
} }
pub fn split<'c>(&'c self) -> (CanTx<'c, 'd, T>, CanRx<'c, 'd, T>) { pub fn split<'c>(&'c mut self) -> (CanTx<'c, 'd, T>, CanRx<'c, 'd, T>) {
(CanTx { can: &self.can }, CanRx { can: &self.can }) let (tx, rx0, rx1) = self.can.split_by_ref();
(CanTx { tx }, CanRx { rx0, rx1 })
} }
pub fn as_mut(&self) -> RefMut<'_, bxcan::Can<BxcanInstance<'d, T>>> { pub fn as_mut(&mut self) -> &mut bxcan::Can<BxcanInstance<'d, T>> {
self.can.borrow_mut() &mut self.can
} }
} }
pub struct CanTx<'c, 'd, T: Instance> { pub struct CanTx<'c, 'd, T: Instance> {
can: &'c RefCell<bxcan::Can<BxcanInstance<'d, T>>>, tx: &'c mut bxcan::Tx<BxcanInstance<'d, T>>,
} }
impl<'c, 'd, T: Instance> CanTx<'c, 'd, T> { impl<'c, 'd, T: Instance> CanTx<'c, 'd, T> {
pub async fn write(&mut self, frame: &Frame) -> bxcan::TransmitStatus { pub async fn write(&mut self, frame: &Frame) -> bxcan::TransmitStatus {
poll_fn(|cx| { poll_fn(|cx| {
T::state().tx_waker.register(cx.waker()); T::state().tx_waker.register(cx.waker());
if let Ok(status) = self.can.borrow_mut().transmit(frame) { if let Ok(status) = self.tx.transmit(frame) {
return Poll::Ready(status); return Poll::Ready(status);
} }
@ -415,11 +410,10 @@ impl<'c, 'd, T: Instance> CanTx<'c, 'd, T> {
/// ///
/// Returns [Err(TryWriteError::Full)] if all transmit mailboxes are full. /// Returns [Err(TryWriteError::Full)] if all transmit mailboxes are full.
pub fn try_write(&mut self, frame: &Frame) -> Result<bxcan::TransmitStatus, TryWriteError> { pub fn try_write(&mut self, frame: &Frame) -> Result<bxcan::TransmitStatus, TryWriteError> {
self.can.borrow_mut().transmit(frame).map_err(|_| TryWriteError::Full) self.tx.transmit(frame).map_err(|_| TryWriteError::Full)
} }
/// Waits for a specific transmit mailbox to become empty async fn flush_inner(mb: bxcan::Mailbox) {
pub async fn flush(&self, mb: bxcan::Mailbox) {
poll_fn(|cx| { poll_fn(|cx| {
T::state().tx_waker.register(cx.waker()); T::state().tx_waker.register(cx.waker());
if T::regs().tsr().read().tme(mb.index()) { if T::regs().tsr().read().tme(mb.index()) {
@ -431,8 +425,12 @@ impl<'c, 'd, T: Instance> CanTx<'c, 'd, T> {
.await; .await;
} }
/// Waits until any of the transmit mailboxes become empty /// Waits for a specific transmit mailbox to become empty
pub async fn flush_any(&self) { pub async fn flush(&self, mb: bxcan::Mailbox) {
Self::flush_inner(mb).await
}
async fn flush_any_inner() {
poll_fn(|cx| { poll_fn(|cx| {
T::state().tx_waker.register(cx.waker()); T::state().tx_waker.register(cx.waker());
@ -449,8 +447,12 @@ impl<'c, 'd, T: Instance> CanTx<'c, 'd, T> {
.await; .await;
} }
/// Waits until all of the transmit mailboxes become empty /// Waits until any of the transmit mailboxes become empty
pub async fn flush_all(&self) { pub async fn flush_any(&self) {
Self::flush_any_inner().await
}
async fn flush_all_inner() {
poll_fn(|cx| { poll_fn(|cx| {
T::state().tx_waker.register(cx.waker()); T::state().tx_waker.register(cx.waker());
@ -466,11 +468,17 @@ impl<'c, 'd, T: Instance> CanTx<'c, 'd, T> {
}) })
.await; .await;
} }
/// Waits until all of the transmit mailboxes become empty
pub async fn flush_all(&self) {
Self::flush_all_inner().await
}
} }
#[allow(dead_code)] #[allow(dead_code)]
pub struct CanRx<'c, 'd, T: Instance> { pub struct CanRx<'c, 'd, T: Instance> {
can: &'c RefCell<bxcan::Can<BxcanInstance<'d, T>>>, rx0: &'c mut bxcan::Rx0<BxcanInstance<'d, T>>,
rx1: &'c mut bxcan::Rx1<BxcanInstance<'d, T>>,
} }
impl<'c, 'd, T: Instance> CanRx<'c, 'd, T> { impl<'c, 'd, T: Instance> CanRx<'c, 'd, T> {
@ -538,7 +546,7 @@ impl<'d, T: Instance> Drop for Can<'d, T> {
} }
impl<'d, T: Instance> Deref for Can<'d, T> { impl<'d, T: Instance> Deref for Can<'d, T> {
type Target = RefCell<bxcan::Can<BxcanInstance<'d, T>>>; type Target = bxcan::Can<BxcanInstance<'d, T>>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.can &self.can

View File

@ -1,136 +1,66 @@
//! Provide access to the STM32 digital-to-analog converter (DAC).
#![macro_use] #![macro_use]
//! Provide access to the STM32 digital-to-analog converter (DAC).
use core::marker::PhantomData; use core::marker::PhantomData;
use embassy_hal_internal::{into_ref, PeripheralRef}; use embassy_hal_internal::{into_ref, PeripheralRef};
use crate::dma::NoDma;
#[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))]
use crate::pac::dac; use crate::pac::dac;
use crate::rcc::RccPeripheral; use crate::rcc::RccPeripheral;
use crate::{peripherals, Peripheral}; use crate::{peripherals, Peripheral};
mod tsel;
pub use tsel::TriggerSel;
/// Operating mode for DAC channel
#[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))]
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Custom Errors pub enum Mode {
pub enum Error { /// Normal mode, channel is connected to external pin with buffer enabled.
UnconfiguredChannel, NormalExternalBuffered,
InvalidValue, /// Normal mode, channel is connected to external pin and internal peripherals
/// with buffer enabled.
NormalBothBuffered,
/// Normal mode, channel is connected to external pin with buffer disabled.
NormalExternalUnbuffered,
/// Normal mode, channel is connected to internal peripherals with buffer disabled.
NormalInternalUnbuffered,
/// Sample-and-hold mode, channel is connected to external pin with buffer enabled.
SampleHoldExternalBuffered,
/// Sample-and-hold mode, channel is connected to external pin and internal peripherals
/// with buffer enabled.
SampleHoldBothBuffered,
/// Sample-and-hold mode, channel is connected to external pin and internal peripherals
/// with buffer disabled.
SampleHoldBothUnbuffered,
/// Sample-and-hold mode, channel is connected to internal peripherals with buffer disabled.
SampleHoldInternalUnbuffered,
} }
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] impl Mode {
/// DAC Channels fn mode(&self) -> dac::vals::Mode {
pub enum Channel {
Ch1,
Ch2,
}
impl Channel {
const fn index(&self) -> usize {
match self { match self {
Channel::Ch1 => 0, Mode::NormalExternalBuffered => dac::vals::Mode::NORMAL_EXT_BUFEN,
Channel::Ch2 => 1, Mode::NormalBothBuffered => dac::vals::Mode::NORMAL_EXT_INT_BUFEN,
Mode::NormalExternalUnbuffered => dac::vals::Mode::NORMAL_EXT_BUFDIS,
Mode::NormalInternalUnbuffered => dac::vals::Mode::NORMAL_INT_BUFDIS,
Mode::SampleHoldExternalBuffered => dac::vals::Mode::SAMPHOLD_EXT_BUFEN,
Mode::SampleHoldBothBuffered => dac::vals::Mode::SAMPHOLD_EXT_INT_BUFEN,
Mode::SampleHoldBothUnbuffered => dac::vals::Mode::SAMPHOLD_EXT_INT_BUFDIS,
Mode::SampleHoldInternalUnbuffered => dac::vals::Mode::SAMPHOLD_INT_BUFDIS,
} }
} }
} }
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Trigger sources for CH1 /// Single 8 or 12 bit value that can be output by the DAC.
pub enum Ch1Trigger { ///
#[cfg(dac_v3)] /// 12-bit values outside the permitted range are silently truncated.
Tim1,
Tim2,
#[cfg(not(dac_v3))]
Tim3,
#[cfg(dac_v3)]
Tim4,
#[cfg(dac_v3)]
Tim5,
Tim6,
Tim7,
#[cfg(dac_v3)]
Tim8,
Tim15,
#[cfg(dac_v3)]
Hrtim1Dactrg1,
#[cfg(dac_v3)]
Hrtim1Dactrg2,
#[cfg(dac_v3)]
Lptim1,
#[cfg(dac_v3)]
Lptim2,
#[cfg(dac_v3)]
Lptim3,
Exti9,
Software,
}
impl Ch1Trigger {
fn tsel(&self) -> dac::vals::Tsel1 {
match self {
#[cfg(dac_v3)]
Ch1Trigger::Tim1 => dac::vals::Tsel1::TIM1_TRGO,
Ch1Trigger::Tim2 => dac::vals::Tsel1::TIM2_TRGO,
#[cfg(not(dac_v3))]
Ch1Trigger::Tim3 => dac::vals::Tsel1::TIM3_TRGO,
#[cfg(dac_v3)]
Ch1Trigger::Tim4 => dac::vals::Tsel1::TIM4_TRGO,
#[cfg(dac_v3)]
Ch1Trigger::Tim5 => dac::vals::Tsel1::TIM5_TRGO,
Ch1Trigger::Tim6 => dac::vals::Tsel1::TIM6_TRGO,
Ch1Trigger::Tim7 => dac::vals::Tsel1::TIM7_TRGO,
#[cfg(dac_v3)]
Ch1Trigger::Tim8 => dac::vals::Tsel1::TIM8_TRGO,
Ch1Trigger::Tim15 => dac::vals::Tsel1::TIM15_TRGO,
#[cfg(dac_v3)]
Ch1Trigger::Hrtim1Dactrg1 => dac::vals::Tsel1::HRTIM1_DACTRG1,
#[cfg(dac_v3)]
Ch1Trigger::Hrtim1Dactrg2 => dac::vals::Tsel1::HRTIM1_DACTRG2,
#[cfg(dac_v3)]
Ch1Trigger::Lptim1 => dac::vals::Tsel1::LPTIM1_OUT,
#[cfg(dac_v3)]
Ch1Trigger::Lptim2 => dac::vals::Tsel1::LPTIM2_OUT,
#[cfg(dac_v3)]
Ch1Trigger::Lptim3 => dac::vals::Tsel1::LPTIM3_OUT,
Ch1Trigger::Exti9 => dac::vals::Tsel1::EXTI9,
Ch1Trigger::Software => dac::vals::Tsel1::SOFTWARE,
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Trigger sources for CH2
pub enum Ch2Trigger {
Tim6,
Tim8,
Tim7,
Tim5,
Tim2,
Tim4,
Exti9,
Software,
}
impl Ch2Trigger {
fn tsel(&self) -> dac::vals::Tsel2 {
match self {
Ch2Trigger::Tim6 => dac::vals::Tsel2::TIM6_TRGO,
Ch2Trigger::Tim8 => dac::vals::Tsel2::TIM8_TRGO,
Ch2Trigger::Tim7 => dac::vals::Tsel2::TIM7_TRGO,
Ch2Trigger::Tim5 => dac::vals::Tsel2::TIM5_TRGO,
Ch2Trigger::Tim2 => dac::vals::Tsel2::TIM2_TRGO,
Ch2Trigger::Tim4 => dac::vals::Tsel2::TIM4_TRGO,
Ch2Trigger::Exti9 => dac::vals::Tsel2::EXTI9,
Ch2Trigger::Software => dac::vals::Tsel2::SOFTWARE,
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Single 8 or 12 bit value that can be output by the DAC
pub enum Value { pub enum Value {
// 8 bit value // 8 bit value
Bit8(u8), Bit8(u8),
@ -142,7 +72,21 @@ pub enum Value {
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Array variant of [`Value`] /// Dual 8 or 12 bit values that can be output by the DAC channels 1 and 2 simultaneously.
///
/// 12-bit values outside the permitted range are silently truncated.
pub enum DualValue {
// 8 bit value
Bit8(u8, u8),
// 12 bit value stored in a u16, left-aligned
Bit12Left(u16, u16),
// 12 bit value stored in a u16, right-aligned
Bit12Right(u16, u16),
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Array variant of [`Value`].
pub enum ValueArray<'a> { pub enum ValueArray<'a> {
// 8 bit values // 8 bit values
Bit8(&'a [u8]), Bit8(&'a [u8]),
@ -151,398 +95,395 @@ pub enum ValueArray<'a> {
// 12 bit values stored in a u16, right-aligned // 12 bit values stored in a u16, right-aligned
Bit12Right(&'a [u16]), Bit12Right(&'a [u16]),
} }
/// Provide common functions for DAC channels
pub trait DacChannel<T: Instance, Tx> {
const CHANNEL: Channel;
/// Enable trigger of the given channel /// Driver for a single DAC channel.
fn set_trigger_enable(&mut self, on: bool) -> Result<(), Error> {
T::regs().cr().modify(|reg| {
reg.set_ten(Self::CHANNEL.index(), on);
});
Ok(())
}
/// Set mode register of the given channel
#[cfg(any(dac_v2, dac_v3))]
fn set_channel_mode(&mut self, val: u8) -> Result<(), Error> {
T::regs().mcr().modify(|reg| {
reg.set_mode(Self::CHANNEL.index(), val);
});
Ok(())
}
/// Set enable register of the given channel
fn set_channel_enable(&mut self, on: bool) -> Result<(), Error> {
T::regs().cr().modify(|reg| {
reg.set_en(Self::CHANNEL.index(), on);
});
Ok(())
}
/// Enable the DAC channel `ch`
fn enable_channel(&mut self) -> Result<(), Error> {
self.set_channel_enable(true)
}
/// Disable the DAC channel `ch`
fn disable_channel(&mut self) -> Result<(), Error> {
self.set_channel_enable(false)
}
/// Perform a software trigger on `ch`
fn trigger(&mut self) {
T::regs().swtrigr().write(|reg| {
reg.set_swtrig(Self::CHANNEL.index(), true);
});
}
/// Set a value to be output by the DAC on trigger.
///
/// The `value` is written to the corresponding "data holding register".
fn set(&mut self, value: Value) -> Result<(), Error> {
match value {
Value::Bit8(v) => T::regs().dhr8r(Self::CHANNEL.index()).write(|reg| reg.set_dhr(v)),
Value::Bit12Left(v) => T::regs().dhr12l(Self::CHANNEL.index()).write(|reg| reg.set_dhr(v)),
Value::Bit12Right(v) => T::regs().dhr12r(Self::CHANNEL.index()).write(|reg| reg.set_dhr(v)),
}
Ok(())
}
}
/// Hold two DAC channels
/// ///
/// Note: This consumes the DAC `Instance` only once, allowing to get both channels simultaneously. /// If you want to use both channels, either together or independently,
/// /// create a [`Dac`] first and use it to access each channel.
/// # Example for obtaining both DAC channels pub struct DacChannel<'d, T: Instance, const N: u8, DMA = NoDma> {
///
/// ```ignore
/// // DMA channels and pins may need to be changed for your controller
/// let (dac_ch1, dac_ch2) =
/// embassy_stm32::dac::Dac::new(p.DAC1, p.DMA1_CH3, p.DMA1_CH4, p.PA4, p.PA5).split();
/// ```
pub struct Dac<'d, T: Instance, TxCh1, TxCh2> {
ch1: DacCh1<'d, T, TxCh1>,
ch2: DacCh2<'d, T, TxCh2>,
}
/// DAC CH1
///
/// Note: This consumes the DAC `Instance`. Use [`Dac::new`] to get both channels simultaneously.
pub struct DacCh1<'d, T: Instance, Tx> {
/// To consume T
_peri: PeripheralRef<'d, T>,
#[allow(unused)] // For chips whose DMA is not (yet) supported
dma: PeripheralRef<'d, Tx>,
}
/// DAC CH2
///
/// Note: This consumes the DAC `Instance`. Use [`Dac::new`] to get both channels simultaneously.
pub struct DacCh2<'d, T: Instance, Tx> {
/// Instead of PeripheralRef to consume T
phantom: PhantomData<&'d mut T>, phantom: PhantomData<&'d mut T>,
#[allow(unused)] // For chips whose DMA is not (yet) supported #[allow(unused)]
dma: PeripheralRef<'d, Tx>, dma: PeripheralRef<'d, DMA>,
} }
impl<'d, T: Instance, Tx> DacCh1<'d, T, Tx> { pub type DacCh1<'d, T, DMA = NoDma> = DacChannel<'d, T, 1, DMA>;
/// Obtain DAC CH1 pub type DacCh2<'d, T, DMA = NoDma> = DacChannel<'d, T, 2, DMA>;
pub fn new(
peri: impl Peripheral<P = T> + 'd,
dma: impl Peripheral<P = Tx> + 'd,
pin: impl Peripheral<P = impl DacPin<T, 1>> + crate::gpio::sealed::Pin + 'd,
) -> Self {
pin.set_as_analog();
into_ref!(peri, dma);
T::enable_and_reset();
let mut dac = Self { _peri: peri, dma }; impl<'d, T: Instance, const N: u8, DMA> DacChannel<'d, T, N, DMA> {
const IDX: usize = (N - 1) as usize;
// Configure each activated channel. All results can be `unwrap`ed since they /// Create a new `DacChannel` instance, consuming the underlying DAC peripheral.
// will only error if the channel is not configured (i.e. ch1, ch2 are false)
#[cfg(any(dac_v2, dac_v3))]
dac.set_channel_mode(0).unwrap();
dac.enable_channel().unwrap();
dac.set_trigger_enable(true).unwrap();
dac
}
/// Select a new trigger for this channel
/// ///
/// **Important**: This disables the channel! /// If you're not using DMA, pass [`dma::NoDma`] for the `dma` argument.
pub fn select_trigger(&mut self, trigger: Ch1Trigger) -> Result<(), Error> {
unwrap!(self.disable_channel());
T::regs().cr().modify(|reg| {
reg.set_tsel1(trigger.tsel());
});
Ok(())
}
/// Write `data` to the DAC CH1 via DMA.
/// ///
/// To prevent delays/glitches when outputting a periodic waveform, the `circular` flag can be set. /// The channel is enabled on creation and begins to drive the output pin.
/// This will configure a circular DMA transfer that periodically outputs the `data`. /// Note that some methods, such as `set_trigger()` and `set_mode()`, will
/// Note that for performance reasons in circular mode the transfer complete interrupt is disabled. /// disable the channel; you must re-enable it with `enable()`.
/// ///
/// **Important:** Channel 1 has to be configured for the DAC instance! /// By default, triggering is disabled, but it can be enabled using
pub async fn write(&mut self, data: ValueArray<'_>, circular: bool) -> Result<(), Error> /// [`DacChannel::set_trigger()`].
where
Tx: DmaCh1<T>,
{
let channel = Channel::Ch1.index();
debug!("Writing to channel {}", channel);
// Enable DAC and DMA
T::regs().cr().modify(|w| {
w.set_en(channel, true);
w.set_dmaen(channel, true);
});
let tx_request = self.dma.request();
let dma_channel = &mut self.dma;
let tx_options = crate::dma::TransferOptions {
circular,
half_transfer_ir: false,
complete_transfer_ir: !circular,
..Default::default()
};
// Initiate the correct type of DMA transfer depending on what data is passed
let tx_f = match data {
ValueArray::Bit8(buf) => unsafe {
crate::dma::Transfer::new_write(
dma_channel,
tx_request,
buf,
T::regs().dhr8r(channel).as_ptr() as *mut u8,
tx_options,
)
},
ValueArray::Bit12Left(buf) => unsafe {
crate::dma::Transfer::new_write(
dma_channel,
tx_request,
buf,
T::regs().dhr12l(channel).as_ptr() as *mut u16,
tx_options,
)
},
ValueArray::Bit12Right(buf) => unsafe {
crate::dma::Transfer::new_write(
dma_channel,
tx_request,
buf,
T::regs().dhr12r(channel).as_ptr() as *mut u16,
tx_options,
)
},
};
tx_f.await;
// finish dma
// TODO: Do we need to check any status registers here?
T::regs().cr().modify(|w| {
// Disable the DAC peripheral
w.set_en(channel, false);
// Disable the DMA. TODO: Is this necessary?
w.set_dmaen(channel, false);
});
Ok(())
}
}
impl<'d, T: Instance, Tx> DacCh2<'d, T, Tx> {
/// Obtain DAC CH2
pub fn new( pub fn new(
_peri: impl Peripheral<P = T> + 'd, _peri: impl Peripheral<P = T> + 'd,
dma: impl Peripheral<P = Tx> + 'd, dma: impl Peripheral<P = DMA> + 'd,
pin: impl Peripheral<P = impl DacPin<T, 2>> + crate::gpio::sealed::Pin + 'd, pin: impl Peripheral<P = impl DacPin<T, N> + crate::gpio::sealed::Pin> + 'd,
) -> Self { ) -> Self {
into_ref!(dma, pin);
pin.set_as_analog(); pin.set_as_analog();
into_ref!(_peri, dma);
T::enable_and_reset(); T::enable_and_reset();
let mut dac = Self { let mut dac = Self {
phantom: PhantomData, phantom: PhantomData,
dma, dma,
}; };
#[cfg(any(dac_v5, dac_v6, dac_v7))]
// Configure each activated channel. All results can be `unwrap`ed since they dac.set_hfsel();
// will only error if the channel is not configured (i.e. ch1, ch2 are false) dac.enable();
#[cfg(any(dac_v2, dac_v3))]
dac.set_channel_mode(0).unwrap();
dac.enable_channel().unwrap();
dac.set_trigger_enable(true).unwrap();
dac dac
} }
/// Select a new trigger for this channel /// Create a new `DacChannel` instance where the external output pin is not used,
pub fn select_trigger(&mut self, trigger: Ch2Trigger) -> Result<(), Error> { /// so the DAC can only be used to generate internal signals.
unwrap!(self.disable_channel()); /// The GPIO pin is therefore available to be used for other functions.
T::regs().cr().modify(|reg| {
reg.set_tsel2(trigger.tsel());
});
Ok(())
}
/// Write `data` to the DAC CH2 via DMA.
/// ///
/// To prevent delays/glitches when outputting a periodic waveform, the `circular` flag can be set. /// The channel is set to [`Mode::NormalInternalUnbuffered`] and enabled on creation.
/// This will configure a circular DMA transfer that periodically outputs the `data`. /// Note that some methods, such as `set_trigger()` and `set_mode()`, will disable the
/// Note that for performance reasons in circular mode the transfer complete interrupt is disabled. /// channel; you must re-enable it with `enable()`.
/// ///
/// **Important:** Channel 2 has to be configured for the DAC instance! /// If you're not using DMA, pass [`dma::NoDma`] for the `dma` argument.
pub async fn write(&mut self, data: ValueArray<'_>, circular: bool) -> Result<(), Error>
where
Tx: DmaCh2<T>,
{
let channel = Channel::Ch2.index();
debug!("Writing to channel {}", channel);
// Enable DAC and DMA
T::regs().cr().modify(|w| {
w.set_en(channel, true);
w.set_dmaen(channel, true);
});
let tx_request = self.dma.request();
let dma_channel = &mut self.dma;
let tx_options = crate::dma::TransferOptions {
circular,
half_transfer_ir: false,
complete_transfer_ir: !circular,
..Default::default()
};
// Initiate the correct type of DMA transfer depending on what data is passed
let tx_f = match data {
ValueArray::Bit8(buf) => unsafe {
crate::dma::Transfer::new_write(
dma_channel,
tx_request,
buf,
T::regs().dhr8r(channel).as_ptr() as *mut u8,
tx_options,
)
},
ValueArray::Bit12Left(buf) => unsafe {
crate::dma::Transfer::new_write(
dma_channel,
tx_request,
buf,
T::regs().dhr12l(channel).as_ptr() as *mut u16,
tx_options,
)
},
ValueArray::Bit12Right(buf) => unsafe {
crate::dma::Transfer::new_write(
dma_channel,
tx_request,
buf,
T::regs().dhr12r(channel).as_ptr() as *mut u16,
tx_options,
)
},
};
tx_f.await;
// finish dma
// TODO: Do we need to check any status registers here?
T::regs().cr().modify(|w| {
// Disable the DAC peripheral
w.set_en(channel, false);
// Disable the DMA. TODO: Is this necessary?
w.set_dmaen(channel, false);
});
Ok(())
}
}
impl<'d, T: Instance, TxCh1, TxCh2> Dac<'d, T, TxCh1, TxCh2> {
/// Create a new DAC instance with both channels.
/// ///
/// This is used to obtain two independent channels via `split()` for use e.g. with DMA. /// By default, triggering is disabled, but it can be enabled using
pub fn new( /// [`DacChannel::set_trigger()`].
peri: impl Peripheral<P = T> + 'd, #[cfg(all(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7), not(any(stm32h56x, stm32h57x))))]
dma_ch1: impl Peripheral<P = TxCh1> + 'd, pub fn new_internal(_peri: impl Peripheral<P = T> + 'd, dma: impl Peripheral<P = DMA> + 'd) -> Self {
dma_ch2: impl Peripheral<P = TxCh2> + 'd, into_ref!(dma);
pin_ch1: impl Peripheral<P = impl DacPin<T, 1>> + crate::gpio::sealed::Pin + 'd,
pin_ch2: impl Peripheral<P = impl DacPin<T, 2>> + crate::gpio::sealed::Pin + 'd,
) -> Self {
pin_ch1.set_as_analog();
pin_ch2.set_as_analog();
into_ref!(peri, dma_ch1, dma_ch2);
T::enable_and_reset(); T::enable_and_reset();
let mut dac = Self {
let mut dac_ch1 = DacCh1 {
_peri: peri,
dma: dma_ch1,
};
let mut dac_ch2 = DacCh2 {
phantom: PhantomData, phantom: PhantomData,
dma: dma_ch2, dma,
}; };
#[cfg(any(dac_v5, dac_v6, dac_v7))]
dac.set_hfsel();
dac.set_mode(Mode::NormalInternalUnbuffered);
dac.enable();
dac
}
// Configure each activated channel. All results can be `unwrap`ed since they /// Enable or disable this channel.
// will only error if the channel is not configured (i.e. ch1, ch2 are false) pub fn set_enable(&mut self, on: bool) {
#[cfg(any(dac_v2, dac_v3))] critical_section::with(|_| {
dac_ch1.set_channel_mode(0).unwrap(); T::regs().cr().modify(|reg| {
dac_ch1.enable_channel().unwrap(); reg.set_en(Self::IDX, on);
dac_ch1.set_trigger_enable(true).unwrap(); });
});
}
#[cfg(any(dac_v2, dac_v3))] /// Enable this channel.
dac_ch2.set_channel_mode(0).unwrap(); pub fn enable(&mut self) {
dac_ch2.enable_channel().unwrap(); self.set_enable(true)
dac_ch2.set_trigger_enable(true).unwrap(); }
Self { /// Disable this channel.
ch1: dac_ch1, pub fn disable(&mut self) {
ch2: dac_ch2, self.set_enable(false)
}
/// Set the trigger source for this channel.
///
/// This method disables the channel, so you may need to re-enable afterwards.
pub fn set_trigger(&mut self, source: TriggerSel) {
critical_section::with(|_| {
T::regs().cr().modify(|reg| {
reg.set_en(Self::IDX, false);
reg.set_tsel(Self::IDX, source as u8);
});
});
}
/// Enable or disable triggering for this channel.
pub fn set_triggering(&mut self, on: bool) {
critical_section::with(|_| {
T::regs().cr().modify(|reg| {
reg.set_ten(Self::IDX, on);
});
});
}
/// Software trigger this channel.
pub fn trigger(&mut self) {
T::regs().swtrigr().write(|reg| {
reg.set_swtrig(Self::IDX, true);
});
}
/// Set mode of this channel.
///
/// This method disables the channel, so you may need to re-enable afterwards.
#[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))]
pub fn set_mode(&mut self, mode: Mode) {
critical_section::with(|_| {
T::regs().cr().modify(|reg| {
reg.set_en(Self::IDX, false);
});
T::regs().mcr().modify(|reg| {
reg.set_mode(Self::IDX, mode.mode());
});
});
}
/// Write a new value to this channel.
///
/// If triggering is not enabled, the new value is immediately output; otherwise,
/// it will be output after the next trigger.
pub fn set(&mut self, value: Value) {
match value {
Value::Bit8(v) => T::regs().dhr8r(Self::IDX).write(|reg| reg.set_dhr(v)),
Value::Bit12Left(v) => T::regs().dhr12l(Self::IDX).write(|reg| reg.set_dhr(v)),
Value::Bit12Right(v) => T::regs().dhr12r(Self::IDX).write(|reg| reg.set_dhr(v)),
} }
} }
/// Split the DAC into CH1 and CH2 for independent use. /// Read the current output value of the DAC.
pub fn split(self) -> (DacCh1<'d, T, TxCh1>, DacCh2<'d, T, TxCh2>) { pub fn read(&self) -> u16 {
T::regs().dor(Self::IDX).read().dor()
}
/// Set HFSEL as appropriate for the current peripheral clock frequency.
#[cfg(dac_v5)]
fn set_hfsel(&mut self) {
if T::frequency() >= crate::time::mhz(80) {
critical_section::with(|_| {
T::regs().cr().modify(|reg| {
reg.set_hfsel(true);
});
});
}
}
/// Set HFSEL as appropriate for the current peripheral clock frequency.
#[cfg(any(dac_v6, dac_v7))]
fn set_hfsel(&mut self) {
if T::frequency() >= crate::time::mhz(160) {
critical_section::with(|_| {
T::regs().mcr().modify(|reg| {
reg.set_hfsel(0b10);
});
});
} else if T::frequency() >= crate::time::mhz(80) {
critical_section::with(|_| {
T::regs().mcr().modify(|reg| {
reg.set_hfsel(0b01);
});
});
}
}
}
macro_rules! impl_dma_methods {
($n:literal, $trait:ident) => {
impl<'d, T: Instance, DMA> DacChannel<'d, T, $n, DMA>
where
DMA: $trait<T>,
{
/// Write `data` to this channel via DMA.
///
/// To prevent delays or glitches when outputing a periodic waveform, the `circular`
/// flag can be set. This configures a circular DMA transfer that continually outputs
/// `data`. Note that for performance reasons in circular mode the transfer-complete
/// interrupt is disabled.
#[cfg(not(gpdma))]
pub async fn write(&mut self, data: ValueArray<'_>, circular: bool) {
// Enable DAC and DMA
T::regs().cr().modify(|w| {
w.set_en(Self::IDX, true);
w.set_dmaen(Self::IDX, true);
});
let tx_request = self.dma.request();
let dma_channel = &mut self.dma;
let tx_options = crate::dma::TransferOptions {
circular,
half_transfer_ir: false,
complete_transfer_ir: !circular,
..Default::default()
};
// Initiate the correct type of DMA transfer depending on what data is passed
let tx_f = match data {
ValueArray::Bit8(buf) => unsafe {
crate::dma::Transfer::new_write(
dma_channel,
tx_request,
buf,
T::regs().dhr8r(Self::IDX).as_ptr() as *mut u8,
tx_options,
)
},
ValueArray::Bit12Left(buf) => unsafe {
crate::dma::Transfer::new_write(
dma_channel,
tx_request,
buf,
T::regs().dhr12l(Self::IDX).as_ptr() as *mut u16,
tx_options,
)
},
ValueArray::Bit12Right(buf) => unsafe {
crate::dma::Transfer::new_write(
dma_channel,
tx_request,
buf,
T::regs().dhr12r(Self::IDX).as_ptr() as *mut u16,
tx_options,
)
},
};
tx_f.await;
T::regs().cr().modify(|w| {
w.set_en(Self::IDX, false);
w.set_dmaen(Self::IDX, false);
});
}
}
};
}
impl_dma_methods!(1, DacDma1);
impl_dma_methods!(2, DacDma2);
impl<'d, T: Instance, const N: u8, DMA> Drop for DacChannel<'d, T, N, DMA> {
fn drop(&mut self) {
T::disable();
}
}
/// DAC driver.
///
/// Use this struct when you want to use both channels, either together or independently.
///
/// # Example
///
/// ```ignore
/// // Pins may need to be changed for your specific device.
/// let (dac_ch1, dac_ch2) = embassy_stm32::dac::Dac::new(p.DAC, NoDma, NoDma, p.PA4, p.PA5).split();
/// ```
pub struct Dac<'d, T: Instance, DMACh1 = NoDma, DMACh2 = NoDma> {
ch1: DacChannel<'d, T, 1, DMACh1>,
ch2: DacChannel<'d, T, 2, DMACh2>,
}
impl<'d, T: Instance, DMACh1, DMACh2> Dac<'d, T, DMACh1, DMACh2> {
/// Create a new `Dac` instance, consuming the underlying DAC peripheral.
///
/// This struct allows you to access both channels of the DAC, where available. You can either
/// call `split()` to obtain separate `DacChannel`s, or use methods on `Dac` to use
/// the two channels together.
///
/// The channels are enabled on creation and begins to drive their output pins.
/// Note that some methods, such as `set_trigger()` and `set_mode()`, will
/// disable the channel; you must re-enable them with `enable()`.
///
/// By default, triggering is disabled, but it can be enabled using the `set_trigger()`
/// method on the underlying channels.
pub fn new(
_peri: impl Peripheral<P = T> + 'd,
dma_ch1: impl Peripheral<P = DMACh1> + 'd,
dma_ch2: impl Peripheral<P = DMACh2> + 'd,
pin_ch1: impl Peripheral<P = impl DacPin<T, 1> + crate::gpio::sealed::Pin> + 'd,
pin_ch2: impl Peripheral<P = impl DacPin<T, 2> + crate::gpio::sealed::Pin> + 'd,
) -> Self {
into_ref!(dma_ch1, dma_ch2, pin_ch1, pin_ch2);
pin_ch1.set_as_analog();
pin_ch2.set_as_analog();
// Enable twice to increment the DAC refcount for each channel.
T::enable_and_reset();
T::enable_and_reset();
Self {
ch1: DacCh1 {
phantom: PhantomData,
dma: dma_ch1,
},
ch2: DacCh2 {
phantom: PhantomData,
dma: dma_ch2,
},
}
}
/// Create a new `Dac` instance where the external output pins are not used,
/// so the DAC can only be used to generate internal signals but the GPIO
/// pins remain available for other functions.
///
/// This struct allows you to access both channels of the DAC, where available. You can either
/// call `split()` to obtain separate `DacChannel`s, or use methods on `Dac` to use the two
/// channels together.
///
/// The channels are set to [`Mode::NormalInternalUnbuffered`] and enabled on creation.
/// Note that some methods, such as `set_trigger()` and `set_mode()`, will disable the
/// channel; you must re-enable them with `enable()`.
///
/// By default, triggering is disabled, but it can be enabled using the `set_trigger()`
/// method on the underlying channels.
#[cfg(all(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7), not(any(stm32h56x, stm32h57x))))]
pub fn new_internal(
_peri: impl Peripheral<P = T> + 'd,
dma_ch1: impl Peripheral<P = DMACh1> + 'd,
dma_ch2: impl Peripheral<P = DMACh2> + 'd,
) -> Self {
into_ref!(dma_ch1, dma_ch2);
// Enable twice to increment the DAC refcount for each channel.
T::enable_and_reset();
T::enable_and_reset();
Self {
ch1: DacCh1 {
phantom: PhantomData,
dma: dma_ch1,
},
ch2: DacCh2 {
phantom: PhantomData,
dma: dma_ch2,
},
}
}
/// Split this `Dac` into separate channels.
///
/// You can access and move the channels around separately after splitting.
pub fn split(self) -> (DacCh1<'d, T, DMACh1>, DacCh2<'d, T, DMACh2>) {
(self.ch1, self.ch2) (self.ch1, self.ch2)
} }
/// Get mutable reference to CH1 /// Temporarily access channel 1.
pub fn ch1_mut(&mut self) -> &mut DacCh1<'d, T, TxCh1> { pub fn ch1(&mut self) -> &mut DacCh1<'d, T, DMACh1> {
&mut self.ch1 &mut self.ch1
} }
/// Get mutable reference to CH2 /// Temporarily access channel 2.
pub fn ch2_mut(&mut self) -> &mut DacCh2<'d, T, TxCh2> { pub fn ch2(&mut self) -> &mut DacCh2<'d, T, DMACh2> {
&mut self.ch2 &mut self.ch2
} }
/// Get reference to CH1 /// Simultaneously update channels 1 and 2 with a new value.
pub fn ch1(&mut self) -> &DacCh1<'d, T, TxCh1> { ///
&self.ch1 /// If triggering is not enabled, the new values are immediately output;
/// otherwise, they will be output after the next trigger.
pub fn set(&mut self, values: DualValue) {
match values {
DualValue::Bit8(v1, v2) => T::regs().dhr8rd().write(|reg| {
reg.set_dhr(0, v1);
reg.set_dhr(1, v2);
}),
DualValue::Bit12Left(v1, v2) => T::regs().dhr12ld().write(|reg| {
reg.set_dhr(0, v1);
reg.set_dhr(1, v2);
}),
DualValue::Bit12Right(v1, v2) => T::regs().dhr12rd().write(|reg| {
reg.set_dhr(0, v1);
reg.set_dhr(1, v2);
}),
}
} }
/// Get reference to CH2
pub fn ch2(&mut self) -> &DacCh2<'d, T, TxCh2> {
&self.ch2
}
}
impl<'d, T: Instance, Tx> DacChannel<T, Tx> for DacCh1<'d, T, Tx> {
const CHANNEL: Channel = Channel::Ch1;
}
impl<'d, T: Instance, Tx> DacChannel<T, Tx> for DacCh2<'d, T, Tx> {
const CHANNEL: Channel = Channel::Ch2;
} }
pub(crate) mod sealed { pub(crate) mod sealed {
@ -552,34 +493,36 @@ pub(crate) mod sealed {
} }
pub trait Instance: sealed::Instance + RccPeripheral + 'static {} pub trait Instance: sealed::Instance + RccPeripheral + 'static {}
dma_trait!(DmaCh1, Instance); dma_trait!(DacDma1, Instance);
dma_trait!(DmaCh2, Instance); dma_trait!(DacDma2, Instance);
/// Marks a pin that can be used with the DAC /// Marks a pin that can be used with the DAC
pub trait DacPin<T: Instance, const C: u8>: crate::gpio::Pin + 'static {} pub trait DacPin<T: Instance, const C: u8>: crate::gpio::Pin + 'static {}
foreach_peripheral!( foreach_peripheral!(
(dac, $inst:ident) => { (dac, $inst:ident) => {
// H7 uses single bit for both DAC1 and DAC2, this is a hack until a proper fix is implemented // H7 uses single bit for both DAC1 and DAC2, this is a hack until a proper fix is implemented
#[cfg(any(rcc_h7, rcc_h7rm0433))] #[cfg(any(rcc_h7, rcc_h7rm0433))]
impl crate::rcc::sealed::RccPeripheral for peripherals::$inst { impl crate::rcc::sealed::RccPeripheral for peripherals::$inst {
fn frequency() -> crate::time::Hertz { fn frequency() -> crate::time::Hertz {
critical_section::with(|_| unsafe { crate::rcc::get_freqs().apb1 }) critical_section::with(|_| unsafe { crate::rcc::get_freqs().pclk1 })
} }
fn enable_and_reset_with_cs(_cs: critical_section::CriticalSection) { fn enable_and_reset_with_cs(_cs: critical_section::CriticalSection) {
crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(true)); // TODO: Increment refcount?
crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(false)); crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(true));
crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(true)); crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(false));
} crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(true));
}
fn disable_with_cs(_cs: critical_section::CriticalSection) { fn disable_with_cs(_cs: critical_section::CriticalSection) {
crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(false)) // TODO: Decrement refcount?
} crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(false))
} }
}
#[cfg(any(rcc_h7, rcc_h7rm0433))] #[cfg(any(rcc_h7, rcc_h7rm0433))]
impl crate::rcc::RccPeripheral for peripherals::$inst {} impl crate::rcc::RccPeripheral for peripherals::$inst {}
impl crate::dac::sealed::Instance for peripherals::$inst { impl crate::dac::sealed::Instance for peripherals::$inst {
fn regs() -> &'static crate::pac::dac::Dac { fn regs() -> &'static crate::pac::dac::Dac {

View File

@ -0,0 +1,282 @@
/// Trigger selection for STM32F0.
#[cfg(stm32f0)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TriggerSel {
Tim6 = 0,
Tim3 = 1,
Tim7 = 2,
Tim15 = 3,
Tim2 = 4,
Exti9 = 6,
Software = 7,
}
/// Trigger selection for STM32F1.
#[cfg(stm32f1)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TriggerSel {
Tim6 = 0,
#[cfg(any(stm32f100, stm32f105, stm32f107))]
Tim3 = 1,
#[cfg(any(stm32f101, stm32f103))]
Tim8 = 1,
Tim7 = 2,
#[cfg(any(stm32f101, stm32f103, stm32f105, stm32f107))]
Tim5 = 3,
#[cfg(all(stm32f100, any(flashsize_4, flashsize_6, flashsize_8, flashsize_b)))]
Tim15 = 3,
#[cfg(all(stm32f100, any(flashsize_c, flashsize_d, flashsize_e)))]
/// Can be remapped to TIM15 with MISC_REMAP in AFIO_MAPR2.
Tim5Or15 = 3,
Tim2 = 4,
Tim4 = 5,
Exti9 = 6,
Software = 7,
}
/// Trigger selection for STM32F2/F4/F7/L4, except F410 or L4+.
#[cfg(all(any(stm32f2, stm32f4, stm32f7, stm32l4_nonplus), not(stm32f410)))]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TriggerSel {
Tim6 = 0,
Tim8 = 1,
#[cfg(not(any(stm32l45x, stm32l46x)))]
Tim7 = 2,
Tim5 = 3,
Tim2 = 4,
Tim4 = 5,
Exti9 = 6,
Software = 7,
}
/// Trigger selection for STM32F410.
#[cfg(stm32f410)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TriggerSel {
Tim5 = 3,
Exti9 = 6,
Software = 7,
}
/// Trigger selection for STM32F301/2 and 318.
#[cfg(any(stm32f301, stm32f302, stm32f318))]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TriggerSel {
Tim6 = 0,
#[cfg(stm32f302)]
/// Requires DAC_TRIG_RMP set in SYSCFG_CFGR1.
Tim3 = 1,
Tim15 = 3,
Tim2 = 4,
#[cfg(all(stm32f302, any(flashsize_6, flashsize_8)))]
Tim4 = 5,
Exti9 = 6,
Software = 7,
}
/// Trigger selection for STM32F303/3x8 (excluding 318 which is like 301, and 378 which is 37x).
#[cfg(any(stm32f303, stm32f328, stm32f358, stm32f398))]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TriggerSel {
Tim6 = 0,
/// * DAC1: defaults to TIM8 but can be remapped to TIM3 with DAC_TRIG_RMP in SYSCFG_CFGR1
/// * DAC2: always TIM3
Tim8Or3 = 1,
Tim7 = 2,
Tim15 = 3,
Tim2 = 4,
Tim4 = 5,
Exti9 = 6,
Software = 7,
}
/// Trigger selection for STM32F37x.
#[cfg(any(stm32f373, stm32f378))]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TriggerSel {
Tim6 = 0,
Tim3 = 1,
Tim7 = 2,
/// TIM5 on DAC1, TIM18 on DAC2
Dac1Tim5Dac2Tim18 = 3,
Tim2 = 4,
Tim4 = 5,
Exti9 = 6,
Software = 7,
}
/// Trigger selection for STM32F334.
#[cfg(stm32f334)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TriggerSel {
Tim6 = 0,
/// Requires DAC_TRIG_RMP set in SYSCFG_CFGR1.
Tim3 = 1,
Tim7 = 2,
/// Can be remapped to HRTIM_DACTRG1 using DAC1_TRIG3_RMP in SYSCFG_CFGR3.
Tim15OrHrtimDacTrg1 = 3,
Tim2 = 4,
/// Requires DAC_TRIG5_RMP set in SYSCFG_CFGR3.
HrtimDacTrg2 = 5,
}
/// Trigger selection for STM32L0.
#[cfg(stm32l0)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TriggerSel {
Tim6 = 0,
Tim3 = 1,
Tim3Ch3 = 2,
Tim21 = 3,
Tim2 = 4,
Tim7 = 5,
Exti9 = 6,
Software = 7,
}
/// Trigger selection for STM32L1.
#[cfg(stm32l1)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TriggerSel {
Tim6 = 0,
Tim7 = 2,
Tim9 = 3,
Tim2 = 4,
Tim4 = 5,
Exti9 = 6,
Software = 7,
}
/// Trigger selection for L4+, L5, U5, H7.
#[cfg(any(stm32l4_plus, stm32l5, stm32u5, stm32h7))]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TriggerSel {
Software = 0,
Tim1 = 1,
Tim2 = 2,
Tim4 = 3,
Tim5 = 4,
Tim6 = 5,
Tim7 = 6,
Tim8 = 7,
Tim15 = 8,
#[cfg(all(stm32h7, hrtim))]
Hrtim1DacTrg1 = 9,
#[cfg(all(stm32h7, hrtim))]
Hrtim1DacTrg2 = 10,
Lptim1 = 11,
#[cfg(not(stm32u5))]
Lptim2 = 12,
#[cfg(stm32u5)]
Lptim3 = 12,
Exti9 = 13,
#[cfg(any(stm32h7ax, stm32h7bx))]
/// RM0455 suggests this might be LPTIM2 on DAC1 and LPTIM3 on DAC2,
/// but it's probably wrong. Please let us know if you find out.
Lptim3 = 14,
#[cfg(any(stm32h72x, stm32h73x))]
Tim23 = 14,
#[cfg(any(stm32h72x, stm32h73x))]
Tim24 = 15,
}
/// Trigger selection for H5.
#[cfg(stm32h5)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TriggerSel {
Software = 0,
Tim1 = 1,
Tim2 = 2,
#[cfg(any(stm32h56x, stm32h57x))]
Tim4 = 3,
#[cfg(stm32h503)]
Tim3 = 3,
#[cfg(any(stm32h56x, stm32h57x))]
Tim5 = 4,
Tim6 = 5,
Tim7 = 6,
#[cfg(any(stm32h56x, stm32h57x))]
Tim8 = 7,
#[cfg(any(stm32h56x, stm32h57x))]
Tim15 = 8,
Lptim1 = 11,
Lptim2 = 12,
Exti9 = 13,
}
/// Trigger selection for G0.
#[cfg(stm32g0)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TriggerSel {
Software = 0,
Tim1 = 1,
Tim2 = 2,
Tim3 = 3,
Tim6 = 5,
Tim7 = 6,
Tim15 = 8,
Lptim1 = 11,
Lptim2 = 12,
Exti9 = 13,
}
/// Trigger selection for G4.
#[cfg(stm32g4)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TriggerSel {
Software = 0,
/// * DAC1, DAC2, DAC4: TIM8
/// * DAC3: TIM1
Dac124Tim8Dac3Tim1 = 1,
Tim7 = 2,
Tim15 = 3,
Tim2 = 4,
Tim4 = 5,
Exti9 = 6,
Tim6 = 7,
Tim3 = 8,
HrtimDacRstTrg1 = 9,
HrtimDacRstTrg2 = 10,
HrtimDacRstTrg3 = 11,
HrtimDacRstTrg4 = 12,
HrtimDacRstTrg5 = 13,
HrtimDacRstTrg6 = 14,
/// * DAC1, DAC4: HRTIM_DAC_TRG1
/// * DAC2: HRTIM_DAC_TRG2
/// * DAC3: HRTIM_DAC_TRG3
HrtimDacTrg123 = 15,
}
/// Trigger selection for WL.
#[cfg(stm32wl)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TriggerSel {
Software = 0,
Tim1 = 1,
Tim2 = 2,
Lptim1 = 11,
Lptim2 = 12,
Lptim3 = 13,
Exti9 = 14,
}
impl TriggerSel {
pub fn tsel(&self) -> u8 {
*self as u8
}
}

View File

@ -299,19 +299,15 @@ impl<'a, C: Channel> Transfer<'a, C> {
pub fn request_stop(&mut self) { pub fn request_stop(&mut self) {
let ch = self.channel.regs().ch(self.channel.num()); let ch = self.channel.regs().ch(self.channel.num());
ch.cr().modify(|w| {
// Disable the channel. Keep the IEs enabled so the irqs still fire. w.set_susp(true);
ch.cr().write(|w| {
w.set_tcie(true);
w.set_useie(true);
w.set_dteie(true);
w.set_suspie(true);
}) })
} }
pub fn is_running(&mut self) -> bool { pub fn is_running(&mut self) -> bool {
let ch = self.channel.regs().ch(self.channel.num()); let ch = self.channel.regs().ch(self.channel.num());
!ch.sr().read().tcf() let sr = ch.sr().read();
!sr.tcf() && !sr.suspf()
} }
/// Gets the total remaining transfers for the channel /// Gets the total remaining transfers for the channel

View File

@ -191,7 +191,7 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
// TODO MTU size setting not found for v1 ethernet, check if correct // TODO MTU size setting not found for v1 ethernet, check if correct
// NOTE(unsafe) We got the peripheral singleton, which means that `rcc::init` was called // NOTE(unsafe) We got the peripheral singleton, which means that `rcc::init` was called
let hclk = unsafe { crate::rcc::get_freqs() }.ahb1; let hclk = unsafe { crate::rcc::get_freqs() }.hclk1;
let hclk_mhz = hclk.0 / 1_000_000; let hclk_mhz = hclk.0 / 1_000_000;
// Set the MDC clock frequency in the range 1MHz - 2.5MHz // Set the MDC clock frequency in the range 1MHz - 2.5MHz

View File

@ -119,13 +119,11 @@ impl<'a> TDesRing<'a> {
// "Preceding reads and writes cannot be moved past subsequent writes." // "Preceding reads and writes cannot be moved past subsequent writes."
fence(Ordering::Release); fence(Ordering::Release);
self.index = self.index + 1;
if self.index == self.descriptors.len() {
self.index = 0;
}
// signal DMA it can try again. // signal DMA it can try again.
ETH.ethernet_dma().dmactx_dtpr().write(|w| w.0 = 0) // See issue #2129
ETH.ethernet_dma().dmactx_dtpr().write(|w| w.0 = &td as *const _ as u32);
self.index = (self.index + 1) % self.descriptors.len();
} }
} }
@ -237,21 +235,19 @@ impl<'a> RDesRing<'a> {
/// Pop the packet previously returned by `available`. /// Pop the packet previously returned by `available`.
pub(crate) fn pop_packet(&mut self) { pub(crate) fn pop_packet(&mut self) {
let descriptor = &mut self.descriptors[self.index]; let rd = &mut self.descriptors[self.index];
assert!(descriptor.available()); assert!(rd.available());
self.descriptors[self.index].set_ready(self.buffers[self.index].0.as_mut_ptr()); rd.set_ready(self.buffers[self.index].0.as_mut_ptr());
// "Preceding reads and writes cannot be moved past subsequent writes." // "Preceding reads and writes cannot be moved past subsequent writes."
fence(Ordering::Release); fence(Ordering::Release);
// signal DMA it can try again. // signal DMA it can try again.
ETH.ethernet_dma().dmacrx_dtpr().write(|w| w.0 = 0); // See issue #2129
ETH.ethernet_dma().dmacrx_dtpr().write(|w| w.0 = &rd as *const _ as u32);
// Increment index. // Increment index.
self.index += 1; self.index = (self.index + 1) % self.descriptors.len();
if self.index == self.descriptors.len() {
self.index = 0
}
} }
} }

View File

@ -164,7 +164,7 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
}); });
// NOTE(unsafe) We got the peripheral singleton, which means that `rcc::init` was called // NOTE(unsafe) We got the peripheral singleton, which means that `rcc::init` was called
let hclk = unsafe { crate::rcc::get_freqs() }.ahb1; let hclk = unsafe { crate::rcc::get_freqs() }.hclk1;
let hclk_mhz = hclk.0 / 1_000_000; let hclk_mhz = hclk.0 / 1_000_000;
// Set the MDC clock frequency in the range 1MHz - 2.5MHz // Set the MDC clock frequency in the range 1MHz - 2.5MHz

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