diff options
| author | xoviat <[email protected]> | 2025-11-04 07:16:27 -0600 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-11-04 07:16:27 -0600 |
| commit | f8f7820ab98cc1f49d7b2a623566f836354916bc (patch) | |
| tree | 8a26ba0e703d82ec70c0cc3487a8ebc56273d1b6 | |
| parent | f85d709f140838cf0161206df01dd77abbac9c5d (diff) | |
| parent | 51f8aeaa0dd2359a669a3c38d194a8a70f26441f (diff) | |
Merge branch 'main' into i2c
44 files changed, 1478 insertions, 534 deletions
diff --git a/embassy-executor/CHANGELOG.md b/embassy-executor/CHANGELOG.md index 47a8ae995..5fbb8cf13 100644 --- a/embassy-executor/CHANGELOG.md +++ b/embassy-executor/CHANGELOG.md | |||
| @@ -13,7 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 13 | - Upgraded rtos-trace | 13 | - Upgraded rtos-trace |
| 14 | - Added optional "highest priority" scheduling | 14 | - Added optional "highest priority" scheduling |
| 15 | - Added optional "earliest deadline first" EDF scheduling | 15 | - Added optional "earliest deadline first" EDF scheduling |
| 16 | - Bump `cortex-ar` to v0.3 | 16 | - Migrate `cortex-ar` to `aarch32-cpu`. The feature name `arch-cortex-ar` remains the same and |
| 17 | legacy ARM architectures are not supported. | ||
| 17 | 18 | ||
| 18 | ## 0.9.1 - 2025-08-31 | 19 | ## 0.9.1 - 2025-08-31 |
| 19 | 20 | ||
diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index e500833c0..d3e5b241a 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml | |||
| @@ -102,7 +102,7 @@ portable-atomic = { version = "1.5", optional = true } | |||
| 102 | cortex-m = { version = "0.7.6", optional = true } | 102 | cortex-m = { version = "0.7.6", optional = true } |
| 103 | 103 | ||
| 104 | # arch-cortex-ar dependencies | 104 | # arch-cortex-ar dependencies |
| 105 | cortex-ar = { version = "0.3", optional = true } | 105 | aarch32-cpu = { version = "0.1", optional = true } |
| 106 | 106 | ||
| 107 | # arch-wasm dependencies | 107 | # arch-wasm dependencies |
| 108 | wasm-bindgen = { version = "0.2.82", optional = true } | 108 | wasm-bindgen = { version = "0.2.82", optional = true } |
| @@ -130,7 +130,7 @@ nightly = ["embassy-executor-macros/nightly"] | |||
| 130 | ## Enable defmt logging | 130 | ## Enable defmt logging |
| 131 | defmt = ["dep:defmt"] | 131 | defmt = ["dep:defmt"] |
| 132 | 132 | ||
| 133 | ## Enable log logging | 133 | ## Enable log logging |
| 134 | log = ["dep:log"] | 134 | log = ["dep:log"] |
| 135 | 135 | ||
| 136 | # Enables turbo wakers, which requires patching core. Not surfaced in the docs by default due to | 136 | # Enables turbo wakers, which requires patching core. Not surfaced in the docs by default due to |
| @@ -145,7 +145,7 @@ arch-std = ["_arch"] | |||
| 145 | ## Cortex-M | 145 | ## Cortex-M |
| 146 | arch-cortex-m = ["_arch", "dep:cortex-m"] | 146 | arch-cortex-m = ["_arch", "dep:cortex-m"] |
| 147 | ## Cortex-A/R | 147 | ## Cortex-A/R |
| 148 | arch-cortex-ar = ["_arch", "dep:cortex-ar"] | 148 | arch-cortex-ar = ["_arch", "dep:aarch32-cpu", "dep:arm-targets"] |
| 149 | ## RISC-V 32 | 149 | ## RISC-V 32 |
| 150 | arch-riscv32 = ["_arch"] | 150 | arch-riscv32 = ["_arch"] |
| 151 | ## WASM | 151 | ## WASM |
| @@ -182,3 +182,6 @@ scheduler-priority = [] | |||
| 182 | ## Enable the embassy_time_driver dependency. | 182 | ## Enable the embassy_time_driver dependency. |
| 183 | ## This can unlock extra APIs, for example for the `sheduler-deadline` | 183 | ## This can unlock extra APIs, for example for the `sheduler-deadline` |
| 184 | embassy-time-driver = ["dep:embassy-time-driver"] | 184 | embassy-time-driver = ["dep:embassy-time-driver"] |
| 185 | |||
| 186 | [build-dependencies] | ||
| 187 | arm-targets = { version = "0.4", optional = true } | ||
diff --git a/embassy-executor/build.rs b/embassy-executor/build.rs index 37becde3e..36e23a632 100644 --- a/embassy-executor/build.rs +++ b/embassy-executor/build.rs | |||
| @@ -4,4 +4,9 @@ mod common; | |||
| 4 | fn main() { | 4 | fn main() { |
| 5 | let mut rustc_cfgs = common::CfgSet::new(); | 5 | let mut rustc_cfgs = common::CfgSet::new(); |
| 6 | common::set_target_cfgs(&mut rustc_cfgs); | 6 | common::set_target_cfgs(&mut rustc_cfgs); |
| 7 | |||
| 8 | // This is used to exclude legacy architecture support. The raw executor needs to be used for | ||
| 9 | // those architectures because SEV/WFE are not supported. | ||
| 10 | #[cfg(feature = "arch-cortex-ar")] | ||
| 11 | arm_targets::process(); | ||
| 7 | } | 12 | } |
diff --git a/embassy-executor/src/arch/cortex_ar.rs b/embassy-executor/src/arch/cortex_ar.rs index a9be3d323..ce572738a 100644 --- a/embassy-executor/src/arch/cortex_ar.rs +++ b/embassy-executor/src/arch/cortex_ar.rs | |||
| @@ -1,3 +1,6 @@ | |||
| 1 | #[cfg(arm_profile = "legacy")] | ||
| 2 | compile_error!("`arch-cortex-ar` does not support the legacy ARM profile, WFE/SEV are not available."); | ||
| 3 | |||
| 1 | #[cfg(feature = "executor-interrupt")] | 4 | #[cfg(feature = "executor-interrupt")] |
| 2 | compile_error!("`executor-interrupt` is not supported with `arch-cortex-ar`."); | 5 | compile_error!("`executor-interrupt` is not supported with `arch-cortex-ar`."); |
| 3 | 6 | ||
| @@ -10,7 +13,7 @@ fn __pender(context: *mut ()) { | |||
| 10 | #[cfg(feature = "executor-thread")] | 13 | #[cfg(feature = "executor-thread")] |
| 11 | // Try to make Rust optimize the branching away if we only use thread mode. | 14 | // Try to make Rust optimize the branching away if we only use thread mode. |
| 12 | if !cfg!(feature = "executor-interrupt") || context == THREAD_PENDER { | 15 | if !cfg!(feature = "executor-interrupt") || context == THREAD_PENDER { |
| 13 | cortex_ar::asm::sev(); | 16 | aarch32_cpu::asm::sev(); |
| 14 | return; | 17 | return; |
| 15 | } | 18 | } |
| 16 | } | 19 | } |
| @@ -23,7 +26,7 @@ mod thread { | |||
| 23 | 26 | ||
| 24 | use core::marker::PhantomData; | 27 | use core::marker::PhantomData; |
| 25 | 28 | ||
| 26 | use cortex_ar::asm::wfe; | 29 | use aarch32_cpu::asm::wfe; |
| 27 | pub use embassy_executor_macros::main_cortex_ar as main; | 30 | pub use embassy_executor_macros::main_cortex_ar as main; |
| 28 | 31 | ||
| 29 | use crate::{Spawner, raw}; | 32 | use crate::{Spawner, raw}; |
diff --git a/embassy-rp/CHANGELOG.md b/embassy-rp/CHANGELOG.md index 57ec13658..3b3cb5351 100644 --- a/embassy-rp/CHANGELOG.md +++ b/embassy-rp/CHANGELOG.md | |||
| @@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 15 | - add `wait_for_alarm` and `alarm_scheduled` methods to rtc module ([#4216](https://github.com/embassy-rs/embassy/pull/4216)) | 15 | - add `wait_for_alarm` and `alarm_scheduled` methods to rtc module ([#4216](https://github.com/embassy-rs/embassy/pull/4216)) |
| 16 | - rp235x: use msplim for stack guard instead of MPU | 16 | - rp235x: use msplim for stack guard instead of MPU |
| 17 | - Add reset_to_usb_boot for rp235x ([#4705](https://github.com/embassy-rs/embassy/pull/4705)) | 17 | - Add reset_to_usb_boot for rp235x ([#4705](https://github.com/embassy-rs/embassy/pull/4705)) |
| 18 | - Add fix #4822 in PIO onewire. Change to disable the state machine before setting y register ([#4824](https://github.com/embassy-rs/embassy/pull/4824)) | ||
| 19 | - Add PIO::Ws2812 color order support | ||
| 18 | 20 | ||
| 19 | ## 0.8.0 - 2025-08-26 | 21 | ## 0.8.0 - 2025-08-26 |
| 20 | 22 | ||
diff --git a/embassy-rp/src/pio_programs/onewire.rs b/embassy-rp/src/pio_programs/onewire.rs index 09babc229..3adab3b79 100644 --- a/embassy-rp/src/pio_programs/onewire.rs +++ b/embassy-rp/src/pio_programs/onewire.rs | |||
| @@ -163,7 +163,11 @@ impl<'d, PIO: Instance, const SM: usize> PioOneWire<'d, PIO, SM> { | |||
| 163 | 163 | ||
| 164 | /// Write bytes to the onewire bus | 164 | /// Write bytes to the onewire bus |
| 165 | pub async fn write_bytes(&mut self, data: &[u8]) { | 165 | pub async fn write_bytes(&mut self, data: &[u8]) { |
| 166 | unsafe { self.sm.set_y(u32::MAX as u32) }; | 166 | unsafe { |
| 167 | self.sm.set_enable(false); | ||
| 168 | self.sm.set_y(u32::MAX as u32); | ||
| 169 | self.sm.set_enable(true); | ||
| 170 | } | ||
| 167 | let (rx, tx) = self.sm.rx_tx(); | 171 | let (rx, tx) = self.sm.rx_tx(); |
| 168 | for b in data { | 172 | for b in data { |
| 169 | tx.wait_push(*b as u32).await; | 173 | tx.wait_push(*b as u32).await; |
| @@ -175,7 +179,11 @@ impl<'d, PIO: Instance, const SM: usize> PioOneWire<'d, PIO, SM> { | |||
| 175 | 179 | ||
| 176 | /// Write bytes to the onewire bus, then apply a strong pullup | 180 | /// Write bytes to the onewire bus, then apply a strong pullup |
| 177 | pub async fn write_bytes_pullup(&mut self, data: &[u8], pullup_time: embassy_time::Duration) { | 181 | pub async fn write_bytes_pullup(&mut self, data: &[u8], pullup_time: embassy_time::Duration) { |
| 178 | unsafe { self.sm.set_y(data.len() as u32 * 8 - 1) }; | 182 | unsafe { |
| 183 | self.sm.set_enable(false); | ||
| 184 | self.sm.set_y(data.len() as u32 * 8 - 1); | ||
| 185 | self.sm.set_enable(true); | ||
| 186 | }; | ||
| 179 | let (rx, tx) = self.sm.rx_tx(); | 187 | let (rx, tx) = self.sm.rx_tx(); |
| 180 | for b in data { | 188 | for b in data { |
| 181 | tx.wait_push(*b as u32).await; | 189 | tx.wait_push(*b as u32).await; |
| @@ -195,7 +203,11 @@ impl<'d, PIO: Instance, const SM: usize> PioOneWire<'d, PIO, SM> { | |||
| 195 | 203 | ||
| 196 | /// Read bytes from the onewire bus | 204 | /// Read bytes from the onewire bus |
| 197 | pub async fn read_bytes(&mut self, data: &mut [u8]) { | 205 | pub async fn read_bytes(&mut self, data: &mut [u8]) { |
| 198 | unsafe { self.sm.set_y(u32::MAX as u32) }; | 206 | unsafe { |
| 207 | self.sm.set_enable(false); | ||
| 208 | self.sm.set_y(u32::MAX as u32); | ||
| 209 | self.sm.set_enable(true); | ||
| 210 | }; | ||
| 199 | let (rx, tx) = self.sm.rx_tx(); | 211 | let (rx, tx) = self.sm.rx_tx(); |
| 200 | for b in data { | 212 | for b in data { |
| 201 | // Write all 1's so that we can read what the device responds | 213 | // Write all 1's so that we can read what the device responds |
diff --git a/embassy-rp/src/pio_programs/ws2812.rs b/embassy-rp/src/pio_programs/ws2812.rs index e6851b1a6..0b6035316 100644 --- a/embassy-rp/src/pio_programs/ws2812.rs +++ b/embassy-rp/src/pio_programs/ws2812.rs | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | //! [ws2812](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) | 1 | //! [ws2812](https://www.sparkfun.com/categories/tags/ws2812) |
| 2 | 2 | ||
| 3 | use embassy_time::Timer; | 3 | use embassy_time::Timer; |
| 4 | use fixed::types::U24F8; | 4 | use fixed::types::U24F8; |
| @@ -16,6 +16,54 @@ const T2: u8 = 5; // data bit | |||
| 16 | const T3: u8 = 3; // stop bit | 16 | const T3: u8 = 3; // stop bit |
| 17 | const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32; | 17 | const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32; |
| 18 | 18 | ||
| 19 | /// Color orders for WS2812B, type RGB8 | ||
| 20 | pub trait RgbColorOrder { | ||
| 21 | /// Pack an 8-bit RGB color into a u32 | ||
| 22 | fn pack(color: RGB8) -> u32; | ||
| 23 | } | ||
| 24 | |||
| 25 | /// Green, Red, Blue order is the common default for WS2812B | ||
| 26 | pub struct Grb; | ||
| 27 | impl RgbColorOrder for Grb { | ||
| 28 | /// Pack an 8-bit RGB color into a u32 in GRB order | ||
| 29 | fn pack(color: RGB8) -> u32 { | ||
| 30 | (u32::from(color.g) << 24) | (u32::from(color.r) << 16) | (u32::from(color.b) << 8) | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | /// Red, Green, Blue is used by some WS2812B implementations | ||
| 35 | pub struct Rgb; | ||
| 36 | impl RgbColorOrder for Rgb { | ||
| 37 | /// Pack an 8-bit RGB color into a u32 in RGB order | ||
| 38 | fn pack(color: RGB8) -> u32 { | ||
| 39 | (u32::from(color.r) << 24) | (u32::from(color.g) << 16) | (u32::from(color.b) << 8) | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | /// Color orders RGBW strips | ||
| 44 | pub trait RgbwColorOrder { | ||
| 45 | /// Pack an RGB+W color into a u32 | ||
| 46 | fn pack(color: RGBW<u8>) -> u32; | ||
| 47 | } | ||
| 48 | |||
| 49 | /// Green, Red, Blue, White order is the common default for RGBW strips | ||
| 50 | pub struct Grbw; | ||
| 51 | impl RgbwColorOrder for Grbw { | ||
| 52 | /// Pack an RGB+W color into a u32 in GRBW order | ||
| 53 | fn pack(color: RGBW<u8>) -> u32 { | ||
| 54 | (u32::from(color.g) << 24) | (u32::from(color.r) << 16) | (u32::from(color.b) << 8) | u32::from(color.a.0) | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | /// Red, Green, Blue, White order | ||
| 59 | pub struct Rgbw; | ||
| 60 | impl RgbwColorOrder for Rgbw { | ||
| 61 | /// Pack an RGB+W color into a u32 in RGBW order | ||
| 62 | fn pack(color: RGBW<u8>) -> u32 { | ||
| 63 | (u32::from(color.r) << 24) | (u32::from(color.g) << 16) | (u32::from(color.b) << 8) | u32::from(color.a.0) | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 19 | /// This struct represents a ws2812 program loaded into pio instruction memory. | 67 | /// This struct represents a ws2812 program loaded into pio instruction memory. |
| 20 | pub struct PioWs2812Program<'a, PIO: Instance> { | 68 | pub struct PioWs2812Program<'a, PIO: Instance> { |
| 21 | prg: LoadedProgram<'a, PIO>, | 69 | prg: LoadedProgram<'a, PIO>, |
| @@ -52,15 +100,37 @@ impl<'a, PIO: Instance> PioWs2812Program<'a, PIO> { | |||
| 52 | 100 | ||
| 53 | /// Pio backed RGB ws2812 driver | 101 | /// Pio backed RGB ws2812 driver |
| 54 | /// Const N is the number of ws2812 leds attached to this pin | 102 | /// Const N is the number of ws2812 leds attached to this pin |
| 55 | pub struct PioWs2812<'d, P: Instance, const S: usize, const N: usize> { | 103 | pub struct PioWs2812<'d, P: Instance, const S: usize, const N: usize, ORDER> |
| 104 | where | ||
| 105 | ORDER: RgbColorOrder, | ||
| 106 | { | ||
| 56 | dma: Peri<'d, AnyChannel>, | 107 | dma: Peri<'d, AnyChannel>, |
| 57 | sm: StateMachine<'d, P, S>, | 108 | sm: StateMachine<'d, P, S>, |
| 109 | _order: core::marker::PhantomData<ORDER>, | ||
| 58 | } | 110 | } |
| 59 | 111 | ||
| 60 | impl<'d, P: Instance, const S: usize, const N: usize> PioWs2812<'d, P, S, N> { | 112 | impl<'d, P: Instance, const S: usize, const N: usize> PioWs2812<'d, P, S, N, Grb> { |
| 61 | /// Configure a pio state machine to use the loaded ws2812 program. | 113 | /// Configure a pio state machine to use the loaded ws2812 program. |
| 114 | /// Uses the default GRB order. | ||
| 62 | pub fn new( | 115 | pub fn new( |
| 63 | pio: &mut Common<'d, P>, | 116 | pio: &mut Common<'d, P>, |
| 117 | sm: StateMachine<'d, P, S>, | ||
| 118 | dma: Peri<'d, impl Channel>, | ||
| 119 | pin: Peri<'d, impl PioPin>, | ||
| 120 | program: &PioWs2812Program<'d, P>, | ||
| 121 | ) -> Self { | ||
| 122 | Self::with_color_order(pio, sm, dma, pin, program) | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | impl<'d, P: Instance, const S: usize, const N: usize, ORDER> PioWs2812<'d, P, S, N, ORDER> | ||
| 127 | where | ||
| 128 | ORDER: RgbColorOrder, | ||
| 129 | { | ||
| 130 | /// Configure a pio state machine to use the loaded ws2812 program. | ||
| 131 | /// Uses the specified color order. | ||
| 132 | pub fn with_color_order( | ||
| 133 | pio: &mut Common<'d, P>, | ||
| 64 | mut sm: StateMachine<'d, P, S>, | 134 | mut sm: StateMachine<'d, P, S>, |
| 65 | dma: Peri<'d, impl Channel>, | 135 | dma: Peri<'d, impl Channel>, |
| 66 | pin: Peri<'d, impl PioPin>, | 136 | pin: Peri<'d, impl PioPin>, |
| @@ -93,7 +163,11 @@ impl<'d, P: Instance, const S: usize, const N: usize> PioWs2812<'d, P, S, N> { | |||
| 93 | sm.set_config(&cfg); | 163 | sm.set_config(&cfg); |
| 94 | sm.set_enable(true); | 164 | sm.set_enable(true); |
| 95 | 165 | ||
| 96 | Self { dma: dma.into(), sm } | 166 | Self { |
| 167 | dma: dma.into(), | ||
| 168 | sm, | ||
| 169 | _order: core::marker::PhantomData, | ||
| 170 | } | ||
| 97 | } | 171 | } |
| 98 | 172 | ||
| 99 | /// Write a buffer of [smart_leds::RGB8] to the ws2812 string | 173 | /// Write a buffer of [smart_leds::RGB8] to the ws2812 string |
| @@ -101,8 +175,7 @@ impl<'d, P: Instance, const S: usize, const N: usize> PioWs2812<'d, P, S, N> { | |||
| 101 | // Precompute the word bytes from the colors | 175 | // Precompute the word bytes from the colors |
| 102 | let mut words = [0u32; N]; | 176 | let mut words = [0u32; N]; |
| 103 | for i in 0..N { | 177 | for i in 0..N { |
| 104 | let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8); | 178 | words[i] = ORDER::pack(colors[i]); |
| 105 | words[i] = word; | ||
| 106 | } | 179 | } |
| 107 | 180 | ||
| 108 | // DMA transfer | 181 | // DMA transfer |
| @@ -115,15 +188,37 @@ impl<'d, P: Instance, const S: usize, const N: usize> PioWs2812<'d, P, S, N> { | |||
| 115 | /// Pio backed RGBW ws2812 driver | 188 | /// Pio backed RGBW ws2812 driver |
| 116 | /// This version is intended for ws2812 leds with 4 addressable lights | 189 | /// This version is intended for ws2812 leds with 4 addressable lights |
| 117 | /// Const N is the number of ws2812 leds attached to this pin | 190 | /// Const N is the number of ws2812 leds attached to this pin |
| 118 | pub struct RgbwPioWs2812<'d, P: Instance, const S: usize, const N: usize> { | 191 | pub struct RgbwPioWs2812<'d, P: Instance, const S: usize, const N: usize, ORDER> |
| 192 | where | ||
| 193 | ORDER: RgbwColorOrder, | ||
| 194 | { | ||
| 119 | dma: Peri<'d, AnyChannel>, | 195 | dma: Peri<'d, AnyChannel>, |
| 120 | sm: StateMachine<'d, P, S>, | 196 | sm: StateMachine<'d, P, S>, |
| 197 | _order: core::marker::PhantomData<ORDER>, | ||
| 121 | } | 198 | } |
| 122 | 199 | ||
| 123 | impl<'d, P: Instance, const S: usize, const N: usize> RgbwPioWs2812<'d, P, S, N> { | 200 | impl<'d, P: Instance, const S: usize, const N: usize> RgbwPioWs2812<'d, P, S, N, Grbw> { |
| 124 | /// Configure a pio state machine to use the loaded ws2812 program. | 201 | /// Configure a pio state machine to use the loaded ws2812 program. |
| 202 | /// Uses the default GRBW color order | ||
| 125 | pub fn new( | 203 | pub fn new( |
| 126 | pio: &mut Common<'d, P>, | 204 | pio: &mut Common<'d, P>, |
| 205 | sm: StateMachine<'d, P, S>, | ||
| 206 | dma: Peri<'d, impl Channel>, | ||
| 207 | pin: Peri<'d, impl PioPin>, | ||
| 208 | program: &PioWs2812Program<'d, P>, | ||
| 209 | ) -> Self { | ||
| 210 | Self::with_color_order(pio, sm, dma, pin, program) | ||
| 211 | } | ||
| 212 | } | ||
| 213 | |||
| 214 | impl<'d, P: Instance, const S: usize, const N: usize, ORDER> RgbwPioWs2812<'d, P, S, N, ORDER> | ||
| 215 | where | ||
| 216 | ORDER: RgbwColorOrder, | ||
| 217 | { | ||
| 218 | /// Configure a pio state machine to use the loaded ws2812 program. | ||
| 219 | /// Uses the specified color order | ||
| 220 | pub fn with_color_order( | ||
| 221 | pio: &mut Common<'d, P>, | ||
| 127 | mut sm: StateMachine<'d, P, S>, | 222 | mut sm: StateMachine<'d, P, S>, |
| 128 | dma: Peri<'d, impl Channel>, | 223 | dma: Peri<'d, impl Channel>, |
| 129 | pin: Peri<'d, impl PioPin>, | 224 | pin: Peri<'d, impl PioPin>, |
| @@ -156,7 +251,11 @@ impl<'d, P: Instance, const S: usize, const N: usize> RgbwPioWs2812<'d, P, S, N> | |||
| 156 | sm.set_config(&cfg); | 251 | sm.set_config(&cfg); |
| 157 | sm.set_enable(true); | 252 | sm.set_enable(true); |
| 158 | 253 | ||
| 159 | Self { dma: dma.into(), sm } | 254 | Self { |
| 255 | dma: dma.into(), | ||
| 256 | sm, | ||
| 257 | _order: core::marker::PhantomData, | ||
| 258 | } | ||
| 160 | } | 259 | } |
| 161 | 260 | ||
| 162 | /// Write a buffer of [smart_leds::RGBW] to the ws2812 string | 261 | /// Write a buffer of [smart_leds::RGBW] to the ws2812 string |
| @@ -164,11 +263,7 @@ impl<'d, P: Instance, const S: usize, const N: usize> RgbwPioWs2812<'d, P, S, N> | |||
| 164 | // Precompute the word bytes from the colors | 263 | // Precompute the word bytes from the colors |
| 165 | let mut words = [0u32; N]; | 264 | let mut words = [0u32; N]; |
| 166 | for i in 0..N { | 265 | for i in 0..N { |
| 167 | let word = (u32::from(colors[i].g) << 24) | 266 | words[i] = ORDER::pack(colors[i]); |
| 168 | | (u32::from(colors[i].r) << 16) | ||
| 169 | | (u32::from(colors[i].b) << 8) | ||
| 170 | | u32::from(colors[i].a.0); | ||
| 171 | words[i] = word; | ||
| 172 | } | 267 | } |
| 173 | 268 | ||
| 174 | // DMA transfer | 269 | // DMA transfer |
diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 8b215f13b..3d6a41f14 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md | |||
| @@ -5,10 +5,6 @@ All notable changes to this project will be documented in this file. | |||
| 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), |
| 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |
| 7 | 7 | ||
| 8 | <!-- next-header --> | ||
| 9 | ### [Unreleased] | ||
| 10 | |||
| 11 | * **Fix(stm32h5):** Prevent a HardFault crash on STM32H5 devices by changing `uid()` to return `[u8; 12]` by value instead of a reference. (Fixes #2696) | ||
| 12 | ## Unreleased - ReleaseDate | 8 | ## Unreleased - ReleaseDate |
| 13 | 9 | ||
| 14 | - fix flash erase on L4 & L5 | 10 | - fix flash erase on L4 & L5 |
| @@ -35,10 +31,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 35 | - feat: stm32/usart: add `eager_reads` option to control if buffered readers return as soon as possible or after more data is available ([#4668](https://github.com/embassy-rs/embassy/pull/4668)) | 31 | - feat: stm32/usart: add `eager_reads` option to control if buffered readers return as soon as possible or after more data is available ([#4668](https://github.com/embassy-rs/embassy/pull/4668)) |
| 36 | - feat: stm32/usart: add `de_assertion_time` and `de_deassertion_time` config options | 32 | - feat: stm32/usart: add `de_assertion_time` and `de_deassertion_time` config options |
| 37 | - change: stm32/uart: BufferedUartRx now returns all available bytes from the internal buffer | 33 | - change: stm32/uart: BufferedUartRx now returns all available bytes from the internal buffer |
| 34 | - fix: Prevent a HardFault crash on STM32H5 devices by changing `uid()` to return `[u8; 12]` by value instead of a reference. (Fixes #2696) | ||
| 38 | - change: timer: added output compare values | 35 | - change: timer: added output compare values |
| 39 | - feat: timer: add ability to set master mode | 36 | - feat: timer: add ability to set master mode |
| 37 | - fix: sdmmc: don't wait for DBCKEND flag on sdmmc_v2 devices as it never fires (Fixes #4723) | ||
| 40 | - fix: usart: fix race condition in ringbuffered usart | 38 | - fix: usart: fix race condition in ringbuffered usart |
| 41 | - feat: Add I2C MultiMaster (Slave) support for I2C v1 | 39 | - feat: Add I2C MultiMaster (Slave) support for I2C v1 |
| 40 | - feat: stm32/fdcan: add ability to control automatic recovery from bus off ([#4821](https://github.com/embassy-rs/embassy/pull/4821)) | ||
| 41 | - low-power: update rtc api to allow reconfig | ||
| 42 | - adc: consolidate ringbuffer | ||
| 43 | - feat: Added RTC low-power support for STM32WLEx ([#4716](https://github.com/embassy-rs/embassy/pull/4716)) | ||
| 44 | - fix: Correct STM32WBA VREFBUFTRIM values | ||
| 42 | 45 | ||
| 43 | ## 0.4.0 - 2025-08-26 | 46 | ## 0.4.0 - 2025-08-26 |
| 44 | 47 | ||
| @@ -51,6 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 51 | - chore: Updated stm32-metapac and stm32-data dependencies | 54 | - chore: Updated stm32-metapac and stm32-data dependencies |
| 52 | - feat: stm32/adc/v3: allow DMA reads to loop through enable channels | 55 | - feat: stm32/adc/v3: allow DMA reads to loop through enable channels |
| 53 | - fix: Fix XSPI not disabling alternate bytes when they were previously enabled | 56 | - fix: Fix XSPI not disabling alternate bytes when they were previously enabled |
| 57 | - feat: stm32/adc/v3: added support for Continuous DMA configuration | ||
| 54 | - fix: Fix stm32h7rs init when using external flash via XSPI | 58 | - fix: Fix stm32h7rs init when using external flash via XSPI |
| 55 | - feat: Add Adc::new_with_clock() to configure analog clock | 59 | - feat: Add Adc::new_with_clock() to configure analog clock |
| 56 | - feat: Add GPDMA linked-list + ringbuffer support ([#3923](https://github.com/embassy-rs/embassy/pull/3923)) | 60 | - feat: Add GPDMA linked-list + ringbuffer support ([#3923](https://github.com/embassy-rs/embassy/pull/3923)) |
diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs index 22ed8295f..ea7341f75 100644 --- a/embassy-stm32/src/adc/mod.rs +++ b/embassy-stm32/src/adc/mod.rs | |||
| @@ -87,14 +87,18 @@ pub(crate) trait SealedAdcChannel<T> { | |||
| 87 | /// Performs a busy-wait delay for a specified number of microseconds. | 87 | /// Performs a busy-wait delay for a specified number of microseconds. |
| 88 | #[allow(unused)] | 88 | #[allow(unused)] |
| 89 | pub(crate) fn blocking_delay_us(us: u32) { | 89 | pub(crate) fn blocking_delay_us(us: u32) { |
| 90 | #[cfg(feature = "time")] | 90 | cfg_if::cfg_if! { |
| 91 | embassy_time::block_for(embassy_time::Duration::from_micros(us as u64)); | 91 | // this does strange things on stm32wlx in low power mode depending on exactly when it's called |
| 92 | #[cfg(not(feature = "time"))] | 92 | // as in sometimes 15 us (1 tick) would take > 20 seconds. |
| 93 | { | 93 | if #[cfg(all(feature = "time", all(not(feature = "low-power"), not(stm32wlex))))] { |
| 94 | let freq = unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 as u64; | 94 | let duration = embassy_time::Duration::from_micros(us as u64); |
| 95 | let us = us as u64; | 95 | embassy_time::block_for(duration); |
| 96 | let cycles = freq * us / 1_000_000; | 96 | } else { |
| 97 | cortex_m::asm::delay(cycles as u32); | 97 | let freq = unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 as u64; |
| 98 | let us = us as u64; | ||
| 99 | let cycles = freq * us / 1_000_000; | ||
| 100 | cortex_m::asm::delay(cycles as u32); | ||
| 101 | } | ||
| 98 | } | 102 | } |
| 99 | } | 103 | } |
| 100 | 104 | ||
diff --git a/embassy-stm32/src/adc/ringbuffered.rs b/embassy-stm32/src/adc/ringbuffered.rs new file mode 100644 index 000000000..1f384efb5 --- /dev/null +++ b/embassy-stm32/src/adc/ringbuffered.rs | |||
| @@ -0,0 +1,179 @@ | |||
| 1 | use core::marker::PhantomData; | ||
| 2 | use core::sync::atomic::{Ordering, compiler_fence}; | ||
| 3 | |||
| 4 | #[allow(unused_imports)] | ||
| 5 | use embassy_hal_internal::Peri; | ||
| 6 | |||
| 7 | use crate::adc::Adc; | ||
| 8 | #[allow(unused_imports)] | ||
| 9 | use crate::adc::{Instance, RxDma}; | ||
| 10 | #[allow(unused_imports)] | ||
| 11 | use crate::dma::{ReadableRingBuffer, TransferOptions}; | ||
| 12 | use crate::rcc; | ||
| 13 | |||
| 14 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 15 | pub struct OverrunError; | ||
| 16 | |||
| 17 | pub struct RingBufferedAdc<'d, T: Instance> { | ||
| 18 | _phantom: PhantomData<T>, | ||
| 19 | ring_buf: ReadableRingBuffer<'d, u16>, | ||
| 20 | } | ||
| 21 | |||
| 22 | impl<'d, T: Instance> RingBufferedAdc<'d, T> { | ||
| 23 | pub(crate) fn new(dma: Peri<'d, impl RxDma<T>>, dma_buf: &'d mut [u16]) -> Self { | ||
| 24 | //dma side setup | ||
| 25 | let opts = TransferOptions { | ||
| 26 | half_transfer_ir: true, | ||
| 27 | circular: true, | ||
| 28 | ..Default::default() | ||
| 29 | }; | ||
| 30 | |||
| 31 | // Safety: we forget the struct before this function returns. | ||
| 32 | let request = dma.request(); | ||
| 33 | |||
| 34 | let ring_buf = | ||
| 35 | unsafe { ReadableRingBuffer::new(dma, request, T::regs().dr().as_ptr() as *mut u16, dma_buf, opts) }; | ||
| 36 | |||
| 37 | Self { | ||
| 38 | _phantom: PhantomData, | ||
| 39 | ring_buf, | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | /// Turns on ADC if it is not already turned on and starts continuous DMA transfer. | ||
| 44 | pub fn start(&mut self) { | ||
| 45 | compiler_fence(Ordering::SeqCst); | ||
| 46 | self.ring_buf.start(); | ||
| 47 | |||
| 48 | Adc::<T>::start(); | ||
| 49 | } | ||
| 50 | |||
| 51 | pub fn stop(&mut self) { | ||
| 52 | Adc::<T>::stop(); | ||
| 53 | |||
| 54 | self.ring_buf.request_pause(); | ||
| 55 | |||
| 56 | compiler_fence(Ordering::SeqCst); | ||
| 57 | } | ||
| 58 | |||
| 59 | pub fn clear(&mut self) { | ||
| 60 | self.ring_buf.clear(); | ||
| 61 | } | ||
| 62 | |||
| 63 | /// Reads measurements from the DMA ring buffer. | ||
| 64 | /// | ||
| 65 | /// This method fills the provided `measurements` array with ADC readings from the DMA buffer. | ||
| 66 | /// The length of the `measurements` array should be exactly half of the DMA buffer length. Because interrupts are only generated if half or full DMA transfer completes. | ||
| 67 | /// | ||
| 68 | /// Each call to `read` will populate the `measurements` array in the same order as the channels defined with `sequence`. | ||
| 69 | /// There will be many sequences worth of measurements in this array because it only returns if at least half of the DMA buffer is filled. | ||
| 70 | /// For example if 2 channels are sampled `measurements` contain: `[sq0 sq1 sq0 sq1 sq0 sq1 ..]`. | ||
| 71 | /// | ||
| 72 | /// Note that the ADC Datarate can be very fast, it is suggested to use DMA mode inside tightly running tasks | ||
| 73 | /// Otherwise, you'll see constant Overrun errors occuring, this means that you're sampling too quickly for the task to handle, and you may need to increase the buffer size. | ||
| 74 | /// Example: | ||
| 75 | /// ```rust,ignore | ||
| 76 | /// const DMA_BUF_LEN: usize = 120; | ||
| 77 | /// use embassy_stm32::adc::{Adc, AdcChannel} | ||
| 78 | /// | ||
| 79 | /// let mut adc = Adc::new(p.ADC1); | ||
| 80 | /// let mut adc_pin0 = p.PA0.degrade_adc(); | ||
| 81 | /// let mut adc_pin1 = p.PA1.degrade_adc(); | ||
| 82 | /// let adc_dma_buf = [0u16; DMA_BUF_LEN]; | ||
| 83 | /// | ||
| 84 | /// let mut ring_buffered_adc: RingBufferedAdc<embassy_stm32::peripherals::ADC1> = adc.into_ring_buffered( | ||
| 85 | /// p.DMA2_CH0, | ||
| 86 | /// adc_dma_buf, [ | ||
| 87 | /// (&mut *adc_pin0, SampleTime::CYCLES160_5), | ||
| 88 | /// (&mut *adc_pin1, SampleTime::CYCLES160_5), | ||
| 89 | /// ].into_iter()); | ||
| 90 | /// | ||
| 91 | /// | ||
| 92 | /// let mut measurements = [0u16; DMA_BUF_LEN / 2]; | ||
| 93 | /// loop { | ||
| 94 | /// match ring_buffered_adc.read(&mut measurements).await { | ||
| 95 | /// Ok(_) => { | ||
| 96 | /// defmt::info!("adc1: {}", measurements); | ||
| 97 | /// } | ||
| 98 | /// Err(e) => { | ||
| 99 | /// defmt::warn!("Error: {:?}", e); | ||
| 100 | /// } | ||
| 101 | /// } | ||
| 102 | /// } | ||
| 103 | /// ``` | ||
| 104 | /// | ||
| 105 | /// | ||
| 106 | /// [`teardown_adc`]: #method.teardown_adc | ||
| 107 | /// [`start_continuous_sampling`]: #method.start_continuous_sampling | ||
| 108 | pub async fn read(&mut self, measurements: &mut [u16]) -> Result<usize, OverrunError> { | ||
| 109 | assert_eq!( | ||
| 110 | self.ring_buf.capacity() / 2, | ||
| 111 | measurements.len(), | ||
| 112 | "Buffer size must be half the size of the ring buffer" | ||
| 113 | ); | ||
| 114 | |||
| 115 | if !self.ring_buf.is_running() { | ||
| 116 | self.start(); | ||
| 117 | } | ||
| 118 | |||
| 119 | #[cfg(adc_v2)] | ||
| 120 | { | ||
| 121 | // Clear overrun flag if set. | ||
| 122 | if T::regs().sr().read().ovr() { | ||
| 123 | self.stop(); | ||
| 124 | |||
| 125 | return Err(OverrunError); | ||
| 126 | } | ||
| 127 | } | ||
| 128 | |||
| 129 | self.ring_buf.read_exact(measurements).await.map_err(|_| OverrunError) | ||
| 130 | } | ||
| 131 | |||
| 132 | /// Read bytes that are readily available in the ring buffer. | ||
| 133 | /// If no bytes are currently available in the buffer the call waits until the some | ||
| 134 | /// bytes are available (at least one byte and at most half the buffer size) | ||
| 135 | /// | ||
| 136 | /// Background receive is started if `start_continuous_sampling()` has not been previously called. | ||
| 137 | /// | ||
| 138 | /// Receive in the background is terminated if an error is returned. | ||
| 139 | /// It must then manually be started again by calling `start_continuous_sampling()` or by re-calling `blocking_read()`. | ||
| 140 | pub fn blocking_read(&mut self, buf: &mut [u16]) -> Result<usize, OverrunError> { | ||
| 141 | if !self.ring_buf.is_running() { | ||
| 142 | self.start(); | ||
| 143 | } | ||
| 144 | |||
| 145 | #[cfg(adc_v2)] | ||
| 146 | { | ||
| 147 | // Clear overrun flag if set. | ||
| 148 | if T::regs().sr().read().ovr() { | ||
| 149 | self.stop(); | ||
| 150 | |||
| 151 | return Err(OverrunError); | ||
| 152 | } | ||
| 153 | } | ||
| 154 | loop { | ||
| 155 | match self.ring_buf.read(buf) { | ||
| 156 | Ok((0, _)) => {} | ||
| 157 | Ok((len, _)) => { | ||
| 158 | return Ok(len); | ||
| 159 | } | ||
| 160 | Err(_) => { | ||
| 161 | self.stop(); | ||
| 162 | |||
| 163 | return Err(OverrunError); | ||
| 164 | } | ||
| 165 | } | ||
| 166 | } | ||
| 167 | } | ||
| 168 | } | ||
| 169 | |||
| 170 | impl<T: Instance> Drop for RingBufferedAdc<'_, T> { | ||
| 171 | fn drop(&mut self) { | ||
| 172 | Adc::<T>::teardown_adc(); | ||
| 173 | |||
| 174 | compiler_fence(Ordering::SeqCst); | ||
| 175 | |||
| 176 | self.ring_buf.request_pause(); | ||
| 177 | rcc::disable::<T>(); | ||
| 178 | } | ||
| 179 | } | ||
diff --git a/embassy-stm32/src/adc/ringbuffered_v2.rs b/embassy-stm32/src/adc/ringbuffered_v2.rs deleted file mode 100644 index 9b2e5b8fe..000000000 --- a/embassy-stm32/src/adc/ringbuffered_v2.rs +++ /dev/null | |||
| @@ -1,432 +0,0 @@ | |||
| 1 | use core::marker::PhantomData; | ||
| 2 | use core::mem; | ||
| 3 | use core::sync::atomic::{Ordering, compiler_fence}; | ||
| 4 | |||
| 5 | use stm32_metapac::adc::vals::SampleTime; | ||
| 6 | |||
| 7 | use crate::adc::{Adc, AdcChannel, Instance, RxDma}; | ||
| 8 | use crate::dma::{Priority, ReadableRingBuffer, TransferOptions}; | ||
| 9 | use crate::pac::adc::vals; | ||
| 10 | use crate::{Peri, rcc}; | ||
| 11 | |||
| 12 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 13 | pub struct OverrunError; | ||
| 14 | |||
| 15 | fn clear_interrupt_flags(r: crate::pac::adc::Adc) { | ||
| 16 | r.sr().modify(|regs| { | ||
| 17 | regs.set_eoc(false); | ||
| 18 | regs.set_ovr(false); | ||
| 19 | }); | ||
| 20 | } | ||
| 21 | |||
| 22 | #[derive(PartialOrd, PartialEq, Debug, Clone, Copy)] | ||
| 23 | pub enum Sequence { | ||
| 24 | One, | ||
| 25 | Two, | ||
| 26 | Three, | ||
| 27 | Four, | ||
| 28 | Five, | ||
| 29 | Six, | ||
| 30 | Seven, | ||
| 31 | Eight, | ||
| 32 | Nine, | ||
| 33 | Ten, | ||
| 34 | Eleven, | ||
| 35 | Twelve, | ||
| 36 | Thirteen, | ||
| 37 | Fourteen, | ||
| 38 | Fifteen, | ||
| 39 | Sixteen, | ||
| 40 | } | ||
| 41 | |||
| 42 | impl From<Sequence> for u8 { | ||
| 43 | fn from(s: Sequence) -> u8 { | ||
| 44 | match s { | ||
| 45 | Sequence::One => 0, | ||
| 46 | Sequence::Two => 1, | ||
| 47 | Sequence::Three => 2, | ||
| 48 | Sequence::Four => 3, | ||
| 49 | Sequence::Five => 4, | ||
| 50 | Sequence::Six => 5, | ||
| 51 | Sequence::Seven => 6, | ||
| 52 | Sequence::Eight => 7, | ||
| 53 | Sequence::Nine => 8, | ||
| 54 | Sequence::Ten => 9, | ||
| 55 | Sequence::Eleven => 10, | ||
| 56 | Sequence::Twelve => 11, | ||
| 57 | Sequence::Thirteen => 12, | ||
| 58 | Sequence::Fourteen => 13, | ||
| 59 | Sequence::Fifteen => 14, | ||
| 60 | Sequence::Sixteen => 15, | ||
| 61 | } | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | impl From<u8> for Sequence { | ||
| 66 | fn from(val: u8) -> Self { | ||
| 67 | match val { | ||
| 68 | 0 => Sequence::One, | ||
| 69 | 1 => Sequence::Two, | ||
| 70 | 2 => Sequence::Three, | ||
| 71 | 3 => Sequence::Four, | ||
| 72 | 4 => Sequence::Five, | ||
| 73 | 5 => Sequence::Six, | ||
| 74 | 6 => Sequence::Seven, | ||
| 75 | 7 => Sequence::Eight, | ||
| 76 | 8 => Sequence::Nine, | ||
| 77 | 9 => Sequence::Ten, | ||
| 78 | 10 => Sequence::Eleven, | ||
| 79 | 11 => Sequence::Twelve, | ||
| 80 | 12 => Sequence::Thirteen, | ||
| 81 | 13 => Sequence::Fourteen, | ||
| 82 | 14 => Sequence::Fifteen, | ||
| 83 | 15 => Sequence::Sixteen, | ||
| 84 | _ => panic!("Invalid sequence number"), | ||
| 85 | } | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | pub struct RingBufferedAdc<'d, T: Instance> { | ||
| 90 | _phantom: PhantomData<T>, | ||
| 91 | ring_buf: ReadableRingBuffer<'d, u16>, | ||
| 92 | } | ||
| 93 | |||
| 94 | impl<'d, T: Instance> Adc<'d, T> { | ||
| 95 | /// Configures the ADC to use a DMA ring buffer for continuous data acquisition. | ||
| 96 | /// | ||
| 97 | /// The `dma_buf` should be large enough to prevent DMA buffer overrun. | ||
| 98 | /// The length of the `dma_buf` should be a multiple of the ADC channel count. | ||
| 99 | /// For example, if 3 channels are measured, its length can be 3 * 40 = 120 measurements. | ||
| 100 | /// | ||
| 101 | /// `read` method is used to read out measurements from the DMA ring buffer, and its buffer should be exactly half of the `dma_buf` length. | ||
| 102 | /// It is critical to call `read` frequently to prevent DMA buffer overrun. | ||
| 103 | /// | ||
| 104 | /// [`read`]: #method.read | ||
| 105 | pub fn into_ring_buffered(self, dma: Peri<'d, impl RxDma<T>>, dma_buf: &'d mut [u16]) -> RingBufferedAdc<'d, T> { | ||
| 106 | assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); | ||
| 107 | |||
| 108 | let opts: crate::dma::TransferOptions = TransferOptions { | ||
| 109 | half_transfer_ir: true, | ||
| 110 | priority: Priority::VeryHigh, | ||
| 111 | ..Default::default() | ||
| 112 | }; | ||
| 113 | |||
| 114 | // Safety: we forget the struct before this function returns. | ||
| 115 | let rx_src = T::regs().dr().as_ptr() as *mut u16; | ||
| 116 | let request = dma.request(); | ||
| 117 | |||
| 118 | let ring_buf = unsafe { ReadableRingBuffer::new(dma, request, rx_src, dma_buf, opts) }; | ||
| 119 | |||
| 120 | // Don't disable the clock | ||
| 121 | mem::forget(self); | ||
| 122 | |||
| 123 | RingBufferedAdc { | ||
| 124 | _phantom: PhantomData, | ||
| 125 | ring_buf, | ||
| 126 | } | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | impl<'d, T: Instance> RingBufferedAdc<'d, T> { | ||
| 131 | fn is_on() -> bool { | ||
| 132 | T::regs().cr2().read().adon() | ||
| 133 | } | ||
| 134 | |||
| 135 | fn stop_adc() { | ||
| 136 | T::regs().cr2().modify(|reg| { | ||
| 137 | reg.set_adon(false); | ||
| 138 | }); | ||
| 139 | } | ||
| 140 | |||
| 141 | fn start_adc() { | ||
| 142 | T::regs().cr2().modify(|reg| { | ||
| 143 | reg.set_adon(true); | ||
| 144 | }); | ||
| 145 | } | ||
| 146 | |||
| 147 | /// Sets the channel sample time | ||
| 148 | /// | ||
| 149 | /// ## SAFETY: | ||
| 150 | /// - ADON == 0 i.e ADC must not be enabled when this is called. | ||
| 151 | unsafe fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { | ||
| 152 | if ch <= 9 { | ||
| 153 | T::regs().smpr2().modify(|reg| reg.set_smp(ch as _, sample_time)); | ||
| 154 | } else { | ||
| 155 | T::regs().smpr1().modify(|reg| reg.set_smp((ch - 10) as _, sample_time)); | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | fn set_channels_sample_time(&mut self, ch: &[u8], sample_time: SampleTime) { | ||
| 160 | let ch_iter = ch.iter(); | ||
| 161 | for idx in ch_iter { | ||
| 162 | unsafe { | ||
| 163 | Self::set_channel_sample_time(*idx, sample_time); | ||
| 164 | } | ||
| 165 | } | ||
| 166 | } | ||
| 167 | |||
| 168 | pub fn set_sample_sequence( | ||
| 169 | &mut self, | ||
| 170 | sequence: Sequence, | ||
| 171 | channel: &mut impl AdcChannel<T>, | ||
| 172 | sample_time: SampleTime, | ||
| 173 | ) { | ||
| 174 | let was_on = Self::is_on(); | ||
| 175 | if !was_on { | ||
| 176 | Self::start_adc(); | ||
| 177 | } | ||
| 178 | |||
| 179 | // Check the sequence is long enough | ||
| 180 | T::regs().sqr1().modify(|r| { | ||
| 181 | let prev: Sequence = r.l().into(); | ||
| 182 | if prev < sequence { | ||
| 183 | let new_l: Sequence = sequence; | ||
| 184 | trace!("Setting sequence length from {:?} to {:?}", prev as u8, new_l as u8); | ||
| 185 | r.set_l(sequence.into()) | ||
| 186 | } else { | ||
| 187 | r.set_l(prev.into()) | ||
| 188 | } | ||
| 189 | }); | ||
| 190 | |||
| 191 | // Set this GPIO as an analog input. | ||
| 192 | channel.setup(); | ||
| 193 | |||
| 194 | // Set the channel in the right sequence field. | ||
| 195 | match sequence { | ||
| 196 | Sequence::One => T::regs().sqr3().modify(|w| w.set_sq(0, channel.channel())), | ||
| 197 | Sequence::Two => T::regs().sqr3().modify(|w| w.set_sq(1, channel.channel())), | ||
| 198 | Sequence::Three => T::regs().sqr3().modify(|w| w.set_sq(2, channel.channel())), | ||
| 199 | Sequence::Four => T::regs().sqr3().modify(|w| w.set_sq(3, channel.channel())), | ||
| 200 | Sequence::Five => T::regs().sqr3().modify(|w| w.set_sq(4, channel.channel())), | ||
| 201 | Sequence::Six => T::regs().sqr3().modify(|w| w.set_sq(5, channel.channel())), | ||
| 202 | Sequence::Seven => T::regs().sqr2().modify(|w| w.set_sq(0, channel.channel())), | ||
| 203 | Sequence::Eight => T::regs().sqr2().modify(|w| w.set_sq(1, channel.channel())), | ||
| 204 | Sequence::Nine => T::regs().sqr2().modify(|w| w.set_sq(2, channel.channel())), | ||
| 205 | Sequence::Ten => T::regs().sqr2().modify(|w| w.set_sq(3, channel.channel())), | ||
| 206 | Sequence::Eleven => T::regs().sqr2().modify(|w| w.set_sq(4, channel.channel())), | ||
| 207 | Sequence::Twelve => T::regs().sqr2().modify(|w| w.set_sq(5, channel.channel())), | ||
| 208 | Sequence::Thirteen => T::regs().sqr1().modify(|w| w.set_sq(0, channel.channel())), | ||
| 209 | Sequence::Fourteen => T::regs().sqr1().modify(|w| w.set_sq(1, channel.channel())), | ||
| 210 | Sequence::Fifteen => T::regs().sqr1().modify(|w| w.set_sq(2, channel.channel())), | ||
| 211 | Sequence::Sixteen => T::regs().sqr1().modify(|w| w.set_sq(3, channel.channel())), | ||
| 212 | }; | ||
| 213 | |||
| 214 | if !was_on { | ||
| 215 | Self::stop_adc(); | ||
| 216 | } | ||
| 217 | |||
| 218 | self.set_channels_sample_time(&[channel.channel()], sample_time); | ||
| 219 | |||
| 220 | Self::start_adc(); | ||
| 221 | } | ||
| 222 | |||
| 223 | /// Turns on ADC if it is not already turned on and starts continuous DMA transfer. | ||
| 224 | pub fn start(&mut self) -> Result<(), OverrunError> { | ||
| 225 | self.setup_adc(); | ||
| 226 | self.ring_buf.clear(); | ||
| 227 | |||
| 228 | Ok(()) | ||
| 229 | } | ||
| 230 | |||
| 231 | fn stop(&mut self, err: OverrunError) -> Result<usize, OverrunError> { | ||
| 232 | self.teardown_adc(); | ||
| 233 | Err(err) | ||
| 234 | } | ||
| 235 | |||
| 236 | /// Stops DMA transfer. | ||
| 237 | /// It does not turn off ADC. | ||
| 238 | /// Calling `start` restarts continuous DMA transfer. | ||
| 239 | /// | ||
| 240 | /// [`start`]: #method.start | ||
| 241 | pub fn teardown_adc(&mut self) { | ||
| 242 | // Stop the DMA transfer | ||
| 243 | self.ring_buf.request_pause(); | ||
| 244 | |||
| 245 | let r = T::regs(); | ||
| 246 | |||
| 247 | // Stop ADC | ||
| 248 | r.cr2().modify(|reg| { | ||
| 249 | // Stop ADC | ||
| 250 | reg.set_swstart(false); | ||
| 251 | // Stop DMA | ||
| 252 | reg.set_dma(false); | ||
| 253 | }); | ||
| 254 | |||
| 255 | r.cr1().modify(|w| { | ||
| 256 | // Disable interrupt for end of conversion | ||
| 257 | w.set_eocie(false); | ||
| 258 | // Disable interrupt for overrun | ||
| 259 | w.set_ovrie(false); | ||
| 260 | }); | ||
| 261 | |||
| 262 | clear_interrupt_flags(r); | ||
| 263 | |||
| 264 | compiler_fence(Ordering::SeqCst); | ||
| 265 | } | ||
| 266 | |||
| 267 | fn setup_adc(&mut self) { | ||
| 268 | compiler_fence(Ordering::SeqCst); | ||
| 269 | |||
| 270 | self.ring_buf.start(); | ||
| 271 | |||
| 272 | let r = T::regs(); | ||
| 273 | |||
| 274 | // Enable ADC | ||
| 275 | let was_on = Self::is_on(); | ||
| 276 | if !was_on { | ||
| 277 | r.cr2().modify(|reg| { | ||
| 278 | reg.set_adon(false); | ||
| 279 | reg.set_swstart(false); | ||
| 280 | }); | ||
| 281 | } | ||
| 282 | |||
| 283 | // Clear all interrupts | ||
| 284 | r.sr().modify(|regs| { | ||
| 285 | regs.set_eoc(false); | ||
| 286 | regs.set_ovr(false); | ||
| 287 | regs.set_strt(false); | ||
| 288 | }); | ||
| 289 | |||
| 290 | r.cr1().modify(|w| { | ||
| 291 | // Enable interrupt for end of conversion | ||
| 292 | w.set_eocie(true); | ||
| 293 | // Enable interrupt for overrun | ||
| 294 | w.set_ovrie(true); | ||
| 295 | // Scanning converisons of multiple channels | ||
| 296 | w.set_scan(true); | ||
| 297 | // Continuous conversion mode | ||
| 298 | w.set_discen(false); | ||
| 299 | }); | ||
| 300 | |||
| 301 | r.cr2().modify(|w| { | ||
| 302 | // Enable DMA mode | ||
| 303 | w.set_dma(true); | ||
| 304 | // Enable continuous conversions | ||
| 305 | w.set_cont(true); | ||
| 306 | // DMA requests are issues as long as DMA=1 and data are converted. | ||
| 307 | w.set_dds(vals::Dds::CONTINUOUS); | ||
| 308 | // EOC flag is set at the end of each conversion. | ||
| 309 | w.set_eocs(vals::Eocs::EACH_CONVERSION); | ||
| 310 | }); | ||
| 311 | |||
| 312 | // Begin ADC conversions | ||
| 313 | T::regs().cr2().modify(|reg| { | ||
| 314 | reg.set_adon(true); | ||
| 315 | reg.set_swstart(true); | ||
| 316 | }); | ||
| 317 | |||
| 318 | super::blocking_delay_us(3); | ||
| 319 | } | ||
| 320 | |||
| 321 | /// Read bytes that are readily available in the ring buffer. | ||
| 322 | /// If no bytes are currently available in the buffer the call waits until the some | ||
| 323 | /// bytes are available (at least one byte and at most half the buffer size) | ||
| 324 | /// | ||
| 325 | /// Background receive is started if `start()` has not been previously called. | ||
| 326 | /// | ||
| 327 | /// Receive in the background is terminated if an error is returned. | ||
| 328 | /// It must then manually be started again by calling `start()` or by re-calling `read()`. | ||
| 329 | pub fn blocking_read<const N: usize>(&mut self, buf: &mut [u16; N]) -> Result<usize, OverrunError> { | ||
| 330 | let r = T::regs(); | ||
| 331 | |||
| 332 | // Start background receive if it was not already started | ||
| 333 | if !r.cr2().read().dma() { | ||
| 334 | self.start()?; | ||
| 335 | } | ||
| 336 | |||
| 337 | // Clear overrun flag if set. | ||
| 338 | if r.sr().read().ovr() { | ||
| 339 | return self.stop(OverrunError); | ||
| 340 | } | ||
| 341 | |||
| 342 | loop { | ||
| 343 | match self.ring_buf.read(buf) { | ||
| 344 | Ok((0, _)) => {} | ||
| 345 | Ok((len, _)) => { | ||
| 346 | return Ok(len); | ||
| 347 | } | ||
| 348 | Err(_) => { | ||
| 349 | return self.stop(OverrunError); | ||
| 350 | } | ||
| 351 | } | ||
| 352 | } | ||
| 353 | } | ||
| 354 | |||
| 355 | /// Reads measurements from the DMA ring buffer. | ||
| 356 | /// | ||
| 357 | /// This method fills the provided `measurements` array with ADC readings from the DMA buffer. | ||
| 358 | /// The length of the `measurements` array should be exactly half of the DMA buffer length. Because interrupts are only generated if half or full DMA transfer completes. | ||
| 359 | /// | ||
| 360 | /// Each call to `read` will populate the `measurements` array in the same order as the channels defined with `set_sample_sequence`. | ||
| 361 | /// There will be many sequences worth of measurements in this array because it only returns if at least half of the DMA buffer is filled. | ||
| 362 | /// For example if 3 channels are sampled `measurements` contain: `[sq0 sq1 sq3 sq0 sq1 sq3 sq0 sq1 sq3 sq0 sq1 sq3..]`. | ||
| 363 | /// | ||
| 364 | /// If an error is returned, it indicates a DMA overrun, and the process must be restarted by calling `start` or `read` again. | ||
| 365 | /// | ||
| 366 | /// By default, the ADC fills the DMA buffer as quickly as possible. To control the sample rate, call `teardown_adc` after each readout, and then start the DMA again at the desired interval. | ||
| 367 | /// Note that even if using `teardown_adc` to control the sample rate, with each call to `read`, measurements equivalent to half the size of the DMA buffer are still collected. | ||
| 368 | /// | ||
| 369 | /// Example: | ||
| 370 | /// ```rust,ignore | ||
| 371 | /// const DMA_BUF_LEN: usize = 120; | ||
| 372 | /// let adc_dma_buf = [0u16; DMA_BUF_LEN]; | ||
| 373 | /// let mut adc: RingBufferedAdc<embassy_stm32::peripherals::ADC1> = adc.into_ring_buffered(p.DMA2_CH0, adc_dma_buf); | ||
| 374 | /// | ||
| 375 | /// adc.set_sample_sequence(Sequence::One, &mut p.PA0, SampleTime::CYCLES112); | ||
| 376 | /// adc.set_sample_sequence(Sequence::Two, &mut p.PA1, SampleTime::CYCLES112); | ||
| 377 | /// adc.set_sample_sequence(Sequence::Three, &mut p.PA2, SampleTime::CYCLES112); | ||
| 378 | /// | ||
| 379 | /// let mut measurements = [0u16; DMA_BUF_LEN / 2]; | ||
| 380 | /// loop { | ||
| 381 | /// match adc.read(&mut measurements).await { | ||
| 382 | /// Ok(_) => { | ||
| 383 | /// defmt::info!("adc1: {}", measurements); | ||
| 384 | /// // Only needed to manually control sample rate. | ||
| 385 | /// adc.teardown_adc(); | ||
| 386 | /// } | ||
| 387 | /// Err(e) => { | ||
| 388 | /// defmt::warn!("Error: {:?}", e); | ||
| 389 | /// // DMA overrun, next call to `read` restarts ADC. | ||
| 390 | /// } | ||
| 391 | /// } | ||
| 392 | /// | ||
| 393 | /// // Manually control sample rate. | ||
| 394 | /// Timer::after_millis(100).await; | ||
| 395 | /// } | ||
| 396 | /// ``` | ||
| 397 | /// | ||
| 398 | /// | ||
| 399 | /// [`set_sample_sequence`]: #method.set_sample_sequence | ||
| 400 | /// [`teardown_adc`]: #method.teardown_adc | ||
| 401 | /// [`start`]: #method.start | ||
| 402 | pub async fn read<const N: usize>(&mut self, measurements: &mut [u16; N]) -> Result<usize, OverrunError> { | ||
| 403 | assert_eq!( | ||
| 404 | self.ring_buf.capacity() / 2, | ||
| 405 | N, | ||
| 406 | "Buffer size must be half the size of the ring buffer" | ||
| 407 | ); | ||
| 408 | |||
| 409 | let r = T::regs(); | ||
| 410 | |||
| 411 | // Start background receive if it was not already started | ||
| 412 | if !r.cr2().read().dma() { | ||
| 413 | self.start()?; | ||
| 414 | } | ||
| 415 | |||
| 416 | // Clear overrun flag if set. | ||
| 417 | if r.sr().read().ovr() { | ||
| 418 | return self.stop(OverrunError); | ||
| 419 | } | ||
| 420 | match self.ring_buf.read_exact(measurements).await { | ||
| 421 | Ok(len) => Ok(len), | ||
| 422 | Err(_) => self.stop(OverrunError), | ||
| 423 | } | ||
| 424 | } | ||
| 425 | } | ||
| 426 | |||
| 427 | impl<T: Instance> Drop for RingBufferedAdc<'_, T> { | ||
| 428 | fn drop(&mut self) { | ||
| 429 | self.teardown_adc(); | ||
| 430 | rcc::disable::<T>(); | ||
| 431 | } | ||
| 432 | } | ||
diff --git a/embassy-stm32/src/adc/v2.rs b/embassy-stm32/src/adc/v2.rs index 93ec78548..90c6294d2 100644 --- a/embassy-stm32/src/adc/v2.rs +++ b/embassy-stm32/src/adc/v2.rs | |||
| @@ -1,11 +1,22 @@ | |||
| 1 | use core::mem; | ||
| 2 | use core::sync::atomic::{Ordering, compiler_fence}; | ||
| 3 | |||
| 1 | use super::blocking_delay_us; | 4 | use super::blocking_delay_us; |
| 2 | use crate::adc::{Adc, AdcChannel, Instance, Resolution, SampleTime}; | 5 | use crate::adc::{Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel}; |
| 6 | use crate::pac::adc::vals; | ||
| 3 | use crate::peripherals::ADC1; | 7 | use crate::peripherals::ADC1; |
| 4 | use crate::time::Hertz; | 8 | use crate::time::Hertz; |
| 5 | use crate::{Peri, rcc}; | 9 | use crate::{Peri, rcc}; |
| 6 | 10 | ||
| 7 | mod ringbuffered_v2; | 11 | mod ringbuffered; |
| 8 | pub use ringbuffered_v2::{RingBufferedAdc, Sequence}; | 12 | pub use ringbuffered::RingBufferedAdc; |
| 13 | |||
| 14 | fn clear_interrupt_flags(r: crate::pac::adc::Adc) { | ||
| 15 | r.sr().modify(|regs| { | ||
| 16 | regs.set_eoc(false); | ||
| 17 | regs.set_ovr(false); | ||
| 18 | }); | ||
| 19 | } | ||
| 9 | 20 | ||
| 10 | /// Default VREF voltage used for sample conversion to millivolts. | 21 | /// Default VREF voltage used for sample conversion to millivolts. |
| 11 | pub const VREF_DEFAULT_MV: u32 = 3300; | 22 | pub const VREF_DEFAULT_MV: u32 = 3300; |
| @@ -112,6 +123,98 @@ where | |||
| 112 | } | 123 | } |
| 113 | } | 124 | } |
| 114 | 125 | ||
| 126 | /// Configures the ADC to use a DMA ring buffer for continuous data acquisition. | ||
| 127 | /// | ||
| 128 | /// The `dma_buf` should be large enough to prevent DMA buffer overrun. | ||
| 129 | /// The length of the `dma_buf` should be a multiple of the ADC channel count. | ||
| 130 | /// For example, if 3 channels are measured, its length can be 3 * 40 = 120 measurements. | ||
| 131 | /// | ||
| 132 | /// `read` method is used to read out measurements from the DMA ring buffer, and its buffer should be exactly half of the `dma_buf` length. | ||
| 133 | /// It is critical to call `read` frequently to prevent DMA buffer overrun. | ||
| 134 | /// | ||
| 135 | /// [`read`]: #method.read | ||
| 136 | pub fn into_ring_buffered<'a>( | ||
| 137 | self, | ||
| 138 | dma: Peri<'d, impl RxDma<T>>, | ||
| 139 | dma_buf: &'d mut [u16], | ||
| 140 | sequence: impl ExactSizeIterator<Item = (&'a mut AnyAdcChannel<T>, SampleTime)>, | ||
| 141 | ) -> RingBufferedAdc<'d, T> { | ||
| 142 | assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); | ||
| 143 | |||
| 144 | T::regs().cr2().modify(|reg| { | ||
| 145 | reg.set_adon(true); | ||
| 146 | }); | ||
| 147 | |||
| 148 | // Check the sequence is long enough | ||
| 149 | T::regs().sqr1().modify(|r| { | ||
| 150 | r.set_l((sequence.len() - 1).try_into().unwrap()); | ||
| 151 | }); | ||
| 152 | |||
| 153 | for (i, (channel, sample_time)) in sequence.enumerate() { | ||
| 154 | // Set this GPIO as an analog input. | ||
| 155 | channel.setup(); | ||
| 156 | |||
| 157 | // Set the channel in the right sequence field. | ||
| 158 | T::regs().sqr3().modify(|w| w.set_sq(i, channel.channel())); | ||
| 159 | |||
| 160 | Self::set_channel_sample_time(channel.channel(), sample_time); | ||
| 161 | } | ||
| 162 | |||
| 163 | compiler_fence(Ordering::SeqCst); | ||
| 164 | |||
| 165 | let r = T::regs(); | ||
| 166 | |||
| 167 | // Clear all interrupts | ||
| 168 | r.sr().modify(|regs| { | ||
| 169 | regs.set_eoc(false); | ||
| 170 | regs.set_ovr(false); | ||
| 171 | regs.set_strt(false); | ||
| 172 | }); | ||
| 173 | |||
| 174 | r.cr1().modify(|w| { | ||
| 175 | // Enable interrupt for end of conversion | ||
| 176 | w.set_eocie(true); | ||
| 177 | // Enable interrupt for overrun | ||
| 178 | w.set_ovrie(true); | ||
| 179 | // Scanning converisons of multiple channels | ||
| 180 | w.set_scan(true); | ||
| 181 | // Continuous conversion mode | ||
| 182 | w.set_discen(false); | ||
| 183 | }); | ||
| 184 | |||
| 185 | r.cr2().modify(|w| { | ||
| 186 | // Enable DMA mode | ||
| 187 | w.set_dma(true); | ||
| 188 | // Enable continuous conversions | ||
| 189 | w.set_cont(true); | ||
| 190 | // DMA requests are issues as long as DMA=1 and data are converted. | ||
| 191 | w.set_dds(vals::Dds::CONTINUOUS); | ||
| 192 | // EOC flag is set at the end of each conversion. | ||
| 193 | w.set_eocs(vals::Eocs::EACH_CONVERSION); | ||
| 194 | }); | ||
| 195 | |||
| 196 | // Don't disable the clock | ||
| 197 | mem::forget(self); | ||
| 198 | |||
| 199 | RingBufferedAdc::new(dma, dma_buf) | ||
| 200 | } | ||
| 201 | |||
| 202 | pub(super) fn start() { | ||
| 203 | // Begin ADC conversions | ||
| 204 | T::regs().cr2().modify(|reg| { | ||
| 205 | reg.set_adon(true); | ||
| 206 | reg.set_swstart(true); | ||
| 207 | }); | ||
| 208 | } | ||
| 209 | |||
| 210 | pub(super) fn stop() { | ||
| 211 | // Stop ADC | ||
| 212 | T::regs().cr2().modify(|reg| { | ||
| 213 | // Stop ADC | ||
| 214 | reg.set_swstart(false); | ||
| 215 | }); | ||
| 216 | } | ||
| 217 | |||
| 115 | pub fn set_sample_time(&mut self, sample_time: SampleTime) { | 218 | pub fn set_sample_time(&mut self, sample_time: SampleTime) { |
| 116 | self.sample_time = sample_time; | 219 | self.sample_time = sample_time; |
| 117 | } | 220 | } |
| @@ -198,6 +301,31 @@ where | |||
| 198 | T::regs().smpr1().modify(|reg| reg.set_smp((ch - 10) as _, sample_time)); | 301 | T::regs().smpr1().modify(|reg| reg.set_smp((ch - 10) as _, sample_time)); |
| 199 | } | 302 | } |
| 200 | } | 303 | } |
| 304 | |||
| 305 | pub(super) fn teardown_adc() { | ||
| 306 | let r = T::regs(); | ||
| 307 | |||
| 308 | // Stop ADC | ||
| 309 | r.cr2().modify(|reg| { | ||
| 310 | // Stop ADC | ||
| 311 | reg.set_swstart(false); | ||
| 312 | // Stop ADC | ||
| 313 | reg.set_adon(false); | ||
| 314 | // Stop DMA | ||
| 315 | reg.set_dma(false); | ||
| 316 | }); | ||
| 317 | |||
| 318 | r.cr1().modify(|w| { | ||
| 319 | // Disable interrupt for end of conversion | ||
| 320 | w.set_eocie(false); | ||
| 321 | // Disable interrupt for overrun | ||
| 322 | w.set_ovrie(false); | ||
| 323 | }); | ||
| 324 | |||
| 325 | clear_interrupt_flags(r); | ||
| 326 | |||
| 327 | compiler_fence(Ordering::SeqCst); | ||
| 328 | } | ||
| 201 | } | 329 | } |
| 202 | 330 | ||
| 203 | impl<'d, T: Instance> Drop for Adc<'d, T> { | 331 | impl<'d, T: Instance> Drop for Adc<'d, T> { |
diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index 47632263b..62c2da557 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs | |||
| @@ -12,6 +12,13 @@ pub use pac::adc::vals::{Ovsr, Ovss, Presc}; | |||
| 12 | use super::{ | 12 | use super::{ |
| 13 | Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, blocking_delay_us, | 13 | Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, blocking_delay_us, |
| 14 | }; | 14 | }; |
| 15 | |||
| 16 | #[cfg(any(adc_v3, adc_g0, adc_u0))] | ||
| 17 | mod ringbuffered; | ||
| 18 | |||
| 19 | #[cfg(any(adc_v3, adc_g0, adc_u0))] | ||
| 20 | use ringbuffered::RingBufferedAdc; | ||
| 21 | |||
| 15 | use crate::dma::Transfer; | 22 | use crate::dma::Transfer; |
| 16 | use crate::{Peri, pac, rcc}; | 23 | use crate::{Peri, pac, rcc}; |
| 17 | 24 | ||
| @@ -174,6 +181,38 @@ impl<'d, T: Instance> Adc<'d, T> { | |||
| 174 | blocking_delay_us(1); | 181 | blocking_delay_us(1); |
| 175 | } | 182 | } |
| 176 | 183 | ||
| 184 | #[cfg(any(adc_v3, adc_g0, adc_u0))] | ||
| 185 | pub(super) fn start() { | ||
| 186 | // Start adc conversion | ||
| 187 | T::regs().cr().modify(|reg| { | ||
| 188 | reg.set_adstart(true); | ||
| 189 | }); | ||
| 190 | } | ||
| 191 | |||
| 192 | #[cfg(any(adc_v3, adc_g0, adc_u0))] | ||
| 193 | pub(super) fn stop() { | ||
| 194 | // Stop adc conversion | ||
| 195 | if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() { | ||
| 196 | T::regs().cr().modify(|reg| { | ||
| 197 | reg.set_adstp(true); | ||
| 198 | }); | ||
| 199 | while T::regs().cr().read().adstart() {} | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | #[cfg(any(adc_v3, adc_g0, adc_u0))] | ||
| 204 | pub(super) fn teardown_adc() { | ||
| 205 | //disable dma control | ||
| 206 | #[cfg(not(any(adc_g0, adc_u0)))] | ||
| 207 | T::regs().cfgr().modify(|reg| { | ||
| 208 | reg.set_dmaen(false); | ||
| 209 | }); | ||
| 210 | #[cfg(any(adc_g0, adc_u0))] | ||
| 211 | T::regs().cfgr1().modify(|reg| { | ||
| 212 | reg.set_dmaen(false); | ||
| 213 | }); | ||
| 214 | } | ||
| 215 | |||
| 177 | /// Initialize the ADC leaving any analog clock at reset value. | 216 | /// Initialize the ADC leaving any analog clock at reset value. |
| 178 | /// For G0 and WL, this is the async clock without prescaler. | 217 | /// For G0 and WL, this is the async clock without prescaler. |
| 179 | pub fn new(adc: Peri<'d, T>) -> Self { | 218 | pub fn new(adc: Peri<'d, T>) -> Self { |
| @@ -423,6 +462,9 @@ impl<'d, T: Instance> Adc<'d, T> { | |||
| 423 | "Asynchronous read sequence cannot be more than 16 in length" | 462 | "Asynchronous read sequence cannot be more than 16 in length" |
| 424 | ); | 463 | ); |
| 425 | 464 | ||
| 465 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 466 | let _device_busy = crate::low_power::DeviceBusy::new(); | ||
| 467 | |||
| 426 | // Ensure no conversions are ongoing and ADC is enabled. | 468 | // Ensure no conversions are ongoing and ADC is enabled. |
| 427 | Self::cancel_conversions(); | 469 | Self::cancel_conversions(); |
| 428 | self.enable(); | 470 | self.enable(); |
| @@ -559,6 +601,137 @@ impl<'d, T: Instance> Adc<'d, T> { | |||
| 559 | }); | 601 | }); |
| 560 | } | 602 | } |
| 561 | 603 | ||
| 604 | /// Configures the ADC to use a DMA ring buffer for continuous data acquisition. | ||
| 605 | /// | ||
| 606 | /// The `dma_buf` should be large enough to prevent DMA buffer overrun. | ||
| 607 | /// The length of the `dma_buf` should be a multiple of the ADC channel count. | ||
| 608 | /// For example, if 3 channels are measured, its length can be 3 * 40 = 120 measurements. | ||
| 609 | /// | ||
| 610 | /// `read` method is used to read out measurements from the DMA ring buffer, and its buffer should be exactly half of the `dma_buf` length. | ||
| 611 | /// It is critical to call `read` frequently to prevent DMA buffer overrun. | ||
| 612 | /// | ||
| 613 | /// [`read`]: #method.read | ||
| 614 | #[cfg(any(adc_v3, adc_g0, adc_u0))] | ||
| 615 | pub fn into_ring_buffered<'a>( | ||
| 616 | &mut self, | ||
| 617 | dma: Peri<'a, impl RxDma<T>>, | ||
| 618 | dma_buf: &'a mut [u16], | ||
| 619 | sequence: impl ExactSizeIterator<Item = (&'a mut AnyAdcChannel<T>, SampleTime)>, | ||
| 620 | ) -> RingBufferedAdc<'a, T> { | ||
| 621 | assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); | ||
| 622 | assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty"); | ||
| 623 | assert!( | ||
| 624 | sequence.len() <= 16, | ||
| 625 | "Asynchronous read sequence cannot be more than 16 in length" | ||
| 626 | ); | ||
| 627 | // reset conversions and enable the adc | ||
| 628 | Self::cancel_conversions(); | ||
| 629 | self.enable(); | ||
| 630 | |||
| 631 | //adc side setup | ||
| 632 | |||
| 633 | // Set sequence length | ||
| 634 | #[cfg(not(any(adc_g0, adc_u0)))] | ||
| 635 | T::regs().sqr1().modify(|w| { | ||
| 636 | w.set_l(sequence.len() as u8 - 1); | ||
| 637 | }); | ||
| 638 | |||
| 639 | #[cfg(adc_g0)] | ||
| 640 | { | ||
| 641 | let mut sample_times = Vec::<SampleTime, SAMPLE_TIMES_CAPACITY>::new(); | ||
| 642 | |||
| 643 | T::regs().chselr().write(|chselr| { | ||
| 644 | T::regs().smpr().write(|smpr| { | ||
| 645 | for (channel, sample_time) in sequence { | ||
| 646 | chselr.set_chsel(channel.channel.into(), true); | ||
| 647 | if let Some(i) = sample_times.iter().position(|&t| t == sample_time) { | ||
| 648 | smpr.set_smpsel(channel.channel.into(), (i as u8).into()); | ||
| 649 | } else { | ||
| 650 | smpr.set_sample_time(sample_times.len(), sample_time); | ||
| 651 | if let Err(_) = sample_times.push(sample_time) { | ||
| 652 | panic!( | ||
| 653 | "Implementation is limited to {} unique sample times among all channels.", | ||
| 654 | SAMPLE_TIMES_CAPACITY | ||
| 655 | ); | ||
| 656 | } | ||
| 657 | } | ||
| 658 | } | ||
| 659 | }) | ||
| 660 | }); | ||
| 661 | } | ||
| 662 | #[cfg(not(adc_g0))] | ||
| 663 | { | ||
| 664 | #[cfg(adc_u0)] | ||
| 665 | let mut channel_mask = 0; | ||
| 666 | |||
| 667 | // Configure channels and ranks | ||
| 668 | for (_i, (channel, sample_time)) in sequence.enumerate() { | ||
| 669 | Self::configure_channel(channel, sample_time); | ||
| 670 | |||
| 671 | // Each channel is sampled according to sequence | ||
| 672 | #[cfg(not(any(adc_g0, adc_u0)))] | ||
| 673 | match _i { | ||
| 674 | 0..=3 => { | ||
| 675 | T::regs().sqr1().modify(|w| { | ||
| 676 | w.set_sq(_i, channel.channel()); | ||
| 677 | }); | ||
| 678 | } | ||
| 679 | 4..=8 => { | ||
| 680 | T::regs().sqr2().modify(|w| { | ||
| 681 | w.set_sq(_i - 4, channel.channel()); | ||
| 682 | }); | ||
| 683 | } | ||
| 684 | 9..=13 => { | ||
| 685 | T::regs().sqr3().modify(|w| { | ||
| 686 | w.set_sq(_i - 9, channel.channel()); | ||
| 687 | }); | ||
| 688 | } | ||
| 689 | 14..=15 => { | ||
| 690 | T::regs().sqr4().modify(|w| { | ||
| 691 | w.set_sq(_i - 14, channel.channel()); | ||
| 692 | }); | ||
| 693 | } | ||
| 694 | _ => unreachable!(), | ||
| 695 | } | ||
| 696 | |||
| 697 | #[cfg(adc_u0)] | ||
| 698 | { | ||
| 699 | channel_mask |= 1 << channel.channel(); | ||
| 700 | } | ||
| 701 | } | ||
| 702 | |||
| 703 | // On G0 and U0 enabled channels are sampled from 0 to last channel. | ||
| 704 | // It is possible to add up to 8 sequences if CHSELRMOD = 1. | ||
| 705 | // However for supporting more than 8 channels alternative CHSELRMOD = 0 approach is used. | ||
| 706 | #[cfg(adc_u0)] | ||
| 707 | T::regs().chselr().modify(|reg| { | ||
| 708 | reg.set_chsel(channel_mask); | ||
| 709 | }); | ||
| 710 | } | ||
| 711 | // Set continuous mode with Circular dma. | ||
| 712 | // Clear overrun flag before starting transfer. | ||
| 713 | T::regs().isr().modify(|reg| { | ||
| 714 | reg.set_ovr(true); | ||
| 715 | }); | ||
| 716 | |||
| 717 | #[cfg(not(any(adc_g0, adc_u0)))] | ||
| 718 | T::regs().cfgr().modify(|reg| { | ||
| 719 | reg.set_discen(false); | ||
| 720 | reg.set_cont(true); | ||
| 721 | reg.set_dmacfg(Dmacfg::CIRCULAR); | ||
| 722 | reg.set_dmaen(true); | ||
| 723 | }); | ||
| 724 | #[cfg(any(adc_g0, adc_u0))] | ||
| 725 | T::regs().cfgr1().modify(|reg| { | ||
| 726 | reg.set_discen(false); | ||
| 727 | reg.set_cont(true); | ||
| 728 | reg.set_dmacfg(Dmacfg::CIRCULAR); | ||
| 729 | reg.set_dmaen(true); | ||
| 730 | }); | ||
| 731 | |||
| 732 | RingBufferedAdc::new(dma, dma_buf) | ||
| 733 | } | ||
| 734 | |||
| 562 | #[cfg(not(adc_g0))] | 735 | #[cfg(not(adc_g0))] |
| 563 | fn configure_channel(channel: &mut impl AdcChannel<T>, sample_time: SampleTime) { | 736 | fn configure_channel(channel: &mut impl AdcChannel<T>, sample_time: SampleTime) { |
| 564 | // RM0492, RM0481, etc. | 737 | // RM0492, RM0481, etc. |
diff --git a/embassy-stm32/src/can/fd/config.rs b/embassy-stm32/src/can/fd/config.rs index e08349f02..4fe634ce4 100644 --- a/embassy-stm32/src/can/fd/config.rs +++ b/embassy-stm32/src/can/fd/config.rs | |||
| @@ -360,6 +360,8 @@ pub struct FdCanConfig { | |||
| 360 | pub global_filter: GlobalFilter, | 360 | pub global_filter: GlobalFilter, |
| 361 | /// TX buffer mode (FIFO or priority queue) | 361 | /// TX buffer mode (FIFO or priority queue) |
| 362 | pub tx_buffer_mode: TxBufferMode, | 362 | pub tx_buffer_mode: TxBufferMode, |
| 363 | /// Automatic recovery from bus off state | ||
| 364 | pub automatic_bus_off_recovery: bool, | ||
| 363 | } | 365 | } |
| 364 | 366 | ||
| 365 | impl FdCanConfig { | 367 | impl FdCanConfig { |
| @@ -456,6 +458,16 @@ impl FdCanConfig { | |||
| 456 | self.tx_buffer_mode = txbm; | 458 | self.tx_buffer_mode = txbm; |
| 457 | self | 459 | self |
| 458 | } | 460 | } |
| 461 | |||
| 462 | /// Enables or disables automatic recovery from bus off state | ||
| 463 | /// | ||
| 464 | /// Automatic recovery is performed by clearing the INIT bit in the CCCR register if | ||
| 465 | /// the BO bit is active in the IR register in the IT0 interrupt. | ||
| 466 | #[inline] | ||
| 467 | pub const fn set_automatic_bus_off_recovery(mut self, enabled: bool) -> Self { | ||
| 468 | self.automatic_bus_off_recovery = enabled; | ||
| 469 | self | ||
| 470 | } | ||
| 459 | } | 471 | } |
| 460 | 472 | ||
| 461 | impl Default for FdCanConfig { | 473 | impl Default for FdCanConfig { |
| @@ -474,6 +486,7 @@ impl Default for FdCanConfig { | |||
| 474 | timestamp_source: TimestampSource::None, | 486 | timestamp_source: TimestampSource::None, |
| 475 | global_filter: GlobalFilter::default(), | 487 | global_filter: GlobalFilter::default(), |
| 476 | tx_buffer_mode: TxBufferMode::Priority, | 488 | tx_buffer_mode: TxBufferMode::Priority, |
| 489 | automatic_bus_off_recovery: true, | ||
| 477 | } | 490 | } |
| 478 | } | 491 | } |
| 479 | } | 492 | } |
diff --git a/embassy-stm32/src/can/fdcan.rs b/embassy-stm32/src/can/fdcan.rs index a142a6d63..9883aff57 100644 --- a/embassy-stm32/src/can/fdcan.rs +++ b/embassy-stm32/src/can/fdcan.rs | |||
| @@ -53,7 +53,7 @@ impl<T: Instance> interrupt::typelevel::Handler<T::IT0Interrupt> for IT0Interrup | |||
| 53 | regs.ir().write(|w| w.set_tefn(true)); | 53 | regs.ir().write(|w| w.set_tefn(true)); |
| 54 | } | 54 | } |
| 55 | 55 | ||
| 56 | T::info().state.lock(|s| { | 56 | let recover_from_bo = T::info().state.lock(|s| { |
| 57 | let state = s.borrow_mut(); | 57 | let state = s.borrow_mut(); |
| 58 | match &state.tx_mode { | 58 | match &state.tx_mode { |
| 59 | TxMode::NonBuffered(waker) => waker.wake(), | 59 | TxMode::NonBuffered(waker) => waker.wake(), |
| @@ -85,11 +85,15 @@ impl<T: Instance> interrupt::typelevel::Handler<T::IT0Interrupt> for IT0Interrup | |||
| 85 | if ir.rfn(1) { | 85 | if ir.rfn(1) { |
| 86 | state.rx_mode.on_interrupt::<T>(1, state.ns_per_timer_tick); | 86 | state.rx_mode.on_interrupt::<T>(1, state.ns_per_timer_tick); |
| 87 | } | 87 | } |
| 88 | |||
| 89 | state.automatic_bus_off_recovery | ||
| 88 | }); | 90 | }); |
| 89 | 91 | ||
| 90 | if ir.bo() { | 92 | if ir.bo() { |
| 91 | regs.ir().write(|w| w.set_bo(true)); | 93 | regs.ir().write(|w| w.set_bo(true)); |
| 92 | if regs.psr().read().bo() { | 94 | if let Some(true) = recover_from_bo |
| 95 | && regs.psr().read().bo() | ||
| 96 | { | ||
| 93 | // Initiate bus-off recovery sequence by resetting CCCR.INIT | 97 | // Initiate bus-off recovery sequence by resetting CCCR.INIT |
| 94 | regs.cccr().modify(|w| w.set_init(false)); | 98 | regs.cccr().modify(|w| w.set_init(false)); |
| 95 | } | 99 | } |
| @@ -263,7 +267,9 @@ impl<'d> CanConfigurator<'d> { | |||
| 263 | pub fn start(self, mode: OperatingMode) -> Can<'d> { | 267 | pub fn start(self, mode: OperatingMode) -> Can<'d> { |
| 264 | let ns_per_timer_tick = calc_ns_per_timer_tick(&self.info, self.periph_clock, self.config.frame_transmit); | 268 | let ns_per_timer_tick = calc_ns_per_timer_tick(&self.info, self.periph_clock, self.config.frame_transmit); |
| 265 | self.info.state.lock(|s| { | 269 | self.info.state.lock(|s| { |
| 266 | s.borrow_mut().ns_per_timer_tick = ns_per_timer_tick; | 270 | let mut state = s.borrow_mut(); |
| 271 | state.ns_per_timer_tick = ns_per_timer_tick; | ||
| 272 | state.automatic_bus_off_recovery = Some(self.config.automatic_bus_off_recovery); | ||
| 267 | }); | 273 | }); |
| 268 | self.info.regs.into_mode(self.config, mode); | 274 | self.info.regs.into_mode(self.config, mode); |
| 269 | Can { | 275 | Can { |
| @@ -861,7 +867,7 @@ struct State { | |||
| 861 | sender_instance_count: usize, | 867 | sender_instance_count: usize, |
| 862 | tx_pin_port: Option<u8>, | 868 | tx_pin_port: Option<u8>, |
| 863 | rx_pin_port: Option<u8>, | 869 | rx_pin_port: Option<u8>, |
| 864 | 870 | automatic_bus_off_recovery: Option<bool>, // controlled by CanConfigurator::start() | |
| 865 | pub err_waker: AtomicWaker, | 871 | pub err_waker: AtomicWaker, |
| 866 | } | 872 | } |
| 867 | 873 | ||
| @@ -876,6 +882,7 @@ impl State { | |||
| 876 | sender_instance_count: 0, | 882 | sender_instance_count: 0, |
| 877 | tx_pin_port: None, | 883 | tx_pin_port: None, |
| 878 | rx_pin_port: None, | 884 | rx_pin_port: None, |
| 885 | automatic_bus_off_recovery: None, | ||
| 879 | } | 886 | } |
| 880 | } | 887 | } |
| 881 | } | 888 | } |
diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 01b6b8800..6f2d03bd1 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs | |||
| @@ -70,6 +70,11 @@ fn debug_print_interrupts(isr: stm32_metapac::i2c::regs::Isr) { | |||
| 70 | } | 70 | } |
| 71 | 71 | ||
| 72 | pub(crate) unsafe fn on_interrupt<T: Instance>() { | 72 | pub(crate) unsafe fn on_interrupt<T: Instance>() { |
| 73 | // restore the clocks to their last configured state as | ||
| 74 | // much is lost in STOP modes | ||
| 75 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 76 | crate::low_power::on_wakeup_irq(); | ||
| 77 | |||
| 73 | let regs = T::info().regs; | 78 | let regs = T::info().regs; |
| 74 | let isr = regs.isr().read(); | 79 | let isr = regs.isr().read(); |
| 75 | 80 | ||
| @@ -814,6 +819,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { | |||
| 814 | 819 | ||
| 815 | /// Write. | 820 | /// Write. |
| 816 | pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> { | 821 | pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> { |
| 822 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 823 | let _device_busy = crate::low_power::DeviceBusy::new(); | ||
| 817 | let timeout = self.timeout(); | 824 | let timeout = self.timeout(); |
| 818 | if write.is_empty() { | 825 | if write.is_empty() { |
| 819 | self.write_internal(address.into(), write, true, timeout) | 826 | self.write_internal(address.into(), write, true, timeout) |
| @@ -828,6 +835,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { | |||
| 828 | /// | 835 | /// |
| 829 | /// The buffers are concatenated in a single write transaction. | 836 | /// The buffers are concatenated in a single write transaction. |
| 830 | pub async fn write_vectored(&mut self, address: Address, write: &[&[u8]]) -> Result<(), Error> { | 837 | pub async fn write_vectored(&mut self, address: Address, write: &[&[u8]]) -> Result<(), Error> { |
| 838 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 839 | let _device_busy = crate::low_power::DeviceBusy::new(); | ||
| 831 | let timeout = self.timeout(); | 840 | let timeout = self.timeout(); |
| 832 | 841 | ||
| 833 | if write.is_empty() { | 842 | if write.is_empty() { |
| @@ -851,6 +860,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { | |||
| 851 | 860 | ||
| 852 | /// Read. | 861 | /// Read. |
| 853 | pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { | 862 | pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { |
| 863 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 864 | let _device_busy = crate::low_power::DeviceBusy::new(); | ||
| 854 | let timeout = self.timeout(); | 865 | let timeout = self.timeout(); |
| 855 | 866 | ||
| 856 | if buffer.is_empty() { | 867 | if buffer.is_empty() { |
| @@ -863,6 +874,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { | |||
| 863 | 874 | ||
| 864 | /// Write, restart, read. | 875 | /// Write, restart, read. |
| 865 | pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> { | 876 | pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> { |
| 877 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 878 | let _device_busy = crate::low_power::DeviceBusy::new(); | ||
| 866 | let timeout = self.timeout(); | 879 | let timeout = self.timeout(); |
| 867 | 880 | ||
| 868 | if write.is_empty() { | 881 | if write.is_empty() { |
| @@ -888,6 +901,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { | |||
| 888 | /// | 901 | /// |
| 889 | /// [transaction contract]: embedded_hal_1::i2c::I2c::transaction | 902 | /// [transaction contract]: embedded_hal_1::i2c::I2c::transaction |
| 890 | pub async fn transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { | 903 | pub async fn transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { |
| 904 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 905 | let _device_busy = crate::low_power::DeviceBusy::new(); | ||
| 891 | let _ = addr; | 906 | let _ = addr; |
| 892 | let _ = operations; | 907 | let _ = operations; |
| 893 | todo!() | 908 | todo!() |
diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index 1b66ca680..cde3153f6 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs | |||
| @@ -43,8 +43,6 @@ | |||
| 43 | //! | 43 | //! |
| 44 | //! // give the RTC to the executor... | 44 | //! // give the RTC to the executor... |
| 45 | //! let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); | 45 | //! let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); |
| 46 | //! static RTC: StaticCell<Rtc> = StaticCell::new(); | ||
| 47 | //! let rtc = RTC.init(rtc); | ||
| 48 | //! embassy_stm32::low_power::stop_with_rtc(rtc); | 46 | //! embassy_stm32::low_power::stop_with_rtc(rtc); |
| 49 | //! | 47 | //! |
| 50 | //! // your application here... | 48 | //! // your application here... |
| @@ -70,6 +68,39 @@ use crate::rtc::Rtc; | |||
| 70 | 68 | ||
| 71 | static mut EXECUTOR: Option<Executor> = None; | 69 | static mut EXECUTOR: Option<Executor> = None; |
| 72 | 70 | ||
| 71 | #[cfg(stm32wlex)] | ||
| 72 | pub(crate) use self::busy::DeviceBusy; | ||
| 73 | #[cfg(stm32wlex)] | ||
| 74 | mod busy { | ||
| 75 | use core::sync::atomic::{AtomicU32, Ordering}; | ||
| 76 | |||
| 77 | // Count of devices blocking STOP | ||
| 78 | static STOP_BLOCKED: AtomicU32 = AtomicU32::new(0); | ||
| 79 | |||
| 80 | /// Check if STOP1 is blocked. | ||
| 81 | pub(crate) fn stop_blocked() -> bool { | ||
| 82 | STOP_BLOCKED.load(Ordering::SeqCst) > 0 | ||
| 83 | } | ||
| 84 | |||
| 85 | /// When ca device goes busy it will construct one of these where it will be dropped when the device goes idle. | ||
| 86 | pub(crate) struct DeviceBusy {} | ||
| 87 | |||
| 88 | impl DeviceBusy { | ||
| 89 | /// Create a new DeviceBusy. | ||
| 90 | pub(crate) fn new() -> Self { | ||
| 91 | STOP_BLOCKED.fetch_add(1, Ordering::SeqCst); | ||
| 92 | trace!("low power: device busy: Stop:{}", STOP_BLOCKED.load(Ordering::SeqCst)); | ||
| 93 | Self {} | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | impl Drop for DeviceBusy { | ||
| 98 | fn drop(&mut self) { | ||
| 99 | STOP_BLOCKED.fetch_sub(1, Ordering::SeqCst); | ||
| 100 | trace!("low power: device idle: Stop:{}", STOP_BLOCKED.load(Ordering::SeqCst)); | ||
| 101 | } | ||
| 102 | } | ||
| 103 | } | ||
| 73 | #[cfg(not(stm32u0))] | 104 | #[cfg(not(stm32u0))] |
| 74 | foreach_interrupt! { | 105 | foreach_interrupt! { |
| 75 | (RTC, rtc, $block:ident, WKUP, $irq:ident) => { | 106 | (RTC, rtc, $block:ident, WKUP, $irq:ident) => { |
| @@ -94,14 +125,22 @@ foreach_interrupt! { | |||
| 94 | 125 | ||
| 95 | #[allow(dead_code)] | 126 | #[allow(dead_code)] |
| 96 | pub(crate) unsafe fn on_wakeup_irq() { | 127 | pub(crate) unsafe fn on_wakeup_irq() { |
| 97 | EXECUTOR.as_mut().unwrap().on_wakeup_irq(); | 128 | if EXECUTOR.is_some() { |
| 129 | trace!("low power: wakeup irq"); | ||
| 130 | EXECUTOR.as_mut().unwrap().on_wakeup_irq(); | ||
| 131 | } | ||
| 98 | } | 132 | } |
| 99 | 133 | ||
| 100 | /// Configure STOP mode with RTC. | 134 | /// Configure STOP mode with RTC. |
| 101 | pub fn stop_with_rtc(rtc: &'static Rtc) { | 135 | pub fn stop_with_rtc(rtc: Rtc) { |
| 102 | unsafe { EXECUTOR.as_mut().unwrap() }.stop_with_rtc(rtc) | 136 | unsafe { EXECUTOR.as_mut().unwrap() }.stop_with_rtc(rtc) |
| 103 | } | 137 | } |
| 104 | 138 | ||
| 139 | /// Reconfigure the RTC, if set. | ||
| 140 | pub fn reconfigure_rtc(f: impl FnOnce(&mut Rtc)) { | ||
| 141 | unsafe { EXECUTOR.as_mut().unwrap() }.reconfigure_rtc(f); | ||
| 142 | } | ||
| 143 | |||
| 105 | /// Get whether the core is ready to enter the given stop mode. | 144 | /// Get whether the core is ready to enter the given stop mode. |
| 106 | /// | 145 | /// |
| 107 | /// This will return false if some peripheral driver is in use that | 146 | /// This will return false if some peripheral driver is in use that |
| @@ -124,10 +163,10 @@ pub enum StopMode { | |||
| 124 | Stop2, | 163 | Stop2, |
| 125 | } | 164 | } |
| 126 | 165 | ||
| 127 | #[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32u0))] | 166 | #[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32wlex, stm32u0))] |
| 128 | use stm32_metapac::pwr::vals::Lpms; | 167 | use stm32_metapac::pwr::vals::Lpms; |
| 129 | 168 | ||
| 130 | #[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32u0))] | 169 | #[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32wlex, stm32u0))] |
| 131 | impl Into<Lpms> for StopMode { | 170 | impl Into<Lpms> for StopMode { |
| 132 | fn into(self) -> Lpms { | 171 | fn into(self) -> Lpms { |
| 133 | match self { | 172 | match self { |
| @@ -177,22 +216,53 @@ impl Executor { | |||
| 177 | } | 216 | } |
| 178 | 217 | ||
| 179 | unsafe fn on_wakeup_irq(&mut self) { | 218 | unsafe fn on_wakeup_irq(&mut self) { |
| 219 | #[cfg(stm32wlex)] | ||
| 220 | { | ||
| 221 | let extscr = crate::pac::PWR.extscr().read(); | ||
| 222 | if extscr.c1stop2f() || extscr.c1stopf() { | ||
| 223 | // when we wake from any stop mode we need to re-initialize the rcc | ||
| 224 | crate::rcc::apply_resume_config(); | ||
| 225 | if extscr.c1stop2f() { | ||
| 226 | // when we wake from STOP2, we need to re-initialize the time driver | ||
| 227 | critical_section::with(|cs| crate::time_driver::init_timer(cs)); | ||
| 228 | // reset the refcounts for STOP2 and STOP1 (initializing the time driver will increment one of them for the timer) | ||
| 229 | // and given that we just woke from STOP2, we can reset them | ||
| 230 | crate::rcc::REFCOUNT_STOP2 = 0; | ||
| 231 | crate::rcc::REFCOUNT_STOP1 = 0; | ||
| 232 | } | ||
| 233 | } | ||
| 234 | } | ||
| 180 | self.time_driver.resume_time(); | 235 | self.time_driver.resume_time(); |
| 181 | trace!("low power: resume"); | 236 | trace!("low power: resume"); |
| 182 | } | 237 | } |
| 183 | 238 | ||
| 184 | pub(self) fn stop_with_rtc(&mut self, rtc: &'static Rtc) { | 239 | pub(self) fn stop_with_rtc(&mut self, rtc: Rtc) { |
| 185 | self.time_driver.set_rtc(rtc); | 240 | self.time_driver.set_rtc(rtc); |
| 186 | 241 | self.time_driver.reconfigure_rtc(|rtc| rtc.enable_wakeup_line()); | |
| 187 | rtc.enable_wakeup_line(); | ||
| 188 | 242 | ||
| 189 | trace!("low power: stop with rtc configured"); | 243 | trace!("low power: stop with rtc configured"); |
| 190 | } | 244 | } |
| 191 | 245 | ||
| 246 | pub(self) fn reconfigure_rtc(&mut self, f: impl FnOnce(&mut Rtc)) { | ||
| 247 | self.time_driver.reconfigure_rtc(f); | ||
| 248 | } | ||
| 249 | |||
| 250 | fn stop1_ok_to_enter(&self) -> bool { | ||
| 251 | #[cfg(stm32wlex)] | ||
| 252 | if self::busy::stop_blocked() { | ||
| 253 | return false; | ||
| 254 | } | ||
| 255 | unsafe { crate::rcc::REFCOUNT_STOP1 == 0 } | ||
| 256 | } | ||
| 257 | |||
| 258 | fn stop2_ok_to_enter(&self) -> bool { | ||
| 259 | self.stop1_ok_to_enter() && unsafe { crate::rcc::REFCOUNT_STOP2 == 0 } | ||
| 260 | } | ||
| 261 | |||
| 192 | fn stop_mode(&self) -> Option<StopMode> { | 262 | fn stop_mode(&self) -> Option<StopMode> { |
| 193 | if unsafe { crate::rcc::REFCOUNT_STOP2 == 0 } && unsafe { crate::rcc::REFCOUNT_STOP1 == 0 } { | 263 | if self.stop2_ok_to_enter() { |
| 194 | Some(StopMode::Stop2) | 264 | Some(StopMode::Stop2) |
| 195 | } else if unsafe { crate::rcc::REFCOUNT_STOP1 == 0 } { | 265 | } else if self.stop1_ok_to_enter() { |
| 196 | Some(StopMode::Stop1) | 266 | Some(StopMode::Stop1) |
| 197 | } else { | 267 | } else { |
| 198 | None | 268 | None |
| @@ -201,7 +271,7 @@ impl Executor { | |||
| 201 | 271 | ||
| 202 | #[allow(unused_variables)] | 272 | #[allow(unused_variables)] |
| 203 | fn configure_stop(&mut self, stop_mode: StopMode) { | 273 | fn configure_stop(&mut self, stop_mode: StopMode) { |
| 204 | #[cfg(any(stm32l4, stm32l5, stm32u5, stm32u0, stm32wba))] | 274 | #[cfg(any(stm32l4, stm32l5, stm32u5, stm32u0, stm32wba, stm32wlex))] |
| 205 | crate::pac::PWR.cr1().modify(|m| m.set_lpms(stop_mode.into())); | 275 | crate::pac::PWR.cr1().modify(|m| m.set_lpms(stop_mode.into())); |
| 206 | #[cfg(stm32h5)] | 276 | #[cfg(stm32h5)] |
| 207 | crate::pac::PWR.pmcr().modify(|v| { | 277 | crate::pac::PWR.pmcr().modify(|v| { |
| @@ -213,6 +283,11 @@ impl Executor { | |||
| 213 | 283 | ||
| 214 | fn configure_pwr(&mut self) { | 284 | fn configure_pwr(&mut self) { |
| 215 | self.scb.clear_sleepdeep(); | 285 | self.scb.clear_sleepdeep(); |
| 286 | // Clear any previous stop flags | ||
| 287 | #[cfg(stm32wlex)] | ||
| 288 | crate::pac::PWR.extscr().modify(|w| { | ||
| 289 | w.set_c1cssf(true); | ||
| 290 | }); | ||
| 216 | 291 | ||
| 217 | compiler_fence(Ordering::SeqCst); | 292 | compiler_fence(Ordering::SeqCst); |
| 218 | 293 | ||
| @@ -266,6 +341,19 @@ impl Executor { | |||
| 266 | executor.inner.poll(); | 341 | executor.inner.poll(); |
| 267 | self.configure_pwr(); | 342 | self.configure_pwr(); |
| 268 | asm!("wfe"); | 343 | asm!("wfe"); |
| 344 | #[cfg(stm32wlex)] | ||
| 345 | { | ||
| 346 | let es = crate::pac::PWR.extscr().read(); | ||
| 347 | match (es.c1stopf(), es.c1stop2f()) { | ||
| 348 | (true, false) => debug!("low power: wake from STOP1"), | ||
| 349 | (false, true) => debug!("low power: wake from STOP2"), | ||
| 350 | (true, true) => debug!("low power: wake from STOP1 and STOP2 ???"), | ||
| 351 | (false, false) => trace!("low power: stop mode not entered"), | ||
| 352 | }; | ||
| 353 | crate::pac::PWR.extscr().modify(|w| { | ||
| 354 | w.set_c1cssf(false); | ||
| 355 | }); | ||
| 356 | } | ||
| 269 | }; | 357 | }; |
| 270 | } | 358 | } |
| 271 | } | 359 | } |
diff --git a/embassy-stm32/src/rcc/l.rs b/embassy-stm32/src/rcc/l.rs index 2e1cbd702..584957c6d 100644 --- a/embassy-stm32/src/rcc/l.rs +++ b/embassy-stm32/src/rcc/l.rs | |||
| @@ -1,3 +1,6 @@ | |||
| 1 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 2 | use core::mem::MaybeUninit; | ||
| 3 | |||
| 1 | #[cfg(any(stm32l0, stm32l1))] | 4 | #[cfg(any(stm32l0, stm32l1))] |
| 2 | pub use crate::pac::pwr::vals::Vos as VoltageScale; | 5 | pub use crate::pac::pwr::vals::Vos as VoltageScale; |
| 3 | use crate::pac::rcc::regs::Cfgr; | 6 | use crate::pac::rcc::regs::Cfgr; |
| @@ -11,6 +14,42 @@ use crate::time::Hertz; | |||
| 11 | /// HSI speed | 14 | /// HSI speed |
| 12 | pub const HSI_FREQ: Hertz = Hertz(16_000_000); | 15 | pub const HSI_FREQ: Hertz = Hertz(16_000_000); |
| 13 | 16 | ||
| 17 | /// Saved RCC Config | ||
| 18 | /// | ||
| 19 | /// Used when exiting STOP2 to re-enable clocks to their last configured state | ||
| 20 | /// for chips that need it. | ||
| 21 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 22 | static mut RESUME_RCC_CONFIG: MaybeUninit<Config> = MaybeUninit::uninit(); | ||
| 23 | |||
| 24 | /// Set the rcc config to be restored when exiting STOP2 | ||
| 25 | /// | ||
| 26 | /// Safety: Sets a mutable global. | ||
| 27 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 28 | pub(crate) unsafe fn set_resume_config(config: Config) { | ||
| 29 | trace!("rcc set_resume_config()"); | ||
| 30 | RESUME_RCC_CONFIG = MaybeUninit::new(config); | ||
| 31 | } | ||
| 32 | |||
| 33 | /// Get the rcc config to be restored when exiting STOP2 | ||
| 34 | /// | ||
| 35 | /// Safety: Reads a mutable global. | ||
| 36 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 37 | pub(crate) unsafe fn get_resume_config() -> Config { | ||
| 38 | *(*core::ptr::addr_of_mut!(RESUME_RCC_CONFIG)).assume_init_ref() | ||
| 39 | } | ||
| 40 | |||
| 41 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 42 | /// Safety: should only be called from low power executable just after resuming from STOP2 | ||
| 43 | pub(crate) unsafe fn apply_resume_config() { | ||
| 44 | trace!("rcc apply_resume_config()"); | ||
| 45 | |||
| 46 | while RCC.cfgr().read().sws() != Sysclk::MSI {} | ||
| 47 | |||
| 48 | let config = get_resume_config(); | ||
| 49 | |||
| 50 | init(config); | ||
| 51 | } | ||
| 52 | |||
| 14 | #[derive(Clone, Copy, Eq, PartialEq)] | 53 | #[derive(Clone, Copy, Eq, PartialEq)] |
| 15 | pub enum HseMode { | 54 | pub enum HseMode { |
| 16 | /// crystal/ceramic oscillator (HSEBYP=0) | 55 | /// crystal/ceramic oscillator (HSEBYP=0) |
| @@ -154,6 +193,10 @@ fn msi_enable(range: MSIRange) { | |||
| 154 | } | 193 | } |
| 155 | 194 | ||
| 156 | pub(crate) unsafe fn init(config: Config) { | 195 | pub(crate) unsafe fn init(config: Config) { |
| 196 | // save the rcc config because if we enter stop 2 we need to re-apply it on wakeup | ||
| 197 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 198 | set_resume_config(config); | ||
| 199 | |||
| 157 | // Switch to MSI to prevent problems with PLL configuration. | 200 | // Switch to MSI to prevent problems with PLL configuration. |
| 158 | if !RCC.cr().read().msion() { | 201 | if !RCC.cr().read().msion() { |
| 159 | // Turn on MSI and configure it to 4MHz. | 202 | // Turn on MSI and configure it to 4MHz. |
diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index addfca3c3..c817dd4b7 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs | |||
| @@ -390,7 +390,7 @@ pub fn disable<T: RccPeripheral>() { | |||
| 390 | /// | 390 | /// |
| 391 | /// This should only be called after `init`. | 391 | /// This should only be called after `init`. |
| 392 | #[cfg(not(feature = "_dual-core"))] | 392 | #[cfg(not(feature = "_dual-core"))] |
| 393 | pub fn reinit<'a>(config: Config, _rcc: &'a mut crate::Peri<'a, crate::peripherals::RCC>) { | 393 | pub fn reinit(config: Config, _rcc: &'_ mut crate::Peri<'_, crate::peripherals::RCC>) { |
| 394 | critical_section::with(|cs| init_rcc(cs, config)) | 394 | critical_section::with(|cs| init_rcc(cs, config)) |
| 395 | } | 395 | } |
| 396 | 396 | ||
diff --git a/embassy-stm32/src/rtc/low_power.rs b/embassy-stm32/src/rtc/low_power.rs index 999f24714..e09d5afb0 100644 --- a/embassy-stm32/src/rtc/low_power.rs +++ b/embassy-stm32/src/rtc/low_power.rs | |||
| @@ -68,7 +68,7 @@ pub(crate) enum WakeupPrescaler { | |||
| 68 | } | 68 | } |
| 69 | 69 | ||
| 70 | #[cfg(any( | 70 | #[cfg(any( |
| 71 | stm32f4, stm32l0, stm32g4, stm32l4, stm32l5, stm32wb, stm32h5, stm32g0, stm32u5, stm32u0, stm32wba | 71 | stm32f4, stm32l0, stm32g4, stm32l4, stm32l5, stm32wb, stm32h5, stm32g0, stm32u5, stm32u0, stm32wba, stm32wlex |
| 72 | ))] | 72 | ))] |
| 73 | impl From<WakeupPrescaler> for crate::pac::rtc::vals::Wucksel { | 73 | impl From<WakeupPrescaler> for crate::pac::rtc::vals::Wucksel { |
| 74 | fn from(val: WakeupPrescaler) -> Self { | 74 | fn from(val: WakeupPrescaler) -> Self { |
| @@ -84,7 +84,7 @@ impl From<WakeupPrescaler> for crate::pac::rtc::vals::Wucksel { | |||
| 84 | } | 84 | } |
| 85 | 85 | ||
| 86 | #[cfg(any( | 86 | #[cfg(any( |
| 87 | stm32f4, stm32l0, stm32g4, stm32l4, stm32l5, stm32wb, stm32h5, stm32g0, stm32u5, stm32u0, stm32wba | 87 | stm32f4, stm32l0, stm32g4, stm32l4, stm32l5, stm32wb, stm32h5, stm32g0, stm32u5, stm32u0, stm32wba, stm32wlex |
| 88 | ))] | 88 | ))] |
| 89 | impl From<crate::pac::rtc::vals::Wucksel> for WakeupPrescaler { | 89 | impl From<crate::pac::rtc::vals::Wucksel> for WakeupPrescaler { |
| 90 | fn from(val: crate::pac::rtc::vals::Wucksel) -> Self { | 90 | fn from(val: crate::pac::rtc::vals::Wucksel) -> Self { |
| @@ -127,7 +127,7 @@ impl Rtc { | |||
| 127 | /// start the wakeup alarm and with a duration that is as close to but less than | 127 | /// start the wakeup alarm and with a duration that is as close to but less than |
| 128 | /// the requested duration, and record the instant the wakeup alarm was started | 128 | /// the requested duration, and record the instant the wakeup alarm was started |
| 129 | pub(crate) fn start_wakeup_alarm( | 129 | pub(crate) fn start_wakeup_alarm( |
| 130 | &self, | 130 | &mut self, |
| 131 | requested_duration: embassy_time::Duration, | 131 | requested_duration: embassy_time::Duration, |
| 132 | cs: critical_section::CriticalSection, | 132 | cs: critical_section::CriticalSection, |
| 133 | ) { | 133 | ) { |
| @@ -179,7 +179,10 @@ impl Rtc { | |||
| 179 | 179 | ||
| 180 | /// stop the wakeup alarm and return the time elapsed since `start_wakeup_alarm` | 180 | /// stop the wakeup alarm and return the time elapsed since `start_wakeup_alarm` |
| 181 | /// was called, otherwise none | 181 | /// was called, otherwise none |
| 182 | pub(crate) fn stop_wakeup_alarm(&self, cs: critical_section::CriticalSection) -> Option<embassy_time::Duration> { | 182 | pub(crate) fn stop_wakeup_alarm( |
| 183 | &mut self, | ||
| 184 | cs: critical_section::CriticalSection, | ||
| 185 | ) -> Option<embassy_time::Duration> { | ||
| 183 | let instant = self.instant().unwrap(); | 186 | let instant = self.instant().unwrap(); |
| 184 | if RTC::regs().cr().read().wute() { | 187 | if RTC::regs().cr().read().wute() { |
| 185 | trace!("rtc: stop wakeup alarm at {}", instant); | 188 | trace!("rtc: stop wakeup alarm at {}", instant); |
diff --git a/embassy-stm32/src/rtc/v2.rs b/embassy-stm32/src/rtc/v2.rs index 23f6ccb0c..8ac022536 100644 --- a/embassy-stm32/src/rtc/v2.rs +++ b/embassy-stm32/src/rtc/v2.rs | |||
| @@ -93,7 +93,7 @@ impl super::Rtc { | |||
| 93 | }) | 93 | }) |
| 94 | } | 94 | } |
| 95 | 95 | ||
| 96 | pub(super) fn write<F, R>(&self, init_mode: bool, f: F) -> R | 96 | pub(super) fn write<F, R>(&mut self, init_mode: bool, f: F) -> R |
| 97 | where | 97 | where |
| 98 | F: FnOnce(crate::pac::rtc::Rtc) -> R, | 98 | F: FnOnce(crate::pac::rtc::Rtc) -> R, |
| 99 | { | 99 | { |
diff --git a/embassy-stm32/src/rtc/v3.rs b/embassy-stm32/src/rtc/v3.rs index 01da5d70a..f7ebea73e 100644 --- a/embassy-stm32/src/rtc/v3.rs +++ b/embassy-stm32/src/rtc/v3.rs | |||
| @@ -95,7 +95,7 @@ impl super::Rtc { | |||
| 95 | }) | 95 | }) |
| 96 | } | 96 | } |
| 97 | 97 | ||
| 98 | pub(super) fn write<F, R>(&self, init_mode: bool, f: F) -> R | 98 | pub(super) fn write<F, R>(&mut self, init_mode: bool, f: F) -> R |
| 99 | where | 99 | where |
| 100 | F: FnOnce(crate::pac::rtc::Rtc) -> R, | 100 | F: FnOnce(crate::pac::rtc::Rtc) -> R, |
| 101 | { | 101 | { |
| @@ -131,7 +131,7 @@ impl SealedInstance for crate::peripherals::RTC { | |||
| 131 | 131 | ||
| 132 | #[cfg(feature = "low-power")] | 132 | #[cfg(feature = "low-power")] |
| 133 | cfg_if::cfg_if!( | 133 | cfg_if::cfg_if!( |
| 134 | if #[cfg(stm32g4)] { | 134 | if #[cfg(any(stm32g4, stm32wlex))] { |
| 135 | const EXTI_WAKEUP_LINE: usize = 20; | 135 | const EXTI_WAKEUP_LINE: usize = 20; |
| 136 | } else if #[cfg(stm32g0)] { | 136 | } else if #[cfg(stm32g0)] { |
| 137 | const EXTI_WAKEUP_LINE: usize = 19; | 137 | const EXTI_WAKEUP_LINE: usize = 19; |
| @@ -142,7 +142,7 @@ impl SealedInstance for crate::peripherals::RTC { | |||
| 142 | 142 | ||
| 143 | #[cfg(feature = "low-power")] | 143 | #[cfg(feature = "low-power")] |
| 144 | cfg_if::cfg_if!( | 144 | cfg_if::cfg_if!( |
| 145 | if #[cfg(stm32g4)] { | 145 | if #[cfg(any(stm32g4, stm32wlex))] { |
| 146 | type WakeupInterrupt = crate::interrupt::typelevel::RTC_WKUP; | 146 | type WakeupInterrupt = crate::interrupt::typelevel::RTC_WKUP; |
| 147 | } else if #[cfg(any(stm32g0, stm32u0))] { | 147 | } else if #[cfg(any(stm32g0, stm32u0))] { |
| 148 | type WakeupInterrupt = crate::interrupt::typelevel::RTC_TAMP; | 148 | type WakeupInterrupt = crate::interrupt::typelevel::RTC_TAMP; |
diff --git a/embassy-stm32/src/sdmmc/mod.rs b/embassy-stm32/src/sdmmc/mod.rs index 408d1b764..e05131040 100644 --- a/embassy-stm32/src/sdmmc/mod.rs +++ b/embassy-stm32/src/sdmmc/mod.rs | |||
| @@ -1032,12 +1032,13 @@ impl<'d, T: Instance> Sdmmc<'d, T> { | |||
| 1032 | 1032 | ||
| 1033 | /// Wait for a previously started datapath transfer to complete from an interrupt. | 1033 | /// Wait for a previously started datapath transfer to complete from an interrupt. |
| 1034 | #[inline] | 1034 | #[inline] |
| 1035 | #[allow(unused)] | ||
| 1035 | async fn complete_datapath_transfer(block: bool) -> Result<(), Error> { | 1036 | async fn complete_datapath_transfer(block: bool) -> Result<(), Error> { |
| 1036 | let regs = T::regs(); | ||
| 1037 | |||
| 1038 | let res = poll_fn(|cx| { | 1037 | let res = poll_fn(|cx| { |
| 1038 | // Compiler might not be sufficiently constrained here | ||
| 1039 | // https://github.com/embassy-rs/embassy/issues/4723 | ||
| 1039 | T::state().register(cx.waker()); | 1040 | T::state().register(cx.waker()); |
| 1040 | let status = regs.star().read(); | 1041 | let status = T::regs().star().read(); |
| 1041 | 1042 | ||
| 1042 | if status.dcrcfail() { | 1043 | if status.dcrcfail() { |
| 1043 | return Poll::Ready(Err(Error::Crc)); | 1044 | return Poll::Ready(Err(Error::Crc)); |
| @@ -1052,10 +1053,13 @@ impl<'d, T: Instance> Sdmmc<'d, T> { | |||
| 1052 | if status.stbiterr() { | 1053 | if status.stbiterr() { |
| 1053 | return Poll::Ready(Err(Error::StBitErr)); | 1054 | return Poll::Ready(Err(Error::StBitErr)); |
| 1054 | } | 1055 | } |
| 1056 | #[cfg(sdmmc_v1)] | ||
| 1055 | let done = match block { | 1057 | let done = match block { |
| 1056 | true => status.dbckend(), | 1058 | true => status.dbckend(), |
| 1057 | false => status.dataend(), | 1059 | false => status.dataend(), |
| 1058 | }; | 1060 | }; |
| 1061 | #[cfg(sdmmc_v2)] | ||
| 1062 | let done = status.dataend(); | ||
| 1059 | if done { | 1063 | if done { |
| 1060 | return Poll::Ready(Ok(())); | 1064 | return Poll::Ready(Ok(())); |
| 1061 | } | 1065 | } |
diff --git a/embassy-stm32/src/time_driver.rs b/embassy-stm32/src/time_driver.rs index 74b10a183..4956d1f68 100644 --- a/embassy-stm32/src/time_driver.rs +++ b/embassy-stm32/src/time_driver.rs | |||
| @@ -1,6 +1,8 @@ | |||
| 1 | #![allow(non_snake_case)] | 1 | #![allow(non_snake_case)] |
| 2 | 2 | ||
| 3 | use core::cell::{Cell, RefCell}; | 3 | use core::cell::{Cell, RefCell}; |
| 4 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 5 | use core::sync::atomic::AtomicU16; | ||
| 4 | use core::sync::atomic::{AtomicU32, Ordering, compiler_fence}; | 6 | use core::sync::atomic::{AtomicU32, Ordering, compiler_fence}; |
| 5 | 7 | ||
| 6 | use critical_section::CriticalSection; | 8 | use critical_section::CriticalSection; |
| @@ -213,7 +215,10 @@ pub(crate) struct RtcDriver { | |||
| 213 | period: AtomicU32, | 215 | period: AtomicU32, |
| 214 | alarm: Mutex<CriticalSectionRawMutex, AlarmState>, | 216 | alarm: Mutex<CriticalSectionRawMutex, AlarmState>, |
| 215 | #[cfg(feature = "low-power")] | 217 | #[cfg(feature = "low-power")] |
| 216 | rtc: Mutex<CriticalSectionRawMutex, Cell<Option<&'static Rtc>>>, | 218 | rtc: Mutex<CriticalSectionRawMutex, RefCell<Option<Rtc>>>, |
| 219 | /// Saved count for the timer (its value is lost when entering STOP2) | ||
| 220 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 221 | saved_count: AtomicU16, | ||
| 217 | queue: Mutex<CriticalSectionRawMutex, RefCell<Queue>>, | 222 | queue: Mutex<CriticalSectionRawMutex, RefCell<Queue>>, |
| 218 | } | 223 | } |
| 219 | 224 | ||
| @@ -221,12 +226,16 @@ embassy_time_driver::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver { | |||
| 221 | period: AtomicU32::new(0), | 226 | period: AtomicU32::new(0), |
| 222 | alarm: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), | 227 | alarm: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), |
| 223 | #[cfg(feature = "low-power")] | 228 | #[cfg(feature = "low-power")] |
| 224 | rtc: Mutex::const_new(CriticalSectionRawMutex::new(), Cell::new(None)), | 229 | rtc: Mutex::const_new(CriticalSectionRawMutex::new(), RefCell::new(None)), |
| 230 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 231 | saved_count: AtomicU16::new(0), | ||
| 225 | queue: Mutex::new(RefCell::new(Queue::new())) | 232 | queue: Mutex::new(RefCell::new(Queue::new())) |
| 226 | }); | 233 | }); |
| 227 | 234 | ||
| 228 | impl RtcDriver { | 235 | impl RtcDriver { |
| 229 | fn init(&'static self, cs: critical_section::CriticalSection) { | 236 | /// initialize the timer, but don't start it. Used for chips like stm32wle5 |
| 237 | /// for low power where the timer config is lost in STOP2. | ||
| 238 | fn init_timer(&'static self, cs: critical_section::CriticalSection) { | ||
| 230 | let r = regs_gp16(); | 239 | let r = regs_gp16(); |
| 231 | 240 | ||
| 232 | rcc::enable_and_reset_with_cs::<T>(cs); | 241 | rcc::enable_and_reset_with_cs::<T>(cs); |
| @@ -259,10 +268,16 @@ impl RtcDriver { | |||
| 259 | w.set_ccie(0, true); | 268 | w.set_ccie(0, true); |
| 260 | }); | 269 | }); |
| 261 | 270 | ||
| 271 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 272 | r.cnt().write(|w| w.set_cnt(self.saved_count.load(Ordering::SeqCst))); | ||
| 273 | |||
| 262 | <T as GeneralInstance1Channel>::CaptureCompareInterrupt::unpend(); | 274 | <T as GeneralInstance1Channel>::CaptureCompareInterrupt::unpend(); |
| 263 | unsafe { <T as GeneralInstance1Channel>::CaptureCompareInterrupt::enable() }; | 275 | unsafe { <T as GeneralInstance1Channel>::CaptureCompareInterrupt::enable() }; |
| 276 | } | ||
| 264 | 277 | ||
| 265 | r.cr1().modify(|w| w.set_cen(true)); | 278 | fn init(&'static self, cs: CriticalSection) { |
| 279 | self.init_timer(cs); | ||
| 280 | regs_gp16().cr1().modify(|w| w.set_cen(true)); | ||
| 266 | } | 281 | } |
| 267 | 282 | ||
| 268 | fn on_interrupt(&self) { | 283 | fn on_interrupt(&self) { |
| @@ -379,7 +394,7 @@ impl RtcDriver { | |||
| 379 | #[cfg(feature = "low-power")] | 394 | #[cfg(feature = "low-power")] |
| 380 | /// Stop the wakeup alarm, if enabled, and add the appropriate offset | 395 | /// Stop the wakeup alarm, if enabled, and add the appropriate offset |
| 381 | fn stop_wakeup_alarm(&self, cs: CriticalSection) { | 396 | fn stop_wakeup_alarm(&self, cs: CriticalSection) { |
| 382 | if let Some(offset) = self.rtc.borrow(cs).get().unwrap().stop_wakeup_alarm(cs) { | 397 | if let Some(offset) = self.rtc.borrow(cs).borrow_mut().as_mut().unwrap().stop_wakeup_alarm(cs) { |
| 383 | self.add_time(offset, cs); | 398 | self.add_time(offset, cs); |
| 384 | } | 399 | } |
| 385 | } | 400 | } |
| @@ -389,7 +404,7 @@ impl RtcDriver { | |||
| 389 | */ | 404 | */ |
| 390 | #[cfg(feature = "low-power")] | 405 | #[cfg(feature = "low-power")] |
| 391 | /// Set the rtc but panic if it's already been set | 406 | /// Set the rtc but panic if it's already been set |
| 392 | pub(crate) fn set_rtc(&self, rtc: &'static Rtc) { | 407 | pub(crate) fn set_rtc(&self, mut rtc: Rtc) { |
| 393 | critical_section::with(|cs| { | 408 | critical_section::with(|cs| { |
| 394 | rtc.stop_wakeup_alarm(cs); | 409 | rtc.stop_wakeup_alarm(cs); |
| 395 | 410 | ||
| @@ -398,6 +413,12 @@ impl RtcDriver { | |||
| 398 | } | 413 | } |
| 399 | 414 | ||
| 400 | #[cfg(feature = "low-power")] | 415 | #[cfg(feature = "low-power")] |
| 416 | /// Set the rtc but panic if it's already been set | ||
| 417 | pub(crate) fn reconfigure_rtc(&self, f: impl FnOnce(&mut Rtc)) { | ||
| 418 | critical_section::with(|cs| f(self.rtc.borrow(cs).borrow_mut().as_mut().unwrap())); | ||
| 419 | } | ||
| 420 | |||
| 421 | #[cfg(feature = "low-power")] | ||
| 401 | /// The minimum pause time beyond which the executor will enter a low-power state. | 422 | /// The minimum pause time beyond which the executor will enter a low-power state. |
| 402 | pub(crate) const MIN_STOP_PAUSE: embassy_time::Duration = embassy_time::Duration::from_millis(250); | 423 | pub(crate) const MIN_STOP_PAUSE: embassy_time::Duration = embassy_time::Duration::from_millis(250); |
| 403 | 424 | ||
| @@ -414,16 +435,24 @@ impl RtcDriver { | |||
| 414 | 435 | ||
| 415 | let time_until_next_alarm = self.time_until_next_alarm(cs); | 436 | let time_until_next_alarm = self.time_until_next_alarm(cs); |
| 416 | if time_until_next_alarm < Self::MIN_STOP_PAUSE { | 437 | if time_until_next_alarm < Self::MIN_STOP_PAUSE { |
| 438 | trace!( | ||
| 439 | "time_until_next_alarm < Self::MIN_STOP_PAUSE ({})", | ||
| 440 | time_until_next_alarm | ||
| 441 | ); | ||
| 417 | Err(()) | 442 | Err(()) |
| 418 | } else { | 443 | } else { |
| 419 | self.rtc | 444 | self.rtc |
| 420 | .borrow(cs) | 445 | .borrow(cs) |
| 421 | .get() | 446 | .borrow_mut() |
| 447 | .as_mut() | ||
| 422 | .unwrap() | 448 | .unwrap() |
| 423 | .start_wakeup_alarm(time_until_next_alarm, cs); | 449 | .start_wakeup_alarm(time_until_next_alarm, cs); |
| 424 | 450 | ||
| 425 | regs_gp16().cr1().modify(|w| w.set_cen(false)); | 451 | regs_gp16().cr1().modify(|w| w.set_cen(false)); |
| 426 | 452 | // save the count for the timer as its lost in STOP2 for stm32wlex | |
| 453 | #[cfg(stm32wlex)] | ||
| 454 | self.saved_count | ||
| 455 | .store(regs_gp16().cnt().read().cnt() as u16, Ordering::SeqCst); | ||
| 427 | Ok(()) | 456 | Ok(()) |
| 428 | } | 457 | } |
| 429 | }) | 458 | }) |
| @@ -521,3 +550,8 @@ pub(crate) fn get_driver() -> &'static RtcDriver { | |||
| 521 | pub(crate) fn init(cs: CriticalSection) { | 550 | pub(crate) fn init(cs: CriticalSection) { |
| 522 | DRIVER.init(cs) | 551 | DRIVER.init(cs) |
| 523 | } | 552 | } |
| 553 | |||
| 554 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 555 | pub(crate) fn init_timer(cs: CriticalSection) { | ||
| 556 | DRIVER.init_timer(cs) | ||
| 557 | } | ||
diff --git a/embassy-stm32/src/vrefbuf/mod.rs b/embassy-stm32/src/vrefbuf/mod.rs index b061306a0..43dd9c800 100644 --- a/embassy-stm32/src/vrefbuf/mod.rs +++ b/embassy-stm32/src/vrefbuf/mod.rs | |||
| @@ -14,10 +14,10 @@ pub struct VoltageReferenceBuffer<'d, T: Instance> { | |||
| 14 | #[cfg(rcc_wba)] | 14 | #[cfg(rcc_wba)] |
| 15 | fn get_refbuf_trim(voltage_scale: Vrs) -> usize { | 15 | fn get_refbuf_trim(voltage_scale: Vrs) -> usize { |
| 16 | match voltage_scale { | 16 | match voltage_scale { |
| 17 | Vrs::VREF0 => 0x0BFA_07A8usize, | 17 | Vrs::VREF0 => 0x0BFA_07ABusize, |
| 18 | Vrs::VREF1 => 0x0BFA_07A9usize, | 18 | Vrs::VREF1 => 0x0BFA_07AAusize, |
| 19 | Vrs::VREF2 => 0x0BFA_07AAusize, | 19 | Vrs::VREF2 => 0x0BFA_07A9usize, |
| 20 | Vrs::VREF3 => 0x0BFA_07ABusize, | 20 | Vrs::VREF3 => 0x0BFA_07A8usize, |
| 21 | _ => panic!("Incorrect Vrs setting!"), | 21 | _ => panic!("Incorrect Vrs setting!"), |
| 22 | } | 22 | } |
| 23 | } | 23 | } |
diff --git a/examples/rp/src/bin/pio_onewire.rs b/examples/rp/src/bin/pio_onewire.rs index 102f13c45..6432edb8a 100644 --- a/examples/rp/src/bin/pio_onewire.rs +++ b/examples/rp/src/bin/pio_onewire.rs | |||
| @@ -61,7 +61,7 @@ async fn main(_spawner: Spawner) { | |||
| 61 | let mut data = [0; 9]; | 61 | let mut data = [0; 9]; |
| 62 | onewire.read_bytes(&mut data).await; | 62 | onewire.read_bytes(&mut data).await; |
| 63 | if crc8(&data) == 0 { | 63 | if crc8(&data) == 0 { |
| 64 | let temp = ((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.; | 64 | let temp = ((data[1] as i16) << 8 | data[0] as i16) as f32 / 16.; |
| 65 | info!("Read device {:x}: {} deg C", device, temp); | 65 | info!("Read device {:x}: {} deg C", device, temp); |
| 66 | } else { | 66 | } else { |
| 67 | warn!("Reading device {:x} failed", device); | 67 | warn!("Reading device {:x} failed", device); |
diff --git a/examples/rp/src/bin/pio_onewire_parasite.rs b/examples/rp/src/bin/pio_onewire_parasite.rs index fd076dee0..78fb94b18 100644 --- a/examples/rp/src/bin/pio_onewire_parasite.rs +++ b/examples/rp/src/bin/pio_onewire_parasite.rs | |||
| @@ -63,7 +63,7 @@ async fn main(_spawner: Spawner) { | |||
| 63 | let mut data = [0; 9]; | 63 | let mut data = [0; 9]; |
| 64 | onewire.read_bytes(&mut data).await; | 64 | onewire.read_bytes(&mut data).await; |
| 65 | if crc8(&data) == 0 { | 65 | if crc8(&data) == 0 { |
| 66 | let temp = ((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.; | 66 | let temp = ((data[1] as i16) << 8 | data[0] as i16) as f32 / 16.; |
| 67 | info!("Read device {:x}: {} deg C", device, temp); | 67 | info!("Read device {:x}: {} deg C", device, temp); |
| 68 | } else { | 68 | } else { |
| 69 | warn!("Reading device {:x} failed. {:02x}", device, data); | 69 | warn!("Reading device {:x} failed. {:02x}", device, data); |
diff --git a/examples/rp235x/src/bin/pio_onewire.rs b/examples/rp235x/src/bin/pio_onewire.rs index 102f13c45..6432edb8a 100644 --- a/examples/rp235x/src/bin/pio_onewire.rs +++ b/examples/rp235x/src/bin/pio_onewire.rs | |||
| @@ -61,7 +61,7 @@ async fn main(_spawner: Spawner) { | |||
| 61 | let mut data = [0; 9]; | 61 | let mut data = [0; 9]; |
| 62 | onewire.read_bytes(&mut data).await; | 62 | onewire.read_bytes(&mut data).await; |
| 63 | if crc8(&data) == 0 { | 63 | if crc8(&data) == 0 { |
| 64 | let temp = ((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.; | 64 | let temp = ((data[1] as i16) << 8 | data[0] as i16) as f32 / 16.; |
| 65 | info!("Read device {:x}: {} deg C", device, temp); | 65 | info!("Read device {:x}: {} deg C", device, temp); |
| 66 | } else { | 66 | } else { |
| 67 | warn!("Reading device {:x} failed", device); | 67 | warn!("Reading device {:x} failed", device); |
diff --git a/examples/rp235x/src/bin/pio_onewire_parasite.rs b/examples/rp235x/src/bin/pio_onewire_parasite.rs index fd076dee0..78fb94b18 100644 --- a/examples/rp235x/src/bin/pio_onewire_parasite.rs +++ b/examples/rp235x/src/bin/pio_onewire_parasite.rs | |||
| @@ -63,7 +63,7 @@ async fn main(_spawner: Spawner) { | |||
| 63 | let mut data = [0; 9]; | 63 | let mut data = [0; 9]; |
| 64 | onewire.read_bytes(&mut data).await; | 64 | onewire.read_bytes(&mut data).await; |
| 65 | if crc8(&data) == 0 { | 65 | if crc8(&data) == 0 { |
| 66 | let temp = ((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.; | 66 | let temp = ((data[1] as i16) << 8 | data[0] as i16) as f32 / 16.; |
| 67 | info!("Read device {:x}: {} deg C", device, temp); | 67 | info!("Read device {:x}: {} deg C", device, temp); |
| 68 | } else { | 68 | } else { |
| 69 | warn!("Reading device {:x} failed. {:02x}", device, data); | 69 | warn!("Reading device {:x} failed. {:02x}", device, data); |
diff --git a/examples/stm32f4/src/bin/adc_dma.rs b/examples/stm32f4/src/bin/adc_dma.rs index c24f01753..f8da91336 100644 --- a/examples/stm32f4/src/bin/adc_dma.rs +++ b/examples/stm32f4/src/bin/adc_dma.rs | |||
| @@ -4,7 +4,7 @@ use cortex_m::singleton; | |||
| 4 | use defmt::*; | 4 | use defmt::*; |
| 5 | use embassy_executor::Spawner; | 5 | use embassy_executor::Spawner; |
| 6 | use embassy_stm32::Peripherals; | 6 | use embassy_stm32::Peripherals; |
| 7 | use embassy_stm32::adc::{Adc, RingBufferedAdc, SampleTime, Sequence}; | 7 | use embassy_stm32::adc::{Adc, AdcChannel, RingBufferedAdc, SampleTime}; |
| 8 | use embassy_time::Instant; | 8 | use embassy_time::Instant; |
| 9 | use {defmt_rtt as _, panic_probe as _}; | 9 | use {defmt_rtt as _, panic_probe as _}; |
| 10 | 10 | ||
| @@ -15,7 +15,7 @@ async fn main(spawner: Spawner) { | |||
| 15 | } | 15 | } |
| 16 | 16 | ||
| 17 | #[embassy_executor::task] | 17 | #[embassy_executor::task] |
| 18 | async fn adc_task(mut p: Peripherals) { | 18 | async fn adc_task(p: Peripherals) { |
| 19 | const ADC_BUF_SIZE: usize = 1024; | 19 | const ADC_BUF_SIZE: usize = 1024; |
| 20 | let adc_data: &mut [u16; ADC_BUF_SIZE] = singleton!(ADCDAT : [u16; ADC_BUF_SIZE] = [0u16; ADC_BUF_SIZE]).unwrap(); | 20 | let adc_data: &mut [u16; ADC_BUF_SIZE] = singleton!(ADCDAT : [u16; ADC_BUF_SIZE] = [0u16; ADC_BUF_SIZE]).unwrap(); |
| 21 | let adc_data2: &mut [u16; ADC_BUF_SIZE] = singleton!(ADCDAT2 : [u16; ADC_BUF_SIZE] = [0u16; ADC_BUF_SIZE]).unwrap(); | 21 | let adc_data2: &mut [u16; ADC_BUF_SIZE] = singleton!(ADCDAT2 : [u16; ADC_BUF_SIZE] = [0u16; ADC_BUF_SIZE]).unwrap(); |
| @@ -23,13 +23,24 @@ async fn adc_task(mut p: Peripherals) { | |||
| 23 | let adc = Adc::new(p.ADC1); | 23 | let adc = Adc::new(p.ADC1); |
| 24 | let adc2 = Adc::new(p.ADC2); | 24 | let adc2 = Adc::new(p.ADC2); |
| 25 | 25 | ||
| 26 | let mut adc: RingBufferedAdc<embassy_stm32::peripherals::ADC1> = adc.into_ring_buffered(p.DMA2_CH0, adc_data); | 26 | let mut adc: RingBufferedAdc<embassy_stm32::peripherals::ADC1> = adc.into_ring_buffered( |
| 27 | let mut adc2: RingBufferedAdc<embassy_stm32::peripherals::ADC2> = adc2.into_ring_buffered(p.DMA2_CH2, adc_data2); | 27 | p.DMA2_CH0, |
| 28 | 28 | adc_data, | |
| 29 | adc.set_sample_sequence(Sequence::One, &mut p.PA0, SampleTime::CYCLES112); | 29 | [ |
| 30 | adc.set_sample_sequence(Sequence::Two, &mut p.PA2, SampleTime::CYCLES112); | 30 | (&mut p.PA0.degrade_adc(), SampleTime::CYCLES112), |
| 31 | adc2.set_sample_sequence(Sequence::One, &mut p.PA1, SampleTime::CYCLES112); | 31 | (&mut p.PA2.degrade_adc(), SampleTime::CYCLES112), |
| 32 | adc2.set_sample_sequence(Sequence::Two, &mut p.PA3, SampleTime::CYCLES112); | 32 | ] |
| 33 | .into_iter(), | ||
| 34 | ); | ||
| 35 | let mut adc2: RingBufferedAdc<embassy_stm32::peripherals::ADC2> = adc2.into_ring_buffered( | ||
| 36 | p.DMA2_CH2, | ||
| 37 | adc_data2, | ||
| 38 | [ | ||
| 39 | (&mut p.PA1.degrade_adc(), SampleTime::CYCLES112), | ||
| 40 | (&mut p.PA3.degrade_adc(), SampleTime::CYCLES112), | ||
| 41 | ] | ||
| 42 | .into_iter(), | ||
| 43 | ); | ||
| 33 | 44 | ||
| 34 | // Note that overrun is a big consideration in this implementation. Whatever task is running the adc.read() calls absolutely must circle back around | 45 | // Note that overrun is a big consideration in this implementation. Whatever task is running the adc.read() calls absolutely must circle back around |
| 35 | // to the adc.read() call before the DMA buffer is wrapped around > 1 time. At this point, the overrun is so significant that the context of | 46 | // to the adc.read() call before the DMA buffer is wrapped around > 1 time. At this point, the overrun is so significant that the context of |
diff --git a/examples/stm32g0/src/bin/onewire_ds18b20.rs b/examples/stm32g0/src/bin/onewire_ds18b20.rs index 62f8711a6..43ecca8c4 100644 --- a/examples/stm32g0/src/bin/onewire_ds18b20.rs +++ b/examples/stm32g0/src/bin/onewire_ds18b20.rs | |||
| @@ -267,7 +267,7 @@ where | |||
| 267 | } | 267 | } |
| 268 | 268 | ||
| 269 | match Self::crc8(&data) == 0 { | 269 | match Self::crc8(&data) == 0 { |
| 270 | true => Ok(((data[1] as u16) << 8 | data[0] as u16) as f32 / 16.), | 270 | true => Ok(((data[1] as i16) << 8 | data[0] as i16) as f32 / 16.), |
| 271 | false => Err(()), | 271 | false => Err(()), |
| 272 | } | 272 | } |
| 273 | } | 273 | } |
diff --git a/examples/stm32h5/src/bin/stop.rs b/examples/stm32h5/src/bin/stop.rs index 3c4f49f64..2026d8f99 100644 --- a/examples/stm32h5/src/bin/stop.rs +++ b/examples/stm32h5/src/bin/stop.rs | |||
| @@ -12,7 +12,6 @@ use embassy_stm32::rcc::{HSIPrescaler, LsConfig}; | |||
| 12 | use embassy_stm32::rtc::{Rtc, RtcConfig}; | 12 | use embassy_stm32::rtc::{Rtc, RtcConfig}; |
| 13 | use embassy_stm32::{Config, Peri}; | 13 | use embassy_stm32::{Config, Peri}; |
| 14 | use embassy_time::Timer; | 14 | use embassy_time::Timer; |
| 15 | use static_cell::StaticCell; | ||
| 16 | use {defmt_rtt as _, panic_probe as _}; | 15 | use {defmt_rtt as _, panic_probe as _}; |
| 17 | 16 | ||
| 18 | #[cortex_m_rt::entry] | 17 | #[cortex_m_rt::entry] |
| @@ -39,8 +38,6 @@ async fn async_main(spawner: Spawner) { | |||
| 39 | 38 | ||
| 40 | // give the RTC to the executor... | 39 | // give the RTC to the executor... |
| 41 | let rtc = Rtc::new(p.RTC, RtcConfig::default()); | 40 | let rtc = Rtc::new(p.RTC, RtcConfig::default()); |
| 42 | static RTC: StaticCell<Rtc> = StaticCell::new(); | ||
| 43 | let rtc = RTC.init(rtc); | ||
| 44 | embassy_stm32::low_power::stop_with_rtc(rtc); | 41 | embassy_stm32::low_power::stop_with_rtc(rtc); |
| 45 | 42 | ||
| 46 | spawner.spawn(unwrap!(blinky(p.PB4.into()))); | 43 | spawner.spawn(unwrap!(blinky(p.PB4.into()))); |
diff --git a/examples/stm32l4/src/bin/adc_dma.rs b/examples/stm32l4/src/bin/adc_dma.rs new file mode 100644 index 000000000..7a9200edd --- /dev/null +++ b/examples/stm32l4/src/bin/adc_dma.rs | |||
| @@ -0,0 +1,51 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use defmt::*; | ||
| 5 | use embassy_executor::Spawner; | ||
| 6 | use embassy_stm32::Config; | ||
| 7 | use embassy_stm32::adc::{Adc, AdcChannel, SampleTime}; | ||
| 8 | use {defmt_rtt as _, panic_probe as _}; | ||
| 9 | |||
| 10 | const DMA_BUF_LEN: usize = 512; | ||
| 11 | |||
| 12 | #[embassy_executor::main] | ||
| 13 | async fn main(_spawner: Spawner) { | ||
| 14 | info!("Hello World!"); | ||
| 15 | |||
| 16 | let mut config = Config::default(); | ||
| 17 | { | ||
| 18 | use embassy_stm32::rcc::*; | ||
| 19 | config.rcc.mux.adcsel = mux::Adcsel::SYS; | ||
| 20 | } | ||
| 21 | let p = embassy_stm32::init(config); | ||
| 22 | |||
| 23 | let mut adc = Adc::new(p.ADC1); | ||
| 24 | let mut adc_pin0 = p.PA0.degrade_adc(); | ||
| 25 | let mut adc_pin1 = p.PA1.degrade_adc(); | ||
| 26 | let mut adc_dma_buf = [0u16; DMA_BUF_LEN]; | ||
| 27 | let mut measurements = [0u16; DMA_BUF_LEN / 2]; | ||
| 28 | let mut ring_buffered_adc = adc.into_ring_buffered( | ||
| 29 | p.DMA1_CH1, | ||
| 30 | &mut adc_dma_buf, | ||
| 31 | [ | ||
| 32 | (&mut adc_pin0, SampleTime::CYCLES640_5), | ||
| 33 | (&mut adc_pin1, SampleTime::CYCLES640_5), | ||
| 34 | ] | ||
| 35 | .into_iter(), | ||
| 36 | ); | ||
| 37 | |||
| 38 | info!("starting measurement loop"); | ||
| 39 | loop { | ||
| 40 | match ring_buffered_adc.read(&mut measurements).await { | ||
| 41 | Ok(_) => { | ||
| 42 | //note: originally there was a print here showing all the samples, | ||
| 43 | //but even that takes too much time and would cause adc overruns | ||
| 44 | info!("adc1 first 10 samples: {}", measurements[0..10]); | ||
| 45 | } | ||
| 46 | Err(e) => { | ||
| 47 | warn!("Error: {:?}", e); | ||
| 48 | } | ||
| 49 | } | ||
| 50 | } | ||
| 51 | } | ||
diff --git a/examples/stm32l5/src/bin/stop.rs b/examples/stm32l5/src/bin/stop.rs index c34053190..7662dbfa8 100644 --- a/examples/stm32l5/src/bin/stop.rs +++ b/examples/stm32l5/src/bin/stop.rs | |||
| @@ -9,7 +9,6 @@ use embassy_stm32::rcc::LsConfig; | |||
| 9 | use embassy_stm32::rtc::{Rtc, RtcConfig}; | 9 | use embassy_stm32::rtc::{Rtc, RtcConfig}; |
| 10 | use embassy_stm32::{Config, Peri}; | 10 | use embassy_stm32::{Config, Peri}; |
| 11 | use embassy_time::Timer; | 11 | use embassy_time::Timer; |
| 12 | use static_cell::StaticCell; | ||
| 13 | use {defmt_rtt as _, panic_probe as _}; | 12 | use {defmt_rtt as _, panic_probe as _}; |
| 14 | 13 | ||
| 15 | #[cortex_m_rt::entry] | 14 | #[cortex_m_rt::entry] |
| @@ -30,8 +29,6 @@ async fn async_main(spawner: Spawner) { | |||
| 30 | 29 | ||
| 31 | // give the RTC to the executor... | 30 | // give the RTC to the executor... |
| 32 | let rtc = Rtc::new(p.RTC, RtcConfig::default()); | 31 | let rtc = Rtc::new(p.RTC, RtcConfig::default()); |
| 33 | static RTC: StaticCell<Rtc> = StaticCell::new(); | ||
| 34 | let rtc = RTC.init(rtc); | ||
| 35 | embassy_stm32::low_power::stop_with_rtc(rtc); | 32 | embassy_stm32::low_power::stop_with_rtc(rtc); |
| 36 | 33 | ||
| 37 | spawner.spawn(unwrap!(blinky(p.PC7.into()))); | 34 | spawner.spawn(unwrap!(blinky(p.PC7.into()))); |
diff --git a/examples/stm32wle5/.cargo/config.toml b/examples/stm32wle5/.cargo/config.toml new file mode 100644 index 000000000..0178d377c --- /dev/null +++ b/examples/stm32wle5/.cargo/config.toml | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] | ||
| 2 | # replace your chip as listed in `probe-rs chip list` | ||
| 3 | runner = "probe-rs run --chip STM32WLE5JCIx --connect-under-reset" | ||
| 4 | |||
| 5 | [build] | ||
| 6 | target = "thumbv7em-none-eabi" | ||
| 7 | |||
| 8 | [env] | ||
| 9 | DEFMT_LOG = "info" | ||
diff --git a/examples/stm32wle5/Cargo.toml b/examples/stm32wle5/Cargo.toml new file mode 100644 index 000000000..f2fc4dd3d --- /dev/null +++ b/examples/stm32wle5/Cargo.toml | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | [package] | ||
| 2 | edition = "2024" | ||
| 3 | name = "embassy-stm32wl-examples" | ||
| 4 | version = "0.1.0" | ||
| 5 | license = "MIT OR Apache-2.0" | ||
| 6 | publish = false | ||
| 7 | |||
| 8 | [dependencies] | ||
| 9 | # Change stm32wl55jc-cm4 to your chip name, if necessary. | ||
| 10 | embassy-stm32 = { version = "0.4.0", path = "../../embassy-stm32", features = ["defmt", "stm32wle5jc", "time-driver-any", "memory-x", "unstable-pac", "exti", "low-power"] } | ||
| 11 | embassy-sync = { version = "0.7.2", path = "../../embassy-sync", features = ["defmt"] } | ||
| 12 | embassy-executor = { version = "0.9.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } | ||
| 13 | embassy-time = { version = "0.5.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-1_000"] } | ||
| 14 | embassy-embedded-hal = { version = "0.5.0", path = "../../embassy-embedded-hal" } | ||
| 15 | |||
| 16 | defmt = "1.0.1" | ||
| 17 | defmt-rtt = { version = "1.1.0", optional = true } | ||
| 18 | defmt-serial = { version = "0.10.0", optional = true } | ||
| 19 | |||
| 20 | cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } | ||
| 21 | cortex-m-rt = "0.7.0" | ||
| 22 | embedded-hal = "1.0.0" | ||
| 23 | embedded-storage = "0.3.1" | ||
| 24 | panic-probe = { version = "1.0.0", features = ["print-defmt"] } | ||
| 25 | static_cell = { version = "2.1.1", default-features = false } | ||
| 26 | |||
| 27 | [profile.release] | ||
| 28 | debug = 2 | ||
| 29 | |||
| 30 | [package.metadata.embassy] | ||
| 31 | build = [ | ||
| 32 | { target = "thumbv7em-none-eabi", artifact-dir = "out/examples/stm32wl" } | ||
| 33 | ] | ||
| 34 | |||
| 35 | [features] | ||
| 36 | default = ["defmt-serial"] | ||
| 37 | defmt-rtt = ["dep:defmt-rtt"] | ||
| 38 | defmt-serial = ["dep:defmt-serial"] | ||
diff --git a/examples/stm32wle5/README.md b/examples/stm32wle5/README.md new file mode 100644 index 000000000..18c3b5071 --- /dev/null +++ b/examples/stm32wle5/README.md | |||
| @@ -0,0 +1,52 @@ | |||
| 1 | # Low Power Examples for STM32WLEx family | ||
| 2 | |||
| 3 | Examples in this repo should work with [LoRa-E5 Dev Board](https://www.st.com/en/partner-products-and-services/lora-e5-development-kit.html) board. | ||
| 4 | |||
| 5 | ## Prerequsits | ||
| 6 | |||
| 7 | - Connect a usb serial adapter to LPUart1 (this is where ALL logging will go) | ||
| 8 | - Optional: Connect an amp meter that ran measure down to 0.1uA to the power test pins | ||
| 9 | - `cargo install defmt-print` so you can print log messahes from LPUart1 | ||
| 10 | |||
| 11 | ## Example Notes | ||
| 12 | |||
| 13 | All examples will set all pins to analog mode before configuring pins for the example, if any. This saves about 500uA!!!! | ||
| 14 | |||
| 15 | - the `adc` example will sleep in STOP1 betwen samples and the chip will only draw about 13uA while sleeping | ||
| 16 | - the `blinky` example will sleep in STOP2 and the chip will only draw 1uA or less while sleeping | ||
| 17 | - the `button_exti` example will sleep in STOP2 and the chip will only draw 1uA or less while sleeping | ||
| 18 | - the `i2c` examples will sleep in STOP1 between reads and the chip only draw about 10uA while sleeping | ||
| 19 | |||
| 20 | For each example you will need to start `defmt-print` with the example binary and the correct serial port in a seperate terminal. Example: | ||
| 21 | ``` | ||
| 22 | defmt-print -w -v -e target/thumbv7em-none-eabi/debug/<module-name> serial --path /dev/cu.usbserial-00000000 --baud 115200 | ||
| 23 | ``` | ||
| 24 | |||
| 25 | Run individual examples with | ||
| 26 | ``` | ||
| 27 | cargo flash --chip STM32WLE5JCIx --connect-under-reset --bin <module-name> | ||
| 28 | ``` | ||
| 29 | for example | ||
| 30 | ``` | ||
| 31 | cargo flash --chip STM32WLE5JCIx --connect-under-reset --bin blinky | ||
| 32 | ``` | ||
| 33 | |||
| 34 | You can also run them with with `run`. However in this case expect probe-rs to be disconnected as soon as flashing is done as all IO pins are set to analog input! | ||
| 35 | ``` | ||
| 36 | cargo run --bin blinky | ||
| 37 | ``` | ||
| 38 | |||
| 39 | ## Checklist before running examples | ||
| 40 | You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using. | ||
| 41 | |||
| 42 | * [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for L432KCU6 it should be `probe-rs run --chip STM32L432KCUx`. (use `probe-rs chip list` to find your chip) | ||
| 43 | * [ ] Update Cargo.toml to have the correct `embassy-stm32` feature. For example for L432KCU6 it should be `stm32l432kc`. Look in the `Cargo.toml` file of the `embassy-stm32` project to find the correct feature flag for your chip. | ||
| 44 | * [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. | ||
| 45 | * [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic | ||
| 46 | |||
| 47 | If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: | ||
| 48 | |||
| 49 | * Which example you are trying to run | ||
| 50 | * Which chip and board you are using | ||
| 51 | |||
| 52 | Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org | ||
diff --git a/examples/stm32wle5/build.rs b/examples/stm32wle5/build.rs new file mode 100644 index 000000000..8cd32d7ed --- /dev/null +++ b/examples/stm32wle5/build.rs | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | fn main() { | ||
| 2 | println!("cargo:rustc-link-arg-bins=--nmagic"); | ||
| 3 | println!("cargo:rustc-link-arg-bins=-Tlink.x"); | ||
| 4 | println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); | ||
| 5 | } | ||
diff --git a/examples/stm32wle5/src/bin/adc.rs b/examples/stm32wle5/src/bin/adc.rs new file mode 100644 index 000000000..ff1a5fa16 --- /dev/null +++ b/examples/stm32wle5/src/bin/adc.rs | |||
| @@ -0,0 +1,100 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use defmt::*; | ||
| 5 | #[cfg(feature = "defmt-rtt")] | ||
| 6 | use defmt_rtt as _; | ||
| 7 | use embassy_executor::Spawner; | ||
| 8 | use embassy_stm32::adc::{Adc, SampleTime}; | ||
| 9 | use embassy_stm32::low_power::Executor; | ||
| 10 | use embassy_stm32::rtc::{Rtc, RtcConfig}; | ||
| 11 | use embassy_time::Timer; | ||
| 12 | use panic_probe as _; | ||
| 13 | use static_cell::StaticCell; | ||
| 14 | |||
| 15 | #[cortex_m_rt::entry] | ||
| 16 | fn main() -> ! { | ||
| 17 | info!("main: Starting!"); | ||
| 18 | Executor::take().run(|spawner| { | ||
| 19 | spawner.spawn(unwrap!(async_main(spawner))); | ||
| 20 | }); | ||
| 21 | } | ||
| 22 | |||
| 23 | #[embassy_executor::task] | ||
| 24 | async fn async_main(_spawner: Spawner) { | ||
| 25 | let mut config = embassy_stm32::Config::default(); | ||
| 26 | // enable HSI clock | ||
| 27 | config.rcc.hsi = true; | ||
| 28 | // enable LSI clock for RTC | ||
| 29 | config.rcc.ls = embassy_stm32::rcc::LsConfig::default_lsi(); | ||
| 30 | config.rcc.msi = Some(embassy_stm32::rcc::MSIRange::RANGE4M); | ||
| 31 | config.rcc.sys = embassy_stm32::rcc::Sysclk::MSI; | ||
| 32 | // enable ADC with HSI clock | ||
| 33 | config.rcc.mux.adcsel = embassy_stm32::pac::rcc::vals::Adcsel::HSI; | ||
| 34 | #[cfg(feature = "defmt-serial")] | ||
| 35 | { | ||
| 36 | // disable debug during sleep to reduce power consumption since we are | ||
| 37 | // using defmt-serial on LPUART1. | ||
| 38 | config.enable_debug_during_sleep = false; | ||
| 39 | // if we are using defmt-serial on LPUART1, we need to use HSI for the clock | ||
| 40 | // so that its registers are preserved during STOP modes. | ||
| 41 | config.rcc.mux.lpuart1sel = embassy_stm32::pac::rcc::vals::Lpuart1sel::HSI; | ||
| 42 | } | ||
| 43 | // Initialize STM32WL peripherals (use default config like wio-e5-async example) | ||
| 44 | let p = embassy_stm32::init(config); | ||
| 45 | |||
| 46 | // start with all GPIOs as analog to reduce power consumption | ||
| 47 | for r in [ | ||
| 48 | embassy_stm32::pac::GPIOA, | ||
| 49 | embassy_stm32::pac::GPIOB, | ||
| 50 | embassy_stm32::pac::GPIOC, | ||
| 51 | embassy_stm32::pac::GPIOH, | ||
| 52 | ] { | ||
| 53 | r.moder().modify(|w| { | ||
| 54 | for i in 0..16 { | ||
| 55 | // don't reset these if probe-rs should stay connected! | ||
| 56 | #[cfg(feature = "defmt-rtt")] | ||
| 57 | if config.enable_debug_during_sleep && r == embassy_stm32::pac::GPIOA && [13, 14].contains(&i) { | ||
| 58 | continue; | ||
| 59 | } | ||
| 60 | w.set_moder(i, embassy_stm32::pac::gpio::vals::Moder::ANALOG); | ||
| 61 | } | ||
| 62 | }); | ||
| 63 | } | ||
| 64 | #[cfg(feature = "defmt-serial")] | ||
| 65 | { | ||
| 66 | use embassy_stm32::mode::Blocking; | ||
| 67 | use embassy_stm32::usart::Uart; | ||
| 68 | let config = embassy_stm32::usart::Config::default(); | ||
| 69 | let uart = Uart::new_blocking(p.LPUART1, p.PC0, p.PC1, config).expect("failed to configure UART!"); | ||
| 70 | static SERIAL: StaticCell<Uart<'static, Blocking>> = StaticCell::new(); | ||
| 71 | defmt_serial::defmt_serial(SERIAL.init(uart)); | ||
| 72 | } | ||
| 73 | |||
| 74 | // give the RTC to the low_power executor... | ||
| 75 | let rtc_config = RtcConfig::default(); | ||
| 76 | let rtc = Rtc::new(p.RTC, rtc_config); | ||
| 77 | embassy_stm32::low_power::stop_with_rtc(rtc); | ||
| 78 | |||
| 79 | info!("Hello World!"); | ||
| 80 | |||
| 81 | let mut adc = Adc::new(p.ADC1); | ||
| 82 | adc.set_sample_time(SampleTime::CYCLES79_5); | ||
| 83 | let mut pin = p.PA10; | ||
| 84 | |||
| 85 | let mut vrefint = adc.enable_vrefint(); | ||
| 86 | let vrefint_sample = adc.blocking_read(&mut vrefint); | ||
| 87 | let convert_to_millivolts = |sample| { | ||
| 88 | // From https://www.st.com/resource/en/datasheet/stm32g031g8.pdf | ||
| 89 | // 6.3.3 Embedded internal reference voltage | ||
| 90 | const VREFINT_MV: u32 = 1212; // mV | ||
| 91 | |||
| 92 | (u32::from(sample) * VREFINT_MV / u32::from(vrefint_sample)) as u16 | ||
| 93 | }; | ||
| 94 | |||
| 95 | loop { | ||
| 96 | let v = adc.blocking_read(&mut pin); | ||
| 97 | info!("--> {} - {} mV", v, convert_to_millivolts(v)); | ||
| 98 | Timer::after_secs(1).await; | ||
| 99 | } | ||
| 100 | } | ||
diff --git a/examples/stm32wle5/src/bin/blinky.rs b/examples/stm32wle5/src/bin/blinky.rs new file mode 100644 index 000000000..1191a1157 --- /dev/null +++ b/examples/stm32wle5/src/bin/blinky.rs | |||
| @@ -0,0 +1,90 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use defmt::*; | ||
| 5 | #[cfg(feature = "defmt-rtt")] | ||
| 6 | use defmt_rtt as _; | ||
| 7 | use embassy_executor::Spawner; | ||
| 8 | use embassy_stm32::gpio::{Level, Output, Speed}; | ||
| 9 | use embassy_stm32::low_power::Executor; | ||
| 10 | use embassy_stm32::rtc::{Rtc, RtcConfig}; | ||
| 11 | use embassy_time::Timer; | ||
| 12 | use panic_probe as _; | ||
| 13 | use static_cell::StaticCell; | ||
| 14 | |||
| 15 | #[cortex_m_rt::entry] | ||
| 16 | fn main() -> ! { | ||
| 17 | info!("main: Starting!"); | ||
| 18 | Executor::take().run(|spawner| { | ||
| 19 | spawner.spawn(unwrap!(async_main(spawner))); | ||
| 20 | }); | ||
| 21 | } | ||
| 22 | |||
| 23 | #[embassy_executor::task] | ||
| 24 | async fn async_main(_spawner: Spawner) { | ||
| 25 | let mut config = embassy_stm32::Config::default(); | ||
| 26 | // enable HSI clock | ||
| 27 | config.rcc.hsi = true; | ||
| 28 | // enable LSI clock for RTC | ||
| 29 | config.rcc.ls = embassy_stm32::rcc::LsConfig::default_lsi(); | ||
| 30 | config.rcc.msi = Some(embassy_stm32::rcc::MSIRange::RANGE4M); | ||
| 31 | config.rcc.sys = embassy_stm32::rcc::Sysclk::MSI; | ||
| 32 | #[cfg(feature = "defmt-serial")] | ||
| 33 | { | ||
| 34 | // disable debug during sleep to reduce power consumption since we are | ||
| 35 | // using defmt-serial on LPUART1. | ||
| 36 | config.enable_debug_during_sleep = false; | ||
| 37 | // if we are using defmt-serial on LPUART1, we need to use HSI for the clock | ||
| 38 | // so that its registers are preserved during STOP modes. | ||
| 39 | config.rcc.mux.lpuart1sel = embassy_stm32::pac::rcc::vals::Lpuart1sel::HSI; | ||
| 40 | } | ||
| 41 | // Initialize STM32WL peripherals (use default config like wio-e5-async example) | ||
| 42 | let p = embassy_stm32::init(config); | ||
| 43 | |||
| 44 | // start with all GPIOs as analog to reduce power consumption | ||
| 45 | for r in [ | ||
| 46 | embassy_stm32::pac::GPIOA, | ||
| 47 | embassy_stm32::pac::GPIOB, | ||
| 48 | embassy_stm32::pac::GPIOC, | ||
| 49 | embassy_stm32::pac::GPIOH, | ||
| 50 | ] { | ||
| 51 | r.moder().modify(|w| { | ||
| 52 | for i in 0..16 { | ||
| 53 | // don't reset these if probe-rs should stay connected! | ||
| 54 | #[cfg(feature = "defmt-rtt")] | ||
| 55 | if config.enable_debug_during_sleep && r == embassy_stm32::pac::GPIOA && [13, 14].contains(&i) { | ||
| 56 | continue; | ||
| 57 | } | ||
| 58 | w.set_moder(i, embassy_stm32::pac::gpio::vals::Moder::ANALOG); | ||
| 59 | } | ||
| 60 | }); | ||
| 61 | } | ||
| 62 | #[cfg(feature = "defmt-serial")] | ||
| 63 | { | ||
| 64 | use embassy_stm32::mode::Blocking; | ||
| 65 | use embassy_stm32::usart::Uart; | ||
| 66 | let config = embassy_stm32::usart::Config::default(); | ||
| 67 | let uart = Uart::new_blocking(p.LPUART1, p.PC0, p.PC1, config).expect("failed to configure UART!"); | ||
| 68 | static SERIAL: StaticCell<Uart<'static, Blocking>> = StaticCell::new(); | ||
| 69 | defmt_serial::defmt_serial(SERIAL.init(uart)); | ||
| 70 | } | ||
| 71 | |||
| 72 | // give the RTC to the low_power executor... | ||
| 73 | let rtc_config = RtcConfig::default(); | ||
| 74 | let rtc = Rtc::new(p.RTC, rtc_config); | ||
| 75 | embassy_stm32::low_power::stop_with_rtc(rtc); | ||
| 76 | |||
| 77 | info!("Hello World!"); | ||
| 78 | |||
| 79 | let mut led = Output::new(p.PB5, Level::High, Speed::Low); | ||
| 80 | |||
| 81 | loop { | ||
| 82 | info!("low"); | ||
| 83 | led.set_low(); | ||
| 84 | Timer::after_millis(500).await; | ||
| 85 | |||
| 86 | info!("high"); | ||
| 87 | led.set_high(); | ||
| 88 | Timer::after_millis(500).await; | ||
| 89 | } | ||
| 90 | } | ||
diff --git a/examples/stm32wle5/src/bin/button_exti.rs b/examples/stm32wle5/src/bin/button_exti.rs new file mode 100644 index 000000000..f07f9724d --- /dev/null +++ b/examples/stm32wle5/src/bin/button_exti.rs | |||
| @@ -0,0 +1,91 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use defmt::*; | ||
| 5 | #[cfg(feature = "defmt-rtt")] | ||
| 6 | use defmt_rtt as _; | ||
| 7 | use embassy_executor::Spawner; | ||
| 8 | use embassy_stm32::exti::ExtiInput; | ||
| 9 | use embassy_stm32::gpio::Pull; | ||
| 10 | use embassy_stm32::low_power::Executor; | ||
| 11 | use embassy_stm32::rtc::{Rtc, RtcConfig}; | ||
| 12 | use panic_probe as _; | ||
| 13 | use static_cell::StaticCell; | ||
| 14 | |||
| 15 | #[cortex_m_rt::entry] | ||
| 16 | fn main() -> ! { | ||
| 17 | info!("main: Starting!"); | ||
| 18 | Executor::take().run(|spawner| { | ||
| 19 | spawner.spawn(unwrap!(async_main(spawner))); | ||
| 20 | }); | ||
| 21 | } | ||
| 22 | |||
| 23 | #[embassy_executor::task] | ||
| 24 | async fn async_main(_spawner: Spawner) { | ||
| 25 | let mut config = embassy_stm32::Config::default(); | ||
| 26 | // enable HSI clock | ||
| 27 | config.rcc.hsi = true; | ||
| 28 | // enable LSI clock for RTC | ||
| 29 | config.rcc.ls = embassy_stm32::rcc::LsConfig::default_lsi(); | ||
| 30 | config.rcc.msi = Some(embassy_stm32::rcc::MSIRange::RANGE4M); | ||
| 31 | config.rcc.sys = embassy_stm32::rcc::Sysclk::MSI; | ||
| 32 | // enable ADC with HSI clock | ||
| 33 | config.rcc.mux.adcsel = embassy_stm32::pac::rcc::vals::Adcsel::HSI; | ||
| 34 | #[cfg(feature = "defmt-serial")] | ||
| 35 | { | ||
| 36 | // disable debug during sleep to reduce power consumption since we are | ||
| 37 | // using defmt-serial on LPUART1. | ||
| 38 | config.enable_debug_during_sleep = false; | ||
| 39 | // if we are using defmt-serial on LPUART1, we need to use HSI for the clock | ||
| 40 | // so that its registers are preserved during STOP modes. | ||
| 41 | config.rcc.mux.lpuart1sel = embassy_stm32::pac::rcc::vals::Lpuart1sel::HSI; | ||
| 42 | } | ||
| 43 | // Initialize STM32WL peripherals (use default config like wio-e5-async example) | ||
| 44 | let p = embassy_stm32::init(config); | ||
| 45 | |||
| 46 | // start with all GPIOs as analog to reduce power consumption | ||
| 47 | for r in [ | ||
| 48 | embassy_stm32::pac::GPIOA, | ||
| 49 | embassy_stm32::pac::GPIOB, | ||
| 50 | embassy_stm32::pac::GPIOC, | ||
| 51 | embassy_stm32::pac::GPIOH, | ||
| 52 | ] { | ||
| 53 | r.moder().modify(|w| { | ||
| 54 | for i in 0..16 { | ||
| 55 | // don't reset these if probe-rs should stay connected! | ||
| 56 | #[cfg(feature = "defmt-rtt")] | ||
| 57 | if config.enable_debug_during_sleep && r == embassy_stm32::pac::GPIOA && [13, 14].contains(&i) { | ||
| 58 | continue; | ||
| 59 | } | ||
| 60 | w.set_moder(i, embassy_stm32::pac::gpio::vals::Moder::ANALOG); | ||
| 61 | } | ||
| 62 | }); | ||
| 63 | } | ||
| 64 | #[cfg(feature = "defmt-serial")] | ||
| 65 | { | ||
| 66 | use embassy_stm32::mode::Blocking; | ||
| 67 | use embassy_stm32::usart::Uart; | ||
| 68 | let config = embassy_stm32::usart::Config::default(); | ||
| 69 | let uart = Uart::new_blocking(p.LPUART1, p.PC0, p.PC1, config).expect("failed to configure UART!"); | ||
| 70 | static SERIAL: StaticCell<Uart<'static, Blocking>> = StaticCell::new(); | ||
| 71 | defmt_serial::defmt_serial(SERIAL.init(uart)); | ||
| 72 | } | ||
| 73 | |||
| 74 | // give the RTC to the low_power executor... | ||
| 75 | let rtc_config = RtcConfig::default(); | ||
| 76 | let rtc = Rtc::new(p.RTC, rtc_config); | ||
| 77 | embassy_stm32::low_power::stop_with_rtc(rtc); | ||
| 78 | |||
| 79 | info!("Hello World!"); | ||
| 80 | |||
| 81 | let mut button = ExtiInput::new(p.PA0, p.EXTI0, Pull::Up); | ||
| 82 | |||
| 83 | info!("Press the USER button..."); | ||
| 84 | |||
| 85 | loop { | ||
| 86 | button.wait_for_falling_edge().await; | ||
| 87 | info!("Pressed!"); | ||
| 88 | button.wait_for_rising_edge().await; | ||
| 89 | info!("Released!"); | ||
| 90 | } | ||
| 91 | } | ||
diff --git a/examples/stm32wle5/src/bin/i2c.rs b/examples/stm32wle5/src/bin/i2c.rs new file mode 100644 index 000000000..af07f911e --- /dev/null +++ b/examples/stm32wle5/src/bin/i2c.rs | |||
| @@ -0,0 +1,110 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use defmt::*; | ||
| 5 | #[cfg(feature = "defmt-rtt")] | ||
| 6 | use defmt_rtt as _; | ||
| 7 | use embassy_executor::Spawner; | ||
| 8 | use embassy_stm32::i2c::I2c; | ||
| 9 | use embassy_stm32::low_power::Executor; | ||
| 10 | use embassy_stm32::rtc::{Rtc, RtcConfig}; | ||
| 11 | use embassy_stm32::time::Hertz; | ||
| 12 | use embassy_stm32::{bind_interrupts, i2c, peripherals}; | ||
| 13 | use embassy_time::{Duration, Timer}; | ||
| 14 | use panic_probe as _; | ||
| 15 | use static_cell::StaticCell; | ||
| 16 | |||
| 17 | bind_interrupts!(struct IrqsI2C{ | ||
| 18 | I2C2_EV => i2c::EventInterruptHandler<peripherals::I2C2>; | ||
| 19 | I2C2_ER => i2c::ErrorInterruptHandler<peripherals::I2C2>; | ||
| 20 | }); | ||
| 21 | |||
| 22 | #[cortex_m_rt::entry] | ||
| 23 | fn main() -> ! { | ||
| 24 | info!("main: Starting!"); | ||
| 25 | Executor::take().run(|spawner| { | ||
| 26 | spawner.spawn(unwrap!(async_main(spawner))); | ||
| 27 | }); | ||
| 28 | } | ||
| 29 | |||
| 30 | #[embassy_executor::task] | ||
| 31 | async fn async_main(_spawner: Spawner) { | ||
| 32 | let mut config = embassy_stm32::Config::default(); | ||
| 33 | // enable HSI clock | ||
| 34 | config.rcc.hsi = true; | ||
| 35 | // enable LSI clock for RTC | ||
| 36 | config.rcc.ls = embassy_stm32::rcc::LsConfig::default_lsi(); | ||
| 37 | config.rcc.msi = Some(embassy_stm32::rcc::MSIRange::RANGE4M); | ||
| 38 | config.rcc.sys = embassy_stm32::rcc::Sysclk::MSI; | ||
| 39 | // enable ADC with HSI clock | ||
| 40 | config.rcc.mux.i2c2sel = embassy_stm32::pac::rcc::vals::I2c2sel::HSI; | ||
| 41 | #[cfg(feature = "defmt-serial")] | ||
| 42 | { | ||
| 43 | // disable debug during sleep to reduce power consumption since we are | ||
| 44 | // using defmt-serial on LPUART1. | ||
| 45 | config.enable_debug_during_sleep = false; | ||
| 46 | // if we are using defmt-serial on LPUART1, we need to use HSI for the clock | ||
| 47 | // so that its registers are preserved during STOP modes. | ||
| 48 | config.rcc.mux.lpuart1sel = embassy_stm32::pac::rcc::vals::Lpuart1sel::HSI; | ||
| 49 | } | ||
| 50 | // Initialize STM32WL peripherals (use default config like wio-e5-async example) | ||
| 51 | let p = embassy_stm32::init(config); | ||
| 52 | |||
| 53 | // start with all GPIOs as analog to reduce power consumption | ||
| 54 | for r in [ | ||
| 55 | embassy_stm32::pac::GPIOA, | ||
| 56 | embassy_stm32::pac::GPIOB, | ||
| 57 | embassy_stm32::pac::GPIOC, | ||
| 58 | embassy_stm32::pac::GPIOH, | ||
| 59 | ] { | ||
| 60 | r.moder().modify(|w| { | ||
| 61 | for i in 0..16 { | ||
| 62 | // don't reset these if probe-rs should stay connected! | ||
| 63 | #[cfg(feature = "defmt-rtt")] | ||
| 64 | if config.enable_debug_during_sleep && r == embassy_stm32::pac::GPIOA && [13, 14].contains(&i) { | ||
| 65 | continue; | ||
| 66 | } | ||
| 67 | w.set_moder(i, embassy_stm32::pac::gpio::vals::Moder::ANALOG); | ||
| 68 | } | ||
| 69 | }); | ||
| 70 | } | ||
| 71 | #[cfg(feature = "defmt-serial")] | ||
| 72 | { | ||
| 73 | use embassy_stm32::mode::Blocking; | ||
| 74 | use embassy_stm32::usart::Uart; | ||
| 75 | let config = embassy_stm32::usart::Config::default(); | ||
| 76 | let uart = Uart::new_blocking(p.LPUART1, p.PC0, p.PC1, config).expect("failed to configure UART!"); | ||
| 77 | static SERIAL: StaticCell<Uart<'static, Blocking>> = StaticCell::new(); | ||
| 78 | defmt_serial::defmt_serial(SERIAL.init(uart)); | ||
| 79 | } | ||
| 80 | |||
| 81 | // give the RTC to the low_power executor... | ||
| 82 | let rtc_config = RtcConfig::default(); | ||
| 83 | let rtc = Rtc::new(p.RTC, rtc_config); | ||
| 84 | embassy_stm32::low_power::stop_with_rtc(rtc); | ||
| 85 | |||
| 86 | info!("Hello World!"); | ||
| 87 | let en3v3 = embassy_stm32::gpio::Output::new( | ||
| 88 | p.PA9, | ||
| 89 | embassy_stm32::gpio::Level::High, | ||
| 90 | embassy_stm32::gpio::Speed::High, | ||
| 91 | ); | ||
| 92 | core::mem::forget(en3v3); // keep the output pin enabled | ||
| 93 | |||
| 94 | let mut i2c = I2c::new(p.I2C2, p.PB15, p.PA15, IrqsI2C, p.DMA1_CH6, p.DMA1_CH7, { | ||
| 95 | let mut config = i2c::Config::default(); | ||
| 96 | config.frequency = Hertz::khz(100); | ||
| 97 | config.timeout = Duration::from_millis(500); | ||
| 98 | config | ||
| 99 | }); | ||
| 100 | |||
| 101 | loop { | ||
| 102 | let mut buffer = [0; 2]; | ||
| 103 | // read the temperature register of the onboard lm75 | ||
| 104 | match i2c.read(0x48, &mut buffer).await { | ||
| 105 | Ok(_) => info!("--> {:?}", buffer), | ||
| 106 | Err(e) => info!("--> Error: {:?}", e), | ||
| 107 | } | ||
| 108 | Timer::after_secs(5).await; | ||
| 109 | } | ||
| 110 | } | ||
diff --git a/examples/stm32wle5/stm32wle5.code-workspace b/examples/stm32wle5/stm32wle5.code-workspace new file mode 100644 index 000000000..a7c4a0ebd --- /dev/null +++ b/examples/stm32wle5/stm32wle5.code-workspace | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | { | ||
| 2 | "folders": [ | ||
| 3 | { | ||
| 4 | "path": "." | ||
| 5 | } | ||
| 6 | ], | ||
| 7 | "settings": { | ||
| 8 | "rust-analyzer.cargo.target": "thumbv7em-none-eabi", | ||
| 9 | "rust-analyzer.cargo.allTargets": false, | ||
| 10 | "rust-analyzer.cargo.targetDir": "target/rust-analyzer", | ||
| 11 | "rust-analyzer.checkOnSave": true, | ||
| 12 | } | ||
| 13 | } | ||
diff --git a/tests/stm32/src/bin/stop.rs b/tests/stm32/src/bin/stop.rs index 833ca05d0..73580f0f2 100644 --- a/tests/stm32/src/bin/stop.rs +++ b/tests/stm32/src/bin/stop.rs | |||
| @@ -14,7 +14,6 @@ use embassy_stm32::low_power::{Executor, StopMode, stop_ready, stop_with_rtc}; | |||
| 14 | use embassy_stm32::rcc::LsConfig; | 14 | use embassy_stm32::rcc::LsConfig; |
| 15 | use embassy_stm32::rtc::{Rtc, RtcConfig}; | 15 | use embassy_stm32::rtc::{Rtc, RtcConfig}; |
| 16 | use embassy_time::Timer; | 16 | use embassy_time::Timer; |
| 17 | use static_cell::StaticCell; | ||
| 18 | 17 | ||
| 19 | #[entry] | 18 | #[entry] |
| 20 | fn main() -> ! { | 19 | fn main() -> ! { |
| @@ -70,9 +69,6 @@ async fn async_main(spawner: Spawner) { | |||
| 70 | 69 | ||
| 71 | rtc.set_datetime(now.into()).expect("datetime not set"); | 70 | rtc.set_datetime(now.into()).expect("datetime not set"); |
| 72 | 71 | ||
| 73 | static RTC: StaticCell<Rtc> = StaticCell::new(); | ||
| 74 | let rtc = RTC.init(rtc); | ||
| 75 | |||
| 76 | stop_with_rtc(rtc); | 72 | stop_with_rtc(rtc); |
| 77 | 73 | ||
| 78 | spawner.spawn(task_1().unwrap()); | 74 | spawner.spawn(task_1().unwrap()); |
