From 617dd353637d5d99e47c357f9115f6f96144ed6b Mon Sep 17 00:00:00 2001 From: Brian Schwind Date: Sat, 4 Oct 2025 13:04:42 +0900 Subject: ospi: use a named enum variant in place of a literal zero --- embassy-stm32/src/ospi/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-stm32/src/ospi/mod.rs b/embassy-stm32/src/ospi/mod.rs index d93cecb69..76e6b46eb 100644 --- a/embassy-stm32/src/ospi/mod.rs +++ b/embassy-stm32/src/ospi/mod.rs @@ -451,7 +451,7 @@ impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { } T::REGS.cr().modify(|w| { - w.set_fmode(0.into()); + w.set_fmode(vals::FunctionalMode::INDIRECT_WRITE); }); // Configure alternate bytes -- cgit From 5e89631367c88e636899ddc3b3d333c92d0a983e Mon Sep 17 00:00:00 2001 From: Brian Schwind Date: Sat, 4 Oct 2025 13:05:26 +0900 Subject: ospi: properly configure the transfer size --- embassy-stm32/src/ospi/mod.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/embassy-stm32/src/ospi/mod.rs b/embassy-stm32/src/ospi/mod.rs index 76e6b46eb..a1f3c8b03 100644 --- a/embassy-stm32/src/ospi/mod.rs +++ b/embassy-stm32/src/ospi/mod.rs @@ -577,7 +577,8 @@ impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { w.set_dmaen(false); }); - self.configure_command(&transaction, Some(buf.len()))?; + let transfer_size_bytes = buf.len() * W::size().bytes(); + self.configure_command(&transaction, Some(transfer_size_bytes))?; let current_address = T::REGS.ar().read().address(); let current_instruction = T::REGS.ir().read().instruction(); @@ -616,7 +617,8 @@ impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { w.set_dmaen(false); }); - self.configure_command(&transaction, Some(buf.len()))?; + let transfer_size_bytes = buf.len() * W::size().bytes(); + self.configure_command(&transaction, Some(transfer_size_bytes))?; T::REGS .cr() @@ -1153,7 +1155,8 @@ impl<'d, T: Instance> Ospi<'d, T, Async> { // Wait for peripheral to be free while T::REGS.sr().read().busy() {} - self.configure_command(&transaction, Some(buf.len()))?; + let transfer_size_bytes = buf.len() * W::size().bytes(); + self.configure_command(&transaction, Some(transfer_size_bytes))?; let current_address = T::REGS.ar().read().address(); let current_instruction = T::REGS.ir().read().instruction(); @@ -1193,7 +1196,8 @@ impl<'d, T: Instance> Ospi<'d, T, Async> { // Wait for peripheral to be free while T::REGS.sr().read().busy() {} - self.configure_command(&transaction, Some(buf.len()))?; + let transfer_size_bytes = buf.len() * W::size().bytes(); + self.configure_command(&transaction, Some(transfer_size_bytes))?; T::REGS .cr() .modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECT_WRITE)); @@ -1226,7 +1230,8 @@ impl<'d, T: Instance> Ospi<'d, T, Async> { // Wait for peripheral to be free while T::REGS.sr().read().busy() {} - self.configure_command(&transaction, Some(buf.len()))?; + let transfer_size_bytes = buf.len() * W::size().bytes(); + self.configure_command(&transaction, Some(transfer_size_bytes))?; let current_address = T::REGS.ar().read().address(); let current_instruction = T::REGS.ir().read().instruction(); @@ -1266,7 +1271,8 @@ impl<'d, T: Instance> Ospi<'d, T, Async> { // Wait for peripheral to be free while T::REGS.sr().read().busy() {} - self.configure_command(&transaction, Some(buf.len()))?; + let transfer_size_bytes = buf.len() * W::size().bytes(); + self.configure_command(&transaction, Some(transfer_size_bytes))?; T::REGS .cr() .modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECT_WRITE)); -- cgit From 5220a76e5f71c4e44c1e2f023df5ea7feb4d4370 Mon Sep 17 00:00:00 2001 From: Brian Schwind Date: Sat, 4 Oct 2025 13:13:16 +0900 Subject: ospi: properly respect the max DMA transfer size when writing --- embassy-stm32/src/ospi/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/ospi/mod.rs b/embassy-stm32/src/ospi/mod.rs index a1f3c8b03..2e4943a1b 100644 --- a/embassy-stm32/src/ospi/mod.rs +++ b/embassy-stm32/src/ospi/mod.rs @@ -1203,7 +1203,7 @@ impl<'d, T: Instance> Ospi<'d, T, Async> { .modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECT_WRITE)); // TODO: implement this using a LinkedList DMA to offload the whole transfer off the CPU. - for chunk in buf.chunks(0xFFFF) { + for chunk in buf.chunks(0xFFFF / W::size().bytes()) { let transfer = unsafe { self.dma .as_mut() @@ -1278,7 +1278,7 @@ impl<'d, T: Instance> Ospi<'d, T, Async> { .modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECT_WRITE)); // TODO: implement this using a LinkedList DMA to offload the whole transfer off the CPU. - for chunk in buf.chunks(0xFFFF) { + for chunk in buf.chunks(0xFFFF / W::size().bytes()) { let transfer = unsafe { self.dma .as_mut() -- cgit From ed527e659e0e5d729f9d0ee2f6f15019a144b68a Mon Sep 17 00:00:00 2001 From: Brian Schwind Date: Sat, 4 Oct 2025 13:18:10 +0900 Subject: ospi: properly respect the max DMA transfer size when reading --- embassy-stm32/src/ospi/mod.rs | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/embassy-stm32/src/ospi/mod.rs b/embassy-stm32/src/ospi/mod.rs index 2e4943a1b..dbcf07469 100644 --- a/embassy-stm32/src/ospi/mod.rs +++ b/embassy-stm32/src/ospi/mod.rs @@ -1171,16 +1171,18 @@ impl<'d, T: Instance> Ospi<'d, T, Async> { T::REGS.ar().write(|v| v.set_address(current_address)); } - let transfer = unsafe { - self.dma - .as_mut() - .unwrap() - .read(T::REGS.dr().as_ptr() as *mut W, buf, Default::default()) - }; + for chunk in buf.chunks_mut(0xFFFF / W::size().bytes()) { + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .read(T::REGS.dr().as_ptr() as *mut W, chunk, Default::default()) + }; - T::REGS.cr().modify(|w| w.set_dmaen(true)); + T::REGS.cr().modify(|w| w.set_dmaen(true)); - transfer.blocking_wait(); + transfer.blocking_wait(); + } finish_dma(T::REGS); @@ -1246,16 +1248,18 @@ impl<'d, T: Instance> Ospi<'d, T, Async> { T::REGS.ar().write(|v| v.set_address(current_address)); } - let transfer = unsafe { - self.dma - .as_mut() - .unwrap() - .read(T::REGS.dr().as_ptr() as *mut W, buf, Default::default()) - }; + for chunk in buf.chunks_mut(0xFFFF / W::size().bytes()) { + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .read(T::REGS.dr().as_ptr() as *mut W, chunk, Default::default()) + }; - T::REGS.cr().modify(|w| w.set_dmaen(true)); + T::REGS.cr().modify(|w| w.set_dmaen(true)); - transfer.await; + transfer.await; + } finish_dma(T::REGS); -- cgit From 2ecb06bf9b7d59ec85793228fefa01c70f3c432c Mon Sep 17 00:00:00 2001 From: Brian Schwind Date: Sat, 4 Oct 2025 13:21:55 +0900 Subject: hspi: use a named enum variant in place of a literal zero --- embassy-stm32/src/hspi/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-stm32/src/hspi/mod.rs b/embassy-stm32/src/hspi/mod.rs index 95d9e5099..277f69496 100644 --- a/embassy-stm32/src/hspi/mod.rs +++ b/embassy-stm32/src/hspi/mod.rs @@ -391,7 +391,7 @@ impl<'d, T: Instance, M: PeriMode> Hspi<'d, T, M> { while T::REGS.sr().read().busy() {} T::REGS.cr().modify(|w| { - w.set_fmode(0.into()); + w.set_fmode(FunctionalMode::IndirectWrite.into()); }); // Configure alternate bytes -- cgit From 7e3ca6067be7e2361ccd75f9712a0d08fa7d2d6a Mon Sep 17 00:00:00 2001 From: Brian Schwind Date: Sat, 4 Oct 2025 13:22:35 +0900 Subject: hspi: properly configure the transfer size --- embassy-stm32/src/hspi/mod.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/embassy-stm32/src/hspi/mod.rs b/embassy-stm32/src/hspi/mod.rs index 277f69496..b64a6b62c 100644 --- a/embassy-stm32/src/hspi/mod.rs +++ b/embassy-stm32/src/hspi/mod.rs @@ -498,7 +498,8 @@ impl<'d, T: Instance, M: PeriMode> Hspi<'d, T, M> { w.set_dmaen(false); }); - self.configure_command(&transaction, Some(buf.len()))?; + let transfer_size_bytes = buf.len() * W::size().bytes(); + self.configure_command(&transaction, Some(transfer_size_bytes))?; let current_address = T::REGS.ar().read().address(); let current_instruction = T::REGS.ir().read().instruction(); @@ -537,7 +538,8 @@ impl<'d, T: Instance, M: PeriMode> Hspi<'d, T, M> { w.set_dmaen(false); }); - self.configure_command(&transaction, Some(buf.len()))?; + let transfer_size_bytes = buf.len() * W::size().bytes(); + self.configure_command(&transaction, Some(transfer_size_bytes))?; T::REGS .cr() @@ -767,7 +769,8 @@ impl<'d, T: Instance> Hspi<'d, T, Async> { // Wait for peripheral to be free while T::REGS.sr().read().busy() {} - self.configure_command(&transaction, Some(buf.len()))?; + let transfer_size_bytes = buf.len() * W::size().bytes(); + self.configure_command(&transaction, Some(transfer_size_bytes))?; let current_address = T::REGS.ar().read().address(); let current_instruction = T::REGS.ir().read().instruction(); @@ -807,7 +810,8 @@ impl<'d, T: Instance> Hspi<'d, T, Async> { // Wait for peripheral to be free while T::REGS.sr().read().busy() {} - self.configure_command(&transaction, Some(buf.len()))?; + let transfer_size_bytes = buf.len() * W::size().bytes(); + self.configure_command(&transaction, Some(transfer_size_bytes))?; T::REGS .cr() .modify(|v| v.set_fmode(FunctionalMode::IndirectWrite.into())); @@ -837,7 +841,8 @@ impl<'d, T: Instance> Hspi<'d, T, Async> { // Wait for peripheral to be free while T::REGS.sr().read().busy() {} - self.configure_command(&transaction, Some(buf.len()))?; + let transfer_size_bytes = buf.len() * W::size().bytes(); + self.configure_command(&transaction, Some(transfer_size_bytes))?; let current_address = T::REGS.ar().read().address(); let current_instruction = T::REGS.ir().read().instruction(); @@ -877,7 +882,8 @@ impl<'d, T: Instance> Hspi<'d, T, Async> { // Wait for peripheral to be free while T::REGS.sr().read().busy() {} - self.configure_command(&transaction, Some(buf.len()))?; + let transfer_size_bytes = buf.len() * W::size().bytes(); + self.configure_command(&transaction, Some(transfer_size_bytes))?; T::REGS .cr() .modify(|v| v.set_fmode(FunctionalMode::IndirectWrite.into())); -- cgit From b0a62ddaaaa3e8d89b7af47d904365e6285f97cb Mon Sep 17 00:00:00 2001 From: Brian Schwind Date: Sat, 4 Oct 2025 13:26:11 +0900 Subject: hspi: properly respect the max DMA transfer size when reading and writing --- embassy-stm32/src/hspi/mod.rs | 73 ++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/embassy-stm32/src/hspi/mod.rs b/embassy-stm32/src/hspi/mod.rs index b64a6b62c..59dd7ca16 100644 --- a/embassy-stm32/src/hspi/mod.rs +++ b/embassy-stm32/src/hspi/mod.rs @@ -785,16 +785,18 @@ impl<'d, T: Instance> Hspi<'d, T, Async> { T::REGS.ar().write(|v| v.set_address(current_address)); } - let transfer = unsafe { - self.dma - .as_mut() - .unwrap() - .read(T::REGS.dr().as_ptr() as *mut W, buf, Default::default()) - }; + for chunk in buf.chunks_mut(0xFFFF / W::size().bytes()) { + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .read(T::REGS.dr().as_ptr() as *mut W, chunk, Default::default()) + }; - T::REGS.cr().modify(|w| w.set_dmaen(true)); + T::REGS.cr().modify(|w| w.set_dmaen(true)); - transfer.blocking_wait(); + transfer.blocking_wait(); + } finish_dma(T::REGS); @@ -816,16 +818,18 @@ impl<'d, T: Instance> Hspi<'d, T, Async> { .cr() .modify(|v| v.set_fmode(FunctionalMode::IndirectWrite.into())); - let transfer = unsafe { - self.dma - .as_mut() - .unwrap() - .write(buf, T::REGS.dr().as_ptr() as *mut W, Default::default()) - }; + for chunk in buf.chunks(0xFFFF / W::size().bytes()) { + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .write(chunk, T::REGS.dr().as_ptr() as *mut W, Default::default()) + }; - T::REGS.cr().modify(|w| w.set_dmaen(true)); + T::REGS.cr().modify(|w| w.set_dmaen(true)); - transfer.blocking_wait(); + transfer.blocking_wait(); + } finish_dma(T::REGS); @@ -857,16 +861,18 @@ impl<'d, T: Instance> Hspi<'d, T, Async> { T::REGS.ar().write(|v| v.set_address(current_address)); } - let transfer = unsafe { - self.dma - .as_mut() - .unwrap() - .read(T::REGS.dr().as_ptr() as *mut W, buf, Default::default()) - }; + for chunk in buf.chunks_mut(0xFFFF / W::size().bytes()) { + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .read(T::REGS.dr().as_ptr() as *mut W, chunk, Default::default()) + }; - T::REGS.cr().modify(|w| w.set_dmaen(true)); + T::REGS.cr().modify(|w| w.set_dmaen(true)); - transfer.await; + transfer.await; + } finish_dma(T::REGS); @@ -888,16 +894,19 @@ impl<'d, T: Instance> Hspi<'d, T, Async> { .cr() .modify(|v| v.set_fmode(FunctionalMode::IndirectWrite.into())); - let transfer = unsafe { - self.dma - .as_mut() - .unwrap() - .write(buf, T::REGS.dr().as_ptr() as *mut W, Default::default()) - }; + // TODO: implement this using a LinkedList DMA to offload the whole transfer off the CPU. + for chunk in buf.chunks(0xFFFF / W::size().bytes()) { + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .write(chunk, T::REGS.dr().as_ptr() as *mut W, Default::default()) + }; - T::REGS.cr().modify(|w| w.set_dmaen(true)); + T::REGS.cr().modify(|w| w.set_dmaen(true)); - transfer.await; + transfer.await; + } finish_dma(T::REGS); -- cgit From 2e46fbf3c94b226ffd07a8c3d0730e138f4f168e Mon Sep 17 00:00:00 2001 From: Brian Schwind Date: Sat, 4 Oct 2025 13:27:37 +0900 Subject: xspi: use a named enum variant in place of a literal zero --- embassy-stm32/src/xspi/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/embassy-stm32/src/xspi/mod.rs b/embassy-stm32/src/xspi/mod.rs index 901569f64..cfc24422a 100644 --- a/embassy-stm32/src/xspi/mod.rs +++ b/embassy-stm32/src/xspi/mod.rs @@ -420,9 +420,9 @@ impl<'d, T: Instance, M: PeriMode> Xspi<'d, T, M> { return Err(XspiError::InvalidCommand); } - T::REGS.cr().modify(|w| { - w.set_fmode(0.into()); - }); + T::REGS + .cr() + .modify(|w| w.set_fmode(Fmode::from_bits(XspiMode::IndirectWrite.into()))); // Configure alternate bytes if let Some(ab) = command.alternate_bytes { -- cgit From f471f72d3173f91ebbc1b6eb797278e7ff988e4e Mon Sep 17 00:00:00 2001 From: Brian Schwind Date: Sat, 4 Oct 2025 13:30:06 +0900 Subject: xspi: properly configure the transfer size --- embassy-stm32/src/xspi/mod.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/embassy-stm32/src/xspi/mod.rs b/embassy-stm32/src/xspi/mod.rs index cfc24422a..1f051bffe 100644 --- a/embassy-stm32/src/xspi/mod.rs +++ b/embassy-stm32/src/xspi/mod.rs @@ -538,8 +538,8 @@ impl<'d, T: Instance, M: PeriMode> Xspi<'d, T, M> { w.set_dmaen(false); }); - // self.configure_command(&transaction, Some(buf.len()))?; - self.configure_command(&transaction, Some(buf.len())).unwrap(); + let transfer_size_bytes = buf.len() * W::size().bytes(); + self.configure_command(&transaction, Some(transfer_size_bytes))?; let current_address = T::REGS.ar().read().address(); let current_instruction = T::REGS.ir().read().instruction(); @@ -578,7 +578,8 @@ impl<'d, T: Instance, M: PeriMode> Xspi<'d, T, M> { w.set_dmaen(false); }); - self.configure_command(&transaction, Some(buf.len()))?; + let transfer_size_bytes = buf.len() * W::size().bytes(); + self.configure_command(&transaction, Some(transfer_size_bytes))?; T::REGS .cr() @@ -1145,7 +1146,8 @@ impl<'d, T: Instance> Xspi<'d, T, Async> { // Wait for peripheral to be free while T::REGS.sr().read().busy() {} - self.configure_command(&transaction, Some(buf.len()))?; + let transfer_size_bytes = buf.len() * W::size().bytes(); + self.configure_command(&transaction, Some(transfer_size_bytes))?; let current_address = T::REGS.ar().read().address(); let current_instruction = T::REGS.ir().read().instruction(); @@ -1185,7 +1187,8 @@ impl<'d, T: Instance> Xspi<'d, T, Async> { // Wait for peripheral to be free while T::REGS.sr().read().busy() {} - self.configure_command(&transaction, Some(buf.len()))?; + let transfer_size_bytes = buf.len() * W::size().bytes(); + self.configure_command(&transaction, Some(transfer_size_bytes))?; T::REGS .cr() .modify(|v| v.set_fmode(Fmode::from_bits(XspiMode::IndirectWrite.into()))); @@ -1215,7 +1218,8 @@ impl<'d, T: Instance> Xspi<'d, T, Async> { // Wait for peripheral to be free while T::REGS.sr().read().busy() {} - self.configure_command(&transaction, Some(buf.len()))?; + let transfer_size_bytes = buf.len() * W::size().bytes(); + self.configure_command(&transaction, Some(transfer_size_bytes))?; let current_address = T::REGS.ar().read().address(); let current_instruction = T::REGS.ir().read().instruction(); @@ -1255,7 +1259,8 @@ impl<'d, T: Instance> Xspi<'d, T, Async> { // Wait for peripheral to be free while T::REGS.sr().read().busy() {} - self.configure_command(&transaction, Some(buf.len()))?; + let transfer_size_bytes = buf.len() * W::size().bytes(); + self.configure_command(&transaction, Some(transfer_size_bytes))?; T::REGS .cr() .modify(|v| v.set_fmode(Fmode::from_bits(XspiMode::IndirectWrite.into()))); -- cgit From 64d0fdf1d1b2ce1a3d90b312e009dfc17171086a Mon Sep 17 00:00:00 2001 From: Brian Schwind Date: Sat, 4 Oct 2025 13:32:46 +0900 Subject: xspi: properly respect the max DMA transfer size when reading and writing --- embassy-stm32/src/xspi/mod.rs | 73 ++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/embassy-stm32/src/xspi/mod.rs b/embassy-stm32/src/xspi/mod.rs index 1f051bffe..6f224ab99 100644 --- a/embassy-stm32/src/xspi/mod.rs +++ b/embassy-stm32/src/xspi/mod.rs @@ -1162,16 +1162,18 @@ impl<'d, T: Instance> Xspi<'d, T, Async> { T::REGS.ar().write(|v| v.set_address(current_address)); } - let transfer = unsafe { - self.dma - .as_mut() - .unwrap() - .read(T::REGS.dr().as_ptr() as *mut W, buf, Default::default()) - }; + for chunk in buf.chunks_mut(0xFFFF / W::size().bytes()) { + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .read(T::REGS.dr().as_ptr() as *mut W, chunk, Default::default()) + }; - T::REGS.cr().modify(|w| w.set_dmaen(true)); + T::REGS.cr().modify(|w| w.set_dmaen(true)); - transfer.blocking_wait(); + transfer.blocking_wait(); + } finish_dma(T::REGS); @@ -1193,16 +1195,18 @@ impl<'d, T: Instance> Xspi<'d, T, Async> { .cr() .modify(|v| v.set_fmode(Fmode::from_bits(XspiMode::IndirectWrite.into()))); - let transfer = unsafe { - self.dma - .as_mut() - .unwrap() - .write(buf, T::REGS.dr().as_ptr() as *mut W, Default::default()) - }; + for chunk in buf.chunks(0xFFFF / W::size().bytes()) { + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .write(chunk, T::REGS.dr().as_ptr() as *mut W, Default::default()) + }; - T::REGS.cr().modify(|w| w.set_dmaen(true)); + T::REGS.cr().modify(|w| w.set_dmaen(true)); - transfer.blocking_wait(); + transfer.blocking_wait(); + } finish_dma(T::REGS); @@ -1234,16 +1238,18 @@ impl<'d, T: Instance> Xspi<'d, T, Async> { T::REGS.ar().write(|v| v.set_address(current_address)); } - let transfer = unsafe { - self.dma - .as_mut() - .unwrap() - .read(T::REGS.dr().as_ptr() as *mut W, buf, Default::default()) - }; + for chunk in buf.chunks_mut(0xFFFF / W::size().bytes()) { + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .read(T::REGS.dr().as_ptr() as *mut W, chunk, Default::default()) + }; - T::REGS.cr().modify(|w| w.set_dmaen(true)); + T::REGS.cr().modify(|w| w.set_dmaen(true)); - transfer.await; + transfer.await; + } finish_dma(T::REGS); @@ -1265,16 +1271,19 @@ impl<'d, T: Instance> Xspi<'d, T, Async> { .cr() .modify(|v| v.set_fmode(Fmode::from_bits(XspiMode::IndirectWrite.into()))); - let transfer = unsafe { - self.dma - .as_mut() - .unwrap() - .write(buf, T::REGS.dr().as_ptr() as *mut W, Default::default()) - }; + // TODO: implement this using a LinkedList DMA to offload the whole transfer off the CPU. + for chunk in buf.chunks(0xFFFF / W::size().bytes()) { + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .write(chunk, T::REGS.dr().as_ptr() as *mut W, Default::default()) + }; - T::REGS.cr().modify(|w| w.set_dmaen(true)); + T::REGS.cr().modify(|w| w.set_dmaen(true)); - transfer.await; + transfer.await; + } finish_dma(T::REGS); -- cgit From 461681028681930e50f41ee00154ac3e1886ebca Mon Sep 17 00:00:00 2001 From: Brian Schwind Date: Sat, 4 Oct 2025 16:54:40 +0900 Subject: Update CHANGELOG.md --- embassy-stm32/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index a6ee5c4b8..2005128d5 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -20,7 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - feat: Configurable gpio speed for QSPI - feat: derive Clone, Copy and defmt::Format for all *SPI-related configs - fix: handle address and data-length errors in OSPI -- feat: Allow OSPI DMA writes larger than 64kB using chunking +- feat: Allow OSPI/HSPI/XSPI DMA writes larger than 64kB using chunking - feat: More ADC enums for g0 PAC, API change for oversampling, allow separate sample times - feat: Add USB CRS sync support for STM32C071 - fix: RTC register definition for STM32L4P5 and L4Q5 as they use v3 register map. @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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)) - feat: stm32/usart: add `de_assertion_time` and `de_deassertion_time` config options - change: stm32/uart: BufferedUartRx now returns all available bytes from the internal buffer +- fix: Properly set the transfer size for OSPI/HSPI/XSPI transfers with word sizes other than 8 bits. ## 0.4.0 - 2025-08-26 -- cgit From 3a958cdf685a5d4780572c310aed26272839f78a Mon Sep 17 00:00:00 2001 From: Eicke Hecht Date: Wed, 19 Nov 2025 13:50:20 +0100 Subject: feat: Add generation of a continuous waveforms for SimplePWM --- embassy-stm32/src/timer/low_level.rs | 44 +++++++++++++++++++++++++++++++++-- embassy-stm32/src/timer/simple_pwm.rs | 6 +++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/timer/low_level.rs b/embassy-stm32/src/timer/low_level.rs index f0105ece8..670298d23 100644 --- a/embassy-stm32/src/timer/low_level.rs +++ b/embassy-stm32/src/timer/low_level.rs @@ -673,7 +673,7 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { self.enable_update_dma(true); } - self.waveform_helper(dma, req, channel, duty).await; + self.waveform_helper(dma, req, channel, duty, false).await; // Since DMA is closed before timer update event trigger DMA is turn off, // this can almost always trigger a DMA FIFO error. @@ -780,6 +780,44 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { let cc_channel = C::CHANNEL; + let original_cc_dma_on_update = self.get_cc_dma_selection() == Ccds::ON_UPDATE; + let original_cc_dma_enabled = self.get_c fsc_dma_enable_state(cc_channel); + + // redirect CC DMA request onto Update Event + if !original_cc_dma_on_update { + self.set_cc_dma_selection(Ccds::ON_UPDATE) + } + + if !original_cc_dma_enabled { + self.set_cc_dma_enable_state(cc_channel, true); + } + + self.waveform_helper(dma, req, cc_channel, duty, false).await; + + // Since DMA is closed before timer Capture Compare Event trigger DMA is turn off, + // this can almost always trigger a DMA FIFO error. + // + // optional TODO: + // clean FEIF after disable UDE + if !original_cc_dma_enabled { + self.set_cc_dma_enable_state(cc_channel, false); + } + + if !original_cc_dma_on_update { + self.set_cc_dma_selection(Ccds::ON_COMPARE) + } + } + + /// Generate a sequence of PWM waveform that will run continously + /// You may want to start this in a new thread as this will block forever + pub async fn waveform_continuous(&mut self, dma: Peri<'_, impl super::Dma>, duty: &[u16]) { + use crate::pac::timer::vals::Ccds; + + #[allow(clippy::let_unit_value)] // eg. stm32f334 + let req = dma.request(); + + let cc_channel = C::CHANNEL; + let original_cc_dma_on_update = self.get_cc_dma_selection() == Ccds::ON_UPDATE; let original_cc_dma_enabled = self.get_cc_dma_enable_state(cc_channel); @@ -792,7 +830,7 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { self.set_cc_dma_enable_state(cc_channel, true); } - self.waveform_helper(dma, req, cc_channel, duty).await; + self.waveform_helper(dma, req, cc_channel, duty, true).await; // Since DMA is closed before timer Capture Compare Event trigger DMA is turn off, // this can almost always trigger a DMA FIFO error. @@ -814,6 +852,7 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { req: dma::Request, channel: Channel, duty: &[u16], + circular: bool, ) { let original_duty_state = self.get_compare_value(channel); let original_enable_state = self.get_channel_enable_state(channel); @@ -832,6 +871,7 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { fifo_threshold: Some(FifoThreshold::Full), #[cfg(not(any(bdma, gpdma)))] mburst: Burst::Incr8, + circular: circular, ..Default::default() }; diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 6c9ef17e0..56d00ea59 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -368,6 +368,12 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { pub async fn waveform(&mut self, dma: Peri<'_, impl super::Dma>, duty: &[u16]) { self.inner.waveform(dma, duty).await; } + /// Generate a sequence of PWM waveform that will run continously + /// You may want to start this in a new thread as this will block forever + #[inline(always)] + pub async fn waveform_continuous(&mut self, dma: Peri<'_, impl super::Dma>, duty: &[u16]) { + self.inner.waveform_continuous(dma, duty).await; + } } impl<'d, T: GeneralInstance4Channel> embedded_hal_1::pwm::ErrorType for SimplePwmChannel<'d, T> { -- cgit From ac1f0ec4ecc6450de7d1c6044e799bca8791f7e5 Mon Sep 17 00:00:00 2001 From: Eicke Hecht Date: Wed, 19 Nov 2025 14:00:02 +0100 Subject: doc: Add changelog --- embassy-stm32/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index b6caf8f65..898a91e4b 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate +- feat: Add continuous waveform method to SimplePWM - change: stm32/eth: ethernet no longer has a hard dependency on station management, and station management can be used independently ([#4871](https://github.com/embassy-rs/embassy/pull/4871)) - feat: allow embassy_executor::main for low power - feat: Add waveform methods to ComplementaryPwm -- cgit From 7e768e63a60e1e583a679b8cf9705c2604fdb28d Mon Sep 17 00:00:00 2001 From: xoviat Date: Sat, 22 Nov 2025 09:46:44 -0600 Subject: low_power: remove device busys for wle --- embassy-stm32/src/i2c/v2.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 61e550ad4..b2ba94e21 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -1075,8 +1075,6 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { /// Write. pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> { - #[cfg(all(feature = "low-power", stm32wlex))] - let _device_busy = crate::low_power::DeviceBusy::new_stop1(); let timeout = self.timeout(); if write.is_empty() { self.write_internal(address.into(), write, true, timeout) @@ -1091,8 +1089,6 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { /// /// The buffers are concatenated in a single write transaction. pub async fn write_vectored(&mut self, address: Address, write: &[&[u8]]) -> Result<(), Error> { - #[cfg(all(feature = "low-power", stm32wlex))] - let _device_busy = crate::low_power::DeviceBusy::new_stop1(); let timeout = self.timeout(); if write.is_empty() { @@ -1124,8 +1120,6 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { /// Read. pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { - #[cfg(all(feature = "low-power", stm32wlex))] - let _device_busy = crate::low_power::DeviceBusy::new_stop1(); let timeout = self.timeout(); if buffer.is_empty() { @@ -1138,8 +1132,6 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { /// Write, restart, read. pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> { - #[cfg(all(feature = "low-power", stm32wlex))] - let _device_busy = crate::low_power::DeviceBusy::new_stop1(); let timeout = self.timeout(); if write.is_empty() { @@ -1165,9 +1157,6 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { /// /// [transaction contract]: embedded_hal_1::i2c::I2c::transaction pub async fn transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { - #[cfg(all(feature = "low-power", stm32wlex))] - let _device_busy = crate::low_power::DeviceBusy::new_stop1(); - if operations.is_empty() { return Err(Error::ZeroLengthTransfer); } -- cgit From 36f4075b9054576ccf6dba2dedb08c62484a0599 Mon Sep 17 00:00:00 2001 From: Eicke Hecht Date: Sun, 23 Nov 2025 12:29:56 +0100 Subject: fix: Fix waveform for channels > 1 and disallow for unsupported dmas --- embassy-stm32/src/timer/low_level.rs | 12 +++++-- examples/stm32f7/src/bin/pwm.rs | 65 ++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 examples/stm32f7/src/bin/pwm.rs diff --git a/embassy-stm32/src/timer/low_level.rs b/embassy-stm32/src/timer/low_level.rs index 670298d23..307d614bf 100644 --- a/embassy-stm32/src/timer/low_level.rs +++ b/embassy-stm32/src/timer/low_level.rs @@ -781,7 +781,7 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { let cc_channel = C::CHANNEL; let original_cc_dma_on_update = self.get_cc_dma_selection() == Ccds::ON_UPDATE; - let original_cc_dma_enabled = self.get_c fsc_dma_enable_state(cc_channel); + let original_cc_dma_enabled = self.get_cc_dma_enable_state(cc_channel); // redirect CC DMA request onto Update Event if !original_cc_dma_on_update { @@ -810,7 +810,12 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { /// Generate a sequence of PWM waveform that will run continously /// You may want to start this in a new thread as this will block forever + pub async fn waveform_continuous(&mut self, dma: Peri<'_, impl super::Dma>, duty: &[u16]) { + + #[cfg(any(bdma, gpdma))] + panic!("unsupported DMA"); + use crate::pac::timer::vals::Ccds; #[allow(clippy::let_unit_value)] // eg. stm32f334 @@ -871,6 +876,7 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { fifo_threshold: Some(FifoThreshold::Full), #[cfg(not(any(bdma, gpdma)))] mburst: Burst::Incr8, + #[cfg(not(any(bdma, gpdma)))] circular: circular, ..Default::default() }; @@ -881,7 +887,7 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { dma, req, duty, - self.regs_1ch().ccr(channel.index()).as_ptr() as *mut u16, + self.regs_gp16().ccr(channel.index()).as_ptr() as *mut u16, dma_transfer_option, ) .await @@ -896,7 +902,7 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { dma, req, duty, - self.regs_1ch().ccr(channel.index()).as_ptr() as *mut u32, + self.regs_gp16().ccr(channel.index()).as_ptr() as *mut u32, dma_transfer_option, ) .await diff --git a/examples/stm32f7/src/bin/pwm.rs b/examples/stm32f7/src/bin/pwm.rs new file mode 100644 index 000000000..872d99859 --- /dev/null +++ b/examples/stm32f7/src/bin/pwm.rs @@ -0,0 +1,65 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::time::Hertz; +use embassy_stm32::{Config, peripherals}; +use embassy_stm32::gpio::OutputType; +use embassy_stm32::time::mhz; +use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL200, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 200 / 2 = 200Mhz + divq: Some(PllQDiv::DIV4), // 8mhz / 4 * 200 / 4 = 100Mhz + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + } + let p = embassy_stm32::init(config); + let ch1_pin = PwmPin::new(p.PE9, OutputType::PushPull); + let ch2_pin = PwmPin::new(p.PE11, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), Some(ch2_pin), None, None, mhz(1), Default::default()); + let mut ch1 = pwm.ch1(); + ch1.enable(); + info!("PWM initialized"); + info!("PWM max duty {}", ch1.max_duty_cycle()); + + info!("PWM duty on channel 1 (D6) 50%"); + ch1.set_duty_cycle_fraction(1, 2); + info!("PWM waveform on channel 2 (D5)"); + const max_duty: usize = 200; + let mut duty = [0u16;max_duty]; + for i in 0..max_duty { + duty[i] = i as u16; + } + pwm.waveform_continuous::(p.DMA2_CH6, &duty).await; + + +} + -- cgit From 1a79bb51bf1490de5cc6f6ad021edd161c088b9f Mon Sep 17 00:00:00 2001 From: Eicke Hecht Date: Sun, 23 Nov 2025 23:38:38 +0100 Subject: wip: adding basic ringbuffered structure --- embassy-stm32/src/timer/mod.rs | 1 + embassy-stm32/src/timer/ringbuffered.rs | 47 +++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 embassy-stm32/src/timer/ringbuffered.rs diff --git a/embassy-stm32/src/timer/mod.rs b/embassy-stm32/src/timer/mod.rs index 804d1ef37..aef3598f1 100644 --- a/embassy-stm32/src/timer/mod.rs +++ b/embassy-stm32/src/timer/mod.rs @@ -13,6 +13,7 @@ pub mod one_pulse; pub mod pwm_input; pub mod qei; pub mod simple_pwm; +pub mod ringbuffered; use crate::interrupt; use crate::rcc::RccPeripheral; diff --git a/embassy-stm32/src/timer/ringbuffered.rs b/embassy-stm32/src/timer/ringbuffered.rs new file mode 100644 index 000000000..d20c5d532 --- /dev/null +++ b/embassy-stm32/src/timer/ringbuffered.rs @@ -0,0 +1,47 @@ +//! RingBuffered PWM driver. + +use core::mem::ManuallyDrop; + +use super::low_level::Timer; +use super::{Channel, GeneralInstance4Channel, TimerChannel, TimerPin}; +use crate::Peri; +use crate::dma::ringbuffer::WritableDmaRingBuffer; +use super::simple_pwm::SimplePwm; + +pub struct RingBufferedPwmChannel<'d, T: GeneralInstance4Channel> { + timer: ManuallyDrop>, + ring_buf: WritableDmaRingBuffer<'d, u8>, + channel: Channel, +} + +/// A group of four [`SimplePwmChannel`]s, obtained from [`SimplePwm::split`]. +pub struct RingBufferedPwmChannels<'d, T: GeneralInstance4Channel> { + /// Channel 1 + pub ch1: RingBufferedPwmChannel<'d, T>, + /// Channel 2 + pub ch2: RingBufferedPwmChannel<'d, T>, + /// Channel 3 + pub ch3: RingBufferedPwmChannel<'d, T>, + /// Channel 4 + pub ch4: RingBufferedPwmChannel<'d, T>, +} + +/// Simple PWM driver. +pub struct RingBufferedPwm<'d, T: GeneralInstance4Channel> { + inner: Timer<'d, T>, +} + +impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { + pub fn into_ring_buffered_channel(mut self, tx_dma: Peri<'_, impl super::Dma>, dma_buf: &'d mut [u8]) -> RingBufferedPwmChannel<'d> { + assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); + let ring_buf = WritableDmaRingBuffer::new(dma_buf); + let channel = C::CHANNEL; + RingBufferedPwmChannel { + timer: unsafe { self.inner.clone_unchecked() }, + channel, + ring_buf + } + + // let ring_buf = WriteableRingBuffer::new(); + } +} -- cgit From a227e61137a689ecd875c41a7efb5f2a6bb73876 Mon Sep 17 00:00:00 2001 From: Eicke Hecht Date: Sun, 23 Nov 2025 23:39:09 +0100 Subject: fix: Formatting and clippy --- examples/stm32f7/src/bin/pwm.rs | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/examples/stm32f7/src/bin/pwm.rs b/examples/stm32f7/src/bin/pwm.rs index 872d99859..53efc2d8a 100644 --- a/examples/stm32f7/src/bin/pwm.rs +++ b/examples/stm32f7/src/bin/pwm.rs @@ -1,15 +1,13 @@ #![no_std] #![no_main] -use defmt::{panic, *}; +use defmt::*; use embassy_executor::Spawner; -use embassy_futures::join::join; -use embassy_stm32::time::Hertz; -use embassy_stm32::{Config, peripherals}; +use embassy_stm32::Config; use embassy_stm32::gpio::OutputType; +use embassy_stm32::time::Hertz; use embassy_stm32::time::mhz; use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; -use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; // If you are trying this and your USB device doesn't connect, the most @@ -44,7 +42,15 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(config); let ch1_pin = PwmPin::new(p.PE9, OutputType::PushPull); let ch2_pin = PwmPin::new(p.PE11, OutputType::PushPull); - let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), Some(ch2_pin), None, None, mhz(1), Default::default()); + let mut pwm = SimplePwm::new( + p.TIM1, + Some(ch1_pin), + Some(ch2_pin), + None, + None, + mhz(1), + Default::default(), + ); let mut ch1 = pwm.ch1(); ch1.enable(); info!("PWM initialized"); @@ -53,13 +59,11 @@ async fn main(_spawner: Spawner) { info!("PWM duty on channel 1 (D6) 50%"); ch1.set_duty_cycle_fraction(1, 2); info!("PWM waveform on channel 2 (D5)"); - const max_duty: usize = 200; - let mut duty = [0u16;max_duty]; - for i in 0..max_duty { + const MAX_DUTY: usize = 200; + let mut duty = [0u16; MAX_DUTY]; + for i in 0..MAX_DUTY { duty[i] = i as u16; } - pwm.waveform_continuous::(p.DMA2_CH6, &duty).await; - - + pwm.waveform_continuous::(p.DMA2_CH6, &duty) + .await; } - -- cgit From d45376583386272bc49fecc7eed8951067f84ac8 Mon Sep 17 00:00:00 2001 From: Eicke Hecht Date: Mon, 24 Nov 2025 21:42:54 +0100 Subject: feat: Implement basic ring buffered PWM channel --- embassy-stm32/src/timer/low_level.rs | 2 - embassy-stm32/src/timer/ringbuffered.rs | 151 +++++++++++++++++++++++++++----- embassy-stm32/src/timer/simple_pwm.rs | 35 ++++++++ 3 files changed, 163 insertions(+), 25 deletions(-) diff --git a/embassy-stm32/src/timer/low_level.rs b/embassy-stm32/src/timer/low_level.rs index 307d614bf..2cee5f1f5 100644 --- a/embassy-stm32/src/timer/low_level.rs +++ b/embassy-stm32/src/timer/low_level.rs @@ -813,8 +813,6 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { pub async fn waveform_continuous(&mut self, dma: Peri<'_, impl super::Dma>, duty: &[u16]) { - #[cfg(any(bdma, gpdma))] - panic!("unsupported DMA"); use crate::pac::timer::vals::Ccds; diff --git a/embassy-stm32/src/timer/ringbuffered.rs b/embassy-stm32/src/timer/ringbuffered.rs index d20c5d532..bb602f8a7 100644 --- a/embassy-stm32/src/timer/ringbuffered.rs +++ b/embassy-stm32/src/timer/ringbuffered.rs @@ -1,19 +1,142 @@ //! RingBuffered PWM driver. use core::mem::ManuallyDrop; +use core::task::Waker; use super::low_level::Timer; -use super::{Channel, GeneralInstance4Channel, TimerChannel, TimerPin}; -use crate::Peri; -use crate::dma::ringbuffer::WritableDmaRingBuffer; -use super::simple_pwm::SimplePwm; +use super::{Channel, GeneralInstance4Channel}; +use crate::dma::ringbuffer::Error; +use crate::dma::WritableRingBuffer; pub struct RingBufferedPwmChannel<'d, T: GeneralInstance4Channel> { timer: ManuallyDrop>, - ring_buf: WritableDmaRingBuffer<'d, u8>, + ring_buf: WritableRingBuffer<'d, u16>, channel: Channel, } +impl<'d, T: GeneralInstance4Channel> RingBufferedPwmChannel<'d, T> { + pub(crate) fn new(timer: ManuallyDrop>, channel: Channel, ring_buf: WritableRingBuffer<'d, u16>) -> Self { + Self { + timer, + ring_buf, + channel, + } + } + + /// Start the ring buffer operation. + /// + /// You must call this after creating it for it to work. + pub fn start(&mut self) { + self.ring_buf.start() + } + + /// Clear all data in the ring buffer. + pub fn clear(&mut self) { + self.ring_buf.clear() + } + + /// Write elements directly to the raw buffer. This can be used to fill the buffer before starting the DMA transfer. + pub fn write_immediate(&mut self, buf: &[u16]) -> Result<(usize, usize), Error> { + self.ring_buf.write_immediate(buf) + } + + /// Write elements from the ring buffer + /// Return a tuple of the length written and the length remaining in the buffer + pub fn write(&mut self, buf: &[u16]) -> Result<(usize, usize), Error> { + self.ring_buf.write(buf) + } + + /// Write an exact number of elements to the ringbuffer. + pub async fn write_exact(&mut self, buffer: &[u16]) -> Result { + self.ring_buf.write_exact(buffer).await + } + + /// Wait for any ring buffer write error. + pub async fn wait_write_error(&mut self) -> Result { + self.ring_buf.wait_write_error().await + } + + /// The current length of the ringbuffer + pub fn len(&mut self) -> Result { + self.ring_buf.len() + } + + /// The capacity of the ringbuffer + pub const fn capacity(&self) -> usize { + self.ring_buf.capacity() + } + + /// Set a waker to be woken when at least one byte is send. + pub fn set_waker(&mut self, waker: &Waker) { + self.ring_buf.set_waker(waker) + } + + /// Request the DMA to reset. The configuration for this channel will not be preserved. + /// + /// This doesn't immediately stop the transfer, you have to wait until is_running returns false. + pub fn request_reset(&mut self) { + self.ring_buf.request_reset() + } + + /// Request the transfer to pause, keeping the existing configuration for this channel. + /// To restart the transfer, call [`start`](Self::start) again. + /// + /// This doesn't immediately stop the transfer, you have to wait until is_running returns false. + pub fn request_pause(&mut self) { + self.ring_buf.request_pause() + } + + /// Return whether DMA is still running. + /// + /// If this returns false, it can be because either the transfer finished, or it was requested to stop early with request_stop. + pub fn is_running(&mut self) -> bool { + self.ring_buf.is_running() + } + + /// Stop the DMA transfer and await until the buffer is empty. + /// + /// This disables the DMA transfer's circular mode so that the transfer stops when all available data has been written. + /// + /// This is designed to be used with streaming output data such as the I2S/SAI or DAC. + pub async fn stop(&mut self) { + self.ring_buf.stop().await + } + + /// Enable the given channel. + pub fn enable(&mut self) { + self.timer.enable_channel(self.channel, true); + } + + /// Disable the given channel. + pub fn disable(&mut self) { + self.timer.enable_channel(self.channel, false); + } + + /// Check whether given channel is enabled + pub fn is_enabled(&self) -> bool { + self.timer.get_channel_enable_state(self.channel) + } + + /// Get max duty value. + /// + /// This value depends on the configured frequency and the timer's clock rate from RCC. + pub fn max_duty_cycle(&self) -> u16 { + let max = self.timer.get_max_compare_value(); + assert!(max < u16::MAX as u32); + max as u16 + 1 + } + + /// Set the output polarity for a given channel. + pub fn set_polarity(&mut self, polarity: super::low_level::OutputPolarity) { + self.timer.set_output_polarity(self.channel, polarity); + } + + /// Set the output compare mode for a given channel. + pub fn set_output_compare_mode(&mut self, mode: super::low_level::OutputCompareMode) { + self.timer.set_output_compare_mode(self.channel, mode); + } +} + /// A group of four [`SimplePwmChannel`]s, obtained from [`SimplePwm::split`]. pub struct RingBufferedPwmChannels<'d, T: GeneralInstance4Channel> { /// Channel 1 @@ -26,22 +149,4 @@ pub struct RingBufferedPwmChannels<'d, T: GeneralInstance4Channel> { pub ch4: RingBufferedPwmChannel<'d, T>, } -/// Simple PWM driver. -pub struct RingBufferedPwm<'d, T: GeneralInstance4Channel> { - inner: Timer<'d, T>, -} - -impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { - pub fn into_ring_buffered_channel(mut self, tx_dma: Peri<'_, impl super::Dma>, dma_buf: &'d mut [u8]) -> RingBufferedPwmChannel<'d> { - assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); - let ring_buf = WritableDmaRingBuffer::new(dma_buf); - let channel = C::CHANNEL; - RingBufferedPwmChannel { - timer: unsafe { self.inner.clone_unchecked() }, - channel, - ring_buf - } - // let ring_buf = WriteableRingBuffer::new(); - } -} diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 56d00ea59..53e0345ea 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -4,8 +4,13 @@ use core::marker::PhantomData; use core::mem::ManuallyDrop; use super::low_level::{CountingMode, OutputCompareMode, OutputPolarity, Timer}; +use super::ringbuffered::RingBufferedPwmChannel; use super::{Ch1, Ch2, Ch3, Ch4, Channel, GeneralInstance4Channel, TimerChannel, TimerPin}; +use crate::dma::WritableRingBuffer; use crate::Peri; +#[cfg(not(any(bdma, gpdma)))] +use crate::dma::{Burst, FifoThreshold}; +use crate::dma::TransferOptions; #[cfg(gpio_v2)] use crate::gpio::Pull; use crate::gpio::{AfType, AnyPin, OutputType, Speed}; @@ -374,6 +379,36 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { pub async fn waveform_continuous(&mut self, dma: Peri<'_, impl super::Dma>, duty: &[u16]) { self.inner.waveform_continuous(dma, duty).await; } + pub fn into_ring_buffered_channel(self, tx_dma: Peri<'d, impl super::Dma>, dma_buf: &'d mut [u16]) -> RingBufferedPwmChannel<'d, T> { + assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); + + let channel = C::CHANNEL; + let request = tx_dma.request(); + + let opts = TransferOptions { + #[cfg(not(any(bdma, gpdma)))] + fifo_threshold: Some(FifoThreshold::Full), + #[cfg(not(any(bdma, gpdma)))] + mburst: Burst::Incr8, + ..Default::default() + }; + + let ring_buf = unsafe { + WritableRingBuffer::new( + tx_dma, + request, + self.inner.regs_gp16().ccr(channel.index()).as_ptr() as *mut u16, + dma_buf, + opts, + ) + }; + + RingBufferedPwmChannel::new( + unsafe { self.inner.clone_unchecked() }, + channel, + ring_buf + ) + } } impl<'d, T: GeneralInstance4Channel> embedded_hal_1::pwm::ErrorType for SimplePwmChannel<'d, T> { -- cgit From 05417e91093dfd7e150083738988259d66ee4e37 Mon Sep 17 00:00:00 2001 From: Eicke Hecht Date: Mon, 24 Nov 2025 21:54:25 +0100 Subject: fix: Warnings and formatting all fixed --- embassy-stm32/src/timer/low_level.rs | 4 +--- embassy-stm32/src/timer/ringbuffered.rs | 25 +++++++++++++++++++++---- embassy-stm32/src/timer/simple_pwm.rs | 31 +++++++++++++++++++++++-------- 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/embassy-stm32/src/timer/low_level.rs b/embassy-stm32/src/timer/low_level.rs index 2cee5f1f5..d1cf11386 100644 --- a/embassy-stm32/src/timer/low_level.rs +++ b/embassy-stm32/src/timer/low_level.rs @@ -812,8 +812,6 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { /// You may want to start this in a new thread as this will block forever pub async fn waveform_continuous(&mut self, dma: Peri<'_, impl super::Dma>, duty: &[u16]) { - - use crate::pac::timer::vals::Ccds; #[allow(clippy::let_unit_value)] // eg. stm32f334 @@ -855,7 +853,7 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { req: dma::Request, channel: Channel, duty: &[u16], - circular: bool, + #[allow(unused_variables)] circular: bool, ) { let original_duty_state = self.get_compare_value(channel); let original_enable_state = self.get_channel_enable_state(channel); diff --git a/embassy-stm32/src/timer/ringbuffered.rs b/embassy-stm32/src/timer/ringbuffered.rs index bb602f8a7..e8f97bf59 100644 --- a/embassy-stm32/src/timer/ringbuffered.rs +++ b/embassy-stm32/src/timer/ringbuffered.rs @@ -5,9 +5,24 @@ use core::task::Waker; use super::low_level::Timer; use super::{Channel, GeneralInstance4Channel}; -use crate::dma::ringbuffer::Error; use crate::dma::WritableRingBuffer; +use crate::dma::ringbuffer::Error; +/// A PWM channel that uses a DMA ring buffer for continuous waveform generation. +/// +/// This allows you to continuously update PWM duty cycles via DMA without blocking the CPU. +/// The ring buffer enables smooth, uninterrupted waveform generation by automatically cycling +/// through duty cycle values stored in memory. +/// +/// You can write new duty cycle values to the ring buffer while it's running, enabling +/// dynamic waveform generation for applications like motor control, LED dimming, or audio output. +/// +/// # Example +/// ```ignore +/// let mut channel = pwm.ch1().into_ring_buffered_channel(dma_ch, &mut buffer); +/// channel.start(); // Start DMA transfer +/// channel.write(&[100, 200, 300]).ok(); // Update duty cycles +/// ``` pub struct RingBufferedPwmChannel<'d, T: GeneralInstance4Channel> { timer: ManuallyDrop>, ring_buf: WritableRingBuffer<'d, u16>, @@ -15,7 +30,11 @@ pub struct RingBufferedPwmChannel<'d, T: GeneralInstance4Channel> { } impl<'d, T: GeneralInstance4Channel> RingBufferedPwmChannel<'d, T> { - pub(crate) fn new(timer: ManuallyDrop>, channel: Channel, ring_buf: WritableRingBuffer<'d, u16>) -> Self { + pub(crate) fn new( + timer: ManuallyDrop>, + channel: Channel, + ring_buf: WritableRingBuffer<'d, u16>, + ) -> Self { Self { timer, ring_buf, @@ -148,5 +167,3 @@ pub struct RingBufferedPwmChannels<'d, T: GeneralInstance4Channel> { /// Channel 4 pub ch4: RingBufferedPwmChannel<'d, T>, } - - diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 53e0345ea..f4656b7bd 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -6,11 +6,11 @@ use core::mem::ManuallyDrop; use super::low_level::{CountingMode, OutputCompareMode, OutputPolarity, Timer}; use super::ringbuffered::RingBufferedPwmChannel; use super::{Ch1, Ch2, Ch3, Ch4, Channel, GeneralInstance4Channel, TimerChannel, TimerPin}; -use crate::dma::WritableRingBuffer; use crate::Peri; +use crate::dma::TransferOptions; +use crate::dma::WritableRingBuffer; #[cfg(not(any(bdma, gpdma)))] use crate::dma::{Burst, FifoThreshold}; -use crate::dma::TransferOptions; #[cfg(gpio_v2)] use crate::gpio::Pull; use crate::gpio::{AfType, AnyPin, OutputType, Speed}; @@ -379,9 +379,27 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { pub async fn waveform_continuous(&mut self, dma: Peri<'_, impl super::Dma>, duty: &[u16]) { self.inner.waveform_continuous(dma, duty).await; } - pub fn into_ring_buffered_channel(self, tx_dma: Peri<'d, impl super::Dma>, dma_buf: &'d mut [u16]) -> RingBufferedPwmChannel<'d, T> { + + /// Convert this PWM channel into a ring-buffered PWM channel. + /// + /// This allows continuous PWM waveform generation using a DMA ring buffer. + /// The ring buffer enables dynamic updates to the PWM duty cycle without blocking. + /// + /// # Arguments + /// * `tx_dma` - The DMA channel to use for transferring duty cycle values + /// * `dma_buf` - The buffer to use as a ring buffer (must be non-empty and <= 65535 elements) + /// + /// # Panics + /// Panics if `dma_buf` is empty or longer than 65535 elements. + pub fn into_ring_buffered_channel( + self, + tx_dma: Peri<'d, impl super::Dma>, + dma_buf: &'d mut [u16], + ) -> RingBufferedPwmChannel<'d, T> { assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); + use crate::pac::timer::vals::Ccds; + let channel = C::CHANNEL; let request = tx_dma.request(); @@ -393,6 +411,7 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { ..Default::default() }; + self.inner.set_cc_dma_selection(Ccds::ON_UPDATE); let ring_buf = unsafe { WritableRingBuffer::new( tx_dma, @@ -403,11 +422,7 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { ) }; - RingBufferedPwmChannel::new( - unsafe { self.inner.clone_unchecked() }, - channel, - ring_buf - ) + RingBufferedPwmChannel::new(unsafe { self.inner.clone_unchecked() }, channel, ring_buf) } } -- cgit From 6a97b6718e1389ce9e5dd3fb989b3b9f3fcfbd09 Mon Sep 17 00:00:00 2001 From: Eicke Hecht Date: Mon, 24 Nov 2025 22:31:04 +0100 Subject: wip: Add a working example for the stm32f767zi nucleo. Currently no PWM signal visible... --- embassy-stm32/src/timer/simple_pwm.rs | 2 + examples/stm32f7/src/bin/pwm.rs | 31 +++--- examples/stm32f7/src/bin/pwm_ringbuffer.rs | 157 +++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 19 deletions(-) create mode 100644 examples/stm32f7/src/bin/pwm_ringbuffer.rs diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index f4656b7bd..8b738741a 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -412,6 +412,8 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { }; self.inner.set_cc_dma_selection(Ccds::ON_UPDATE); + self.inner.set_cc_dma_enable_state(channel, true); + let ring_buf = unsafe { WritableRingBuffer::new( tx_dma, diff --git a/examples/stm32f7/src/bin/pwm.rs b/examples/stm32f7/src/bin/pwm.rs index 53efc2d8a..c1c7ec6ce 100644 --- a/examples/stm32f7/src/bin/pwm.rs +++ b/examples/stm32f7/src/bin/pwm.rs @@ -8,6 +8,7 @@ use embassy_stm32::gpio::OutputType; use embassy_stm32::time::Hertz; use embassy_stm32::time::mhz; use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; +use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; // If you are trying this and your USB device doesn't connect, the most @@ -41,29 +42,21 @@ async fn main(_spawner: Spawner) { } let p = embassy_stm32::init(config); let ch1_pin = PwmPin::new(p.PE9, OutputType::PushPull); - let ch2_pin = PwmPin::new(p.PE11, OutputType::PushPull); - let mut pwm = SimplePwm::new( - p.TIM1, - Some(ch1_pin), - Some(ch2_pin), - None, - None, - mhz(1), - Default::default(), - ); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, mhz(1), Default::default()); let mut ch1 = pwm.ch1(); ch1.enable(); + info!("PWM initialized"); info!("PWM max duty {}", ch1.max_duty_cycle()); - info!("PWM duty on channel 1 (D6) 50%"); - ch1.set_duty_cycle_fraction(1, 2); - info!("PWM waveform on channel 2 (D5)"); - const MAX_DUTY: usize = 200; - let mut duty = [0u16; MAX_DUTY]; - for i in 0..MAX_DUTY { - duty[i] = i as u16; + loop { + ch1.set_duty_cycle_fully_off(); + Timer::after_millis(300).await; + ch1.set_duty_cycle_fraction(1, 4); + Timer::after_millis(300).await; + ch1.set_duty_cycle_fraction(1, 2); + Timer::after_millis(300).await; + ch1.set_duty_cycle(ch1.max_duty_cycle() - 1); + Timer::after_millis(300).await; } - pwm.waveform_continuous::(p.DMA2_CH6, &duty) - .await; } diff --git a/examples/stm32f7/src/bin/pwm_ringbuffer.rs b/examples/stm32f7/src/bin/pwm_ringbuffer.rs new file mode 100644 index 000000000..a8744f190 --- /dev/null +++ b/examples/stm32f7/src/bin/pwm_ringbuffer.rs @@ -0,0 +1,157 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::Config; +use embassy_stm32::gpio::OutputType; +use embassy_stm32::time::mhz; +use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("PWM Ring Buffer Example"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + use embassy_stm32::time::Hertz; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL200, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 200 / 2 = 200Mhz + divq: Some(PllQDiv::DIV4), // 8mhz / 4 * 200 / 4 = 100Mhz + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + } + let p = embassy_stm32::init(config); + + // Initialize PWM on TIM1 + let ch1_pin = PwmPin::new(p.PE9, OutputType::PushPull); + let ch2_pin = PwmPin::new(p.PE11, OutputType::PushPull); + let mut pwm = SimplePwm::new( + p.TIM1, + Some(ch1_pin), + Some(ch2_pin), + None, + None, + mhz(1), + Default::default(), + ); + + // Use channel 1 for static PWM at 50% + let mut ch1 = pwm.ch1(); + ch1.enable(); + ch1.set_duty_cycle_fraction(1, 2); + info!("Channel 1 (PE9/D6): Static 50% duty cycle"); + + // Get max duty from channel 1 before converting channel 2 + let max_duty = ch1.max_duty_cycle(); + info!("PWM max duty: {}", max_duty); + + // Create a DMA ring buffer for channel 2 + const BUFFER_SIZE: usize = 128; + static mut DMA_BUFFER: [u16; BUFFER_SIZE] = [0u16; BUFFER_SIZE]; + let dma_buffer = unsafe { &mut *core::ptr::addr_of_mut!(DMA_BUFFER) }; + + // Pre-fill buffer with initial sine wave using lookup table approach + for i in 0..BUFFER_SIZE { + // Simple sine approximation using triangle wave + let phase = (i * 256) / BUFFER_SIZE; + let sine_approx = if phase < 128 { + phase as u16 * 2 + } else { + (255 - phase) as u16 * 2 + }; + dma_buffer[i] = (sine_approx as u32 * max_duty as u32 / 256) as u16; + } + + // Convert channel 2 to ring-buffered PWM + let mut ring_pwm = pwm.into_ring_buffered_channel::(p.DMA2_CH6, dma_buffer); + + info!("Ring buffer capacity: {}", ring_pwm.capacity()); + + // Enable the PWM channel output + ring_pwm.enable(); + + // Pre-write some initial data to the buffer before starting + info!("Pre-writing initial waveform data..."); + + // Start the DMA ring buffer + ring_pwm.start(); + info!("Channel 2 (PE11/D5): Ring buffered sine wave started"); + + // Give DMA time to start consuming + Timer::after_millis(10).await; + + // Continuously update the waveform + let mut phase: f32 = 0.0; + let mut amplitude: f32 = 1.0; + let mut amplitude_direction = -0.05; + + loop { + Timer::after_millis(50).await; + + // Generate new waveform data with varying amplitude + let mut new_data = [0u16; 32]; + for i in 0..new_data.len() { + // Triangle wave approximation for sine + let pos = ((i as u32 + phase as u32) * 4) % 256; + let sine_approx = if pos < 128 { + pos as u16 * 2 + } else { + (255 - pos) as u16 * 2 + }; + let scaled = (sine_approx as u32 * (amplitude * 256.0) as u32) / (256 * 256); + new_data[i] = ((scaled * max_duty as u32) / 256) as u16; + } + + // Write new data to the ring buffer + match ring_pwm.write(&new_data) { + Ok((written, _remaining)) => { + if written < new_data.len() { + info!("Ring buffer getting full, wrote {} of {}", written, new_data.len()); + } + } + Err(e) => { + info!("Write error: {:?}", e); + } + } + + // Update phase for animation effect + phase += 2.0; + if phase >= 64.0 { + phase = 0.0; + } + + // Vary amplitude for breathing effect + amplitude += amplitude_direction; + if amplitude <= 0.2 || amplitude >= 1.0 { + amplitude_direction = -amplitude_direction; + } + + // Log buffer status periodically + if (phase as u32) % 10 == 0 { + match ring_pwm.len() { + Ok(len) => info!("Ring buffer fill: {}/{}", len, ring_pwm.capacity()), + Err(_) => info!("Error reading buffer length"), + } + } + } +} -- cgit From 96ffbec30a0d17470dd93a6c50f4264060bf1e69 Mon Sep 17 00:00:00 2001 From: Eicke Hecht Date: Mon, 24 Nov 2025 22:44:56 +0100 Subject: fix: formatting --- embassy-stm32/src/timer/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-stm32/src/timer/mod.rs b/embassy-stm32/src/timer/mod.rs index aef3598f1..3fa363881 100644 --- a/embassy-stm32/src/timer/mod.rs +++ b/embassy-stm32/src/timer/mod.rs @@ -12,8 +12,8 @@ pub mod low_level; pub mod one_pulse; pub mod pwm_input; pub mod qei; -pub mod simple_pwm; pub mod ringbuffered; +pub mod simple_pwm; use crate::interrupt; use crate::rcc::RccPeripheral; -- cgit From ff939095a2a883361346ea0cf6f2b6f9d1d24936 Mon Sep 17 00:00:00 2001 From: Eicke Hecht Date: Mon, 24 Nov 2025 23:03:02 +0100 Subject: fix: Formatting, use nightly... --- embassy-stm32/src/timer/simple_pwm.rs | 3 +-- examples/stm32f7/src/bin/pwm.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 8b738741a..bbe7ef595 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -7,10 +7,9 @@ use super::low_level::{CountingMode, OutputCompareMode, OutputPolarity, Timer}; use super::ringbuffered::RingBufferedPwmChannel; use super::{Ch1, Ch2, Ch3, Ch4, Channel, GeneralInstance4Channel, TimerChannel, TimerPin}; use crate::Peri; -use crate::dma::TransferOptions; -use crate::dma::WritableRingBuffer; #[cfg(not(any(bdma, gpdma)))] use crate::dma::{Burst, FifoThreshold}; +use crate::dma::{TransferOptions, WritableRingBuffer}; #[cfg(gpio_v2)] use crate::gpio::Pull; use crate::gpio::{AfType, AnyPin, OutputType, Speed}; diff --git a/examples/stm32f7/src/bin/pwm.rs b/examples/stm32f7/src/bin/pwm.rs index c1c7ec6ce..b071eb597 100644 --- a/examples/stm32f7/src/bin/pwm.rs +++ b/examples/stm32f7/src/bin/pwm.rs @@ -5,8 +5,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::Config; use embassy_stm32::gpio::OutputType; -use embassy_stm32::time::Hertz; -use embassy_stm32::time::mhz; +use embassy_stm32::time::{Hertz, mhz}; use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -- cgit From 883a998b8da7274d1c157157d5a5d4110904a93c Mon Sep 17 00:00:00 2001 From: Alex Strandberg <8642246+alexstrandberg@users.noreply.github.com> Date: Mon, 24 Nov 2025 17:03:29 -0500 Subject: embassy-rp: add support for TX-only, no SCK SPI --- embassy-rp/CHANGELOG.md | 1 + embassy-rp/src/spi.rs | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/embassy-rp/CHANGELOG.md b/embassy-rp/CHANGELOG.md index 4b0d738a7..fa8609bbf 100644 --- a/embassy-rp/CHANGELOG.md +++ b/embassy-rp/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add reset_to_usb_boot for rp235x ([#4705](https://github.com/embassy-rs/embassy/pull/4705)) - 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)) - Add PIO::Ws2812 color order support +- Add TX-only, no SCK SPI support ## 0.8.0 - 2025-08-26 diff --git a/embassy-rp/src/spi.rs b/embassy-rp/src/spi.rs index d9410e78d..39f128214 100644 --- a/embassy-rp/src/spi.rs +++ b/embassy-rp/src/spi.rs @@ -308,6 +308,11 @@ impl<'d, T: Instance> Spi<'d, T, Blocking> { ) } + /// Create an SPI driver in blocking mode supporting writes only, without SCK pin. + pub fn new_blocking_txonly_nosck(inner: Peri<'d, T>, mosi: Peri<'d, impl MosiPin + 'd>, config: Config) -> Self { + Self::new_inner(inner, None, Some(mosi.into()), None, None, None, None, config) + } + /// Create an SPI driver in blocking mode supporting reads only. pub fn new_blocking_rxonly( inner: Peri<'d, T>, @@ -371,6 +376,26 @@ impl<'d, T: Instance> Spi<'d, T, Async> { ) } + /// Create an SPI driver in async mode supporting DMA write operations only, + /// without SCK pin. + pub fn new_txonly_nosck( + inner: Peri<'d, T>, + mosi: Peri<'d, impl MosiPin + 'd>, + tx_dma: Peri<'d, impl Channel>, + config: Config, + ) -> Self { + Self::new_inner( + inner, + None, + Some(mosi.into()), + None, + None, + Some(tx_dma.into()), + None, + config, + ) + } + /// Create an SPI driver in async mode supporting DMA read operations only. pub fn new_rxonly( inner: Peri<'d, T>, -- cgit From 6f2ad3b696936ff2a3995fda3c8aee5728bd8f53 Mon Sep 17 00:00:00 2001 From: Haobo Gu Date: Tue, 25 Nov 2025 11:18:01 +0800 Subject: chore: bump usbd-hid version Signed-off-by: Haobo Gu --- embassy-usb/CHANGELOG.md | 1 + embassy-usb/Cargo.toml | 2 +- examples/nrf52840/Cargo.toml | 2 +- examples/nrf5340/Cargo.toml | 2 +- examples/rp/Cargo.toml | 2 +- examples/rp235x/Cargo.toml | 2 +- examples/stm32f4/Cargo.toml | 2 +- examples/stm32g4/Cargo.toml | 2 +- examples/stm32l5/Cargo.toml | 2 +- 9 files changed, 9 insertions(+), 8 deletions(-) diff --git a/embassy-usb/CHANGELOG.md b/embassy-usb/CHANGELOG.md index cfb1bf021..3dd71ffbc 100644 --- a/embassy-usb/CHANGELOG.md +++ b/embassy-usb/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate - Add support for USB HID Boot Protocol Mode +- Bump usbd-hid from 0.8.1 to 0.9.0 ## 0.5.1 - 2025-08-26 diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index aeb7392f1..3d1e005e4 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -69,5 +69,5 @@ heapless = "0.8" embedded-io-async = "0.6.1" # for HID -usbd-hid = { version = "0.8.1", optional = true } +usbd-hid = { version = "0.9.0", optional = true } ssmarshal = { version = "1.0", default-features = false, optional = true } diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index a026d6352..1fe3d2419 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml @@ -28,7 +28,7 @@ cortex-m-rt = "0.7.0" panic-probe = { version = "1.0.0", features = ["print-defmt"] } rand = { version = "0.9.0", default-features = false } embedded-storage = "0.3.1" -usbd-hid = "0.8.1" +usbd-hid = "0.9.0" serde = { version = "1.0.136", default-features = false } embedded-hal = { version = "1.0" } embedded-hal-async = { version = "1.0" } diff --git a/examples/nrf5340/Cargo.toml b/examples/nrf5340/Cargo.toml index 4dcbdd715..97efe58e8 100644 --- a/examples/nrf5340/Cargo.toml +++ b/examples/nrf5340/Cargo.toml @@ -23,7 +23,7 @@ cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-sing cortex-m-rt = "0.7.0" panic-probe = { version = "1.0.0", features = ["print-defmt"] } embedded-storage = "0.3.1" -usbd-hid = "0.8.1" +usbd-hid = "0.9.0" serde = { version = "1.0.136", default-features = false } [profile.release] diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index 640addb28..9d7d99259 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -45,7 +45,7 @@ display-interface = "0.5.0" byte-slice-cast = { version = "1.2.0", default-features = false } smart-leds = "0.4.0" heapless = "0.8" -usbd-hid = "0.8.1" +usbd-hid = "0.9.0" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = "1.0" diff --git a/examples/rp235x/Cargo.toml b/examples/rp235x/Cargo.toml index 39a4f421a..ad396275b 100644 --- a/examples/rp235x/Cargo.toml +++ b/examples/rp235x/Cargo.toml @@ -46,7 +46,7 @@ display-interface = "0.5.0" byte-slice-cast = { version = "1.2.0", default-features = false } smart-leds = "0.3.0" heapless = "0.8" -usbd-hid = "0.8.1" +usbd-hid = "0.9.0" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = "1.0" diff --git a/examples/stm32f4/Cargo.toml b/examples/stm32f4/Cargo.toml index d06b7505c..b4555045a 100644 --- a/examples/stm32f4/Cargo.toml +++ b/examples/stm32f4/Cargo.toml @@ -32,7 +32,7 @@ critical-section = "1.1" nb = "1.0.0" embedded-storage = "0.3.1" micromath = "2.0.0" -usbd-hid = "0.8.1" +usbd-hid = "0.9.0" static_cell = "2" chrono = { version = "^0.4", default-features = false} diff --git a/examples/stm32g4/Cargo.toml b/examples/stm32g4/Cargo.toml index 8bbeb594c..d1c19582b 100644 --- a/examples/stm32g4/Cargo.toml +++ b/examples/stm32g4/Cargo.toml @@ -13,7 +13,7 @@ embassy-executor = { path = "../../embassy-executor", features = ["arch-cortex-m embassy-time = { path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { path = "../../embassy-futures" } -usbd-hid = "0.8.1" +usbd-hid = "0.9.0" defmt = "1.0.1" defmt-rtt = "1.0.0" diff --git a/examples/stm32l5/Cargo.toml b/examples/stm32l5/Cargo.toml index b6158c854..586b00836 100644 --- a/examples/stm32l5/Cargo.toml +++ b/examples/stm32l5/Cargo.toml @@ -14,7 +14,7 @@ embassy-time = { version = "0.5.0", path = "../../embassy-time", features = ["de embassy-usb = { version = "0.5.1", path = "../../embassy-usb", features = ["defmt"] } embassy-net = { version = "0.7.1", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } embassy-futures = { version = "0.1.2", path = "../../embassy-futures" } -usbd-hid = "0.8.1" +usbd-hid = "0.9.0" defmt = "1.0.1" defmt-rtt = "1.0.0" -- cgit From 9330b56e651a864f35e1e72861245a6d221ee725 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Tue, 25 Nov 2025 12:40:27 +0100 Subject: feat: add ppi events for SPIS peripheral --- embassy-nrf/CHANGELOG.md | 1 + embassy-nrf/src/spis.rs | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/embassy-nrf/CHANGELOG.md b/embassy-nrf/CHANGELOG.md index be79bde5d..cfb040ef5 100644 --- a/embassy-nrf/CHANGELOG.md +++ b/embassy-nrf/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - changed: `gpiote::InputChannel::wait()` now ensures events are seen as soon as the function is called, even if the future is not polled - bugfix: use correct flash size for nRF54l - changed: add workaround for anomaly 66 on nrf52 +- added: expose PPI events available on SPIS peripheral ## 0.8.0 - 2025-09-30 diff --git a/embassy-nrf/src/spis.rs b/embassy-nrf/src/spis.rs index 96a9c0ae0..6f837c317 100644 --- a/embassy-nrf/src/spis.rs +++ b/embassy-nrf/src/spis.rs @@ -17,6 +17,7 @@ use crate::gpio::{self, AnyPin, OutputDrive, Pin as GpioPin, SealedPin as _, con use crate::interrupt::typelevel::Interrupt; use crate::pac::gpio::vals as gpiovals; use crate::pac::spis::vals; +use crate::ppi::Event; use crate::util::slice_in_ram_or; use crate::{interrupt, pac}; @@ -334,6 +335,20 @@ impl<'d> Spis<'d> { Ok((n_rx, n_tx)) } + /// Returns the ACQUIRED event, for use with PPI. + /// + /// This event will fire when the semaphore is acquired. + pub fn event_acquired(&self) -> Event<'d> { + Event::from_reg(self.r.events_acquired()) + } + + /// Returns the END event, for use with PPI. + /// + /// This event will fire when the slave transaction is complete. + pub fn event_end(&self) -> Event<'d> { + Event::from_reg(self.r.events_end()) + } + async fn async_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(usize, usize), Error> { match self.async_inner_from_ram(rx, tx).await { Ok(n) => Ok(n), -- cgit From 424d9d3aa961d4170be96ac23331aa5a3cba3e5b Mon Sep 17 00:00:00 2001 From: xoviat Date: Tue, 25 Nov 2025 08:23:53 -0600 Subject: stm32: remove waveform method --- embassy-stm32/CHANGELOG.md | 1 + embassy-stm32/src/timer/complementary_pwm.rs | 6 ----- embassy-stm32/src/timer/low_level.rs | 37 ---------------------------- embassy-stm32/src/timer/simple_pwm.rs | 6 ----- 4 files changed, 1 insertion(+), 49 deletions(-) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 6140b3238..949ea03b5 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate +- change: remove waveform timer method - change: low power: store stop mode for dma channels - fix: Fixed ADC4 enable() for WBA - feat: allow use of anyadcchannel for adc4 diff --git a/embassy-stm32/src/timer/complementary_pwm.rs b/embassy-stm32/src/timer/complementary_pwm.rs index 6d4c70dff..9f34f3ec7 100644 --- a/embassy-stm32/src/timer/complementary_pwm.rs +++ b/embassy-stm32/src/timer/complementary_pwm.rs @@ -266,12 +266,6 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { .waveform_up_multi_channel(dma, starting_channel, ending_channel, duty) .await; } - - /// Generate a sequence of PWM waveform - #[inline(always)] - pub async fn waveform(&mut self, dma: Peri<'_, impl super::Dma>, duty: &[u16]) { - self.inner.waveform(dma, duty).await; - } } impl<'d, T: AdvancedInstance4Channel> embedded_hal_02::Pwm for ComplementaryPwm<'d, T> { diff --git a/embassy-stm32/src/timer/low_level.rs b/embassy-stm32/src/timer/low_level.rs index f0105ece8..8fbedafdf 100644 --- a/embassy-stm32/src/timer/low_level.rs +++ b/embassy-stm32/src/timer/low_level.rs @@ -771,43 +771,6 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { } } - /// Generate a sequence of PWM waveform - pub async fn waveform(&mut self, dma: Peri<'_, impl super::Dma>, duty: &[u16]) { - use crate::pac::timer::vals::Ccds; - - #[allow(clippy::let_unit_value)] // eg. stm32f334 - let req = dma.request(); - - let cc_channel = C::CHANNEL; - - let original_cc_dma_on_update = self.get_cc_dma_selection() == Ccds::ON_UPDATE; - let original_cc_dma_enabled = self.get_cc_dma_enable_state(cc_channel); - - // redirect CC DMA request onto Update Event - if !original_cc_dma_on_update { - self.set_cc_dma_selection(Ccds::ON_UPDATE) - } - - if !original_cc_dma_enabled { - self.set_cc_dma_enable_state(cc_channel, true); - } - - self.waveform_helper(dma, req, cc_channel, duty).await; - - // Since DMA is closed before timer Capture Compare Event trigger DMA is turn off, - // this can almost always trigger a DMA FIFO error. - // - // optional TODO: - // clean FEIF after disable UDE - if !original_cc_dma_enabled { - self.set_cc_dma_enable_state(cc_channel, false); - } - - if !original_cc_dma_on_update { - self.set_cc_dma_selection(Ccds::ON_COMPARE) - } - } - async fn waveform_helper( &mut self, dma: Peri<'_, impl dma::Channel>, diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 6c9ef17e0..15399b108 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -362,12 +362,6 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { .waveform_up_multi_channel(dma, starting_channel, ending_channel, duty) .await; } - - /// Generate a sequence of PWM waveform - #[inline(always)] - pub async fn waveform(&mut self, dma: Peri<'_, impl super::Dma>, duty: &[u16]) { - self.inner.waveform(dma, duty).await; - } } impl<'d, T: GeneralInstance4Channel> embedded_hal_1::pwm::ErrorType for SimplePwmChannel<'d, T> { -- cgit From 2612f07f549fa0b9d8565ef760814d5f7ebea785 Mon Sep 17 00:00:00 2001 From: xoviat Date: Tue, 25 Nov 2025 08:54:11 -0600 Subject: cleanup low-level timer methods --- embassy-stm32/src/timer/complementary_pwm.rs | 11 ++- embassy-stm32/src/timer/low_level.rs | 137 +++++++++------------------ embassy-stm32/src/timer/simple_pwm.rs | 11 ++- 3 files changed, 59 insertions(+), 100 deletions(-) diff --git a/embassy-stm32/src/timer/complementary_pwm.rs b/embassy-stm32/src/timer/complementary_pwm.rs index 9f34f3ec7..77f19a37b 100644 --- a/embassy-stm32/src/timer/complementary_pwm.rs +++ b/embassy-stm32/src/timer/complementary_pwm.rs @@ -220,9 +220,11 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { /// /// Note: /// you will need to provide corresponding TIMx_UP DMA channel to use this method. - #[inline(always)] pub async fn waveform_up(&mut self, dma: Peri<'_, impl super::UpDma>, channel: Channel, duty: &[u16]) { - self.inner.waveform_up(dma, channel, duty).await + self.inner.enable_channel(channel, true); + self.inner.enable_update_dma(true); + self.inner.setup_update_dma(dma, channel, duty).await; + self.inner.enable_update_dma(false); } /// Generate a multichannel sequence of PWM waveforms using DMA triggered by timer update events. @@ -254,7 +256,6 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { /// Also be aware that embassy timers use one of timers internally. It is possible to /// switch this timer by using `time-driver-timX` feature. /// - #[inline(always)] pub async fn waveform_up_multi_channel( &mut self, dma: Peri<'_, impl super::UpDma>, @@ -262,9 +263,11 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { ending_channel: Channel, duty: &[u16], ) { + self.inner.enable_update_dma(true); self.inner - .waveform_up_multi_channel(dma, starting_channel, ending_channel, duty) + .setup_update_dma_burst(dma, starting_channel, ending_channel, duty) .await; + self.inner.enable_update_dma(false); } } diff --git a/embassy-stm32/src/timer/low_level.rs b/embassy-stm32/src/timer/low_level.rs index 8fbedafdf..f986c8dab 100644 --- a/embassy-stm32/src/timer/low_level.rs +++ b/embassy-stm32/src/timer/low_level.rs @@ -13,9 +13,10 @@ use embassy_hal_internal::Peri; pub use stm32_metapac::timer::vals::{FilterValue, Mms as MasterMode, Sms as SlaveMode, Ts as TriggerSource}; use super::*; +use crate::dma::Transfer; use crate::pac::timer::vals; +use crate::rcc; use crate::time::Hertz; -use crate::{dma, rcc}; /// Input capture mode. #[derive(Clone, Copy)] @@ -663,25 +664,51 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { /// /// Note: /// you will need to provide corresponding TIMx_UP DMA channel to use this method. - pub async fn waveform_up(&mut self, dma: Peri<'_, impl super::UpDma>, channel: Channel, duty: &[u16]) { + pub fn setup_update_dma<'a>( + &mut self, + dma: Peri<'a, impl super::UpDma>, + channel: Channel, + duty: &'a [u16], + ) -> Transfer<'a> { #[allow(clippy::let_unit_value)] // eg. stm32f334 let req = dma.request(); - let original_update_dma_state = self.get_update_dma_state(); + unsafe { + #[cfg(not(any(bdma, gpdma)))] + use crate::dma::{Burst, FifoThreshold}; + use crate::dma::{Transfer, TransferOptions}; - if !original_update_dma_state { - self.enable_update_dma(true); - } + let dma_transfer_option = TransferOptions { + #[cfg(not(any(bdma, gpdma)))] + fifo_threshold: Some(FifoThreshold::Full), + #[cfg(not(any(bdma, gpdma)))] + mburst: Burst::Incr8, + ..Default::default() + }; - self.waveform_helper(dma, req, channel, duty).await; + match self.bits() { + TimerBits::Bits16 => Transfer::new_write( + dma, + req, + duty, + self.regs_1ch().ccr(channel.index()).as_ptr() as *mut u16, + dma_transfer_option, + ), + #[cfg(not(any(stm32l0)))] + TimerBits::Bits32 => { + #[cfg(not(any(bdma, gpdma)))] + panic!("unsupported timer bits"); - // Since DMA is closed before timer update event trigger DMA is turn off, - // this can almost always trigger a DMA FIFO error. - // - // optional TODO: - // clean FEIF after disable UDE - if !original_update_dma_state { - self.enable_update_dma(false); + #[cfg(any(bdma, gpdma))] + Transfer::new_write( + dma, + req, + duty, + self.regs_1ch().ccr(channel.index()).as_ptr() as *mut u32, + dma_transfer_option, + ) + } + } } } @@ -714,13 +741,13 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { /// Also be aware that embassy timers use one of timers internally. It is possible to /// switch this timer by using `time-driver-timX` feature. /// - pub async fn waveform_up_multi_channel( + pub fn setup_update_dma_burst<'a>( &mut self, - dma: Peri<'_, impl super::UpDma>, + dma: Peri<'a, impl super::UpDma>, starting_channel: Channel, ending_channel: Channel, - duty: &[u16], - ) { + duty: &'a [u16], + ) -> Transfer<'a> { let cr1_addr = self.regs_gp16().cr1().as_ptr() as u32; let start_ch_index = starting_channel.index(); let end_ch_index = ending_channel.index(); @@ -738,11 +765,6 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { #[allow(clippy::let_unit_value)] // eg. stm32f334 let req = dma.request(); - let original_update_dma_state = self.get_update_dma_state(); - if !original_update_dma_state { - self.enable_update_dma(true); - } - unsafe { #[cfg(not(any(bdma, gpdma)))] use crate::dma::{Burst, FifoThreshold}; @@ -763,76 +785,7 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { self.regs_gp16().dmar().as_ptr() as *mut u16, dma_transfer_option, ) - .await - }; - - if !original_update_dma_state { - self.enable_update_dma(false); - } - } - - async fn waveform_helper( - &mut self, - dma: Peri<'_, impl dma::Channel>, - req: dma::Request, - channel: Channel, - duty: &[u16], - ) { - let original_duty_state = self.get_compare_value(channel); - let original_enable_state = self.get_channel_enable_state(channel); - - if !original_enable_state { - self.enable_channel(channel, true); - } - - unsafe { - #[cfg(not(any(bdma, gpdma)))] - use crate::dma::{Burst, FifoThreshold}; - use crate::dma::{Transfer, TransferOptions}; - - let dma_transfer_option = TransferOptions { - #[cfg(not(any(bdma, gpdma)))] - fifo_threshold: Some(FifoThreshold::Full), - #[cfg(not(any(bdma, gpdma)))] - mburst: Burst::Incr8, - ..Default::default() - }; - - match self.bits() { - TimerBits::Bits16 => { - Transfer::new_write( - dma, - req, - duty, - self.regs_1ch().ccr(channel.index()).as_ptr() as *mut u16, - dma_transfer_option, - ) - .await - } - #[cfg(not(any(stm32l0)))] - TimerBits::Bits32 => { - #[cfg(not(any(bdma, gpdma)))] - panic!("unsupported timer bits"); - - #[cfg(any(bdma, gpdma))] - Transfer::new_write( - dma, - req, - duty, - self.regs_1ch().ccr(channel.index()).as_ptr() as *mut u32, - dma_transfer_option, - ) - .await - } - }; - }; - - // restore output compare state - if !original_enable_state { - self.enable_channel(channel, false); } - - self.set_compare_value(channel, original_duty_state); } /// Get capture value for a channel. diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 15399b108..eb1b66358 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -316,9 +316,11 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { /// You will need to provide corresponding `TIMx_UP` DMA channel to use this method. /// Also be aware that embassy timers use one of timers internally. It is possible to /// switch this timer by using `time-driver-timX` feature. - #[inline(always)] pub async fn waveform_up(&mut self, dma: Peri<'_, impl super::UpDma>, channel: Channel, duty: &[u16]) { - self.inner.waveform_up(dma, channel, duty).await; + self.inner.enable_channel(channel, true); + self.inner.enable_update_dma(true); + self.inner.setup_update_dma(dma, channel, duty).await; + self.inner.enable_update_dma(false); } /// Generate a multichannel sequence of PWM waveforms using DMA triggered by timer update events. @@ -350,7 +352,6 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { /// Also be aware that embassy timers use one of timers internally. It is possible to /// switch this timer by using `time-driver-timX` feature. /// - #[inline(always)] pub async fn waveform_up_multi_channel( &mut self, dma: Peri<'_, impl super::UpDma>, @@ -358,9 +359,11 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { ending_channel: Channel, duty: &[u16], ) { + self.inner.enable_update_dma(true); self.inner - .waveform_up_multi_channel(dma, starting_channel, ending_channel, duty) + .setup_update_dma_burst(dma, starting_channel, ending_channel, duty) .await; + self.inner.enable_update_dma(false); } } -- cgit From 843d890483561d11aa217a68891e2f4ae8de2f94 Mon Sep 17 00:00:00 2001 From: Matan Radomski Date: Tue, 25 Nov 2025 17:35:21 +0200 Subject: Added 375KHz Clock Support --- embassy-time-driver/Cargo.toml | 2 ++ embassy-time-driver/gen_tick.py | 3 ++- embassy-time-driver/src/tick.rs | 3 +++ embassy-time/Cargo.toml | 2 ++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/embassy-time-driver/Cargo.toml b/embassy-time-driver/Cargo.toml index a52e82433..cbb6168b9 100644 --- a/embassy-time-driver/Cargo.toml +++ b/embassy-time-driver/Cargo.toml @@ -118,6 +118,8 @@ tick-hz-256_000 = [] tick-hz-262_144 = [] ## 320.0kHz Tick Rate tick-hz-320_000 = [] +## 375.0kHz Tick Rate +tick-hz-375_000 = [] ## 512.0kHz Tick Rate tick-hz-512_000 = [] ## 524.288kHz Tick Rate diff --git a/embassy-time-driver/gen_tick.py b/embassy-time-driver/gen_tick.py index 080434457..3cb6552df 100644 --- a/embassy-time-driver/gen_tick.py +++ b/embassy-time-driver/gen_tick.py @@ -1,5 +1,4 @@ import os -from glob import glob abspath = os.path.abspath(__file__) dname = os.path.dirname(abspath) @@ -22,6 +21,8 @@ for i in range(1, 30): ticks.append(10 * i * 1_000_000) for i in range(15, 50): ticks.append(20 * i * 1_000_000) + +ticks.append(375 * 1000) ticks.append(133 * 1_000_000) seen = set() diff --git a/embassy-time-driver/src/tick.rs b/embassy-time-driver/src/tick.rs index 5059e1628..247ec9ab3 100644 --- a/embassy-time-driver/src/tick.rs +++ b/embassy-time-driver/src/tick.rs @@ -74,6 +74,8 @@ pub const TICK_HZ: u64 = 256_000; pub const TICK_HZ: u64 = 262_144; #[cfg(feature = "tick-hz-320_000")] pub const TICK_HZ: u64 = 320_000; +#[cfg(feature = "tick-hz-375_000")] +pub const TICK_HZ: u64 = 375_000; #[cfg(feature = "tick-hz-512_000")] pub const TICK_HZ: u64 = 512_000; #[cfg(feature = "tick-hz-524_288")] @@ -358,6 +360,7 @@ pub const TICK_HZ: u64 = 5_242_880_000; feature = "tick-hz-256_000", feature = "tick-hz-262_144", feature = "tick-hz-320_000", + feature = "tick-hz-375_000", feature = "tick-hz-512_000", feature = "tick-hz-524_288", feature = "tick-hz-640_000", diff --git a/embassy-time/Cargo.toml b/embassy-time/Cargo.toml index 05614dbf5..a7ed51e78 100644 --- a/embassy-time/Cargo.toml +++ b/embassy-time/Cargo.toml @@ -178,6 +178,8 @@ tick-hz-256_000 = ["embassy-time-driver/tick-hz-256_000"] tick-hz-262_144 = ["embassy-time-driver/tick-hz-262_144"] ## 320.0kHz Tick Rate tick-hz-320_000 = ["embassy-time-driver/tick-hz-320_000"] +## 375.0kHz Tick Rate +tick-hz-375_000 = ["embassy-time-driver/tick-hz-375_000"] ## 512.0kHz Tick Rate tick-hz-512_000 = ["embassy-time-driver/tick-hz-512_000"] ## 524.288kHz Tick Rate -- cgit From 2632bc42e02a4162680cefaf9f08689952907c0b Mon Sep 17 00:00:00 2001 From: Matan Radomski Date: Tue, 25 Nov 2025 17:39:11 +0200 Subject: Updated Changelog --- embassy-time-driver/CHANGELOG.md | 3 ++- embassy-time/CHANGELOG.md | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/embassy-time-driver/CHANGELOG.md b/embassy-time-driver/CHANGELOG.md index cdd432437..4951f8c3e 100644 --- a/embassy-time-driver/CHANGELOG.md +++ b/embassy-time-driver/CHANGELOG.md @@ -11,7 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 0.2.1 - 2025-08-26 - Allow inlining on time driver boundary -- add 133MHz tick rate to support PR2040 @ 133MHz when `TIMERx`'s `SOURCE` is set to `SYSCLK` +- Add 133MHz tick rate to support PR2040 @ 133MHz when `TIMERx`'s `SOURCE` is set to `SYSCLK` +- Add 375KHz tick rate support ## 0.2.0 - 2025-01-02 diff --git a/embassy-time/CHANGELOG.md b/embassy-time/CHANGELOG.md index 4a50da8ef..17f8a3837 100644 --- a/embassy-time/CHANGELOG.md +++ b/embassy-time/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate - Add as_nanos and from_nanos where missing +- Added 375KHz tick rate support ## 0.5.0 - 2025-08-26 -- cgit From 2e70c376c884a64fc931406350fecb6c0314dcf0 Mon Sep 17 00:00:00 2001 From: xoviat Date: Tue, 25 Nov 2025 10:14:55 -0600 Subject: timer: add writable ring buffer --- embassy-stm32/CHANGELOG.md | 1 + embassy-stm32/src/timer/low_level.rs | 35 +++++- embassy-stm32/src/timer/mod.rs | 1 + embassy-stm32/src/timer/ringbuffered.rs | 169 +++++++++++++++++++++++++++++ embassy-stm32/src/timer/simple_pwm.rs | 31 ++++++ examples/stm32f7/src/bin/pwm.rs | 61 +++++++++++ examples/stm32f7/src/bin/pwm_ringbuffer.rs | 153 ++++++++++++++++++++++++++ 7 files changed, 450 insertions(+), 1 deletion(-) create mode 100644 embassy-stm32/src/timer/ringbuffered.rs create mode 100644 examples/stm32f7/src/bin/pwm.rs create mode 100644 examples/stm32f7/src/bin/pwm_ringbuffer.rs diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 949ea03b5..d3e5ba48d 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate +- feat: Add continuous waveform method to SimplePWM - change: remove waveform timer method - change: low power: store stop mode for dma channels - fix: Fixed ADC4 enable() for WBA diff --git a/embassy-stm32/src/timer/low_level.rs b/embassy-stm32/src/timer/low_level.rs index f986c8dab..aba08081f 100644 --- a/embassy-stm32/src/timer/low_level.rs +++ b/embassy-stm32/src/timer/low_level.rs @@ -13,7 +13,7 @@ use embassy_hal_internal::Peri; pub use stm32_metapac::timer::vals::{FilterValue, Mms as MasterMode, Sms as SlaveMode, Ts as TriggerSource}; use super::*; -use crate::dma::Transfer; +use crate::dma::{Transfer, WritableRingBuffer}; use crate::pac::timer::vals; use crate::rcc; use crate::time::Hertz; @@ -660,6 +660,39 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { } } + /// Setup a ring buffer for the channel + pub fn setup_ring_buffer<'a>( + &mut self, + dma: Peri<'a, impl super::UpDma>, + channel: Channel, + dma_buf: &'a mut [u16], + ) -> WritableRingBuffer<'a, u16> { + #[allow(clippy::let_unit_value)] // eg. stm32f334 + let req = dma.request(); + + unsafe { + use crate::dma::TransferOptions; + #[cfg(not(any(bdma, gpdma)))] + use crate::dma::{Burst, FifoThreshold}; + + let dma_transfer_option = TransferOptions { + #[cfg(not(any(bdma, gpdma)))] + fifo_threshold: Some(FifoThreshold::Full), + #[cfg(not(any(bdma, gpdma)))] + mburst: Burst::Incr8, + ..Default::default() + }; + + WritableRingBuffer::new( + dma, + req, + self.regs_1ch().ccr(channel.index()).as_ptr() as *mut u16, + dma_buf, + dma_transfer_option, + ) + } + } + /// Generate a sequence of PWM waveform /// /// Note: diff --git a/embassy-stm32/src/timer/mod.rs b/embassy-stm32/src/timer/mod.rs index 804d1ef37..3fa363881 100644 --- a/embassy-stm32/src/timer/mod.rs +++ b/embassy-stm32/src/timer/mod.rs @@ -12,6 +12,7 @@ pub mod low_level; pub mod one_pulse; pub mod pwm_input; pub mod qei; +pub mod ringbuffered; pub mod simple_pwm; use crate::interrupt; diff --git a/embassy-stm32/src/timer/ringbuffered.rs b/embassy-stm32/src/timer/ringbuffered.rs new file mode 100644 index 000000000..e8f97bf59 --- /dev/null +++ b/embassy-stm32/src/timer/ringbuffered.rs @@ -0,0 +1,169 @@ +//! RingBuffered PWM driver. + +use core::mem::ManuallyDrop; +use core::task::Waker; + +use super::low_level::Timer; +use super::{Channel, GeneralInstance4Channel}; +use crate::dma::WritableRingBuffer; +use crate::dma::ringbuffer::Error; + +/// A PWM channel that uses a DMA ring buffer for continuous waveform generation. +/// +/// This allows you to continuously update PWM duty cycles via DMA without blocking the CPU. +/// The ring buffer enables smooth, uninterrupted waveform generation by automatically cycling +/// through duty cycle values stored in memory. +/// +/// You can write new duty cycle values to the ring buffer while it's running, enabling +/// dynamic waveform generation for applications like motor control, LED dimming, or audio output. +/// +/// # Example +/// ```ignore +/// let mut channel = pwm.ch1().into_ring_buffered_channel(dma_ch, &mut buffer); +/// channel.start(); // Start DMA transfer +/// channel.write(&[100, 200, 300]).ok(); // Update duty cycles +/// ``` +pub struct RingBufferedPwmChannel<'d, T: GeneralInstance4Channel> { + timer: ManuallyDrop>, + ring_buf: WritableRingBuffer<'d, u16>, + channel: Channel, +} + +impl<'d, T: GeneralInstance4Channel> RingBufferedPwmChannel<'d, T> { + pub(crate) fn new( + timer: ManuallyDrop>, + channel: Channel, + ring_buf: WritableRingBuffer<'d, u16>, + ) -> Self { + Self { + timer, + ring_buf, + channel, + } + } + + /// Start the ring buffer operation. + /// + /// You must call this after creating it for it to work. + pub fn start(&mut self) { + self.ring_buf.start() + } + + /// Clear all data in the ring buffer. + pub fn clear(&mut self) { + self.ring_buf.clear() + } + + /// Write elements directly to the raw buffer. This can be used to fill the buffer before starting the DMA transfer. + pub fn write_immediate(&mut self, buf: &[u16]) -> Result<(usize, usize), Error> { + self.ring_buf.write_immediate(buf) + } + + /// Write elements from the ring buffer + /// Return a tuple of the length written and the length remaining in the buffer + pub fn write(&mut self, buf: &[u16]) -> Result<(usize, usize), Error> { + self.ring_buf.write(buf) + } + + /// Write an exact number of elements to the ringbuffer. + pub async fn write_exact(&mut self, buffer: &[u16]) -> Result { + self.ring_buf.write_exact(buffer).await + } + + /// Wait for any ring buffer write error. + pub async fn wait_write_error(&mut self) -> Result { + self.ring_buf.wait_write_error().await + } + + /// The current length of the ringbuffer + pub fn len(&mut self) -> Result { + self.ring_buf.len() + } + + /// The capacity of the ringbuffer + pub const fn capacity(&self) -> usize { + self.ring_buf.capacity() + } + + /// Set a waker to be woken when at least one byte is send. + pub fn set_waker(&mut self, waker: &Waker) { + self.ring_buf.set_waker(waker) + } + + /// Request the DMA to reset. The configuration for this channel will not be preserved. + /// + /// This doesn't immediately stop the transfer, you have to wait until is_running returns false. + pub fn request_reset(&mut self) { + self.ring_buf.request_reset() + } + + /// Request the transfer to pause, keeping the existing configuration for this channel. + /// To restart the transfer, call [`start`](Self::start) again. + /// + /// This doesn't immediately stop the transfer, you have to wait until is_running returns false. + pub fn request_pause(&mut self) { + self.ring_buf.request_pause() + } + + /// Return whether DMA is still running. + /// + /// If this returns false, it can be because either the transfer finished, or it was requested to stop early with request_stop. + pub fn is_running(&mut self) -> bool { + self.ring_buf.is_running() + } + + /// Stop the DMA transfer and await until the buffer is empty. + /// + /// This disables the DMA transfer's circular mode so that the transfer stops when all available data has been written. + /// + /// This is designed to be used with streaming output data such as the I2S/SAI or DAC. + pub async fn stop(&mut self) { + self.ring_buf.stop().await + } + + /// Enable the given channel. + pub fn enable(&mut self) { + self.timer.enable_channel(self.channel, true); + } + + /// Disable the given channel. + pub fn disable(&mut self) { + self.timer.enable_channel(self.channel, false); + } + + /// Check whether given channel is enabled + pub fn is_enabled(&self) -> bool { + self.timer.get_channel_enable_state(self.channel) + } + + /// Get max duty value. + /// + /// This value depends on the configured frequency and the timer's clock rate from RCC. + pub fn max_duty_cycle(&self) -> u16 { + let max = self.timer.get_max_compare_value(); + assert!(max < u16::MAX as u32); + max as u16 + 1 + } + + /// Set the output polarity for a given channel. + pub fn set_polarity(&mut self, polarity: super::low_level::OutputPolarity) { + self.timer.set_output_polarity(self.channel, polarity); + } + + /// Set the output compare mode for a given channel. + pub fn set_output_compare_mode(&mut self, mode: super::low_level::OutputCompareMode) { + self.timer.set_output_compare_mode(self.channel, mode); + } +} + +/// A group of four [`SimplePwmChannel`]s, obtained from [`SimplePwm::split`]. +pub struct RingBufferedPwmChannels<'d, T: GeneralInstance4Channel> { + /// Channel 1 + pub ch1: RingBufferedPwmChannel<'d, T>, + /// Channel 2 + pub ch2: RingBufferedPwmChannel<'d, T>, + /// Channel 3 + pub ch3: RingBufferedPwmChannel<'d, T>, + /// Channel 4 + pub ch4: RingBufferedPwmChannel<'d, T>, +} diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index eb1b66358..bc33a52ea 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -4,8 +4,12 @@ use core::marker::PhantomData; use core::mem::ManuallyDrop; use super::low_level::{CountingMode, OutputCompareMode, OutputPolarity, Timer}; +use super::ringbuffered::RingBufferedPwmChannel; use super::{Ch1, Ch2, Ch3, Ch4, Channel, GeneralInstance4Channel, TimerChannel, TimerPin}; use crate::Peri; +#[cfg(not(any(bdma, gpdma)))] +use crate::dma::{Burst, FifoThreshold}; +use crate::dma::{TransferOptions, WritableRingBuffer}; #[cfg(gpio_v2)] use crate::gpio::Pull; use crate::gpio::{AfType, AnyPin, OutputType, Speed}; @@ -158,6 +162,33 @@ impl<'d, T: GeneralInstance4Channel> SimplePwmChannel<'d, T> { pub fn set_output_compare_mode(&mut self, mode: OutputCompareMode) { self.timer.set_output_compare_mode(self.channel, mode); } + + /// Convert this PWM channel into a ring-buffered PWM channel. + /// + /// This allows continuous PWM waveform generation using a DMA ring buffer. + /// The ring buffer enables dynamic updates to the PWM duty cycle without blocking. + /// + /// # Arguments + /// * `tx_dma` - The DMA channel to use for transferring duty cycle values + /// * `dma_buf` - The buffer to use as a ring buffer (must be non-empty and <= 65535 elements) + /// + /// # Panics + /// Panics if `dma_buf` is empty or longer than 65535 elements. + pub fn into_ring_buffered_channel( + mut self, + tx_dma: Peri<'d, impl super::UpDma>, + dma_buf: &'d mut [u16], + ) -> RingBufferedPwmChannel<'d, T> { + assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); + + self.timer.enable_update_dma(true); + + RingBufferedPwmChannel::new( + unsafe { self.timer.clone_unchecked() }, + self.channel, + self.timer.setup_ring_buffer(tx_dma, self.channel, dma_buf), + ) + } } /// A group of four [`SimplePwmChannel`]s, obtained from [`SimplePwm::split`]. diff --git a/examples/stm32f7/src/bin/pwm.rs b/examples/stm32f7/src/bin/pwm.rs new file mode 100644 index 000000000..b071eb597 --- /dev/null +++ b/examples/stm32f7/src/bin/pwm.rs @@ -0,0 +1,61 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::Config; +use embassy_stm32::gpio::OutputType; +use embassy_stm32::time::{Hertz, mhz}; +use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL200, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 200 / 2 = 200Mhz + divq: Some(PllQDiv::DIV4), // 8mhz / 4 * 200 / 4 = 100Mhz + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + } + let p = embassy_stm32::init(config); + let ch1_pin = PwmPin::new(p.PE9, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, mhz(1), Default::default()); + let mut ch1 = pwm.ch1(); + ch1.enable(); + + info!("PWM initialized"); + info!("PWM max duty {}", ch1.max_duty_cycle()); + + loop { + ch1.set_duty_cycle_fully_off(); + Timer::after_millis(300).await; + ch1.set_duty_cycle_fraction(1, 4); + Timer::after_millis(300).await; + ch1.set_duty_cycle_fraction(1, 2); + Timer::after_millis(300).await; + ch1.set_duty_cycle(ch1.max_duty_cycle() - 1); + Timer::after_millis(300).await; + } +} diff --git a/examples/stm32f7/src/bin/pwm_ringbuffer.rs b/examples/stm32f7/src/bin/pwm_ringbuffer.rs new file mode 100644 index 000000000..4d191ac13 --- /dev/null +++ b/examples/stm32f7/src/bin/pwm_ringbuffer.rs @@ -0,0 +1,153 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::Config; +use embassy_stm32::gpio::OutputType; +use embassy_stm32::time::mhz; +use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("PWM Ring Buffer Example"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + use embassy_stm32::time::Hertz; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL200, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 200 / 2 = 200Mhz + divq: Some(PllQDiv::DIV4), // 8mhz / 4 * 200 / 4 = 100Mhz + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + } + let p = embassy_stm32::init(config); + + // Initialize PWM on TIM1 + let ch1_pin = PwmPin::new(p.PE9, OutputType::PushPull); + let ch2_pin = PwmPin::new(p.PE11, OutputType::PushPull); + let mut pwm = SimplePwm::new( + p.TIM1, + Some(ch1_pin), + Some(ch2_pin), + None, + None, + mhz(1), + Default::default(), + ); + + // Use channel 1 for static PWM at 50% + let mut ch1 = pwm.ch1(); + ch1.enable(); + ch1.set_duty_cycle_fraction(1, 2); + info!("Channel 1 (PE9/D6): Static 50% duty cycle"); + + // Get max duty from channel 1 before converting channel 2 + let max_duty = ch1.max_duty_cycle(); + info!("PWM max duty: {}", max_duty); + + // Create a DMA ring buffer for channel 2 + const BUFFER_SIZE: usize = 128; + static mut DMA_BUFFER: [u16; BUFFER_SIZE] = [0u16; BUFFER_SIZE]; + let dma_buffer = unsafe { &mut *core::ptr::addr_of_mut!(DMA_BUFFER) }; + + // Pre-fill buffer with initial sine wave using lookup table approach + for i in 0..BUFFER_SIZE { + // Simple sine approximation using triangle wave + let phase = (i * 256) / BUFFER_SIZE; + let sine_approx = if phase < 128 { + phase as u16 * 2 + } else { + (255 - phase) as u16 * 2 + }; + dma_buffer[i] = (sine_approx as u32 * max_duty as u32 / 256) as u16; + } + + // Convert channel 2 to ring-buffered PWM + let mut ring_pwm = pwm.ch1().into_ring_buffered_channel(p.DMA2_CH5, dma_buffer); + + info!("Ring buffer capacity: {}", ring_pwm.capacity()); + + // Pre-write some initial data to the buffer before starting + info!("Pre-writing initial waveform data..."); + + ring_pwm.write(&[0; BUFFER_SIZE]).unwrap(); + + // Enable the PWM channel output + ring_pwm.enable(); + + // Start the DMA ring buffer + ring_pwm.start(); + info!("Channel 2 (PE11/D5): Ring buffered sine wave started"); + + // Give DMA time to start consuming + Timer::after_millis(10).await; + + // Continuously update the waveform + let mut phase: f32 = 0.0; + let mut amplitude: f32 = 1.0; + let mut amplitude_direction = -0.05; + + loop { + // Generate new waveform data with varying amplitude + let mut new_data = [0u16; 32]; + for i in 0..new_data.len() { + // Triangle wave approximation for sine + let pos = ((i as u32 + phase as u32) * 4) % 256; + let sine_approx = if pos < 128 { + pos as u16 * 2 + } else { + (255 - pos) as u16 * 2 + }; + let scaled = (sine_approx as u32 * (amplitude * 256.0) as u32) / (256 * 256); + new_data[i] = ((scaled * max_duty as u32) / 256) as u16; + } + + // Write new data to the ring buffer + match ring_pwm.write_exact(&new_data).await { + Ok(_remaining) => {} + Err(e) => { + info!("Write error: {:?}", e); + } + } + + // Update phase for animation effect + phase += 2.0; + if phase >= 64.0 { + phase = 0.0; + } + + // Vary amplitude for breathing effect + amplitude += amplitude_direction; + if amplitude <= 0.2 || amplitude >= 1.0 { + amplitude_direction = -amplitude_direction; + } + + // Log buffer status periodically + if (phase as u32) % 10 == 0 { + match ring_pwm.len() { + Ok(len) => info!("Ring buffer fill: {}/{}", len, ring_pwm.capacity()), + Err(_) => info!("Error reading buffer length"), + } + } + } +} -- cgit From b2ec93caf32153157fd87cceccaaec460ada7aaa Mon Sep 17 00:00:00 2001 From: xoviat Date: Tue, 25 Nov 2025 10:18:55 -0600 Subject: pwm: cleanup --- embassy-stm32/src/timer/simple_pwm.rs | 53 ----------------------------------- 1 file changed, 53 deletions(-) diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index bd0b1d7de..bc33a52ea 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -396,59 +396,6 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { .await; self.inner.enable_update_dma(false); } - /// Generate a sequence of PWM waveform that will run continously - /// You may want to start this in a new thread as this will block forever - #[inline(always)] - pub async fn waveform_continuous(&mut self, dma: Peri<'_, impl super::Dma>, duty: &[u16]) { - self.inner.waveform_continuous(dma, duty).await; - } - - /// Convert this PWM channel into a ring-buffered PWM channel. - /// - /// This allows continuous PWM waveform generation using a DMA ring buffer. - /// The ring buffer enables dynamic updates to the PWM duty cycle without blocking. - /// - /// # Arguments - /// * `tx_dma` - The DMA channel to use for transferring duty cycle values - /// * `dma_buf` - The buffer to use as a ring buffer (must be non-empty and <= 65535 elements) - /// - /// # Panics - /// Panics if `dma_buf` is empty or longer than 65535 elements. - pub fn into_ring_buffered_channel( - self, - tx_dma: Peri<'d, impl super::Dma>, - dma_buf: &'d mut [u16], - ) -> RingBufferedPwmChannel<'d, T> { - assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); - - use crate::pac::timer::vals::Ccds; - - let channel = C::CHANNEL; - let request = tx_dma.request(); - - let opts = TransferOptions { - #[cfg(not(any(bdma, gpdma)))] - fifo_threshold: Some(FifoThreshold::Full), - #[cfg(not(any(bdma, gpdma)))] - mburst: Burst::Incr8, - ..Default::default() - }; - - self.inner.set_cc_dma_selection(Ccds::ON_UPDATE); - self.inner.set_cc_dma_enable_state(channel, true); - - let ring_buf = unsafe { - WritableRingBuffer::new( - tx_dma, - request, - self.inner.regs_gp16().ccr(channel.index()).as_ptr() as *mut u16, - dma_buf, - opts, - ) - }; - - RingBufferedPwmChannel::new(unsafe { self.inner.clone_unchecked() }, channel, ring_buf) - } } impl<'d, T: GeneralInstance4Channel> embedded_hal_1::pwm::ErrorType for SimplePwmChannel<'d, T> { -- cgit From 309ee44c7484c4d11adc3fbd527536027eac8a94 Mon Sep 17 00:00:00 2001 From: xoviat Date: Tue, 25 Nov 2025 10:25:06 -0600 Subject: fmt --- embassy-stm32/src/timer/simple_pwm.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index bc33a52ea..484e9fd81 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -7,9 +7,6 @@ use super::low_level::{CountingMode, OutputCompareMode, OutputPolarity, Timer}; use super::ringbuffered::RingBufferedPwmChannel; use super::{Ch1, Ch2, Ch3, Ch4, Channel, GeneralInstance4Channel, TimerChannel, TimerPin}; use crate::Peri; -#[cfg(not(any(bdma, gpdma)))] -use crate::dma::{Burst, FifoThreshold}; -use crate::dma::{TransferOptions, WritableRingBuffer}; #[cfg(gpio_v2)] use crate::gpio::Pull; use crate::gpio::{AfType, AnyPin, OutputType, Speed}; -- cgit From d2d00b57c8bf5b6879c5df5021f44652d1fd52ee Mon Sep 17 00:00:00 2001 From: xoviat Date: Tue, 25 Nov 2025 11:47:44 -0600 Subject: stm32: allow granular stop for uart --- embassy-stm32/CHANGELOG.md | 1 + embassy-stm32/src/rcc/mod.rs | 39 ++++++++++++++++++++++++++++----- embassy-stm32/src/usart/mod.rs | 20 +++++++++++++---- embassy-stm32/src/usart/ringbuffered.rs | 3 +++ 4 files changed, 53 insertions(+), 10 deletions(-) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index d3e5ba48d..5c31b5a11 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate +- feat: allow granular stop for regular usart - feat: Add continuous waveform method to SimplePWM - change: remove waveform timer method - change: low power: store stop mode for dma channels diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index 85434fa83..f38d9078d 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -234,9 +234,6 @@ impl RccInfo { } } - #[cfg(feature = "low-power")] - increment_stop_refcount(_cs, self.stop_mode); - // set the xxxRST bit let reset_ptr = self.reset_ptr(); if let Some(reset_ptr) = reset_ptr { @@ -292,9 +289,6 @@ impl RccInfo { } } - #[cfg(feature = "low-power")] - decrement_stop_refcount(_cs, self.stop_mode); - // clear the xxxEN bit let enable_ptr = self.enable_ptr(); unsafe { @@ -303,13 +297,46 @@ impl RccInfo { } } + pub(crate) fn increment_stop_refcount_with_cs(&self, _cs: CriticalSection) { + #[cfg(feature = "low-power")] + increment_stop_refcount(_cs, self.stop_mode); + } + + pub(crate) fn increment_stop_refcount(&self) { + critical_section::with(|cs| self.increment_stop_refcount_with_cs(cs)) + } + + pub(crate) fn decrement_stop_refcount_with_cs(&self, _cs: CriticalSection) { + #[cfg(feature = "low-power")] + decrement_stop_refcount(_cs, self.stop_mode); + } + + pub(crate) fn decrement_stop_refcount(&self) { + critical_section::with(|cs| self.decrement_stop_refcount_with_cs(cs)) + } + // TODO: should this be `unsafe`? pub(crate) fn enable_and_reset(&self) { + critical_section::with(|cs| { + self.enable_and_reset_with_cs(cs); + self.increment_stop_refcount_with_cs(cs); + }) + } + + pub(crate) fn enable_and_reset_without_stop(&self) { critical_section::with(|cs| self.enable_and_reset_with_cs(cs)) } // TODO: should this be `unsafe`? pub(crate) fn disable(&self) { + critical_section::with(|cs| { + self.disable_with_cs(cs); + self.decrement_stop_refcount_with_cs(cs); + }) + } + + // TODO: should this be `unsafe`? + pub(crate) fn disable_without_stop(&self) { critical_section::with(|cs| self.disable_with_cs(cs)) } diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index 0e7da634d..1af78b358 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -491,6 +491,9 @@ impl<'d> UartTx<'d, Async> { /// Initiate an asynchronous UART write pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { + self.info.rcc.increment_stop_refcount(); + let _ = OnDrop::new(|| self.info.rcc.decrement_stop_refcount()); + let r = self.info.regs; half_duplex_set_rx_tx_before_write(&r, self.duplex == Duplex::Half(HalfDuplexReadback::Readback)); @@ -508,6 +511,9 @@ impl<'d> UartTx<'d, Async> { /// Wait until transmission complete pub async fn flush(&mut self) -> Result<(), Error> { + self.info.rcc.increment_stop_refcount(); + let _ = OnDrop::new(|| self.info.rcc.decrement_stop_refcount()); + flush(&self.info, &self.state).await } } @@ -569,7 +575,7 @@ impl<'d, M: Mode> UartTx<'d, M> { let state = self.state; state.tx_rx_refcount.store(1, Ordering::Relaxed); - info.rcc.enable_and_reset(); + info.rcc.enable_and_reset_without_stop(); info.regs.cr3().modify(|w| { w.set_ctse(self.cts.is_some()); @@ -726,6 +732,9 @@ impl<'d> UartRx<'d, Async> { /// Initiate an asynchronous UART read pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.info.rcc.increment_stop_refcount(); + let _ = OnDrop::new(|| self.info.rcc.decrement_stop_refcount()); + self.inner_read(buffer, false).await?; Ok(()) @@ -733,6 +742,9 @@ impl<'d> UartRx<'d, Async> { /// Initiate an asynchronous read with idle line detection enabled pub async fn read_until_idle(&mut self, buffer: &mut [u8]) -> Result { + self.info.rcc.increment_stop_refcount(); + let _ = OnDrop::new(|| self.info.rcc.decrement_stop_refcount()); + self.inner_read(buffer, true).await } @@ -1004,7 +1016,7 @@ impl<'d, M: Mode> UartRx<'d, M> { .eager_reads .store(config.eager_reads.unwrap_or(0), Ordering::Relaxed); - info.rcc.enable_and_reset(); + info.rcc.enable_and_reset_without_stop(); info.regs.cr3().write(|w| { w.set_rtse(self.rts.is_some()); @@ -1143,7 +1155,7 @@ fn drop_tx_rx(info: &Info, state: &State) { refcount == 1 }); if is_last_drop { - info.rcc.disable(); + info.rcc.disable_without_stop(); } } @@ -1506,7 +1518,7 @@ impl<'d, M: Mode> Uart<'d, M> { .eager_reads .store(config.eager_reads.unwrap_or(0), Ordering::Relaxed); - info.rcc.enable_and_reset(); + info.rcc.enable_and_reset_without_stop(); info.regs.cr3().write(|w| { w.set_rtse(self.rx.rts.is_some()); diff --git a/embassy-stm32/src/usart/ringbuffered.rs b/embassy-stm32/src/usart/ringbuffered.rs index bac570d27..cc5224b69 100644 --- a/embassy-stm32/src/usart/ringbuffered.rs +++ b/embassy-stm32/src/usart/ringbuffered.rs @@ -117,6 +117,8 @@ impl<'d> UartRx<'d, Async> { let rx = unsafe { self.rx.as_ref().map(|x| x.clone_unchecked()) }; let rts = unsafe { self.rts.as_ref().map(|x| x.clone_unchecked()) }; + info.rcc.increment_stop_refcount(); + // Don't disable the clock mem::forget(self); @@ -324,6 +326,7 @@ impl<'d> RingBufferedUartRx<'d> { impl Drop for RingBufferedUartRx<'_> { fn drop(&mut self) { + self.info.rcc.decrement_stop_refcount(); self.stop_uart(); self.rx.as_ref().map(|x| x.set_as_disconnected()); self.rts.as_ref().map(|x| x.set_as_disconnected()); -- cgit From 5298671b0c132f58f3f76273bcd35656dc6e6d3d Mon Sep 17 00:00:00 2001 From: xoviat Date: Tue, 25 Nov 2025 11:57:59 -0600 Subject: fmt --- embassy-stm32/src/rcc/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index f38d9078d..b6ecc6c18 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -302,6 +302,7 @@ impl RccInfo { increment_stop_refcount(_cs, self.stop_mode); } + #[allow(dead_code)] pub(crate) fn increment_stop_refcount(&self) { critical_section::with(|cs| self.increment_stop_refcount_with_cs(cs)) } @@ -311,6 +312,7 @@ impl RccInfo { decrement_stop_refcount(_cs, self.stop_mode); } + #[allow(dead_code)] pub(crate) fn decrement_stop_refcount(&self) { critical_section::with(|cs| self.decrement_stop_refcount_with_cs(cs)) } @@ -323,6 +325,7 @@ impl RccInfo { }) } + #[allow(dead_code)] pub(crate) fn enable_and_reset_without_stop(&self) { critical_section::with(|cs| self.enable_and_reset_with_cs(cs)) } @@ -336,6 +339,7 @@ impl RccInfo { } // TODO: should this be `unsafe`? + #[allow(dead_code)] pub(crate) fn disable_without_stop(&self) { critical_section::with(|cs| self.disable_with_cs(cs)) } -- cgit From 5792daf3afb9366c362fc57c89870ffb05df8b8c Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Tue, 25 Nov 2025 11:28:55 -0700 Subject: Remove atomic-polyfill --- embassy-rp/Cargo.toml | 3 +-- embassy-rp/src/pio/mod.rs | 26 ++++++++++++++++++-------- embassy-rp/src/uart/buffered.rs | 13 ++++++++++--- embassy-rp/src/uart/mod.rs | 28 +++++++++++++++++++++++----- 4 files changed, 52 insertions(+), 18 deletions(-) diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index 9ad4b47a3..421f0b0f6 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml @@ -47,7 +47,7 @@ rt = [ "rp-pac/rt" ] defmt = ["dep:defmt", "embassy-usb-driver/defmt", "embassy-hal-internal/defmt"] ## Enable log support log = ["dep:log"] -## Enable chrono support +## Enable chrono support chrono = ["dep:chrono"] ## Configure the [`critical-section`](https://docs.rs/critical-section) crate to use an implementation that is safe for multicore use on rp2040. @@ -159,7 +159,6 @@ embassy-futures = { version = "0.1.2", path = "../embassy-futures" } embassy-hal-internal = { version = "0.3.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } embassy-embedded-hal = { version = "0.5.0", path = "../embassy-embedded-hal" } embassy-usb-driver = { version = "0.2.0", path = "../embassy-usb-driver" } -atomic-polyfill = "1.0.1" defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } nb = "1.1.0" diff --git a/embassy-rp/src/pio/mod.rs b/embassy-rp/src/pio/mod.rs index 92b2c603e..fd0b4c072 100644 --- a/embassy-rp/src/pio/mod.rs +++ b/embassy-rp/src/pio/mod.rs @@ -1,11 +1,11 @@ //! PIO driver. +use core::cell::Cell; use core::future::Future; use core::marker::PhantomData; use core::pin::Pin as FuturePin; -use core::sync::atomic::{Ordering, compiler_fence}; +use core::sync::atomic::{AtomicU8, Ordering, compiler_fence}; use core::task::{Context, Poll}; -use atomic_polyfill::{AtomicU8, AtomicU64}; use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; use fixed::FixedU32; @@ -1232,7 +1232,10 @@ impl<'d, PIO: Instance> Common<'d, PIO> { w.set_pde(false); }); // we can be relaxed about this because we're &mut here and nothing is cached - PIO::state().used_pins.fetch_or(1 << pin.pin_bank(), Ordering::Relaxed); + critical_section::with(|_| { + let val = PIO::state().used_pins.get(); + PIO::state().used_pins.set(val | 1 << pin.pin_bank()); + }); Pin { pin: pin.into(), pio: PhantomData::default(), @@ -1369,7 +1372,7 @@ impl<'d, PIO: Instance> Pio<'d, PIO> { /// Create a new instance of a PIO peripheral. pub fn new(_pio: Peri<'d, PIO>, _irq: impl Binding>) -> Self { PIO::state().users.store(5, Ordering::Release); - PIO::state().used_pins.store(0, Ordering::Release); + critical_section::with(|_| PIO::state().used_pins.set(0)); PIO::Interrupt::unpend(); unsafe { PIO::Interrupt::enable() }; @@ -1413,13 +1416,20 @@ impl<'d, PIO: Instance> Pio<'d, PIO> { // other way. pub struct State { users: AtomicU8, - used_pins: AtomicU64, + used_pins: Cell, } +unsafe impl Sync for State {} + fn on_pio_drop() { let state = PIO::state(); - if state.users.fetch_sub(1, Ordering::AcqRel) == 1 { - let used_pins = state.used_pins.load(Ordering::Relaxed); + let users_state = critical_section::with(|_| { + let val = state.users.load(Ordering::Acquire) - 1; + state.users.store(val, Ordering::Release); + val + }); + if users_state == 1 { + let used_pins = critical_section::with(|_| state.used_pins.get()); let null = pac::io::vals::Gpio0ctrlFuncsel::NULL as _; for i in 0..crate::gpio::BANK0_PIN_COUNT { if used_pins & (1 << i) != 0 { @@ -1444,7 +1454,7 @@ trait SealedInstance { fn state() -> &'static State { static STATE: State = State { users: AtomicU8::new(0), - used_pins: AtomicU64::new(0), + used_pins: Cell::new(0), }; &STATE diff --git a/embassy-rp/src/uart/buffered.rs b/embassy-rp/src/uart/buffered.rs index 02649ad81..fdb8ce776 100644 --- a/embassy-rp/src/uart/buffered.rs +++ b/embassy-rp/src/uart/buffered.rs @@ -1,8 +1,8 @@ //! Buffered UART driver. use core::future::Future; use core::slice; +use core::sync::atomic::{AtomicU8, Ordering}; -use atomic_polyfill::AtomicU8; use embassy_hal_internal::atomic_ring_buffer::RingBuffer; use super::*; @@ -241,7 +241,11 @@ impl BufferedUartRx { } fn get_rx_error(state: &State) -> Option { - let errs = state.rx_error.swap(0, Ordering::Relaxed); + let errs = critical_section::with(|_| { + let val = state.rx_error.load(Ordering::Relaxed); + state.rx_error.store(0, Ordering::Relaxed); + val + }); if errs & RXE_OVERRUN != 0 { Some(Error::Overrun) } else if errs & RXE_BREAK != 0 { @@ -555,7 +559,10 @@ impl interrupt::typelevel::Handler for BufferedInterr } let dr = r.uartdr().read(); if (dr.0 >> 8) != 0 { - s.rx_error.fetch_or((dr.0 >> 8) as u8, Ordering::Relaxed); + critical_section::with(|_| { + let val = s.rx_error.load(Ordering::Relaxed); + s.rx_error.store(val | ((dr.0 >> 8) as u8), Ordering::Relaxed); + }); error = true; // only fill the buffer with valid characters. the current character is fine // if the error is an overrun, but if we add it to the buffer we'll report diff --git a/embassy-rp/src/uart/mod.rs b/embassy-rp/src/uart/mod.rs index 8be87a5d2..b7b569dd5 100644 --- a/embassy-rp/src/uart/mod.rs +++ b/embassy-rp/src/uart/mod.rs @@ -1,9 +1,9 @@ //! UART driver. use core::future::poll_fn; use core::marker::PhantomData; +use core::sync::atomic::{AtomicU16, Ordering}; use core::task::Poll; -use atomic_polyfill::{AtomicU16, Ordering}; use embassy_futures::select::{Either, select}; use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; @@ -456,7 +456,12 @@ impl<'d> UartRx<'d, Async> { transfer, poll_fn(|cx| { self.dma_state.rx_err_waker.register(cx.waker()); - match self.dma_state.rx_errs.swap(0, Ordering::Relaxed) { + let rx_errs = critical_section::with(|_| { + let val = self.dma_state.rx_errs.load(Ordering::Relaxed); + self.dma_state.rx_errs.store(0, Ordering::Relaxed); + val + }); + match rx_errs { 0 => Poll::Pending, e => Poll::Ready(Uartris(e as u32)), } @@ -468,7 +473,11 @@ impl<'d> UartRx<'d, Async> { Either::First(()) => { // We're here because the DMA finished, BUT if an error occurred on the LAST // byte, then we may still need to grab the error state! - Uartris(self.dma_state.rx_errs.swap(0, Ordering::Relaxed) as u32) + Uartris(critical_section::with(|_| { + let val = self.dma_state.rx_errs.load(Ordering::Relaxed); + self.dma_state.rx_errs.store(0, Ordering::Relaxed); + val + }) as u32) } Either::Second(e) => { // We're here because we errored, which means this is the error that @@ -616,7 +625,12 @@ impl<'d> UartRx<'d, Async> { transfer, poll_fn(|cx| { self.dma_state.rx_err_waker.register(cx.waker()); - match self.dma_state.rx_errs.swap(0, Ordering::Relaxed) { + let rx_errs = critical_section::with(|_| { + let val = self.dma_state.rx_errs.load(Ordering::Relaxed); + self.dma_state.rx_errs.store(0, Ordering::Relaxed); + val + }); + match rx_errs { 0 => Poll::Pending, e => Poll::Ready(Uartris(e as u32)), } @@ -629,7 +643,11 @@ impl<'d> UartRx<'d, Async> { Either::First(()) => { // We're here because the DMA finished, BUT if an error occurred on the LAST // byte, then we may still need to grab the error state! - Uartris(self.dma_state.rx_errs.swap(0, Ordering::Relaxed) as u32) + Uartris(critical_section::with(|_| { + let val = self.dma_state.rx_errs.load(Ordering::Relaxed); + self.dma_state.rx_errs.store(0, Ordering::Relaxed); + val + }) as u32) } Either::Second(e) => { // We're here because we errored, which means this is the error that -- cgit From f8ec795741663f559295327911a51559c526ba8f Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Tue, 25 Nov 2025 11:37:22 -0700 Subject: Update CHANGELOG.md --- embassy-rp/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-rp/CHANGELOG.md b/embassy-rp/CHANGELOG.md index fa8609bbf..7480b729f 100644 --- a/embassy-rp/CHANGELOG.md +++ b/embassy-rp/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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)) - Add PIO::Ws2812 color order support - Add TX-only, no SCK SPI support +- Remove atomic-polyfill with critical-section instead ([#4948](https://github.com/embassy-rs/embassy/pull/4948)) ## 0.8.0 - 2025-08-26 @@ -116,4 +117,3 @@ Small release fixing a few gnarly bugs, upgrading is strongly recommended. - rename the Channel trait to Slice and the PwmPin to PwmChannel - i2c: Fix race condition that appears on fast repeated transfers. - Add a basic "read to break" function - -- cgit From edd8878f8cbd15a56a6c845a2a8772a016e24d4b Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Tue, 25 Nov 2025 12:05:52 -0700 Subject: Use two AtomicU32 instead --- embassy-rp/src/pio/mod.rs | 56 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/embassy-rp/src/pio/mod.rs b/embassy-rp/src/pio/mod.rs index fd0b4c072..1c370fdfc 100644 --- a/embassy-rp/src/pio/mod.rs +++ b/embassy-rp/src/pio/mod.rs @@ -1,9 +1,8 @@ //! PIO driver. -use core::cell::Cell; use core::future::Future; use core::marker::PhantomData; use core::pin::Pin as FuturePin; -use core::sync::atomic::{AtomicU8, Ordering, compiler_fence}; +use core::sync::atomic::{AtomicU8, AtomicU32, Ordering, compiler_fence}; use core::task::{Context, Poll}; use embassy_hal_internal::{Peri, PeripheralType}; @@ -1233,9 +1232,12 @@ impl<'d, PIO: Instance> Common<'d, PIO> { }); // we can be relaxed about this because we're &mut here and nothing is cached critical_section::with(|_| { - let val = PIO::state().used_pins.get(); - PIO::state().used_pins.set(val | 1 << pin.pin_bank()); + let val = PIO::state().used_pins.load(Ordering::Relaxed); + PIO::state() + .used_pins + .store(val | 1 << pin.pin_bank(), Ordering::Relaxed); }); + Pin { pin: pin.into(), pio: PhantomData::default(), @@ -1372,7 +1374,7 @@ impl<'d, PIO: Instance> Pio<'d, PIO> { /// Create a new instance of a PIO peripheral. pub fn new(_pio: Peri<'d, PIO>, _irq: impl Binding>) -> Self { PIO::state().users.store(5, Ordering::Release); - critical_section::with(|_| PIO::state().used_pins.set(0)); + PIO::state().used_pins.store(0, Ordering::Release); PIO::Interrupt::unpend(); unsafe { PIO::Interrupt::enable() }; @@ -1407,6 +1409,42 @@ impl<'d, PIO: Instance> Pio<'d, PIO> { } } +struct AtomicU64 { + upper_32: AtomicU32, + lower_32: AtomicU32, +} + +impl AtomicU64 { + const fn new(val: u64) -> Self { + let upper_32 = (val >> 32) as u32; + let lower_32 = val as u32; + + Self { + upper_32: AtomicU32::new(upper_32), + lower_32: AtomicU32::new(lower_32), + } + } + + fn load(&self, order: Ordering) -> u64 { + let (upper, lower) = critical_section::with(|_| (self.upper_32.load(order), self.lower_32.load(order))); + + let upper = (upper as u64) << 32; + let lower = lower as u64; + + upper | lower + } + + fn store(&self, val: u64, order: Ordering) { + let upper_32 = (val >> 32) as u32; + let lower_32 = val as u32; + + critical_section::with(|_| { + self.upper_32.store(upper_32, order); + self.lower_32.store(lower_32, order); + }); + } +} + /// Representation of the PIO state keeping a record of which pins are assigned to /// each PIO. // make_pio_pin notionally takes ownership of the pin it is given, but the wrapped pin @@ -1416,11 +1454,9 @@ impl<'d, PIO: Instance> Pio<'d, PIO> { // other way. pub struct State { users: AtomicU8, - used_pins: Cell, + used_pins: AtomicU64, } -unsafe impl Sync for State {} - fn on_pio_drop() { let state = PIO::state(); let users_state = critical_section::with(|_| { @@ -1429,7 +1465,7 @@ fn on_pio_drop() { val }); if users_state == 1 { - let used_pins = critical_section::with(|_| state.used_pins.get()); + let used_pins = state.used_pins.load(Ordering::Relaxed); let null = pac::io::vals::Gpio0ctrlFuncsel::NULL as _; for i in 0..crate::gpio::BANK0_PIN_COUNT { if used_pins & (1 << i) != 0 { @@ -1454,7 +1490,7 @@ trait SealedInstance { fn state() -> &'static State { static STATE: State = State { users: AtomicU8::new(0), - used_pins: Cell::new(0), + used_pins: AtomicU64::new(0), }; &STATE -- cgit From 0847f4ca4657ea2174fc160f96a69f4c916d146e Mon Sep 17 00:00:00 2001 From: xoviat Date: Tue, 25 Nov 2025 16:53:26 -0600 Subject: stm32: extract busychannel into common api --- embassy-stm32/src/dma/dma_bdma.rs | 15 ++++--- embassy-stm32/src/dma/gpdma/mod.rs | 10 ++--- embassy-stm32/src/dma/gpdma/ringbuffered.rs | 11 ++--- embassy-stm32/src/dma/mod.rs | 50 ++++----------------- embassy-stm32/src/low_power.rs | 22 +++------- embassy-stm32/src/rcc/mod.rs | 68 ++++++++++++++++++++++++++++- embassy-stm32/src/usart/mod.rs | 12 ++--- 7 files changed, 105 insertions(+), 83 deletions(-) diff --git a/embassy-stm32/src/dma/dma_bdma.rs b/embassy-stm32/src/dma/dma_bdma.rs index b46ae2813..adc084474 100644 --- a/embassy-stm32/src/dma/dma_bdma.rs +++ b/embassy-stm32/src/dma/dma_bdma.rs @@ -8,8 +8,9 @@ use embassy_sync::waitqueue::AtomicWaker; use super::ringbuffer::{DmaCtrl, Error, ReadableDmaRingBuffer, WritableDmaRingBuffer}; use super::word::{Word, WordSize}; -use super::{AnyChannel, BusyChannel, Channel, Dir, Request, STATE}; +use super::{AnyChannel, Channel, Dir, Request, STATE}; use crate::interrupt::typelevel::Interrupt; +use crate::rcc::BusyPeripheral; use crate::{interrupt, pac}; pub(crate) struct ChannelInfo { @@ -602,7 +603,7 @@ impl AnyChannel { /// DMA transfer. #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct Transfer<'a> { - channel: BusyChannel<'a>, + channel: BusyPeripheral>, } impl<'a> Transfer<'a> { @@ -714,7 +715,7 @@ impl<'a> Transfer<'a> { ); channel.start(); Self { - channel: BusyChannel::new(channel), + channel: BusyPeripheral::new(channel), } } @@ -818,7 +819,7 @@ impl<'a> DmaCtrl for DmaCtrlImpl<'a> { /// Ringbuffer for receiving data using DMA circular mode. pub struct ReadableRingBuffer<'a, W: Word> { - channel: BusyChannel<'a>, + channel: BusyPeripheral>, ringbuf: ReadableDmaRingBuffer<'a, W>, } @@ -855,7 +856,7 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> { ); Self { - channel: BusyChannel::new(channel), + channel: BusyPeripheral::new(channel), ringbuf: ReadableDmaRingBuffer::new(buffer), } } @@ -974,7 +975,7 @@ impl<'a, W: Word> Drop for ReadableRingBuffer<'a, W> { /// Ringbuffer for writing data using DMA circular mode. pub struct WritableRingBuffer<'a, W: Word> { - channel: BusyChannel<'a>, + channel: BusyPeripheral>, ringbuf: WritableDmaRingBuffer<'a, W>, } @@ -1011,7 +1012,7 @@ impl<'a, W: Word> WritableRingBuffer<'a, W> { ); Self { - channel: BusyChannel::new(channel), + channel: BusyPeripheral::new(channel), ringbuf: WritableDmaRingBuffer::new(buffer), } } diff --git a/embassy-stm32/src/dma/gpdma/mod.rs b/embassy-stm32/src/dma/gpdma/mod.rs index 383c74a78..bfd0570f8 100644 --- a/embassy-stm32/src/dma/gpdma/mod.rs +++ b/embassy-stm32/src/dma/gpdma/mod.rs @@ -11,10 +11,10 @@ use linked_list::Table; use super::word::{Word, WordSize}; use super::{AnyChannel, Channel, Dir, Request, STATE}; -use crate::dma::BusyChannel; use crate::interrupt::typelevel::Interrupt; use crate::pac; use crate::pac::gpdma::vals; +use crate::rcc::BusyPeripheral; pub mod linked_list; pub mod ringbuffered; @@ -409,7 +409,7 @@ impl AnyChannel { /// Linked-list DMA transfer. #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct LinkedListTransfer<'a, const ITEM_COUNT: usize> { - channel: BusyChannel<'a>, + channel: BusyPeripheral>, } impl<'a, const ITEM_COUNT: usize> LinkedListTransfer<'a, ITEM_COUNT> { @@ -431,7 +431,7 @@ impl<'a, const ITEM_COUNT: usize> LinkedListTransfer<'a, ITEM_COUNT> { channel.start(); Self { - channel: BusyChannel::new(channel), + channel: BusyPeripheral::new(channel), } } @@ -508,7 +508,7 @@ impl<'a, const ITEM_COUNT: usize> Future for LinkedListTransfer<'a, ITEM_COUNT> /// DMA transfer. #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct Transfer<'a> { - channel: BusyChannel<'a>, + channel: BusyPeripheral>, } impl<'a> Transfer<'a> { @@ -629,7 +629,7 @@ impl<'a> Transfer<'a> { channel.start(); Self { - channel: BusyChannel::new(channel), + channel: BusyPeripheral::new(channel), } } diff --git a/embassy-stm32/src/dma/gpdma/ringbuffered.rs b/embassy-stm32/src/dma/gpdma/ringbuffered.rs index 54e4d5f71..c150d0b95 100644 --- a/embassy-stm32/src/dma/gpdma/ringbuffered.rs +++ b/embassy-stm32/src/dma/gpdma/ringbuffered.rs @@ -12,7 +12,8 @@ use super::{AnyChannel, STATE, TransferOptions}; use crate::dma::gpdma::linked_list::{RunMode, Table}; use crate::dma::ringbuffer::{DmaCtrl, Error, ReadableDmaRingBuffer, WritableDmaRingBuffer}; use crate::dma::word::Word; -use crate::dma::{BusyChannel, Channel, Dir, Request}; +use crate::dma::{Channel, Dir, Request}; +use crate::rcc::BusyPeripheral; struct DmaCtrlImpl<'a>(Peri<'a, AnyChannel>); @@ -49,7 +50,7 @@ impl<'a> DmaCtrl for DmaCtrlImpl<'a> { /// Ringbuffer for receiving data using GPDMA linked-list mode. pub struct ReadableRingBuffer<'a, W: Word> { - channel: BusyChannel<'a>, + channel: BusyPeripheral>, ringbuf: ReadableDmaRingBuffer<'a, W>, table: Table<2>, options: TransferOptions, @@ -70,7 +71,7 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> { let table = Table::<2>::new_ping_pong::(request, peri_addr, buffer, Dir::PeripheralToMemory); Self { - channel: BusyChannel::new(channel), + channel: BusyPeripheral::new(channel), ringbuf: ReadableDmaRingBuffer::new(buffer), table, options, @@ -189,7 +190,7 @@ impl<'a, W: Word> Drop for ReadableRingBuffer<'a, W> { /// Ringbuffer for writing data using GPDMA linked-list mode. pub struct WritableRingBuffer<'a, W: Word> { - channel: BusyChannel<'a>, + channel: BusyPeripheral>, ringbuf: WritableDmaRingBuffer<'a, W>, table: Table<2>, options: TransferOptions, @@ -210,7 +211,7 @@ impl<'a, W: Word> WritableRingBuffer<'a, W> { let table = Table::<2>::new_ping_pong::(request, peri_addr, buffer, Dir::MemoryToPeripheral); Self { - channel: BusyChannel::new(channel), + channel: BusyPeripheral::new(channel), ringbuf: WritableDmaRingBuffer::new(buffer), table, options, diff --git a/embassy-stm32/src/dma/mod.rs b/embassy-stm32/src/dma/mod.rs index 4becc2d87..efb324fa6 100644 --- a/embassy-stm32/src/dma/mod.rs +++ b/embassy-stm32/src/dma/mod.rs @@ -3,14 +3,12 @@ #[cfg(any(bdma, dma))] mod dma_bdma; -use core::ops; #[cfg(any(bdma, dma))] pub use dma_bdma::*; #[cfg(gpdma)] pub(crate) mod gpdma; -use embassy_hal_internal::Peri; #[cfg(gpdma)] pub use gpdma::ringbuffered::*; #[cfg(gpdma)] @@ -27,9 +25,10 @@ pub(crate) use util::*; pub(crate) mod ringbuffer; pub mod word; -use embassy_hal_internal::{PeripheralType, impl_peripheral}; +use embassy_hal_internal::{Peri, PeripheralType, impl_peripheral}; use crate::interrupt; +use crate::rcc::StoppablePeripheral; /// The direction of a DMA transfer. #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -48,6 +47,13 @@ pub type Request = u8; #[cfg(not(any(dma_v2, bdma_v2, gpdma, dmamux)))] pub type Request = (); +impl<'a> StoppablePeripheral for Peri<'a, AnyChannel> { + #[cfg(feature = "low-power")] + fn stop_mode(&self) -> crate::rcc::StopMode { + self.stop_mode + } +} + pub(crate) trait SealedChannel { #[cfg(not(stm32n6))] fn id(&self) -> u8; @@ -103,44 +109,6 @@ macro_rules! dma_channel_impl { }; } -pub(crate) struct BusyChannel<'a> { - channel: Peri<'a, AnyChannel>, -} - -impl<'a> BusyChannel<'a> { - pub fn new(channel: Peri<'a, AnyChannel>) -> Self { - #[cfg(feature = "low-power")] - critical_section::with(|cs| { - crate::rcc::increment_stop_refcount(cs, channel.stop_mode); - }); - - Self { channel } - } -} - -impl<'a> Drop for BusyChannel<'a> { - fn drop(&mut self) { - #[cfg(feature = "low-power")] - critical_section::with(|cs| { - crate::rcc::decrement_stop_refcount(cs, self.stop_mode); - }); - } -} - -impl<'a> ops::Deref for BusyChannel<'a> { - type Target = Peri<'a, AnyChannel>; - - fn deref(&self) -> &Self::Target { - &self.channel - } -} - -impl<'a> ops::DerefMut for BusyChannel<'a> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.channel - } -} - /// Type-erased DMA channel. pub struct AnyChannel { pub(crate) id: u8, diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index bd8290da0..cdf3323fb 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs @@ -51,7 +51,7 @@ use embassy_executor::*; use crate::interrupt; pub use crate::rcc::StopMode; -use crate::rcc::{RCC_CONFIG, REFCOUNT_STOP1, REFCOUNT_STOP2, decrement_stop_refcount, increment_stop_refcount}; +use crate::rcc::{BusyPeripheral, RCC_CONFIG, REFCOUNT_STOP1, REFCOUNT_STOP2}; use crate::time_driver::get_driver; const THREAD_PENDER: usize = usize::MAX; @@ -59,7 +59,9 @@ const THREAD_PENDER: usize = usize::MAX; static mut EXECUTOR_TAKEN: bool = false; /// Prevent the device from going into the stop mode if held -pub struct DeviceBusy(StopMode); +pub struct DeviceBusy { + _stop_mode: BusyPeripheral, +} impl DeviceBusy { /// Create a new DeviceBusy with stop1. @@ -74,19 +76,9 @@ impl DeviceBusy { /// Create a new DeviceBusy. pub fn new(stop_mode: StopMode) -> Self { - critical_section::with(|cs| { - increment_stop_refcount(cs, stop_mode); - }); - - Self(stop_mode) - } -} - -impl Drop for DeviceBusy { - fn drop(&mut self) { - critical_section::with(|cs| { - decrement_stop_refcount(cs, self.0); - }); + Self { + _stop_mode: BusyPeripheral::new(stop_mode), + } } } diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index b6ecc6c18..1dd634cfe 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -4,6 +4,7 @@ #![allow(missing_docs)] // TODO use core::mem::MaybeUninit; +use core::ops; mod bd; pub use bd::*; @@ -112,7 +113,7 @@ pub fn clocks<'a>(_rcc: &'a crate::Peri<'a, crate::peripherals::RCC>) -> &'a Clo } #[cfg(feature = "low-power")] -pub(crate) fn increment_stop_refcount(_cs: CriticalSection, stop_mode: StopMode) { +fn increment_stop_refcount(_cs: CriticalSection, stop_mode: StopMode) { match stop_mode { StopMode::Standby => {} StopMode::Stop2 => unsafe { @@ -125,7 +126,7 @@ pub(crate) fn increment_stop_refcount(_cs: CriticalSection, stop_mode: StopMode) } #[cfg(feature = "low-power")] -pub(crate) fn decrement_stop_refcount(_cs: CriticalSection, stop_mode: StopMode) { +fn decrement_stop_refcount(_cs: CriticalSection, stop_mode: StopMode) { match stop_mode { StopMode::Standby => {} StopMode::Stop2 => unsafe { @@ -182,6 +183,12 @@ pub enum StopMode { Standby, } +#[cfg(feature = "low-power")] +type BusyRccPeripheral = BusyPeripheral; + +#[cfg(not(feature = "low-power"))] +type BusyRccPeripheral = (); + impl RccInfo { /// Safety: /// - `reset_offset_and_bit`, if set, must correspond to valid xxxRST bit @@ -297,6 +304,7 @@ impl RccInfo { } } + #[allow(dead_code)] pub(crate) fn increment_stop_refcount_with_cs(&self, _cs: CriticalSection) { #[cfg(feature = "low-power")] increment_stop_refcount(_cs, self.stop_mode); @@ -304,9 +312,11 @@ impl RccInfo { #[allow(dead_code)] pub(crate) fn increment_stop_refcount(&self) { + #[cfg(feature = "low-power")] critical_section::with(|cs| self.increment_stop_refcount_with_cs(cs)) } + #[allow(dead_code)] pub(crate) fn decrement_stop_refcount_with_cs(&self, _cs: CriticalSection) { #[cfg(feature = "low-power")] decrement_stop_refcount(_cs, self.stop_mode); @@ -314,6 +324,7 @@ impl RccInfo { #[allow(dead_code)] pub(crate) fn decrement_stop_refcount(&self) { + #[cfg(feature = "low-power")] critical_section::with(|cs| self.decrement_stop_refcount_with_cs(cs)) } @@ -344,6 +355,12 @@ impl RccInfo { critical_section::with(|cs| self.disable_with_cs(cs)) } + #[allow(dead_code)] + pub(crate) fn block_stop(&self) -> BusyRccPeripheral { + #[cfg(feature = "low-power")] + BusyPeripheral::new(self.stop_mode) + } + fn reset_ptr(&self) -> Option<*mut u32> { if self.reset_offset_or_0xff != 0xff { Some(unsafe { (RCC.as_ptr() as *mut u32).add(self.reset_offset_or_0xff as _) }) @@ -357,6 +374,53 @@ impl RccInfo { } } +pub(crate) trait StoppablePeripheral { + #[cfg(feature = "low-power")] + #[allow(dead_code)] + fn stop_mode(&self) -> StopMode; +} + +#[cfg(feature = "low-power")] +impl<'a> StoppablePeripheral for StopMode { + fn stop_mode(&self) -> StopMode { + *self + } +} + +pub(crate) struct BusyPeripheral { + peripheral: T, +} + +impl BusyPeripheral { + pub fn new(peripheral: T) -> Self { + #[cfg(feature = "low-power")] + critical_section::with(|cs| increment_stop_refcount(cs, peripheral.stop_mode())); + + Self { peripheral } + } +} + +impl Drop for BusyPeripheral { + fn drop(&mut self) { + #[cfg(feature = "low-power")] + critical_section::with(|cs| decrement_stop_refcount(cs, self.peripheral.stop_mode())); + } +} + +impl ops::Deref for BusyPeripheral { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.peripheral + } +} + +impl ops::DerefMut for BusyPeripheral { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.peripheral + } +} + #[allow(unused)] mod util { use crate::time::Hertz; diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index 1af78b358..8047d6005 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -491,8 +491,7 @@ impl<'d> UartTx<'d, Async> { /// Initiate an asynchronous UART write pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { - self.info.rcc.increment_stop_refcount(); - let _ = OnDrop::new(|| self.info.rcc.decrement_stop_refcount()); + let _ = self.info.rcc.block_stop(); let r = self.info.regs; @@ -511,8 +510,7 @@ impl<'d> UartTx<'d, Async> { /// Wait until transmission complete pub async fn flush(&mut self) -> Result<(), Error> { - self.info.rcc.increment_stop_refcount(); - let _ = OnDrop::new(|| self.info.rcc.decrement_stop_refcount()); + let _ = self.info.rcc.block_stop(); flush(&self.info, &self.state).await } @@ -732,8 +730,7 @@ impl<'d> UartRx<'d, Async> { /// Initiate an asynchronous UART read pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { - self.info.rcc.increment_stop_refcount(); - let _ = OnDrop::new(|| self.info.rcc.decrement_stop_refcount()); + let _ = self.info.rcc.block_stop(); self.inner_read(buffer, false).await?; @@ -742,8 +739,7 @@ impl<'d> UartRx<'d, Async> { /// Initiate an asynchronous read with idle line detection enabled pub async fn read_until_idle(&mut self, buffer: &mut [u8]) -> Result { - self.info.rcc.increment_stop_refcount(); - let _ = OnDrop::new(|| self.info.rcc.decrement_stop_refcount()); + let _ = self.info.rcc.block_stop(); self.inner_read(buffer, true).await } -- cgit