Compare commits
236 Commits
stm32wl-hi
...
cache-lock
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
9e230b64a4 | |||
17b4cf8ce7 | |||
df4aa0fe25 | |||
188ee59ba6 | |||
591612db7e | |||
d673f8a865 | |||
82593bd404 | |||
a39ae12edc | |||
0ef1cb29f7 | |||
64ab23d17d | |||
18c9bcd44a | |||
e895ea2d8b | |||
b9e13cb5d1 | |||
46ff2c82aa | |||
a84ad741a4 | |||
412bcad2d1 | |||
e70c531d3d | |||
7c5f963d1f | |||
62e1e1637c | |||
3d03c18d4f | |||
2157c5a4e3 | |||
0fb677aad7 | |||
b1d0947a18 | |||
5b3f75dc72 | |||
6f2995cd4c | |||
88ada52146 | |||
d622181205 | |||
630443a4d6 | |||
035800bfbd | |||
c7803bb8f4 | |||
d496a1213c | |||
241488ef1c | |||
88b2cdd6a0 | |||
35ffdf2143 | |||
3cbc687424 | |||
4f7b831676 | |||
f20f170b1f | |||
67010d123c | |||
51708c8ed1 | |||
361fde35cf | |||
7ce3b19389 | |||
10f08445e4 | |||
f24a1b62bb | |||
bbd12c9372 | |||
6906cc9c25 | |||
cb211f88d3 | |||
3f262a2603 | |||
d94b9fe6fb | |||
2aaf4bf96b | |||
0c97ce2fcc | |||
62d6bb6c8a | |||
a9dc887060 | |||
137e47f98d | |||
05a9b11316 | |||
561126b0d6 |
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 '{}' \+
|
12
ci.sh
12
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 \
|
||||
@ -192,9 +194,13 @@ cargo batch \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g071rb --out-dir out/tests/stm32g071rb \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32c031c6 --out-dir out/tests/stm32c031c6 \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi --out-dir out/tests/stm32h755zi \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h753zi --out-dir out/tests/stm32h753zi \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7a3zi --out-dir out/tests/stm32h7a3zi \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --out-dir out/tests/stm32wb55rg \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h563zi --out-dir out/tests/stm32h563zi \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585ai --out-dir out/tests/stm32u585ai \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u5a5zj --out-dir out/tests/stm32u5a5zj \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wba52cg --out-dir out/tests/stm32wba52cg \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l073rz --out-dir out/tests/stm32l073rz \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l152re --out-dir out/tests/stm32l152re \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4a6zg --out-dir out/tests/stm32l4a6zg \
|
||||
@ -213,8 +219,14 @@ cargo batch \
|
||||
|
||||
rm out/tests/stm32wb55rg/wpan_mac
|
||||
rm out/tests/stm32wb55rg/wpan_ble
|
||||
|
||||
|
||||
# unstable, I think it's running out of RAM?
|
||||
rm out/tests/stm32f207zg/eth
|
||||
|
||||
# doesn't work, gives "noise error", no idea why. usart_dma does pass.
|
||||
rm out/tests/stm32u5a5zj/usart
|
||||
|
||||
if [[ -z "${TELEPROBE_TOKEN-}" ]]; then
|
||||
echo No teleprobe token found, skipping running HIL tests
|
||||
exit
|
||||
|
@ -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 ch::driver::LinkState;
|
||||
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,
|
||||
@ -133,7 +139,7 @@ impl<'a> Control<'a> {
|
||||
|
||||
Timer::after_millis(100).await;
|
||||
|
||||
self.state_ch.set_ethernet_address(mac_addr);
|
||||
self.state_ch.set_hardware_address(HardwareAddress::Ethernet(mac_addr));
|
||||
|
||||
debug!("INIT DONE");
|
||||
}
|
||||
@ -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)]
|
||||
#![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;
|
||||
|
||||
|
@ -10,3 +10,4 @@
|
||||
* xref:examples.adoc[Examples]
|
||||
* xref:developer.adoc[Developer]
|
||||
** xref:developer_stm32.adoc[Developer: STM32]
|
||||
* xref:faq.adoc[Frequently Asked Questions]
|
||||
|
@ -48,7 +48,7 @@ The `Spawner` is the way the main application spawns other tasks. The `Periphera
|
||||
include::example$basic/src/main.rs[lines="22..-1"]
|
||||
----
|
||||
|
||||
What happens when the `blinker` task has been spawned and main returns? Well, the main entry point is actually just like any other task, except that you can only have one and it takes some specific type arguments. The magic lies within the `#[embassy::main]` macro. The macro does the following:
|
||||
What happens when the `blinker` task has been spawned and main returns? Well, the main entry point is actually just like any other task, except that you can only have one and it takes some specific type arguments. The magic lies within the `#[embassy_executor::main]` macro. The macro does the following:
|
||||
|
||||
. Creates an Embassy Executor
|
||||
. Initializes the microcontroller HAL to get the `Peripherals`
|
||||
|
25
docs/modules/ROOT/pages/faq.adoc
Normal file
25
docs/modules/ROOT/pages/faq.adoc
Normal file
@ -0,0 +1,25 @@
|
||||
= 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!
|
||||
|
||||
== 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, try_blocks))]
|
||||
#![cfg_attr(feature = "nightly", allow(stable_features, unknown_lints, async_fn_in_trait))]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
//! Utilities to use `embedded-hal` traits with Embassy.
|
||||
|
@ -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"
|
||||
@ -34,7 +34,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)
|
||||
@ -57,12 +57,13 @@ 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"
|
||||
|
||||
# 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 }
|
||||
|
@ -115,12 +115,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 +146,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 +158,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 +167,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 +197,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 +219,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,10 @@ 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,6 +8,9 @@ mod thread {
|
||||
use core::marker::PhantomData;
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
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
|
||||
|
@ -33,6 +33,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;
|
||||
@ -46,7 +47,6 @@ pub use spawner::*;
|
||||
pub mod _export {
|
||||
#[cfg(feature = "rtos-trace")]
|
||||
pub use rtos_trace::trace;
|
||||
pub use static_cell::StaticCell;
|
||||
|
||||
/// Expands the given block of code when `embassy-executor` is compiled with
|
||||
/// the `rtos-trace-interrupt` feature.
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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)]
|
||||
|
@ -5,14 +5,12 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 0.2.0 - 2023-10-15
|
||||
## 0.2.0 - 2023-10-18
|
||||
|
||||
- Update embassy-net-driver
|
||||
- `Runner::new` now takes an `embassy_net_driver::HardwareAddress` parameter
|
||||
- Added `Runner::set_ieee802154_address`, `Runner::ieee802154_address`
|
||||
- Update `embassy-net-driver` to v0.2
|
||||
- `Runner::new` now takes an `embassy_net_driver::HardwareAddress` parameter.
|
||||
- `Runner::set_ethernet_address` is now `set_hardware_address`.
|
||||
|
||||
## 0.1.0 - 2023-06-29
|
||||
|
||||
- First release
|
||||
|
||||
|
||||
|
@ -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" }
|
||||
|
@ -7,7 +7,9 @@ The `embassy-net-driver` trait is polling-based. To implement it, you must write
|
||||
hand, and hook up the `Waker`s provided by `embassy-net` to the right interrupt handlers so that `embassy-net`
|
||||
knows when to poll your driver again to make more progress.
|
||||
|
||||
With `embassy-net-driver-channel`
|
||||
With `embassy-net-driver-channel` you get a "channel-like" interface instead, where you can send/receive packets
|
||||
to/from embassy-net. The intended usage is to spawn a "driver task" in the background that does this, passing
|
||||
packets between the hardware and the channel.
|
||||
|
||||
## A note about deadlocks
|
||||
|
||||
@ -18,19 +20,19 @@ loop {
|
||||
// Wait for either..
|
||||
match select(
|
||||
// ... the chip signaling an interrupt, indicating a packet is available to receive, or
|
||||
irq_pin.wait_for_low(),
|
||||
irq_pin.wait_for_low(),
|
||||
// ... a TX buffer becoming available, i.e. embassy-net wants to send a packet
|
||||
tx_chan.tx_buf(),
|
||||
).await {
|
||||
Either::First(_) => {
|
||||
// a packet is ready to be received!
|
||||
let buf = rx_chan.rx_buf().await; // allocate a rx buf from the packet queue
|
||||
let n = receive_packet_over_spi(buf).await;
|
||||
let n = receive_packet_over_spi(buf).await;
|
||||
rx_chan.rx_done(n);
|
||||
}
|
||||
Either::Second(buf) => {
|
||||
// a packet is ready to be sent!
|
||||
send_packet_over_spi(buf).await;
|
||||
send_packet_over_spi(buf).await;
|
||||
tx_chan.tx_done();
|
||||
}
|
||||
}
|
||||
@ -41,7 +43,7 @@ However, this code has a latent deadlock bug. The symptom is it can hang at `rx_
|
||||
|
||||
The reason is that, under load, both the TX and RX queues can get full at the same time. When this happens, the `embassy-net` task stalls trying to send because the TX queue is full, therefore it stops processing packets in the RX queue. Your driver task also stalls because the RX queue is full, therefore it stops processing packets in the TX queue.
|
||||
|
||||
The fix is to make sure to always service the TX queue while you're waiting for space to become available in the TX queue. For example, select on either "tx_chan.tx_buf() available" or "INT is low AND rx_chan.rx_buf() available":
|
||||
The fix is to make sure to always service the TX queue while you're waiting for space to become available in the RX queue. For example, select on either "tx_chan.tx_buf() available" or "INT is low AND rx_chan.rx_buf() available":
|
||||
|
||||
```rust,ignore
|
||||
loop {
|
||||
@ -58,12 +60,12 @@ loop {
|
||||
).await {
|
||||
Either::First(buf) => {
|
||||
// a packet is ready to be received!
|
||||
let n = receive_packet_over_spi(buf).await;
|
||||
let n = receive_packet_over_spi(buf).await;
|
||||
rx_chan.rx_done(n);
|
||||
}
|
||||
Either::Second(buf) => {
|
||||
// a packet is ready to be sent!
|
||||
send_packet_over_spi(buf).await;
|
||||
send_packet_over_spi(buf).await;
|
||||
tx_chan.tx_done();
|
||||
}
|
||||
}
|
||||
@ -79,12 +81,10 @@ These `embassy-net` drivers are implemented using this crate. You can look at th
|
||||
- [`embassy-net-wiznet`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-wiznet) for Wiznet SPI Ethernet MAC+PHY chips.
|
||||
- [`embassy-net-esp-hosted`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-esp-hosted) for using ESP32 chips with the [`esp-hosted`](https://github.com/espressif/esp-hosted) firmware as WiFi adapters for another non-ESP32 MCU.
|
||||
|
||||
|
||||
## Interoperability
|
||||
|
||||
This crate can run on any executor.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
This work is licensed under either of
|
||||
|
@ -8,9 +8,8 @@ use core::cell::RefCell;
|
||||
use core::mem::MaybeUninit;
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
use driver::HardwareAddress;
|
||||
pub use embassy_net_driver as driver;
|
||||
use embassy_net_driver::{Capabilities, LinkState, Medium};
|
||||
use embassy_net_driver::{Capabilities, LinkState};
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embassy_sync::blocking_mutex::Mutex;
|
||||
use embassy_sync::waitqueue::WakerRegistration;
|
||||
@ -161,18 +160,10 @@ impl<'d> StateRunner<'d> {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_ethernet_address(&self, address: [u8; 6]) {
|
||||
pub fn set_hardware_address(&self, address: driver::HardwareAddress) {
|
||||
self.shared.lock(|s| {
|
||||
let s = &mut *s.borrow_mut();
|
||||
s.hardware_address = driver::HardwareAddress::Ethernet(address);
|
||||
s.waker.wake();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_ieee802154_address(&self, address: [u8; 8]) {
|
||||
self.shared.lock(|s| {
|
||||
let s = &mut *s.borrow_mut();
|
||||
s.hardware_address = driver::HardwareAddress::Ieee802154(address);
|
||||
s.hardware_address = address;
|
||||
s.waker.wake();
|
||||
});
|
||||
}
|
||||
@ -232,11 +223,6 @@ pub fn new<'d, const MTU: usize, const N_RX: usize, const N_TX: usize>(
|
||||
) -> (Runner<'d, MTU>, Device<'d, MTU>) {
|
||||
let mut caps = Capabilities::default();
|
||||
caps.max_transmission_unit = MTU;
|
||||
caps.medium = match &hardware_address {
|
||||
HardwareAddress::Ethernet(_) => Medium::Ethernet,
|
||||
HardwareAddress::Ieee802154(_) => Medium::Ieee802154,
|
||||
HardwareAddress::Ip => Medium::Ip,
|
||||
};
|
||||
|
||||
// safety: this is a self-referential struct, however:
|
||||
// - it can't move while the `'d` borrow is active.
|
||||
|
@ -5,13 +5,13 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 0.2.0 - 2023-10-15
|
||||
## 0.2.0 - 2023-10-18
|
||||
|
||||
- Added `Driver::ieee802154_address`
|
||||
- Added `Medium::Ieee802154`
|
||||
- Added support for IEEE 802.15.4 mediums.
|
||||
- Added `Driver::hardware_address()`, `HardwareAddress`.
|
||||
- Removed `Medium` enum. The medium is deduced out of the hardware address.
|
||||
- Removed `Driver::ethernet_address()`. Replacement is `hardware_address()`.
|
||||
|
||||
## 0.1.0 - 2023-06-29
|
||||
|
||||
- First release
|
||||
|
||||
|
||||
|
@ -7,12 +7,23 @@ use core::task::Context;
|
||||
/// Representation of an hardware address, such as an Ethernet address or an IEEE802.15.4 address.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub enum HardwareAddress {
|
||||
/// A six-octet Ethernet address
|
||||
/// Ethernet medium, with a A six-octet Ethernet address.
|
||||
///
|
||||
/// Devices of this type send and receive Ethernet frames,
|
||||
/// and interfaces using it must do neighbor discovery via ARP or NDISC.
|
||||
///
|
||||
/// Examples of devices of this type are Ethernet, WiFi (802.11), Linux `tap`, and VPNs in tap (layer 2) mode.
|
||||
Ethernet([u8; 6]),
|
||||
/// An eight-octet IEEE802.15.4 address
|
||||
/// 6LoWPAN over IEEE802.15.4, with an eight-octet address.
|
||||
Ieee802154([u8; 8]),
|
||||
/// Indicates that a Driver is IP-native, and has no hardware address
|
||||
/// Indicates that a Driver is IP-native, and has no hardware address.
|
||||
///
|
||||
/// Devices of this type send and receive IP frames, without an
|
||||
/// Ethernet header. MAC addresses are not used, and no neighbor discovery (ARP, NDISC) is done.
|
||||
///
|
||||
/// Examples of devices of this type are the Linux `tun`, PPP interfaces, VPNs in tun (layer 3) mode.
|
||||
Ip,
|
||||
}
|
||||
|
||||
@ -64,6 +75,10 @@ pub trait Driver {
|
||||
fn capabilities(&self) -> Capabilities;
|
||||
|
||||
/// Get the device's hardware address.
|
||||
///
|
||||
/// The returned hardware address also determines the "medium" of this driver. This indicates
|
||||
/// what kind of packet the sent/received bytes are, and determines some behaviors of
|
||||
/// the interface. For example, ARP/NDISC address resolution is only done for Ethernet mediums.
|
||||
fn hardware_address(&self) -> HardwareAddress;
|
||||
}
|
||||
|
||||
@ -124,13 +139,6 @@ pub trait TxToken {
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub struct Capabilities {
|
||||
/// Medium of the device.
|
||||
///
|
||||
/// This indicates what kind of packet the sent/received bytes are, and determines
|
||||
/// some behaviors of Interface. For example, ARP/NDISC address resolution is only done
|
||||
/// for Ethernet mediums.
|
||||
pub medium: Medium,
|
||||
|
||||
/// Maximum transmission unit.
|
||||
///
|
||||
/// The network device is unable to send or receive frames larger than the value returned
|
||||
@ -161,32 +169,6 @@ pub struct Capabilities {
|
||||
pub checksum: ChecksumCapabilities,
|
||||
}
|
||||
|
||||
/// Type of medium of a device.
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Medium {
|
||||
/// Ethernet medium. Devices of this type send and receive Ethernet frames,
|
||||
/// and interfaces using it must do neighbor discovery via ARP or NDISC.
|
||||
///
|
||||
/// Examples of devices of this type are Ethernet, WiFi (802.11), Linux `tap`, and VPNs in tap (layer 2) mode.
|
||||
Ethernet,
|
||||
|
||||
/// IP medium. Devices of this type send and receive IP frames, without an
|
||||
/// Ethernet header. MAC addresses are not used, and no neighbor discovery (ARP, NDISC) is done.
|
||||
///
|
||||
/// Examples of devices of this type are the Linux `tun`, PPP interfaces, VPNs in tun (layer 3) mode.
|
||||
Ip,
|
||||
|
||||
/// IEEE 802_15_4 medium
|
||||
Ieee802154,
|
||||
}
|
||||
|
||||
impl Default for Medium {
|
||||
fn default() -> Medium {
|
||||
Medium::Ethernet
|
||||
}
|
||||
}
|
||||
|
||||
/// A description of checksum behavior for every supported protocol.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
|
@ -19,7 +19,7 @@ mod traits;
|
||||
use core::cmp;
|
||||
use core::convert::TryInto;
|
||||
|
||||
use embassy_net_driver::{Capabilities, HardwareAddress, LinkState, Medium};
|
||||
use embassy_net_driver::{Capabilities, HardwareAddress, LinkState};
|
||||
use embassy_time::Duration;
|
||||
use embedded_hal::digital::OutputPin;
|
||||
use embedded_hal::spi::{Operation, SpiDevice};
|
||||
@ -671,7 +671,6 @@ where
|
||||
fn capabilities(&self) -> Capabilities {
|
||||
let mut caps = Capabilities::default();
|
||||
caps.max_transmission_unit = MTU;
|
||||
caps.medium = Medium::Ethernet;
|
||||
caps
|
||||
}
|
||||
|
||||
|
@ -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/"
|
||||
|
@ -1,5 +1,5 @@
|
||||
use ch::driver::LinkState;
|
||||
use embassy_net_driver_channel as ch;
|
||||
use embassy_net_driver_channel::driver::{HardwareAddress, LinkState};
|
||||
use heapless::String;
|
||||
|
||||
use crate::ioctl::Shared;
|
||||
@ -77,7 +77,7 @@ impl<'a> Control<'a> {
|
||||
|
||||
let mac_addr = self.get_mac_addr().await?;
|
||||
debug!("mac addr: {:02x}", mac_addr);
|
||||
self.state_ch.set_ethernet_address(mac_addr);
|
||||
self.state_ch.set_hardware_address(HardwareAddress::Ethernet(mac_addr));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -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,14 +1,15 @@
|
||||
//! [`embassy-net`](https://crates.io/crates/embassy-net) driver for WIZnet ethernet chips.
|
||||
#![no_std]
|
||||
#![feature(async_fn_in_trait)]
|
||||
#![allow(stable_features, unknown_lints, async_fn_in_trait)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
pub mod chip;
|
||||
mod device;
|
||||
|
||||
use embassy_futures::select::{select, Either};
|
||||
use embassy_futures::select::{select3, Either3};
|
||||
use embassy_net_driver_channel as ch;
|
||||
use embassy_net_driver_channel::driver::LinkState;
|
||||
use embassy_time::Timer;
|
||||
use embassy_time::{Duration, Ticker, Timer};
|
||||
use embedded_hal::digital::OutputPin;
|
||||
use embedded_hal_async::digital::Wait;
|
||||
use embedded_hal_async::spi::SpiDevice;
|
||||
@ -49,32 +50,34 @@ pub struct Runner<'d, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin> {
|
||||
impl<'d, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, C, SPI, INT, RST> {
|
||||
pub async fn run(mut self) -> ! {
|
||||
let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split();
|
||||
let mut tick = Ticker::every(Duration::from_millis(500));
|
||||
loop {
|
||||
if self.mac.is_link_up().await {
|
||||
state_chan.set_link_state(LinkState::Up);
|
||||
loop {
|
||||
match select(
|
||||
async {
|
||||
self.int.wait_for_low().await.ok();
|
||||
rx_chan.rx_buf().await
|
||||
},
|
||||
tx_chan.tx_buf(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Either::First(p) => {
|
||||
if let Ok(n) = self.mac.read_frame(p).await {
|
||||
rx_chan.rx_done(n);
|
||||
}
|
||||
}
|
||||
Either::Second(p) => {
|
||||
self.mac.write_frame(p).await.ok();
|
||||
tx_chan.tx_done();
|
||||
}
|
||||
match select3(
|
||||
async {
|
||||
self.int.wait_for_low().await.ok();
|
||||
rx_chan.rx_buf().await
|
||||
},
|
||||
tx_chan.tx_buf(),
|
||||
tick.next(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Either3::First(p) => {
|
||||
if let Ok(n) = self.mac.read_frame(p).await {
|
||||
rx_chan.rx_done(n);
|
||||
}
|
||||
}
|
||||
Either3::Second(p) => {
|
||||
self.mac.write_frame(p).await.ok();
|
||||
tx_chan.tx_done();
|
||||
}
|
||||
Either3::Third(()) => {
|
||||
if self.mac.is_link_up().await {
|
||||
state_chan.set_link_state(LinkState::Up);
|
||||
} else {
|
||||
state_chan.set_link_state(LinkState::Down);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state_chan.set_link_state(LinkState::Down);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,18 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 0.2.0 - 2023-10-15
|
||||
## Unreleased
|
||||
|
||||
- Avoid never resolving `TcpIo::read` when the output buffer is empty.
|
||||
- Update to `smoltcp` git.
|
||||
- Forward constants from `smoltcp` in DNS query results so changing DNS result size in `smoltcp` properly propagates.
|
||||
|
||||
## 0.2.1 - 2023-10-31
|
||||
|
||||
- Re-add impl_trait_projections
|
||||
- Fix: Reset DHCP socket when the link up is detected
|
||||
|
||||
## 0.2.0 - 2023-10-18
|
||||
|
||||
- Re-export `smoltcp::wire::IpEndpoint`
|
||||
- Add poll functions on UdpSocket
|
||||
@ -27,5 +38,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
## 0.1.0 - 2023-06-29
|
||||
|
||||
- First release
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
use core::task::Context;
|
||||
|
||||
use embassy_net_driver::{Capabilities, Checksum, Driver, Medium, RxToken, TxToken};
|
||||
use smoltcp::phy;
|
||||
use embassy_net_driver::{Capabilities, Checksum, Driver, RxToken, TxToken};
|
||||
use smoltcp::phy::{self, Medium};
|
||||
use smoltcp::time::Instant;
|
||||
|
||||
pub(crate) struct DriverAdapter<'d, 'c, T>
|
||||
@ -11,6 +11,7 @@ where
|
||||
// must be Some when actually using this to rx/tx
|
||||
pub cx: Option<&'d mut Context<'c>>,
|
||||
pub inner: &'d mut T,
|
||||
pub medium: Medium,
|
||||
}
|
||||
|
||||
impl<'d, 'c, T> phy::Device for DriverAdapter<'d, 'c, T>
|
||||
@ -46,19 +47,7 @@ where
|
||||
|
||||
smolcaps.max_transmission_unit = caps.max_transmission_unit;
|
||||
smolcaps.max_burst_size = caps.max_burst_size;
|
||||
smolcaps.medium = match caps.medium {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
Medium::Ethernet => phy::Medium::Ethernet,
|
||||
#[cfg(feature = "medium-ip")]
|
||||
Medium::Ip => phy::Medium::Ip,
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
Medium::Ieee802154 => phy::Medium::Ieee802154,
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => panic!(
|
||||
"Unsupported medium {:?}. Make sure to enable it in embassy-net's Cargo features.",
|
||||
caps.medium
|
||||
),
|
||||
};
|
||||
smolcaps.medium = self.medium;
|
||||
smolcaps.checksum.ipv4 = convert(caps.checksum.ipv4);
|
||||
smolcaps.checksum.tcp = convert(caps.checksum.tcp);
|
||||
smolcaps.checksum.udp = convert(caps.checksum.udp);
|
||||
|
@ -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")]
|
||||
|
||||
@ -33,6 +34,7 @@ use heapless::Vec;
|
||||
pub use smoltcp::iface::MulticastError;
|
||||
#[allow(unused_imports)]
|
||||
use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage};
|
||||
use smoltcp::phy::Medium;
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
use smoltcp::socket::dhcpv4::{self, RetryConfig};
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
@ -264,14 +266,17 @@ pub(crate) struct SocketStack {
|
||||
next_local_port: u16,
|
||||
}
|
||||
|
||||
fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> HardwareAddress {
|
||||
fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> (HardwareAddress, Medium) {
|
||||
match addr {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
driver::HardwareAddress::Ethernet(eth) => HardwareAddress::Ethernet(EthernetAddress(eth)),
|
||||
driver::HardwareAddress::Ethernet(eth) => (HardwareAddress::Ethernet(EthernetAddress(eth)), Medium::Ethernet),
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
driver::HardwareAddress::Ieee802154(ieee) => HardwareAddress::Ieee802154(Ieee802154Address::Extended(ieee)),
|
||||
driver::HardwareAddress::Ieee802154(ieee) => (
|
||||
HardwareAddress::Ieee802154(Ieee802154Address::Extended(ieee)),
|
||||
Medium::Ieee802154,
|
||||
),
|
||||
#[cfg(feature = "medium-ip")]
|
||||
driver::HardwareAddress::Ip => HardwareAddress::Ip,
|
||||
driver::HardwareAddress::Ip => (HardwareAddress::Ip, Medium::Ip),
|
||||
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => panic!(
|
||||
@ -289,7 +294,8 @@ impl<D: Driver> Stack<D> {
|
||||
resources: &'static mut StackResources<SOCK>,
|
||||
random_seed: u64,
|
||||
) -> Self {
|
||||
let mut iface_cfg = smoltcp::iface::Config::new(to_smoltcp_hardware_address(device.hardware_address()));
|
||||
let (hardware_addr, medium) = to_smoltcp_hardware_address(device.hardware_address());
|
||||
let mut iface_cfg = smoltcp::iface::Config::new(hardware_addr);
|
||||
iface_cfg.random_seed = random_seed;
|
||||
|
||||
let iface = Interface::new(
|
||||
@ -297,6 +303,7 @@ impl<D: Driver> Stack<D> {
|
||||
&mut DriverAdapter {
|
||||
inner: &mut device,
|
||||
cx: None,
|
||||
medium,
|
||||
},
|
||||
instant_to_smoltcp(Instant::now()),
|
||||
);
|
||||
@ -356,7 +363,7 @@ impl<D: Driver> Stack<D> {
|
||||
|
||||
/// Get the hardware address of the network interface.
|
||||
pub fn hardware_address(&self) -> HardwareAddress {
|
||||
self.with(|_s, i| to_smoltcp_hardware_address(i.device.hardware_address()))
|
||||
self.with(|_s, i| to_smoltcp_hardware_address(i.device.hardware_address()).0)
|
||||
}
|
||||
|
||||
/// Get whether the link is up.
|
||||
@ -487,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")]
|
||||
@ -605,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
|
||||
@ -642,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
|
||||
@ -812,18 +827,28 @@ impl<D: Driver> Inner<D> {
|
||||
fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) {
|
||||
s.waker.register(cx.waker());
|
||||
|
||||
let (_hardware_addr, medium) = to_smoltcp_hardware_address(self.device.hardware_address());
|
||||
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
if self.device.capabilities().medium == embassy_net_driver::Medium::Ethernet
|
||||
|| self.device.capabilities().medium == embassy_net_driver::Medium::Ieee802154
|
||||
{
|
||||
s.iface
|
||||
.set_hardware_addr(to_smoltcp_hardware_address(self.device.hardware_address()));
|
||||
let do_set = match medium {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
Medium::Ethernet => true,
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
Medium::Ieee802154 => true,
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => false,
|
||||
};
|
||||
if do_set {
|
||||
s.iface.set_hardware_addr(_hardware_addr);
|
||||
}
|
||||
}
|
||||
|
||||
let timestamp = instant_to_smoltcp(Instant::now());
|
||||
let mut smoldev = DriverAdapter {
|
||||
cx: Some(cx),
|
||||
inner: &mut self.device,
|
||||
medium,
|
||||
};
|
||||
s.iface.poll(timestamp, &mut smoldev, &mut s.sockets);
|
||||
|
||||
@ -844,6 +869,9 @@ impl<D: Driver> Inner<D> {
|
||||
let socket = s.sockets.get_mut::<dhcpv4::Socket>(dhcp_handle);
|
||||
|
||||
if self.link_up {
|
||||
if old_link_up != self.link_up {
|
||||
socket.reset();
|
||||
}
|
||||
match socket.poll() {
|
||||
None => {}
|
||||
Some(dhcpv4::Event::Deconfigured) => {
|
||||
|
@ -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" }
|
||||
|
@ -213,6 +213,7 @@ impl<'d> Adc<'d, Async> {
|
||||
ch: &mut Channel<'_>,
|
||||
buf: &mut [W],
|
||||
fcs_err: bool,
|
||||
div: u16,
|
||||
dma: impl Peripheral<P = impl dma::Channel>,
|
||||
) -> Result<(), Error> {
|
||||
let r = Self::regs();
|
||||
@ -258,6 +259,7 @@ impl<'d> Adc<'d, Async> {
|
||||
// start conversions and wait for dma to finish. we can't report errors early
|
||||
// because there's no interrupt to signal them, and inspecting every element
|
||||
// of the fifo is too costly to do here.
|
||||
r.div().write_set(|w| w.set_int(div));
|
||||
r.cs().write_set(|w| w.set_start_many(true));
|
||||
dma.await;
|
||||
mem::drop(auto_reset);
|
||||
@ -275,9 +277,10 @@ impl<'d> Adc<'d, Async> {
|
||||
&mut self,
|
||||
ch: &mut Channel<'_>,
|
||||
buf: &mut [S],
|
||||
div: u16,
|
||||
dma: impl Peripheral<P = impl dma::Channel>,
|
||||
) -> Result<(), Error> {
|
||||
self.read_many_inner(ch, buf, false, dma).await
|
||||
self.read_many_inner(ch, buf, false, div, dma).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -285,11 +288,12 @@ impl<'d> Adc<'d, Async> {
|
||||
&mut self,
|
||||
ch: &mut Channel<'_>,
|
||||
buf: &mut [Sample],
|
||||
div: u16,
|
||||
dma: impl Peripheral<P = impl dma::Channel>,
|
||||
) {
|
||||
// errors are reported in individual samples
|
||||
let _ = self
|
||||
.read_many_inner(ch, unsafe { mem::transmute::<_, &mut [u16]>(buf) }, true, dma)
|
||||
.read_many_inner(ch, unsafe { mem::transmute::<_, &mut [u16]>(buf) }, true, div, dma)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -10,16 +10,39 @@ use crate::gpio::sealed::Pin as _;
|
||||
use crate::gpio::{AnyPin, Pin as GpioPin};
|
||||
use crate::{pac, peripherals, RegExt};
|
||||
|
||||
/// The configuration of a PWM slice.
|
||||
/// Note the period in clock cycles of a slice can be computed as:
|
||||
/// `(top + 1) * (phase_correct ? 1 : 2) * divider`
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone)]
|
||||
pub struct Config {
|
||||
/// Inverts the PWM output signal on channel A.
|
||||
pub invert_a: bool,
|
||||
/// Inverts the PWM output signal on channel B.
|
||||
pub invert_b: bool,
|
||||
/// Enables phase-correct mode for PWM operation.
|
||||
/// In phase-correct mode, the PWM signal is generated in such a way that
|
||||
/// the pulse is always centered regardless of the duty cycle.
|
||||
/// The output frequency is halved when phase-correct mode is enabled.
|
||||
pub phase_correct: bool,
|
||||
/// Enables the PWM slice, allowing it to generate an output.
|
||||
pub enable: bool,
|
||||
/// A fractional clock divider, represented as a fixed-point number with
|
||||
/// 8 integer bits and 4 fractional bits. It allows precise control over
|
||||
/// the PWM output frequency by gating the PWM counter increment.
|
||||
/// A higher value will result in a slower output frequency.
|
||||
pub divider: fixed::FixedU16<fixed::types::extra::U4>,
|
||||
/// The output on channel A goes high when `compare_a` is higher than the
|
||||
/// counter. A compare of 0 will produce an always low output, while a
|
||||
/// compare of `top + 1` will produce an always high output.
|
||||
pub compare_a: u16,
|
||||
/// The output on channel B goes high when `compare_b` is higher than the
|
||||
/// counter. A compare of 0 will produce an always low output, while a
|
||||
/// compare of `top + 1` will produce an always high output.
|
||||
pub compare_b: u16,
|
||||
/// The point at which the counter wraps, representing the maximum possible
|
||||
/// period. The counter will either wrap to 0 or reverse depending on the
|
||||
/// setting of `phase_correct`.
|
||||
pub top: u16,
|
||||
}
|
||||
|
||||
@ -173,6 +196,9 @@ impl<'d, T: Channel> Pwm<'d, T> {
|
||||
});
|
||||
}
|
||||
|
||||
/// Advances a slice’s output phase by one count while it is running
|
||||
/// by inserting a pulse into the clock enable. The counter
|
||||
/// will not count faster than once per cycle.
|
||||
#[inline]
|
||||
pub fn phase_advance(&mut self) {
|
||||
let p = self.inner.regs();
|
||||
@ -180,6 +206,9 @@ impl<'d, T: Channel> Pwm<'d, T> {
|
||||
while p.csr().read().ph_adv() {}
|
||||
}
|
||||
|
||||
/// Retards a slice’s output phase by one count while it is running
|
||||
/// by deleting a pulse from the clock enable. The counter will not
|
||||
/// count backward when clock enable is permenantly low.
|
||||
#[inline]
|
||||
pub fn phase_retard(&mut self) {
|
||||
let p = self.inner.regs();
|
||||
|
@ -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,5 +1,9 @@
|
||||
#![no_std]
|
||||
#![cfg_attr(any(feature = "ble", feature = "mac"), feature(async_fn_in_trait))]
|
||||
#![cfg_attr(
|
||||
any(feature = "ble", feature = "mac"),
|
||||
allow(stable_features, unknown_lints, async_fn_in_trait)
|
||||
)]
|
||||
#![cfg_attr(feature = "mac", feature(type_alias_impl_trait, concat_bytes))]
|
||||
|
||||
// This must go FIRST so that all the other modules see its macros.
|
||||
|
@ -1,9 +1,8 @@
|
||||
#![allow(incomplete_features)]
|
||||
#![deny(unused_must_use)]
|
||||
|
||||
use core::task::Context;
|
||||
|
||||
use embassy_net_driver::{Capabilities, HardwareAddress, LinkState, Medium};
|
||||
use embassy_net_driver::{Capabilities, HardwareAddress, LinkState};
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_sync::channel::Channel;
|
||||
|
||||
@ -60,24 +59,15 @@ impl<'d> embassy_net_driver::Driver for Driver<'d> {
|
||||
let mut caps = Capabilities::default();
|
||||
caps.max_transmission_unit = MTU;
|
||||
// caps.max_burst_size = Some(self.tx.len());
|
||||
|
||||
caps.medium = Medium::Ieee802154;
|
||||
caps
|
||||
}
|
||||
|
||||
fn link_state(&mut self, _cx: &mut Context) -> LinkState {
|
||||
// if self.phy.poll_link(&mut self.station_management, cx) {
|
||||
// LinkState::Up
|
||||
// } else {
|
||||
// LinkState::Down
|
||||
// }
|
||||
|
||||
LinkState::Down
|
||||
}
|
||||
|
||||
fn hardware_address(&self) -> HardwareAddress {
|
||||
// self.mac_addr
|
||||
|
||||
HardwareAddress::Ieee802154([0; 8])
|
||||
}
|
||||
}
|
||||
|
@ -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-c20cbde88fdfaef4645361d09df0cb63a4dc6462" }
|
||||
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-fbb8f77326dd066aa6c0d66b3b46e76a569dda8b" }
|
||||
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-c20cbde88fdfaef4645361d09df0cb63a4dc6462", default-features = false, features = ["metadata"]}
|
||||
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-fbb8f77326dd066aa6c0d66b3b46e76a569dda8b", 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/)
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
use std::fmt::Write as _;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
@ -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,6 +61,7 @@ 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" => {
|
||||
@ -352,7 +353,7 @@ fn main() {
|
||||
// ========
|
||||
// Generate DMA IRQs.
|
||||
|
||||
let mut dma_irqs: HashMap<&str, Vec<(&str, &str, &str)>> = HashMap::new();
|
||||
let mut dma_irqs: BTreeMap<&str, Vec<(&str, &str, &str)>> = BTreeMap::new();
|
||||
|
||||
for p in METADATA.peripherals {
|
||||
if let Some(r) = &p.registers {
|
||||
@ -371,22 +372,27 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
for (irq, channels) in dma_irqs {
|
||||
let irq = format_ident!("{}", irq);
|
||||
let dma_irqs: TokenStream = dma_irqs
|
||||
.iter()
|
||||
.map(|(irq, channels)| {
|
||||
let irq = format_ident!("{}", irq);
|
||||
|
||||
let xdma = format_ident!("{}", channels[0].0);
|
||||
let channels = channels.iter().map(|(_, dma, ch)| format_ident!("{}_{}", dma, ch));
|
||||
let xdma = format_ident!("{}", channels[0].0);
|
||||
let channels = channels.iter().map(|(_, dma, ch)| format_ident!("{}_{}", dma, ch));
|
||||
|
||||
g.extend(quote! {
|
||||
#[cfg(feature = "rt")]
|
||||
#[crate::interrupt]
|
||||
unsafe fn #irq () {
|
||||
#(
|
||||
<crate::peripherals::#channels as crate::dma::#xdma::sealed::Channel>::on_irq();
|
||||
)*
|
||||
quote! {
|
||||
#[cfg(feature = "rt")]
|
||||
#[crate::interrupt]
|
||||
unsafe fn #irq () {
|
||||
#(
|
||||
<crate::peripherals::#channels as crate::dma::#xdma::sealed::Channel>::on_irq();
|
||||
)*
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
g.extend(dma_irqs);
|
||||
|
||||
// ========
|
||||
// Extract the rcc registers
|
||||
@ -433,7 +439,7 @@ fn main() {
|
||||
// Generate RccPeripheral impls
|
||||
|
||||
let refcounted_peripherals = HashSet::from(["usart", "adc"]);
|
||||
let mut refcount_statics = HashSet::new();
|
||||
let mut refcount_statics = BTreeSet::new();
|
||||
|
||||
for p in METADATA.peripherals {
|
||||
if !singletons.contains(&p.name.to_string()) {
|
||||
@ -551,6 +557,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 {
|
||||
@ -558,8 +589,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
|
||||
@ -567,8 +597,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
|
||||
}
|
||||
}
|
||||
|
||||
@ -799,7 +828,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)),
|
||||
@ -915,17 +944,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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -763,6 +763,13 @@ pub(crate) unsafe fn init(_cs: CriticalSection) {
|
||||
<crate::peripherals::AFIO as crate::rcc::sealed::RccPeripheral>::enable_and_reset_with_cs(_cs);
|
||||
|
||||
crate::_generated::init_gpio();
|
||||
|
||||
// Setting this bit is mandatory to use PG[15:2].
|
||||
#[cfg(stm32u5)]
|
||||
crate::pac::PWR.svmcr().modify(|w| {
|
||||
w.set_io2sv(true);
|
||||
w.set_io2vmen(true);
|
||||
});
|
||||
}
|
||||
|
||||
mod eh02 {
|
||||
|
@ -1,21 +1,16 @@
|
||||
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 crate::dma::{NoDma, Transfer};
|
||||
use crate::gpio::sealed::AFType;
|
||||
use crate::gpio::Pull;
|
||||
use crate::i2c::{Error, Instance, SclPin, SdaPin};
|
||||
@ -24,6 +19,23 @@ use crate::pac::i2c;
|
||||
use crate::time::Hertz;
|
||||
use crate::{interrupt, Peripheral};
|
||||
|
||||
#[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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "time"))]
|
||||
pub fn no_timeout_fn() -> impl Fn() -> Result<(), Error> {
|
||||
move || Ok(())
|
||||
}
|
||||
|
||||
/// Interrupt handler.
|
||||
pub struct InterruptHandler<T: Instance> {
|
||||
_phantom: PhantomData<T>,
|
||||
@ -260,21 +272,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 +440,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 +530,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 +611,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 +658,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 +669,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 +757,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
|
||||
|
||||
@ -1201,15 +1233,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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ use core::task::Poll;
|
||||
use self::sealed::Instance;
|
||||
use crate::interrupt;
|
||||
use crate::interrupt::typelevel::Interrupt;
|
||||
use crate::pac::rcc::vals::{Lptim1sel, Lptim2sel};
|
||||
use crate::peripherals::IPCC;
|
||||
use crate::rcc::sealed::RccPeripheral;
|
||||
|
||||
@ -273,7 +274,7 @@ fn _configure_pwr() {
|
||||
|
||||
// set LPTIM1 & LPTIM2 clock source
|
||||
rcc.ccipr().modify(|w| {
|
||||
w.set_lptim1sel(0b00); // PCLK
|
||||
w.set_lptim2sel(0b00); // PCLK
|
||||
w.set_lptim1sel(Lptim1sel::PCLK1);
|
||||
w.set_lptim2sel(Lptim2sel::PCLK1);
|
||||
});
|
||||
}
|
||||
|
@ -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,43 +194,108 @@ 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) => {
|
||||
impl<'d, 'p, P: NonInvertingPin<crate::peripherals::$inst>> crate::adc::sealed::AdcPin<crate::peripherals::$adc>
|
||||
for OpAmpOutput<'d, 'p, crate::peripherals::$inst, P>
|
||||
{
|
||||
fn channel(&self) -> u8 {
|
||||
$ch
|
||||
}
|
||||
}
|
||||
foreach_adc!(
|
||||
($adc, $common_inst:ident, $adc_clock:ident) => {
|
||||
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>
|
||||
{
|
||||
}
|
||||
};
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[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);
|
||||
};
|
||||
);
|
||||
|
||||
@ -141,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 {
|
||||
@ -157,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 {}
|
||||
};
|
||||
}
|
||||
|
469
embassy-stm32/src/rcc/f.rs
Normal file
469
embassy-stm32/src/rcc/f.rs
Normal file
@ -0,0 +1,469 @@
|
||||
use stm32_metapac::flash::vals::Latency;
|
||||
|
||||
pub use crate::pac::rcc::vals::{
|
||||
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;
|
||||
|
||||
// TODO: on some F4s, PLLM is shared between all PLLs. Enforce that.
|
||||
// TODO: on some F4s, add support for plli2s_src
|
||||
//
|
||||
// plli2s plli2s_m plli2s_src pllsai pllsai_m
|
||||
// f401 y shared
|
||||
// f410
|
||||
// f411 y individual
|
||||
// f412 y individual y
|
||||
// f4[12]3 y individual y
|
||||
// f446 y individual y individual
|
||||
// f4[67]9 y shared y shared
|
||||
// f4[23][79] y shared y shared
|
||||
// f4[01][57] y shared
|
||||
|
||||
/// HSI speed
|
||||
pub const HSI_FREQ: Hertz = Hertz(16_000_000);
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum HseMode {
|
||||
/// crystal/ceramic oscillator (HSEBYP=0)
|
||||
Oscillator,
|
||||
/// external analog clock (low swing) (HSEBYP=1)
|
||||
Bypass,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub struct Hse {
|
||||
/// HSE frequency.
|
||||
pub freq: Hertz,
|
||||
/// HSE mode.
|
||||
pub mode: HseMode,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Pll {
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
/// 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
|
||||
#[non_exhaustive]
|
||||
pub struct Config {
|
||||
pub hsi: bool,
|
||||
pub hse: Option<Hse>,
|
||||
pub sys: Sysclk,
|
||||
|
||||
pub pll_src: PllSource,
|
||||
|
||||
pub pll: Option<Pll>,
|
||||
#[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))]
|
||||
pub plli2s: Option<Pll>,
|
||||
#[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))]
|
||||
pub pllsai: Option<Pll>,
|
||||
|
||||
pub ahb_pre: AHBPrescaler,
|
||||
pub apb1_pre: APBPrescaler,
|
||||
pub apb2_pre: APBPrescaler,
|
||||
|
||||
pub ls: super::LsConfig,
|
||||
|
||||
#[cfg(stm32f2)]
|
||||
pub voltage: VoltageScale,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
hsi: true,
|
||||
hse: None,
|
||||
sys: Sysclk::HSI,
|
||||
pll_src: PllSource::HSI,
|
||||
pll: None,
|
||||
#[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))]
|
||||
plli2s: None,
|
||||
#[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))]
|
||||
pllsai: None,
|
||||
|
||||
ahb_pre: AHBPrescaler::DIV1,
|
||||
apb1_pre: APBPrescaler::DIV1,
|
||||
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(any(stm32f446, stm32f4x9, stm32f427, stm32f437, stm32f7))]
|
||||
{
|
||||
PWR.cr1().modify(|w| w.set_oden(true));
|
||||
while !PWR.csr1().read().odrdy() {}
|
||||
|
||||
PWR.cr1().modify(|w| w.set_odswen(true));
|
||||
while !PWR.csr1().read().odswrdy() {}
|
||||
}
|
||||
|
||||
// Configure HSI
|
||||
let hsi = match config.hsi {
|
||||
false => {
|
||||
RCC.cr().modify(|w| w.set_hsion(false));
|
||||
None
|
||||
}
|
||||
true => {
|
||||
RCC.cr().modify(|w| w.set_hsion(true));
|
||||
while !RCC.cr().read().hsirdy() {}
|
||||
Some(HSI_FREQ)
|
||||
}
|
||||
};
|
||||
|
||||
// Configure HSE
|
||||
let hse = match config.hse {
|
||||
None => {
|
||||
RCC.cr().modify(|w| w.set_hseon(false));
|
||||
None
|
||||
}
|
||||
Some(hse) => {
|
||||
match hse.mode {
|
||||
HseMode::Bypass => assert!(max::HSE_BYP.contains(&hse.freq)),
|
||||
HseMode::Oscillator => assert!(max::HSE_OSC.contains(&hse.freq)),
|
||||
}
|
||||
|
||||
RCC.cr().modify(|w| w.set_hsebyp(hse.mode != HseMode::Oscillator));
|
||||
RCC.cr().modify(|w| w.set_hseon(true));
|
||||
while !RCC.cr().read().hserdy() {}
|
||||
Some(hse.freq)
|
||||
}
|
||||
};
|
||||
|
||||
// Configure PLLs.
|
||||
let pll_input = PllInput {
|
||||
hse,
|
||||
hsi,
|
||||
source: config.pll_src,
|
||||
};
|
||||
let pll = init_pll(PllInstance::Pll, config.pll, &pll_input);
|
||||
#[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))]
|
||||
let _plli2s = init_pll(PllInstance::Plli2s, config.plli2s, &pll_input);
|
||||
#[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))]
|
||||
let _pllsai = init_pll(PllInstance::Pllsai, config.pllsai, &pll_input);
|
||||
|
||||
// Configure sysclk
|
||||
let sys = match config.sys {
|
||||
Sysclk::HSI => unwrap!(hsi),
|
||||
Sysclk::HSE => unwrap!(hse),
|
||||
Sysclk::PLL1_P => unwrap!(pll.p),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let hclk = sys / config.ahb_pre;
|
||||
let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk, config.apb1_pre);
|
||||
let (pclk2, pclk2_tim) = super::util::calc_pclk(hclk, config.apb2_pre);
|
||||
|
||||
assert!(max::SYSCLK.contains(&sys));
|
||||
assert!(max::HCLK.contains(&hclk));
|
||||
assert!(max::PCLK1.contains(&pclk1));
|
||||
assert!(max::PCLK2.contains(&pclk2));
|
||||
|
||||
let rtc = config.ls.init();
|
||||
|
||||
#[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);
|
||||
w.set_hpre(config.ahb_pre);
|
||||
w.set_ppre1(config.apb1_pre);
|
||||
w.set_ppre2(config.apb2_pre);
|
||||
});
|
||||
while RCC.cfgr().read().sws() != config.sys {}
|
||||
|
||||
set_freqs(Clocks {
|
||||
sys,
|
||||
hclk1: hclk,
|
||||
hclk2: hclk,
|
||||
hclk3: hclk,
|
||||
pclk1,
|
||||
pclk2,
|
||||
pclk1_tim,
|
||||
pclk2_tim,
|
||||
rtc,
|
||||
pll1_q: pll.q,
|
||||
#[cfg(all(rcc_f4, not(stm32f410)))]
|
||||
plli2s1_q: _plli2s.q,
|
||||
#[cfg(all(rcc_f4, not(stm32f410)))]
|
||||
plli2s1_r: _plli2s.r,
|
||||
|
||||
#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))]
|
||||
pllsai1_q: _pllsai.q,
|
||||
#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))]
|
||||
pllsai1_r: _pllsai.r,
|
||||
});
|
||||
}
|
||||
|
||||
struct PllInput {
|
||||
source: PllSource,
|
||||
hsi: Option<Hertz>,
|
||||
hse: Option<Hertz>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[allow(unused)]
|
||||
struct PllOutput {
|
||||
p: Option<Hertz>,
|
||||
q: Option<Hertz>,
|
||||
r: Option<Hertz>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
enum PllInstance {
|
||||
Pll,
|
||||
#[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))]
|
||||
Plli2s,
|
||||
#[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))]
|
||||
Pllsai,
|
||||
}
|
||||
|
||||
fn pll_enable(instance: PllInstance, enabled: bool) {
|
||||
match instance {
|
||||
PllInstance::Pll => {
|
||||
RCC.cr().modify(|w| w.set_pllon(enabled));
|
||||
while RCC.cr().read().pllrdy() != enabled {}
|
||||
}
|
||||
#[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))]
|
||||
PllInstance::Plli2s => {
|
||||
RCC.cr().modify(|w| w.set_plli2son(enabled));
|
||||
while RCC.cr().read().plli2srdy() != enabled {}
|
||||
}
|
||||
#[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))]
|
||||
PllInstance::Pllsai => {
|
||||
RCC.cr().modify(|w| w.set_pllsaion(enabled));
|
||||
while RCC.cr().read().pllsairdy() != enabled {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 input.source {
|
||||
PllSource::HSE => input.hse,
|
||||
PllSource::HSI => input.hsi,
|
||||
};
|
||||
|
||||
let pll_src = pll_src.unwrap();
|
||||
|
||||
let in_freq = pll_src / pll.prediv;
|
||||
assert!(max::PLL_IN.contains(&in_freq));
|
||||
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);
|
||||
|
||||
macro_rules! write_fields {
|
||||
($w:ident) => {
|
||||
$w.set_plln(pll.mul);
|
||||
if let Some(divp) = pll.divp {
|
||||
$w.set_pllp(divp);
|
||||
}
|
||||
if let Some(divq) = pll.divq {
|
||||
$w.set_pllq(divq);
|
||||
}
|
||||
#[cfg(any(stm32f4, stm32f7))]
|
||||
if let Some(divr) = pll.divr {
|
||||
$w.set_pllr(divr);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match instance {
|
||||
PllInstance::Pll => RCC.pllcfgr().write(|w| {
|
||||
w.set_pllm(pll.prediv);
|
||||
w.set_pllsrc(input.source);
|
||||
write_fields!(w);
|
||||
}),
|
||||
#[cfg(any(all(stm32f4, not(stm32f410)), stm32f7))]
|
||||
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);
|
||||
}),
|
||||
}
|
||||
|
||||
// Enable PLL
|
||||
pll_enable(instance, true);
|
||||
|
||||
PllOutput { p, q, r }
|
||||
}
|
||||
|
||||
#[cfg(stm32f7)]
|
||||
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(50_000_000);
|
||||
|
||||
pub(crate) const SYSCLK: RangeInclusive<Hertz> = Hertz(12_500_000)..=Hertz(216_000_000);
|
||||
pub(crate) const HCLK: RangeInclusive<Hertz> = Hertz(12_500_000)..=Hertz(216_000_000);
|
||||
pub(crate) const PCLK1: RangeInclusive<Hertz> = Hertz(12_500_000)..=Hertz(216_000_000 / 4);
|
||||
pub(crate) const PCLK2: RangeInclusive<Hertz> = Hertz(12_500_000)..=Hertz(216_000_000 / 2);
|
||||
|
||||
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(stm32f4)]
|
||||
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(50_000_000);
|
||||
|
||||
#[cfg(stm32f401)]
|
||||
pub(crate) const SYSCLK: RangeInclusive<Hertz> = Hertz(0)..=Hertz(84_000_000);
|
||||
#[cfg(any(stm32f405, stm32f407, stm32f415, stm32f417,))]
|
||||
pub(crate) const SYSCLK: RangeInclusive<Hertz> = Hertz(0)..=Hertz(168_000_000);
|
||||
#[cfg(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423,))]
|
||||
pub(crate) const SYSCLK: RangeInclusive<Hertz> = Hertz(0)..=Hertz(100_000_000);
|
||||
#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479,))]
|
||||
pub(crate) const SYSCLK: RangeInclusive<Hertz> = Hertz(0)..=Hertz(180_000_000);
|
||||
|
||||
pub(crate) const HCLK: RangeInclusive<Hertz> = Hertz(0)..=Hertz(SYSCLK.end().0);
|
||||
|
||||
pub(crate) const PCLK1: RangeInclusive<Hertz> = Hertz(0)..=Hertz(PCLK2.end().0 / 2);
|
||||
|
||||
#[cfg(any(stm32f401, stm32f410, stm32f411, stm32f412, stm32f413, stm32f423,))]
|
||||
pub(crate) const PCLK2: RangeInclusive<Hertz> = Hertz(0)..=Hertz(HCLK.end().0);
|
||||
#[cfg(not(any(stm32f401, stm32f410, stm32f411, stm32f412, stm32f413, stm32f423,)))]
|
||||
pub(crate) const PCLK2: RangeInclusive<Hertz> = Hertz(0)..=Hertz(HCLK.end().0 / 2);
|
||||
|
||||
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);
|
||||
}
|
@ -127,7 +127,7 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
}
|
||||
|
||||
if config.usb_pll {
|
||||
RCC.cfgr3().modify(|w| w.set_usbsw(Usbsw::PLLCLK));
|
||||
RCC.cfgr3().modify(|w| w.set_usbsw(Usbsw::PLL1_P));
|
||||
}
|
||||
// TODO: Option to use CRS (Clock Recovery)
|
||||
|
||||
@ -140,7 +140,7 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
RCC.cfgr().modify(|w| {
|
||||
w.set_ppre(Ppre::from_bits(ppre_bits));
|
||||
w.set_hpre(Hpre::from_bits(hpre_bits));
|
||||
w.set_sw(Sw::PLL)
|
||||
w.set_sw(Sw::PLL1_P)
|
||||
});
|
||||
} else {
|
||||
RCC.cfgr().modify(|w| {
|
||||
|
@ -169,7 +169,7 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
#[cfg(not(rcc_f100))]
|
||||
w.set_usbpre(Usbpre::from_bits(usbpre as u8));
|
||||
w.set_sw(if pllmul_bits.is_some() {
|
||||
Sw::PLL
|
||||
Sw::PLL1_P
|
||||
} else if config.hse.is_some() {
|
||||
Sw::HSE
|
||||
} else {
|
||||
|
@ -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::PLL)
|
||||
}
|
||||
};
|
||||
// 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,
|
||||
});
|
||||
}
|
@ -214,7 +214,7 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
// CFGR has been written before (PLL, PLL48, clock divider) don't overwrite these settings
|
||||
RCC.cfgr().modify(|w| {
|
||||
w.set_sw(match (pll_config, config.hse) {
|
||||
(Some(_), _) => Sw::PLL,
|
||||
(Some(_), _) => Sw::PLL1_P,
|
||||
(None, Some(_)) => Sw::HSE,
|
||||
(None, None) => Sw::HSI,
|
||||
})
|
||||
@ -271,7 +271,7 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
pll_config.unwrap();
|
||||
assert!((pclk2 == sysclk) || (pclk2 * 2u32 == sysclk));
|
||||
|
||||
RCC.cfgr3().modify(|w| w.set_hrtim1sw(Timsw::PLL));
|
||||
RCC.cfgr3().modify(|w| w.set_hrtim1sw(Timsw::PLL1_P));
|
||||
|
||||
Some(sysclk * 2u32)
|
||||
}
|
||||
|
@ -1,400 +0,0 @@
|
||||
use crate::pac::rcc::vals::{Hpre, Pllm, Plln, Pllq, Pllr, Ppre, Sw};
|
||||
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);
|
||||
|
||||
/// Clocks configuration
|
||||
#[non_exhaustive]
|
||||
#[derive(Default)]
|
||||
pub struct Config {
|
||||
pub hse: Option<Hertz>,
|
||||
pub bypass_hse: bool,
|
||||
pub hclk: Option<Hertz>,
|
||||
pub sys_ck: Option<Hertz>,
|
||||
pub pclk1: Option<Hertz>,
|
||||
pub pclk2: Option<Hertz>,
|
||||
|
||||
#[cfg(not(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446)))]
|
||||
pub plli2s: Option<Hertz>,
|
||||
|
||||
#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))]
|
||||
pub pllsai: Option<Hertz>,
|
||||
|
||||
pub pll48: bool,
|
||||
pub ls: super::LsConfig,
|
||||
}
|
||||
|
||||
#[cfg(stm32f410)]
|
||||
fn setup_i2s_pll(_vco_in: u32, _plli2s: Option<u32>) -> Option<u32> {
|
||||
None
|
||||
}
|
||||
|
||||
// Not currently implemented, but will be in the future
|
||||
#[cfg(any(stm32f411, stm32f412, stm32f413, stm32f423, stm32f446))]
|
||||
fn setup_i2s_pll(_vco_in: u32, _plli2s: Option<u32>) -> Option<u32> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423)))]
|
||||
fn calculate_sai_i2s_pll_values(vco_in: u32, max_div: u32, target: Option<u32>) -> Option<(u32, u32, u32)> {
|
||||
let min_div = 2;
|
||||
let target = match target {
|
||||
Some(target) => target,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
// We loop through the possible divider values to find the best configuration. Looping
|
||||
// through all possible "N" values would result in more iterations.
|
||||
let (n, outdiv, output, _error) = (min_div..=max_div)
|
||||
.filter_map(|outdiv| {
|
||||
let target_vco_out = match target.checked_mul(outdiv) {
|
||||
Some(x) => x,
|
||||
None => return None,
|
||||
};
|
||||
let n = (target_vco_out + (vco_in >> 1)) / vco_in;
|
||||
let vco_out = vco_in * n;
|
||||
if !(100_000_000..=432_000_000).contains(&vco_out) {
|
||||
return None;
|
||||
}
|
||||
let output = vco_out / outdiv;
|
||||
let error = (output as i32 - target as i32).unsigned_abs();
|
||||
Some((n, outdiv, output, error))
|
||||
})
|
||||
.min_by_key(|(_, _, _, error)| *error)?;
|
||||
|
||||
Some((n, outdiv, output))
|
||||
}
|
||||
|
||||
#[cfg(not(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446)))]
|
||||
fn setup_i2s_pll(vco_in: u32, plli2s: Option<u32>) -> Option<u32> {
|
||||
let (n, outdiv, output) = calculate_sai_i2s_pll_values(vco_in, 7, plli2s)?;
|
||||
|
||||
RCC.plli2scfgr().modify(|w| {
|
||||
w.set_plli2sn(n as u16);
|
||||
w.set_plli2sr(outdiv as u8);
|
||||
#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))]
|
||||
w.set_plli2sq(outdiv as u8); //set sai divider same as i2s
|
||||
});
|
||||
|
||||
Some(output)
|
||||
}
|
||||
|
||||
#[cfg(not(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479)))]
|
||||
fn setup_sai_pll(_vco_in: u32, _pllsai: Option<u32>) -> Option<u32> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))]
|
||||
fn setup_sai_pll(vco_in: u32, pllsai: Option<u32>) -> Option<u32> {
|
||||
let (n, outdiv, output) = calculate_sai_i2s_pll_values(vco_in, 15, pllsai)?;
|
||||
|
||||
RCC.pllsaicfgr().modify(|w| {
|
||||
w.set_pllsain(n as u16);
|
||||
w.set_pllsaiq(outdiv as u8);
|
||||
});
|
||||
|
||||
Some(output)
|
||||
}
|
||||
|
||||
fn setup_pll(
|
||||
pllsrcclk: u32,
|
||||
use_hse: bool,
|
||||
pllsysclk: Option<u32>,
|
||||
plli2s: Option<u32>,
|
||||
pllsai: Option<u32>,
|
||||
pll48clk: bool,
|
||||
) -> PllResults {
|
||||
use crate::pac::rcc::vals::{Pllp, Pllsrc};
|
||||
|
||||
let sysclk = pllsysclk.unwrap_or(pllsrcclk);
|
||||
if pllsysclk.is_none() && !pll48clk {
|
||||
RCC.pllcfgr().modify(|w| w.set_pllsrc(Pllsrc::from_bits(use_hse as u8)));
|
||||
|
||||
return PllResults {
|
||||
use_pll: false,
|
||||
pllsysclk: None,
|
||||
pll48clk: None,
|
||||
plli2sclk: None,
|
||||
pllsaiclk: None,
|
||||
};
|
||||
}
|
||||
// Input divisor from PLL source clock, must result to frequency in
|
||||
// the range from 1 to 2 MHz
|
||||
let pllm_min = (pllsrcclk + 1_999_999) / 2_000_000;
|
||||
let pllm_max = pllsrcclk / 1_000_000;
|
||||
|
||||
// Sysclk output divisor must be one of 2, 4, 6 or 8
|
||||
let sysclk_div = core::cmp::min(8, (432_000_000 / sysclk) & !1);
|
||||
|
||||
let target_freq = if pll48clk { 48_000_000 } else { sysclk * sysclk_div };
|
||||
|
||||
// Find the lowest pllm value that minimize the difference between
|
||||
// target frequency and the real vco_out frequency.
|
||||
let pllm = unwrap!((pllm_min..=pllm_max).min_by_key(|pllm| {
|
||||
let vco_in = pllsrcclk / pllm;
|
||||
let plln = target_freq / vco_in;
|
||||
target_freq - vco_in * plln
|
||||
}));
|
||||
|
||||
let vco_in = pllsrcclk / pllm;
|
||||
assert!((1_000_000..=2_000_000).contains(&vco_in));
|
||||
|
||||
// Main scaler, must result in >= 100MHz (>= 192MHz for F401)
|
||||
// and <= 432MHz, min 50, max 432
|
||||
let plln = if pll48clk {
|
||||
// try the different valid pllq according to the valid
|
||||
// main scaller values, and take the best
|
||||
let pllq = unwrap!((4..=9).min_by_key(|pllq| {
|
||||
let plln = 48_000_000 * pllq / vco_in;
|
||||
let pll48_diff = 48_000_000 - vco_in * plln / pllq;
|
||||
let sysclk_diff = (sysclk as i32 - (vco_in * plln / sysclk_div) as i32).abs();
|
||||
(pll48_diff, sysclk_diff)
|
||||
}));
|
||||
48_000_000 * pllq / vco_in
|
||||
} else {
|
||||
sysclk * sysclk_div / vco_in
|
||||
};
|
||||
|
||||
let pllp = (sysclk_div / 2) - 1;
|
||||
|
||||
let pllq = (vco_in * plln + 47_999_999) / 48_000_000;
|
||||
let real_pll48clk = vco_in * plln / pllq;
|
||||
|
||||
RCC.pllcfgr().modify(|w| {
|
||||
w.set_pllm(Pllm::from_bits(pllm as u8));
|
||||
w.set_plln(Plln::from_bits(plln as u16));
|
||||
w.set_pllp(Pllp::from_bits(pllp as u8));
|
||||
w.set_pllq(Pllq::from_bits(pllq as u8));
|
||||
w.set_pllsrc(Pllsrc::from_bits(use_hse as u8));
|
||||
w.set_pllr(Pllr::from_bits(0));
|
||||
});
|
||||
|
||||
let real_pllsysclk = vco_in * plln / sysclk_div;
|
||||
|
||||
PllResults {
|
||||
use_pll: true,
|
||||
pllsysclk: Some(real_pllsysclk),
|
||||
pll48clk: if pll48clk { Some(real_pll48clk) } else { None },
|
||||
plli2sclk: setup_i2s_pll(vco_in, plli2s),
|
||||
pllsaiclk: setup_sai_pll(vco_in, pllsai),
|
||||
}
|
||||
}
|
||||
|
||||
fn flash_setup(sysclk: u32) {
|
||||
use crate::pac::flash::vals::Latency;
|
||||
|
||||
// Be conservative with voltage ranges
|
||||
const FLASH_LATENCY_STEP: u32 = 30_000_000;
|
||||
|
||||
critical_section::with(|_| {
|
||||
FLASH
|
||||
.acr()
|
||||
.modify(|w| w.set_latency(Latency::from_bits(((sysclk - 1) / FLASH_LATENCY_STEP) as u8)));
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn init(config: Config) {
|
||||
let pllsrcclk = config.hse.map(|hse| hse.0).unwrap_or(HSI_FREQ.0);
|
||||
let sysclk = config.sys_ck.map(|sys| sys.0).unwrap_or(pllsrcclk);
|
||||
let sysclk_on_pll = sysclk != pllsrcclk;
|
||||
|
||||
let plls = setup_pll(
|
||||
pllsrcclk,
|
||||
config.hse.is_some(),
|
||||
if sysclk_on_pll { Some(sysclk) } else { None },
|
||||
#[cfg(not(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446)))]
|
||||
config.plli2s.map(|i2s| i2s.0),
|
||||
#[cfg(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446))]
|
||||
None,
|
||||
#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))]
|
||||
config.pllsai.map(|sai| sai.0),
|
||||
#[cfg(not(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479)))]
|
||||
None,
|
||||
config.pll48,
|
||||
);
|
||||
|
||||
if config.pll48 {
|
||||
let freq = unwrap!(plls.pll48clk);
|
||||
|
||||
assert!((max::PLL_48_CLK as i32 - freq as i32).abs() <= max::PLL_48_TOLERANCE as i32);
|
||||
}
|
||||
|
||||
let sysclk = if sysclk_on_pll { unwrap!(plls.pllsysclk) } else { sysclk };
|
||||
|
||||
// AHB prescaler
|
||||
let hclk = config.hclk.map(|h| h.0).unwrap_or(sysclk);
|
||||
let (hpre_bits, hpre_div) = match (sysclk + hclk - 1) / hclk {
|
||||
0 => unreachable!(),
|
||||
1 => (Hpre::DIV1, 1),
|
||||
2 => (Hpre::DIV2, 2),
|
||||
3..=5 => (Hpre::DIV4, 4),
|
||||
6..=11 => (Hpre::DIV8, 8),
|
||||
12..=39 => (Hpre::DIV16, 16),
|
||||
40..=95 => (Hpre::DIV64, 64),
|
||||
96..=191 => (Hpre::DIV128, 128),
|
||||
192..=383 => (Hpre::DIV256, 256),
|
||||
_ => (Hpre::DIV512, 512),
|
||||
};
|
||||
|
||||
// Calculate real AHB clock
|
||||
let hclk = sysclk / hpre_div;
|
||||
|
||||
let pclk1 = config
|
||||
.pclk1
|
||||
.map(|p| p.0)
|
||||
.unwrap_or_else(|| core::cmp::min(max::PCLK1_MAX, hclk));
|
||||
|
||||
let (ppre1_bits, ppre1) = match (hclk + pclk1 - 1) / pclk1 {
|
||||
0 => unreachable!(),
|
||||
1 => (0b000, 1),
|
||||
2 => (0b100, 2),
|
||||
3..=5 => (0b101, 4),
|
||||
6..=11 => (0b110, 8),
|
||||
_ => (0b111, 16),
|
||||
};
|
||||
let timer_mul1 = if ppre1 == 1 { 1 } else { 2 };
|
||||
|
||||
// Calculate real APB1 clock
|
||||
let pclk1 = hclk / ppre1;
|
||||
assert!(pclk1 <= max::PCLK1_MAX);
|
||||
|
||||
let pclk2 = config
|
||||
.pclk2
|
||||
.map(|p| p.0)
|
||||
.unwrap_or_else(|| core::cmp::min(max::PCLK2_MAX, hclk));
|
||||
let (ppre2_bits, ppre2) = match (hclk + pclk2 - 1) / pclk2 {
|
||||
0 => unreachable!(),
|
||||
1 => (0b000, 1),
|
||||
2 => (0b100, 2),
|
||||
3..=5 => (0b101, 4),
|
||||
6..=11 => (0b110, 8),
|
||||
_ => (0b111, 16),
|
||||
};
|
||||
let timer_mul2 = if ppre2 == 1 { 1 } else { 2 };
|
||||
|
||||
// Calculate real APB2 clock
|
||||
let pclk2 = hclk / ppre2;
|
||||
assert!(pclk2 <= max::PCLK2_MAX);
|
||||
|
||||
flash_setup(sysclk);
|
||||
|
||||
if config.hse.is_some() {
|
||||
RCC.cr().modify(|w| {
|
||||
w.set_hsebyp(config.bypass_hse);
|
||||
w.set_hseon(true);
|
||||
});
|
||||
while !RCC.cr().read().hserdy() {}
|
||||
}
|
||||
|
||||
if plls.use_pll {
|
||||
RCC.cr().modify(|w| w.set_pllon(true));
|
||||
|
||||
if hclk > max::HCLK_OVERDRIVE_FREQUENCY {
|
||||
PWR.cr1().modify(|w| w.set_oden(true));
|
||||
while !PWR.csr1().read().odrdy() {}
|
||||
|
||||
PWR.cr1().modify(|w| w.set_odswen(true));
|
||||
while !PWR.csr1().read().odswrdy() {}
|
||||
}
|
||||
|
||||
while !RCC.cr().read().pllrdy() {}
|
||||
}
|
||||
|
||||
#[cfg(not(stm32f410))]
|
||||
if plls.plli2sclk.is_some() {
|
||||
RCC.cr().modify(|w| w.set_plli2son(true));
|
||||
|
||||
while !RCC.cr().read().plli2srdy() {}
|
||||
}
|
||||
|
||||
#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))]
|
||||
if plls.pllsaiclk.is_some() {
|
||||
RCC.cr().modify(|w| w.set_pllsaion(true));
|
||||
while !RCC.cr().read().pllsairdy() {}
|
||||
}
|
||||
|
||||
RCC.cfgr().modify(|w| {
|
||||
w.set_ppre2(Ppre::from_bits(ppre2_bits));
|
||||
w.set_ppre1(Ppre::from_bits(ppre1_bits));
|
||||
w.set_hpre(hpre_bits);
|
||||
});
|
||||
|
||||
// Wait for the new prescalers to kick in
|
||||
// "The clocks are divided with the new prescaler factor from 1 to 16 AHB cycles after write"
|
||||
cortex_m::asm::delay(16);
|
||||
|
||||
RCC.cfgr().modify(|w| {
|
||||
w.set_sw(if sysclk_on_pll {
|
||||
Sw::PLL
|
||||
} else if config.hse.is_some() {
|
||||
Sw::HSE
|
||||
} else {
|
||||
Sw::HSI
|
||||
})
|
||||
});
|
||||
|
||||
let rtc = config.ls.init();
|
||||
|
||||
set_freqs(Clocks {
|
||||
sys: Hertz(sysclk),
|
||||
pclk1: Hertz(pclk1),
|
||||
pclk2: Hertz(pclk2),
|
||||
|
||||
pclk1_tim: Hertz(pclk1 * timer_mul1),
|
||||
pclk2_tim: Hertz(pclk2 * timer_mul2),
|
||||
|
||||
hclk1: Hertz(hclk),
|
||||
hclk2: Hertz(hclk),
|
||||
hclk3: Hertz(hclk),
|
||||
|
||||
pll1_q: plls.pll48clk.map(Hertz),
|
||||
|
||||
#[cfg(not(stm32f410))]
|
||||
plli2s1_q: plls.plli2sclk.map(Hertz),
|
||||
#[cfg(not(stm32f410))]
|
||||
plli2s1_r: None,
|
||||
|
||||
#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))]
|
||||
pllsai1_q: plls.pllsaiclk.map(Hertz),
|
||||
#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))]
|
||||
pllsai1_r: None,
|
||||
|
||||
rtc,
|
||||
});
|
||||
}
|
||||
|
||||
struct PllResults {
|
||||
use_pll: bool,
|
||||
pllsysclk: Option<u32>,
|
||||
pll48clk: Option<u32>,
|
||||
#[allow(dead_code)]
|
||||
plli2sclk: Option<u32>,
|
||||
#[allow(dead_code)]
|
||||
pllsaiclk: Option<u32>,
|
||||
}
|
||||
|
||||
mod max {
|
||||
#[cfg(stm32f401)]
|
||||
pub(crate) const SYSCLK_MAX: u32 = 84_000_000;
|
||||
#[cfg(any(stm32f405, stm32f407, stm32f415, stm32f417,))]
|
||||
pub(crate) const SYSCLK_MAX: u32 = 168_000_000;
|
||||
#[cfg(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423,))]
|
||||
pub(crate) const SYSCLK_MAX: u32 = 100_000_000;
|
||||
#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479,))]
|
||||
pub(crate) const SYSCLK_MAX: u32 = 180_000_000;
|
||||
|
||||
pub(crate) const HCLK_OVERDRIVE_FREQUENCY: u32 = 168_000_000;
|
||||
|
||||
pub(crate) const PCLK1_MAX: u32 = PCLK2_MAX / 2;
|
||||
|
||||
#[cfg(any(stm32f401, stm32f410, stm32f411, stm32f412, stm32f413, stm32f423,))]
|
||||
pub(crate) const PCLK2_MAX: u32 = SYSCLK_MAX;
|
||||
#[cfg(not(any(stm32f401, stm32f410, stm32f411, stm32f412, stm32f413, stm32f423,)))]
|
||||
pub(crate) const PCLK2_MAX: u32 = SYSCLK_MAX / 2;
|
||||
|
||||
pub(crate) const PLL_48_CLK: u32 = 48_000_000;
|
||||
pub(crate) const PLL_48_TOLERANCE: u32 = 120_000;
|
||||
}
|
@ -1,305 +0,0 @@
|
||||
use crate::pac::pwr::vals::Vos;
|
||||
use crate::pac::rcc::vals::{Hpre, Pllm, Plln, Pllp, Pllq, Pllsrc, Ppre, Sw};
|
||||
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);
|
||||
|
||||
/// Clocks configuration
|
||||
#[non_exhaustive]
|
||||
#[derive(Default)]
|
||||
pub struct Config {
|
||||
pub hse: Option<Hertz>,
|
||||
pub bypass_hse: bool,
|
||||
pub hclk: Option<Hertz>,
|
||||
pub sys_ck: Option<Hertz>,
|
||||
pub pclk1: Option<Hertz>,
|
||||
pub pclk2: Option<Hertz>,
|
||||
|
||||
pub pll48: bool,
|
||||
pub ls: super::LsConfig,
|
||||
}
|
||||
|
||||
fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option<u32>, pll48clk: bool) -> PllResults {
|
||||
let sysclk = pllsysclk.unwrap_or(pllsrcclk);
|
||||
if pllsysclk.is_none() && !pll48clk {
|
||||
RCC.pllcfgr().modify(|w| w.set_pllsrc(Pllsrc::from_bits(use_hse as u8)));
|
||||
|
||||
return PllResults {
|
||||
use_pll: false,
|
||||
pllsysclk: None,
|
||||
pll48clk: None,
|
||||
};
|
||||
}
|
||||
// Input divisor from PLL source clock, must result to frequency in
|
||||
// the range from 1 to 2 MHz
|
||||
let pllm_min = (pllsrcclk + 1_999_999) / 2_000_000;
|
||||
let pllm_max = pllsrcclk / 1_000_000;
|
||||
|
||||
// Sysclk output divisor must be one of 2, 4, 6 or 8
|
||||
let sysclk_div = core::cmp::min(8, (432_000_000 / sysclk) & !1);
|
||||
|
||||
let target_freq = if pll48clk { 48_000_000 } else { sysclk * sysclk_div };
|
||||
|
||||
// Find the lowest pllm value that minimize the difference between
|
||||
// target frequency and the real vco_out frequency.
|
||||
let pllm = unwrap!((pllm_min..=pllm_max).min_by_key(|pllm| {
|
||||
let vco_in = pllsrcclk / pllm;
|
||||
let plln = target_freq / vco_in;
|
||||
target_freq - vco_in * plln
|
||||
}));
|
||||
|
||||
let vco_in = pllsrcclk / pllm;
|
||||
assert!((1_000_000..=2_000_000).contains(&vco_in));
|
||||
|
||||
// Main scaler, must result in >= 100MHz (>= 192MHz for F401)
|
||||
// and <= 432MHz, min 50, max 432
|
||||
let plln = if pll48clk {
|
||||
// try the different valid pllq according to the valid
|
||||
// main scaller values, and take the best
|
||||
let pllq = unwrap!((4..=9).min_by_key(|pllq| {
|
||||
let plln = 48_000_000 * pllq / vco_in;
|
||||
let pll48_diff = 48_000_000 - vco_in * plln / pllq;
|
||||
let sysclk_diff = (sysclk as i32 - (vco_in * plln / sysclk_div) as i32).abs();
|
||||
(pll48_diff, sysclk_diff)
|
||||
}));
|
||||
48_000_000 * pllq / vco_in
|
||||
} else {
|
||||
sysclk * sysclk_div / vco_in
|
||||
};
|
||||
|
||||
let pllp = (sysclk_div / 2) - 1;
|
||||
|
||||
let pllq = (vco_in * plln + 47_999_999) / 48_000_000;
|
||||
let real_pll48clk = vco_in * plln / pllq;
|
||||
|
||||
RCC.pllcfgr().modify(|w| {
|
||||
w.set_pllm(Pllm::from_bits(pllm as u8));
|
||||
w.set_plln(Plln::from_bits(plln as u16));
|
||||
w.set_pllp(Pllp::from_bits(pllp as u8));
|
||||
w.set_pllq(Pllq::from_bits(pllq as u8));
|
||||
w.set_pllsrc(Pllsrc::from_bits(use_hse as u8));
|
||||
});
|
||||
|
||||
let real_pllsysclk = vco_in * plln / sysclk_div;
|
||||
|
||||
PllResults {
|
||||
use_pll: true,
|
||||
pllsysclk: Some(real_pllsysclk),
|
||||
pll48clk: if pll48clk { Some(real_pll48clk) } else { None },
|
||||
}
|
||||
}
|
||||
|
||||
fn flash_setup(sysclk: u32) {
|
||||
use crate::pac::flash::vals::Latency;
|
||||
|
||||
// Be conservative with voltage ranges
|
||||
const FLASH_LATENCY_STEP: u32 = 30_000_000;
|
||||
|
||||
critical_section::with(|_| {
|
||||
FLASH
|
||||
.acr()
|
||||
.modify(|w| w.set_latency(Latency::from_bits(((sysclk - 1) / FLASH_LATENCY_STEP) as u8)));
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn init(config: Config) {
|
||||
if let Some(hse) = config.hse {
|
||||
if config.bypass_hse {
|
||||
assert!((max::HSE_BYPASS_MIN..=max::HSE_BYPASS_MAX).contains(&hse.0));
|
||||
} else {
|
||||
assert!((max::HSE_OSC_MIN..=max::HSE_OSC_MAX).contains(&hse.0));
|
||||
}
|
||||
}
|
||||
|
||||
let pllsrcclk = config.hse.map(|hse| hse.0).unwrap_or(HSI_FREQ.0);
|
||||
let sysclk = config.sys_ck.map(|sys| sys.0).unwrap_or(pllsrcclk);
|
||||
let sysclk_on_pll = sysclk != pllsrcclk;
|
||||
|
||||
assert!((max::SYSCLK_MIN..=max::SYSCLK_MAX).contains(&sysclk));
|
||||
|
||||
let plls = setup_pll(
|
||||
pllsrcclk,
|
||||
config.hse.is_some(),
|
||||
if sysclk_on_pll { Some(sysclk) } else { None },
|
||||
config.pll48,
|
||||
);
|
||||
|
||||
if config.pll48 {
|
||||
let freq = unwrap!(plls.pll48clk);
|
||||
|
||||
assert!((max::PLL_48_CLK as i32 - freq as i32).abs() <= max::PLL_48_TOLERANCE as i32);
|
||||
}
|
||||
|
||||
let sysclk = if sysclk_on_pll { unwrap!(plls.pllsysclk) } else { sysclk };
|
||||
|
||||
// AHB prescaler
|
||||
let hclk = config.hclk.map(|h| h.0).unwrap_or(sysclk);
|
||||
let (hpre_bits, hpre_div) = match (sysclk + hclk - 1) / hclk {
|
||||
0 => unreachable!(),
|
||||
1 => (Hpre::DIV1, 1),
|
||||
2 => (Hpre::DIV2, 2),
|
||||
3..=5 => (Hpre::DIV4, 4),
|
||||
6..=11 => (Hpre::DIV8, 8),
|
||||
12..=39 => (Hpre::DIV16, 16),
|
||||
40..=95 => (Hpre::DIV64, 64),
|
||||
96..=191 => (Hpre::DIV128, 128),
|
||||
192..=383 => (Hpre::DIV256, 256),
|
||||
_ => (Hpre::DIV512, 512),
|
||||
};
|
||||
|
||||
// Calculate real AHB clock
|
||||
let hclk = sysclk / hpre_div;
|
||||
|
||||
assert!(hclk <= max::HCLK_MAX);
|
||||
|
||||
let pclk1 = config
|
||||
.pclk1
|
||||
.map(|p| p.0)
|
||||
.unwrap_or_else(|| core::cmp::min(max::PCLK1_MAX, hclk));
|
||||
|
||||
let (ppre1_bits, ppre1) = match (hclk + pclk1 - 1) / pclk1 {
|
||||
0 => unreachable!(),
|
||||
1 => (0b000, 1),
|
||||
2 => (0b100, 2),
|
||||
3..=5 => (0b101, 4),
|
||||
6..=11 => (0b110, 8),
|
||||
_ => (0b111, 16),
|
||||
};
|
||||
let timer_mul1 = if ppre1 == 1 { 1 } else { 2 };
|
||||
|
||||
// Calculate real APB1 clock
|
||||
let pclk1 = hclk / ppre1;
|
||||
assert!((max::PCLK1_MIN..=max::PCLK1_MAX).contains(&pclk1));
|
||||
|
||||
let pclk2 = config
|
||||
.pclk2
|
||||
.map(|p| p.0)
|
||||
.unwrap_or_else(|| core::cmp::min(max::PCLK2_MAX, hclk));
|
||||
let (ppre2_bits, ppre2) = match (hclk + pclk2 - 1) / pclk2 {
|
||||
0 => unreachable!(),
|
||||
1 => (0b000, 1),
|
||||
2 => (0b100, 2),
|
||||
3..=5 => (0b101, 4),
|
||||
6..=11 => (0b110, 8),
|
||||
_ => (0b111, 16),
|
||||
};
|
||||
let timer_mul2 = if ppre2 == 1 { 1 } else { 2 };
|
||||
|
||||
// Calculate real APB2 clock
|
||||
let pclk2 = hclk / ppre2;
|
||||
assert!((max::PCLK2_MIN..=max::PCLK2_MAX).contains(&pclk2));
|
||||
|
||||
flash_setup(sysclk);
|
||||
|
||||
if config.hse.is_some() {
|
||||
RCC.cr().modify(|w| {
|
||||
w.set_hsebyp(config.bypass_hse);
|
||||
w.set_hseon(true);
|
||||
});
|
||||
while !RCC.cr().read().hserdy() {}
|
||||
}
|
||||
|
||||
if plls.use_pll {
|
||||
RCC.cr().modify(|w| w.set_pllon(false));
|
||||
|
||||
// setup VOSScale
|
||||
let vos_scale = if sysclk <= 144_000_000 {
|
||||
3
|
||||
} else if sysclk <= 168_000_000 {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
};
|
||||
PWR.cr1().modify(|w| {
|
||||
w.set_vos(match vos_scale {
|
||||
3 => Vos::SCALE3,
|
||||
2 => Vos::SCALE2,
|
||||
1 => Vos::SCALE1,
|
||||
_ => panic!("Invalid VOS Scale."),
|
||||
})
|
||||
});
|
||||
|
||||
RCC.cr().modify(|w| w.set_pllon(true));
|
||||
|
||||
if hclk > max::HCLK_OVERDRIVE_FREQUENCY {
|
||||
PWR.cr1().modify(|w| w.set_oden(true));
|
||||
while !PWR.csr1().read().odrdy() {}
|
||||
|
||||
PWR.cr1().modify(|w| w.set_odswen(true));
|
||||
while !PWR.csr1().read().odswrdy() {}
|
||||
}
|
||||
|
||||
while !RCC.cr().read().pllrdy() {}
|
||||
}
|
||||
|
||||
RCC.cfgr().modify(|w| {
|
||||
w.set_ppre2(Ppre::from_bits(ppre2_bits));
|
||||
w.set_ppre1(Ppre::from_bits(ppre1_bits));
|
||||
w.set_hpre(hpre_bits);
|
||||
});
|
||||
|
||||
// Wait for the new prescalers to kick in
|
||||
// "The clocks are divided with the new prescaler factor from 1 to 16 AHB cycles after write"
|
||||
cortex_m::asm::delay(16);
|
||||
|
||||
RCC.cfgr().modify(|w| {
|
||||
w.set_sw(if sysclk_on_pll {
|
||||
Sw::PLL
|
||||
} else if config.hse.is_some() {
|
||||
Sw::HSE
|
||||
} else {
|
||||
Sw::HSI
|
||||
})
|
||||
});
|
||||
|
||||
let rtc = config.ls.init();
|
||||
|
||||
set_freqs(Clocks {
|
||||
sys: Hertz(sysclk),
|
||||
pclk1: Hertz(pclk1),
|
||||
pclk2: Hertz(pclk2),
|
||||
|
||||
pclk1_tim: Hertz(pclk1 * timer_mul1),
|
||||
pclk2_tim: Hertz(pclk2 * timer_mul2),
|
||||
|
||||
hclk1: Hertz(hclk),
|
||||
hclk2: Hertz(hclk),
|
||||
hclk3: Hertz(hclk),
|
||||
|
||||
pll1_q: plls.pll48clk.map(Hertz),
|
||||
|
||||
rtc,
|
||||
});
|
||||
}
|
||||
|
||||
struct PllResults {
|
||||
use_pll: bool,
|
||||
pllsysclk: Option<u32>,
|
||||
pll48clk: Option<u32>,
|
||||
}
|
||||
|
||||
mod max {
|
||||
pub(crate) const HSE_OSC_MIN: u32 = 4_000_000;
|
||||
pub(crate) const HSE_OSC_MAX: u32 = 26_000_000;
|
||||
pub(crate) const HSE_BYPASS_MIN: u32 = 1_000_000;
|
||||
pub(crate) const HSE_BYPASS_MAX: u32 = 50_000_000;
|
||||
|
||||
pub(crate) const HCLK_MAX: u32 = 216_000_000;
|
||||
pub(crate) const HCLK_OVERDRIVE_FREQUENCY: u32 = 180_000_000;
|
||||
|
||||
pub(crate) const SYSCLK_MIN: u32 = 12_500_000;
|
||||
pub(crate) const SYSCLK_MAX: u32 = 216_000_000;
|
||||
|
||||
pub(crate) const PCLK1_MIN: u32 = SYSCLK_MIN;
|
||||
pub(crate) const PCLK1_MAX: u32 = SYSCLK_MAX / 4;
|
||||
|
||||
pub(crate) const PCLK2_MIN: u32 = SYSCLK_MIN;
|
||||
pub(crate) const PCLK2_MAX: u32 = SYSCLK_MAX / 2;
|
||||
|
||||
// USB specification allows +-0.25%
|
||||
pub(crate) const PLL_48_CLK: u32 = 48_000_000;
|
||||
pub(crate) const PLL_48_TOLERANCE: u32 = 120_000;
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use crate::pac::flash::vals::Latency;
|
||||
use crate::pac::rcc::vals::{self, Sw};
|
||||
pub use crate::pac::rcc::vals::{
|
||||
Hpre as AHBPrescaler, Hsidiv as HSI16Prescaler, Pllm, Plln, Pllp, Pllq, Pllr, Ppre as APBPrescaler,
|
||||
Hpre as AHBPrescaler, Hsidiv as HSIPrescaler, Pllm, Plln, Pllp, Pllq, Pllr, Ppre as APBPrescaler,
|
||||
};
|
||||
use crate::pac::{FLASH, PWR, RCC};
|
||||
use crate::rcc::{set_freqs, Clocks};
|
||||
@ -14,7 +14,7 @@ pub const HSI_FREQ: Hertz = Hertz(16_000_000);
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ClockSrc {
|
||||
HSE(Hertz),
|
||||
HSI16(HSI16Prescaler),
|
||||
HSI(HSIPrescaler),
|
||||
PLL(PllConfig),
|
||||
LSI,
|
||||
}
|
||||
@ -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`.
|
||||
@ -46,9 +46,9 @@ pub struct PllConfig {
|
||||
impl Default for PllConfig {
|
||||
#[inline]
|
||||
fn default() -> PllConfig {
|
||||
// HSI16 / 1 * 8 / 2 = 64 MHz
|
||||
// HSI / 1 * 8 / 2 = 64 MHz
|
||||
PllConfig {
|
||||
source: PllSrc::HSI16,
|
||||
source: PllSource::HSI,
|
||||
m: Pllm::DIV1,
|
||||
n: Plln::MUL8,
|
||||
r: Pllr::DIV2,
|
||||
@ -59,8 +59,8 @@ impl Default for PllConfig {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum PllSrc {
|
||||
HSI16,
|
||||
pub enum PllSource {
|
||||
HSI,
|
||||
HSE(Hertz),
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ impl Default for Config {
|
||||
#[inline]
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
mux: ClockSrc::HSI16(HSI16Prescaler::DIV1),
|
||||
mux: ClockSrc::HSI(HSIPrescaler::DIV1),
|
||||
ahb_pre: AHBPrescaler::DIV1,
|
||||
apb_pre: APBPrescaler::DIV1,
|
||||
low_power_run: false,
|
||||
@ -89,8 +89,8 @@ impl Default for Config {
|
||||
impl PllConfig {
|
||||
pub(crate) fn init(self) -> Hertz {
|
||||
let (src, input_freq) = match self.source {
|
||||
PllSrc::HSI16 => (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::HSI16 => {
|
||||
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() {}
|
||||
}
|
||||
@ -167,8 +167,8 @@ impl PllConfig {
|
||||
|
||||
pub(crate) unsafe fn init(config: Config) {
|
||||
let (sys_clk, sw) = match config.mux {
|
||||
ClockSrc::HSI16(div) => {
|
||||
// Enable HSI16
|
||||
ClockSrc::HSI(div) => {
|
||||
// Enable HSI
|
||||
RCC.cr().write(|w| {
|
||||
w.set_hsidiv(div);
|
||||
w.set_hsion(true)
|
||||
|
@ -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;
|
||||
|
||||
@ -18,22 +17,22 @@ pub const HSI_FREQ: Hertz = Hertz(16_000_000);
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ClockSrc {
|
||||
HSE(Hertz),
|
||||
HSI16,
|
||||
HSI,
|
||||
PLL,
|
||||
}
|
||||
|
||||
/// PLL clock input source
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum PllSrc {
|
||||
HSI16,
|
||||
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::HSI16 => 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,23 +91,17 @@ 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 {
|
||||
Config {
|
||||
mux: ClockSrc::HSI16,
|
||||
mux: ClockSrc::HSI,
|
||||
ahb_pre: AHBPrescaler::DIV1,
|
||||
apb1_pre: APBPrescaler::DIV1,
|
||||
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::HSI16 => {
|
||||
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
|
||||
@ -196,8 +179,8 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
});
|
||||
|
||||
let (sys_clk, sw) = match config.mux {
|
||||
ClockSrc::HSI16 => {
|
||||
// Enable HSI16
|
||||
ClockSrc::HSI => {
|
||||
// Enable HSI
|
||||
RCC.cr().write(|w| w.set_hsion(true));
|
||||
while !RCC.cr().read().hsirdy() {}
|
||||
|
||||
@ -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
|
||||
}
|
||||
};
|
||||
|
@ -6,8 +6,11 @@ use crate::pac::pwr::vals::Vos;
|
||||
pub use crate::pac::rcc::vals::Adcdacsel as AdcClockSource;
|
||||
#[cfg(stm32h7)]
|
||||
pub use crate::pac::rcc::vals::Adcsel as AdcClockSource;
|
||||
use crate::pac::rcc::vals::{Ckpersel, Hsidiv, Pllrge, Pllsrc, Pllvcosel, Sw, Timpre};
|
||||
pub use crate::pac::rcc::vals::{Ckpersel as PerClockSource, Plldiv as PllDiv, Pllm as PllPreDiv, Plln as PllMul};
|
||||
pub use crate::pac::rcc::vals::{
|
||||
Ckpersel as PerClockSource, Hsidiv as HSIPrescaler, Plldiv as PllDiv, Pllm as PllPreDiv, Plln as PllMul,
|
||||
Pllsrc as PllSource, Sw as Sysclk,
|
||||
};
|
||||
use crate::pac::rcc::vals::{Ckpersel, Pllrge, Pllvcosel, Timpre};
|
||||
use crate::pac::{FLASH, PWR, RCC};
|
||||
use crate::rcc::{set_freqs, Clocks};
|
||||
use crate::time::Hertz;
|
||||
@ -18,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);
|
||||
@ -58,50 +58,9 @@ pub struct Hse {
|
||||
pub mode: HseMode,
|
||||
}
|
||||
|
||||
#[cfg(stm32h7)]
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum Lse {
|
||||
/// 32.768 kHz crystal/ceramic oscillator (LSEBYP=0)
|
||||
Oscillator,
|
||||
/// external clock input up to 1MHz (LSEBYP=1)
|
||||
Bypass(Hertz),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum Hsi {
|
||||
/// 64Mhz
|
||||
Mhz64,
|
||||
/// 32Mhz (divided by 2)
|
||||
Mhz32,
|
||||
/// 16Mhz (divided by 4)
|
||||
Mhz16,
|
||||
/// 8Mhz (divided by 8)
|
||||
Mhz8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum Sysclk {
|
||||
/// HSI selected as sysclk
|
||||
HSI,
|
||||
/// HSE selected as sysclk
|
||||
HSE,
|
||||
/// CSI selected as sysclk
|
||||
CSI,
|
||||
/// PLL1_P selected as sysclk
|
||||
Pll1P,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum PllSource {
|
||||
Hsi,
|
||||
Csi,
|
||||
Hse,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Pll {
|
||||
/// Source clock selection.
|
||||
#[cfg(stm32h5)]
|
||||
pub source: PllSource,
|
||||
|
||||
/// PLL pre-divider (DIVM).
|
||||
@ -161,15 +120,12 @@ impl From<TimerPrescaler> for Timpre {
|
||||
/// Configuration of the core clocks
|
||||
#[non_exhaustive]
|
||||
pub struct Config {
|
||||
pub hsi: Option<Hsi>,
|
||||
pub hsi: Option<HSIPrescaler>,
|
||||
pub hse: Option<Hse>,
|
||||
pub csi: bool,
|
||||
pub hsi48: bool,
|
||||
pub hsi48: Option<super::Hsi48Config>,
|
||||
pub sys: Sysclk,
|
||||
|
||||
#[cfg(stm32h7)]
|
||||
pub pll_src: PllSource,
|
||||
|
||||
pub pll1: Option<Pll>,
|
||||
pub pll2: Option<Pll>,
|
||||
#[cfg(any(rcc_h5, stm32h7))]
|
||||
@ -193,13 +149,11 @@ pub struct Config {
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
hsi: Some(Hsi::Mhz64),
|
||||
hsi: Some(HSIPrescaler::DIV1),
|
||||
hse: None,
|
||||
csi: false,
|
||||
hsi48: false,
|
||||
hsi48: Some(Default::default()),
|
||||
sys: Sysclk::HSI,
|
||||
#[cfg(stm32h7)]
|
||||
pll_src: PllSource::Hsi,
|
||||
pll1: None,
|
||||
pll2: None,
|
||||
#[cfg(any(rcc_h5, stm32h7))]
|
||||
@ -214,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(),
|
||||
@ -312,19 +271,13 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
RCC.cr().modify(|w| w.set_hsion(false));
|
||||
None
|
||||
}
|
||||
Some(hsi) => {
|
||||
let (freq, hsidiv) = match hsi {
|
||||
Hsi::Mhz64 => (HSI_FREQ / 1u32, Hsidiv::DIV1),
|
||||
Hsi::Mhz32 => (HSI_FREQ / 2u32, Hsidiv::DIV2),
|
||||
Hsi::Mhz16 => (HSI_FREQ / 4u32, Hsidiv::DIV4),
|
||||
Hsi::Mhz8 => (HSI_FREQ / 8u32, Hsidiv::DIV8),
|
||||
};
|
||||
Some(hsidiv) => {
|
||||
RCC.cr().modify(|w| {
|
||||
w.set_hsidiv(hsidiv);
|
||||
w.set_hsion(true);
|
||||
});
|
||||
while !RCC.cr().read().hsirdy() {}
|
||||
Some(freq)
|
||||
Some(HSI_FREQ / hsidiv)
|
||||
}
|
||||
};
|
||||
|
||||
@ -350,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));
|
||||
@ -369,25 +315,29 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
}
|
||||
};
|
||||
|
||||
// H7 has shared PLLSRC, check it's equal in all PLLs.
|
||||
#[cfg(stm32h7)]
|
||||
{
|
||||
let plls = [&config.pll1, &config.pll2, &config.pll3];
|
||||
if !super::util::all_equal(plls.into_iter().flatten().map(|p| p.source)) {
|
||||
panic!("Source must be equal across all enabled PLLs.")
|
||||
};
|
||||
}
|
||||
|
||||
// Configure PLLs.
|
||||
let pll_input = PllInput {
|
||||
csi,
|
||||
hse,
|
||||
hsi,
|
||||
#[cfg(stm32h7)]
|
||||
source: config.pll_src,
|
||||
};
|
||||
let pll_input = PllInput { csi, hse, hsi };
|
||||
let pll1 = init_pll(0, config.pll1, &pll_input);
|
||||
let pll2 = init_pll(1, config.pll2, &pll_input);
|
||||
#[cfg(any(rcc_h5, stm32h7))]
|
||||
let pll3 = init_pll(2, config.pll3, &pll_input);
|
||||
|
||||
// Configure sysclk
|
||||
let (sys, sw) = match config.sys {
|
||||
Sysclk::HSI => (unwrap!(hsi), Sw::HSI),
|
||||
Sysclk::HSE => (unwrap!(hse), Sw::HSE),
|
||||
Sysclk::CSI => (unwrap!(csi), Sw::CSI),
|
||||
Sysclk::Pll1P => (unwrap!(pll1.p), Sw::PLL1_P),
|
||||
let sys = match config.sys {
|
||||
Sysclk::HSI => unwrap!(hsi),
|
||||
Sysclk::HSE => unwrap!(hse),
|
||||
Sysclk::CSI => unwrap!(csi),
|
||||
Sysclk::PLL1_P => unwrap!(pll1.p),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
// Check limits.
|
||||
@ -398,7 +348,14 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
VoltageScale::Scale2 => (Hertz(150_000_000), Hertz(150_000_000)),
|
||||
VoltageScale::Scale3 => (Hertz(100_000_000), Hertz(100_000_000)),
|
||||
};
|
||||
#[cfg(stm32h7)]
|
||||
#[cfg(pwr_h7rm0455)]
|
||||
let (d1cpre_clk_max, hclk_max, pclk_max) = match config.voltage_scale {
|
||||
VoltageScale::Scale0 => (Hertz(280_000_000), Hertz(280_000_000), Hertz(140_000_000)),
|
||||
VoltageScale::Scale1 => (Hertz(225_000_000), Hertz(225_000_000), Hertz(112_500_000)),
|
||||
VoltageScale::Scale2 => (Hertz(160_000_000), Hertz(160_000_000), Hertz(80_000_000)),
|
||||
VoltageScale::Scale3 => (Hertz(88_000_000), Hertz(88_000_000), Hertz(44_000_000)),
|
||||
};
|
||||
#[cfg(all(stm32h7, not(pwr_h7rm0455)))]
|
||||
let (d1cpre_clk_max, hclk_max, pclk_max) = match config.voltage_scale {
|
||||
VoltageScale::Scale0 => (Hertz(480_000_000), Hertz(240_000_000), Hertz(120_000_000)),
|
||||
VoltageScale::Scale1 => (Hertz(400_000_000), Hertz(200_000_000), Hertz(100_000_000)),
|
||||
@ -504,8 +461,8 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
|
||||
RCC.cfgr().modify(|w| w.set_timpre(config.timer_prescaler.into()));
|
||||
|
||||
RCC.cfgr().modify(|w| w.set_sw(sw));
|
||||
while RCC.cfgr().read().sws() != sw {}
|
||||
RCC.cfgr().modify(|w| w.set_sw(config.sys));
|
||||
while RCC.cfgr().read().sws() != config.sys {}
|
||||
|
||||
// IO compensation cell - Requires CSI clock and SYSCFG
|
||||
#[cfg(stm32h7)] // TODO h5
|
||||
@ -590,8 +547,6 @@ struct PllInput {
|
||||
hsi: Option<Hertz>,
|
||||
hse: Option<Hertz>,
|
||||
csi: Option<Hertz>,
|
||||
#[cfg(stm32h7)]
|
||||
source: PllSource,
|
||||
}
|
||||
|
||||
struct PllOutput {
|
||||
@ -621,15 +576,11 @@ fn init_pll(num: usize, config: Option<Pll>, input: &PllInput) -> PllOutput {
|
||||
};
|
||||
};
|
||||
|
||||
#[cfg(stm32h5)]
|
||||
let source = config.source;
|
||||
#[cfg(stm32h7)]
|
||||
let source = input.source;
|
||||
|
||||
let (in_clk, src) = match source {
|
||||
PllSource::Hsi => (unwrap!(input.hsi), Pllsrc::HSI),
|
||||
PllSource::Hse => (unwrap!(input.hse), Pllsrc::HSE),
|
||||
PllSource::Csi => (unwrap!(input.csi), Pllsrc::CSI),
|
||||
let in_clk = match config.source {
|
||||
PllSource::DISABLE => panic!("must not set PllSource::Disable"),
|
||||
PllSource::HSI => unwrap!(input.hsi),
|
||||
PllSource::HSE => unwrap!(input.hse),
|
||||
PllSource::CSI => unwrap!(input.csi),
|
||||
};
|
||||
|
||||
let ref_clk = in_clk / config.prediv as u32;
|
||||
@ -669,7 +620,7 @@ fn init_pll(num: usize, config: Option<Pll>, input: &PllInput) -> PllOutput {
|
||||
|
||||
#[cfg(stm32h5)]
|
||||
RCC.pllcfgr(num).write(|w| {
|
||||
w.set_pllsrc(src);
|
||||
w.set_pllsrc(config.source);
|
||||
w.set_divm(config.prediv);
|
||||
w.set_pllvcosel(vco_range);
|
||||
w.set_pllrge(ref_range);
|
||||
@ -683,7 +634,7 @@ fn init_pll(num: usize, config: Option<Pll>, input: &PllInput) -> PllOutput {
|
||||
{
|
||||
RCC.pllckselr().modify(|w| {
|
||||
w.set_divm(num, config.prediv);
|
||||
w.set_pllsrc(src);
|
||||
w.set_pllsrc(config.source);
|
||||
});
|
||||
RCC.pllcfgr().modify(|w| {
|
||||
w.set_pllvcosel(num, vco_range);
|
||||
|
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
|
||||
}
|
642
embassy-stm32/src/rcc/l.rs
Normal file
642
embassy-stm32/src/rcc/l.rs
Normal file
@ -0,0 +1,642 @@
|
||||
#[cfg(any(stm32l0, stm32l1))]
|
||||
pub use crate::pac::pwr::vals::Vos as VoltageScale;
|
||||
use crate::pac::rcc::regs::Cfgr;
|
||||
#[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, Ppre as APBPrescaler, Sw as ClockSrc};
|
||||
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, Eq, PartialEq)]
|
||||
pub enum HseMode {
|
||||
/// crystal/ceramic oscillator (HSEBYP=0)
|
||||
Oscillator,
|
||||
/// external analog clock (low swing) (HSEBYP=1)
|
||||
Bypass,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub struct Hse {
|
||||
/// HSE frequency.
|
||||
pub freq: Hertz,
|
||||
/// HSE mode.
|
||||
pub mode: HseMode,
|
||||
/// HSE prescaler
|
||||
#[cfg(any(stm32wb, stm32wl))]
|
||||
pub prescaler: HsePrescaler,
|
||||
}
|
||||
|
||||
/// Clocks configuration
|
||||
pub struct Config {
|
||||
// base clock sources
|
||||
pub msi: Option<MSIRange>,
|
||||
pub hsi: bool,
|
||||
pub hse: Option<Hse>,
|
||||
#[cfg(crs)]
|
||||
pub hsi48: Option<super::Hsi48Config>,
|
||||
|
||||
// pll
|
||||
pub pll: Option<Pll>,
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
||||
pub pllsai1: Option<Pll>,
|
||||
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
||||
pub pllsai2: Option<Pll>,
|
||||
|
||||
// sysclk, buses.
|
||||
pub mux: ClockSrc,
|
||||
pub ahb_pre: AHBPrescaler,
|
||||
pub apb1_pre: APBPrescaler,
|
||||
pub apb2_pre: APBPrescaler,
|
||||
#[cfg(any(stm32wl5x, stm32wb))]
|
||||
pub core2_ahb_pre: AHBPrescaler,
|
||||
#[cfg(any(stm32wl, stm32wb))]
|
||||
pub shared_ahb_pre: AHBPrescaler,
|
||||
|
||||
// muxes
|
||||
#[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 {
|
||||
#[inline]
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
hse: None,
|
||||
hsi: false,
|
||||
msi: Some(MSIRange::RANGE4M),
|
||||
mux: ClockSrc::MSI,
|
||||
ahb_pre: AHBPrescaler::DIV1,
|
||||
apb1_pre: APBPrescaler::DIV1,
|
||||
apb2_pre: APBPrescaler::DIV1,
|
||||
#[cfg(any(stm32wl5x, stm32wb))]
|
||||
core2_ahb_pre: AHBPrescaler::DIV1,
|
||||
#[cfg(any(stm32wl, stm32wb))]
|
||||
shared_ahb_pre: AHBPrescaler::DIV1,
|
||||
pll: None,
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
||||
pllsai1: None,
|
||||
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
||||
pllsai2: None,
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(stm32wb)]
|
||||
pub const WPAN_DEFAULT: Config = Config {
|
||||
hse: Some(Hse {
|
||||
freq: Hertz(32_000_000),
|
||||
mode: HseMode::Oscillator,
|
||||
prescaler: HsePrescaler::DIV1,
|
||||
}),
|
||||
mux: ClockSrc::PLL1_R,
|
||||
#[cfg(crs)]
|
||||
hsi48: Some(super::Hsi48Config { sync_from_usb: false }),
|
||||
msi: None,
|
||||
hsi: false,
|
||||
clk48_src: Clk48Src::PLL1_Q,
|
||||
|
||||
ls: super::LsConfig::default_lse(),
|
||||
|
||||
pll: Some(Pll {
|
||||
source: PllSource::HSE,
|
||||
prediv: PllPreDiv::DIV2,
|
||||
mul: PllMul::MUL12,
|
||||
divp: Some(PllPDiv::DIV3), // 32 / 2 * 12 / 3 = 64Mhz
|
||||
divq: Some(PllQDiv::DIV4), // 32 / 2 * 12 / 4 = 48Mhz
|
||||
divr: Some(PllRDiv::DIV3), // 32 / 2 * 12 / 3 = 64Mhz
|
||||
}),
|
||||
pllsai1: None,
|
||||
|
||||
ahb_pre: AHBPrescaler::DIV1,
|
||||
core2_ahb_pre: AHBPrescaler::DIV2,
|
||||
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.
|
||||
msi_enable(MSIRange::RANGE4M)
|
||||
}
|
||||
if RCC.cfgr().read().sws() != ClockSrc::MSI {
|
||||
// Set MSI as a clock source, reset prescalers.
|
||||
RCC.cfgr().write_value(Cfgr::default());
|
||||
// Wait for clock switch status bits to change.
|
||||
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);
|
||||
});
|
||||
|
||||
let rtc = config.ls.init();
|
||||
|
||||
let msi = config.msi.map(|range| {
|
||||
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() {}
|
||||
|
||||
HSI_FREQ
|
||||
});
|
||||
|
||||
let hse = config.hse.map(|hse| {
|
||||
RCC.cr().modify(|w| {
|
||||
#[cfg(stm32wl)]
|
||||
w.set_hsebyppwr(hse.mode == HseMode::Bypass);
|
||||
#[cfg(not(stm32wl))]
|
||||
w.set_hsebyp(hse.mode == HseMode::Bypass);
|
||||
w.set_hseon(true);
|
||||
});
|
||||
while !RCC.cr().read().hserdy() {}
|
||||
|
||||
hse.freq
|
||||
});
|
||||
|
||||
#[cfg(crs)]
|
||||
let _hsi48 = config.hsi48.map(|config| {
|
||||
//
|
||||
super::init_hsi48(config)
|
||||
});
|
||||
#[cfg(not(crs))]
|
||||
let _hsi48: Option<Hertz> = None;
|
||||
|
||||
let _plls = [
|
||||
&config.pll,
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
||||
&config.pllsai1,
|
||||
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
||||
&config.pllsai2,
|
||||
];
|
||||
|
||||
// L4 has shared PLLSRC, PLLM, check it's equal in all PLLs.
|
||||
#[cfg(all(stm32l4, not(rcc_l4plus)))]
|
||||
match super::util::get_equal(_plls.into_iter().flatten().map(|p| (p.source, p.prediv))) {
|
||||
Err(()) => panic!("Source must be equal across all enabled PLLs."),
|
||||
Ok(None) => {}
|
||||
Ok(Some((source, prediv))) => RCC.pllcfgr().write(|w| {
|
||||
w.set_pllm(prediv);
|
||||
w.set_pllsrc(source);
|
||||
}),
|
||||
};
|
||||
|
||||
// L4+, WL has shared PLLSRC, check it's equal in all PLLs.
|
||||
#[cfg(any(rcc_l4plus, stm32wl))]
|
||||
match super::util::get_equal(_plls.into_iter().flatten().map(|p| p.source)) {
|
||||
Err(()) => panic!("Source must be equal across all enabled PLLs."),
|
||||
Ok(None) => {}
|
||||
Ok(Some(source)) => RCC.pllcfgr().write(|w| {
|
||||
w.set_pllsrc(source);
|
||||
}),
|
||||
};
|
||||
|
||||
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);
|
||||
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
||||
let _pllsai2 = init_pll(PllInstance::Pllsai2, config.pllsai2, &pll_input);
|
||||
|
||||
let sys_clk = match config.mux {
|
||||
ClockSrc::HSE => hse.unwrap(),
|
||||
ClockSrc::HSI => hsi.unwrap(),
|
||||
ClockSrc::MSI => msi.unwrap(),
|
||||
ClockSrc::PLL1_R => pll.r.unwrap(),
|
||||
};
|
||||
|
||||
#[cfg(any(rcc_l0_v2, stm32l4, stm32l5, stm32wb))]
|
||||
RCC.ccipr().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::MSI => msi,
|
||||
Clk48Src::PLLSAI1_Q => pllsai1.q,
|
||||
Clk48Src::PLL1_Q => pll.q,
|
||||
};
|
||||
|
||||
#[cfg(rcc_l4plus)]
|
||||
assert!(sys_clk.0 <= 120_000_000);
|
||||
#[cfg(all(stm32l4, not(rcc_l4plus)))]
|
||||
assert!(sys_clk.0 <= 80_000_000);
|
||||
|
||||
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(any(stm32l4, stm32l5, stm32wlex))]
|
||||
let hclk2 = hclk1;
|
||||
#[cfg(any(stm32wl5x, stm32wb))]
|
||||
let hclk2 = sys_clk / config.core2_ahb_pre;
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wlex))]
|
||||
let hclk3 = hclk1;
|
||||
#[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,
|
||||
0..=32_000_000 => 1,
|
||||
0..=48_000_000 => 2,
|
||||
0..=64_000_000 => 3,
|
||||
_ => 4,
|
||||
};
|
||||
#[cfg(stm32l5)]
|
||||
let latency = match hclk1.0 {
|
||||
// VCORE Range 0 (performance), others TODO
|
||||
0..=20_000_000 => 0,
|
||||
0..=40_000_000 => 1,
|
||||
0..=60_000_000 => 2,
|
||||
0..=80_000_000 => 3,
|
||||
0..=100_000_000 => 4,
|
||||
_ => 5,
|
||||
};
|
||||
#[cfg(stm32wl)]
|
||||
let latency = match hclk3.0 {
|
||||
// VOS RANGE1, others TODO.
|
||||
..=18_000_000 => 0,
|
||||
..=36_000_000 => 1,
|
||||
_ => 2,
|
||||
};
|
||||
#[cfg(stm32wb)]
|
||||
let latency = match hclk3.0 {
|
||||
// VOS RANGE1, others TODO.
|
||||
..=18_000_000 => 0,
|
||||
..=36_000_000 => 1,
|
||||
..=54_000_000 => 2,
|
||||
..=64_000_000 => 3,
|
||||
_ => 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 {}
|
||||
|
||||
RCC.cfgr().modify(|w| {
|
||||
w.set_sw(config.mux);
|
||||
w.set_hpre(config.ahb_pre);
|
||||
w.set_ppre1(config.apb1_pre);
|
||||
w.set_ppre2(config.apb2_pre);
|
||||
});
|
||||
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| {
|
||||
w.set_shdhpre(config.shared_ahb_pre);
|
||||
#[cfg(any(stm32wl5x, stm32wb))]
|
||||
w.set_c2hpre(config.core2_ahb_pre);
|
||||
});
|
||||
while !RCC.extcfgr().read().shdhpref() {}
|
||||
#[cfg(any(stm32wl5x, stm32wb))]
|
||||
while !RCC.extcfgr().read().c2hpref() {}
|
||||
}
|
||||
|
||||
set_freqs(Clocks {
|
||||
sys: sys_clk,
|
||||
hclk1,
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))]
|
||||
hclk2,
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))]
|
||||
hclk3,
|
||||
pclk1,
|
||||
pclk2,
|
||||
pclk1_tim,
|
||||
pclk2_tim,
|
||||
#[cfg(stm32wl)]
|
||||
pclk3: hclk3,
|
||||
#[cfg(rcc_l4)]
|
||||
hsi: None,
|
||||
#[cfg(rcc_l4)]
|
||||
lse: None,
|
||||
#[cfg(rcc_l4)]
|
||||
pllsai1_p: None,
|
||||
#[cfg(rcc_l4)]
|
||||
pllsai2_p: None,
|
||||
#[cfg(rcc_l4)]
|
||||
pll1_p: None,
|
||||
#[cfg(rcc_l4)]
|
||||
pll1_q: None,
|
||||
#[cfg(rcc_l4)]
|
||||
sai1_extclk: None,
|
||||
#[cfg(rcc_l4)]
|
||||
sai2_extclk: None,
|
||||
rtc,
|
||||
});
|
||||
}
|
||||
|
||||
#[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),
|
||||
MSIRange::RANGE200K => Hertz(200_000),
|
||||
MSIRange::RANGE400K => Hertz(400_000),
|
||||
MSIRange::RANGE800K => Hertz(800_000),
|
||||
MSIRange::RANGE1M => Hertz(1_000_000),
|
||||
MSIRange::RANGE2M => Hertz(2_000_000),
|
||||
MSIRange::RANGE4M => Hertz(4_000_000),
|
||||
MSIRange::RANGE8M => Hertz(8_000_000),
|
||||
MSIRange::RANGE16M => Hertz(16_000_000),
|
||||
MSIRange::RANGE24M => Hertz(24_000_000),
|
||||
MSIRange::RANGE32M => Hertz(32_000_000),
|
||||
MSIRange::RANGE48M => Hertz(48_000_000),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
enum PllInstance {
|
||||
Pll,
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
||||
Pllsai1,
|
||||
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
||||
Pllsai2,
|
||||
}
|
||||
|
||||
fn pll_enable(instance: PllInstance, enabled: bool) {
|
||||
match instance {
|
||||
PllInstance::Pll => {
|
||||
RCC.cr().modify(|w| w.set_pllon(enabled));
|
||||
while RCC.cr().read().pllrdy() != enabled {}
|
||||
}
|
||||
#[cfg(any(stm32l4, stm32l5, stm32wb))]
|
||||
PllInstance::Pllsai1 => {
|
||||
RCC.cr().modify(|w| w.set_pllsai1on(enabled));
|
||||
while RCC.cr().read().pllsai1rdy() != enabled {}
|
||||
}
|
||||
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
||||
PllInstance::Pllsai2 => {
|
||||
RCC.cr().modify(|w| w.set_pllsai2on(enabled));
|
||||
while RCC.cr().read().pllsai2rdy() != enabled {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use pll::*;
|
||||
|
||||
#[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;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Pll {
|
||||
/// PLL source
|
||||
pub source: PllSource,
|
||||
|
||||
/// PLL multiplication factor.
|
||||
pub mul: PllMul,
|
||||
|
||||
/// PLL main output division factor.
|
||||
pub div: PllDiv,
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
@ -1,219 +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),
|
||||
HSI16,
|
||||
}
|
||||
|
||||
/// PLL clock input source
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum PLLSource {
|
||||
HSI16,
|
||||
HSE(Hertz),
|
||||
}
|
||||
|
||||
impl From<PLLSource> for Pllsrc {
|
||||
fn from(val: PLLSource) -> Pllsrc {
|
||||
match val {
|
||||
PLLSource::HSI16 => 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::HSI16 => {
|
||||
// Enable HSI16
|
||||
RCC.cr().write(|w| w.set_hsi16on(true));
|
||||
while !RCC.cr().read().hsi16rdy() {}
|
||||
|
||||
(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::HSI16 => {
|
||||
// Enable HSI
|
||||
RCC.cr().write(|w| w.set_hsi16on(true));
|
||||
while !RCC.cr().read().hsi16rdy() {}
|
||||
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::PLL)
|
||||
}
|
||||
};
|
||||
|
||||
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 ahb_freq = sys_clk / config.ahb_pre;
|
||||
|
||||
let (apb1_freq, apb1_tim_freq) = match config.apb1_pre {
|
||||
APBPrescaler::DIV1 => (ahb_freq, ahb_freq),
|
||||
pre => {
|
||||
let freq = ahb_freq / pre;
|
||||
(freq, freq * 2u32)
|
||||
}
|
||||
};
|
||||
|
||||
let (apb2_freq, apb2_tim_freq) = match config.apb2_pre {
|
||||
APBPrescaler::DIV1 => (ahb_freq, ahb_freq),
|
||||
pre => {
|
||||
let freq = ahb_freq / pre;
|
||||
(freq, freq * 2u32)
|
||||
}
|
||||
};
|
||||
|
||||
#[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: ahb_freq,
|
||||
pclk1: apb1_freq,
|
||||
pclk2: apb2_freq,
|
||||
pclk1_tim: apb1_tim_freq,
|
||||
pclk2_tim: apb2_tim_freq,
|
||||
rtc,
|
||||
});
|
||||
}
|
@ -1,446 +0,0 @@
|
||||
use crate::pac::rcc::regs::Cfgr;
|
||||
use crate::pac::rcc::vals::Msirgsel;
|
||||
pub use crate::pac::rcc::vals::{
|
||||
Clk48sel as Clk48Src, 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,
|
||||
};
|
||||
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 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
|
||||
pub struct Config {
|
||||
// base clock sources
|
||||
pub msi: Option<MSIRange>,
|
||||
pub hsi16: bool,
|
||||
pub hse: Option<Hertz>,
|
||||
#[cfg(not(any(stm32l47x, stm32l48x)))]
|
||||
pub hsi48: bool,
|
||||
|
||||
// pll
|
||||
pub pll: Option<Pll>,
|
||||
pub pllsai1: Option<Pll>,
|
||||
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
||||
pub pllsai2: Option<Pll>,
|
||||
|
||||
// sysclk, buses.
|
||||
pub mux: ClockSrc,
|
||||
pub ahb_pre: AHBPrescaler,
|
||||
pub apb1_pre: APBPrescaler,
|
||||
pub apb2_pre: APBPrescaler,
|
||||
|
||||
// muxes
|
||||
pub clk48_src: Clk48Src,
|
||||
|
||||
// low speed LSI/LSE/RTC
|
||||
pub ls: super::LsConfig,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
#[inline]
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
hse: None,
|
||||
hsi16: false,
|
||||
msi: Some(MSIRange::RANGE4M),
|
||||
mux: ClockSrc::MSI,
|
||||
ahb_pre: AHBPrescaler::DIV1,
|
||||
apb1_pre: APBPrescaler::DIV1,
|
||||
apb2_pre: APBPrescaler::DIV1,
|
||||
pll: None,
|
||||
pllsai1: None,
|
||||
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
||||
pllsai2: None,
|
||||
#[cfg(not(any(stm32l471, stm32l475, stm32l476, stm32l486)))]
|
||||
hsi48: true,
|
||||
clk48_src: Clk48Src::HSI48,
|
||||
ls: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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| {
|
||||
w.set_msirgsel(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() {}
|
||||
}
|
||||
if RCC.cfgr().read().sws() != ClockSrc::MSI {
|
||||
// Set MSI as a clock source, reset prescalers.
|
||||
RCC.cfgr().write_value(Cfgr::default());
|
||||
// Wait for clock switch status bits to change.
|
||||
while RCC.cfgr().read().sws() != ClockSrc::MSI {}
|
||||
}
|
||||
|
||||
#[cfg(stm32l5)]
|
||||
crate::pac::PWR.cr1().modify(|w| {
|
||||
w.set_vos(crate::pac::pwr::vals::Vos::RANGE0);
|
||||
});
|
||||
|
||||
let rtc = config.ls.init();
|
||||
|
||||
let msi = config.msi.map(|range| {
|
||||
// Enable MSI
|
||||
RCC.cr().write(|w| {
|
||||
w.set_msirange(range);
|
||||
w.set_msirgsel(Msirgsel::CR);
|
||||
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 {}
|
||||
|
||||
msirange_to_hertz(range)
|
||||
});
|
||||
|
||||
let hsi16 = config.hsi16.then(|| {
|
||||
RCC.cr().write(|w| w.set_hsion(true));
|
||||
while !RCC.cr().read().hsirdy() {}
|
||||
|
||||
HSI_FREQ
|
||||
});
|
||||
|
||||
let hse = config.hse.map(|freq| {
|
||||
RCC.cr().write(|w| w.set_hseon(true));
|
||||
while !RCC.cr().read().hserdy() {}
|
||||
|
||||
freq
|
||||
});
|
||||
|
||||
#[cfg(not(any(stm32l47x, stm32l48x)))]
|
||||
let hsi48 = config.hsi48.then(|| {
|
||||
RCC.crrcr().modify(|w| w.set_hsi48on(true));
|
||||
while !RCC.crrcr().read().hsi48rdy() {}
|
||||
|
||||
Hertz(48_000_000)
|
||||
});
|
||||
#[cfg(any(stm32l47x, stm32l48x))]
|
||||
let hsi48 = None;
|
||||
|
||||
let _plls = [
|
||||
&config.pll,
|
||||
&config.pllsai1,
|
||||
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
||||
&config.pllsai2,
|
||||
];
|
||||
|
||||
// L4 has shared PLLSRC, PLLM, check it's equal in all PLLs.
|
||||
#[cfg(all(stm32l4, not(rcc_l4plus)))]
|
||||
match get_equal(_plls.into_iter().flatten().map(|p| (p.source, p.prediv))) {
|
||||
Err(()) => panic!("Source must be equal across all enabled PLLs."),
|
||||
Ok(None) => {}
|
||||
Ok(Some((source, prediv))) => RCC.pllcfgr().write(|w| {
|
||||
w.set_pllm(prediv);
|
||||
w.set_pllsrc(source);
|
||||
}),
|
||||
};
|
||||
|
||||
// L4+ has shared PLLSRC, check it's equal in all PLLs.
|
||||
#[cfg(any(rcc_l4plus))]
|
||||
match get_equal(_plls.into_iter().flatten().map(|p| p.source)) {
|
||||
Err(()) => panic!("Source must be equal across all enabled PLLs."),
|
||||
Ok(None) => {}
|
||||
Ok(Some(source)) => RCC.pllcfgr().write(|w| {
|
||||
w.set_pllsrc(source);
|
||||
}),
|
||||
};
|
||||
|
||||
let pll_input = PllInput { hse, hsi16, msi };
|
||||
let pll = init_pll(PllInstance::Pll, config.pll, &pll_input);
|
||||
let pllsai1 = init_pll(PllInstance::Pllsai1, config.pllsai1, &pll_input);
|
||||
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
||||
let _pllsai2 = init_pll(PllInstance::Pllsai2, config.pllsai2, &pll_input);
|
||||
|
||||
let sys_clk = match config.mux {
|
||||
ClockSrc::HSE => hse.unwrap(),
|
||||
#[cfg(rcc_l5)]
|
||||
ClockSrc::HSI16 => hsi16.unwrap(),
|
||||
#[cfg(not(rcc_l5))]
|
||||
ClockSrc::HSI => hsi16.unwrap(),
|
||||
ClockSrc::MSI => msi.unwrap(),
|
||||
ClockSrc::PLL => pll._r.unwrap(),
|
||||
};
|
||||
|
||||
#[cfg(stm32l4)]
|
||||
RCC.ccipr().modify(|w| w.set_clk48sel(config.clk48_src));
|
||||
#[cfg(stm32l5)]
|
||||
RCC.ccipr1().modify(|w| w.set_clk48sel(config.clk48_src));
|
||||
let _clk48 = match config.clk48_src {
|
||||
Clk48Src::HSI48 => hsi48,
|
||||
Clk48Src::MSI => msi,
|
||||
Clk48Src::PLLSAI1_Q => pllsai1._q,
|
||||
#[cfg(rcc_l5)]
|
||||
Clk48Src::PLL_Q => pll._q,
|
||||
#[cfg(not(rcc_l5))]
|
||||
Clk48Src::PLL1_Q => pll._q,
|
||||
};
|
||||
|
||||
#[cfg(rcc_l4plus)]
|
||||
assert!(sys_clk.0 <= 120_000_000);
|
||||
#[cfg(all(stm32l4, not(rcc_l4plus)))]
|
||||
assert!(sys_clk.0 <= 80_000_000);
|
||||
|
||||
// Set flash wait states
|
||||
#[cfg(stm32l4)]
|
||||
FLASH.acr().modify(|w| {
|
||||
w.set_latency(match sys_clk.0 {
|
||||
0..=16_000_000 => 0,
|
||||
0..=32_000_000 => 1,
|
||||
0..=48_000_000 => 2,
|
||||
0..=64_000_000 => 3,
|
||||
_ => 4,
|
||||
})
|
||||
});
|
||||
// VCORE Range 0 (performance), others TODO
|
||||
#[cfg(stm32l5)]
|
||||
FLASH.acr().modify(|w| {
|
||||
w.set_latency(match sys_clk.0 {
|
||||
0..=20_000_000 => 0,
|
||||
0..=40_000_000 => 1,
|
||||
0..=60_000_000 => 2,
|
||||
0..=80_000_000 => 3,
|
||||
0..=100_000_000 => 4,
|
||||
_ => 5,
|
||||
})
|
||||
});
|
||||
|
||||
RCC.cfgr().modify(|w| {
|
||||
w.set_sw(config.mux);
|
||||
w.set_hpre(config.ahb_pre);
|
||||
w.set_ppre1(config.apb1_pre);
|
||||
w.set_ppre2(config.apb2_pre);
|
||||
});
|
||||
|
||||
let ahb_freq = sys_clk / config.ahb_pre;
|
||||
|
||||
let (apb1_freq, apb1_tim_freq) = match config.apb1_pre {
|
||||
APBPrescaler::DIV1 => (ahb_freq, ahb_freq),
|
||||
pre => {
|
||||
let freq = ahb_freq / pre;
|
||||
(freq, freq * 2u32)
|
||||
}
|
||||
};
|
||||
|
||||
let (apb2_freq, apb2_tim_freq) = match config.apb2_pre {
|
||||
APBPrescaler::DIV1 => (ahb_freq, ahb_freq),
|
||||
pre => {
|
||||
let freq = ahb_freq / pre;
|
||||
(freq, freq * 2u32)
|
||||
}
|
||||
};
|
||||
|
||||
set_freqs(Clocks {
|
||||
sys: sys_clk,
|
||||
hclk1: ahb_freq,
|
||||
hclk2: ahb_freq,
|
||||
hclk3: ahb_freq,
|
||||
pclk1: apb1_freq,
|
||||
pclk2: apb2_freq,
|
||||
pclk1_tim: apb1_tim_freq,
|
||||
pclk2_tim: apb2_tim_freq,
|
||||
#[cfg(rcc_l4)]
|
||||
hsi: None,
|
||||
#[cfg(rcc_l4)]
|
||||
lse: None,
|
||||
#[cfg(rcc_l4)]
|
||||
pllsai1_p: None,
|
||||
#[cfg(rcc_l4)]
|
||||
pllsai2_p: None,
|
||||
#[cfg(rcc_l4)]
|
||||
pll1_p: None,
|
||||
#[cfg(rcc_l4)]
|
||||
pll1_q: None,
|
||||
#[cfg(rcc_l4)]
|
||||
sai1_extclk: None,
|
||||
#[cfg(rcc_l4)]
|
||||
sai2_extclk: None,
|
||||
rtc,
|
||||
});
|
||||
}
|
||||
|
||||
fn msirange_to_hertz(range: MSIRange) -> Hertz {
|
||||
match range {
|
||||
MSIRange::RANGE100K => Hertz(100_000),
|
||||
MSIRange::RANGE200K => Hertz(200_000),
|
||||
MSIRange::RANGE400K => Hertz(400_000),
|
||||
MSIRange::RANGE800K => Hertz(800_000),
|
||||
MSIRange::RANGE1M => Hertz(1_000_000),
|
||||
MSIRange::RANGE2M => Hertz(2_000_000),
|
||||
MSIRange::RANGE4M => Hertz(4_000_000),
|
||||
MSIRange::RANGE8M => Hertz(8_000_000),
|
||||
MSIRange::RANGE16M => Hertz(16_000_000),
|
||||
MSIRange::RANGE24M => Hertz(24_000_000),
|
||||
MSIRange::RANGE32M => Hertz(32_000_000),
|
||||
MSIRange::RANGE48M => Hertz(48_000_000),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn get_equal<T: Eq>(mut iter: impl Iterator<Item = T>) -> Result<Option<T>, ()> {
|
||||
let Some(x) = iter.next() else { return Ok(None) };
|
||||
if !iter.all(|y| y == x) {
|
||||
return Err(());
|
||||
}
|
||||
return Ok(Some(x));
|
||||
}
|
||||
|
||||
struct PllInput {
|
||||
hsi16: Option<Hertz>,
|
||||
hse: Option<Hertz>,
|
||||
msi: Option<Hertz>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct PllOutput {
|
||||
_p: Option<Hertz>,
|
||||
_q: Option<Hertz>,
|
||||
_r: Option<Hertz>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
enum PllInstance {
|
||||
Pll,
|
||||
Pllsai1,
|
||||
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
||||
Pllsai2,
|
||||
}
|
||||
|
||||
fn init_pll(instance: PllInstance, config: Option<Pll>, input: &PllInput) -> PllOutput {
|
||||
// Disable PLL
|
||||
match instance {
|
||||
PllInstance::Pll => {
|
||||
RCC.cr().modify(|w| w.set_pllon(false));
|
||||
while RCC.cr().read().pllrdy() {}
|
||||
}
|
||||
PllInstance::Pllsai1 => {
|
||||
RCC.cr().modify(|w| w.set_pllsai1on(false));
|
||||
while RCC.cr().read().pllsai1rdy() {}
|
||||
}
|
||||
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
||||
PllInstance::Pllsai2 => {
|
||||
RCC.cr().modify(|w| w.set_pllsai2on(false));
|
||||
while RCC.cr().read().pllsai2rdy() {}
|
||||
}
|
||||
}
|
||||
|
||||
let Some(pll) = config else { return PllOutput::default() };
|
||||
|
||||
let pll_src = match pll.source {
|
||||
PLLSource::NONE => panic!("must not select PLL source as NONE"),
|
||||
PLLSource::HSE => input.hse,
|
||||
#[cfg(rcc_l5)]
|
||||
PLLSource::HSI16 => input.hsi16,
|
||||
#[cfg(not(rcc_l5))]
|
||||
PLLSource::HSI => input.hsi16,
|
||||
PLLSource::MSI => input.msi,
|
||||
};
|
||||
|
||||
let pll_src = pll_src.unwrap();
|
||||
|
||||
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);
|
||||
}),
|
||||
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
|
||||
match instance {
|
||||
PllInstance::Pll => {
|
||||
RCC.cr().modify(|w| w.set_pllon(true));
|
||||
while !RCC.cr().read().pllrdy() {}
|
||||
}
|
||||
PllInstance::Pllsai1 => {
|
||||
RCC.cr().modify(|w| w.set_pllsai1on(true));
|
||||
while !RCC.cr().read().pllsai1rdy() {}
|
||||
}
|
||||
#[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))]
|
||||
PllInstance::Pllsai2 => {
|
||||
RCC.cr().modify(|w| w.set_pllsai2on(true));
|
||||
while !RCC.cr().read().pllsai2rdy() {}
|
||||
}
|
||||
}
|
||||
|
||||
PllOutput { _p: p, _q: q, _r: r }
|
||||
}
|
@ -9,25 +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), path = "f4.rs")]
|
||||
#[cfg_attr(rcc_f7, path = "f7.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), 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_wb, path = "wb.rs")]
|
||||
#[cfg_attr(rcc_wba, path = "wba.rs")]
|
||||
#[cfg_attr(any(rcc_wl5, rcc_wle), path = "wl.rs")]
|
||||
mod _version;
|
||||
#[cfg(feature = "low-power")]
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
pub use _version::*;
|
||||
|
||||
@ -186,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
|
||||
///
|
||||
@ -249,3 +236,33 @@ pub(crate) mod sealed {
|
||||
}
|
||||
|
||||
pub trait RccPeripheral: sealed::RccPeripheral + 'static {}
|
||||
|
||||
#[allow(unused)]
|
||||
mod util {
|
||||
use crate::time::Hertz;
|
||||
|
||||
pub fn calc_pclk<D>(hclk: Hertz, ppre: D) -> (Hertz, Hertz)
|
||||
where
|
||||
Hertz: core::ops::Div<D, Output = Hertz>,
|
||||
{
|
||||
let pclk = hclk / ppre;
|
||||
let pclk_tim = if hclk == pclk { pclk } else { pclk * 2u32 };
|
||||
(pclk, pclk_tim)
|
||||
}
|
||||
|
||||
pub fn all_equal<T: Eq>(mut iter: impl Iterator<Item = T>) -> bool {
|
||||
let Some(x) = iter.next() else { return true };
|
||||
if !iter.all(|y| y == x) {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn get_equal<T: Eq>(mut iter: impl Iterator<Item = T>) -> Result<Option<T>, ()> {
|
||||
let Some(x) = iter.next() else { return Ok(None) };
|
||||
if !iter.all(|y| y == x) {
|
||||
return Err(());
|
||||
}
|
||||
Ok(Some(x))
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ pub const HSI_FREQ: Hertz = Hertz(16_000_000);
|
||||
pub use crate::pac::pwr::vals::Vos as VoltageScale;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum ClockSrc {
|
||||
/// Use an internal medium speed oscillator (MSIS) as the system clock.
|
||||
MSI(Msirange),
|
||||
@ -19,9 +20,9 @@ pub enum ClockSrc {
|
||||
/// never exceed 50 MHz.
|
||||
HSE(Hertz),
|
||||
/// Use the 16 MHz internal high speed oscillator as the system clock.
|
||||
HSI16,
|
||||
HSI,
|
||||
/// Use PLL1 as the system clock.
|
||||
PLL1R(PllConfig),
|
||||
PLL1_R(PllConfig),
|
||||
}
|
||||
|
||||
impl Default for ClockSrc {
|
||||
@ -34,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.
|
||||
@ -53,10 +54,10 @@ pub struct PllConfig {
|
||||
}
|
||||
|
||||
impl PllConfig {
|
||||
/// A configuration for HSI16 / 1 * 10 / 1 = 160 MHz
|
||||
pub const fn hsi16_160mhz() -> Self {
|
||||
/// A configuration for HSI / 1 * 10 / 1 = 160 MHz
|
||||
pub const fn hsi_160mhz() -> Self {
|
||||
PllConfig {
|
||||
source: PllSrc::HSI16,
|
||||
source: PllSource::HSI,
|
||||
m: Pllm::DIV1,
|
||||
n: Plln::MUL10,
|
||||
r: Plldiv::DIV1,
|
||||
@ -66,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,
|
||||
@ -75,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.
|
||||
@ -84,15 +85,15 @@ pub enum PllSrc {
|
||||
/// never exceed 50 MHz.
|
||||
HSE(Hertz),
|
||||
/// Use the 16 MHz internal high speed oscillator as the PLL source.
|
||||
HSI16,
|
||||
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::HSI16 => Pllsrc::HSI16,
|
||||
PllSource::MSIS(..) => Pllsrc::MSIS,
|
||||
PllSource::HSE(..) => Pllsrc::HSE,
|
||||
PllSource::HSI => Pllsrc::HSI,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -102,8 +103,8 @@ impl Into<Sw> for ClockSrc {
|
||||
match self {
|
||||
ClockSrc::MSI(..) => Sw::MSIS,
|
||||
ClockSrc::HSE(..) => Sw::HSE,
|
||||
ClockSrc::HSI16 => Sw::HSI16,
|
||||
ClockSrc::PLL1R(..) => Sw::PLL1_R,
|
||||
ClockSrc::HSI => Sw::HSI,
|
||||
ClockSrc::PLL1_R(..) => Sw::PLL1_R,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -114,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`.
|
||||
@ -125,7 +126,7 @@ pub struct Config {
|
||||
}
|
||||
|
||||
impl Config {
|
||||
unsafe fn init_hsi16(&self) -> Hertz {
|
||||
unsafe fn init_hsi(&self) -> Hertz {
|
||||
RCC.cr().write(|w| w.set_hsion(true));
|
||||
while !RCC.cr().read().hsirdy() {}
|
||||
|
||||
@ -169,7 +170,7 @@ impl Config {
|
||||
|
||||
RCC.icscr1().modify(|w| {
|
||||
w.set_msisrange(range);
|
||||
w.set_msirgsel(Msirgsel::RCC_ICSCR1);
|
||||
w.set_msirgsel(Msirgsel::ICSCR1);
|
||||
});
|
||||
RCC.cr().write(|w| {
|
||||
w.set_msipllen(false);
|
||||
@ -188,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(),
|
||||
}
|
||||
@ -211,13 +212,13 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
let sys_clk = match config.mux {
|
||||
ClockSrc::MSI(range) => config.init_msis(range),
|
||||
ClockSrc::HSE(freq) => config.init_hse(freq),
|
||||
ClockSrc::HSI16 => config.init_hsi16(),
|
||||
ClockSrc::PLL1R(pll) => {
|
||||
ClockSrc::HSI => config.init_hsi(),
|
||||
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::HSI16 => config.init_hsi16(),
|
||||
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
|
||||
@ -292,7 +293,7 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
// Set the prescaler for PWR EPOD
|
||||
w.set_pllmboost(mboost);
|
||||
|
||||
// Enable PLL1R output
|
||||
// Enable PLL1_R output
|
||||
w.set_pllren(true);
|
||||
});
|
||||
|
||||
@ -321,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
|
||||
|
@ -1,258 +0,0 @@
|
||||
pub use crate::pac::rcc::vals::{
|
||||
Hpre as AHBPrescaler, Hsepre as HsePrescaler, Pllm, Plln, Pllp, Pllq, Pllr, Pllsrc as PllSource,
|
||||
Ppre as APBPrescaler, Sw as Sysclk,
|
||||
};
|
||||
use crate::rcc::{set_freqs, Clocks};
|
||||
use crate::time::{mhz, Hertz};
|
||||
|
||||
/// HSI speed
|
||||
pub const HSI_FREQ: Hertz = Hertz(16_000_000);
|
||||
|
||||
pub struct Hse {
|
||||
pub prediv: HsePrescaler,
|
||||
|
||||
pub frequency: Hertz,
|
||||
}
|
||||
|
||||
pub struct PllMux {
|
||||
/// Source clock selection.
|
||||
pub source: PllSource,
|
||||
|
||||
/// PLL pre-divider (DIVM). Must be between 1 and 63.
|
||||
pub prediv: Pllm,
|
||||
}
|
||||
|
||||
pub struct Pll {
|
||||
/// PLL multiplication factor. Must be between 4 and 512.
|
||||
pub mul: Plln,
|
||||
|
||||
/// PLL P division factor. If None, PLL P output is disabled. Must be between 1 and 128.
|
||||
/// On PLL1, it must be even (in particular, it cannot be 1.)
|
||||
pub divp: Option<Pllp>,
|
||||
/// PLL Q division factor. If None, PLL Q output is disabled. Must be between 1 and 128.
|
||||
pub divq: Option<Pllq>,
|
||||
/// PLL R division factor. If None, PLL R output is disabled. Must be between 1 and 128.
|
||||
pub divr: Option<Pllr>,
|
||||
}
|
||||
|
||||
/// Clocks configutation
|
||||
pub struct Config {
|
||||
pub hse: Option<Hse>,
|
||||
pub sys: Sysclk,
|
||||
pub mux: Option<PllMux>,
|
||||
pub hsi48: bool,
|
||||
|
||||
pub pll: Option<Pll>,
|
||||
pub pllsai: Option<Pll>,
|
||||
|
||||
pub ahb1_pre: AHBPrescaler,
|
||||
pub ahb2_pre: AHBPrescaler,
|
||||
pub ahb3_pre: AHBPrescaler,
|
||||
pub apb1_pre: APBPrescaler,
|
||||
pub apb2_pre: APBPrescaler,
|
||||
|
||||
pub ls: super::LsConfig,
|
||||
}
|
||||
|
||||
pub const WPAN_DEFAULT: Config = Config {
|
||||
hse: Some(Hse {
|
||||
frequency: mhz(32),
|
||||
prediv: HsePrescaler::DIV1,
|
||||
}),
|
||||
sys: Sysclk::PLL,
|
||||
mux: Some(PllMux {
|
||||
source: PllSource::HSE,
|
||||
prediv: Pllm::DIV2,
|
||||
}),
|
||||
hsi48: true,
|
||||
|
||||
ls: super::LsConfig::default_lse(),
|
||||
|
||||
pll: Some(Pll {
|
||||
mul: Plln::MUL12,
|
||||
divp: Some(Pllp::DIV3),
|
||||
divq: Some(Pllq::DIV4),
|
||||
divr: Some(Pllr::DIV3),
|
||||
}),
|
||||
pllsai: None,
|
||||
|
||||
ahb1_pre: AHBPrescaler::DIV1,
|
||||
ahb2_pre: AHBPrescaler::DIV2,
|
||||
ahb3_pre: AHBPrescaler::DIV1,
|
||||
apb1_pre: APBPrescaler::DIV1,
|
||||
apb2_pre: APBPrescaler::DIV1,
|
||||
};
|
||||
|
||||
impl Default for Config {
|
||||
#[inline]
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
hse: None,
|
||||
sys: Sysclk::HSI16,
|
||||
mux: None,
|
||||
pll: None,
|
||||
pllsai: None,
|
||||
hsi48: true,
|
||||
|
||||
ls: Default::default(),
|
||||
|
||||
ahb1_pre: AHBPrescaler::DIV1,
|
||||
ahb2_pre: AHBPrescaler::DIV1,
|
||||
ahb3_pre: AHBPrescaler::DIV1,
|
||||
apb1_pre: APBPrescaler::DIV1,
|
||||
apb2_pre: APBPrescaler::DIV1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(stm32wb)]
|
||||
/// RCC initialization function
|
||||
pub(crate) unsafe fn init(config: Config) {
|
||||
let hse_clk = config.hse.as_ref().map(|hse| hse.frequency / hse.prediv);
|
||||
|
||||
let mux_clk = config.mux.as_ref().map(|pll_mux| {
|
||||
(match pll_mux.source {
|
||||
PllSource::HSE => hse_clk.unwrap(),
|
||||
PllSource::HSI16 => HSI_FREQ,
|
||||
_ => unreachable!(),
|
||||
} / pll_mux.prediv)
|
||||
});
|
||||
|
||||
let (pll_r, _pll_q, _pll_p) = match &config.pll {
|
||||
Some(pll) => {
|
||||
let pll_vco = mux_clk.unwrap() * pll.mul as u32;
|
||||
|
||||
(
|
||||
pll.divr.map(|divr| pll_vco / divr),
|
||||
pll.divq.map(|divq| pll_vco / divq),
|
||||
pll.divp.map(|divp| pll_vco / divp),
|
||||
)
|
||||
}
|
||||
None => (None, None, None),
|
||||
};
|
||||
|
||||
let sys_clk = match config.sys {
|
||||
Sysclk::HSE => hse_clk.unwrap(),
|
||||
Sysclk::HSI16 => HSI_FREQ,
|
||||
Sysclk::PLL => pll_r.unwrap(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let ahb1_clk = sys_clk / config.ahb1_pre;
|
||||
let ahb2_clk = sys_clk / config.ahb2_pre;
|
||||
let ahb3_clk = sys_clk / config.ahb3_pre;
|
||||
|
||||
let (apb1_clk, apb1_tim_clk) = match config.apb1_pre {
|
||||
APBPrescaler::DIV1 => (ahb1_clk, ahb1_clk),
|
||||
pre => {
|
||||
let freq = ahb1_clk / pre;
|
||||
(freq, freq * 2u32)
|
||||
}
|
||||
};
|
||||
|
||||
let (apb2_clk, apb2_tim_clk) = match config.apb2_pre {
|
||||
APBPrescaler::DIV1 => (ahb1_clk, ahb1_clk),
|
||||
pre => {
|
||||
let freq = ahb1_clk / pre;
|
||||
(freq, freq * 2u32)
|
||||
}
|
||||
};
|
||||
|
||||
let rcc = crate::pac::RCC;
|
||||
|
||||
let needs_hsi = if let Some(pll_mux) = &config.mux {
|
||||
pll_mux.source == PllSource::HSI16
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if needs_hsi || config.sys == Sysclk::HSI16 {
|
||||
rcc.cr().modify(|w| {
|
||||
w.set_hsion(true);
|
||||
});
|
||||
|
||||
while !rcc.cr().read().hsirdy() {}
|
||||
}
|
||||
|
||||
rcc.cfgr().modify(|w| w.set_stopwuck(true));
|
||||
|
||||
let rtc = config.ls.init();
|
||||
|
||||
match &config.hse {
|
||||
Some(hse) => {
|
||||
rcc.cr().modify(|w| {
|
||||
w.set_hsepre(hse.prediv);
|
||||
w.set_hseon(true);
|
||||
});
|
||||
|
||||
while !rcc.cr().read().hserdy() {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match &config.mux {
|
||||
Some(pll_mux) => {
|
||||
rcc.pllcfgr().modify(|w| {
|
||||
w.set_pllm(pll_mux.prediv);
|
||||
w.set_pllsrc(pll_mux.source.into());
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
match &config.pll {
|
||||
Some(pll) => {
|
||||
rcc.pllcfgr().modify(|w| {
|
||||
w.set_plln(pll.mul);
|
||||
pll.divp.map(|divp| {
|
||||
w.set_pllpen(true);
|
||||
w.set_pllp(divp)
|
||||
});
|
||||
pll.divq.map(|divq| {
|
||||
w.set_pllqen(true);
|
||||
w.set_pllq(divq)
|
||||
});
|
||||
pll.divr.map(|divr| {
|
||||
w.set_pllren(true);
|
||||
w.set_pllr(divr);
|
||||
});
|
||||
});
|
||||
|
||||
rcc.cr().modify(|w| w.set_pllon(true));
|
||||
|
||||
while !rcc.cr().read().pllrdy() {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let _hsi48 = config.hsi48.then(|| {
|
||||
rcc.crrcr().modify(|w| w.set_hsi48on(true));
|
||||
while !rcc.crrcr().read().hsi48rdy() {}
|
||||
|
||||
Hertz(48_000_000)
|
||||
});
|
||||
|
||||
rcc.cfgr().modify(|w| {
|
||||
w.set_sw(config.sys.into());
|
||||
w.set_hpre(config.ahb1_pre);
|
||||
w.set_ppre1(config.apb1_pre);
|
||||
w.set_ppre2(config.apb2_pre);
|
||||
});
|
||||
|
||||
rcc.extcfgr().modify(|w| {
|
||||
w.set_c2hpre(config.ahb2_pre);
|
||||
w.set_shdhpre(config.ahb3_pre);
|
||||
});
|
||||
|
||||
set_freqs(Clocks {
|
||||
sys: sys_clk,
|
||||
hclk1: ahb1_clk,
|
||||
hclk2: ahb2_clk,
|
||||
hclk3: ahb3_clk,
|
||||
pclk1: apb1_clk,
|
||||
pclk2: apb2_clk,
|
||||
pclk1_tim: apb1_tim_clk,
|
||||
pclk2_tim: apb2_tim_clk,
|
||||
rtc,
|
||||
})
|
||||
}
|
@ -13,20 +13,20 @@ pub use crate::pac::rcc::vals::{Hpre as AHBPrescaler, Ppre as APBPrescaler};
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum ClockSrc {
|
||||
HSE(Hertz),
|
||||
HSI16,
|
||||
HSI,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum PllSrc {
|
||||
pub enum PllSource {
|
||||
HSE(Hertz),
|
||||
HSI16,
|
||||
HSI,
|
||||
}
|
||||
|
||||
impl Into<Pllsrc> for PllSrc {
|
||||
impl Into<Pllsrc> for PllSource {
|
||||
fn into(self) -> Pllsrc {
|
||||
match self {
|
||||
PllSrc::HSE(..) => Pllsrc::HSE,
|
||||
PllSrc::HSI16 => Pllsrc::HSI16,
|
||||
PllSource::HSE(..) => Pllsrc::HSE,
|
||||
PllSource::HSI => Pllsrc::HSI,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -35,7 +35,7 @@ impl Into<Sw> for ClockSrc {
|
||||
fn into(self) -> Sw {
|
||||
match self {
|
||||
ClockSrc::HSE(..) => Sw::HSE,
|
||||
ClockSrc::HSI16 => Sw::HSI16,
|
||||
ClockSrc::HSI => Sw::HSI,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -52,7 +52,7 @@ pub struct Config {
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mux: ClockSrc::HSI16,
|
||||
mux: ClockSrc::HSI,
|
||||
ahb_pre: AHBPrescaler::DIV1,
|
||||
apb1_pre: APBPrescaler::DIV1,
|
||||
apb2_pre: APBPrescaler::DIV1,
|
||||
@ -70,7 +70,7 @@ pub(crate) unsafe fn init(config: Config) {
|
||||
|
||||
freq
|
||||
}
|
||||
ClockSrc::HSI16 => {
|
||||
ClockSrc::HSI => {
|
||||
RCC.cr().write(|w| w.set_hsion(true));
|
||||
while !RCC.cr().read().hsirdy() {}
|
||||
|
||||
|
@ -1,184 +0,0 @@
|
||||
pub use crate::pac::pwr::vals::Vos as VoltageScale;
|
||||
use crate::pac::rcc::vals::Sw;
|
||||
pub use crate::pac::rcc::vals::{
|
||||
Adcsel as AdcClockSource, Hpre as AHBPrescaler, Msirange as MSIRange, Pllm, Plln, Pllp, Pllq, Pllr,
|
||||
Pllsrc as PllSource, 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);
|
||||
|
||||
/// HSE speed
|
||||
pub const HSE_FREQ: Hertz = Hertz(32_000_000);
|
||||
|
||||
/// System clock mux source
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ClockSrc {
|
||||
MSI(MSIRange),
|
||||
HSE,
|
||||
HSI16,
|
||||
}
|
||||
|
||||
/// Clocks configutation
|
||||
pub struct Config {
|
||||
pub mux: ClockSrc,
|
||||
pub ahb_pre: AHBPrescaler,
|
||||
pub shd_ahb_pre: AHBPrescaler,
|
||||
pub apb1_pre: APBPrescaler,
|
||||
pub apb2_pre: APBPrescaler,
|
||||
pub adc_clock_source: AdcClockSource,
|
||||
pub ls: super::LsConfig,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
#[inline]
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
mux: ClockSrc::MSI(MSIRange::RANGE4M),
|
||||
ahb_pre: AHBPrescaler::DIV1,
|
||||
shd_ahb_pre: AHBPrescaler::DIV1,
|
||||
apb1_pre: APBPrescaler::DIV1,
|
||||
apb2_pre: APBPrescaler::DIV1,
|
||||
adc_clock_source: AdcClockSource::HSI16,
|
||||
ls: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn init(config: Config) {
|
||||
let (sys_clk, sw, vos) = match config.mux {
|
||||
ClockSrc::HSI16 => (HSI_FREQ, Sw::HSI16, VoltageScale::RANGE2),
|
||||
ClockSrc::HSE => (HSE_FREQ, Sw::HSE, VoltageScale::RANGE1),
|
||||
ClockSrc::MSI(range) => (msirange_to_hertz(range), Sw::MSI, msirange_to_vos(range)),
|
||||
};
|
||||
|
||||
let ahb_freq = sys_clk / config.ahb_pre;
|
||||
let shd_ahb_freq = sys_clk / config.shd_ahb_pre;
|
||||
|
||||
let (apb1_freq, apb1_tim_freq) = match config.apb1_pre {
|
||||
APBPrescaler::DIV1 => (ahb_freq, ahb_freq),
|
||||
pre => {
|
||||
let freq = ahb_freq / pre;
|
||||
(freq, freq * 2u32)
|
||||
}
|
||||
};
|
||||
|
||||
let (apb2_freq, apb2_tim_freq) = match config.apb2_pre {
|
||||
APBPrescaler::DIV1 => (ahb_freq, ahb_freq),
|
||||
pre => {
|
||||
let freq = ahb_freq / pre;
|
||||
(freq, freq * 2u32)
|
||||
}
|
||||
};
|
||||
|
||||
// Adjust flash latency
|
||||
let flash_clk_src_freq = shd_ahb_freq;
|
||||
let ws = match vos {
|
||||
VoltageScale::RANGE1 => match flash_clk_src_freq.0 {
|
||||
0..=18_000_000 => 0b000,
|
||||
18_000_001..=36_000_000 => 0b001,
|
||||
_ => 0b010,
|
||||
},
|
||||
VoltageScale::RANGE2 => match flash_clk_src_freq.0 {
|
||||
0..=6_000_000 => 0b000,
|
||||
6_000_001..=12_000_000 => 0b001,
|
||||
_ => 0b010,
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
FLASH.acr().modify(|w| {
|
||||
w.set_latency(ws);
|
||||
});
|
||||
|
||||
while FLASH.acr().read().latency() != ws {}
|
||||
|
||||
match config.mux {
|
||||
ClockSrc::HSI16 => {
|
||||
// Enable HSI16
|
||||
RCC.cr().write(|w| w.set_hsion(true));
|
||||
while !RCC.cr().read().hsirdy() {}
|
||||
}
|
||||
ClockSrc::HSE => {
|
||||
// Enable HSE
|
||||
RCC.cr().write(|w| {
|
||||
w.set_hsebyppwr(true);
|
||||
w.set_hseon(true);
|
||||
});
|
||||
while !RCC.cr().read().hserdy() {}
|
||||
}
|
||||
ClockSrc::MSI(range) => {
|
||||
let cr = RCC.cr().read();
|
||||
assert!(!cr.msion() || cr.msirdy());
|
||||
RCC.cr().write(|w| {
|
||||
w.set_msirgsel(true);
|
||||
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() {}
|
||||
}
|
||||
}
|
||||
|
||||
RCC.extcfgr().modify(|w| {
|
||||
w.set_shdhpre(config.shd_ahb_pre);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
// ADC clock MUX
|
||||
RCC.ccipr().modify(|w| w.set_adcsel(config.adc_clock_source));
|
||||
|
||||
// TODO: switch voltage range
|
||||
|
||||
let rtc = config.ls.init();
|
||||
|
||||
set_freqs(Clocks {
|
||||
sys: sys_clk,
|
||||
hclk1: ahb_freq,
|
||||
hclk2: ahb_freq,
|
||||
hclk3: shd_ahb_freq,
|
||||
pclk1: apb1_freq,
|
||||
pclk2: apb2_freq,
|
||||
pclk3: shd_ahb_freq,
|
||||
pclk1_tim: apb1_tim_freq,
|
||||
pclk2_tim: apb2_tim_freq,
|
||||
rtc,
|
||||
});
|
||||
}
|
||||
|
||||
fn msirange_to_hertz(range: MSIRange) -> Hertz {
|
||||
match range {
|
||||
MSIRange::RANGE100K => Hertz(100_000),
|
||||
MSIRange::RANGE200K => Hertz(200_000),
|
||||
MSIRange::RANGE400K => Hertz(400_000),
|
||||
MSIRange::RANGE800K => Hertz(800_000),
|
||||
MSIRange::RANGE1M => Hertz(1_000_000),
|
||||
MSIRange::RANGE2M => Hertz(2_000_000),
|
||||
MSIRange::RANGE4M => Hertz(4_000_000),
|
||||
MSIRange::RANGE8M => Hertz(8_000_000),
|
||||
MSIRange::RANGE16M => Hertz(16_000_000),
|
||||
MSIRange::RANGE24M => Hertz(24_000_000),
|
||||
MSIRange::RANGE32M => Hertz(32_000_000),
|
||||
MSIRange::RANGE48M => Hertz(48_000_000),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn msirange_to_vos(range: MSIRange) -> VoltageScale {
|
||||
if range.to_bits() > MSIRange::RANGE16M.to_bits() {
|
||||
VoltageScale::RANGE1
|
||||
} else {
|
||||
VoltageScale::RANGE2
|
||||
}
|
||||
}
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user