Compare commits
216 Commits
hil-test
...
use-execut
Author | SHA1 | Date | |
---|---|---|---|
0bd47c779b | |||
4481631326 | |||
cf13f70ea9 | |||
6bdacb4f69 | |||
ff3baf1e90 | |||
d5dcbadbbe | |||
0ad0d27150 | |||
03d500f548 | |||
8b46343b34 | |||
a3ea01473a | |||
cf84c8bfd1 | |||
09d7950313 | |||
267cbaebe6 | |||
31fc337e2f | |||
135f350020 | |||
897663e023 | |||
2218d30c80 | |||
f5c9e3baa6 | |||
47bac9df70 | |||
3efc3eee57 | |||
bc65b8f7ec | |||
78f8e6112a | |||
996c3c1f7e | |||
171cdb94c7 | |||
1fbc150fd6 | |||
259cf6192b | |||
5528c33649 | |||
e8ff5a2baf | |||
4f8c79c911 | |||
738971938a | |||
dec7c1a28b | |||
e0727fe1f6 | |||
ba9cc102e2 | |||
32b59148a8 | |||
bd2e6b0422 | |||
85059f693b | |||
c211d51c06 | |||
7f1fd199bb | |||
5ddd2cf9a6 | |||
88f893da45 | |||
aedd41eac4 | |||
19ba7da3fd | |||
06a83c0f89 | |||
766ec77ec5 | |||
d1af696605 | |||
2386619f1f | |||
382949e1ff | |||
454828accb | |||
cf82fa687c | |||
7f258cd3c4 | |||
3a939fb511 | |||
b8e9e00b44 | |||
ff2f1049b1 | |||
be17e1b363 | |||
f3c77e59c4 | |||
30424d83ff | |||
e3be0b957a | |||
5221705495 | |||
814e096d22 | |||
5a60024af7 | |||
f482a105b8 | |||
7589b5e13e | |||
2efa73f431 | |||
270ec324b0 | |||
ca0d02933b | |||
5bc7557826 | |||
4947b13615 | |||
006260fedd | |||
e3ee24017d | |||
467b53076c | |||
27e6634c9d | |||
1f9b649f80 | |||
bef9b7a853 | |||
50a983fd9b | |||
7e5deae589 | |||
a2c440ef8c | |||
fd670a9ae5 | |||
ef69f386ab | |||
872f1ec4c2 | |||
6ccd8c051e | |||
c46418f123 | |||
19ff043acd | |||
ea99671729 | |||
8eff749823 | |||
cdcd3e26dd | |||
18c1f9dd56 | |||
ac7134ed0d | |||
ace5221080 | |||
2376b3bdfa | |||
f00e97a5f1 | |||
066dc297ed | |||
4fe344ebc0 | |||
0f2208c0af | |||
6c42885d4a | |||
39c7371621 | |||
4647df14b1 | |||
3b33cc4691 | |||
b3367be9c8 | |||
da4feb3693 | |||
05a4bb3a4a | |||
4b4c28d875 | |||
1977acdb11 | |||
0b015bd727 | |||
79acb560ec | |||
94586576a0 | |||
a3e200d011 | |||
3bccb67231 | |||
553f0158c0 | |||
b44a5fcaf6 | |||
8effff3383 | |||
05f983c607 | |||
d44383e9a7 | |||
37a773c037 | |||
d1adc93614 | |||
78a7ee7ec4 | |||
e3fe13e905 | |||
38bfa6916f | |||
50139752bc | |||
db4cd73894 | |||
8369f7614a | |||
326bc98bd2 | |||
584fc358fd | |||
b8f9341edc | |||
7ca557b917 | |||
8d8d50cc7a | |||
5948d934d3 | |||
746936f8cd | |||
8f543062aa | |||
15660cfc68 | |||
74f70dc7b4 | |||
7084570478 | |||
58719dcb58 | |||
b4eef6b1ee | |||
70a700e430 | |||
b8679c0cc8 | |||
ad861179cc | |||
d9b00c01e0 | |||
239ad5ebea | |||
46cffcc8d4 | |||
e6462f1e88 | |||
d464d1a841 | |||
980c3cf42b | |||
28eb4cd817 | |||
3de01bc223 | |||
0272deb158 | |||
c4a8b79dbc | |||
fa45dcd034 | |||
6ff91851b1 | |||
8911a4d855 | |||
056c409443 | |||
3f2abd4fd5 | |||
dc467e89a0 | |||
655ed3aa88 | |||
14ec0d27bf | |||
649f1a122a | |||
413b394d31 | |||
7ea2c3508a | |||
ec744558b2 | |||
1b9292dbcd | |||
44486c5b39 | |||
1f51367eb9 | |||
d6f42eafad | |||
dcce8945af | |||
d98c064bfe | |||
a904538555 | |||
4ef3dc5b90 | |||
bab61f9665 | |||
2765f0978f | |||
bc07539133 | |||
b4a82b7ed4 | |||
e2688dda22 | |||
d0d8585e4c | |||
729d69246a | |||
e78a6db151 | |||
f8721c3786 | |||
e519e00265 | |||
35bb20abe7 | |||
dd6a29adb2 | |||
5f9602d28b | |||
74683c706b | |||
2795e1350d | |||
aed3e5674f | |||
d941882066 | |||
3f74ff7235 | |||
9cead47212 | |||
78739d4aa9 | |||
e07e790613 | |||
ca283eed0c | |||
57edf289ea | |||
3912f5d67b | |||
c9b50e46a5 | |||
aa97fe7cbd | |||
ad07ea0290 | |||
573734008a | |||
f4a78e00a7 | |||
0d6094c8b1 | |||
b1e5b6ffe1 | |||
b6fc682117 | |||
0beb84768e | |||
b98a279367 | |||
e8a3cfaed6 | |||
0cc3e18db6 | |||
6b19c0abd1 | |||
f956d19e6e | |||
ceb0d0bf08 | |||
b3879ec223 | |||
bda99e59ec | |||
25c2a9baaa | |||
1e362c750b | |||
1a51a84313 | |||
7f72dbdaf2 | |||
e8c162ac03 | |||
1aaa19748a | |||
188ee59ba6 | |||
591612db7e | |||
2aaf4bf96b |
10
.github/ci/build-stable.sh
vendored
10
.github/ci/build-stable.sh
vendored
@ -12,9 +12,19 @@ export CARGO_TARGET_DIR=/ci/cache/target
|
||||
# used when pointing stm32-metapac to a CI-built one.
|
||||
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 save /ci/cache/filetime.json
|
||||
|
||||
sed -i 's/channel.*/channel = "stable"/g' rust-toolchain.toml
|
||||
|
||||
./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
10
.github/ci/build.sh
vendored
@ -18,7 +18,17 @@ fi
|
||||
# used when pointing stm32-metapac to a CI-built one.
|
||||
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 save /ci/cache/filetime.json
|
||||
|
||||
./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
3
.github/ci/test.sh
vendored
@ -15,6 +15,9 @@ export CARGO_NET_GIT_FETCH_WITH_CLI=true
|
||||
hashtime restore /ci/cache/filetime.json || true
|
||||
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-embedded-hal/Cargo.toml
|
||||
cargo test --manifest-path ./embassy-hal-internal/Cargo.toml
|
||||
|
6
ci.sh
6
ci.sh
@ -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-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,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,nightly,dhcpv4-hostname \
|
||||
--- 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,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,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,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 \
|
||||
@ -188,6 +190,7 @@ cargo batch \
|
||||
--- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --out-dir out/examples/wasm \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/stm32f103c8 \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/stm32f429zi \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f446re --out-dir out/tests/stm32f446re \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re --out-dir out/tests/stm32g491re \
|
||||
--- 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 \
|
||||
@ -218,6 +221,9 @@ cargo batch \
|
||||
rm out/tests/stm32wb55rg/wpan_mac
|
||||
rm out/tests/stm32wb55rg/wpan_ble
|
||||
|
||||
# not in CI yet.
|
||||
rm -rf out/tests/stm32f446re
|
||||
|
||||
# unstable, I think it's running out of RAM?
|
||||
rm out/tests/stm32f207zg/eth
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
#![no_std]
|
||||
#![allow(incomplete_features)]
|
||||
#![feature(async_fn_in_trait)]
|
||||
#![allow(stable_features, unknown_lints, async_fn_in_trait)]
|
||||
|
||||
use core::slice;
|
||||
|
||||
|
@ -12,7 +12,7 @@ firmware-logs = []
|
||||
|
||||
[dependencies]
|
||||
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-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel"}
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
use core::cmp::{max, min};
|
||||
use core::iter::zip;
|
||||
|
||||
use embassy_net_driver_channel as ch;
|
||||
use embassy_net_driver_channel::driver::{HardwareAddress, LinkState};
|
||||
use embassy_time::Timer;
|
||||
|
||||
pub use crate::bus::SpiBusCyw43;
|
||||
use crate::consts::*;
|
||||
use crate::events::{Event, EventSubscriber, Events};
|
||||
use crate::fmt::Bytes;
|
||||
@ -17,6 +17,12 @@ pub struct Error {
|
||||
pub status: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AddMulticastAddressError {
|
||||
NotMulticast,
|
||||
NoFreeSlots,
|
||||
}
|
||||
|
||||
pub struct Control<'a> {
|
||||
state_ch: ch::StateRunner<'a>,
|
||||
events: &'a Events,
|
||||
@ -317,6 +323,54 @@ impl<'a> Control<'a> {
|
||||
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) {
|
||||
let mut buf = [0; 8];
|
||||
buf[0..4].copy_from_slice(&val1.to_le_bytes());
|
||||
|
@ -1,7 +1,7 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![allow(incomplete_features)]
|
||||
#![feature(async_fn_in_trait, type_alias_impl_trait, concat_bytes)]
|
||||
#![feature(async_fn_in_trait, type_alias_impl_trait)]
|
||||
#![allow(stable_features, unknown_lints, async_fn_in_trait)]
|
||||
#![deny(unused_must_use)]
|
||||
|
||||
// This mod MUST go first, so that the others see its macros.
|
||||
@ -27,7 +27,7 @@ use ioctl::IoctlState;
|
||||
|
||||
use crate::bus::Bus;
|
||||
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::structs::BssInfo;
|
||||
|
||||
|
@ -1,54 +1,48 @@
|
||||
macro_rules! nvram {
|
||||
($($s:literal,)*) => {
|
||||
concat_bytes!($($s, b"\x00",)* b"\x00\x00")
|
||||
};
|
||||
}
|
||||
|
||||
pub static NVRAM: &'static [u8] = &*nvram!(
|
||||
b"NVRAMRev=$Rev$",
|
||||
b"manfid=0x2d0",
|
||||
b"prodid=0x0727",
|
||||
b"vendid=0x14e4",
|
||||
b"devid=0x43e2",
|
||||
b"boardtype=0x0887",
|
||||
b"boardrev=0x1100",
|
||||
b"boardnum=22",
|
||||
b"macaddr=00:A0:50:b5:59:5e",
|
||||
b"sromrev=11",
|
||||
b"boardflags=0x00404001",
|
||||
b"boardflags3=0x04000000",
|
||||
b"xtalfreq=37400",
|
||||
b"nocrc=1",
|
||||
b"ag0=255",
|
||||
b"aa2g=1",
|
||||
b"ccode=ALL",
|
||||
b"pa0itssit=0x20",
|
||||
b"extpagain2g=0",
|
||||
b"pa2ga0=-168,6649,-778",
|
||||
b"AvVmid_c0=0x0,0xc8",
|
||||
b"cckpwroffset0=5",
|
||||
b"maxp2ga0=84",
|
||||
b"txpwrbckof=6",
|
||||
b"cckbw202gpo=0",
|
||||
b"legofdmbw202gpo=0x66111111",
|
||||
b"mcsbw202gpo=0x77711111",
|
||||
b"propbw202gpo=0xdd",
|
||||
b"ofdmdigfilttype=18",
|
||||
b"ofdmdigfilttypebe=18",
|
||||
b"papdmode=1",
|
||||
b"papdvalidtest=1",
|
||||
b"pacalidx2g=45",
|
||||
b"papdepsoffset=-30",
|
||||
b"papdendidx=58",
|
||||
b"ltecxmux=0",
|
||||
b"ltecxpadnum=0x0102",
|
||||
b"ltecxfnsel=0x44",
|
||||
b"ltecxgcigpio=0x01",
|
||||
b"il0macaddr=00:90:4c:c5:12:38",
|
||||
b"wl0id=0x431b",
|
||||
b"deadman_to=0xffffffff",
|
||||
b"muxenab=0x100",
|
||||
b"spurconfig=0x3",
|
||||
b"glitch_based_crsmin=1",
|
||||
b"btc_mode=1",
|
||||
);
|
||||
pub static NVRAM: &'static [u8] = b"
|
||||
NVRAMRev=$Rev$\x00\
|
||||
manfid=0x2d0\x00\
|
||||
prodid=0x0727\x00\
|
||||
vendid=0x14e4\x00\
|
||||
devid=0x43e2\x00\
|
||||
boardtype=0x0887\x00\
|
||||
boardrev=0x1100\x00\
|
||||
boardnum=22\x00\
|
||||
macaddr=00:A0:50:b5:59:5e\x00\
|
||||
sromrev=11\x00\
|
||||
boardflags=0x00404001\x00\
|
||||
boardflags3=0x04000000\x00\
|
||||
xtalfreq=37400\x00\
|
||||
nocrc=1\x00\
|
||||
ag0=255\x00\
|
||||
aa2g=1\x00\
|
||||
ccode=ALL\x00\
|
||||
pa0itssit=0x20\x00\
|
||||
extpagain2g=0\x00\
|
||||
pa2ga0=-168,6649,-778\x00\
|
||||
AvVmid_c0=0x0,0xc8\x00\
|
||||
cckpwroffset0=5\x00\
|
||||
maxp2ga0=84\x00\
|
||||
txpwrbckof=6\x00\
|
||||
cckbw202gpo=0\x00\
|
||||
legofdmbw202gpo=0x66111111\x00\
|
||||
mcsbw202gpo=0x77711111\x00\
|
||||
propbw202gpo=0xdd\x00\
|
||||
ofdmdigfilttype=18\x00\
|
||||
ofdmdigfilttypebe=18\x00\
|
||||
papdmode=1\x00\
|
||||
papdvalidtest=1\x00\
|
||||
pacalidx2g=45\x00\
|
||||
papdepsoffset=-30\x00\
|
||||
papdendidx=58\x00\
|
||||
ltecxmux=0\x00\
|
||||
ltecxpadnum=0x0102\x00\
|
||||
ltecxfnsel=0x44\x00\
|
||||
ltecxgcigpio=0x01\x00\
|
||||
il0macaddr=00:90:4c:c5:12:38\x00\
|
||||
wl0id=0x431b\x00\
|
||||
deadman_to=0xffffffff\x00\
|
||||
muxenab=0x100\x00\
|
||||
spurconfig=0x3\x00\
|
||||
glitch_based_crsmin=1\x00\
|
||||
btc_mode=1\x00\
|
||||
\x00";
|
||||
|
@ -10,3 +10,4 @@
|
||||
* xref:examples.adoc[Examples]
|
||||
* xref:developer.adoc[Developer]
|
||||
** xref:developer_stm32.adoc[Developer: STM32]
|
||||
* xref:faq.adoc[Frequently Asked Questions]
|
||||
|
38
docs/modules/ROOT/pages/faq.adoc
Normal file
38
docs/modules/ROOT/pages/faq.adoc
Normal 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].
|
@ -28,7 +28,7 @@ digest = "0.10"
|
||||
log = { version = "0.4", 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-sync = { version = "0.3.0", path = "../../embassy-sync" }
|
||||
embassy-sync = { version = "0.4.0", path = "../../embassy-sync" }
|
||||
embedded-storage = "0.3.0"
|
||||
embedded-storage-async = { version = "0.4.0", optional = true }
|
||||
salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true }
|
||||
|
@ -1,4 +1,5 @@
|
||||
#![cfg_attr(feature = "nightly", feature(async_fn_in_trait))]
|
||||
#![cfg_attr(feature = "nightly", allow(stable_features, unknown_lints, async_fn_in_trait))]
|
||||
#![no_std]
|
||||
#![warn(missing_docs)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
@ -16,7 +16,7 @@ target = "thumbv7em-none-eabi"
|
||||
[dependencies]
|
||||
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-boot = { path = "../boot", default-features = false }
|
||||
cortex-m = { version = "0.7.6" }
|
||||
|
@ -17,7 +17,7 @@ defmt = { version = "0.3", optional = true }
|
||||
defmt-rtt = { version = "0.4", optional = true }
|
||||
log = { version = "0.4", optional = true }
|
||||
|
||||
embassy-sync = { path = "../../embassy-sync" }
|
||||
embassy-sync = { version = "0.4.0", path = "../../embassy-sync" }
|
||||
embassy-rp = { path = "../../embassy-rp", default-features = false }
|
||||
embassy-boot = { path = "../boot", default-features = false }
|
||||
embassy-time = { path = "../../embassy-time" }
|
||||
|
@ -18,7 +18,7 @@ defmt = { version = "0.3", optional = true }
|
||||
defmt-rtt = { version = "0.4", optional = true }
|
||||
log = { version = "0.4", optional = true }
|
||||
|
||||
embassy-sync = { path = "../../embassy-sync" }
|
||||
embassy-sync = { version = "0.4.0", path = "../../embassy-sync" }
|
||||
embassy-stm32 = { path = "../../embassy-stm32", default-features = false }
|
||||
embassy-boot = { path = "../boot", default-features = false }
|
||||
cortex-m = { version = "0.7.6" }
|
||||
|
@ -20,7 +20,7 @@ default = ["time"]
|
||||
|
||||
[dependencies]
|
||||
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.5", path = "../embassy-time", optional = true }
|
||||
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [
|
||||
"unproven",
|
||||
|
@ -1,5 +1,6 @@
|
||||
#![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)]
|
||||
|
||||
//! Utilities to use `embedded-hal` traits with Embassy.
|
||||
|
@ -66,19 +66,29 @@ where
|
||||
let mut bus = self.bus.lock().await;
|
||||
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
let op_res: Result<(), BUS::Error> = try {
|
||||
let op_res = 'ops: {
|
||||
for op in operations {
|
||||
match op {
|
||||
Operation::Read(buf) => bus.read(buf).await?,
|
||||
Operation::Write(buf) => bus.write(buf).await?,
|
||||
Operation::Transfer(read, write) => bus.transfer(read, write).await?,
|
||||
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await?,
|
||||
let res = match op {
|
||||
Operation::Read(buf) => bus.read(buf).await,
|
||||
Operation::Write(buf) => bus.write(buf).await,
|
||||
Operation::Transfer(read, write) => bus.transfer(read, write).await,
|
||||
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await,
|
||||
#[cfg(not(feature = "time"))]
|
||||
Operation::DelayUs(_) => return Err(SpiDeviceError::DelayUsNotSupported),
|
||||
Operation::DelayUs(us) => return Err(SpiDeviceError::DelayUsNotSupported),
|
||||
#[cfg(feature = "time")]
|
||||
Operation::DelayUs(us) => embassy_time::Timer::after_micros(*us as _).await,
|
||||
Operation::DelayUs(us) => match bus.flush().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.
|
||||
@ -131,19 +141,29 @@ where
|
||||
bus.set_config(&self.config).map_err(|_| SpiDeviceError::Config)?;
|
||||
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
let op_res: Result<(), BUS::Error> = try {
|
||||
let op_res = 'ops: {
|
||||
for op in operations {
|
||||
match op {
|
||||
Operation::Read(buf) => bus.read(buf).await?,
|
||||
Operation::Write(buf) => bus.write(buf).await?,
|
||||
Operation::Transfer(read, write) => bus.transfer(read, write).await?,
|
||||
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await?,
|
||||
let res = match op {
|
||||
Operation::Read(buf) => bus.read(buf).await,
|
||||
Operation::Write(buf) => bus.write(buf).await,
|
||||
Operation::Transfer(read, write) => bus.transfer(read, write).await,
|
||||
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await,
|
||||
#[cfg(not(feature = "time"))]
|
||||
Operation::DelayUs(_) => return Err(SpiDeviceError::DelayUsNotSupported),
|
||||
Operation::DelayUs(us) => return Err(SpiDeviceError::DelayUsNotSupported),
|
||||
#[cfg(feature = "time")]
|
||||
Operation::DelayUs(us) => embassy_time::Timer::after_micros(*us as _).await,
|
||||
Operation::DelayUs(us) => match bus.flush().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.
|
||||
|
@ -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/),
|
||||
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
|
||||
|
||||
- Replaced Pender. Implementations now must define an extern function called `__pender`.
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "embassy-executor"
|
||||
version = "0.3.0"
|
||||
version = "0.3.3"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "async/await executor designed for embedded usage"
|
||||
@ -27,6 +27,30 @@ default-target = "thumbv7em-none-eabi"
|
||||
targets = ["thumbv7em-none-eabi"]
|
||||
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]
|
||||
|
||||
# Architecture
|
||||
@ -34,7 +58,7 @@ _arch = [] # some arch was picked
|
||||
arch-std = ["_arch", "critical-section/std"]
|
||||
arch-cortex-m = ["_arch", "dep:cortex-m"]
|
||||
arch-xtensa = ["_arch"]
|
||||
arch-riscv32 = ["_arch"]
|
||||
arch-riscv32 = ["_arch", "dep:portable-atomic"]
|
||||
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)
|
||||
@ -43,30 +67,53 @@ executor-thread = []
|
||||
executor-interrupt = []
|
||||
|
||||
# Enable nightly-only features
|
||||
nightly = []
|
||||
nightly = ["embassy-macros/nightly"]
|
||||
|
||||
turbowakers = []
|
||||
|
||||
integrated-timers = ["dep:embassy-time"]
|
||||
|
||||
# Trace interrupt invocations with rtos-trace.
|
||||
rtos-trace-interrupt = ["rtos-trace", "embassy-macros/rtos-trace-interrupt"]
|
||||
# BEGIN AUTOGENERATED CONFIG FEATURES
|
||||
# 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]
|
||||
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.5", 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 }
|
||||
# END AUTOGENERATED CONFIG FEATURES
|
||||
|
@ -2,10 +2,36 @@
|
||||
|
||||
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.
|
||||
- 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`.
|
||||
- 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.
|
||||
- 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.
|
||||
|
@ -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() {
|
||||
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();
|
||||
|
||||
if target.starts_with("thumbv6m-") {
|
||||
|
82
embassy-executor/gen_config.py
Normal file
82
embassy-executor/gen_config.py
Normal 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)
|
@ -51,7 +51,6 @@ mod thread {
|
||||
use core::arch::asm;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
pub use embassy_macros::main_cortex_m as main;
|
||||
|
||||
use crate::{raw, Spawner};
|
||||
@ -115,12 +114,12 @@ mod thread {
|
||||
pub use interrupt::*;
|
||||
#[cfg(feature = "executor-interrupt")]
|
||||
mod interrupt {
|
||||
use core::cell::UnsafeCell;
|
||||
use core::cell::{Cell, UnsafeCell};
|
||||
use core::mem::MaybeUninit;
|
||||
|
||||
use atomic_polyfill::{AtomicBool, Ordering};
|
||||
use cortex_m::interrupt::InterruptNumber;
|
||||
use cortex_m::peripheral::NVIC;
|
||||
use critical_section::Mutex;
|
||||
|
||||
use crate::raw;
|
||||
|
||||
@ -146,7 +145,7 @@ mod interrupt {
|
||||
/// It is somewhat more complex to use, it's recommended to use the thread-mode
|
||||
/// [`Executor`] instead, if it works for your use case.
|
||||
pub struct InterruptExecutor {
|
||||
started: AtomicBool,
|
||||
started: Mutex<Cell<bool>>,
|
||||
executor: UnsafeCell<MaybeUninit<raw::Executor>>,
|
||||
}
|
||||
|
||||
@ -158,7 +157,7 @@ mod interrupt {
|
||||
#[inline]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
started: AtomicBool::new(false),
|
||||
started: Mutex::new(Cell::new(false)),
|
||||
executor: UnsafeCell::new(MaybeUninit::uninit()),
|
||||
}
|
||||
}
|
||||
@ -167,7 +166,8 @@ mod interrupt {
|
||||
///
|
||||
/// # 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) {
|
||||
let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
|
||||
executor.poll();
|
||||
@ -196,11 +196,7 @@ mod interrupt {
|
||||
/// do it after.
|
||||
///
|
||||
pub fn start(&'static self, irq: impl InterruptNumber) -> crate::SendSpawner {
|
||||
if self
|
||||
.started
|
||||
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_err()
|
||||
{
|
||||
if critical_section::with(|cs| self.started.borrow(cs).replace(true)) {
|
||||
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
|
||||
/// 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.
|
||||
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.");
|
||||
}
|
||||
let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
|
||||
|
@ -6,10 +6,9 @@ pub use thread::*;
|
||||
#[cfg(feature = "executor-thread")]
|
||||
mod thread {
|
||||
use core::marker::PhantomData;
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
pub use embassy_macros::main_riscv as main;
|
||||
use portable_atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::{raw, Spawner};
|
||||
|
||||
|
@ -8,7 +8,6 @@ mod thread {
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::{Condvar, Mutex};
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
pub use embassy_macros::main_std as main;
|
||||
|
||||
use crate::{raw, Spawner};
|
||||
|
@ -8,7 +8,6 @@ mod thread {
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
pub use embassy_macros::main_wasm as main;
|
||||
use js_sys::Promise;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
@ -8,6 +8,8 @@ mod thread {
|
||||
use core::marker::PhantomData;
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
pub use embassy_macros::main_riscv as main;
|
||||
|
||||
use crate::{raw, Spawner};
|
||||
|
||||
/// global atomic used to keep track of whether there is work to do since sev() is not available on Xtensa
|
||||
|
@ -1,5 +1,5 @@
|
||||
#![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)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![warn(missing_docs)]
|
||||
@ -7,7 +7,6 @@
|
||||
// This mod MUST go first, so that the others see its macros.
|
||||
pub(crate) mod fmt;
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
pub use embassy_macros::task;
|
||||
|
||||
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;
|
||||
|
||||
#[cfg(feature = "_arch")]
|
||||
#[allow(unused_imports)] // don't warn if the module is empty.
|
||||
pub use arch::*;
|
||||
|
||||
pub mod raw;
|
||||
@ -40,29 +40,101 @@ pub mod raw;
|
||||
mod spawner;
|
||||
pub use spawner::*;
|
||||
|
||||
mod config {
|
||||
#![allow(unused)]
|
||||
include!(concat!(env!("OUT_DIR"), "/config.rs"));
|
||||
}
|
||||
|
||||
/// Implementation details for embassy macros.
|
||||
/// Do not use. Used for macros and HALs only. Not covered by semver guarantees.
|
||||
#[doc(hidden)]
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
pub mod _export {
|
||||
#[cfg(feature = "rtos-trace")]
|
||||
pub use rtos_trace::trace;
|
||||
pub use static_cell::StaticCell;
|
||||
use core::alloc::Layout;
|
||||
use core::cell::{Cell, UnsafeCell};
|
||||
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
|
||||
/// the `rtos-trace-interrupt` feature.
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
#[cfg(feature = "rtos-trace-interrupt")]
|
||||
macro_rules! rtos_trace_interrupt {
|
||||
($($tt:tt)*) => { $($tt)* };
|
||||
use critical_section::{CriticalSection, Mutex};
|
||||
|
||||
use crate::raw::TaskPool;
|
||||
|
||||
struct Arena<const N: usize> {
|
||||
buf: UnsafeCell<MaybeUninit<[u8; N]>>,
|
||||
ptr: Mutex<Cell<*mut u8>>,
|
||||
}
|
||||
|
||||
/// Does not expand the given block of code when `embassy-executor` is
|
||||
/// compiled without the `rtos-trace-interrupt` feature.
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
#[cfg(not(feature = "rtos-trace-interrupt"))]
|
||||
macro_rules! rtos_trace_interrupt {
|
||||
($($tt:tt)*) => {};
|
||||
unsafe impl<const N: usize> Sync for Arena<N> {}
|
||||
unsafe impl<const N: usize> Send for Arena<N> {}
|
||||
|
||||
impl<const N: usize> Arena<N> {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
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 _) }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,15 @@
|
||||
//! 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.
|
||||
|
||||
#[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;
|
||||
|
||||
#[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")]
|
||||
mod timer_queue;
|
||||
pub(crate) mod util;
|
||||
@ -21,7 +29,6 @@ use core::pin::Pin;
|
||||
use core::ptr::NonNull;
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
use atomic_polyfill::{AtomicU32, Ordering};
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
use embassy_time::driver::{self, AlarmHandle};
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
@ -30,21 +37,14 @@ use embassy_time::Instant;
|
||||
use rtos_trace::trace;
|
||||
|
||||
use self::run_queue::{RunQueue, RunQueueItem};
|
||||
use self::state::State;
|
||||
use self::util::{SyncUnsafeCell, UninitCell};
|
||||
pub use self::waker::task_from_waker;
|
||||
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.
|
||||
pub(crate) struct TaskHeader {
|
||||
pub(crate) state: AtomicU32,
|
||||
pub(crate) state: State,
|
||||
pub(crate) run_queue_item: RunQueueItem,
|
||||
pub(crate) executor: SyncUnsafeCell<Option<&'static SyncExecutor>>,
|
||||
poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>,
|
||||
@ -116,7 +116,7 @@ impl<F: Future + 'static> TaskStorage<F> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
raw: TaskHeader {
|
||||
state: AtomicU32::new(0),
|
||||
state: State::new(),
|
||||
run_queue_item: RunQueueItem::new(),
|
||||
executor: SyncUnsafeCell::new(None),
|
||||
// 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) {
|
||||
Poll::Ready(_) => {
|
||||
this.future.drop_in_place();
|
||||
this.raw.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel);
|
||||
this.raw.state.despawn();
|
||||
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
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.
|
||||
pub fn claim(task: &'static TaskStorage<F>) -> Option<Self> {
|
||||
task.raw
|
||||
.state
|
||||
.compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire)
|
||||
.ok()
|
||||
.map(|_| Self { task })
|
||||
task.raw.state.spawn().then(|| Self { task })
|
||||
}
|
||||
|
||||
fn initialize_impl<S>(self, future: impl FnOnce() -> F) -> SpawnToken<S> {
|
||||
@ -394,8 +390,7 @@ impl SyncExecutor {
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
task.expires_at.set(Instant::MAX);
|
||||
|
||||
let state = task.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel);
|
||||
if state & STATE_SPAWNED == 0 {
|
||||
if !task.state.run_dequeue() {
|
||||
// If task is not running, ignore it. This can happen in the following scenario:
|
||||
// - Task gets dequeued, poll starts
|
||||
// - While task is being polled, it gets woken. It gets placed in the queue.
|
||||
@ -546,18 +541,7 @@ impl Executor {
|
||||
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
|
||||
pub fn wake_task(task: TaskRef) {
|
||||
let header = task.header();
|
||||
|
||||
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() {
|
||||
if header.state.run_enqueue() {
|
||||
// We have just marked the task as scheduled, so enqueue it.
|
||||
unsafe {
|
||||
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`].
|
||||
pub fn wake_task_no_pend(task: TaskRef) {
|
||||
let header = task.header();
|
||||
|
||||
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() {
|
||||
if header.state.run_enqueue() {
|
||||
// We have just marked the task as scheduled, so enqueue it.
|
||||
unsafe {
|
||||
let executor = header.executor.get().unwrap_unchecked();
|
||||
|
@ -1,7 +1,6 @@
|
||||
use core::ptr;
|
||||
use core::ptr::NonNull;
|
||||
|
||||
use atomic_polyfill::{AtomicPtr, Ordering};
|
||||
use core::sync::atomic::{AtomicPtr, Ordering};
|
||||
|
||||
use super::{TaskHeader, TaskRef};
|
||||
use crate::raw::util::SyncUnsafeCell;
|
75
embassy-executor/src/raw/run_queue_critical_section.rs
Normal file
75
embassy-executor/src/raw/run_queue_critical_section.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
73
embassy-executor/src/raw/state_atomics.rs
Normal file
73
embassy-executor/src/raw/state_atomics.rs
Normal 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);
|
||||
}
|
||||
}
|
103
embassy-executor/src/raw/state_atomics_arm.rs
Normal file
103
embassy-executor/src/raw/state_atomics_arm.rs
Normal 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);
|
||||
}
|
||||
}
|
93
embassy-executor/src/raw/state_critical_section.rs
Normal file
93
embassy-executor/src/raw/state_critical_section.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
use core::cmp::min;
|
||||
|
||||
use atomic_polyfill::Ordering;
|
||||
use embassy_time::Instant;
|
||||
|
||||
use super::{TaskRef, STATE_TIMER_QUEUED};
|
||||
use super::TaskRef;
|
||||
use crate::raw::util::SyncUnsafeCell;
|
||||
|
||||
pub(crate) struct TimerQueueItem {
|
||||
@ -32,10 +31,7 @@ impl TimerQueue {
|
||||
pub(crate) unsafe fn update(&self, p: TaskRef) {
|
||||
let task = p.header();
|
||||
if task.expires_at.get() != Instant::MAX {
|
||||
let old_state = task.state.fetch_or(STATE_TIMER_QUEUED, Ordering::AcqRel);
|
||||
let is_new = old_state & STATE_TIMER_QUEUED == 0;
|
||||
|
||||
if is_new {
|
||||
if task.state.timer_enqueue() {
|
||||
task.timer_queue_item.next.set(self.head.get());
|
||||
self.head.set(Some(p));
|
||||
}
|
||||
@ -75,7 +71,7 @@ impl TimerQueue {
|
||||
} else {
|
||||
// Remove it
|
||||
prev.set(task.timer_queue_item.next.get());
|
||||
task.state.fetch_and(!STATE_TIMER_QUEUED, Ordering::AcqRel);
|
||||
task.state.timer_dequeue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use core::task::{RawWaker, RawWakerVTable, Waker};
|
||||
|
||||
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 {
|
||||
RawWaker::new(p, &VTABLE)
|
||||
|
137
embassy-executor/tests/test.rs
Normal file
137
embassy-executor/tests/test.rs
Normal 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
|
||||
]
|
||||
)
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "embassy-futures"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
description = "no-std, no-alloc utilities for working with futures"
|
||||
repository = "https://github.com/embassy-rs/embassy"
|
||||
|
@ -21,7 +21,7 @@ defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.14", 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 }
|
||||
embedded-hal-async = { version = "=1.0.0-rc.1" }
|
||||
embedded-hal = { version = "0.2", features = ["unproven"] }
|
||||
|
@ -1,5 +1,6 @@
|
||||
#![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.
|
||||
|
||||
pub(crate) mod fmt;
|
||||
|
@ -21,5 +21,4 @@ proc-macro2 = "1.0.29"
|
||||
proc-macro = true
|
||||
|
||||
[features]
|
||||
# Enabling this cause interrupt::take! to require embassy-executor
|
||||
rtos-trace-interrupt = []
|
||||
nightly = []
|
@ -53,8 +53,7 @@ pub fn wasm() -> TokenStream {
|
||||
quote! {
|
||||
#[wasm_bindgen::prelude::wasm_bindgen(start)]
|
||||
pub fn main() -> Result<(), wasm_bindgen::JsValue> {
|
||||
static EXECUTOR: ::embassy_executor::_export::StaticCell<::embassy_executor::Executor> = ::embassy_executor::_export::StaticCell::new();
|
||||
let executor = EXECUTOR.init(::embassy_executor::Executor::new());
|
||||
let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new()));
|
||||
|
||||
executor.start(|spawner| {
|
||||
spawner.spawn(__embassy_main(spawner)).unwrap();
|
||||
|
@ -79,6 +79,7 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
|
||||
task_inner.vis = syn::Visibility::Inherited;
|
||||
task_inner.sig.ident = task_inner_ident.clone();
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
let mut task_outer: ItemFn = parse_quote! {
|
||||
#visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> {
|
||||
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,)*)) }
|
||||
}
|
||||
};
|
||||
#[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());
|
||||
|
||||
|
@ -10,7 +10,7 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
heapless = "0.7.16"
|
||||
heapless = "0.8"
|
||||
defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4", default-features = false, optional = true }
|
||||
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" }
|
||||
@ -22,9 +22,7 @@ embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
|
||||
bitfield = "0.14.0"
|
||||
|
||||
[dev-dependencies]
|
||||
# reenable when https://github.com/dbrgn/embedded-hal-mock/pull/86 is merged.
|
||||
#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"] }
|
||||
embedded-hal-mock = { version = "=0.10.0-rc.1", features = ["embedded-hal-async", "eh1"] }
|
||||
crc = "3.0.1"
|
||||
env_logger = "0.10"
|
||||
critical-section = { version = "1.1.2", features = ["std"] }
|
||||
|
@ -1,5 +1,6 @@
|
||||
#![deny(clippy::pedantic)]
|
||||
#![feature(async_fn_in_trait)]
|
||||
#![allow(stable_features, unknown_lints, async_fn_in_trait)]
|
||||
#![cfg_attr(not(any(test, feature = "std")), no_std)]
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
@ -24,6 +24,6 @@ features = ["defmt"]
|
||||
defmt = { version = "0.3", 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-net-driver = { version = "0.2.0", path = "../embassy-net-driver" }
|
||||
|
@ -8,16 +8,16 @@ defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.14", optional = true }
|
||||
|
||||
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-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel"}
|
||||
|
||||
embedded-hal = { 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"] }
|
||||
heapless = "0.7.16"
|
||||
heapless = "0.8"
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-esp-hosted-v$VERSION/embassy-net-esp-hosted/src/"
|
||||
|
@ -97,8 +97,8 @@ impl<'a> Control<'a> {
|
||||
|
||||
pub async fn connect(&mut self, ssid: &str, password: &str) -> Result<(), Error> {
|
||||
let req = proto::CtrlMsgReqConnectAp {
|
||||
ssid: String::from(ssid),
|
||||
pwd: String::from(password),
|
||||
ssid: unwrap!(String::try_from(ssid)),
|
||||
pwd: unwrap!(String::try_from(password)),
|
||||
bssid: String::new(),
|
||||
listen_interval: 3,
|
||||
is_wpa3_supported: false,
|
||||
|
@ -19,7 +19,7 @@ embedded-io-async = { version = "0.6.0" }
|
||||
embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel" }
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
|
||||
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]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-ppp-v$VERSION/embassy-net-ppp/src/"
|
||||
|
@ -1,5 +1,6 @@
|
||||
#![no_std]
|
||||
#![feature(async_fn_in_trait)]
|
||||
#![allow(stable_features, unknown_lints, async_fn_in_trait)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
pub mod chip;
|
||||
|
@ -5,6 +5,17 @@ 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`
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "embassy-net"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Async TCP/IP network stack for embedded systems"
|
||||
@ -25,7 +25,7 @@ features = ["nightly", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "me
|
||||
default = []
|
||||
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"]
|
||||
|
||||
@ -46,21 +46,21 @@ igmp = ["smoltcp/proto-igmp"]
|
||||
defmt = { version = "0.3", 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",
|
||||
"async",
|
||||
] }
|
||||
|
||||
embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" }
|
||||
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 }
|
||||
|
||||
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"
|
||||
generic-array = { version = "0.14.4", default-features = false }
|
||||
stable_deref_trait = { version = "1.2.0", default-features = false }
|
||||
futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] }
|
||||
atomic-pool = "1.0"
|
||||
embedded-nal-async = { version = "0.6.0", optional = true }
|
||||
embedded-nal-async = { version = "0.7", optional = true }
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
|
@ -63,7 +63,11 @@ where
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
@ -101,7 +105,8 @@ where
|
||||
async fn get_host_by_address(
|
||||
&self,
|
||||
_addr: embedded_nal_async::IpAddr,
|
||||
) -> Result<heapless::String<256>, Self::Error> {
|
||||
_result: &mut [u8],
|
||||
) -> Result<usize, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
#![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)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
@ -493,7 +494,11 @@ impl<D: Driver> Stack<D> {
|
||||
|
||||
/// Make a query for a given name and return the corresponding IP addresses.
|
||||
#[cfg(feature = "dns")]
|
||||
pub async fn dns_query(&self, name: &str, qtype: dns::DnsQueryType) -> Result<Vec<IpAddress, 1>, dns::Error> {
|
||||
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
|
||||
match qtype {
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
@ -611,9 +616,11 @@ impl<D: Driver> Stack<D> {
|
||||
let addr = addr.into();
|
||||
|
||||
self.with_mut(|s, i| {
|
||||
let (_hardware_addr, medium) = to_smoltcp_hardware_address(i.device.hardware_address());
|
||||
let mut smoldev = DriverAdapter {
|
||||
cx: Some(cx),
|
||||
inner: &mut i.device,
|
||||
medium,
|
||||
};
|
||||
|
||||
match s
|
||||
@ -648,9 +655,11 @@ impl<D: Driver> Stack<D> {
|
||||
let addr = addr.into();
|
||||
|
||||
self.with_mut(|s, i| {
|
||||
let (_hardware_addr, medium) = to_smoltcp_hardware_address(i.device.hardware_address());
|
||||
let mut smoldev = DriverAdapter {
|
||||
cx: Some(cx),
|
||||
inner: &mut i.device,
|
||||
medium,
|
||||
};
|
||||
|
||||
match s
|
||||
|
@ -390,6 +390,13 @@ impl<'d> TcpIo<'d> {
|
||||
// CAUTION: smoltcp semantics around EOF are different to what you'd expect
|
||||
// from posix-like IO, so we have to tweak things here.
|
||||
self.with_mut(|s, _| match s.recv_slice(buf) {
|
||||
// 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
|
||||
Ok(0) => {
|
||||
s.register_recv_waker(cx.waker());
|
||||
@ -611,10 +618,7 @@ pub mod client {
|
||||
async fn connect<'a>(
|
||||
&'a self,
|
||||
remote: embedded_nal_async::SocketAddr,
|
||||
) -> Result<Self::Connection<'a>, Self::Error>
|
||||
where
|
||||
Self: 'a,
|
||||
{
|
||||
) -> Result<Self::Connection<'a>, Self::Error> {
|
||||
let addr: crate::IpAddress = match remote.ip() {
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
IpAddr::V4(addr) => crate::IpAddress::Ipv4(crate::Ipv4Address::from_bytes(&addr.octets())),
|
||||
|
@ -95,7 +95,7 @@ _nrf52832_anomaly_109 = []
|
||||
|
||||
[dependencies]
|
||||
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-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" }
|
||||
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 }
|
||||
cortex-m-rt = ">=0.6.15,<0.8"
|
||||
cortex-m = "0.7.6"
|
||||
futures = { version = "0.3.17", default-features = false }
|
||||
critical-section = "1.1"
|
||||
rand_core = "0.6.3"
|
||||
fixed = "1.10.0"
|
||||
|
@ -12,7 +12,7 @@ use core::cmp::min;
|
||||
use core::future::poll_fn;
|
||||
use core::marker::PhantomData;
|
||||
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 embassy_hal_internal::atomic_ring_buffer::RingBuffer;
|
||||
@ -41,7 +41,9 @@ mod sealed {
|
||||
|
||||
pub rx_waker: AtomicWaker,
|
||||
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,
|
||||
}
|
||||
}
|
||||
@ -65,7 +67,9 @@ impl State {
|
||||
|
||||
rx_waker: AtomicWaker::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),
|
||||
}
|
||||
}
|
||||
@ -104,28 +108,20 @@ impl<U: UarteInstance> interrupt::typelevel::Handler<U::Interrupt> for Interrupt
|
||||
s.rx_waker.wake();
|
||||
}
|
||||
|
||||
// If not RXing, start.
|
||||
if s.rx_bufs.load(Ordering::Relaxed) == 0 {
|
||||
let (ptr, len) = rx.push_buf();
|
||||
if len >= half_len {
|
||||
//trace!(" irq_rx: starting {:?}", half_len);
|
||||
s.rx_bufs.store(1, Ordering::Relaxed);
|
||||
if r.events_endrx.read().bits() != 0 {
|
||||
//trace!(" irq_rx: endrx");
|
||||
r.events_endrx.reset();
|
||||
|
||||
// Set up the DMA read
|
||||
r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) });
|
||||
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());
|
||||
}
|
||||
let val = s.rx_ended_count.load(Ordering::Relaxed);
|
||||
s.rx_ended_count.store(val.wrapping_add(1), Ordering::Relaxed);
|
||||
}
|
||||
|
||||
if r.events_rxstarted.read().bits() != 0 {
|
||||
if r.events_rxstarted.read().bits() != 0 || !s.rx_started.load(Ordering::Relaxed) {
|
||||
//trace!(" irq_rx: rxstarted");
|
||||
let (ptr, len) = rx.push_buf();
|
||||
if len >= half_len {
|
||||
r.events_rxstarted.reset();
|
||||
|
||||
//trace!(" irq_rx: starting second {:?}", half_len);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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) });
|
||||
|
||||
// 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);
|
||||
|
||||
r.events_rxstarted.reset();
|
||||
s.rx_started_count.store(rx_started.wrapping_add(1), Ordering::Relaxed);
|
||||
s.rx_started.store(true, Ordering::Relaxed);
|
||||
} else {
|
||||
//trace!(" irq_rx: rxstarted no buf");
|
||||
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 hwfc = cts.is_some();
|
||||
|
||||
rxd.conf().write(|w| w.input().connect().drive().h0h1());
|
||||
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
|
||||
let s = U::buffered_state();
|
||||
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();
|
||||
unsafe { s.tx_buf.init(tx_buffer.as_mut_ptr(), len) };
|
||||
let len = rx_buffer.len();
|
||||
@ -311,7 +349,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> {
|
||||
|
||||
// Configure
|
||||
r.config.write(|w| {
|
||||
w.hwfc().bit(false);
|
||||
w.hwfc().bit(hwfc);
|
||||
w.parity().variant(config.parity);
|
||||
w
|
||||
});
|
||||
@ -333,6 +371,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> {
|
||||
w.endtx().set();
|
||||
w.rxstarted().set();
|
||||
w.error().set();
|
||||
w.endrx().set();
|
||||
w
|
||||
});
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
#![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")]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#![macro_use]
|
||||
|
||||
use core::future::poll_fn;
|
||||
use core::marker::PhantomData;
|
||||
use core::sync::atomic::{compiler_fence, Ordering};
|
||||
use core::task::Poll;
|
||||
@ -9,7 +10,6 @@ use core::task::Poll;
|
||||
use embassy_hal_internal::drop::OnDrop;
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
use fixed::types::I7F1;
|
||||
use futures::future::poll_fn;
|
||||
|
||||
use crate::chip::EASY_DMA_SIZE;
|
||||
use crate::gpio::sealed::Pin;
|
||||
|
@ -59,7 +59,7 @@ nightly = ["embedded-hal-1", "embedded-hal-async", "embedded-storage-async", "em
|
||||
unstable-traits = ["embedded-hal-1", "embedded-hal-nb"]
|
||||
|
||||
[dependencies]
|
||||
embassy-sync = { version = "0.3.0", path = "../embassy-sync" }
|
||||
embassy-sync = { version = "0.4.0", path = "../embassy-sync" }
|
||||
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-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] }
|
||||
@ -94,5 +94,5 @@ pio = {version= "0.2.1" }
|
||||
rp2040-boot2 = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
embassy-executor = { version = "0.3.0", path = "../embassy-executor", features = ["nightly", "arch-std", "executor-thread"] }
|
||||
static_cell = "1.1"
|
||||
embassy-executor = { version = "0.3.3", path = "../embassy-executor", features = ["nightly", "arch-std", "executor-thread"] }
|
||||
static_cell = { version = "2" }
|
||||
|
@ -1,5 +1,6 @@
|
||||
#![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.
|
||||
pub(crate) mod fmt;
|
||||
|
@ -12,7 +12,7 @@ features = ["stm32wb55rg"]
|
||||
|
||||
[dependencies]
|
||||
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.5", path = "../embassy-time", optional = true }
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
|
||||
embassy-hal-internal = { version = "0.1.0", path = "../embassy-hal-internal" }
|
||||
@ -21,7 +21,7 @@ embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver", option
|
||||
|
||||
defmt = { version = "0.3", optional = true }
|
||||
cortex-m = "0.7.6"
|
||||
heapless = "0.7.16"
|
||||
heapless = "0.8"
|
||||
aligned = "0.4.1"
|
||||
|
||||
bit_field = "0.10.2"
|
||||
|
@ -1,6 +1,9 @@
|
||||
#![no_std]
|
||||
#![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.
|
||||
mod fmt;
|
||||
|
@ -1,4 +1,3 @@
|
||||
#![allow(incomplete_features)]
|
||||
#![deny(unused_must_use)]
|
||||
|
||||
use core::task::Context;
|
||||
|
@ -18,7 +18,7 @@ flavors = [
|
||||
{ regex_feature = "stm32f7.*", target = "thumbv7em-none-eabi" },
|
||||
{ regex_feature = "stm32c0.*", 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 = "stm32h7.*", target = "thumbv7em-none-eabi" },
|
||||
{ regex_feature = "stm32l0.*", target = "thumbv6m-none-eabi", features = ["low-power"] },
|
||||
@ -32,14 +32,14 @@ flavors = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
embassy-sync = { version = "0.3.0", path = "../embassy-sync" }
|
||||
embassy-sync = { version = "0.4.0", path = "../embassy-sync" }
|
||||
embassy-time = { version = "0.1.5", path = "../embassy-time", optional = true }
|
||||
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-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" }
|
||||
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-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-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"
|
||||
embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "a4f293d3a6f72158385f79c98634cb8a14d0d2fc", optional = true }
|
||||
critical-section = "1.1"
|
||||
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-bcc9b6bf9fa195e91625849efc4ba473d9ace4e9" }
|
||||
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-7117ad49c06fa00c388130a34977e029910083bd" }
|
||||
vcell = "0.1.3"
|
||||
bxcan = "0.7.0"
|
||||
nb = "1.0.0"
|
||||
@ -76,7 +76,7 @@ critical-section = { version = "1.1", features = ["std"] }
|
||||
[build-dependencies]
|
||||
proc-macro2 = "1.0.36"
|
||||
quote = "1.0.15"
|
||||
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-bcc9b6bf9fa195e91625849efc4ba473d9ace4e9", 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]
|
||||
@ -90,6 +90,7 @@ defmt = ["dep:defmt", "bxcan/unstable-defmt", "embassy-sync/defmt", "embassy-emb
|
||||
|
||||
exti = []
|
||||
low-power = [ "dep:embassy-executor", "embassy-executor/arch-cortex-m" ]
|
||||
low-power-debug-with-sleep = []
|
||||
embassy-executor = []
|
||||
|
||||
## Automatically generate `memory.x` file using [`stm32-metapac`](https://docs.rs/stm32-metapac/)
|
||||
|
@ -6,7 +6,7 @@ use std::{env, fs};
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
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() {
|
||||
let target = env::var("TARGET").unwrap();
|
||||
@ -61,10 +61,10 @@ fn main() {
|
||||
let mut singletons: Vec<String> = Vec::new();
|
||||
for p in METADATA.peripherals {
|
||||
if let Some(r) = &p.registers {
|
||||
println!("cargo:rustc-cfg=peri_{}", p.name.to_ascii_lowercase());
|
||||
match r.kind {
|
||||
// Generate singletons per pin, not per port
|
||||
"gpio" => {
|
||||
println!("{}", p.name);
|
||||
let port_letter = p.name.strip_prefix("GPIO").unwrap();
|
||||
for pin_num in 0..16 {
|
||||
singletons.push(format!("P{}{}", port_letter, pin_num));
|
||||
@ -556,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! {
|
||||
impl crate::rcc::sealed::RccPeripheral for peripherals::#pname {
|
||||
fn frequency() -> crate::time::Hertz {
|
||||
@ -563,8 +588,7 @@ fn main() {
|
||||
}
|
||||
fn enable_and_reset_with_cs(_cs: critical_section::CriticalSection) {
|
||||
#before_enable
|
||||
#[cfg(feature = "low-power")]
|
||||
crate::rcc::clock_refcount_add(_cs);
|
||||
#incr_stop_refcount
|
||||
crate::pac::RCC.#en_reg().modify(|w| w.#set_en_field(true));
|
||||
#after_enable
|
||||
#rst
|
||||
@ -572,8 +596,7 @@ fn main() {
|
||||
fn disable_with_cs(_cs: critical_section::CriticalSection) {
|
||||
#before_disable
|
||||
crate::pac::RCC.#en_reg().modify(|w| w.#set_en_field(false));
|
||||
#[cfg(feature = "low-power")]
|
||||
crate::rcc::clock_refcount_sub(_cs);
|
||||
#decr_stop_refcount
|
||||
}
|
||||
}
|
||||
|
||||
@ -804,7 +827,7 @@ fn main() {
|
||||
(("fmc", "NCE"), quote!(crate::fmc::NCEPin)),
|
||||
(("fmc", "NOE"), quote!(crate::fmc::NOEPin)),
|
||||
(("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", "BA1"), quote!(crate::fmc::BA1Pin)),
|
||||
(("timer", "CH1"), quote!(crate::timer::Channel1Pin)),
|
||||
@ -920,17 +943,23 @@ fn main() {
|
||||
}
|
||||
|
||||
if regs.kind == "opamp" {
|
||||
if !pin.signal.starts_with("VP") {
|
||||
continue;
|
||||
if pin.signal.starts_with("VP") {
|
||||
// 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
|
||||
@ -967,8 +996,8 @@ fn main() {
|
||||
// SDMMCv1 uses the same channel for both directions, so just implement for RX
|
||||
(("sdmmc", "RX"), quote!(crate::sdmmc::SdmmcDma)),
|
||||
(("quadspi", "QUADSPI"), quote!(crate::qspi::QuadDma)),
|
||||
(("dac", "CH1"), quote!(crate::dac::DmaCh1)),
|
||||
(("dac", "CH2"), quote!(crate::dac::DmaCh2)),
|
||||
(("dac", "CH1"), quote!(crate::dac::DacDma1)),
|
||||
(("dac", "CH2"), quote!(crate::dac::DacDma2)),
|
||||
]
|
||||
.into();
|
||||
|
||||
@ -1108,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
|
||||
|
||||
@ -1266,6 +1312,9 @@ fn main() {
|
||||
|
||||
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_interrupt", &interrupts_table);
|
||||
make_table(&mut m, "foreach_peripheral", &peripherals_table);
|
||||
@ -1302,15 +1351,6 @@ fn main() {
|
||||
|
||||
if let Some(core) = core_name {
|
||||
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]);
|
||||
}
|
||||
|
||||
// =======
|
||||
@ -1325,16 +1365,25 @@ fn main() {
|
||||
if &chip_name[..8] == "stm32wba" {
|
||||
println!("cargo:rustc-cfg={}", &chip_name[..8]); // stm32wba
|
||||
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 {
|
||||
println!("cargo:rustc-cfg={}", &chip_name[..7]); // stm32f4
|
||||
println!("cargo:rustc-cfg={}", &chip_name[..9]); // stm32f429
|
||||
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=package_{}", &chip_name[9..10]);
|
||||
println!("cargo:rustc-cfg=flashsize_{}", &chip_name[10..11]);
|
||||
}
|
||||
|
||||
// Handle time-driver-XXXX features.
|
||||
if env::var("CARGO_FEATURE_TIME_DRIVER_ANY").is_ok() {}
|
||||
println!("cargo:rustc-cfg={}", &chip_name[..chip_name.len() - 2]);
|
||||
// Mark the L4+ chips as they have many differences to regular L4.
|
||||
if &chip_name[..7] == "stm32l4" {
|
||||
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");
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use core::cell::{RefCell, RefMut};
|
||||
use core::future::poll_fn;
|
||||
use core::marker::PhantomData;
|
||||
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 can: RefCell<bxcan::Can<BxcanInstance<'d, T>>>,
|
||||
pub can: bxcan::Can<BxcanInstance<'d, T>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -175,17 +174,12 @@ impl<'d, T: Instance> Can<'d, T> {
|
||||
tx.set_as_af(tx.af_num(), AFType::OutputPushPull);
|
||||
|
||||
let can = bxcan::Can::builder(BxcanInstance(peri)).leave_disabled();
|
||||
let can_ref_cell = RefCell::new(can);
|
||||
Self { can: can_ref_cell }
|
||||
Self { can }
|
||||
}
|
||||
|
||||
pub fn set_bitrate(&mut self, bitrate: u32) {
|
||||
let bit_timing = Self::calc_bxcan_timings(T::frequency(), bitrate).unwrap();
|
||||
self.can
|
||||
.borrow_mut()
|
||||
.modify_config()
|
||||
.set_bit_timing(bit_timing)
|
||||
.leave_disabled();
|
||||
self.can.modify_config().set_bit_timing(bit_timing).leave_disabled();
|
||||
}
|
||||
|
||||
/// 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).
|
||||
/// Contrary to enable method from bxcan library, this will not freeze the executor while waiting.
|
||||
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.
|
||||
// Yield to allow other tasks to execute while can bus is initializing.
|
||||
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
|
||||
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.
|
||||
///
|
||||
/// Returns [Err(TryWriteError::Full)] if all transmit mailboxes are full.
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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.
|
||||
///
|
||||
/// Returns [Err(TryReadError::Empty)] if there are no frames in the rx queue.
|
||||
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.
|
||||
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) {
|
||||
@ -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))
|
||||
}
|
||||
|
||||
pub fn split<'c>(&'c self) -> (CanTx<'c, 'd, T>, CanRx<'c, 'd, T>) {
|
||||
(CanTx { can: &self.can }, CanRx { can: &self.can })
|
||||
pub fn split<'c>(&'c mut self) -> (CanTx<'c, 'd, T>, CanRx<'c, 'd, T>) {
|
||||
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>>> {
|
||||
self.can.borrow_mut()
|
||||
pub fn as_mut(&mut self) -> &mut bxcan::Can<BxcanInstance<'d, T>> {
|
||||
&mut self.can
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
pub async fn write(&mut self, frame: &Frame) -> bxcan::TransmitStatus {
|
||||
poll_fn(|cx| {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -415,11 +410,10 @@ impl<'c, 'd, T: Instance> CanTx<'c, 'd, T> {
|
||||
///
|
||||
/// Returns [Err(TryWriteError::Full)] if all transmit mailboxes are full.
|
||||
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
|
||||
pub async fn flush(&self, mb: bxcan::Mailbox) {
|
||||
async fn flush_inner(mb: bxcan::Mailbox) {
|
||||
poll_fn(|cx| {
|
||||
T::state().tx_waker.register(cx.waker());
|
||||
if T::regs().tsr().read().tme(mb.index()) {
|
||||
@ -431,8 +425,12 @@ impl<'c, 'd, T: Instance> CanTx<'c, 'd, T> {
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Waits until any of the transmit mailboxes become empty
|
||||
pub async fn flush_any(&self) {
|
||||
/// Waits for a specific transmit mailbox to become empty
|
||||
pub async fn flush(&self, mb: bxcan::Mailbox) {
|
||||
Self::flush_inner(mb).await
|
||||
}
|
||||
|
||||
async fn flush_any_inner() {
|
||||
poll_fn(|cx| {
|
||||
T::state().tx_waker.register(cx.waker());
|
||||
|
||||
@ -449,8 +447,12 @@ impl<'c, 'd, T: Instance> CanTx<'c, 'd, T> {
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Waits until all of the transmit mailboxes become empty
|
||||
pub async fn flush_all(&self) {
|
||||
/// Waits until any of the transmit mailboxes become empty
|
||||
pub async fn flush_any(&self) {
|
||||
Self::flush_any_inner().await
|
||||
}
|
||||
|
||||
async fn flush_all_inner() {
|
||||
poll_fn(|cx| {
|
||||
T::state().tx_waker.register(cx.waker());
|
||||
|
||||
@ -466,11 +468,17 @@ impl<'c, 'd, T: Instance> CanTx<'c, 'd, T> {
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Waits until all of the transmit mailboxes become empty
|
||||
pub async fn flush_all(&self) {
|
||||
Self::flush_all_inner().await
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
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> {
|
||||
@ -538,7 +546,7 @@ impl<'d, T: Instance> Drop 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 {
|
||||
&self.can
|
||||
|
@ -1,136 +1,66 @@
|
||||
//! Provide access to the STM32 digital-to-analog converter (DAC).
|
||||
#![macro_use]
|
||||
|
||||
//! Provide access to the STM32 digital-to-analog converter (DAC).
|
||||
use core::marker::PhantomData;
|
||||
|
||||
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::rcc::RccPeripheral;
|
||||
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)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
/// Custom Errors
|
||||
pub enum Error {
|
||||
UnconfiguredChannel,
|
||||
InvalidValue,
|
||||
pub enum Mode {
|
||||
/// Normal mode, channel is connected to external pin with buffer enabled.
|
||||
NormalExternalBuffered,
|
||||
/// 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_attr(feature = "defmt", derive(defmt::Format))]
|
||||
/// DAC Channels
|
||||
pub enum Channel {
|
||||
Ch1,
|
||||
Ch2,
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
const fn index(&self) -> usize {
|
||||
#[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))]
|
||||
impl Mode {
|
||||
fn mode(&self) -> dac::vals::Mode {
|
||||
match self {
|
||||
Channel::Ch1 => 0,
|
||||
Channel::Ch2 => 1,
|
||||
Mode::NormalExternalBuffered => dac::vals::Mode::NORMAL_EXT_BUFEN,
|
||||
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)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
/// Trigger sources for CH1
|
||||
pub enum Ch1Trigger {
|
||||
#[cfg(dac_v3)]
|
||||
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
|
||||
/// Single 8 or 12 bit value that can be output by the DAC.
|
||||
///
|
||||
/// 12-bit values outside the permitted range are silently truncated.
|
||||
pub enum Value {
|
||||
// 8 bit value
|
||||
Bit8(u8),
|
||||
@ -142,7 +72,21 @@ pub enum Value {
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
#[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> {
|
||||
// 8 bit values
|
||||
Bit8(&'a [u8]),
|
||||
@ -151,398 +95,395 @@ pub enum ValueArray<'a> {
|
||||
// 12 bit values stored in a u16, right-aligned
|
||||
Bit12Right(&'a [u16]),
|
||||
}
|
||||
/// Provide common functions for DAC channels
|
||||
pub trait DacChannel<T: Instance, Tx> {
|
||||
const CHANNEL: Channel;
|
||||
|
||||
/// Enable trigger of the given 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
|
||||
/// Driver for a single DAC channel.
|
||||
///
|
||||
/// Note: This consumes the DAC `Instance` only once, allowing to get both channels simultaneously.
|
||||
///
|
||||
/// # Example for obtaining both DAC channels
|
||||
///
|
||||
/// ```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
|
||||
/// If you want to use both channels, either together or independently,
|
||||
/// create a [`Dac`] first and use it to access each channel.
|
||||
pub struct DacChannel<'d, T: Instance, const N: u8, DMA = NoDma> {
|
||||
phantom: PhantomData<&'d mut T>,
|
||||
#[allow(unused)] // For chips whose DMA is not (yet) supported
|
||||
dma: PeripheralRef<'d, Tx>,
|
||||
#[allow(unused)]
|
||||
dma: PeripheralRef<'d, DMA>,
|
||||
}
|
||||
|
||||
impl<'d, T: Instance, Tx> DacCh1<'d, T, Tx> {
|
||||
/// Obtain DAC CH1
|
||||
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();
|
||||
pub type DacCh1<'d, T, DMA = NoDma> = DacChannel<'d, T, 1, DMA>;
|
||||
pub type DacCh2<'d, T, DMA = NoDma> = DacChannel<'d, T, 2, DMA>;
|
||||
|
||||
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
|
||||
// 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
|
||||
/// Create a new `DacChannel` instance, consuming the underlying DAC peripheral.
|
||||
///
|
||||
/// **Important**: This disables the channel!
|
||||
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.
|
||||
/// If you're not using DMA, pass [`dma::NoDma`] for the `dma` argument.
|
||||
///
|
||||
/// To prevent delays/glitches when outputting a periodic waveform, the `circular` flag can be set.
|
||||
/// This will configure a circular DMA transfer that periodically outputs the `data`.
|
||||
/// Note that for performance reasons in circular mode the transfer complete interrupt is disabled.
|
||||
/// The channel is enabled on creation and begins to drive the output pin.
|
||||
/// Note that some methods, such as `set_trigger()` and `set_mode()`, will
|
||||
/// disable the channel; you must re-enable it with `enable()`.
|
||||
///
|
||||
/// **Important:** Channel 1 has to be configured for the DAC instance!
|
||||
pub async fn write(&mut self, data: ValueArray<'_>, circular: bool) -> Result<(), Error>
|
||||
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
|
||||
/// By default, triggering is disabled, but it can be enabled using
|
||||
/// [`DacChannel::set_trigger()`].
|
||||
pub fn new(
|
||||
_peri: impl Peripheral<P = T> + 'd,
|
||||
dma: impl Peripheral<P = Tx> + 'd,
|
||||
pin: impl Peripheral<P = impl DacPin<T, 2>> + crate::gpio::sealed::Pin + 'd,
|
||||
dma: impl Peripheral<P = DMA> + 'd,
|
||||
pin: impl Peripheral<P = impl DacPin<T, N> + crate::gpio::sealed::Pin> + 'd,
|
||||
) -> Self {
|
||||
into_ref!(dma, pin);
|
||||
pin.set_as_analog();
|
||||
into_ref!(_peri, dma);
|
||||
T::enable_and_reset();
|
||||
|
||||
let mut dac = Self {
|
||||
phantom: PhantomData,
|
||||
dma,
|
||||
};
|
||||
|
||||
// Configure each activated channel. All results can be `unwrap`ed since they
|
||||
// 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();
|
||||
|
||||
#[cfg(any(dac_v5, dac_v6, dac_v7))]
|
||||
dac.set_hfsel();
|
||||
dac.enable();
|
||||
dac
|
||||
}
|
||||
|
||||
/// Select a new trigger for this channel
|
||||
pub fn select_trigger(&mut self, trigger: Ch2Trigger) -> Result<(), Error> {
|
||||
unwrap!(self.disable_channel());
|
||||
T::regs().cr().modify(|reg| {
|
||||
reg.set_tsel2(trigger.tsel());
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write `data` to the DAC CH2 via DMA.
|
||||
/// Create a new `DacChannel` instance where the external output pin is not used,
|
||||
/// so the DAC can only be used to generate internal signals.
|
||||
/// The GPIO pin is therefore available to be used for other functions.
|
||||
///
|
||||
/// To prevent delays/glitches when outputting a periodic waveform, the `circular` flag can be set.
|
||||
/// This will configure a circular DMA transfer that periodically outputs the `data`.
|
||||
/// Note that for performance reasons in circular mode the transfer complete interrupt is disabled.
|
||||
/// The channel is 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 it with `enable()`.
|
||||
///
|
||||
/// **Important:** Channel 2 has to be configured for the DAC instance!
|
||||
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.
|
||||
/// If you're not using DMA, pass [`dma::NoDma`] for the `dma` argument.
|
||||
///
|
||||
/// This is used to obtain two independent channels via `split()` for use e.g. with DMA.
|
||||
pub fn new(
|
||||
peri: impl Peripheral<P = T> + 'd,
|
||||
dma_ch1: impl Peripheral<P = TxCh1> + 'd,
|
||||
dma_ch2: impl Peripheral<P = TxCh2> + '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 {
|
||||
pin_ch1.set_as_analog();
|
||||
pin_ch2.set_as_analog();
|
||||
into_ref!(peri, dma_ch1, dma_ch2);
|
||||
/// By default, triggering is disabled, but it can be enabled using
|
||||
/// [`DacChannel::set_trigger()`].
|
||||
#[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: impl Peripheral<P = DMA> + 'd) -> Self {
|
||||
into_ref!(dma);
|
||||
T::enable_and_reset();
|
||||
|
||||
let mut dac_ch1 = DacCh1 {
|
||||
_peri: peri,
|
||||
dma: dma_ch1,
|
||||
};
|
||||
|
||||
let mut dac_ch2 = DacCh2 {
|
||||
let mut dac = Self {
|
||||
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
|
||||
// will only error if the channel is not configured (i.e. ch1, ch2 are false)
|
||||
#[cfg(any(dac_v2, dac_v3))]
|
||||
dac_ch1.set_channel_mode(0).unwrap();
|
||||
dac_ch1.enable_channel().unwrap();
|
||||
dac_ch1.set_trigger_enable(true).unwrap();
|
||||
/// Enable or disable this channel.
|
||||
pub fn set_enable(&mut self, on: bool) {
|
||||
critical_section::with(|_| {
|
||||
T::regs().cr().modify(|reg| {
|
||||
reg.set_en(Self::IDX, on);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(any(dac_v2, dac_v3))]
|
||||
dac_ch2.set_channel_mode(0).unwrap();
|
||||
dac_ch2.enable_channel().unwrap();
|
||||
dac_ch2.set_trigger_enable(true).unwrap();
|
||||
/// Enable this channel.
|
||||
pub fn enable(&mut self) {
|
||||
self.set_enable(true)
|
||||
}
|
||||
|
||||
Self {
|
||||
ch1: dac_ch1,
|
||||
ch2: dac_ch2,
|
||||
/// Disable this channel.
|
||||
pub fn disable(&mut self) {
|
||||
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.
|
||||
pub fn split(self) -> (DacCh1<'d, T, TxCh1>, DacCh2<'d, T, TxCh2>) {
|
||||
/// Read the current output value of the DAC.
|
||||
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)
|
||||
}
|
||||
|
||||
/// Get mutable reference to CH1
|
||||
pub fn ch1_mut(&mut self) -> &mut DacCh1<'d, T, TxCh1> {
|
||||
/// Temporarily access channel 1.
|
||||
pub fn ch1(&mut self) -> &mut DacCh1<'d, T, DMACh1> {
|
||||
&mut self.ch1
|
||||
}
|
||||
|
||||
/// Get mutable reference to CH2
|
||||
pub fn ch2_mut(&mut self) -> &mut DacCh2<'d, T, TxCh2> {
|
||||
/// Temporarily access channel 2.
|
||||
pub fn ch2(&mut self) -> &mut DacCh2<'d, T, DMACh2> {
|
||||
&mut self.ch2
|
||||
}
|
||||
|
||||
/// Get reference to CH1
|
||||
pub fn ch1(&mut self) -> &DacCh1<'d, T, TxCh1> {
|
||||
&self.ch1
|
||||
/// Simultaneously update channels 1 and 2 with a new value.
|
||||
///
|
||||
/// 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 {
|
||||
@ -552,34 +493,36 @@ pub(crate) mod sealed {
|
||||
}
|
||||
|
||||
pub trait Instance: sealed::Instance + RccPeripheral + 'static {}
|
||||
dma_trait!(DmaCh1, Instance);
|
||||
dma_trait!(DmaCh2, Instance);
|
||||
dma_trait!(DacDma1, Instance);
|
||||
dma_trait!(DacDma2, Instance);
|
||||
|
||||
/// Marks a pin that can be used with the DAC
|
||||
pub trait DacPin<T: Instance, const C: u8>: crate::gpio::Pin + 'static {}
|
||||
|
||||
foreach_peripheral!(
|
||||
(dac, $inst:ident) => {
|
||||
// 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))]
|
||||
impl crate::rcc::sealed::RccPeripheral for peripherals::$inst {
|
||||
fn frequency() -> crate::time::Hertz {
|
||||
critical_section::with(|_| unsafe { crate::rcc::get_freqs().pclk1 })
|
||||
}
|
||||
// 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))]
|
||||
impl crate::rcc::sealed::RccPeripheral for peripherals::$inst {
|
||||
fn frequency() -> crate::time::Hertz {
|
||||
critical_section::with(|_| unsafe { crate::rcc::get_freqs().pclk1 })
|
||||
}
|
||||
|
||||
fn enable_and_reset_with_cs(_cs: critical_section::CriticalSection) {
|
||||
crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(true));
|
||||
crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(false));
|
||||
crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(true));
|
||||
}
|
||||
fn enable_and_reset_with_cs(_cs: critical_section::CriticalSection) {
|
||||
// TODO: Increment refcount?
|
||||
crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(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) {
|
||||
crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(false))
|
||||
}
|
||||
}
|
||||
fn disable_with_cs(_cs: critical_section::CriticalSection) {
|
||||
// TODO: Decrement refcount?
|
||||
crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(false))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(rcc_h7, rcc_h7rm0433))]
|
||||
impl crate::rcc::RccPeripheral for peripherals::$inst {}
|
||||
#[cfg(any(rcc_h7, rcc_h7rm0433))]
|
||||
impl crate::rcc::RccPeripheral for peripherals::$inst {}
|
||||
|
||||
impl crate::dac::sealed::Instance for peripherals::$inst {
|
||||
fn regs() -> &'static crate::pac::dac::Dac {
|
||||
|
282
embassy-stm32/src/dac/tsel.rs
Normal file
282
embassy-stm32/src/dac/tsel.rs
Normal 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
|
||||
}
|
||||
}
|
@ -299,19 +299,15 @@ impl<'a, C: Channel> Transfer<'a, C> {
|
||||
|
||||
pub fn request_stop(&mut self) {
|
||||
let ch = self.channel.regs().ch(self.channel.num());
|
||||
|
||||
// Disable the channel. Keep the IEs enabled so the irqs still fire.
|
||||
ch.cr().write(|w| {
|
||||
w.set_tcie(true);
|
||||
w.set_useie(true);
|
||||
w.set_dteie(true);
|
||||
w.set_suspie(true);
|
||||
ch.cr().modify(|w| {
|
||||
w.set_susp(true);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_running(&mut self) -> bool {
|
||||
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
|
||||
|
@ -119,13 +119,11 @@ impl<'a> TDesRing<'a> {
|
||||
// "Preceding reads and writes cannot be moved past subsequent writes."
|
||||
fence(Ordering::Release);
|
||||
|
||||
self.index = self.index + 1;
|
||||
if self.index == self.descriptors.len() {
|
||||
self.index = 0;
|
||||
}
|
||||
|
||||
// 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`.
|
||||
pub(crate) fn pop_packet(&mut self) {
|
||||
let descriptor = &mut self.descriptors[self.index];
|
||||
assert!(descriptor.available());
|
||||
let rd = &mut self.descriptors[self.index];
|
||||
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."
|
||||
fence(Ordering::Release);
|
||||
|
||||
// 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.
|
||||
self.index += 1;
|
||||
if self.index == self.descriptors.len() {
|
||||
self.index = 0
|
||||
}
|
||||
self.index = (self.index + 1) % self.descriptors.len();
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,9 @@ pub unsafe fn on_irq() {
|
||||
#[cfg(any(exti_c0, exti_g0, exti_l5, exti_u5, exti_h5, exti_h50))]
|
||||
let bits = EXTI.rpr(0).read().0 | EXTI.fpr(0).read().0;
|
||||
|
||||
// We don't handle or change any EXTI lines above 16.
|
||||
let bits = bits & 0x0000FFFF;
|
||||
|
||||
// Mask all the channels that fired.
|
||||
cpu_regs().imr(0).modify(|w| w.0 &= !bits);
|
||||
|
||||
|
@ -465,7 +465,7 @@ pub(crate) fn assert_not_corrupted_read(end_address: u32) {
|
||||
feature = "stm32f439vg",
|
||||
feature = "stm32f439zg",
|
||||
))]
|
||||
if second_bank_read && unsafe { pac::DBGMCU.idcode().read().rev_id() < REVISION_3 && !pa12_is_output_pull_low() } {
|
||||
if second_bank_read && pac::DBGMCU.idcode().read().rev_id() < REVISION_3 && !pa12_is_output_pull_low() {
|
||||
panic!("Read corruption for stm32f42xxG and stm32f43xxG in dual bank mode when PA12 is in use for chips below revision 3, see errata 2.2.11");
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,37 @@ pub struct Fmc<'d, T: Instance> {
|
||||
|
||||
unsafe impl<'d, T> Send for Fmc<'d, T> where T: Instance {}
|
||||
|
||||
impl<'d, T> Fmc<'d, T>
|
||||
where
|
||||
T: Instance,
|
||||
{
|
||||
/// Create a raw FMC instance.
|
||||
///
|
||||
/// **Note:** This is currently used to provide access to some basic FMC functions
|
||||
/// for manual configuration for memory types that stm32-fmc does not support.
|
||||
pub fn new_raw(_instance: impl Peripheral<P = T> + 'd) -> Self {
|
||||
Self { peri: PhantomData }
|
||||
}
|
||||
|
||||
/// Enable the FMC peripheral and reset it.
|
||||
pub fn enable(&mut self) {
|
||||
T::enable_and_reset();
|
||||
}
|
||||
|
||||
/// Enable the memory controller on applicable chips.
|
||||
pub fn memory_controller_enable(&mut self) {
|
||||
// fmc v1 and v2 does not have the fmcen bit
|
||||
// fsmc v1, v2 and v3 does not have the fmcen bit
|
||||
// This is a "not" because it is expected that all future versions have this bit
|
||||
#[cfg(not(any(fmc_v1x3, fmc_v2x1, fsmc_v1x0, fsmc_v1x3, fsmc_v2x3, fsmc_v3x1)))]
|
||||
T::REGS.bcr1().modify(|r| r.set_fmcen(true));
|
||||
}
|
||||
|
||||
pub fn source_clock_hz(&self) -> u32 {
|
||||
<T as crate::rcc::sealed::RccPeripheral>::frequency().0
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'d, T> stm32_fmc::FmcPeripheral for Fmc<'d, T>
|
||||
where
|
||||
T: Instance,
|
||||
|
@ -1,11 +1,14 @@
|
||||
#![macro_use]
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use crate::interrupt;
|
||||
|
||||
#[cfg_attr(i2c_v1, path = "v1.rs")]
|
||||
#[cfg_attr(i2c_v2, path = "v2.rs")]
|
||||
mod _version;
|
||||
pub use _version::*;
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
use crate::peripherals;
|
||||
|
||||
@ -23,6 +26,20 @@ pub enum Error {
|
||||
|
||||
pub(crate) mod sealed {
|
||||
use super::*;
|
||||
|
||||
pub struct State {
|
||||
#[allow(unused)]
|
||||
pub waker: AtomicWaker,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
waker: AtomicWaker::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Instance: crate::rcc::RccPeripheral {
|
||||
fn regs() -> crate::pac::i2c::I2c;
|
||||
fn state() -> &'static State;
|
||||
@ -30,7 +47,8 @@ pub(crate) mod sealed {
|
||||
}
|
||||
|
||||
pub trait Instance: sealed::Instance + 'static {
|
||||
type Interrupt: interrupt::typelevel::Interrupt;
|
||||
type EventInterrupt: interrupt::typelevel::Interrupt;
|
||||
type ErrorInterrupt: interrupt::typelevel::Interrupt;
|
||||
}
|
||||
|
||||
pin_trait!(SclPin, Instance);
|
||||
@ -38,21 +56,148 @@ pin_trait!(SdaPin, Instance);
|
||||
dma_trait!(RxDma, Instance);
|
||||
dma_trait!(TxDma, Instance);
|
||||
|
||||
foreach_interrupt!(
|
||||
($inst:ident, i2c, $block:ident, EV, $irq:ident) => {
|
||||
/// Interrupt handler.
|
||||
pub struct EventInterruptHandler<T: Instance> {
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Instance> interrupt::typelevel::Handler<T::EventInterrupt> for EventInterruptHandler<T> {
|
||||
unsafe fn on_interrupt() {
|
||||
_version::on_interrupt::<T>()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ErrorInterruptHandler<T: Instance> {
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Instance> interrupt::typelevel::Handler<T::ErrorInterrupt> for ErrorInterruptHandler<T> {
|
||||
unsafe fn on_interrupt() {
|
||||
_version::on_interrupt::<T>()
|
||||
}
|
||||
}
|
||||
|
||||
foreach_peripheral!(
|
||||
(i2c, $inst:ident) => {
|
||||
impl sealed::Instance for peripherals::$inst {
|
||||
fn regs() -> crate::pac::i2c::I2c {
|
||||
crate::pac::$inst
|
||||
}
|
||||
|
||||
fn state() -> &'static State {
|
||||
static STATE: State = State::new();
|
||||
fn state() -> &'static sealed::State {
|
||||
static STATE: sealed::State = sealed::State::new();
|
||||
&STATE
|
||||
}
|
||||
}
|
||||
|
||||
impl Instance for peripherals::$inst {
|
||||
type Interrupt = crate::interrupt::typelevel::$irq;
|
||||
type EventInterrupt = crate::_generated::peripheral_interrupts::$inst::EV;
|
||||
type ErrorInterrupt = crate::_generated::peripheral_interrupts::$inst::ER;
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
mod eh02 {
|
||||
use super::*;
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Read for I2c<'d, T> {
|
||||
type Error = Error;
|
||||
|
||||
fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_read(address, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Write for I2c<'d, T> {
|
||||
type Error = Error;
|
||||
|
||||
fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_write(address, write)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, T> {
|
||||
type Error = Error;
|
||||
|
||||
fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_write_read(address, write, read)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-traits")]
|
||||
mod eh1 {
|
||||
use super::*;
|
||||
use crate::dma::NoDma;
|
||||
|
||||
impl embedded_hal_1::i2c::Error for Error {
|
||||
fn kind(&self) -> embedded_hal_1::i2c::ErrorKind {
|
||||
match *self {
|
||||
Self::Bus => embedded_hal_1::i2c::ErrorKind::Bus,
|
||||
Self::Arbitration => embedded_hal_1::i2c::ErrorKind::ArbitrationLoss,
|
||||
Self::Nack => {
|
||||
embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Unknown)
|
||||
}
|
||||
Self::Timeout => embedded_hal_1::i2c::ErrorKind::Other,
|
||||
Self::Crc => embedded_hal_1::i2c::ErrorKind::Other,
|
||||
Self::Overrun => embedded_hal_1::i2c::ErrorKind::Overrun,
|
||||
Self::ZeroLengthTransfer => embedded_hal_1::i2c::ErrorKind::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance, TXDMA, RXDMA> embedded_hal_1::i2c::ErrorType for I2c<'d, T, TXDMA, RXDMA> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_1::i2c::I2c for I2c<'d, T, NoDma, NoDma> {
|
||||
fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_read(address, read)
|
||||
}
|
||||
|
||||
fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_write(address, write)
|
||||
}
|
||||
|
||||
fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_write_read(address, write, read)
|
||||
}
|
||||
|
||||
fn transaction(
|
||||
&mut self,
|
||||
_address: u8,
|
||||
_operations: &mut [embedded_hal_1::i2c::Operation<'_>],
|
||||
) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "unstable-traits", feature = "nightly"))]
|
||||
mod eha {
|
||||
use super::*;
|
||||
|
||||
impl<'d, T: Instance, TXDMA: TxDma<T>, RXDMA: RxDma<T>> embedded_hal_async::i2c::I2c for I2c<'d, T, TXDMA, RXDMA> {
|
||||
async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.read(address, read).await
|
||||
}
|
||||
|
||||
async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
|
||||
self.write(address, write).await
|
||||
}
|
||||
|
||||
async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.write_read(address, write, read).await
|
||||
}
|
||||
|
||||
async fn transaction(
|
||||
&mut self,
|
||||
address: u8,
|
||||
operations: &mut [embedded_hal_1::i2c::Operation<'_>],
|
||||
) -> Result<(), Self::Error> {
|
||||
let _ = address;
|
||||
let _ = operations;
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,33 @@
|
||||
use core::future::poll_fn;
|
||||
use core::marker::PhantomData;
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_embedded_hal::SetConfig;
|
||||
use embassy_futures::select::{select, Either};
|
||||
use embassy_hal_internal::drop::OnDrop;
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
|
||||
use crate::dma::NoDma;
|
||||
use super::*;
|
||||
use crate::dma::{NoDma, Transfer};
|
||||
use crate::gpio::sealed::AFType;
|
||||
use crate::gpio::Pull;
|
||||
use crate::i2c::{Error, Instance, SclPin, SdaPin};
|
||||
use crate::interrupt::typelevel::Interrupt;
|
||||
use crate::pac::i2c;
|
||||
use crate::time::Hertz;
|
||||
use crate::{interrupt, Peripheral};
|
||||
|
||||
/// Interrupt handler.
|
||||
pub struct InterruptHandler<T: Instance> {
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
|
||||
unsafe fn on_interrupt() {}
|
||||
pub unsafe fn on_interrupt<T: Instance>() {
|
||||
let regs = T::regs();
|
||||
// i2c v2 only woke the task on transfer complete interrupts. v1 uses interrupts for a bunch of
|
||||
// other stuff, so we wake the task on every interrupt.
|
||||
T::state().waker.wake();
|
||||
critical_section::with(|_| {
|
||||
// Clear event interrupt flag.
|
||||
regs.cr2().modify(|w| {
|
||||
w.set_itevten(false);
|
||||
w.set_iterren(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
@ -27,14 +37,6 @@ pub struct Config {
|
||||
pub scl_pullup: bool,
|
||||
}
|
||||
|
||||
pub struct State {}
|
||||
|
||||
impl State {
|
||||
pub(crate) const fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct I2c<'d, T: Instance, TXDMA = NoDma, RXDMA = NoDma> {
|
||||
phantom: PhantomData<&'d mut T>,
|
||||
#[allow(dead_code)]
|
||||
@ -48,7 +50,9 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
_peri: impl Peripheral<P = T> + 'd,
|
||||
scl: impl Peripheral<P = impl SclPin<T>> + 'd,
|
||||
sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
|
||||
_irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
|
||||
_irq: impl interrupt::typelevel::Binding<T::EventInterrupt, EventInterruptHandler<T>>
|
||||
+ interrupt::typelevel::Binding<T::ErrorInterrupt, ErrorInterruptHandler<T>>
|
||||
+ 'd,
|
||||
tx_dma: impl Peripheral<P = TXDMA> + 'd,
|
||||
rx_dma: impl Peripheral<P = RXDMA> + 'd,
|
||||
freq: Hertz,
|
||||
@ -98,6 +102,9 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
reg.set_pe(true);
|
||||
});
|
||||
|
||||
unsafe { T::EventInterrupt::enable() };
|
||||
unsafe { T::ErrorInterrupt::enable() };
|
||||
|
||||
Self {
|
||||
phantom: PhantomData,
|
||||
tx_dma,
|
||||
@ -105,40 +112,58 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_and_clear_error_flags(&self) -> Result<i2c::regs::Sr1, Error> {
|
||||
fn check_and_clear_error_flags() -> Result<i2c::regs::Sr1, Error> {
|
||||
// Note that flags should only be cleared once they have been registered. If flags are
|
||||
// cleared otherwise, there may be an inherent race condition and flags may be missed.
|
||||
let sr1 = T::regs().sr1().read();
|
||||
|
||||
if sr1.timeout() {
|
||||
T::regs().sr1().modify(|reg| reg.set_timeout(false));
|
||||
T::regs().sr1().write(|reg| {
|
||||
reg.0 = !0;
|
||||
reg.set_timeout(false);
|
||||
});
|
||||
return Err(Error::Timeout);
|
||||
}
|
||||
|
||||
if sr1.pecerr() {
|
||||
T::regs().sr1().modify(|reg| reg.set_pecerr(false));
|
||||
T::regs().sr1().write(|reg| {
|
||||
reg.0 = !0;
|
||||
reg.set_pecerr(false);
|
||||
});
|
||||
return Err(Error::Crc);
|
||||
}
|
||||
|
||||
if sr1.ovr() {
|
||||
T::regs().sr1().modify(|reg| reg.set_ovr(false));
|
||||
T::regs().sr1().write(|reg| {
|
||||
reg.0 = !0;
|
||||
reg.set_ovr(false);
|
||||
});
|
||||
return Err(Error::Overrun);
|
||||
}
|
||||
|
||||
if sr1.af() {
|
||||
T::regs().sr1().modify(|reg| reg.set_af(false));
|
||||
T::regs().sr1().write(|reg| {
|
||||
reg.0 = !0;
|
||||
reg.set_af(false);
|
||||
});
|
||||
return Err(Error::Nack);
|
||||
}
|
||||
|
||||
if sr1.arlo() {
|
||||
T::regs().sr1().modify(|reg| reg.set_arlo(false));
|
||||
T::regs().sr1().write(|reg| {
|
||||
reg.0 = !0;
|
||||
reg.set_arlo(false);
|
||||
});
|
||||
return Err(Error::Arbitration);
|
||||
}
|
||||
|
||||
// The errata indicates that BERR may be incorrectly detected. It recommends ignoring and
|
||||
// clearing the BERR bit instead.
|
||||
if sr1.berr() {
|
||||
T::regs().sr1().modify(|reg| reg.set_berr(false));
|
||||
T::regs().sr1().write(|reg| {
|
||||
reg.0 = !0;
|
||||
reg.set_berr(false);
|
||||
});
|
||||
}
|
||||
|
||||
Ok(sr1)
|
||||
@ -157,13 +182,13 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
});
|
||||
|
||||
// Wait until START condition was generated
|
||||
while !self.check_and_clear_error_flags()?.start() {
|
||||
while !Self::check_and_clear_error_flags()?.start() {
|
||||
check_timeout()?;
|
||||
}
|
||||
|
||||
// Also wait until signalled we're master and everything is waiting for us
|
||||
while {
|
||||
self.check_and_clear_error_flags()?;
|
||||
Self::check_and_clear_error_flags()?;
|
||||
|
||||
let sr2 = T::regs().sr2().read();
|
||||
!sr2.msl() && !sr2.busy()
|
||||
@ -177,7 +202,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
// Wait until address was sent
|
||||
// Wait for the address to be acknowledged
|
||||
// Check for any I2C errors. If a NACK occurs, the ADDR bit will never be set.
|
||||
while !self.check_and_clear_error_flags()?.addr() {
|
||||
while !Self::check_and_clear_error_flags()?.addr() {
|
||||
check_timeout()?;
|
||||
}
|
||||
|
||||
@ -197,7 +222,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
// Wait until we're ready for sending
|
||||
while {
|
||||
// Check for any I2C errors. If a NACK occurs, the ADDR bit will never be set.
|
||||
!self.check_and_clear_error_flags()?.txe()
|
||||
!Self::check_and_clear_error_flags()?.txe()
|
||||
} {
|
||||
check_timeout()?;
|
||||
}
|
||||
@ -208,7 +233,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
// Wait until byte is transferred
|
||||
while {
|
||||
// Check for any potential error conditions.
|
||||
!self.check_and_clear_error_flags()?.btf()
|
||||
!Self::check_and_clear_error_flags()?.btf()
|
||||
} {
|
||||
check_timeout()?;
|
||||
}
|
||||
@ -219,7 +244,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
fn recv_byte(&self, check_timeout: impl Fn() -> Result<(), Error>) -> Result<u8, Error> {
|
||||
while {
|
||||
// Check for any potential error conditions.
|
||||
self.check_and_clear_error_flags()?;
|
||||
Self::check_and_clear_error_flags()?;
|
||||
|
||||
!T::regs().sr1().read().rxne()
|
||||
} {
|
||||
@ -244,7 +269,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
});
|
||||
|
||||
// Wait until START condition was generated
|
||||
while !self.check_and_clear_error_flags()?.start() {
|
||||
while !Self::check_and_clear_error_flags()?.start() {
|
||||
check_timeout()?;
|
||||
}
|
||||
|
||||
@ -261,7 +286,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
|
||||
// Wait until address was sent
|
||||
// Wait for the address to be acknowledged
|
||||
while !self.check_and_clear_error_flags()?.addr() {
|
||||
while !Self::check_and_clear_error_flags()?.addr() {
|
||||
check_timeout()?;
|
||||
}
|
||||
|
||||
@ -336,6 +361,356 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
pub fn blocking_write_read(&mut self, addr: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> {
|
||||
self.blocking_write_read_timeout(addr, write, read, || Ok(()))
|
||||
}
|
||||
|
||||
// Async
|
||||
|
||||
#[inline] // pretty sure this should always be inlined
|
||||
fn enable_interrupts() -> () {
|
||||
T::regs().cr2().modify(|w| {
|
||||
w.set_iterren(true);
|
||||
w.set_itevten(true);
|
||||
});
|
||||
}
|
||||
|
||||
async fn write_with_stop(&mut self, address: u8, write: &[u8], send_stop: bool) -> Result<(), Error>
|
||||
where
|
||||
TXDMA: crate::i2c::TxDma<T>,
|
||||
{
|
||||
let dma_transfer = unsafe {
|
||||
let regs = T::regs();
|
||||
regs.cr2().modify(|w| {
|
||||
// DMA mode can be enabled for transmission by setting the DMAEN bit in the I2C_CR2 register.
|
||||
w.set_dmaen(true);
|
||||
w.set_itbufen(false);
|
||||
});
|
||||
// Set the I2C_DR register address in the DMA_SxPAR register. The data will be moved to this address from the memory after each TxE event.
|
||||
let dst = regs.dr().as_ptr() as *mut u8;
|
||||
|
||||
let ch = &mut self.tx_dma;
|
||||
let request = ch.request();
|
||||
Transfer::new_write(ch, request, write, dst, Default::default())
|
||||
};
|
||||
|
||||
let on_drop = OnDrop::new(|| {
|
||||
let regs = T::regs();
|
||||
regs.cr2().modify(|w| {
|
||||
w.set_dmaen(false);
|
||||
w.set_iterren(false);
|
||||
w.set_itevten(false);
|
||||
})
|
||||
});
|
||||
|
||||
Self::enable_interrupts();
|
||||
|
||||
// Send a START condition
|
||||
T::regs().cr1().modify(|reg| {
|
||||
reg.set_start(true);
|
||||
});
|
||||
|
||||
let state = T::state();
|
||||
|
||||
// Wait until START condition was generated
|
||||
poll_fn(|cx| {
|
||||
state.waker.register(cx.waker());
|
||||
|
||||
match Self::check_and_clear_error_flags() {
|
||||
Err(e) => Poll::Ready(Err(e)),
|
||||
Ok(sr1) => {
|
||||
if sr1.start() {
|
||||
Poll::Ready(Ok(()))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Also wait until signalled we're master and everything is waiting for us
|
||||
Self::enable_interrupts();
|
||||
poll_fn(|cx| {
|
||||
state.waker.register(cx.waker());
|
||||
|
||||
match Self::check_and_clear_error_flags() {
|
||||
Err(e) => Poll::Ready(Err(e)),
|
||||
Ok(_) => {
|
||||
let sr2 = T::regs().sr2().read();
|
||||
if !sr2.msl() && !sr2.busy() {
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Set up current address, we're trying to talk to
|
||||
Self::enable_interrupts();
|
||||
T::regs().dr().write(|reg| reg.set_dr(address << 1));
|
||||
|
||||
poll_fn(|cx| {
|
||||
state.waker.register(cx.waker());
|
||||
match Self::check_and_clear_error_flags() {
|
||||
Err(e) => Poll::Ready(Err(e)),
|
||||
Ok(sr1) => {
|
||||
if sr1.addr() {
|
||||
// Clear the ADDR condition by reading SR2.
|
||||
T::regs().sr2().read();
|
||||
Poll::Ready(Ok(()))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
Self::enable_interrupts();
|
||||
let poll_error = poll_fn(|cx| {
|
||||
state.waker.register(cx.waker());
|
||||
|
||||
match Self::check_and_clear_error_flags() {
|
||||
// Unclear why the Err turbofish is necessary here? The compiler didn’t require it in the other
|
||||
// identical poll_fn check_and_clear matches.
|
||||
Err(e) => Poll::Ready(Err::<T, Error>(e)),
|
||||
Ok(_) => Poll::Pending,
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for either the DMA transfer to successfully finish, or an I2C error to occur.
|
||||
match select(dma_transfer, poll_error).await {
|
||||
Either::Second(Err(e)) => Err(e),
|
||||
_ => Ok(()),
|
||||
}?;
|
||||
|
||||
// The I2C transfer itself will take longer than the DMA transfer, so wait for that to finish too.
|
||||
|
||||
// 18.3.8 “Master transmitter: In the interrupt routine after the EOT interrupt, disable DMA
|
||||
// requests then wait for a BTF event before programming the Stop condition.”
|
||||
|
||||
// TODO: If this has to be done “in the interrupt routine after the EOT interrupt”, where to put it?
|
||||
T::regs().cr2().modify(|w| {
|
||||
w.set_dmaen(false);
|
||||
});
|
||||
|
||||
Self::enable_interrupts();
|
||||
poll_fn(|cx| {
|
||||
state.waker.register(cx.waker());
|
||||
|
||||
match Self::check_and_clear_error_flags() {
|
||||
Err(e) => Poll::Ready(Err(e)),
|
||||
Ok(sr1) => {
|
||||
if sr1.btf() {
|
||||
if send_stop {
|
||||
T::regs().cr1().modify(|w| {
|
||||
w.set_stop(true);
|
||||
});
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
drop(on_drop);
|
||||
|
||||
// Fallthrough is success
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error>
|
||||
where
|
||||
TXDMA: crate::i2c::TxDma<T>,
|
||||
{
|
||||
self.write_with_stop(address, write, true).await?;
|
||||
|
||||
// Wait for STOP condition to transmit.
|
||||
Self::enable_interrupts();
|
||||
poll_fn(|cx| {
|
||||
T::state().waker.register(cx.waker());
|
||||
// TODO: error interrupts are enabled here, should we additional check for and return errors?
|
||||
if T::regs().cr1().read().stop() {
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error>
|
||||
where
|
||||
RXDMA: crate::i2c::RxDma<T>,
|
||||
{
|
||||
let state = T::state();
|
||||
let buffer_len = buffer.len();
|
||||
|
||||
let dma_transfer = unsafe {
|
||||
let regs = T::regs();
|
||||
regs.cr2().modify(|w| {
|
||||
// DMA mode can be enabled for transmission by setting the DMAEN bit in the I2C_CR2 register.
|
||||
w.set_itbufen(false);
|
||||
w.set_dmaen(true);
|
||||
});
|
||||
// Set the I2C_DR register address in the DMA_SxPAR register. The data will be moved to this address from the memory after each TxE event.
|
||||
let src = regs.dr().as_ptr() as *mut u8;
|
||||
|
||||
let ch = &mut self.rx_dma;
|
||||
let request = ch.request();
|
||||
Transfer::new_read(ch, request, src, buffer, Default::default())
|
||||
};
|
||||
|
||||
let on_drop = OnDrop::new(|| {
|
||||
let regs = T::regs();
|
||||
regs.cr2().modify(|w| {
|
||||
w.set_dmaen(false);
|
||||
w.set_iterren(false);
|
||||
w.set_itevten(false);
|
||||
})
|
||||
});
|
||||
|
||||
Self::enable_interrupts();
|
||||
|
||||
// Send a START condition and set ACK bit
|
||||
T::regs().cr1().modify(|reg| {
|
||||
reg.set_start(true);
|
||||
reg.set_ack(true);
|
||||
});
|
||||
|
||||
// Wait until START condition was generated
|
||||
poll_fn(|cx| {
|
||||
state.waker.register(cx.waker());
|
||||
|
||||
match Self::check_and_clear_error_flags() {
|
||||
Err(e) => Poll::Ready(Err(e)),
|
||||
Ok(sr1) => {
|
||||
if sr1.start() {
|
||||
Poll::Ready(Ok(()))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Also wait until signalled we're master and everything is waiting for us
|
||||
Self::enable_interrupts();
|
||||
poll_fn(|cx| {
|
||||
state.waker.register(cx.waker());
|
||||
|
||||
// blocking read didn’t have a check_and_clear call here, but blocking write did so
|
||||
// I’m adding it here in case that was an oversight.
|
||||
match Self::check_and_clear_error_flags() {
|
||||
Err(e) => Poll::Ready(Err(e)),
|
||||
Ok(_) => {
|
||||
let sr2 = T::regs().sr2().read();
|
||||
if !sr2.msl() && !sr2.busy() {
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Set up current address, we're trying to talk to
|
||||
T::regs().dr().write(|reg| reg.set_dr((address << 1) + 1));
|
||||
|
||||
// Wait for the address to be acknowledged
|
||||
|
||||
Self::enable_interrupts();
|
||||
poll_fn(|cx| {
|
||||
state.waker.register(cx.waker());
|
||||
|
||||
match Self::check_and_clear_error_flags() {
|
||||
Err(e) => Poll::Ready(Err(e)),
|
||||
Ok(sr1) => {
|
||||
if sr1.addr() {
|
||||
// 18.3.8: When a single byte must be received: the NACK must be programmed during EV6
|
||||
// event, i.e. program ACK=0 when ADDR=1, before clearing ADDR flag.
|
||||
if buffer_len == 1 {
|
||||
T::regs().cr1().modify(|w| {
|
||||
w.set_ack(false);
|
||||
});
|
||||
}
|
||||
Poll::Ready(Ok(()))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Clear ADDR condition by reading SR2
|
||||
T::regs().sr2().read();
|
||||
|
||||
// 18.3.8: When a single byte must be received: [snip] Then the
|
||||
// user can program the STOP condition either after clearing ADDR flag, or in the
|
||||
// DMA Transfer Complete interrupt routine.
|
||||
if buffer_len == 1 {
|
||||
T::regs().cr1().modify(|w| {
|
||||
w.set_stop(true);
|
||||
});
|
||||
} else {
|
||||
// If, in the I2C_CR2 register, the LAST bit is set, I2C
|
||||
// automatically sends a NACK after the next byte following EOT_1. The user can
|
||||
// generate a Stop condition in the DMA Transfer Complete interrupt routine if enabled.
|
||||
T::regs().cr2().modify(|w| {
|
||||
w.set_last(true);
|
||||
})
|
||||
}
|
||||
|
||||
// Wait for bytes to be received, or an error to occur.
|
||||
Self::enable_interrupts();
|
||||
let poll_error = poll_fn(|cx| {
|
||||
state.waker.register(cx.waker());
|
||||
|
||||
match Self::check_and_clear_error_flags() {
|
||||
Err(e) => Poll::Ready(Err::<T, Error>(e)),
|
||||
_ => Poll::Pending,
|
||||
}
|
||||
});
|
||||
|
||||
match select(dma_transfer, poll_error).await {
|
||||
Either::Second(Err(e)) => Err(e),
|
||||
_ => Ok(()),
|
||||
}?;
|
||||
|
||||
// Wait for the STOP to be sent (STOP bit cleared).
|
||||
Self::enable_interrupts();
|
||||
poll_fn(|cx| {
|
||||
state.waker.register(cx.waker());
|
||||
// TODO: error interrupts are enabled here, should we additional check for and return errors?
|
||||
if T::regs().cr1().read().stop() {
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
drop(on_drop);
|
||||
|
||||
// Fallthrough is success
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error>
|
||||
where
|
||||
RXDMA: crate::i2c::RxDma<T>,
|
||||
TXDMA: crate::i2c::TxDma<T>,
|
||||
{
|
||||
self.write_with_stop(address, write, false).await?;
|
||||
self.read(address, read).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance, TXDMA, RXDMA> Drop for I2c<'d, T, TXDMA, RXDMA> {
|
||||
@ -344,77 +719,6 @@ impl<'d, T: Instance, TXDMA, RXDMA> Drop for I2c<'d, T, TXDMA, RXDMA> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Read for I2c<'d, T> {
|
||||
type Error = Error;
|
||||
|
||||
fn read(&mut self, addr: u8, read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_read(addr, read)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Write for I2c<'d, T> {
|
||||
type Error = Error;
|
||||
|
||||
fn write(&mut self, addr: u8, write: &[u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_write(addr, write)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, T> {
|
||||
type Error = Error;
|
||||
|
||||
fn write_read(&mut self, addr: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_write_read(addr, write, read)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-traits")]
|
||||
mod eh1 {
|
||||
use super::*;
|
||||
|
||||
impl embedded_hal_1::i2c::Error for Error {
|
||||
fn kind(&self) -> embedded_hal_1::i2c::ErrorKind {
|
||||
match *self {
|
||||
Self::Bus => embedded_hal_1::i2c::ErrorKind::Bus,
|
||||
Self::Arbitration => embedded_hal_1::i2c::ErrorKind::ArbitrationLoss,
|
||||
Self::Nack => {
|
||||
embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Unknown)
|
||||
}
|
||||
Self::Timeout => embedded_hal_1::i2c::ErrorKind::Other,
|
||||
Self::Crc => embedded_hal_1::i2c::ErrorKind::Other,
|
||||
Self::Overrun => embedded_hal_1::i2c::ErrorKind::Overrun,
|
||||
Self::ZeroLengthTransfer => embedded_hal_1::i2c::ErrorKind::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_1::i2c::ErrorType for I2c<'d, T> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_1::i2c::I2c for I2c<'d, T> {
|
||||
fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_read(address, read)
|
||||
}
|
||||
|
||||
fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_write(address, write)
|
||||
}
|
||||
|
||||
fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_write_read(address, write, read)
|
||||
}
|
||||
|
||||
fn transaction(
|
||||
&mut self,
|
||||
_address: u8,
|
||||
_operations: &mut [embedded_hal_1::i2c::Operation<'_>],
|
||||
) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Mode {
|
||||
Fast,
|
||||
Standard,
|
||||
|
@ -1,48 +1,51 @@
|
||||
use core::cmp;
|
||||
#[cfg(feature = "time")]
|
||||
use core::future::poll_fn;
|
||||
use core::marker::PhantomData;
|
||||
#[cfg(feature = "time")]
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_embedded_hal::SetConfig;
|
||||
#[cfg(feature = "time")]
|
||||
use embassy_hal_internal::drop::OnDrop;
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
#[cfg(feature = "time")]
|
||||
use embassy_time::{Duration, Instant};
|
||||
|
||||
use crate::dma::NoDma;
|
||||
#[cfg(feature = "time")]
|
||||
use crate::dma::Transfer;
|
||||
use super::*;
|
||||
use crate::dma::{NoDma, Transfer};
|
||||
use crate::gpio::sealed::AFType;
|
||||
use crate::gpio::Pull;
|
||||
use crate::i2c::{Error, Instance, SclPin, SdaPin};
|
||||
use crate::interrupt::typelevel::Interrupt;
|
||||
use crate::pac::i2c;
|
||||
use crate::time::Hertz;
|
||||
use crate::{interrupt, Peripheral};
|
||||
|
||||
/// Interrupt handler.
|
||||
pub struct InterruptHandler<T: Instance> {
|
||||
_phantom: PhantomData<T>,
|
||||
#[cfg(feature = "time")]
|
||||
fn timeout_fn(timeout: Duration) -> impl Fn() -> Result<(), Error> {
|
||||
let deadline = Instant::now() + timeout;
|
||||
move || {
|
||||
if Instant::now() > deadline {
|
||||
Err(Error::Timeout)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
|
||||
unsafe fn on_interrupt() {
|
||||
let regs = T::regs();
|
||||
let isr = regs.isr().read();
|
||||
#[cfg(not(feature = "time"))]
|
||||
pub fn no_timeout_fn() -> impl Fn() -> Result<(), Error> {
|
||||
move || Ok(())
|
||||
}
|
||||
|
||||
if isr.tcr() || isr.tc() {
|
||||
T::state().waker.wake();
|
||||
}
|
||||
// The flag can only be cleared by writting to nbytes, we won't do that here, so disable
|
||||
// the interrupt
|
||||
critical_section::with(|_| {
|
||||
regs.cr1().modify(|w| w.set_tcie(false));
|
||||
});
|
||||
pub unsafe fn on_interrupt<T: Instance>() {
|
||||
let regs = T::regs();
|
||||
let isr = regs.isr().read();
|
||||
|
||||
if isr.tcr() || isr.tc() {
|
||||
T::state().waker.wake();
|
||||
}
|
||||
// The flag can only be cleared by writting to nbytes, we won't do that here, so disable
|
||||
// the interrupt
|
||||
critical_section::with(|_| {
|
||||
regs.cr1().modify(|w| w.set_tcie(false));
|
||||
});
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
@ -65,18 +68,6 @@ impl Default for Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
waker: AtomicWaker,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub(crate) const fn new() -> Self {
|
||||
Self {
|
||||
waker: AtomicWaker::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct I2c<'d, T: Instance, TXDMA = NoDma, RXDMA = NoDma> {
|
||||
_peri: PeripheralRef<'d, T>,
|
||||
#[allow(dead_code)]
|
||||
@ -92,7 +83,9 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
peri: impl Peripheral<P = T> + 'd,
|
||||
scl: impl Peripheral<P = impl SclPin<T>> + 'd,
|
||||
sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
|
||||
_irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
|
||||
_irq: impl interrupt::typelevel::Binding<T::EventInterrupt, EventInterruptHandler<T>>
|
||||
+ interrupt::typelevel::Binding<T::ErrorInterrupt, ErrorInterruptHandler<T>>
|
||||
+ 'd,
|
||||
tx_dma: impl Peripheral<P = TXDMA> + 'd,
|
||||
rx_dma: impl Peripheral<P = RXDMA> + 'd,
|
||||
freq: Hertz,
|
||||
@ -138,8 +131,8 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
reg.set_pe(true);
|
||||
});
|
||||
|
||||
T::Interrupt::unpend();
|
||||
unsafe { T::Interrupt::enable() };
|
||||
unsafe { T::EventInterrupt::enable() };
|
||||
unsafe { T::ErrorInterrupt::enable() };
|
||||
|
||||
Self {
|
||||
_peri: peri,
|
||||
@ -260,21 +253,12 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
}
|
||||
|
||||
fn flush_txdr(&self) {
|
||||
//if $i2c.isr.read().txis().bit_is_set() {
|
||||
//$i2c.txdr.write(|w| w.txdata().bits(0));
|
||||
//}
|
||||
|
||||
if T::regs().isr().read().txis() {
|
||||
T::regs().txdr().write(|w| w.set_txdata(0));
|
||||
}
|
||||
if !T::regs().isr().read().txe() {
|
||||
T::regs().isr().modify(|w| w.set_txe(true))
|
||||
}
|
||||
|
||||
// If TXDR is not flagged as empty, write 1 to flush it
|
||||
//if $i2c.isr.read().txe().is_not_empty() {
|
||||
//$i2c.isr.write(|w| w.txe().set_bit());
|
||||
//}
|
||||
}
|
||||
|
||||
fn wait_txe(&self, check_timeout: impl Fn() -> Result<(), Error>) -> Result<(), Error> {
|
||||
@ -437,7 +421,6 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
async fn write_dma_internal(
|
||||
&mut self,
|
||||
address: u8,
|
||||
@ -528,7 +511,6 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
async fn read_dma_internal(
|
||||
&mut self,
|
||||
address: u8,
|
||||
@ -610,42 +592,38 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
|
||||
// =========================
|
||||
// Async public API
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error>
|
||||
where
|
||||
TXDMA: crate::i2c::TxDma<T>,
|
||||
{
|
||||
self.write_timeout(address, write, self.timeout).await
|
||||
}
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
pub async fn write_timeout(&mut self, address: u8, write: &[u8], timeout: Duration) -> Result<(), Error>
|
||||
where
|
||||
TXDMA: crate::i2c::TxDma<T>,
|
||||
{
|
||||
if write.is_empty() {
|
||||
self.write_internal(address, write, true, timeout_fn(timeout))
|
||||
self.write_internal(address, write, true, timeout_fn(self.timeout))
|
||||
} else {
|
||||
embassy_time::with_timeout(
|
||||
timeout,
|
||||
self.write_dma_internal(address, write, true, true, timeout_fn(timeout)),
|
||||
self.timeout,
|
||||
self.write_dma_internal(address, write, true, true, timeout_fn(self.timeout)),
|
||||
)
|
||||
.await
|
||||
.unwrap_or(Err(Error::Timeout))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
pub async fn write_vectored(&mut self, address: u8, write: &[&[u8]]) -> Result<(), Error>
|
||||
#[cfg(not(feature = "time"))]
|
||||
pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error>
|
||||
where
|
||||
TXDMA: crate::i2c::TxDma<T>,
|
||||
{
|
||||
self.write_vectored_timeout(address, write, self.timeout).await
|
||||
if write.is_empty() {
|
||||
self.write_internal(address, write, true, no_timeout_fn())
|
||||
} else {
|
||||
self.write_dma_internal(address, write, true, true, no_timeout_fn())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
pub async fn write_vectored_timeout(&mut self, address: u8, write: &[&[u8]], timeout: Duration) -> Result<(), Error>
|
||||
pub async fn write_vectored(&mut self, address: u8, write: &[&[u8]]) -> Result<(), Error>
|
||||
where
|
||||
TXDMA: crate::i2c::TxDma<T>,
|
||||
{
|
||||
@ -661,8 +639,8 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
let is_last = next.is_none();
|
||||
|
||||
embassy_time::with_timeout(
|
||||
timeout,
|
||||
self.write_dma_internal(address, c, first, is_last, timeout_fn(timeout)),
|
||||
self.timeout,
|
||||
self.write_dma_internal(address, c, first, is_last, timeout_fn(self.timeout)),
|
||||
)
|
||||
.await
|
||||
.unwrap_or(Err(Error::Timeout))?;
|
||||
@ -672,66 +650,79 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "time"))]
|
||||
pub async fn write_vectored(&mut self, address: u8, write: &[&[u8]]) -> Result<(), Error>
|
||||
where
|
||||
TXDMA: crate::i2c::TxDma<T>,
|
||||
{
|
||||
if write.is_empty() {
|
||||
return Err(Error::ZeroLengthTransfer);
|
||||
}
|
||||
let mut iter = write.iter();
|
||||
|
||||
let mut first = true;
|
||||
let mut current = iter.next();
|
||||
while let Some(c) = current {
|
||||
let next = iter.next();
|
||||
let is_last = next.is_none();
|
||||
|
||||
self.write_dma_internal(address, c, first, is_last, no_timeout_fn())
|
||||
.await?;
|
||||
first = false;
|
||||
current = next;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error>
|
||||
where
|
||||
RXDMA: crate::i2c::RxDma<T>,
|
||||
{
|
||||
self.read_timeout(address, buffer, self.timeout).await
|
||||
if buffer.is_empty() {
|
||||
self.read_internal(address, buffer, false, timeout_fn(self.timeout))
|
||||
} else {
|
||||
embassy_time::with_timeout(
|
||||
self.timeout,
|
||||
self.read_dma_internal(address, buffer, false, timeout_fn(self.timeout)),
|
||||
)
|
||||
.await
|
||||
.unwrap_or(Err(Error::Timeout))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
pub async fn read_timeout(&mut self, address: u8, buffer: &mut [u8], timeout: Duration) -> Result<(), Error>
|
||||
#[cfg(not(feature = "time"))]
|
||||
pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error>
|
||||
where
|
||||
RXDMA: crate::i2c::RxDma<T>,
|
||||
{
|
||||
if buffer.is_empty() {
|
||||
self.read_internal(address, buffer, false, timeout_fn(timeout))
|
||||
self.read_internal(address, buffer, false, no_timeout_fn())
|
||||
} else {
|
||||
embassy_time::with_timeout(
|
||||
timeout,
|
||||
self.read_dma_internal(address, buffer, false, timeout_fn(timeout)),
|
||||
)
|
||||
.await
|
||||
.unwrap_or(Err(Error::Timeout))
|
||||
self.read_dma_internal(address, buffer, false, no_timeout_fn()).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error>
|
||||
where
|
||||
TXDMA: super::TxDma<T>,
|
||||
RXDMA: super::RxDma<T>,
|
||||
{
|
||||
self.write_read_timeout(address, write, read, self.timeout).await
|
||||
}
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
pub async fn write_read_timeout(
|
||||
&mut self,
|
||||
address: u8,
|
||||
write: &[u8],
|
||||
read: &mut [u8],
|
||||
timeout: Duration,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
TXDMA: super::TxDma<T>,
|
||||
RXDMA: super::RxDma<T>,
|
||||
{
|
||||
let start_instant = Instant::now();
|
||||
let check_timeout = timeout_fn(timeout);
|
||||
let check_timeout = timeout_fn(self.timeout);
|
||||
if write.is_empty() {
|
||||
self.write_internal(address, write, false, &check_timeout)?;
|
||||
} else {
|
||||
embassy_time::with_timeout(
|
||||
timeout,
|
||||
self.timeout,
|
||||
self.write_dma_internal(address, write, true, true, &check_timeout),
|
||||
)
|
||||
.await
|
||||
.unwrap_or(Err(Error::Timeout))?;
|
||||
}
|
||||
|
||||
let time_left_until_timeout = timeout - Instant::now().duration_since(start_instant);
|
||||
let time_left_until_timeout = self.timeout - Instant::now().duration_since(start_instant);
|
||||
|
||||
if read.is_empty() {
|
||||
self.read_internal(address, read, true, &check_timeout)?;
|
||||
@ -747,6 +738,28 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "time"))]
|
||||
pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error>
|
||||
where
|
||||
TXDMA: super::TxDma<T>,
|
||||
RXDMA: super::RxDma<T>,
|
||||
{
|
||||
let no_timeout = no_timeout_fn();
|
||||
if write.is_empty() {
|
||||
self.write_internal(address, write, false, &no_timeout)?;
|
||||
} else {
|
||||
self.write_dma_internal(address, write, true, true, &no_timeout).await?;
|
||||
}
|
||||
|
||||
if read.is_empty() {
|
||||
self.read_internal(address, read, true, &no_timeout)?;
|
||||
} else {
|
||||
self.read_dma_internal(address, read, true, &no_timeout).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// =========================
|
||||
// Blocking public API
|
||||
|
||||
@ -955,35 +968,6 @@ impl<'d, T: Instance, TXDMA, RXDMA> Drop for I2c<'d, T, TXDMA, RXDMA> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
mod eh02 {
|
||||
use super::*;
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Read for I2c<'d, T> {
|
||||
type Error = Error;
|
||||
|
||||
fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_read(address, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Write for I2c<'d, T> {
|
||||
type Error = Error;
|
||||
|
||||
fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_write(address, write)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, T> {
|
||||
type Error = Error;
|
||||
|
||||
fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_write_read(address, write, read)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// I2C Stop Configuration
|
||||
///
|
||||
/// Peripheral options for generating the STOP condition
|
||||
@ -1108,83 +1092,6 @@ impl Timings {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-traits")]
|
||||
mod eh1 {
|
||||
use super::*;
|
||||
|
||||
impl embedded_hal_1::i2c::Error for Error {
|
||||
fn kind(&self) -> embedded_hal_1::i2c::ErrorKind {
|
||||
match *self {
|
||||
Self::Bus => embedded_hal_1::i2c::ErrorKind::Bus,
|
||||
Self::Arbitration => embedded_hal_1::i2c::ErrorKind::ArbitrationLoss,
|
||||
Self::Nack => {
|
||||
embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Unknown)
|
||||
}
|
||||
Self::Timeout => embedded_hal_1::i2c::ErrorKind::Other,
|
||||
Self::Crc => embedded_hal_1::i2c::ErrorKind::Other,
|
||||
Self::Overrun => embedded_hal_1::i2c::ErrorKind::Overrun,
|
||||
Self::ZeroLengthTransfer => embedded_hal_1::i2c::ErrorKind::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance, TXDMA, RXDMA> embedded_hal_1::i2c::ErrorType for I2c<'d, T, TXDMA, RXDMA> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> embedded_hal_1::i2c::I2c for I2c<'d, T, NoDma, NoDma> {
|
||||
fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_read(address, read)
|
||||
}
|
||||
|
||||
fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_write(address, write)
|
||||
}
|
||||
|
||||
fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.blocking_write_read(address, write, read)
|
||||
}
|
||||
|
||||
fn transaction(
|
||||
&mut self,
|
||||
_address: u8,
|
||||
_operations: &mut [embedded_hal_1::i2c::Operation<'_>],
|
||||
) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "unstable-traits", feature = "nightly", feature = "time"))]
|
||||
mod eha {
|
||||
use super::super::{RxDma, TxDma};
|
||||
use super::*;
|
||||
|
||||
impl<'d, T: Instance, TXDMA: TxDma<T>, RXDMA: RxDma<T>> embedded_hal_async::i2c::I2c for I2c<'d, T, TXDMA, RXDMA> {
|
||||
async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.read(address, read).await
|
||||
}
|
||||
|
||||
async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
|
||||
self.write(address, write).await
|
||||
}
|
||||
|
||||
async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.write_read(address, write, read).await
|
||||
}
|
||||
|
||||
async fn transaction(
|
||||
&mut self,
|
||||
address: u8,
|
||||
operations: &mut [embedded_hal_1::i2c::Operation<'_>],
|
||||
) -> Result<(), Self::Error> {
|
||||
let _ = address;
|
||||
let _ = operations;
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> SetConfig for I2c<'d, T> {
|
||||
type Config = Hertz;
|
||||
type ConfigError = ();
|
||||
@ -1201,15 +1108,3 @@ impl<'d, T: Instance> SetConfig for I2c<'d, T> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
fn timeout_fn(timeout: Duration) -> impl Fn() -> Result<(), Error> {
|
||||
let deadline = Instant::now() + timeout;
|
||||
move || {
|
||||
if Instant::now() > deadline {
|
||||
Err(Error::Timeout)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
#![cfg_attr(not(test), 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))]
|
||||
|
||||
//! ## Feature flags
|
||||
#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
|
||||
@ -226,8 +227,9 @@ pub fn init(config: Config) -> Peripherals {
|
||||
time_driver::init(cs);
|
||||
|
||||
#[cfg(feature = "low-power")]
|
||||
while !crate::rcc::low_power_ready() {
|
||||
crate::rcc::clock_refcount_sub(cs);
|
||||
{
|
||||
crate::rcc::REFCOUNT_STOP2 = 0;
|
||||
crate::rcc::REFCOUNT_STOP1 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,50 @@
|
||||
/// The STM32 line of microcontrollers support various deep-sleep modes which exploit clock-gating
|
||||
/// to reduce power consumption. `embassy-stm32` provides a low-power executor, [`Executor`] which
|
||||
/// can use knowledge of which peripherals are currently blocked upon to transparently and safely
|
||||
/// enter such low-power modes (currently, only `STOP2`) when idle.
|
||||
///
|
||||
/// The executor determines which peripherals are active by their RCC state; consequently,
|
||||
/// low-power states can only be entered if all peripherals have been `drop`'d. There are a few
|
||||
/// exceptions to this rule:
|
||||
///
|
||||
/// * `GPIO`
|
||||
/// * `RCC`
|
||||
///
|
||||
/// Since entering and leaving low-power modes typically incurs a significant latency, the
|
||||
/// low-power executor will only attempt to enter when the next timer event is at least
|
||||
/// [`time_driver::MIN_STOP_PAUSE`] in the future.
|
||||
///
|
||||
/// Currently there is no macro analogous to `embassy_executor::main` for this executor;
|
||||
/// consequently one must define their entrypoint manually. Moveover, you must relinquish control
|
||||
/// of the `RTC` peripheral to the executor. This will typically look like
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use embassy_executor::Spawner;
|
||||
/// use embassy_stm32::low_power::Executor;
|
||||
/// use embassy_stm32::rtc::{Rtc, RtcConfig};
|
||||
/// use static_cell::make_static;
|
||||
///
|
||||
/// #[cortex_m_rt::entry]
|
||||
/// fn main() -> ! {
|
||||
/// Executor::take().run(|spawner| {
|
||||
/// unwrap!(spawner.spawn(async_main(spawner)));
|
||||
/// });
|
||||
/// }
|
||||
///
|
||||
/// #[embassy_executor::task]
|
||||
/// async fn async_main(spawner: Spawner) {
|
||||
/// // initialize the platform...
|
||||
/// let mut config = embassy_stm32::Config::default();
|
||||
/// let p = embassy_stm32::init(config);
|
||||
///
|
||||
/// // give the RTC to the executor...
|
||||
/// let mut rtc = Rtc::new(p.RTC, RtcConfig::default());
|
||||
/// let rtc = make_static!(rtc);
|
||||
/// embassy_stm32::low_power::stop_with_rtc(rtc);
|
||||
///
|
||||
/// // your application here...
|
||||
/// }
|
||||
/// ```
|
||||
use core::arch::asm;
|
||||
use core::marker::PhantomData;
|
||||
use core::sync::atomic::{compiler_fence, Ordering};
|
||||
@ -6,7 +53,6 @@ use cortex_m::peripheral::SCB;
|
||||
use embassy_executor::*;
|
||||
|
||||
use crate::interrupt;
|
||||
use crate::rcc::low_power_ready;
|
||||
use crate::time_driver::{get_driver, RtcDriver};
|
||||
|
||||
const THREAD_PENDER: usize = usize::MAX;
|
||||
@ -33,6 +79,21 @@ pub fn stop_with_rtc(rtc: &'static Rtc) {
|
||||
unsafe { EXECUTOR.as_mut().unwrap() }.stop_with_rtc(rtc)
|
||||
}
|
||||
|
||||
pub fn stop_ready(stop_mode: StopMode) -> bool {
|
||||
match unsafe { EXECUTOR.as_mut().unwrap() }.stop_mode() {
|
||||
Some(StopMode::Stop2) => true,
|
||||
Some(StopMode::Stop1) => stop_mode == StopMode::Stop1,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(PartialEq)]
|
||||
pub enum StopMode {
|
||||
Stop1,
|
||||
Stop2,
|
||||
}
|
||||
|
||||
/// Thread mode executor, using WFE/SEV.
|
||||
///
|
||||
/// This is the simplest and most common kind of executor. It runs on
|
||||
@ -53,7 +114,7 @@ pub struct Executor {
|
||||
impl Executor {
|
||||
/// Create a new Executor.
|
||||
pub fn take() -> &'static mut Self {
|
||||
unsafe {
|
||||
critical_section::with(|_| unsafe {
|
||||
assert!(EXECUTOR.is_none());
|
||||
|
||||
EXECUTOR = Some(Self {
|
||||
@ -64,7 +125,7 @@ impl Executor {
|
||||
});
|
||||
|
||||
EXECUTOR.as_mut().unwrap()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn on_wakeup_irq(&mut self) {
|
||||
@ -80,17 +141,39 @@ impl Executor {
|
||||
trace!("low power: stop with rtc configured");
|
||||
}
|
||||
|
||||
fn stop_mode(&self) -> Option<StopMode> {
|
||||
if unsafe { crate::rcc::REFCOUNT_STOP2 == 0 } && unsafe { crate::rcc::REFCOUNT_STOP1 == 0 } {
|
||||
Some(StopMode::Stop2)
|
||||
} else if unsafe { crate::rcc::REFCOUNT_STOP1 == 0 } {
|
||||
Some(StopMode::Stop1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_stop(&mut self, _stop_mode: StopMode) {
|
||||
// TODO: configure chip-specific settings for stop
|
||||
}
|
||||
|
||||
fn configure_pwr(&mut self) {
|
||||
self.scb.clear_sleepdeep();
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
if !low_power_ready() {
|
||||
let stop_mode = self.stop_mode();
|
||||
if stop_mode.is_none() {
|
||||
trace!("low power: not ready to stop");
|
||||
} else if self.time_driver.pause_time().is_err() {
|
||||
trace!("low power: failed to pause time");
|
||||
} else {
|
||||
trace!("low power: stop");
|
||||
let stop_mode = stop_mode.unwrap();
|
||||
match stop_mode {
|
||||
StopMode::Stop1 => trace!("low power: stop 1"),
|
||||
StopMode::Stop2 => trace!("low power: stop 2"),
|
||||
}
|
||||
self.configure_stop(stop_mode);
|
||||
|
||||
#[cfg(not(feature = "low-power-debug-with-sleep"))]
|
||||
self.scb.set_sleepdeep();
|
||||
}
|
||||
}
|
||||
|
@ -13,40 +13,77 @@ pub enum OpAmpGain {
|
||||
Mul16,
|
||||
}
|
||||
|
||||
pub struct OpAmpOutput<'d, 'p, T: Instance, P: NonInvertingPin<T>> {
|
||||
_inner: &'d OpAmp<'d, T>,
|
||||
_input: &'p mut P,
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum OpAmpSpeed {
|
||||
Normal,
|
||||
HighSpeed,
|
||||
}
|
||||
|
||||
#[cfg(opamp_g4)]
|
||||
impl From<OpAmpSpeed> for crate::pac::opamp::vals::OpampCsrOpahsm {
|
||||
fn from(v: OpAmpSpeed) -> Self {
|
||||
match v {
|
||||
OpAmpSpeed::Normal => crate::pac::opamp::vals::OpampCsrOpahsm::NORMAL,
|
||||
OpAmpSpeed::HighSpeed => crate::pac::opamp::vals::OpampCsrOpahsm::HIGHSPEED,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// OpAmp external outputs, wired to a GPIO pad.
|
||||
///
|
||||
/// This struct can also be used as an ADC input.
|
||||
pub struct OpAmpOutput<'d, T: Instance> {
|
||||
_inner: &'d OpAmp<'d, T>,
|
||||
}
|
||||
|
||||
/// OpAmp internal outputs, wired directly to ADC inputs.
|
||||
///
|
||||
/// This struct can be used as an ADC input.
|
||||
pub struct OpAmpInternalOutput<'d, T: Instance> {
|
||||
_inner: &'d OpAmp<'d, T>,
|
||||
}
|
||||
|
||||
/// OpAmp driver.
|
||||
pub struct OpAmp<'d, T: Instance> {
|
||||
_inner: PeripheralRef<'d, T>,
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> OpAmp<'d, T> {
|
||||
pub fn new(opamp: impl Peripheral<P = T> + 'd) -> Self {
|
||||
Self::new_inner(opamp)
|
||||
}
|
||||
|
||||
fn new_inner(opamp: impl Peripheral<P = T> + 'd) -> Self {
|
||||
/// Create a new driver instance.
|
||||
///
|
||||
/// Does not enable the opamp, but does set the speed mode on some families.
|
||||
pub fn new(opamp: impl Peripheral<P = T> + 'd, #[cfg(opamp_g4)] speed: OpAmpSpeed) -> Self {
|
||||
into_ref!(opamp);
|
||||
|
||||
#[cfg(opamp_f3)]
|
||||
T::regs().opampcsr().modify(|w| {
|
||||
w.set_opampen(true);
|
||||
});
|
||||
|
||||
#[cfg(opamp_g4)]
|
||||
T::regs().opamp_csr().modify(|w| {
|
||||
w.set_opaen(true);
|
||||
w.set_opahsm(speed.into());
|
||||
});
|
||||
|
||||
Self { _inner: opamp }
|
||||
}
|
||||
|
||||
pub fn buffer_for<'a, 'b, P>(&'a mut self, pin: &'b mut P, gain: OpAmpGain) -> OpAmpOutput<'a, 'b, T, P>
|
||||
where
|
||||
P: NonInvertingPin<T>,
|
||||
{
|
||||
/// Configure the OpAmp as a buffer for the provided input pin,
|
||||
/// outputting to the provided output pin, and enable the opamp.
|
||||
///
|
||||
/// The input pin is configured for analogue mode but not consumed,
|
||||
/// so it may subsequently be used for ADC or comparator inputs.
|
||||
///
|
||||
/// The output pin is held within the returned [`OpAmpOutput`] struct,
|
||||
/// preventing it being used elsewhere. The `OpAmpOutput` can then be
|
||||
/// directly used as an ADC input. The opamp will be disabled when the
|
||||
/// [`OpAmpOutput`] is dropped.
|
||||
pub fn buffer_ext(
|
||||
&'d mut self,
|
||||
in_pin: impl Peripheral<P = impl NonInvertingPin<T> + crate::gpio::sealed::Pin>,
|
||||
out_pin: impl Peripheral<P = impl OutputPin<T> + crate::gpio::sealed::Pin> + 'd,
|
||||
gain: OpAmpGain,
|
||||
) -> OpAmpOutput<'d, T> {
|
||||
into_ref!(in_pin);
|
||||
into_ref!(out_pin);
|
||||
in_pin.set_as_analog();
|
||||
out_pin.set_as_analog();
|
||||
|
||||
let (vm_sel, pga_gain) = match gain {
|
||||
OpAmpGain::Mul1 => (0b11, 0b00),
|
||||
OpAmpGain::Mul2 => (0b10, 0b00),
|
||||
@ -57,24 +94,89 @@ impl<'d, T: Instance> OpAmp<'d, T> {
|
||||
|
||||
#[cfg(opamp_f3)]
|
||||
T::regs().opampcsr().modify(|w| {
|
||||
w.set_vp_sel(pin.channel());
|
||||
w.set_vp_sel(in_pin.channel());
|
||||
w.set_vm_sel(vm_sel);
|
||||
w.set_pga_gain(pga_gain);
|
||||
w.set_opampen(true);
|
||||
});
|
||||
|
||||
#[cfg(opamp_g4)]
|
||||
T::regs().opamp_csr().modify(|w| {
|
||||
use crate::pac::opamp::vals::*;
|
||||
|
||||
w.set_vp_sel(OpampCsrVpSel::from_bits(in_pin.channel()));
|
||||
w.set_vm_sel(OpampCsrVmSel::from_bits(vm_sel));
|
||||
w.set_pga_gain(OpampCsrPgaGain::from_bits(pga_gain));
|
||||
w.set_opaintoen(OpampCsrOpaintoen::OUTPUTPIN);
|
||||
w.set_opaen(true);
|
||||
});
|
||||
|
||||
OpAmpOutput { _inner: self }
|
||||
}
|
||||
|
||||
/// Configure the OpAmp as a buffer for the provided input pin,
|
||||
/// with the output only used internally, and enable the opamp.
|
||||
///
|
||||
/// The input pin is configured for analogue mode but not consumed,
|
||||
/// so it may be subsequently used for ADC or comparator inputs.
|
||||
///
|
||||
/// The returned `OpAmpInternalOutput` struct may be used as an ADC input.
|
||||
/// The opamp output will be disabled when it is dropped.
|
||||
#[cfg(opamp_g4)]
|
||||
pub fn buffer_int(
|
||||
&'d mut self,
|
||||
pin: impl Peripheral<P = impl NonInvertingPin<T> + crate::gpio::sealed::Pin>,
|
||||
gain: OpAmpGain,
|
||||
) -> OpAmpInternalOutput<'d, T> {
|
||||
into_ref!(pin);
|
||||
pin.set_as_analog();
|
||||
|
||||
let (vm_sel, pga_gain) = match gain {
|
||||
OpAmpGain::Mul1 => (0b11, 0b00),
|
||||
OpAmpGain::Mul2 => (0b10, 0b00),
|
||||
OpAmpGain::Mul4 => (0b10, 0b01),
|
||||
OpAmpGain::Mul8 => (0b10, 0b10),
|
||||
OpAmpGain::Mul16 => (0b10, 0b11),
|
||||
};
|
||||
|
||||
T::regs().opamp_csr().modify(|w| {
|
||||
use crate::pac::opamp::vals::*;
|
||||
w.set_vp_sel(OpampCsrVpSel::from_bits(pin.channel()));
|
||||
w.set_vm_sel(OpampCsrVmSel::from_bits(vm_sel));
|
||||
w.set_pga_gain(OpampCsrPgaGain::from_bits(pga_gain));
|
||||
w.set_opaintoen(OpampCsrOpaintoen::ADCCHANNEL);
|
||||
w.set_opaen(true);
|
||||
});
|
||||
|
||||
OpAmpOutput {
|
||||
_inner: self,
|
||||
_input: pin,
|
||||
}
|
||||
OpAmpInternalOutput { _inner: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> Drop for OpAmpOutput<'d, T> {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(opamp_f3)]
|
||||
T::regs().opampcsr().modify(|w| {
|
||||
w.set_opampen(false);
|
||||
});
|
||||
|
||||
#[cfg(opamp_g4)]
|
||||
T::regs().opamp_csr().modify(|w| {
|
||||
w.set_opaen(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> Drop for OpAmpInternalOutput<'d, T> {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(opamp_f3)]
|
||||
T::regs().opampcsr().modify(|w| {
|
||||
w.set_opampen(false);
|
||||
});
|
||||
|
||||
#[cfg(opamp_g4)]
|
||||
T::regs().opamp_csr().modify(|w| {
|
||||
w.set_opaen(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,27 +194,28 @@ pub(crate) mod sealed {
|
||||
pub trait InvertingPin<T: Instance> {
|
||||
fn channel(&self) -> u8;
|
||||
}
|
||||
|
||||
pub trait OutputPin<T: Instance> {}
|
||||
}
|
||||
|
||||
pub trait NonInvertingPin<T: Instance>: sealed::NonInvertingPin<T> {}
|
||||
|
||||
pub trait InvertingPin<T: Instance>: sealed::InvertingPin<T> {}
|
||||
pub trait OutputPin<T: Instance>: sealed::OutputPin<T> {}
|
||||
|
||||
#[cfg(opamp_f3)]
|
||||
macro_rules! impl_opamp_output {
|
||||
macro_rules! impl_opamp_external_output {
|
||||
($inst:ident, $adc:ident, $ch:expr) => {
|
||||
foreach_adc!(
|
||||
($adc, $common_inst:ident, $adc_clock:ident) => {
|
||||
impl<'d, 'p, P: NonInvertingPin<crate::peripherals::$inst>> crate::adc::sealed::AdcPin<crate::peripherals::$adc>
|
||||
for OpAmpOutput<'d, 'p, crate::peripherals::$inst, P>
|
||||
impl<'d> crate::adc::sealed::AdcPin<crate::peripherals::$adc>
|
||||
for OpAmpOutput<'d, crate::peripherals::$inst>
|
||||
{
|
||||
fn channel(&self) -> u8 {
|
||||
$ch
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, 'p, P: NonInvertingPin<crate::peripherals::$inst>> crate::adc::AdcPin<crate::peripherals::$adc>
|
||||
for OpAmpOutput<'d, 'p, crate::peripherals::$inst, P>
|
||||
impl<'d> crate::adc::AdcPin<crate::peripherals::$adc>
|
||||
for OpAmpOutput<'d, crate::peripherals::$inst>
|
||||
{
|
||||
}
|
||||
};
|
||||
@ -120,19 +223,79 @@ macro_rules! impl_opamp_output {
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(opamp_f3)]
|
||||
foreach_peripheral!(
|
||||
(opamp, OPAMP1) => {
|
||||
impl_opamp_output!(OPAMP1, ADC1, 3);
|
||||
impl_opamp_external_output!(OPAMP1, ADC1, 3);
|
||||
};
|
||||
(opamp, OPAMP2) => {
|
||||
impl_opamp_output!(OPAMP2, ADC2, 3);
|
||||
impl_opamp_external_output!(OPAMP2, ADC2, 3);
|
||||
};
|
||||
(opamp, OPAMP3) => {
|
||||
impl_opamp_output!(OPAMP3, ADC3, 1);
|
||||
impl_opamp_external_output!(OPAMP3, ADC3, 1);
|
||||
};
|
||||
// OPAMP4 only in STM32G4 Cat 3 devices
|
||||
(opamp, OPAMP4) => {
|
||||
impl_opamp_output!(OPAMP4, ADC4, 3);
|
||||
impl_opamp_external_output!(OPAMP4, ADC4, 3);
|
||||
};
|
||||
// OPAMP5 only in STM32G4 Cat 3 devices
|
||||
(opamp, OPAMP5) => {
|
||||
impl_opamp_external_output!(OPAMP5, ADC5, 1);
|
||||
};
|
||||
// OPAMP6 only in STM32G4 Cat 3/4 devices
|
||||
(opamp, OPAMP6) => {
|
||||
impl_opamp_external_output!(OPAMP6, ADC1, 14);
|
||||
};
|
||||
);
|
||||
|
||||
#[cfg(opamp_g4)]
|
||||
macro_rules! impl_opamp_internal_output {
|
||||
($inst:ident, $adc:ident, $ch:expr) => {
|
||||
foreach_adc!(
|
||||
($adc, $common_inst:ident, $adc_clock:ident) => {
|
||||
impl<'d> crate::adc::sealed::AdcPin<crate::peripherals::$adc>
|
||||
for OpAmpInternalOutput<'d, crate::peripherals::$inst>
|
||||
{
|
||||
fn channel(&self) -> u8 {
|
||||
$ch
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> crate::adc::AdcPin<crate::peripherals::$adc>
|
||||
for OpAmpInternalOutput<'d, crate::peripherals::$inst>
|
||||
{
|
||||
}
|
||||
};
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(opamp_g4)]
|
||||
foreach_peripheral!(
|
||||
(opamp, OPAMP1) => {
|
||||
impl_opamp_internal_output!(OPAMP1, ADC1, 13);
|
||||
};
|
||||
(opamp, OPAMP2) => {
|
||||
impl_opamp_internal_output!(OPAMP2, ADC2, 16);
|
||||
};
|
||||
(opamp, OPAMP3) => {
|
||||
impl_opamp_internal_output!(OPAMP3, ADC2, 18);
|
||||
// Only in Cat 3/4 devices
|
||||
impl_opamp_internal_output!(OPAMP3, ADC3, 13);
|
||||
};
|
||||
// OPAMP4 only in Cat 3 devices
|
||||
(opamp, OPAMP4) => {
|
||||
impl_opamp_internal_output!(OPAMP4, ADC5, 5);
|
||||
};
|
||||
// OPAMP5 only in Cat 3 devices
|
||||
(opamp, OPAMP5) => {
|
||||
impl_opamp_internal_output!(OPAMP5, ADC5, 3);
|
||||
};
|
||||
// OPAMP6 only in Cat 3/4 devices
|
||||
(opamp, OPAMP6) => {
|
||||
// Only in Cat 3 devices
|
||||
impl_opamp_internal_output!(OPAMP6, ADC4, 17);
|
||||
// Only in Cat 4 devices
|
||||
impl_opamp_internal_output!(OPAMP6, ADC3, 17);
|
||||
};
|
||||
);
|
||||
|
||||
@ -145,13 +308,12 @@ foreach_peripheral! {
|
||||
}
|
||||
|
||||
impl Instance for crate::peripherals::$inst {
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! impl_opamp_pin {
|
||||
macro_rules! impl_opamp_vp_pin {
|
||||
($inst:ident, $pin:ident, $ch:expr) => {
|
||||
impl crate::opamp::NonInvertingPin<peripherals::$inst> for crate::peripherals::$pin {}
|
||||
impl crate::opamp::sealed::NonInvertingPin<peripherals::$inst> for crate::peripherals::$pin {
|
||||
@ -161,3 +323,11 @@ macro_rules! impl_opamp_pin {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! impl_opamp_vout_pin {
|
||||
($inst:ident, $pin:ident) => {
|
||||
impl crate::opamp::OutputPin<peripherals::$inst> for crate::peripherals::$pin {}
|
||||
impl crate::opamp::sealed::OutputPin<peripherals::$inst> for crate::peripherals::$pin {}
|
||||
};
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
use stm32_metapac::flash::vals::Latency;
|
||||
|
||||
pub use crate::pac::rcc::vals::{
|
||||
Hpre as AHBPrescaler, Pllm as PllPreDiv, Plln as PllMul, Pllp, Pllq, Pllr, Pllsrc as PllSource,
|
||||
Ppre as APBPrescaler, Sw as Sysclk,
|
||||
Hpre as AHBPrescaler, Pllm as PllPreDiv, Plln as PllMul, Pllp as PllPDiv, Pllq as PllQDiv, Pllr as PllRDiv,
|
||||
Pllsrc as PllSource, Ppre as APBPrescaler, Sw as Sysclk,
|
||||
};
|
||||
#[cfg(any(stm32f4, stm32f7))]
|
||||
use crate::pac::PWR;
|
||||
use crate::pac::{FLASH, RCC};
|
||||
use crate::rcc::{set_freqs, Clocks};
|
||||
use crate::time::Hertz;
|
||||
@ -48,11 +52,27 @@ pub struct Pll {
|
||||
pub mul: PllMul,
|
||||
|
||||
/// PLL P division factor. If None, PLL P output is disabled.
|
||||
pub divp: Option<Pllp>,
|
||||
pub divp: Option<PllPDiv>,
|
||||
/// PLL Q division factor. If None, PLL Q output is disabled.
|
||||
pub divq: Option<Pllq>,
|
||||
pub divq: Option<PllQDiv>,
|
||||
/// PLL R division factor. If None, PLL R output is disabled.
|
||||
pub divr: Option<Pllr>,
|
||||
pub divr: Option<PllRDiv>,
|
||||
}
|
||||
|
||||
/// Voltage range of the power supply used.
|
||||
///
|
||||
/// Used to calculate flash waitstates. See
|
||||
/// RM0033 - Table 3. Number of wait states according to Cortex®-M3 clock frequency
|
||||
#[cfg(stm32f2)]
|
||||
pub enum VoltageScale {
|
||||
/// 2.7 to 3.6 V
|
||||
Range0,
|
||||
/// 2.4 to 2.7 V
|
||||
Range1,
|
||||
/// 2.1 to 2.4 V
|
||||
Range2,
|
||||
/// 1.8 to 2.1 V
|
||||
Range3,
|
||||
}
|
||||
|
||||
/// Configuration of the core clocks
|
||||
@ -65,7 +85,7 @@ pub struct Config {
|
||||
pub pll_src: PllSource,
|
||||
|
||||
pub pll: Option<Pll>,
|
||||
#[cfg(any(all(stm32f4, not(stm32f410)), stm32f7))]
|
||||
#[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))]
|
||||
pub plli2s: Option<Pll>,
|
||||
#[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))]
|
||||
pub pllsai: Option<Pll>,
|
||||
@ -75,6 +95,9 @@ pub struct Config {
|
||||
pub apb2_pre: APBPrescaler,
|
||||
|
||||
pub ls: super::LsConfig,
|
||||
|
||||
#[cfg(stm32f2)]
|
||||
pub voltage: VoltageScale,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@ -85,7 +108,7 @@ impl Default for Config {
|
||||
sys: Sysclk::HSI,
|
||||
pll_src: PllSource::HSI,
|
||||
pll: None,
|
||||
#[cfg(any(all(stm32f4, not(stm32f410)), stm32f7))]
|
||||
#[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))]
|
||||
plli2s: None,
|
||||
#[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))]
|
||||
pllsai: None,
|
||||
@ -95,17 +118,24 @@ impl Default for Config {
|
||||
apb2_pre: APBPrescaler::DIV1,
|
||||
|
||||
ls: Default::default(),
|
||||
|
||||
#[cfg(stm32f2)]
|
||||
voltage: VoltageScale::Range3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn init(config: Config) {
|
||||
// set VOS to SCALE1, if use PLL
|
||||
// TODO: check real clock speed before set VOS
|
||||
#[cfg(any(stm32f4, stm32f7))]
|
||||
if config.pll.is_some() {
|
||||
PWR.cr1().modify(|w| w.set_vos(crate::pac::pwr::vals::Vos::SCALE1));
|
||||
}
|
||||
|
||||
// always enable overdrive for now. Make it configurable in the future.
|
||||
#[cfg(not(any(
|
||||
stm32f401, stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f405, stm32f407, stm32f415, stm32f417
|
||||
)))]
|
||||
#[cfg(any(stm32f446, stm32f4x9, stm32f427, stm32f437, stm32f7))]
|
||||
{
|
||||
use crate::pac::PWR;
|
||||
PWR.cr1().modify(|w| w.set_oden(true));
|
||||
while !PWR.csr1().read().odrdy() {}
|
||||
|
||||
@ -152,9 +182,9 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
source: config.pll_src,
|
||||
};
|
||||
let pll = init_pll(PllInstance::Pll, config.pll, &pll_input);
|
||||
#[cfg(any(all(stm32f4, not(any(stm32f410, stm32f429))), stm32f7))]
|
||||
#[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))]
|
||||
let _plli2s = init_pll(PllInstance::Plli2s, config.plli2s, &pll_input);
|
||||
#[cfg(all(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7), not(stm32f429)))]
|
||||
#[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))]
|
||||
let _pllsai = init_pll(PllInstance::Pllsai, config.pllsai, &pll_input);
|
||||
|
||||
// Configure sysclk
|
||||
@ -176,7 +206,48 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
|
||||
let rtc = config.ls.init();
|
||||
|
||||
flash_setup(hclk);
|
||||
#[cfg(stm32f2)]
|
||||
let latency = match (config.voltage, hclk.0) {
|
||||
(VoltageScale::Range3, ..=16_000_000) => Latency::WS0,
|
||||
(VoltageScale::Range3, ..=32_000_000) => Latency::WS1,
|
||||
(VoltageScale::Range3, ..=48_000_000) => Latency::WS2,
|
||||
(VoltageScale::Range3, ..=64_000_000) => Latency::WS3,
|
||||
(VoltageScale::Range3, ..=80_000_000) => Latency::WS4,
|
||||
(VoltageScale::Range3, ..=96_000_000) => Latency::WS5,
|
||||
(VoltageScale::Range3, ..=112_000_000) => Latency::WS6,
|
||||
(VoltageScale::Range3, ..=120_000_000) => Latency::WS7,
|
||||
(VoltageScale::Range2, ..=18_000_000) => Latency::WS0,
|
||||
(VoltageScale::Range2, ..=36_000_000) => Latency::WS1,
|
||||
(VoltageScale::Range2, ..=54_000_000) => Latency::WS2,
|
||||
(VoltageScale::Range2, ..=72_000_000) => Latency::WS3,
|
||||
(VoltageScale::Range2, ..=90_000_000) => Latency::WS4,
|
||||
(VoltageScale::Range2, ..=108_000_000) => Latency::WS5,
|
||||
(VoltageScale::Range2, ..=120_000_000) => Latency::WS6,
|
||||
(VoltageScale::Range1, ..=24_000_000) => Latency::WS0,
|
||||
(VoltageScale::Range1, ..=48_000_000) => Latency::WS1,
|
||||
(VoltageScale::Range1, ..=72_000_000) => Latency::WS2,
|
||||
(VoltageScale::Range1, ..=96_000_000) => Latency::WS3,
|
||||
(VoltageScale::Range1, ..=120_000_000) => Latency::WS4,
|
||||
(VoltageScale::Range0, ..=30_000_000) => Latency::WS0,
|
||||
(VoltageScale::Range0, ..=60_000_000) => Latency::WS1,
|
||||
(VoltageScale::Range0, ..=90_000_000) => Latency::WS2,
|
||||
(VoltageScale::Range0, ..=120_000_000) => Latency::WS3,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
#[cfg(any(stm32f4, stm32f7))]
|
||||
let latency = {
|
||||
// Be conservative with voltage ranges
|
||||
const FLASH_LATENCY_STEP: u32 = 30_000_000;
|
||||
|
||||
let latency = (hclk.0 - 1) / FLASH_LATENCY_STEP;
|
||||
debug!("flash: latency={}", latency);
|
||||
|
||||
Latency::from_bits(latency as u8)
|
||||
};
|
||||
|
||||
FLASH.acr().write(|w| w.set_latency(latency));
|
||||
while FLASH.acr().read().latency() != latency {}
|
||||
|
||||
RCC.cfgr().modify(|w| {
|
||||
w.set_sw(config.sys);
|
||||
@ -197,25 +268,15 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
pclk2_tim,
|
||||
rtc,
|
||||
pll1_q: pll.q,
|
||||
#[cfg(all(rcc_f4, not(any(stm32f410, stm32f429))))]
|
||||
#[cfg(all(rcc_f4, not(stm32f410)))]
|
||||
plli2s1_q: _plli2s.q,
|
||||
#[cfg(all(rcc_f4, not(any(stm32f410, stm32f429))))]
|
||||
#[cfg(all(rcc_f4, not(stm32f410)))]
|
||||
plli2s1_r: _plli2s.r,
|
||||
|
||||
#[cfg(stm32f429)]
|
||||
plli2s1_q: None,
|
||||
#[cfg(stm32f429)]
|
||||
plli2s1_r: None,
|
||||
|
||||
#[cfg(any(stm32f427, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))]
|
||||
#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))]
|
||||
pllsai1_q: _pllsai.q,
|
||||
#[cfg(any(stm32f427, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))]
|
||||
#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))]
|
||||
pllsai1_r: _pllsai.r,
|
||||
|
||||
#[cfg(stm32f429)]
|
||||
pllsai1_q: None,
|
||||
#[cfg(stm32f429)]
|
||||
pllsai1_r: None,
|
||||
});
|
||||
}
|
||||
|
||||
@ -233,11 +294,10 @@ struct PllOutput {
|
||||
r: Option<Hertz>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
enum PllInstance {
|
||||
Pll,
|
||||
#[cfg(any(all(stm32f4, not(stm32f410)), stm32f7))]
|
||||
#[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))]
|
||||
Plli2s,
|
||||
#[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))]
|
||||
Pllsai,
|
||||
@ -249,7 +309,7 @@ fn pll_enable(instance: PllInstance, enabled: bool) {
|
||||
RCC.cr().modify(|w| w.set_pllon(enabled));
|
||||
while RCC.cr().read().pllrdy() != enabled {}
|
||||
}
|
||||
#[cfg(any(all(stm32f4, not(stm32f410)), stm32f7))]
|
||||
#[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))]
|
||||
PllInstance::Plli2s => {
|
||||
RCC.cr().modify(|w| w.set_plli2son(enabled));
|
||||
while RCC.cr().read().plli2srdy() != enabled {}
|
||||
@ -280,6 +340,18 @@ fn init_pll(instance: PllInstance, config: Option<Pll>, input: &PllInput) -> Pll
|
||||
let vco_freq = in_freq * pll.mul;
|
||||
assert!(max::PLL_VCO.contains(&vco_freq));
|
||||
|
||||
// stm32f2 plls are like swiss cheese
|
||||
#[cfg(stm32f2)]
|
||||
match instance {
|
||||
PllInstance::Pll => {
|
||||
assert!(pll.divr.is_none());
|
||||
}
|
||||
PllInstance::Plli2s => {
|
||||
assert!(pll.divp.is_none());
|
||||
assert!(pll.divq.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
let p = pll.divp.map(|div| vco_freq / div);
|
||||
let q = pll.divq.map(|div| vco_freq / div);
|
||||
let r = pll.divr.map(|div| vco_freq / div);
|
||||
@ -293,6 +365,7 @@ fn init_pll(instance: PllInstance, config: Option<Pll>, input: &PllInput) -> Pll
|
||||
if let Some(divq) = pll.divq {
|
||||
$w.set_pllq(divq);
|
||||
}
|
||||
#[cfg(any(stm32f4, stm32f7))]
|
||||
if let Some(divr) = pll.divr {
|
||||
$w.set_pllr(divr);
|
||||
}
|
||||
@ -309,6 +382,12 @@ fn init_pll(instance: PllInstance, config: Option<Pll>, input: &PllInput) -> Pll
|
||||
PllInstance::Plli2s => RCC.plli2scfgr().write(|w| {
|
||||
write_fields!(w);
|
||||
}),
|
||||
#[cfg(stm32f2)]
|
||||
PllInstance::Plli2s => RCC.plli2scfgr().write(|w| {
|
||||
if let Some(divr) = pll.divr {
|
||||
w.set_pllr(divr);
|
||||
}
|
||||
}),
|
||||
#[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))]
|
||||
PllInstance::Pllsai => RCC.pllsaicfgr().write(|w| {
|
||||
write_fields!(w);
|
||||
@ -321,22 +400,6 @@ fn init_pll(instance: PllInstance, config: Option<Pll>, input: &PllInput) -> Pll
|
||||
PllOutput { p, q, r }
|
||||
}
|
||||
|
||||
fn flash_setup(clk: Hertz) {
|
||||
use crate::pac::flash::vals::Latency;
|
||||
|
||||
// Be conservative with voltage ranges
|
||||
const FLASH_LATENCY_STEP: u32 = 30_000_000;
|
||||
|
||||
let latency = (clk.0 - 1) / FLASH_LATENCY_STEP;
|
||||
debug!("flash: latency={}", latency);
|
||||
|
||||
let latency = Latency::from_bits(latency as u8);
|
||||
FLASH.acr().write(|w| {
|
||||
w.set_latency(latency);
|
||||
});
|
||||
while FLASH.acr().read().latency() != latency {}
|
||||
}
|
||||
|
||||
#[cfg(stm32f7)]
|
||||
mod max {
|
||||
use core::ops::RangeInclusive;
|
||||
@ -385,3 +448,22 @@ mod max {
|
||||
pub(crate) const PLL_IN: RangeInclusive<Hertz> = Hertz(1_000_000)..=Hertz(2_100_000);
|
||||
pub(crate) const PLL_VCO: RangeInclusive<Hertz> = Hertz(100_000_000)..=Hertz(432_000_000);
|
||||
}
|
||||
|
||||
#[cfg(stm32f2)]
|
||||
mod max {
|
||||
use core::ops::RangeInclusive;
|
||||
|
||||
use crate::time::Hertz;
|
||||
|
||||
pub(crate) const HSE_OSC: RangeInclusive<Hertz> = Hertz(4_000_000)..=Hertz(26_000_000);
|
||||
pub(crate) const HSE_BYP: RangeInclusive<Hertz> = Hertz(1_000_000)..=Hertz(26_000_000);
|
||||
|
||||
pub(crate) const SYSCLK: RangeInclusive<Hertz> = Hertz(0)..=Hertz(120_000_000);
|
||||
|
||||
pub(crate) const HCLK: RangeInclusive<Hertz> = Hertz(0)..=Hertz(SYSCLK.end().0);
|
||||
pub(crate) const PCLK1: RangeInclusive<Hertz> = Hertz(0)..=Hertz(SYSCLK.end().0 / 4);
|
||||
pub(crate) const PCLK2: RangeInclusive<Hertz> = Hertz(0)..=Hertz(SYSCLK.end().0 / 2);
|
||||
|
||||
pub(crate) const PLL_IN: RangeInclusive<Hertz> = Hertz(0_950_000)..=Hertz(2_100_000);
|
||||
pub(crate) const PLL_VCO: RangeInclusive<Hertz> = Hertz(192_000_000)..=Hertz(432_000_000);
|
||||
}
|
@ -1,320 +0,0 @@
|
||||
use crate::pac::flash::vals::Latency;
|
||||
use crate::pac::rcc::vals::Sw;
|
||||
pub use crate::pac::rcc::vals::{
|
||||
Hpre as AHBPrescaler, Pllm as PLLPreDiv, Plln as PLLMul, Pllp as PLLPDiv, Pllq as PLLQDiv, Pllsrc as PLLSrc,
|
||||
Ppre as APBPrescaler,
|
||||
};
|
||||
use crate::pac::{FLASH, RCC};
|
||||
use crate::rcc::{set_freqs, Clocks};
|
||||
use crate::time::Hertz;
|
||||
|
||||
/// HSI speed
|
||||
pub const HSI_FREQ: Hertz = Hertz(16_000_000);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct HSEConfig {
|
||||
pub frequency: Hertz,
|
||||
pub source: HSESrc,
|
||||
}
|
||||
|
||||
/// System clock mux source
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ClockSrc {
|
||||
HSE,
|
||||
HSI,
|
||||
PLL,
|
||||
}
|
||||
|
||||
/// HSE clock source
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum HSESrc {
|
||||
/// Crystal/ceramic resonator
|
||||
Crystal,
|
||||
/// External clock source, HSE bypassed
|
||||
Bypass,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct PLLConfig {
|
||||
pub pre_div: PLLPreDiv,
|
||||
pub mul: PLLMul,
|
||||
pub p_div: PLLPDiv,
|
||||
pub q_div: PLLQDiv,
|
||||
}
|
||||
|
||||
impl Default for PLLConfig {
|
||||
fn default() -> Self {
|
||||
PLLConfig {
|
||||
pre_div: PLLPreDiv::DIV16,
|
||||
mul: PLLMul::MUL192,
|
||||
p_div: PLLPDiv::DIV2,
|
||||
q_div: PLLQDiv::DIV4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PLLConfig {
|
||||
pub fn clocks(&self, src_freq: Hertz) -> PLLClocks {
|
||||
let in_freq = src_freq / self.pre_div;
|
||||
let vco_freq = src_freq / self.pre_div * self.mul;
|
||||
let main_freq = vco_freq / self.p_div;
|
||||
let pll48_freq = vco_freq / self.q_div;
|
||||
PLLClocks {
|
||||
in_freq,
|
||||
vco_freq,
|
||||
main_freq,
|
||||
pll48_freq,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub struct PLLClocks {
|
||||
pub in_freq: Hertz,
|
||||
pub vco_freq: Hertz,
|
||||
pub main_freq: Hertz,
|
||||
pub pll48_freq: Hertz,
|
||||
}
|
||||
|
||||
/// Voltage range of the power supply used.
|
||||
///
|
||||
/// Used to calculate flash waitstates. See
|
||||
/// RM0033 - Table 3. Number of wait states according to Cortex®-M3 clock frequency
|
||||
pub enum VoltageScale {
|
||||
/// 2.7 to 3.6 V
|
||||
Range0,
|
||||
/// 2.4 to 2.7 V
|
||||
Range1,
|
||||
/// 2.1 to 2.4 V
|
||||
Range2,
|
||||
/// 1.8 to 2.1 V
|
||||
Range3,
|
||||
}
|
||||
|
||||
impl VoltageScale {
|
||||
const fn wait_states(&self, ahb_freq: Hertz) -> Option<Latency> {
|
||||
let ahb_freq = ahb_freq.0;
|
||||
// Reference: RM0033 - Table 3. Number of wait states according to Cortex®-M3 clock
|
||||
// frequency
|
||||
match self {
|
||||
VoltageScale::Range3 => {
|
||||
if ahb_freq <= 16_000_000 {
|
||||
Some(Latency::WS0)
|
||||
} else if ahb_freq <= 32_000_000 {
|
||||
Some(Latency::WS1)
|
||||
} else if ahb_freq <= 48_000_000 {
|
||||
Some(Latency::WS2)
|
||||
} else if ahb_freq <= 64_000_000 {
|
||||
Some(Latency::WS3)
|
||||
} else if ahb_freq <= 80_000_000 {
|
||||
Some(Latency::WS4)
|
||||
} else if ahb_freq <= 96_000_000 {
|
||||
Some(Latency::WS5)
|
||||
} else if ahb_freq <= 112_000_000 {
|
||||
Some(Latency::WS6)
|
||||
} else if ahb_freq <= 120_000_000 {
|
||||
Some(Latency::WS7)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
VoltageScale::Range2 => {
|
||||
if ahb_freq <= 18_000_000 {
|
||||
Some(Latency::WS0)
|
||||
} else if ahb_freq <= 36_000_000 {
|
||||
Some(Latency::WS1)
|
||||
} else if ahb_freq <= 54_000_000 {
|
||||
Some(Latency::WS2)
|
||||
} else if ahb_freq <= 72_000_000 {
|
||||
Some(Latency::WS3)
|
||||
} else if ahb_freq <= 90_000_000 {
|
||||
Some(Latency::WS4)
|
||||
} else if ahb_freq <= 108_000_000 {
|
||||
Some(Latency::WS5)
|
||||
} else if ahb_freq <= 120_000_000 {
|
||||
Some(Latency::WS6)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
VoltageScale::Range1 => {
|
||||
if ahb_freq <= 24_000_000 {
|
||||
Some(Latency::WS0)
|
||||
} else if ahb_freq <= 48_000_000 {
|
||||
Some(Latency::WS1)
|
||||
} else if ahb_freq <= 72_000_000 {
|
||||
Some(Latency::WS2)
|
||||
} else if ahb_freq <= 96_000_000 {
|
||||
Some(Latency::WS3)
|
||||
} else if ahb_freq <= 120_000_000 {
|
||||
Some(Latency::WS4)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
VoltageScale::Range0 => {
|
||||
if ahb_freq <= 30_000_000 {
|
||||
Some(Latency::WS0)
|
||||
} else if ahb_freq <= 60_000_000 {
|
||||
Some(Latency::WS1)
|
||||
} else if ahb_freq <= 90_000_000 {
|
||||
Some(Latency::WS2)
|
||||
} else if ahb_freq <= 120_000_000 {
|
||||
Some(Latency::WS3)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Clocks configuration
|
||||
pub struct Config {
|
||||
pub hse: Option<HSEConfig>,
|
||||
pub hsi: bool,
|
||||
pub pll_mux: PLLSrc,
|
||||
pub pll: PLLConfig,
|
||||
pub mux: ClockSrc,
|
||||
pub voltage: VoltageScale,
|
||||
pub ahb_pre: AHBPrescaler,
|
||||
pub apb1_pre: APBPrescaler,
|
||||
pub apb2_pre: APBPrescaler,
|
||||
pub ls: super::LsConfig,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
#[inline]
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
hse: None,
|
||||
hsi: true,
|
||||
pll_mux: PLLSrc::HSI,
|
||||
pll: PLLConfig::default(),
|
||||
voltage: VoltageScale::Range3,
|
||||
mux: ClockSrc::HSI,
|
||||
ahb_pre: AHBPrescaler::DIV1,
|
||||
apb1_pre: APBPrescaler::DIV1,
|
||||
apb2_pre: APBPrescaler::DIV1,
|
||||
ls: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn init(config: Config) {
|
||||
// Make sure HSI is enabled
|
||||
RCC.cr().write(|w| w.set_hsion(true));
|
||||
while !RCC.cr().read().hsirdy() {}
|
||||
|
||||
if let Some(hse_config) = config.hse {
|
||||
RCC.cr().modify(|w| {
|
||||
w.set_hsebyp(match hse_config.source {
|
||||
HSESrc::Bypass => true,
|
||||
HSESrc::Crystal => false,
|
||||
});
|
||||
w.set_hseon(true)
|
||||
});
|
||||
while !RCC.cr().read().hserdy() {}
|
||||
}
|
||||
|
||||
let pll_src_freq = match config.pll_mux {
|
||||
PLLSrc::HSE => {
|
||||
let hse_config = config
|
||||
.hse
|
||||
.unwrap_or_else(|| panic!("HSE must be configured to be used as PLL input"));
|
||||
hse_config.frequency
|
||||
}
|
||||
PLLSrc::HSI => HSI_FREQ,
|
||||
};
|
||||
|
||||
// Reference: STM32F215xx/217xx datasheet Table 33. Main PLL characteristics
|
||||
let pll_clocks = config.pll.clocks(pll_src_freq);
|
||||
assert!(Hertz(950_000) <= pll_clocks.in_freq && pll_clocks.in_freq <= Hertz(2_100_000));
|
||||
assert!(Hertz(192_000_000) <= pll_clocks.vco_freq && pll_clocks.vco_freq <= Hertz(432_000_000));
|
||||
assert!(Hertz(24_000_000) <= pll_clocks.main_freq && pll_clocks.main_freq <= Hertz(120_000_000));
|
||||
// USB actually requires == 48 MHz, but other PLL48 peripherals are fine with <= 48MHz
|
||||
assert!(pll_clocks.pll48_freq <= Hertz(48_000_000));
|
||||
|
||||
RCC.pllcfgr().write(|w| {
|
||||
w.set_pllsrc(config.pll_mux);
|
||||
w.set_pllm(config.pll.pre_div);
|
||||
w.set_plln(config.pll.mul);
|
||||
w.set_pllp(config.pll.p_div);
|
||||
w.set_pllq(config.pll.q_div);
|
||||
});
|
||||
|
||||
let (sys_clk, sw) = match config.mux {
|
||||
ClockSrc::HSI => {
|
||||
assert!(config.hsi, "HSI must be enabled to be used as system clock");
|
||||
(HSI_FREQ, Sw::HSI)
|
||||
}
|
||||
ClockSrc::HSE => {
|
||||
let hse_config = config
|
||||
.hse
|
||||
.unwrap_or_else(|| panic!("HSE must be configured to be used as PLL input"));
|
||||
(hse_config.frequency, Sw::HSE)
|
||||
}
|
||||
ClockSrc::PLL => {
|
||||
RCC.cr().modify(|w| w.set_pllon(true));
|
||||
while !RCC.cr().read().pllrdy() {}
|
||||
(pll_clocks.main_freq, Sw::PLL1_P)
|
||||
}
|
||||
};
|
||||
// RM0033 Figure 9. Clock tree suggests max SYSCLK/HCLK is 168 MHz, but datasheet specifies PLL
|
||||
// max output to be 120 MHz, so there's no way to get higher frequencies
|
||||
assert!(sys_clk <= Hertz(120_000_000));
|
||||
|
||||
let ahb_freq = sys_clk / config.ahb_pre;
|
||||
// Reference: STM32F215xx/217xx datasheet Table 13. General operating conditions
|
||||
assert!(ahb_freq <= Hertz(120_000_000));
|
||||
|
||||
let (apb1_freq, apb1_tim_freq) = match config.apb1_pre {
|
||||
APBPrescaler::DIV1 => (ahb_freq, ahb_freq),
|
||||
pre => {
|
||||
let freq = ahb_freq / pre;
|
||||
(freq, Hertz(freq.0 * 2))
|
||||
}
|
||||
};
|
||||
// Reference: STM32F215xx/217xx datasheet Table 13. General operating conditions
|
||||
assert!(apb1_freq <= Hertz(30_000_000));
|
||||
|
||||
let (apb2_freq, apb2_tim_freq) = match config.apb2_pre {
|
||||
APBPrescaler::DIV1 => (ahb_freq, ahb_freq),
|
||||
pre => {
|
||||
let freq = ahb_freq / pre;
|
||||
(freq, Hertz(freq.0 * 2))
|
||||
}
|
||||
};
|
||||
// Reference: STM32F215xx/217xx datasheet Table 13. General operating conditions
|
||||
assert!(apb2_freq <= Hertz(60_000_000));
|
||||
|
||||
let flash_ws = unwrap!(config.voltage.wait_states(ahb_freq));
|
||||
FLASH.acr().modify(|w| w.set_latency(flash_ws));
|
||||
|
||||
RCC.cfgr().modify(|w| {
|
||||
w.set_sw(sw.into());
|
||||
w.set_hpre(config.ahb_pre);
|
||||
w.set_ppre1(config.apb1_pre);
|
||||
w.set_ppre2(config.apb2_pre);
|
||||
});
|
||||
while RCC.cfgr().read().sws().to_bits() != sw.to_bits() {}
|
||||
|
||||
// Turn off HSI to save power if we don't need it
|
||||
if !config.hsi {
|
||||
RCC.cr().modify(|w| w.set_hsion(false));
|
||||
}
|
||||
|
||||
let rtc = config.ls.init();
|
||||
|
||||
set_freqs(Clocks {
|
||||
sys: sys_clk,
|
||||
hclk1: ahb_freq,
|
||||
hclk2: ahb_freq,
|
||||
hclk3: ahb_freq,
|
||||
pclk1: apb1_freq,
|
||||
pclk1_tim: apb1_tim_freq,
|
||||
pclk2: apb2_freq,
|
||||
pclk2_tim: apb2_tim_freq,
|
||||
pll1_q: Some(pll_clocks.pll48_freq),
|
||||
rtc,
|
||||
});
|
||||
}
|
@ -346,10 +346,7 @@ fn calc_pll(config: &Config, Hertz(sysclk): Hertz) -> (Hertz, PllConfig) {
|
||||
None => {
|
||||
cfg_if::cfg_if! {
|
||||
// For some chips PREDIV is always two, and cannot be changed
|
||||
if #[cfg(any(
|
||||
stm32f302xd, stm32f302xe, stm32f303xd,
|
||||
stm32f303xe, stm32f398xe
|
||||
))] {
|
||||
if #[cfg(any(flashsize_d, flashsize_e))] {
|
||||
let (multiplier, divisor) = get_mul_div(sysclk, HSI_FREQ.0);
|
||||
(
|
||||
Hertz((HSI_FREQ.0 / divisor) * multiplier),
|
||||
|
@ -28,7 +28,7 @@ pub enum ClockSrc {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct PllConfig {
|
||||
/// The source from which the PLL receives a clock signal
|
||||
pub source: PllSrc,
|
||||
pub source: PllSource,
|
||||
/// The initial divisor of that clock signal
|
||||
pub m: Pllm,
|
||||
/// The PLL VCO multiplier, which must be in the range `8..=86`.
|
||||
@ -48,7 +48,7 @@ impl Default for PllConfig {
|
||||
fn default() -> PllConfig {
|
||||
// HSI / 1 * 8 / 2 = 64 MHz
|
||||
PllConfig {
|
||||
source: PllSrc::HSI,
|
||||
source: PllSource::HSI,
|
||||
m: Pllm::DIV1,
|
||||
n: Plln::MUL8,
|
||||
r: Pllr::DIV2,
|
||||
@ -59,7 +59,7 @@ impl Default for PllConfig {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum PllSrc {
|
||||
pub enum PllSource {
|
||||
HSI,
|
||||
HSE(Hertz),
|
||||
}
|
||||
@ -89,8 +89,8 @@ impl Default for Config {
|
||||
impl PllConfig {
|
||||
pub(crate) fn init(self) -> Hertz {
|
||||
let (src, input_freq) = match self.source {
|
||||
PllSrc::HSI => (vals::Pllsrc::HSI, HSI_FREQ),
|
||||
PllSrc::HSE(freq) => (vals::Pllsrc::HSE, freq),
|
||||
PllSource::HSI => (vals::Pllsrc::HSI, HSI_FREQ),
|
||||
PllSource::HSE(freq) => (vals::Pllsrc::HSE, freq),
|
||||
};
|
||||
|
||||
let m_freq = input_freq / self.m;
|
||||
@ -121,11 +121,11 @@ impl PllConfig {
|
||||
// > 3. Change the desired parameter.
|
||||
// Enable whichever clock source we're using, and wait for it to become ready
|
||||
match self.source {
|
||||
PllSrc::HSI => {
|
||||
PllSource::HSI => {
|
||||
RCC.cr().write(|w| w.set_hsion(true));
|
||||
while !RCC.cr().read().hsirdy() {}
|
||||
}
|
||||
PllSrc::HSE(_) => {
|
||||
PllSource::HSE(_) => {
|
||||
RCC.cr().write(|w| w.set_hseon(true));
|
||||
while !RCC.cr().read().hserdy() {}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ pub use crate::pac::rcc::vals::{
|
||||
Pllr as PllR, Ppre as APBPrescaler,
|
||||
};
|
||||
use crate::pac::{PWR, RCC};
|
||||
use crate::rcc::sealed::RccPeripheral;
|
||||
use crate::rcc::{set_freqs, Clocks};
|
||||
use crate::time::Hertz;
|
||||
|
||||
@ -24,16 +23,16 @@ pub enum ClockSrc {
|
||||
|
||||
/// PLL clock input source
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum PllSrc {
|
||||
pub enum PllSource {
|
||||
HSI,
|
||||
HSE(Hertz),
|
||||
}
|
||||
|
||||
impl Into<Pllsrc> for PllSrc {
|
||||
impl Into<Pllsrc> for PllSource {
|
||||
fn into(self) -> Pllsrc {
|
||||
match self {
|
||||
PllSrc::HSE(..) => Pllsrc::HSE,
|
||||
PllSrc::HSI => Pllsrc::HSI,
|
||||
PllSource::HSE(..) => Pllsrc::HSE,
|
||||
PllSource::HSI => Pllsrc::HSI,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -45,7 +44,7 @@ impl Into<Pllsrc> for PllSrc {
|
||||
/// frequency ranges for each of these settings.
|
||||
pub struct Pll {
|
||||
/// PLL Source clock selection.
|
||||
pub source: PllSrc,
|
||||
pub source: PllSource,
|
||||
|
||||
/// PLL pre-divider
|
||||
pub prediv_m: PllM,
|
||||
@ -67,23 +66,13 @@ pub struct Pll {
|
||||
pub enum Clock48MhzSrc {
|
||||
/// Use the High Speed Internal Oscillator. For USB usage, the CRS must be used to calibrate the
|
||||
/// oscillator to comply with the USB specification for oscillator tolerance.
|
||||
Hsi48(Option<CrsConfig>),
|
||||
Hsi48(super::Hsi48Config),
|
||||
/// Use the PLLQ output. The PLL must be configured to output a 48MHz clock. For USB usage the
|
||||
/// PLL needs to be using the HSE source to comply with the USB specification for oscillator
|
||||
/// tolerance.
|
||||
PllQ,
|
||||
}
|
||||
|
||||
/// Sets the sync source for the Clock Recovery System (CRS).
|
||||
pub enum CrsSyncSource {
|
||||
/// Use an external GPIO to sync the CRS.
|
||||
Gpio,
|
||||
/// Use the Low Speed External oscillator to sync the CRS.
|
||||
Lse,
|
||||
/// Use the USB SOF to sync the CRS.
|
||||
Usb,
|
||||
}
|
||||
|
||||
/// Clocks configutation
|
||||
pub struct Config {
|
||||
pub mux: ClockSrc,
|
||||
@ -102,12 +91,6 @@ pub struct Config {
|
||||
pub ls: super::LsConfig,
|
||||
}
|
||||
|
||||
/// Configuration for the Clock Recovery System (CRS) used to trim the HSI48 oscillator.
|
||||
pub struct CrsConfig {
|
||||
/// Sync source for the CRS.
|
||||
pub sync_src: CrsSyncSource,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
#[inline]
|
||||
fn default() -> Config {
|
||||
@ -118,7 +101,7 @@ impl Default for Config {
|
||||
apb2_pre: APBPrescaler::DIV1,
|
||||
low_power_run: false,
|
||||
pll: None,
|
||||
clock_48mhz_src: Some(Clock48MhzSrc::Hsi48(None)),
|
||||
clock_48mhz_src: Some(Clock48MhzSrc::Hsi48(Default::default())),
|
||||
adc12_clock_source: Adcsel::DISABLE,
|
||||
adc345_clock_source: Adcsel::DISABLE,
|
||||
ls: Default::default(),
|
||||
@ -135,13 +118,13 @@ pub struct PllFreq {
|
||||
pub(crate) unsafe fn init(config: Config) {
|
||||
let pll_freq = config.pll.map(|pll_config| {
|
||||
let src_freq = match pll_config.source {
|
||||
PllSrc::HSI => {
|
||||
PllSource::HSI => {
|
||||
RCC.cr().write(|w| w.set_hsion(true));
|
||||
while !RCC.cr().read().hsirdy() {}
|
||||
|
||||
HSI_FREQ
|
||||
}
|
||||
PllSrc::HSE(freq) => {
|
||||
PllSource::HSE(freq) => {
|
||||
RCC.cr().write(|w| w.set_hseon(true));
|
||||
while !RCC.cr().read().hserdy() {}
|
||||
freq
|
||||
@ -288,33 +271,8 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
|
||||
crate::pac::rcc::vals::Clk48sel::PLL1_Q
|
||||
}
|
||||
Clock48MhzSrc::Hsi48(crs_config) => {
|
||||
// Enable HSI48
|
||||
RCC.crrcr().modify(|w| w.set_hsi48on(true));
|
||||
// Wait for HSI48 to turn on
|
||||
while RCC.crrcr().read().hsi48rdy() == false {}
|
||||
|
||||
// Enable and setup CRS if needed
|
||||
if let Some(crs_config) = crs_config {
|
||||
crate::peripherals::CRS::enable_and_reset();
|
||||
|
||||
let sync_src = match crs_config.sync_src {
|
||||
CrsSyncSource::Gpio => crate::pac::crs::vals::Syncsrc::GPIO,
|
||||
CrsSyncSource::Lse => crate::pac::crs::vals::Syncsrc::LSE,
|
||||
CrsSyncSource::Usb => crate::pac::crs::vals::Syncsrc::USB,
|
||||
};
|
||||
|
||||
crate::pac::CRS.cfgr().modify(|w| {
|
||||
w.set_syncsrc(sync_src);
|
||||
});
|
||||
|
||||
// These are the correct settings for standard USB operation. If other settings
|
||||
// are needed there will need to be additional config options for the CRS.
|
||||
crate::pac::CRS.cr().modify(|w| {
|
||||
w.set_autotrimen(true);
|
||||
w.set_cen(true);
|
||||
});
|
||||
}
|
||||
Clock48MhzSrc::Hsi48(config) => {
|
||||
super::init_hsi48(config);
|
||||
crate::pac::rcc::vals::Clk48sel::HSI48
|
||||
}
|
||||
};
|
||||
|
@ -21,9 +21,6 @@ pub const HSI_FREQ: Hertz = Hertz(64_000_000);
|
||||
/// CSI speed
|
||||
pub const CSI_FREQ: Hertz = Hertz(4_000_000);
|
||||
|
||||
/// HSI48 speed
|
||||
pub const HSI48_FREQ: Hertz = Hertz(48_000_000);
|
||||
|
||||
const VCO_RANGE: RangeInclusive<Hertz> = Hertz(150_000_000)..=Hertz(420_000_000);
|
||||
#[cfg(any(stm32h5, pwr_h7rm0455))]
|
||||
const VCO_WIDE_RANGE: RangeInclusive<Hertz> = Hertz(128_000_000)..=Hertz(560_000_000);
|
||||
@ -126,7 +123,7 @@ pub struct Config {
|
||||
pub hsi: Option<HSIPrescaler>,
|
||||
pub hse: Option<Hse>,
|
||||
pub csi: bool,
|
||||
pub hsi48: bool,
|
||||
pub hsi48: Option<super::Hsi48Config>,
|
||||
pub sys: Sysclk,
|
||||
|
||||
pub pll1: Option<Pll>,
|
||||
@ -155,7 +152,7 @@ impl Default for Config {
|
||||
hsi: Some(HSIPrescaler::DIV1),
|
||||
hse: None,
|
||||
csi: false,
|
||||
hsi48: false,
|
||||
hsi48: Some(Default::default()),
|
||||
sys: Sysclk::HSI,
|
||||
pll1: None,
|
||||
pll2: None,
|
||||
@ -171,7 +168,12 @@ impl Default for Config {
|
||||
apb4_pre: APBPrescaler::DIV1,
|
||||
|
||||
per_clock_source: PerClockSource::HSI,
|
||||
adc_clock_source: AdcClockSource::from_bits(0), // PLL2_P on H7, HCLK on H5
|
||||
|
||||
#[cfg(stm32h5)]
|
||||
adc_clock_source: AdcClockSource::HCLK1,
|
||||
#[cfg(stm32h7)]
|
||||
adc_clock_source: AdcClockSource::PER,
|
||||
|
||||
timer_prescaler: TimerPrescaler::DefaultX2,
|
||||
voltage_scale: VoltageScale::Scale0,
|
||||
ls: Default::default(),
|
||||
@ -301,14 +303,7 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
};
|
||||
|
||||
// Configure HSI48.
|
||||
RCC.cr().modify(|w| w.set_hsi48on(config.hsi48));
|
||||
let _hsi48 = match config.hsi48 {
|
||||
false => None,
|
||||
true => {
|
||||
while !RCC.cr().read().hsi48rdy() {}
|
||||
Some(CSI_FREQ)
|
||||
}
|
||||
};
|
||||
let _hsi48 = config.hsi48.map(super::init_hsi48);
|
||||
|
||||
// Configure CSI.
|
||||
RCC.cr().modify(|w| w.set_csion(config.csi));
|
||||
|
62
embassy-stm32/src/rcc/hsi48.rs
Normal file
62
embassy-stm32/src/rcc/hsi48.rs
Normal file
@ -0,0 +1,62 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use crate::pac::crs::vals::Syncsrc;
|
||||
use crate::pac::{CRS, RCC};
|
||||
use crate::rcc::sealed::RccPeripheral;
|
||||
use crate::time::Hertz;
|
||||
|
||||
/// HSI48 speed
|
||||
pub const HSI48_FREQ: Hertz = Hertz(48_000_000);
|
||||
|
||||
/// Configuration for the HSI48 clock
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Hsi48Config {
|
||||
/// Enable CRS Sync from USB Start Of Frame (SOF) events.
|
||||
/// Required if HSI48 is going to be used as USB clock.
|
||||
///
|
||||
/// Other use cases of CRS are not supported yet.
|
||||
pub sync_from_usb: bool,
|
||||
}
|
||||
|
||||
impl Default for Hsi48Config {
|
||||
fn default() -> Self {
|
||||
Self { sync_from_usb: false }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn init_hsi48(config: Hsi48Config) -> Hertz {
|
||||
// Enable VREFINT reference for HSI48 oscillator
|
||||
#[cfg(stm32l0)]
|
||||
crate::pac::SYSCFG.cfgr3().modify(|w| {
|
||||
w.set_enref_hsi48(true);
|
||||
w.set_en_vrefint(true);
|
||||
});
|
||||
|
||||
// Enable HSI48
|
||||
#[cfg(not(any(stm32u5, stm32g0, stm32h5, stm32h7, stm32u5, stm32wba, stm32f0)))]
|
||||
let r = RCC.crrcr();
|
||||
#[cfg(any(stm32u5, stm32g0, stm32h5, stm32h7, stm32u5, stm32wba))]
|
||||
let r = RCC.cr();
|
||||
#[cfg(any(stm32f0))]
|
||||
let r = RCC.cr2();
|
||||
|
||||
r.modify(|w| w.set_hsi48on(true));
|
||||
while r.read().hsi48rdy() == false {}
|
||||
|
||||
if config.sync_from_usb {
|
||||
crate::peripherals::CRS::enable_and_reset();
|
||||
|
||||
CRS.cfgr().modify(|w| {
|
||||
w.set_syncsrc(Syncsrc::USB);
|
||||
});
|
||||
|
||||
// These are the correct settings for standard USB operation. If other settings
|
||||
// are needed there will need to be additional config options for the CRS.
|
||||
crate::pac::CRS.cr().modify(|w| {
|
||||
w.set_autotrimen(true);
|
||||
w.set_cen(true);
|
||||
});
|
||||
}
|
||||
|
||||
HSI48_FREQ
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
#[cfg(any(stm32l0, stm32l1))]
|
||||
pub use crate::pac::pwr::vals::Vos as VoltageScale;
|
||||
use crate::pac::rcc::regs::Cfgr;
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))]
|
||||
pub use crate::pac::rcc::vals::Adcsel as AdcClockSource;
|
||||
#[cfg(any(rcc_l0_v2, stm32l4, stm32l5, stm32wb))]
|
||||
pub use crate::pac::rcc::vals::Clk48sel as Clk48Src;
|
||||
#[cfg(any(stm32wb, stm32wl))]
|
||||
pub use crate::pac::rcc::vals::Hsepre as HsePrescaler;
|
||||
pub use crate::pac::rcc::vals::{
|
||||
Hpre as AHBPrescaler, Msirange as MSIRange, Pllm as PllPreDiv, Plln as PllMul, Pllp as PllPDiv, Pllq as PllQDiv,
|
||||
Pllr as PllRDiv, Pllsrc as PLLSource, Ppre as APBPrescaler, Sw as ClockSrc,
|
||||
};
|
||||
pub use crate::pac::rcc::vals::{Hpre as AHBPrescaler, Msirange as MSIRange, Ppre as APBPrescaler, Sw as ClockSrc};
|
||||
use crate::pac::{FLASH, RCC};
|
||||
use crate::rcc::{set_freqs, Clocks};
|
||||
use crate::time::Hertz;
|
||||
@ -33,33 +34,14 @@ pub struct Hse {
|
||||
pub prescaler: HsePrescaler,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Pll {
|
||||
/// PLL source
|
||||
pub source: PLLSource,
|
||||
|
||||
/// PLL pre-divider (DIVM).
|
||||
pub prediv: PllPreDiv,
|
||||
|
||||
/// PLL multiplication factor.
|
||||
pub mul: PllMul,
|
||||
|
||||
/// PLL P division factor. If None, PLL P output is disabled.
|
||||
pub divp: Option<PllPDiv>,
|
||||
/// PLL Q division factor. If None, PLL Q output is disabled.
|
||||
pub divq: Option<PllQDiv>,
|
||||
/// PLL R division factor. If None, PLL R output is disabled.
|
||||
pub divr: Option<PllRDiv>,
|
||||
}
|
||||
|
||||
/// Clocks configutation
|
||||
/// Clocks configuration
|
||||
pub struct Config {
|
||||
// base clock sources
|
||||
pub msi: Option<MSIRange>,
|
||||
pub hsi: bool,
|
||||
pub hse: Option<Hse>,
|
||||
#[cfg(any(all(stm32l4, not(any(stm32l47x, stm32l48x))), stm32l5, stm32wb))]
|
||||
pub hsi48: bool,
|
||||
#[cfg(crs)]
|
||||
pub hsi48: Option<super::Hsi48Config>,
|
||||
|
||||
// pll
|
||||
pub pll: Option<Pll>,
|
||||
@ -79,11 +61,17 @@ pub struct Config {
|
||||
pub shared_ahb_pre: AHBPrescaler,
|
||||
|
||||
// muxes
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
||||
#[cfg(any(rcc_l0_v2, stm32l4, stm32l5, stm32wb))]
|
||||
pub clk48_src: Clk48Src,
|
||||
|
||||
// low speed LSI/LSE/RTC
|
||||
pub ls: super::LsConfig,
|
||||
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))]
|
||||
pub adc_clock_source: AdcClockSource,
|
||||
|
||||
#[cfg(any(stm32l0, stm32l1))]
|
||||
pub voltage_scale: VoltageScale,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@ -106,11 +94,15 @@ impl Default for Config {
|
||||
pllsai1: None,
|
||||
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
||||
pllsai2: None,
|
||||
#[cfg(any(all(stm32l4, not(any(stm32l47x, stm32l48x))), stm32l5, stm32wb))]
|
||||
hsi48: true,
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
||||
#[cfg(crs)]
|
||||
hsi48: Some(Default::default()),
|
||||
#[cfg(any(rcc_l0_v2, stm32l4, stm32l5, stm32wb))]
|
||||
clk48_src: Clk48Src::HSI48,
|
||||
ls: Default::default(),
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))]
|
||||
adc_clock_source: AdcClockSource::SYS,
|
||||
#[cfg(any(stm32l0, stm32l1))]
|
||||
voltage_scale: VoltageScale::RANGE1,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -123,7 +115,8 @@ pub const WPAN_DEFAULT: Config = Config {
|
||||
prescaler: HsePrescaler::DIV1,
|
||||
}),
|
||||
mux: ClockSrc::PLL1_R,
|
||||
hsi48: true,
|
||||
#[cfg(crs)]
|
||||
hsi48: Some(super::Hsi48Config { sync_from_usb: false }),
|
||||
msi: None,
|
||||
hsi: false,
|
||||
clk48_src: Clk48Src::PLL1_Q,
|
||||
@ -131,7 +124,7 @@ pub const WPAN_DEFAULT: Config = Config {
|
||||
ls: super::LsConfig::default_lse(),
|
||||
|
||||
pll: Some(Pll {
|
||||
source: PLLSource::HSE,
|
||||
source: PllSource::HSE,
|
||||
prediv: PllPreDiv::DIV2,
|
||||
mul: PllMul::MUL12,
|
||||
divp: Some(PllPDiv::DIV3), // 32 / 2 * 12 / 3 = 64Mhz
|
||||
@ -145,22 +138,29 @@ pub const WPAN_DEFAULT: Config = Config {
|
||||
shared_ahb_pre: AHBPrescaler::DIV1,
|
||||
apb1_pre: APBPrescaler::DIV1,
|
||||
apb2_pre: APBPrescaler::DIV1,
|
||||
adc_clock_source: AdcClockSource::SYS,
|
||||
};
|
||||
|
||||
fn msi_enable(range: MSIRange) {
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))]
|
||||
RCC.cr().modify(|w| {
|
||||
#[cfg(not(stm32wb))]
|
||||
w.set_msirgsel(crate::pac::rcc::vals::Msirgsel::CR);
|
||||
w.set_msirange(range);
|
||||
w.set_msipllen(false);
|
||||
});
|
||||
#[cfg(any(stm32l0, stm32l1))]
|
||||
RCC.icscr().modify(|w| w.set_msirange(range));
|
||||
|
||||
RCC.cr().modify(|w| w.set_msion(true));
|
||||
while !RCC.cr().read().msirdy() {}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn init(config: Config) {
|
||||
// Switch to MSI to prevent problems with PLL configuration.
|
||||
if !RCC.cr().read().msion() {
|
||||
// Turn on MSI and configure it to 4MHz.
|
||||
RCC.cr().modify(|w| {
|
||||
#[cfg(not(stm32wb))]
|
||||
w.set_msirgsel(crate::pac::rcc::vals::Msirgsel::CR);
|
||||
w.set_msirange(MSIRange::RANGE4M);
|
||||
w.set_msipllen(false);
|
||||
w.set_msion(true)
|
||||
});
|
||||
|
||||
// Wait until MSI is running
|
||||
while !RCC.cr().read().msirdy() {}
|
||||
msi_enable(MSIRange::RANGE4M)
|
||||
}
|
||||
if RCC.cfgr().read().sws() != ClockSrc::MSI {
|
||||
// Set MSI as a clock source, reset prescalers.
|
||||
@ -169,6 +169,14 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
while RCC.cfgr().read().sws() != ClockSrc::MSI {}
|
||||
}
|
||||
|
||||
// Set voltage scale
|
||||
#[cfg(any(stm32l0, stm32l1))]
|
||||
{
|
||||
while crate::pac::PWR.csr().read().vosf() {}
|
||||
crate::pac::PWR.cr().write(|w| w.set_vos(config.voltage_scale));
|
||||
while crate::pac::PWR.csr().read().vosf() {}
|
||||
}
|
||||
|
||||
#[cfg(stm32l5)]
|
||||
crate::pac::PWR.cr1().modify(|w| {
|
||||
w.set_vos(crate::pac::pwr::vals::Vos::RANGE0);
|
||||
@ -177,24 +185,16 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
let rtc = config.ls.init();
|
||||
|
||||
let msi = config.msi.map(|range| {
|
||||
// Enable MSI
|
||||
RCC.cr().modify(|w| {
|
||||
#[cfg(not(stm32wb))]
|
||||
w.set_msirgsel(crate::pac::rcc::vals::Msirgsel::CR);
|
||||
w.set_msirange(range);
|
||||
w.set_msion(true);
|
||||
|
||||
// If LSE is enabled, enable calibration of MSI
|
||||
w.set_msipllen(config.ls.lse.is_some());
|
||||
});
|
||||
while !RCC.cr().read().msirdy() {}
|
||||
|
||||
// Enable as clock source for USB, RNG if running at 48 MHz
|
||||
if range == MSIRange::RANGE48M {}
|
||||
|
||||
msi_enable(range);
|
||||
msirange_to_hertz(range)
|
||||
});
|
||||
|
||||
// If LSE is enabled and the right freq, enable calibration of MSI
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))]
|
||||
if config.ls.lse.map(|x| x.frequency) == Some(Hertz(32_768)) {
|
||||
RCC.cr().modify(|w| w.set_msipllen(true));
|
||||
}
|
||||
|
||||
let hsi = config.hsi.then(|| {
|
||||
RCC.cr().modify(|w| w.set_hsion(true));
|
||||
while !RCC.cr().read().hsirdy() {}
|
||||
@ -215,15 +215,13 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
hse.freq
|
||||
});
|
||||
|
||||
#[cfg(any(all(stm32l4, not(any(stm32l47x, stm32l48x))), stm32l5, stm32wb))]
|
||||
let hsi48 = config.hsi48.then(|| {
|
||||
RCC.crrcr().modify(|w| w.set_hsi48on(true));
|
||||
while !RCC.crrcr().read().hsi48rdy() {}
|
||||
|
||||
Hertz(48_000_000)
|
||||
#[cfg(crs)]
|
||||
let _hsi48 = config.hsi48.map(|config| {
|
||||
//
|
||||
super::init_hsi48(config)
|
||||
});
|
||||
#[cfg(any(stm32l47x, stm32l48x))]
|
||||
let hsi48 = None;
|
||||
#[cfg(not(crs))]
|
||||
let _hsi48: Option<Hertz> = None;
|
||||
|
||||
let _plls = [
|
||||
&config.pll,
|
||||
@ -254,7 +252,12 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
}),
|
||||
};
|
||||
|
||||
let pll_input = PllInput { hse, hsi, msi };
|
||||
let pll_input = PllInput {
|
||||
hse,
|
||||
hsi,
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))]
|
||||
msi,
|
||||
};
|
||||
let pll = init_pll(PllInstance::Pll, config.pll, &pll_input);
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
||||
let pllsai1 = init_pll(PllInstance::Pllsai1, config.pllsai1, &pll_input);
|
||||
@ -268,13 +271,16 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
ClockSrc::PLL1_R => pll.r.unwrap(),
|
||||
};
|
||||
|
||||
#[cfg(stm32l4)]
|
||||
#[cfg(any(rcc_l0_v2, stm32l4, stm32l5, stm32wb))]
|
||||
RCC.ccipr().modify(|w| w.set_clk48sel(config.clk48_src));
|
||||
#[cfg(stm32l5)]
|
||||
RCC.ccipr1().modify(|w| w.set_clk48sel(config.clk48_src));
|
||||
#[cfg(any(rcc_l0_v2))]
|
||||
let _clk48 = match config.clk48_src {
|
||||
Clk48Src::HSI48 => _hsi48,
|
||||
Clk48Src::PLL1_VCO_DIV_2 => pll.clk48,
|
||||
};
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
||||
let _clk48 = match config.clk48_src {
|
||||
Clk48Src::HSI48 => hsi48,
|
||||
Clk48Src::HSI48 => _hsi48,
|
||||
Clk48Src::MSI => msi,
|
||||
Clk48Src::PLLSAI1_Q => pllsai1.q,
|
||||
Clk48Src::PLL1_Q => pll.q,
|
||||
@ -288,16 +294,23 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
let hclk1 = sys_clk / config.ahb_pre;
|
||||
let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk1, config.apb1_pre);
|
||||
let (pclk2, pclk2_tim) = super::util::calc_pclk(hclk1, config.apb2_pre);
|
||||
#[cfg(not(any(stm32wl5x, stm32wb)))]
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wlex))]
|
||||
let hclk2 = hclk1;
|
||||
#[cfg(any(stm32wl5x, stm32wb))]
|
||||
let hclk2 = sys_clk / config.core2_ahb_pre;
|
||||
#[cfg(not(any(stm32wl, stm32wb)))]
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wlex))]
|
||||
let hclk3 = hclk1;
|
||||
#[cfg(any(stm32wl, stm32wb))]
|
||||
#[cfg(any(stm32wl5x, stm32wb))]
|
||||
let hclk3 = sys_clk / config.shared_ahb_pre;
|
||||
|
||||
// Set flash wait states
|
||||
#[cfg(any(stm32l0, stm32l1))]
|
||||
let latency = match (config.voltage_scale, sys_clk.0) {
|
||||
(VoltageScale::RANGE1, ..=16_000_000) => false,
|
||||
(VoltageScale::RANGE2, ..=8_000_000) => false,
|
||||
(VoltageScale::RANGE3, ..=4_200_000) => false,
|
||||
_ => true,
|
||||
};
|
||||
#[cfg(stm32l4)]
|
||||
let latency = match hclk1.0 {
|
||||
0..=16_000_000 => 0,
|
||||
@ -333,6 +346,10 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
_ => 4,
|
||||
};
|
||||
|
||||
#[cfg(stm32l1)]
|
||||
FLASH.acr().write(|w| w.set_acc64(true));
|
||||
#[cfg(not(stm32l5))]
|
||||
FLASH.acr().modify(|w| w.set_prften(true));
|
||||
FLASH.acr().modify(|w| w.set_latency(latency));
|
||||
while FLASH.acr().read().latency() != latency {}
|
||||
|
||||
@ -344,6 +361,9 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
});
|
||||
while RCC.cfgr().read().sws() != config.mux {}
|
||||
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))]
|
||||
RCC.ccipr().modify(|w| w.set_adcsel(config.adc_clock_source));
|
||||
|
||||
#[cfg(any(stm32wl, stm32wb))]
|
||||
{
|
||||
RCC.extcfgr().modify(|w| {
|
||||
@ -359,7 +379,9 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
set_freqs(Clocks {
|
||||
sys: sys_clk,
|
||||
hclk1,
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))]
|
||||
hclk2,
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))]
|
||||
hclk3,
|
||||
pclk1,
|
||||
pclk2,
|
||||
@ -387,6 +409,12 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(any(stm32l0, stm32l1))]
|
||||
fn msirange_to_hertz(range: MSIRange) -> Hertz {
|
||||
Hertz(32_768 * (1 << (range as u8 + 1)))
|
||||
}
|
||||
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))]
|
||||
fn msirange_to_hertz(range: MSIRange) -> Hertz {
|
||||
match range {
|
||||
MSIRange::RANGE100K => Hertz(100_000),
|
||||
@ -405,20 +433,6 @@ fn msirange_to_hertz(range: MSIRange) -> Hertz {
|
||||
}
|
||||
}
|
||||
|
||||
struct PllInput {
|
||||
hsi: Option<Hertz>,
|
||||
hse: Option<Hertz>,
|
||||
msi: Option<Hertz>,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Default)]
|
||||
struct PllOutput {
|
||||
p: Option<Hertz>,
|
||||
q: Option<Hertz>,
|
||||
r: Option<Hertz>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
enum PllInstance {
|
||||
Pll,
|
||||
@ -447,77 +461,182 @@ fn pll_enable(instance: PllInstance, enabled: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
fn init_pll(instance: PllInstance, config: Option<Pll>, input: &PllInput) -> PllOutput {
|
||||
// Disable PLL
|
||||
pll_enable(instance, false);
|
||||
pub use pll::*;
|
||||
|
||||
let Some(pll) = config else { return PllOutput::default() };
|
||||
#[cfg(any(stm32l0, stm32l1))]
|
||||
mod pll {
|
||||
use super::{pll_enable, PllInstance};
|
||||
pub use crate::pac::rcc::vals::{Plldiv as PllDiv, Pllmul as PllMul, Pllsrc as PllSource};
|
||||
use crate::pac::RCC;
|
||||
use crate::time::Hertz;
|
||||
|
||||
let pll_src = match pll.source {
|
||||
PLLSource::DISABLE => panic!("must not select PLL source as DISABLE"),
|
||||
PLLSource::HSE => input.hse,
|
||||
PLLSource::HSI => input.hsi,
|
||||
PLLSource::MSI => input.msi,
|
||||
};
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Pll {
|
||||
/// PLL source
|
||||
pub source: PllSource,
|
||||
|
||||
let pll_src = pll_src.unwrap();
|
||||
/// PLL multiplication factor.
|
||||
pub mul: PllMul,
|
||||
|
||||
let vco_freq = pll_src / pll.prediv * pll.mul;
|
||||
|
||||
let p = pll.divp.map(|div| vco_freq / div);
|
||||
let q = pll.divq.map(|div| vco_freq / div);
|
||||
let r = pll.divr.map(|div| vco_freq / div);
|
||||
|
||||
#[cfg(stm32l5)]
|
||||
if instance == PllInstance::Pllsai2 {
|
||||
assert!(q.is_none(), "PLLSAI2_Q is not available on L5");
|
||||
assert!(r.is_none(), "PLLSAI2_R is not available on L5");
|
||||
/// PLL main output division factor.
|
||||
pub div: PllDiv,
|
||||
}
|
||||
|
||||
macro_rules! write_fields {
|
||||
($w:ident) => {
|
||||
$w.set_plln(pll.mul);
|
||||
if let Some(divp) = pll.divp {
|
||||
$w.set_pllp(divp);
|
||||
$w.set_pllpen(true);
|
||||
}
|
||||
if let Some(divq) = pll.divq {
|
||||
$w.set_pllq(divq);
|
||||
$w.set_pllqen(true);
|
||||
}
|
||||
if let Some(divr) = pll.divr {
|
||||
$w.set_pllr(divr);
|
||||
$w.set_pllren(true);
|
||||
}
|
||||
pub(super) struct PllInput {
|
||||
pub hsi: Option<Hertz>,
|
||||
pub hse: Option<Hertz>,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Default)]
|
||||
pub(super) struct PllOutput {
|
||||
pub r: Option<Hertz>,
|
||||
pub clk48: Option<Hertz>,
|
||||
}
|
||||
|
||||
pub(super) fn init_pll(instance: PllInstance, config: Option<Pll>, input: &PllInput) -> PllOutput {
|
||||
// Disable PLL
|
||||
pll_enable(instance, false);
|
||||
|
||||
let Some(pll) = config else { return PllOutput::default() };
|
||||
|
||||
let pll_src = match pll.source {
|
||||
PllSource::HSE => unwrap!(input.hse),
|
||||
PllSource::HSI => unwrap!(input.hsi),
|
||||
};
|
||||
|
||||
let vco_freq = pll_src * pll.mul;
|
||||
|
||||
let r = vco_freq / pll.div;
|
||||
let clk48 = (vco_freq == Hertz(96_000_000)).then_some(Hertz(48_000_000));
|
||||
|
||||
assert!(r <= Hertz(32_000_000));
|
||||
|
||||
RCC.cfgr().write(move |w| {
|
||||
w.set_pllmul(pll.mul);
|
||||
w.set_plldiv(pll.div);
|
||||
w.set_pllsrc(pll.source);
|
||||
});
|
||||
|
||||
// Enable PLL
|
||||
pll_enable(instance, true);
|
||||
|
||||
PllOutput { r: Some(r), clk48 }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))]
|
||||
mod pll {
|
||||
use super::{pll_enable, PllInstance};
|
||||
pub use crate::pac::rcc::vals::{
|
||||
Pllm as PllPreDiv, Plln as PllMul, Pllp as PllPDiv, Pllq as PllQDiv, Pllr as PllRDiv, Pllsrc as PllSource,
|
||||
};
|
||||
use crate::pac::RCC;
|
||||
use crate::time::Hertz;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Pll {
|
||||
/// PLL source
|
||||
pub source: PllSource,
|
||||
|
||||
/// PLL pre-divider (DIVM).
|
||||
pub prediv: PllPreDiv,
|
||||
|
||||
/// PLL multiplication factor.
|
||||
pub mul: PllMul,
|
||||
|
||||
/// PLL P division factor. If None, PLL P output is disabled.
|
||||
pub divp: Option<PllPDiv>,
|
||||
/// PLL Q division factor. If None, PLL Q output is disabled.
|
||||
pub divq: Option<PllQDiv>,
|
||||
/// PLL R division factor. If None, PLL R output is disabled.
|
||||
pub divr: Option<PllRDiv>,
|
||||
}
|
||||
|
||||
pub(super) struct PllInput {
|
||||
pub hsi: Option<Hertz>,
|
||||
pub hse: Option<Hertz>,
|
||||
pub msi: Option<Hertz>,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Default)]
|
||||
pub(super) struct PllOutput {
|
||||
pub p: Option<Hertz>,
|
||||
pub q: Option<Hertz>,
|
||||
pub r: Option<Hertz>,
|
||||
}
|
||||
|
||||
pub(super) fn init_pll(instance: PllInstance, config: Option<Pll>, input: &PllInput) -> PllOutput {
|
||||
// Disable PLL
|
||||
pll_enable(instance, false);
|
||||
|
||||
let Some(pll) = config else { return PllOutput::default() };
|
||||
|
||||
let pll_src = match pll.source {
|
||||
PllSource::DISABLE => panic!("must not select PLL source as DISABLE"),
|
||||
PllSource::HSE => unwrap!(input.hse),
|
||||
PllSource::HSI => unwrap!(input.hsi),
|
||||
PllSource::MSI => unwrap!(input.msi),
|
||||
};
|
||||
|
||||
let vco_freq = pll_src / pll.prediv * pll.mul;
|
||||
|
||||
let p = pll.divp.map(|div| vco_freq / div);
|
||||
let q = pll.divq.map(|div| vco_freq / div);
|
||||
let r = pll.divr.map(|div| vco_freq / div);
|
||||
|
||||
#[cfg(stm32l5)]
|
||||
if instance == PllInstance::Pllsai2 {
|
||||
assert!(q.is_none(), "PLLSAI2_Q is not available on L5");
|
||||
assert!(r.is_none(), "PLLSAI2_R is not available on L5");
|
||||
}
|
||||
|
||||
macro_rules! write_fields {
|
||||
($w:ident) => {
|
||||
$w.set_plln(pll.mul);
|
||||
if let Some(divp) = pll.divp {
|
||||
$w.set_pllp(divp);
|
||||
$w.set_pllpen(true);
|
||||
}
|
||||
if let Some(divq) = pll.divq {
|
||||
$w.set_pllq(divq);
|
||||
$w.set_pllqen(true);
|
||||
}
|
||||
if let Some(divr) = pll.divr {
|
||||
$w.set_pllr(divr);
|
||||
$w.set_pllren(true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match instance {
|
||||
PllInstance::Pll => RCC.pllcfgr().write(|w| {
|
||||
w.set_pllm(pll.prediv);
|
||||
w.set_pllsrc(pll.source);
|
||||
write_fields!(w);
|
||||
}),
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
||||
PllInstance::Pllsai1 => RCC.pllsai1cfgr().write(|w| {
|
||||
#[cfg(any(rcc_l4plus, stm32l5))]
|
||||
w.set_pllm(pll.prediv);
|
||||
#[cfg(stm32l5)]
|
||||
w.set_pllsrc(pll.source);
|
||||
write_fields!(w);
|
||||
}),
|
||||
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
||||
PllInstance::Pllsai2 => RCC.pllsai2cfgr().write(|w| {
|
||||
#[cfg(any(rcc_l4plus, stm32l5))]
|
||||
w.set_pllm(pll.prediv);
|
||||
#[cfg(stm32l5)]
|
||||
w.set_pllsrc(pll.source);
|
||||
write_fields!(w);
|
||||
}),
|
||||
}
|
||||
|
||||
// Enable PLL
|
||||
pll_enable(instance, true);
|
||||
|
||||
PllOutput { p, q, r }
|
||||
}
|
||||
|
||||
match instance {
|
||||
PllInstance::Pll => RCC.pllcfgr().write(|w| {
|
||||
w.set_pllm(pll.prediv);
|
||||
w.set_pllsrc(pll.source);
|
||||
write_fields!(w);
|
||||
}),
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
||||
PllInstance::Pllsai1 => RCC.pllsai1cfgr().write(|w| {
|
||||
#[cfg(any(rcc_l4plus, stm32l5))]
|
||||
w.set_pllm(pll.prediv);
|
||||
#[cfg(stm32l5)]
|
||||
w.set_pllsrc(pll.source);
|
||||
write_fields!(w);
|
||||
}),
|
||||
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
||||
PllInstance::Pllsai2 => RCC.pllsai2cfgr().write(|w| {
|
||||
#[cfg(any(rcc_l4plus, stm32l5))]
|
||||
w.set_pllm(pll.prediv);
|
||||
#[cfg(stm32l5)]
|
||||
w.set_pllsrc(pll.source);
|
||||
write_fields!(w);
|
||||
}),
|
||||
}
|
||||
|
||||
// Enable PLL
|
||||
pll_enable(instance, true);
|
||||
|
||||
PllOutput { p, q, r }
|
||||
}
|
@ -1,205 +0,0 @@
|
||||
pub use crate::pac::pwr::vals::Vos as VoltageScale;
|
||||
pub use crate::pac::rcc::vals::{
|
||||
Hpre as AHBPrescaler, Msirange as MSIRange, Plldiv as PLLDiv, Pllmul as PLLMul, Ppre as APBPrescaler,
|
||||
};
|
||||
use crate::pac::rcc::vals::{Pllsrc, Sw};
|
||||
#[cfg(crs)]
|
||||
use crate::pac::{crs, CRS, SYSCFG};
|
||||
use crate::pac::{FLASH, PWR, RCC};
|
||||
use crate::rcc::{set_freqs, Clocks};
|
||||
use crate::time::Hertz;
|
||||
|
||||
/// HSI speed
|
||||
pub const HSI_FREQ: Hertz = Hertz(16_000_000);
|
||||
|
||||
/// System clock mux source
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ClockSrc {
|
||||
MSI(MSIRange),
|
||||
PLL(PLLSource, PLLMul, PLLDiv),
|
||||
HSE(Hertz),
|
||||
HSI,
|
||||
}
|
||||
|
||||
/// PLL clock input source
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum PLLSource {
|
||||
HSI,
|
||||
HSE(Hertz),
|
||||
}
|
||||
|
||||
impl From<PLLSource> for Pllsrc {
|
||||
fn from(val: PLLSource) -> Pllsrc {
|
||||
match val {
|
||||
PLLSource::HSI => Pllsrc::HSI,
|
||||
PLLSource::HSE(_) => Pllsrc::HSE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Clocks configutation
|
||||
pub struct Config {
|
||||
pub mux: ClockSrc,
|
||||
pub ahb_pre: AHBPrescaler,
|
||||
pub apb1_pre: APBPrescaler,
|
||||
pub apb2_pre: APBPrescaler,
|
||||
#[cfg(crs)]
|
||||
pub enable_hsi48: bool,
|
||||
pub ls: super::LsConfig,
|
||||
pub voltage_scale: VoltageScale,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
#[inline]
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
mux: ClockSrc::MSI(MSIRange::RANGE5),
|
||||
ahb_pre: AHBPrescaler::DIV1,
|
||||
apb1_pre: APBPrescaler::DIV1,
|
||||
apb2_pre: APBPrescaler::DIV1,
|
||||
#[cfg(crs)]
|
||||
enable_hsi48: false,
|
||||
voltage_scale: VoltageScale::RANGE1,
|
||||
ls: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn init(config: Config) {
|
||||
// Set voltage scale
|
||||
while PWR.csr().read().vosf() {}
|
||||
PWR.cr().write(|w| w.set_vos(config.voltage_scale));
|
||||
while PWR.csr().read().vosf() {}
|
||||
|
||||
let (sys_clk, sw) = match config.mux {
|
||||
ClockSrc::MSI(range) => {
|
||||
// Set MSI range
|
||||
RCC.icscr().write(|w| w.set_msirange(range));
|
||||
|
||||
// Enable MSI
|
||||
RCC.cr().write(|w| w.set_msion(true));
|
||||
while !RCC.cr().read().msirdy() {}
|
||||
|
||||
let freq = 32_768 * (1 << (range as u8 + 1));
|
||||
(Hertz(freq), Sw::MSI)
|
||||
}
|
||||
ClockSrc::HSI => {
|
||||
// Enable HSI
|
||||
RCC.cr().write(|w| w.set_hsion(true));
|
||||
while !RCC.cr().read().hsirdy() {}
|
||||
|
||||
(HSI_FREQ, Sw::HSI)
|
||||
}
|
||||
ClockSrc::HSE(freq) => {
|
||||
// Enable HSE
|
||||
RCC.cr().write(|w| w.set_hseon(true));
|
||||
while !RCC.cr().read().hserdy() {}
|
||||
|
||||
(freq, Sw::HSE)
|
||||
}
|
||||
ClockSrc::PLL(src, mul, div) => {
|
||||
let freq = match src {
|
||||
PLLSource::HSE(freq) => {
|
||||
// Enable HSE
|
||||
RCC.cr().write(|w| w.set_hseon(true));
|
||||
while !RCC.cr().read().hserdy() {}
|
||||
freq
|
||||
}
|
||||
PLLSource::HSI => {
|
||||
// Enable HSI
|
||||
RCC.cr().write(|w| w.set_hsion(true));
|
||||
while !RCC.cr().read().hsirdy() {}
|
||||
HSI_FREQ
|
||||
}
|
||||
};
|
||||
|
||||
// Disable PLL
|
||||
RCC.cr().modify(|w| w.set_pllon(false));
|
||||
while RCC.cr().read().pllrdy() {}
|
||||
|
||||
let freq = freq * mul / div;
|
||||
|
||||
assert!(freq <= Hertz(32_000_000));
|
||||
|
||||
RCC.cfgr().write(move |w| {
|
||||
w.set_pllmul(mul);
|
||||
w.set_plldiv(div);
|
||||
w.set_pllsrc(src.into());
|
||||
});
|
||||
|
||||
// Enable PLL
|
||||
RCC.cr().modify(|w| w.set_pllon(true));
|
||||
while !RCC.cr().read().pllrdy() {}
|
||||
|
||||
(freq, Sw::PLL1_P)
|
||||
}
|
||||
};
|
||||
|
||||
let rtc = config.ls.init();
|
||||
|
||||
let wait_states = match (config.voltage_scale, sys_clk.0) {
|
||||
(VoltageScale::RANGE1, ..=16_000_000) => 0,
|
||||
(VoltageScale::RANGE2, ..=8_000_000) => 0,
|
||||
(VoltageScale::RANGE3, ..=4_200_000) => 0,
|
||||
_ => 1,
|
||||
};
|
||||
|
||||
#[cfg(stm32l1)]
|
||||
FLASH.acr().write(|w| w.set_acc64(true));
|
||||
FLASH.acr().modify(|w| w.set_prften(true));
|
||||
FLASH.acr().modify(|w| w.set_latency(wait_states != 0));
|
||||
|
||||
RCC.cfgr().modify(|w| {
|
||||
w.set_sw(sw);
|
||||
w.set_hpre(config.ahb_pre);
|
||||
w.set_ppre1(config.apb1_pre);
|
||||
w.set_ppre2(config.apb2_pre);
|
||||
});
|
||||
|
||||
let hclk1 = sys_clk / config.ahb_pre;
|
||||
let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk1, config.apb1_pre);
|
||||
let (pclk2, pclk2_tim) = super::util::calc_pclk(hclk1, config.apb2_pre);
|
||||
|
||||
#[cfg(crs)]
|
||||
if config.enable_hsi48 {
|
||||
// Reset CRS peripheral
|
||||
RCC.apb1rstr().modify(|w| w.set_crsrst(true));
|
||||
RCC.apb1rstr().modify(|w| w.set_crsrst(false));
|
||||
|
||||
// Enable CRS peripheral
|
||||
RCC.apb1enr().modify(|w| w.set_crsen(true));
|
||||
|
||||
// Initialize CRS
|
||||
CRS.cfgr().write(|w|
|
||||
|
||||
// Select LSE as synchronization source
|
||||
w.set_syncsrc(crs::vals::Syncsrc::LSE));
|
||||
CRS.cr().modify(|w| {
|
||||
w.set_autotrimen(true);
|
||||
w.set_cen(true);
|
||||
});
|
||||
|
||||
// Enable VREFINT reference for HSI48 oscillator
|
||||
SYSCFG.cfgr3().modify(|w| {
|
||||
w.set_enref_hsi48(true);
|
||||
w.set_en_vrefint(true);
|
||||
});
|
||||
|
||||
// Select HSI48 as USB clock
|
||||
RCC.ccipr().modify(|w| w.set_hsi48msel(true));
|
||||
|
||||
// Enable dedicated USB clock
|
||||
RCC.crrcr().modify(|w| w.set_hsi48on(true));
|
||||
while !RCC.crrcr().read().hsi48rdy() {}
|
||||
}
|
||||
|
||||
set_freqs(Clocks {
|
||||
sys: sys_clk,
|
||||
hclk1,
|
||||
pclk1,
|
||||
pclk2,
|
||||
pclk1_tim,
|
||||
pclk2_tim,
|
||||
rtc,
|
||||
});
|
||||
}
|
@ -9,22 +9,23 @@ mod mco;
|
||||
pub use bd::*;
|
||||
pub use mco::*;
|
||||
|
||||
#[cfg(crs)]
|
||||
mod hsi48;
|
||||
#[cfg(crs)]
|
||||
pub use hsi48::*;
|
||||
|
||||
#[cfg_attr(rcc_f0, path = "f0.rs")]
|
||||
#[cfg_attr(any(rcc_f1, rcc_f100, rcc_f1cl), path = "f1.rs")]
|
||||
#[cfg_attr(rcc_f2, path = "f2.rs")]
|
||||
#[cfg_attr(any(rcc_f3, rcc_f3_v2), path = "f3.rs")]
|
||||
#[cfg_attr(any(rcc_f4, rcc_f410, rcc_f7), path = "f4f7.rs")]
|
||||
#[cfg_attr(any(stm32f1), path = "f1.rs")]
|
||||
#[cfg_attr(any(stm32f3), path = "f3.rs")]
|
||||
#[cfg_attr(any(stm32f2, stm32f4, stm32f7), path = "f.rs")]
|
||||
#[cfg_attr(rcc_c0, path = "c0.rs")]
|
||||
#[cfg_attr(rcc_g0, path = "g0.rs")]
|
||||
#[cfg_attr(rcc_g4, path = "g4.rs")]
|
||||
#[cfg_attr(any(rcc_h5, rcc_h50, rcc_h7, rcc_h7rm0433, rcc_h7ab), path = "h.rs")]
|
||||
#[cfg_attr(any(rcc_l0, rcc_l0_v2, rcc_l1), path = "l0l1.rs")]
|
||||
#[cfg_attr(any(rcc_l4, rcc_l4plus, rcc_l5, rcc_wl5, rcc_wle, rcc_wb), path = "l4l5.rs")]
|
||||
#[cfg_attr(any(stm32h5, stm32h7), path = "h.rs")]
|
||||
#[cfg_attr(any(stm32l0, stm32l1, stm32l4, stm32l5, stm32wb, stm32wl), path = "l.rs")]
|
||||
#[cfg_attr(rcc_u5, path = "u5.rs")]
|
||||
#[cfg_attr(rcc_wba, path = "wba.rs")]
|
||||
mod _version;
|
||||
#[cfg(feature = "low-power")]
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
pub use _version::*;
|
||||
|
||||
@ -183,27 +184,16 @@ pub struct Clocks {
|
||||
}
|
||||
|
||||
#[cfg(feature = "low-power")]
|
||||
static CLOCK_REFCOUNT: AtomicU32 = AtomicU32::new(0);
|
||||
/// Must be written within a critical section
|
||||
///
|
||||
/// May be read without a critical section
|
||||
pub(crate) static mut REFCOUNT_STOP1: u32 = 0;
|
||||
|
||||
#[cfg(feature = "low-power")]
|
||||
pub fn low_power_ready() -> bool {
|
||||
// trace!("clock refcount: {}", CLOCK_REFCOUNT.load(Ordering::SeqCst));
|
||||
CLOCK_REFCOUNT.load(Ordering::SeqCst) == 0
|
||||
}
|
||||
|
||||
#[cfg(feature = "low-power")]
|
||||
pub(crate) fn clock_refcount_add(_cs: critical_section::CriticalSection) {
|
||||
// We don't check for overflow because constructing more than u32 peripherals is unlikely
|
||||
let n = CLOCK_REFCOUNT.load(Ordering::Relaxed);
|
||||
CLOCK_REFCOUNT.store(n + 1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[cfg(feature = "low-power")]
|
||||
pub(crate) fn clock_refcount_sub(_cs: critical_section::CriticalSection) {
|
||||
let n = CLOCK_REFCOUNT.load(Ordering::Relaxed);
|
||||
assert!(n != 0);
|
||||
CLOCK_REFCOUNT.store(n - 1, Ordering::Relaxed);
|
||||
}
|
||||
/// Must be written within a critical section
|
||||
///
|
||||
/// May be read without a critical section
|
||||
pub(crate) static mut REFCOUNT_STOP2: u32 = 0;
|
||||
|
||||
/// Frozen clock frequencies
|
||||
///
|
||||
|
@ -35,7 +35,7 @@ impl Default for ClockSrc {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct PllConfig {
|
||||
/// The clock source for the PLL.
|
||||
pub source: PllSrc,
|
||||
pub source: PllSource,
|
||||
/// The PLL prescaler.
|
||||
///
|
||||
/// The clock speed of the `source` divided by `m` must be between 4 and 16 MHz.
|
||||
@ -57,7 +57,7 @@ impl PllConfig {
|
||||
/// A configuration for HSI / 1 * 10 / 1 = 160 MHz
|
||||
pub const fn hsi_160mhz() -> Self {
|
||||
PllConfig {
|
||||
source: PllSrc::HSI,
|
||||
source: PllSource::HSI,
|
||||
m: Pllm::DIV1,
|
||||
n: Plln::MUL10,
|
||||
r: Plldiv::DIV1,
|
||||
@ -67,7 +67,7 @@ impl PllConfig {
|
||||
/// A configuration for MSIS @ 48 MHz / 3 * 10 / 1 = 160 MHz
|
||||
pub const fn msis_160mhz() -> Self {
|
||||
PllConfig {
|
||||
source: PllSrc::MSIS(Msirange::RANGE_48MHZ),
|
||||
source: PllSource::MSIS(Msirange::RANGE_48MHZ),
|
||||
m: Pllm::DIV3,
|
||||
n: Plln::MUL10,
|
||||
r: Plldiv::DIV1,
|
||||
@ -76,7 +76,7 @@ impl PllConfig {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum PllSrc {
|
||||
pub enum PllSource {
|
||||
/// Use an internal medium speed oscillator as the PLL source.
|
||||
MSIS(Msirange),
|
||||
/// Use the external high speed clock as the system PLL source.
|
||||
@ -88,12 +88,12 @@ pub enum PllSrc {
|
||||
HSI,
|
||||
}
|
||||
|
||||
impl Into<Pllsrc> for PllSrc {
|
||||
impl Into<Pllsrc> for PllSource {
|
||||
fn into(self) -> Pllsrc {
|
||||
match self {
|
||||
PllSrc::MSIS(..) => Pllsrc::MSIS,
|
||||
PllSrc::HSE(..) => Pllsrc::HSE,
|
||||
PllSrc::HSI => Pllsrc::HSI,
|
||||
PllSource::MSIS(..) => Pllsrc::MSIS,
|
||||
PllSource::HSE(..) => Pllsrc::HSE,
|
||||
PllSource::HSI => Pllsrc::HSI,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -115,7 +115,7 @@ pub struct Config {
|
||||
pub apb1_pre: APBPrescaler,
|
||||
pub apb2_pre: APBPrescaler,
|
||||
pub apb3_pre: APBPrescaler,
|
||||
pub hsi48: bool,
|
||||
pub hsi48: Option<super::Hsi48Config>,
|
||||
/// The voltage range influences the maximum clock frequencies for different parts of the
|
||||
/// device. In particular, system clocks exceeding 110 MHz require `RANGE1`, and system clocks
|
||||
/// exceeding 55 MHz require at least `RANGE2`.
|
||||
@ -189,7 +189,7 @@ impl Default for Config {
|
||||
apb1_pre: APBPrescaler::DIV1,
|
||||
apb2_pre: APBPrescaler::DIV1,
|
||||
apb3_pre: APBPrescaler::DIV1,
|
||||
hsi48: true,
|
||||
hsi48: Some(Default::default()),
|
||||
voltage_range: VoltageScale::RANGE3,
|
||||
ls: Default::default(),
|
||||
}
|
||||
@ -216,9 +216,9 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
ClockSrc::PLL1_R(pll) => {
|
||||
// Configure the PLL source
|
||||
let source_clk = match pll.source {
|
||||
PllSrc::MSIS(range) => config.init_msis(range),
|
||||
PllSrc::HSE(hertz) => config.init_hse(hertz),
|
||||
PllSrc::HSI => config.init_hsi(),
|
||||
PllSource::MSIS(range) => config.init_msis(range),
|
||||
PllSource::HSE(hertz) => config.init_hse(hertz),
|
||||
PllSource::HSI => config.init_hsi(),
|
||||
};
|
||||
|
||||
// Calculate the reference clock, which is the source divided by m
|
||||
@ -322,10 +322,7 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
}
|
||||
};
|
||||
|
||||
if config.hsi48 {
|
||||
RCC.cr().modify(|w| w.set_hsi48on(true));
|
||||
while !RCC.cr().read().hsi48rdy() {}
|
||||
}
|
||||
let _hsi48 = config.hsi48.map(super::init_hsi48);
|
||||
|
||||
// The clock source is ready
|
||||
// Calculate and set the flash wait states
|
||||
|
@ -17,16 +17,16 @@ pub enum ClockSrc {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum PllSrc {
|
||||
pub enum PllSource {
|
||||
HSE(Hertz),
|
||||
HSI,
|
||||
}
|
||||
|
||||
impl Into<Pllsrc> for PllSrc {
|
||||
impl Into<Pllsrc> for PllSource {
|
||||
fn into(self) -> Pllsrc {
|
||||
match self {
|
||||
PllSrc::HSE(..) => Pllsrc::HSE,
|
||||
PllSrc::HSI => Pllsrc::HSI,
|
||||
PllSource::HSE(..) => Pllsrc::HSE,
|
||||
PllSource::HSI => Pllsrc::HSI,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,64 @@ use core::convert::From;
|
||||
#[cfg(feature = "chrono")]
|
||||
use chrono::{self, Datelike, NaiveDate, Timelike, Weekday};
|
||||
|
||||
use super::byte_to_bcd2;
|
||||
use crate::pac::rtc::Rtc;
|
||||
#[cfg(any(feature = "defmt", feature = "time"))]
|
||||
use crate::peripherals::RTC;
|
||||
#[cfg(any(feature = "defmt", feature = "time"))]
|
||||
use crate::rtc::sealed::Instance;
|
||||
|
||||
/// Represents an instant in time that can be substracted to compute a duration
|
||||
pub struct RtcInstant {
|
||||
/// 0..59
|
||||
pub second: u8,
|
||||
/// 0..256
|
||||
pub subsecond: u16,
|
||||
}
|
||||
|
||||
impl RtcInstant {
|
||||
#[cfg(not(rtc_v2f2))]
|
||||
pub(super) const fn from(second: u8, subsecond: u16) -> Result<Self, Error> {
|
||||
if second > 59 {
|
||||
Err(Error::InvalidSecond)
|
||||
} else {
|
||||
Ok(Self { second, subsecond })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for RtcInstant {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(
|
||||
fmt,
|
||||
"{}:{}",
|
||||
self.second,
|
||||
RTC::regs().prer().read().prediv_s() - self.subsecond,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
impl core::ops::Sub for RtcInstant {
|
||||
type Output = embassy_time::Duration;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
use embassy_time::{Duration, TICK_HZ};
|
||||
|
||||
let second = if self.second < rhs.second {
|
||||
self.second + 60
|
||||
} else {
|
||||
self.second
|
||||
};
|
||||
|
||||
let psc = RTC::regs().prer().read().prediv_s() as u32;
|
||||
|
||||
let self_ticks = second as u32 * (psc + 1) + (psc - self.subsecond as u32);
|
||||
let other_ticks = rhs.second as u32 * (psc + 1) + (psc - rhs.subsecond as u32);
|
||||
let rtc_ticks = self_ticks - other_ticks;
|
||||
|
||||
Duration::from_ticks(((rtc_ticks * TICK_HZ as u32) / (psc + 1)) as u64)
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors regarding the [`DateTime`] struct.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@ -32,19 +88,85 @@ pub enum Error {
|
||||
/// Structure containing date and time information
|
||||
pub struct DateTime {
|
||||
/// 0..4095
|
||||
pub year: u16,
|
||||
year: u16,
|
||||
/// 1..12, 1 is January
|
||||
pub month: u8,
|
||||
month: u8,
|
||||
/// 1..28,29,30,31 depending on month
|
||||
pub day: u8,
|
||||
day: u8,
|
||||
///
|
||||
pub day_of_week: DayOfWeek,
|
||||
day_of_week: DayOfWeek,
|
||||
/// 0..23
|
||||
pub hour: u8,
|
||||
hour: u8,
|
||||
/// 0..59
|
||||
pub minute: u8,
|
||||
minute: u8,
|
||||
/// 0..59
|
||||
pub second: u8,
|
||||
second: u8,
|
||||
}
|
||||
|
||||
impl DateTime {
|
||||
pub const fn year(&self) -> u16 {
|
||||
self.year
|
||||
}
|
||||
|
||||
pub const fn month(&self) -> u8 {
|
||||
self.month
|
||||
}
|
||||
|
||||
pub const fn day(&self) -> u8 {
|
||||
self.day
|
||||
}
|
||||
|
||||
pub const fn day_of_week(&self) -> DayOfWeek {
|
||||
self.day_of_week
|
||||
}
|
||||
|
||||
pub const fn hour(&self) -> u8 {
|
||||
self.hour
|
||||
}
|
||||
|
||||
pub const fn minute(&self) -> u8 {
|
||||
self.minute
|
||||
}
|
||||
|
||||
pub const fn second(&self) -> u8 {
|
||||
self.second
|
||||
}
|
||||
|
||||
pub fn from(
|
||||
year: u16,
|
||||
month: u8,
|
||||
day: u8,
|
||||
day_of_week: u8,
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
) -> Result<Self, Error> {
|
||||
let day_of_week = day_of_week_from_u8(day_of_week)?;
|
||||
|
||||
if year > 4095 {
|
||||
Err(Error::InvalidYear)
|
||||
} else if month < 1 || month > 12 {
|
||||
Err(Error::InvalidMonth)
|
||||
} else if day < 1 || day > 31 {
|
||||
Err(Error::InvalidDay)
|
||||
} else if hour > 23 {
|
||||
Err(Error::InvalidHour)
|
||||
} else if minute > 59 {
|
||||
Err(Error::InvalidMinute)
|
||||
} else if second > 59 {
|
||||
Err(Error::InvalidSecond)
|
||||
} else {
|
||||
Ok(Self {
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
day_of_week,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
@ -77,13 +199,13 @@ impl From<DateTime> for chrono::NaiveDateTime {
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum DayOfWeek {
|
||||
Monday = 0,
|
||||
Tuesday = 1,
|
||||
Wednesday = 2,
|
||||
Thursday = 3,
|
||||
Friday = 4,
|
||||
Saturday = 5,
|
||||
Sunday = 6,
|
||||
Monday = 1,
|
||||
Tuesday = 2,
|
||||
Wednesday = 3,
|
||||
Thursday = 4,
|
||||
Friday = 5,
|
||||
Saturday = 6,
|
||||
Sunday = 7,
|
||||
}
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
@ -108,92 +230,19 @@ impl From<DayOfWeek> for chrono::Weekday {
|
||||
}
|
||||
}
|
||||
|
||||
fn day_of_week_from_u8(v: u8) -> Result<DayOfWeek, Error> {
|
||||
pub(super) const fn day_of_week_from_u8(v: u8) -> Result<DayOfWeek, Error> {
|
||||
Ok(match v {
|
||||
0 => DayOfWeek::Monday,
|
||||
1 => DayOfWeek::Tuesday,
|
||||
2 => DayOfWeek::Wednesday,
|
||||
3 => DayOfWeek::Thursday,
|
||||
4 => DayOfWeek::Friday,
|
||||
5 => DayOfWeek::Saturday,
|
||||
6 => DayOfWeek::Sunday,
|
||||
1 => DayOfWeek::Monday,
|
||||
2 => DayOfWeek::Tuesday,
|
||||
3 => DayOfWeek::Wednesday,
|
||||
4 => DayOfWeek::Thursday,
|
||||
5 => DayOfWeek::Friday,
|
||||
6 => DayOfWeek::Saturday,
|
||||
7 => DayOfWeek::Sunday,
|
||||
x => return Err(Error::InvalidDayOfWeek(x)),
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 {
|
||||
pub(super) const fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 {
|
||||
dotw as u8
|
||||
}
|
||||
|
||||
pub(super) fn validate_datetime(dt: &DateTime) -> Result<(), Error> {
|
||||
if dt.year > 4095 {
|
||||
Err(Error::InvalidYear)
|
||||
} else if dt.month < 1 || dt.month > 12 {
|
||||
Err(Error::InvalidMonth)
|
||||
} else if dt.day < 1 || dt.day > 31 {
|
||||
Err(Error::InvalidDay)
|
||||
} else if dt.hour > 23 {
|
||||
Err(Error::InvalidHour)
|
||||
} else if dt.minute > 59 {
|
||||
Err(Error::InvalidMinute)
|
||||
} else if dt.second > 59 {
|
||||
Err(Error::InvalidSecond)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn write_date_time(rtc: &Rtc, t: DateTime) {
|
||||
let (ht, hu) = byte_to_bcd2(t.hour as u8);
|
||||
let (mnt, mnu) = byte_to_bcd2(t.minute as u8);
|
||||
let (st, su) = byte_to_bcd2(t.second as u8);
|
||||
|
||||
let (dt, du) = byte_to_bcd2(t.day as u8);
|
||||
let (mt, mu) = byte_to_bcd2(t.month as u8);
|
||||
let yr = t.year as u16;
|
||||
let yr_offset = (yr - 1970_u16) as u8;
|
||||
let (yt, yu) = byte_to_bcd2(yr_offset);
|
||||
|
||||
use crate::pac::rtc::vals::Ampm;
|
||||
|
||||
rtc.tr().write(|w| {
|
||||
w.set_ht(ht);
|
||||
w.set_hu(hu);
|
||||
w.set_mnt(mnt);
|
||||
w.set_mnu(mnu);
|
||||
w.set_st(st);
|
||||
w.set_su(su);
|
||||
w.set_pm(Ampm::AM);
|
||||
});
|
||||
|
||||
rtc.dr().write(|w| {
|
||||
w.set_dt(dt);
|
||||
w.set_du(du);
|
||||
w.set_mt(mt > 0);
|
||||
w.set_mu(mu);
|
||||
w.set_yt(yt);
|
||||
w.set_yu(yu);
|
||||
w.set_wdu(day_of_week_to_u8(t.day_of_week));
|
||||
});
|
||||
}
|
||||
|
||||
pub(super) fn datetime(
|
||||
year: u16,
|
||||
month: u8,
|
||||
day: u8,
|
||||
day_of_week: u8,
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
) -> Result<DateTime, Error> {
|
||||
let day_of_week = day_of_week_from_u8(day_of_week)?;
|
||||
Ok(DateTime {
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
day_of_week,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
})
|
||||
}
|
||||
|
@ -9,7 +9,11 @@ use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
#[cfg(feature = "low-power")]
|
||||
use embassy_sync::blocking_mutex::Mutex;
|
||||
|
||||
use self::datetime::day_of_week_to_u8;
|
||||
#[cfg(not(rtc_v2f2))]
|
||||
use self::datetime::RtcInstant;
|
||||
pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError};
|
||||
use crate::pac::rtc::regs::{Dr, Tr};
|
||||
use crate::time::Hertz;
|
||||
|
||||
/// refer to AN4759 to compare features of RTC2 and RTC3
|
||||
@ -29,113 +33,133 @@ use embassy_hal_internal::Peripheral;
|
||||
use crate::peripherals::RTC;
|
||||
use crate::rtc::sealed::Instance;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum WakeupPrescaler {
|
||||
Div2 = 2,
|
||||
Div4 = 4,
|
||||
Div8 = 8,
|
||||
Div16 = 16,
|
||||
}
|
||||
|
||||
#[cfg(any(stm32wb, stm32f4, stm32l0, stm32g4))]
|
||||
impl From<WakeupPrescaler> for crate::pac::rtc::vals::Wucksel {
|
||||
fn from(val: WakeupPrescaler) -> Self {
|
||||
use crate::pac::rtc::vals::Wucksel;
|
||||
|
||||
match val {
|
||||
WakeupPrescaler::Div2 => Wucksel::DIV2,
|
||||
WakeupPrescaler::Div4 => Wucksel::DIV4,
|
||||
WakeupPrescaler::Div8 => Wucksel::DIV8,
|
||||
WakeupPrescaler::Div16 => Wucksel::DIV16,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(stm32wb, stm32f4, stm32l0, stm32g4))]
|
||||
impl From<crate::pac::rtc::vals::Wucksel> for WakeupPrescaler {
|
||||
fn from(val: crate::pac::rtc::vals::Wucksel) -> Self {
|
||||
use crate::pac::rtc::vals::Wucksel;
|
||||
|
||||
match val {
|
||||
Wucksel::DIV2 => WakeupPrescaler::Div2,
|
||||
Wucksel::DIV4 => WakeupPrescaler::Div4,
|
||||
Wucksel::DIV8 => WakeupPrescaler::Div8,
|
||||
Wucksel::DIV16 => WakeupPrescaler::Div16,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "low-power")]
|
||||
impl WakeupPrescaler {
|
||||
pub fn compute_min(val: u32) -> Self {
|
||||
*[
|
||||
WakeupPrescaler::Div2,
|
||||
WakeupPrescaler::Div4,
|
||||
WakeupPrescaler::Div8,
|
||||
WakeupPrescaler::Div16,
|
||||
]
|
||||
.iter()
|
||||
.skip_while(|psc| **psc as u32 <= val)
|
||||
.next()
|
||||
.unwrap_or(&WakeupPrescaler::Div16)
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can occur on methods on [RtcClock]
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum RtcError {
|
||||
/// An invalid DateTime was given or stored on the hardware.
|
||||
InvalidDateTime(DateTimeError),
|
||||
|
||||
/// The current time could not be read
|
||||
ReadFailure,
|
||||
|
||||
/// The RTC clock is not running
|
||||
NotRunning,
|
||||
}
|
||||
|
||||
#[cfg(feature = "low-power")]
|
||||
/// Represents an instant in time that can be substracted to compute a duration
|
||||
struct RtcInstant {
|
||||
second: u8,
|
||||
subsecond: u16,
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "low-power", feature = "defmt"))]
|
||||
impl defmt::Format for RtcInstant {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(
|
||||
fmt,
|
||||
"{}:{}",
|
||||
self.second,
|
||||
RTC::regs().prer().read().prediv_s() - self.subsecond,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "low-power")]
|
||||
impl core::ops::Sub for RtcInstant {
|
||||
type Output = embassy_time::Duration;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
use embassy_time::{Duration, TICK_HZ};
|
||||
|
||||
let second = if self.second < rhs.second {
|
||||
self.second + 60
|
||||
} else {
|
||||
self.second
|
||||
};
|
||||
|
||||
let psc = RTC::regs().prer().read().prediv_s() as u32;
|
||||
|
||||
let self_ticks = second as u32 * (psc + 1) + (psc - self.subsecond as u32);
|
||||
let other_ticks = rhs.second as u32 * (psc + 1) + (psc - rhs.subsecond as u32);
|
||||
let rtc_ticks = self_ticks - other_ticks;
|
||||
|
||||
Duration::from_ticks(((rtc_ticks * TICK_HZ as u32) / (psc + 1)) as u64)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RtcTimeProvider {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl RtcTimeProvider {
|
||||
#[cfg(not(rtc_v2f2))]
|
||||
pub(crate) fn instant(&self) -> Result<RtcInstant, RtcError> {
|
||||
self.read(|_, tr, ss| {
|
||||
let second = bcd2_to_byte((tr.st(), tr.su()));
|
||||
|
||||
RtcInstant::from(second, ss).map_err(RtcError::InvalidDateTime)
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the current datetime.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Will return an `RtcError::InvalidDateTime` if the stored value in the system is not a valid [`DayOfWeek`].
|
||||
pub fn now(&self) -> Result<DateTime, RtcError> {
|
||||
// For RM0433 we use BYPSHAD=1 to work around errata ES0392 2.19.1
|
||||
#[cfg(rcc_h7rm0433)]
|
||||
loop {
|
||||
let r = RTC::regs();
|
||||
let ss = r.ssr().read().ss();
|
||||
let dr = r.dr().read();
|
||||
let tr = r.tr().read();
|
||||
|
||||
// If an RTCCLK edge occurs during read we may see inconsistent values
|
||||
// so read ssr again and see if it has changed. (see RM0433 Rev 7 46.3.9)
|
||||
let ss_after = r.ssr().read().ss();
|
||||
if ss == ss_after {
|
||||
let second = bcd2_to_byte((tr.st(), tr.su()));
|
||||
let minute = bcd2_to_byte((tr.mnt(), tr.mnu()));
|
||||
let hour = bcd2_to_byte((tr.ht(), tr.hu()));
|
||||
|
||||
let weekday = dr.wdu();
|
||||
let day = bcd2_to_byte((dr.dt(), dr.du()));
|
||||
let month = bcd2_to_byte((dr.mt() as u8, dr.mu()));
|
||||
let year = bcd2_to_byte((dr.yt(), dr.yu())) as u16 + 1970_u16;
|
||||
|
||||
return self::datetime::datetime(year, month, day, weekday, hour, minute, second)
|
||||
.map_err(RtcError::InvalidDateTime);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(rcc_h7rm0433))]
|
||||
{
|
||||
let r = RTC::regs();
|
||||
let tr = r.tr().read();
|
||||
self.read(|dr, tr, _| {
|
||||
let second = bcd2_to_byte((tr.st(), tr.su()));
|
||||
let minute = bcd2_to_byte((tr.mnt(), tr.mnu()));
|
||||
let hour = bcd2_to_byte((tr.ht(), tr.hu()));
|
||||
// Reading either RTC_SSR or RTC_TR locks the values in the higher-order
|
||||
// calendar shadow registers until RTC_DR is read.
|
||||
let dr = r.dr().read();
|
||||
|
||||
let weekday = dr.wdu();
|
||||
let day = bcd2_to_byte((dr.dt(), dr.du()));
|
||||
let month = bcd2_to_byte((dr.mt() as u8, dr.mu()));
|
||||
let year = bcd2_to_byte((dr.yt(), dr.yu())) as u16 + 1970_u16;
|
||||
|
||||
self::datetime::datetime(year, month, day, weekday, hour, minute, second).map_err(RtcError::InvalidDateTime)
|
||||
DateTime::from(year, month, day, weekday, hour, minute, second).map_err(RtcError::InvalidDateTime)
|
||||
})
|
||||
}
|
||||
|
||||
fn read<R>(&self, mut f: impl FnMut(Dr, Tr, u16) -> Result<R, RtcError>) -> Result<R, RtcError> {
|
||||
let r = RTC::regs();
|
||||
|
||||
#[cfg(not(rtc_v2f2))]
|
||||
let read_ss = || r.ssr().read().ss();
|
||||
#[cfg(rtc_v2f2)]
|
||||
let read_ss = || 0;
|
||||
|
||||
let mut ss = read_ss();
|
||||
for _ in 0..5 {
|
||||
let tr = r.tr().read();
|
||||
let dr = r.dr().read();
|
||||
let ss_after = read_ss();
|
||||
|
||||
// If an RTCCLK edge occurs during read we may see inconsistent values
|
||||
// so read ssr again and see if it has changed. (see RM0433 Rev 7 46.3.9)
|
||||
if ss == ss_after {
|
||||
return f(dr, tr, ss.try_into().unwrap());
|
||||
} else {
|
||||
ss = ss_after
|
||||
}
|
||||
}
|
||||
|
||||
return Err(RtcError::ReadFailure);
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,11 +208,7 @@ impl Default for RtcCalibrationCyclePeriod {
|
||||
impl Rtc {
|
||||
pub fn new(_rtc: impl Peripheral<P = RTC>, rtc_config: RtcConfig) -> Self {
|
||||
#[cfg(not(any(stm32l0, stm32f3, stm32l1, stm32f0, stm32f2)))]
|
||||
critical_section::with(|cs| {
|
||||
<RTC as crate::rcc::sealed::RccPeripheral>::enable_and_reset_with_cs(cs);
|
||||
#[cfg(feature = "low-power")]
|
||||
crate::rcc::clock_refcount_sub(cs);
|
||||
});
|
||||
<RTC as crate::rcc::sealed::RccPeripheral>::enable_and_reset();
|
||||
|
||||
let mut this = Self {
|
||||
#[cfg(feature = "low-power")]
|
||||
@ -203,6 +223,14 @@ impl Rtc {
|
||||
|
||||
this.configure(async_psc, sync_psc);
|
||||
|
||||
// Wait for the clock to update after initialization
|
||||
#[cfg(not(rtc_v2f2))]
|
||||
{
|
||||
let now = this.instant().unwrap();
|
||||
|
||||
while this.instant().unwrap().subsecond == now.subsecond {}
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
@ -222,24 +250,47 @@ impl Rtc {
|
||||
///
|
||||
/// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range.
|
||||
pub fn set_datetime(&mut self, t: DateTime) -> Result<(), RtcError> {
|
||||
self::datetime::validate_datetime(&t).map_err(RtcError::InvalidDateTime)?;
|
||||
self.write(true, |rtc| self::datetime::write_date_time(rtc, t));
|
||||
self.write(true, |rtc| {
|
||||
let (ht, hu) = byte_to_bcd2(t.hour() as u8);
|
||||
let (mnt, mnu) = byte_to_bcd2(t.minute() as u8);
|
||||
let (st, su) = byte_to_bcd2(t.second() as u8);
|
||||
|
||||
let (dt, du) = byte_to_bcd2(t.day() as u8);
|
||||
let (mt, mu) = byte_to_bcd2(t.month() as u8);
|
||||
let yr = t.year() as u16;
|
||||
let yr_offset = (yr - 1970_u16) as u8;
|
||||
let (yt, yu) = byte_to_bcd2(yr_offset);
|
||||
|
||||
use crate::pac::rtc::vals::Ampm;
|
||||
|
||||
rtc.tr().write(|w| {
|
||||
w.set_ht(ht);
|
||||
w.set_hu(hu);
|
||||
w.set_mnt(mnt);
|
||||
w.set_mnu(mnu);
|
||||
w.set_st(st);
|
||||
w.set_su(su);
|
||||
w.set_pm(Ampm::AM);
|
||||
});
|
||||
|
||||
rtc.dr().write(|w| {
|
||||
w.set_dt(dt);
|
||||
w.set_du(du);
|
||||
w.set_mt(mt > 0);
|
||||
w.set_mu(mu);
|
||||
w.set_yt(yt);
|
||||
w.set_yu(yu);
|
||||
w.set_wdu(day_of_week_to_u8(t.day_of_week()));
|
||||
});
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "low-power")]
|
||||
#[cfg(not(rtc_v2f2))]
|
||||
/// Return the current instant.
|
||||
fn instant(&self) -> RtcInstant {
|
||||
let r = RTC::regs();
|
||||
let tr = r.tr().read();
|
||||
let subsecond = r.ssr().read().ss();
|
||||
let second = bcd2_to_byte((tr.st(), tr.su()));
|
||||
|
||||
// Unlock the registers
|
||||
r.dr().read();
|
||||
|
||||
RtcInstant { second, subsecond }
|
||||
fn instant(&self) -> Result<RtcInstant, RtcError> {
|
||||
self.time_provider().instant()
|
||||
}
|
||||
|
||||
/// Return the current datetime.
|
||||
@ -281,6 +332,114 @@ impl Rtc {
|
||||
pub fn write_backup_register(&self, register: usize, value: u32) {
|
||||
RTC::write_backup_register(&RTC::regs(), register, value)
|
||||
}
|
||||
|
||||
#[cfg(feature = "low-power")]
|
||||
/// start the wakeup alarm and wtih a duration that is as close to but less than
|
||||
/// the requested duration, and record the instant the wakeup alarm was started
|
||||
pub(crate) fn start_wakeup_alarm(
|
||||
&self,
|
||||
requested_duration: embassy_time::Duration,
|
||||
cs: critical_section::CriticalSection,
|
||||
) {
|
||||
use embassy_time::{Duration, TICK_HZ};
|
||||
|
||||
#[cfg(any(rtc_v3, rtc_v3u5))]
|
||||
use crate::pac::rtc::vals::Calrf;
|
||||
|
||||
// Panic if the rcc mod knows we're not using low-power rtc
|
||||
#[cfg(any(rcc_wb, rcc_f4, rcc_f410))]
|
||||
unsafe { crate::rcc::get_freqs() }.rtc.unwrap();
|
||||
|
||||
let requested_duration = requested_duration.as_ticks().clamp(0, u32::MAX as u64);
|
||||
let rtc_hz = Self::frequency().0 as u64;
|
||||
let rtc_ticks = requested_duration * rtc_hz / TICK_HZ;
|
||||
let prescaler = WakeupPrescaler::compute_min((rtc_ticks / u16::MAX as u64) as u32);
|
||||
|
||||
// adjust the rtc ticks to the prescaler and subtract one rtc tick
|
||||
let rtc_ticks = rtc_ticks / prescaler as u64;
|
||||
let rtc_ticks = rtc_ticks.clamp(0, (u16::MAX - 1) as u64).saturating_sub(1) as u16;
|
||||
|
||||
self.write(false, |regs| {
|
||||
regs.cr().modify(|w| w.set_wute(false));
|
||||
|
||||
#[cfg(any(
|
||||
rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb
|
||||
))]
|
||||
{
|
||||
regs.isr().modify(|w| w.set_wutf(false));
|
||||
while !regs.isr().read().wutwf() {}
|
||||
}
|
||||
|
||||
#[cfg(any(rtc_v3, rtc_v3u5))]
|
||||
{
|
||||
regs.scr().write(|w| w.set_cwutf(Calrf::CLEAR));
|
||||
while !regs.icsr().read().wutwf() {}
|
||||
}
|
||||
|
||||
regs.cr().modify(|w| w.set_wucksel(prescaler.into()));
|
||||
regs.wutr().write(|w| w.set_wut(rtc_ticks));
|
||||
regs.cr().modify(|w| w.set_wute(true));
|
||||
regs.cr().modify(|w| w.set_wutie(true));
|
||||
});
|
||||
|
||||
let instant = self.instant().unwrap();
|
||||
trace!(
|
||||
"rtc: start wakeup alarm for {} ms (psc: {}, ticks: {}) at {}",
|
||||
Duration::from_ticks(rtc_ticks as u64 * TICK_HZ * prescaler as u64 / rtc_hz).as_millis(),
|
||||
prescaler as u32,
|
||||
rtc_ticks,
|
||||
instant,
|
||||
);
|
||||
|
||||
assert!(self.stop_time.borrow(cs).replace(Some(instant)).is_none())
|
||||
}
|
||||
|
||||
#[cfg(feature = "low-power")]
|
||||
/// stop the wakeup alarm and return the time elapsed since `start_wakeup_alarm`
|
||||
/// was called, otherwise none
|
||||
pub(crate) fn stop_wakeup_alarm(&self, cs: critical_section::CriticalSection) -> Option<embassy_time::Duration> {
|
||||
use crate::interrupt::typelevel::Interrupt;
|
||||
#[cfg(any(rtc_v3, rtc_v3u5))]
|
||||
use crate::pac::rtc::vals::Calrf;
|
||||
|
||||
let instant = self.instant().unwrap();
|
||||
if RTC::regs().cr().read().wute() {
|
||||
trace!("rtc: stop wakeup alarm at {}", instant);
|
||||
|
||||
self.write(false, |regs| {
|
||||
regs.cr().modify(|w| w.set_wutie(false));
|
||||
regs.cr().modify(|w| w.set_wute(false));
|
||||
|
||||
#[cfg(any(
|
||||
rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb
|
||||
))]
|
||||
regs.isr().modify(|w| w.set_wutf(false));
|
||||
|
||||
#[cfg(any(rtc_v3, rtc_v3u5))]
|
||||
regs.scr().write(|w| w.set_cwutf(Calrf::CLEAR));
|
||||
|
||||
crate::pac::EXTI
|
||||
.pr(0)
|
||||
.modify(|w| w.set_line(RTC::EXTI_WAKEUP_LINE, true));
|
||||
|
||||
<RTC as crate::rtc::sealed::Instance>::WakeupInterrupt::unpend();
|
||||
});
|
||||
}
|
||||
|
||||
self.stop_time.borrow(cs).take().map(|stop_time| instant - stop_time)
|
||||
}
|
||||
|
||||
#[cfg(feature = "low-power")]
|
||||
pub(crate) fn enable_wakeup_line(&self) {
|
||||
use crate::interrupt::typelevel::Interrupt;
|
||||
use crate::pac::EXTI;
|
||||
|
||||
<RTC as crate::rtc::sealed::Instance>::WakeupInterrupt::unpend();
|
||||
unsafe { <RTC as crate::rtc::sealed::Instance>::WakeupInterrupt::enable() };
|
||||
|
||||
EXTI.rtsr(0).modify(|w| w.set_line(RTC::EXTI_WAKEUP_LINE, true));
|
||||
EXTI.imr(0).modify(|w| w.set_line(RTC::EXTI_WAKEUP_LINE, true));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn byte_to_bcd2(byte: u8) -> (u8, u8) {
|
||||
|
@ -6,159 +6,20 @@ use crate::peripherals::RTC;
|
||||
use crate::rtc::sealed::Instance;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum WakeupPrescaler {
|
||||
Div2 = 2,
|
||||
Div4 = 4,
|
||||
Div8 = 8,
|
||||
Div16 = 16,
|
||||
}
|
||||
|
||||
#[cfg(any(stm32wb, stm32f4, stm32l0))]
|
||||
impl From<WakeupPrescaler> for crate::pac::rtc::vals::Wucksel {
|
||||
fn from(val: WakeupPrescaler) -> Self {
|
||||
use crate::pac::rtc::vals::Wucksel;
|
||||
|
||||
match val {
|
||||
WakeupPrescaler::Div2 => Wucksel::DIV2,
|
||||
WakeupPrescaler::Div4 => Wucksel::DIV4,
|
||||
WakeupPrescaler::Div8 => Wucksel::DIV8,
|
||||
WakeupPrescaler::Div16 => Wucksel::DIV16,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(stm32wb, stm32f4, stm32l0))]
|
||||
impl From<crate::pac::rtc::vals::Wucksel> for WakeupPrescaler {
|
||||
fn from(val: crate::pac::rtc::vals::Wucksel) -> Self {
|
||||
use crate::pac::rtc::vals::Wucksel;
|
||||
|
||||
match val {
|
||||
Wucksel::DIV2 => WakeupPrescaler::Div2,
|
||||
Wucksel::DIV4 => WakeupPrescaler::Div4,
|
||||
Wucksel::DIV8 => WakeupPrescaler::Div8,
|
||||
Wucksel::DIV16 => WakeupPrescaler::Div16,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl WakeupPrescaler {
|
||||
pub fn compute_min(val: u32) -> Self {
|
||||
*[
|
||||
WakeupPrescaler::Div2,
|
||||
WakeupPrescaler::Div4,
|
||||
WakeupPrescaler::Div8,
|
||||
WakeupPrescaler::Div16,
|
||||
]
|
||||
.iter()
|
||||
.skip_while(|psc| **psc as u32 <= val)
|
||||
.next()
|
||||
.unwrap_or(&WakeupPrescaler::Div16)
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Rtc {
|
||||
#[cfg(feature = "low-power")]
|
||||
/// start the wakeup alarm and wtih a duration that is as close to but less than
|
||||
/// the requested duration, and record the instant the wakeup alarm was started
|
||||
pub(crate) fn start_wakeup_alarm(
|
||||
&self,
|
||||
requested_duration: embassy_time::Duration,
|
||||
cs: critical_section::CriticalSection,
|
||||
) {
|
||||
use embassy_time::{Duration, TICK_HZ};
|
||||
|
||||
// Panic if the rcc mod knows we're not using low-power rtc
|
||||
#[cfg(any(rcc_wb, rcc_f4, rcc_f410))]
|
||||
unsafe { crate::rcc::get_freqs() }.rtc.unwrap();
|
||||
|
||||
let requested_duration = requested_duration.as_ticks().clamp(0, u32::MAX as u64);
|
||||
let rtc_hz = Self::frequency().0 as u64;
|
||||
let rtc_ticks = requested_duration * rtc_hz / TICK_HZ;
|
||||
let prescaler = WakeupPrescaler::compute_min((rtc_ticks / u16::MAX as u64) as u32);
|
||||
|
||||
// adjust the rtc ticks to the prescaler and subtract one rtc tick
|
||||
let rtc_ticks = rtc_ticks / prescaler as u64;
|
||||
let rtc_ticks = rtc_ticks.clamp(0, (u16::MAX - 1) as u64).saturating_sub(1) as u16;
|
||||
|
||||
self.write(false, |regs| {
|
||||
regs.cr().modify(|w| w.set_wute(false));
|
||||
regs.isr().modify(|w| w.set_wutf(false));
|
||||
while !regs.isr().read().wutwf() {}
|
||||
|
||||
regs.cr().modify(|w| w.set_wucksel(prescaler.into()));
|
||||
regs.wutr().write(|w| w.set_wut(rtc_ticks));
|
||||
regs.cr().modify(|w| w.set_wute(true));
|
||||
regs.cr().modify(|w| w.set_wutie(true));
|
||||
});
|
||||
|
||||
trace!(
|
||||
"rtc: start wakeup alarm for {} ms (psc: {}, ticks: {}) at {}",
|
||||
Duration::from_ticks(rtc_ticks as u64 * TICK_HZ * prescaler as u64 / rtc_hz).as_millis(),
|
||||
prescaler as u32,
|
||||
rtc_ticks,
|
||||
self.instant(),
|
||||
);
|
||||
|
||||
assert!(self.stop_time.borrow(cs).replace(Some(self.instant())).is_none())
|
||||
}
|
||||
|
||||
#[cfg(feature = "low-power")]
|
||||
/// stop the wakeup alarm and return the time elapsed since `start_wakeup_alarm`
|
||||
/// was called, otherwise none
|
||||
pub(crate) fn stop_wakeup_alarm(&self, cs: critical_section::CriticalSection) -> Option<embassy_time::Duration> {
|
||||
use crate::interrupt::typelevel::Interrupt;
|
||||
|
||||
if RTC::regs().cr().read().wute() {
|
||||
trace!("rtc: stop wakeup alarm at {}", self.instant());
|
||||
|
||||
self.write(false, |regs| {
|
||||
regs.cr().modify(|w| w.set_wutie(false));
|
||||
regs.cr().modify(|w| w.set_wute(false));
|
||||
regs.isr().modify(|w| w.set_wutf(false));
|
||||
|
||||
crate::pac::EXTI
|
||||
.pr(0)
|
||||
.modify(|w| w.set_line(RTC::EXTI_WAKEUP_LINE, true));
|
||||
|
||||
<RTC as crate::rtc::sealed::Instance>::WakeupInterrupt::unpend();
|
||||
});
|
||||
}
|
||||
|
||||
self.stop_time
|
||||
.borrow(cs)
|
||||
.take()
|
||||
.map(|stop_time| self.instant() - stop_time)
|
||||
}
|
||||
|
||||
#[cfg(feature = "low-power")]
|
||||
pub(crate) fn enable_wakeup_line(&self) {
|
||||
use crate::interrupt::typelevel::Interrupt;
|
||||
use crate::pac::EXTI;
|
||||
|
||||
<RTC as crate::rtc::sealed::Instance>::WakeupInterrupt::unpend();
|
||||
unsafe { <RTC as crate::rtc::sealed::Instance>::WakeupInterrupt::enable() };
|
||||
|
||||
EXTI.rtsr(0).modify(|w| w.set_line(RTC::EXTI_WAKEUP_LINE, true));
|
||||
EXTI.imr(0).modify(|w| w.set_line(RTC::EXTI_WAKEUP_LINE, true));
|
||||
}
|
||||
|
||||
/// Applies the RTC config
|
||||
/// It this changes the RTC clock source the time will be reset
|
||||
pub(super) fn configure(&mut self, async_psc: u8, sync_psc: u16) {
|
||||
self.write(true, |rtc| {
|
||||
rtc.cr().modify(|w| {
|
||||
#[cfg(not(rtc_v2f2))]
|
||||
w.set_bypshad(true);
|
||||
#[cfg(rtc_v2f2)]
|
||||
w.set_fmt(false);
|
||||
#[cfg(not(rtc_v2f2))]
|
||||
w.set_fmt(stm32_metapac::rtc::vals::Fmt::TWENTY_FOUR_HOUR);
|
||||
w.set_osel(Osel::DISABLED);
|
||||
w.set_pol(Pol::HIGH);
|
||||
#[cfg(rcc_h7rm0433)]
|
||||
w.set_bypshad(true);
|
||||
});
|
||||
|
||||
rtc.prer().modify(|w| {
|
||||
|
@ -11,6 +11,7 @@ impl super::Rtc {
|
||||
pub(super) fn configure(&mut self, async_psc: u8, sync_psc: u16) {
|
||||
self.write(true, |rtc| {
|
||||
rtc.cr().modify(|w| {
|
||||
w.set_bypshad(true);
|
||||
w.set_fmt(Fmt::TWENTYFOURHOUR);
|
||||
w.set_osel(Osel::DISABLED);
|
||||
w.set_pol(Pol::HIGH);
|
||||
@ -94,7 +95,7 @@ impl super::Rtc {
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn write<F, R>(&mut self, init_mode: bool, f: F) -> R
|
||||
pub(super) fn write<F, R>(&self, init_mode: bool, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&crate::pac::rtc::Rtc) -> R,
|
||||
{
|
||||
@ -128,6 +129,12 @@ impl super::Rtc {
|
||||
impl sealed::Instance for crate::peripherals::RTC {
|
||||
const BACKUP_REGISTER_COUNT: usize = 32;
|
||||
|
||||
#[cfg(all(feature = "low-power", stm32g4))]
|
||||
const EXTI_WAKEUP_LINE: usize = 20;
|
||||
|
||||
#[cfg(all(feature = "low-power", stm32g4))]
|
||||
type WakeupInterrupt = crate::interrupt::typelevel::RTC_WKUP;
|
||||
|
||||
fn read_backup_register(_rtc: &Rtc, register: usize) -> Option<u32> {
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
if register < Self::BACKUP_REGISTER_COUNT {
|
||||
|
@ -207,27 +207,40 @@ impl Protocol {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum SyncEnable {
|
||||
Asynchronous,
|
||||
pub enum SyncInput {
|
||||
/// Not synced to any other SAI unit.
|
||||
None,
|
||||
/// Syncs with the other A/B sub-block within the SAI unit
|
||||
Internal,
|
||||
/// Syncs with a sub-block in the other SAI unit - use set_sync_output() and set_sync_input()
|
||||
#[cfg(any(sai_v4))]
|
||||
External,
|
||||
/// Syncs with a sub-block in the other SAI unit
|
||||
#[cfg(sai_v4)]
|
||||
External(SyncInputInstance),
|
||||
}
|
||||
|
||||
impl SyncEnable {
|
||||
#[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))]
|
||||
impl SyncInput {
|
||||
pub const fn syncen(&self) -> vals::Syncen {
|
||||
match self {
|
||||
SyncEnable::Asynchronous => vals::Syncen::ASYNCHRONOUS,
|
||||
SyncEnable::Internal => vals::Syncen::INTERNAL,
|
||||
SyncInput::None => vals::Syncen::ASYNCHRONOUS,
|
||||
SyncInput::Internal => vals::Syncen::INTERNAL,
|
||||
#[cfg(any(sai_v4))]
|
||||
SyncEnable::External => vals::Syncen::EXTERNAL,
|
||||
SyncInput::External(_) => vals::Syncen::EXTERNAL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(sai_v4)]
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum SyncInputInstance {
|
||||
#[cfg(peri_sai1)]
|
||||
Sai1 = 0,
|
||||
#[cfg(peri_sai2)]
|
||||
Sai2 = 1,
|
||||
#[cfg(peri_sai3)]
|
||||
Sai3 = 2,
|
||||
#[cfg(peri_sai4)]
|
||||
Sai4 = 3,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum StereoMono {
|
||||
Stereo,
|
||||
@ -428,8 +441,8 @@ impl MasterClockDivider {
|
||||
pub struct Config {
|
||||
pub mode: Mode,
|
||||
pub tx_rx: TxRx,
|
||||
pub sync_enable: SyncEnable,
|
||||
pub is_sync_output: bool,
|
||||
pub sync_input: SyncInput,
|
||||
pub sync_output: bool,
|
||||
pub protocol: Protocol,
|
||||
pub slot_size: SlotSize,
|
||||
pub slot_count: word::U4,
|
||||
@ -459,8 +472,8 @@ impl Default for Config {
|
||||
Self {
|
||||
mode: Mode::Master,
|
||||
tx_rx: TxRx::Transmitter,
|
||||
is_sync_output: false,
|
||||
sync_enable: SyncEnable::Asynchronous,
|
||||
sync_output: false,
|
||||
sync_input: SyncInput::None,
|
||||
protocol: Protocol::Free,
|
||||
slot_size: SlotSize::DataSize,
|
||||
slot_count: word::U4(2),
|
||||
@ -608,18 +621,18 @@ impl<'d, T: Instance> Sai<'d, T> {
|
||||
|
||||
fn update_synchronous_config(config: &mut Config) {
|
||||
config.mode = Mode::Slave;
|
||||
config.is_sync_output = false;
|
||||
config.sync_output = false;
|
||||
|
||||
#[cfg(any(sai_v1, sai_v2, sai_v3))]
|
||||
{
|
||||
config.sync_enable = SyncEnable::Internal;
|
||||
config.sync_input = SyncInput::Internal;
|
||||
}
|
||||
|
||||
#[cfg(any(sai_v4))]
|
||||
{
|
||||
//this must either be Internal or External
|
||||
//The asynchronous sub-block on the same SAI needs to enable is_sync_output
|
||||
assert!(config.sync_enable != SyncEnable::Asynchronous);
|
||||
//The asynchronous sub-block on the same SAI needs to enable sync_output
|
||||
assert!(config.sync_input != SyncInput::None);
|
||||
}
|
||||
}
|
||||
|
||||
@ -866,20 +879,13 @@ impl<'d, T: Instance, C: Channel, W: word::Word> SubBlock<'d, T, C, W> {
|
||||
|
||||
#[cfg(any(sai_v4))]
|
||||
{
|
||||
// Not totally clear from the datasheet if this is right
|
||||
// This is only used if using SyncEnable::External on the other SAI unit
|
||||
// Syncing from SAIX subblock A to subblock B does not require this
|
||||
// Only syncing from SAI1 subblock A/B to SAI2 subblock A/B
|
||||
let value: u8 = if T::REGS.as_ptr() == stm32_metapac::SAI1.as_ptr() {
|
||||
1 //this is SAI1, so sync with SAI2
|
||||
} else {
|
||||
0 //this is SAI2, so sync with SAI1
|
||||
};
|
||||
T::REGS.gcr().modify(|w| {
|
||||
w.set_syncin(value);
|
||||
});
|
||||
if let SyncInput::External(i) = config.sync_input {
|
||||
T::REGS.gcr().modify(|w| {
|
||||
w.set_syncin(i as u8);
|
||||
});
|
||||
}
|
||||
|
||||
if config.is_sync_output {
|
||||
if config.sync_output {
|
||||
let syncout: u8 = match sub_block {
|
||||
WhichSubBlock::A => 0b01,
|
||||
WhichSubBlock::B => 0b10,
|
||||
@ -903,7 +909,7 @@ impl<'d, T: Instance, C: Channel, W: word::Word> SubBlock<'d, T, C, W> {
|
||||
w.set_ds(config.data_size.ds());
|
||||
w.set_lsbfirst(config.bit_order.lsbfirst());
|
||||
w.set_ckstr(config.clock_strobe.ckstr());
|
||||
w.set_syncen(config.sync_enable.syncen());
|
||||
w.set_syncen(config.sync_input.syncen());
|
||||
w.set_mono(config.stereo_mono.mono());
|
||||
w.set_outdriv(config.output_drive.outdriv());
|
||||
w.set_mckdiv(config.master_clock_divider.mckdiv());
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user