From 060d1f6e6f36b01e1f0ec4beb20499ac22d94b24 Mon Sep 17 00:00:00 2001 From: Jamie Bird Date: Fri, 21 Jun 2024 15:09:57 +0100 Subject: [PATCH 01/57] Fix: Ensure I2C bus is free before master-write operation The I2C master-write function was failing when executed immediately after an I2C read operation, requiring manual delays to function correctly. This fix introduces a check to ensure the I2C bus is free before initiating the write operation. According to the RM0399 manual for STM32H7 chips, the BUSY bit (Bit 15 in the I2C ISR register) indicates whether a communication is in progress on the bus. The BUSY bit is set by hardware when a START condition is detected and cleared when a STOP condition is detected or when PE = 0. This fix prevents the write operation from starting until the BUSY bit is cleared. --- embassy-stm32/src/i2c/v2.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 80163c287..27381cd3c 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -109,6 +109,11 @@ impl<'d, M: Mode> I2c<'d, M> { timeout.check()?; } + // Wait for the bus to be free + while info.regs.isr().read().busy(){ + timeout.check()?; + }; + let reload = if reload { i2c::vals::Reload::NOTCOMPLETED } else { From 18ba56534bbff5fb00382e54aba8dbc32365e896 Mon Sep 17 00:00:00 2001 From: Jamie Bird Date: Fri, 21 Jun 2024 15:29:02 +0100 Subject: [PATCH 02/57] Fix Formatting Issues --- embassy-stm32/src/i2c/v2.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 27381cd3c..8c8df79dd 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -110,9 +110,9 @@ impl<'d, M: Mode> I2c<'d, M> { } // Wait for the bus to be free - while info.regs.isr().read().busy(){ + while info.regs.isr().read().busy() { timeout.check()?; - }; + } let reload = if reload { i2c::vals::Reload::NOTCOMPLETED From 00ff1409cd34744d7d5f42c6aa099f6088683d3d Mon Sep 17 00:00:00 2001 From: Alexandros Liarokapis Date: Tue, 18 Jun 2024 18:42:27 +0300 Subject: [PATCH 03/57] Enables half transfer ir when constructing a ReadableDmaRingBuffer The half transfer irq needs to be enabled in order for the hardware to notify the waker when the transfer is at half. This is needed to ensure no overuns occur when using `ReadableDmaRingBuffer`'s `read_exact`. Otherwise we are only notified when the DMA has completed its cycle and is on its way to start overwriting the data. The docs in the dma_bdma buf module also seem to imply that the half transfer irq must be enabled for proper operation. The only consumers of the `ReadableDmaRingBuffer` api are the sai module and the `RingBufferedUartRx`. The former enables the irq manually when constructing the transfer options while the latter does not. This may also be the cause for #1441. --- embassy-stm32/src/dma/dma_bdma.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/embassy-stm32/src/dma/dma_bdma.rs b/embassy-stm32/src/dma/dma_bdma.rs index a462e317f..8a6aa53a0 100644 --- a/embassy-stm32/src/dma/dma_bdma.rs +++ b/embassy-stm32/src/dma/dma_bdma.rs @@ -904,6 +904,7 @@ impl<'a, W: Word> WritableRingBuffer<'a, W> { let data_size = W::size(); let buffer_ptr = buffer.as_mut_ptr(); + options.half_transfer_ir = true; options.complete_transfer_ir = true; options.circular = true; From 3883a5b2de75d60301307bffb64181f755c397aa Mon Sep 17 00:00:00 2001 From: Alexandros Liarokapis Date: Sun, 23 Jun 2024 12:43:24 +0300 Subject: [PATCH 04/57] Enables adc v4 averaging support. The Adc v4 peripheral includes a hardware oversampler. This PR adds an averaging interface that keeps most of the current interface backwards compatible while allowing for the common use-case of hardware-averaging. A more comprehensive oversampler interface may be exposed in the future. --- embassy-stm32/src/adc/v4.rs | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/embassy-stm32/src/adc/v4.rs b/embassy-stm32/src/adc/v4.rs index 50db646fe..b15aebc10 100644 --- a/embassy-stm32/src/adc/v4.rs +++ b/embassy-stm32/src/adc/v4.rs @@ -126,6 +126,21 @@ impl Prescaler { } } +/// Number of samples used for averaging. +pub enum Averaging { + Disabled, + Samples2, + Samples4, + Samples8, + Samples16, + Samples32, + Samples64, + Samples128, + Samples256, + Samples512, + Samples1024, +} + impl<'d, T: Instance> Adc<'d, T> { /// Create a new ADC driver. pub fn new(adc: impl Peripheral

+ 'd) -> Self { @@ -252,6 +267,29 @@ impl<'d, T: Instance> Adc<'d, T> { T::regs().cfgr().modify(|reg| reg.set_res(resolution.into())); } + /// Set hardware averaging. + pub fn set_averaging(&mut self, averaging: Averaging) { + let (enable, samples, right_shift) = match averaging { + Averaging::Disabled => (false, 0, 0), + Averaging::Samples2 => (true, 1, 1), + Averaging::Samples4 => (true, 3, 2), + Averaging::Samples8 => (true, 7, 3), + Averaging::Samples16 => (true, 15, 4), + Averaging::Samples32 => (true, 31, 5), + Averaging::Samples64 => (true, 63, 6), + Averaging::Samples128 => (true, 127, 7), + Averaging::Samples256 => (true, 255, 8), + Averaging::Samples512 => (true, 511, 9), + Averaging::Samples1024 => (true, 1023, 10), + }; + + T::regs().cfgr2().modify(|reg| { + reg.set_rovse(enable); + reg.set_osvr(samples); + reg.set_ovss(right_shift); + }) + } + /// Perform a single conversion. fn convert(&mut self) -> u16 { T::regs().isr().modify(|reg| { From 2655426cd80b828593c8cd60930da3ebbd60e85c Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Sun, 23 Jun 2024 16:43:12 +0200 Subject: [PATCH 05/57] Add async wait to TSC --- embassy-stm32/src/tsc/mod.rs | 139 ++++++++++++++++++++++---------- examples/stm32u5/src/bin/tsc.rs | 12 ++- 2 files changed, 107 insertions(+), 44 deletions(-) diff --git a/embassy-stm32/src/tsc/mod.rs b/embassy-stm32/src/tsc/mod.rs index 8ba208ce7..6497ff0ee 100644 --- a/embassy-stm32/src/tsc/mod.rs +++ b/embassy-stm32/src/tsc/mod.rs @@ -71,9 +71,13 @@ use embassy_hal_internal::{into_ref, PeripheralRef}; pub use enums::*; use crate::gpio::{AfType, AnyPin, OutputType, Speed}; -use crate::pac::tsc::Tsc as Regs; +use crate::interrupt; +use crate::interrupt::typelevel::Interrupt; use crate::rcc::{self, RccPeripheral}; use crate::{peripherals, Peripheral}; +use core::future::poll_fn; +use core::task::Poll; +use embassy_sync::waitqueue::AtomicWaker; #[cfg(tsc_v1)] const TSC_NUM_GROUPS: u32 = 6; @@ -90,6 +94,18 @@ pub enum Error { Test, } +/// TSC interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + T::regs().ier().write(|w| w.set_eoaie(false)); + T::waker().wake(); + } +} + /// Pin type definition to control IO parameters pub enum PinType { /// Sensing channel pin connected to an electrode @@ -510,6 +526,7 @@ impl<'d, T: Instance> Tsc<'d, T> { /// Create new TSC driver pub fn new( peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, g1: Option>, g2: Option>, g3: Option>, @@ -663,7 +680,7 @@ impl<'d, T: Instance> Tsc<'d, T> { rcc::enable_and_reset::(); - T::REGS.cr().modify(|w| { + T::regs().cr().modify(|w| { w.set_tsce(true); w.set_ctph(config.ct_pulse_high_length.into()); w.set_ctpl(config.ct_pulse_low_length.into()); @@ -691,33 +708,39 @@ impl<'d, T: Instance> Tsc<'d, T> { // Set IO configuration // Disable Schmitt trigger hysteresis on all used TSC IOs - T::REGS + T::regs() .iohcr() .write(|w| w.0 = !(config.channel_ios | config.shield_ios | config.sampling_ios)); // Set channel and shield IOs - T::REGS.ioccr().write(|w| w.0 = config.channel_ios | config.shield_ios); + T::regs() + .ioccr() + .write(|w| w.0 = config.channel_ios | config.shield_ios); // Set sampling IOs - T::REGS.ioscr().write(|w| w.0 = config.sampling_ios); + T::regs().ioscr().write(|w| w.0 = config.sampling_ios); // Set the groups to be acquired - T::REGS + T::regs() .iogcsr() .write(|w| w.0 = Self::extract_groups(config.channel_ios)); // Disable interrupts - T::REGS.ier().modify(|w| { + T::regs().ier().modify(|w| { w.set_eoaie(false); w.set_mceie(false); }); // Clear flags - T::REGS.icr().modify(|w| { + T::regs().icr().modify(|w| { w.set_eoaic(true); w.set_mceic(true); }); + unsafe { + T::Interrupt::enable(); + } + Self { _peri: peri, _g1: g1, @@ -740,24 +763,24 @@ impl<'d, T: Instance> Tsc<'d, T> { self.state = State::Busy; // Disable interrupts - T::REGS.ier().modify(|w| { + T::regs().ier().modify(|w| { w.set_eoaie(false); w.set_mceie(false); }); // Clear flags - T::REGS.icr().modify(|w| { + T::regs().icr().modify(|w| { w.set_eoaic(true); w.set_mceic(true); }); // Set the touch sensing IOs not acquired to the default mode - T::REGS.cr().modify(|w| { + T::regs().cr().modify(|w| { w.set_iodef(self.config.io_default_mode); }); // Start the acquisition - T::REGS.cr().modify(|w| { + T::regs().cr().modify(|w| { w.set_start(true); }); } @@ -767,41 +790,41 @@ impl<'d, T: Instance> Tsc<'d, T> { self.state = State::Busy; // Enable interrupts - T::REGS.ier().modify(|w| { + T::regs().ier().modify(|w| { w.set_eoaie(true); w.set_mceie(self.config.max_count_interrupt); }); // Clear flags - T::REGS.icr().modify(|w| { + T::regs().icr().modify(|w| { w.set_eoaic(true); w.set_mceic(true); }); // Set the touch sensing IOs not acquired to the default mode - T::REGS.cr().modify(|w| { + T::regs().cr().modify(|w| { w.set_iodef(self.config.io_default_mode); }); // Start the acquisition - T::REGS.cr().modify(|w| { + T::regs().cr().modify(|w| { w.set_start(true); }); } /// Stop charge transfer acquisition pub fn stop(&mut self) { - T::REGS.cr().modify(|w| { + T::regs().cr().modify(|w| { w.set_start(false); }); // Set the touch sensing IOs in low power mode - T::REGS.cr().modify(|w| { + T::regs().cr().modify(|w| { w.set_iodef(false); }); // Clear flags - T::REGS.icr().modify(|w| { + T::regs().icr().modify(|w| { w.set_eoaic(true); w.set_mceic(true); }); @@ -811,23 +834,23 @@ impl<'d, T: Instance> Tsc<'d, T> { /// Stop charge transfer acquisition and clear interrupts pub fn stop_it(&mut self) { - T::REGS.cr().modify(|w| { + T::regs().cr().modify(|w| { w.set_start(false); }); // Set the touch sensing IOs in low power mode - T::REGS.cr().modify(|w| { + T::regs().cr().modify(|w| { w.set_iodef(false); }); // Disable interrupts - T::REGS.ier().modify(|w| { + T::regs().ier().modify(|w| { w.set_eoaie(false); w.set_mceie(false); }); // Clear flags - T::REGS.icr().modify(|w| { + T::regs().icr().modify(|w| { w.set_eoaic(true); w.set_mceic(true); }); @@ -840,11 +863,31 @@ impl<'d, T: Instance> Tsc<'d, T> { while self.get_state() == State::Busy {} } + /// Asyncronously wait for the end of an acquisition + pub async fn pend_for_acquisition(&mut self) { + poll_fn(|cx| match self.get_state() { + State::Busy => { + T::waker().register(cx.waker()); + T::regs().ier().write(|w| w.set_eoaie(true)); + if self.get_state() != State::Busy { + T::regs().ier().write(|w| w.set_eoaie(false)); + return Poll::Ready(()); + } + Poll::Pending + } + _ => { + T::regs().ier().write(|w| w.set_eoaie(false)); + Poll::Ready(()) + } + }) + .await; + } + /// Get current state of acquisition pub fn get_state(&mut self) -> State { if self.state == State::Busy { - if T::REGS.isr().read().eoaf() { - if T::REGS.isr().read().mcef() { + if T::regs().isr().read().eoaf() { + if T::regs().isr().read().mcef() { self.state = State::Error } else { self.state = State::Ready @@ -859,16 +902,16 @@ impl<'d, T: Instance> Tsc<'d, T> { // Status bits are set by hardware when the acquisition on the corresponding // enabled analog IO group is complete, cleared when new acquisition is started let status = match index { - Group::One => T::REGS.iogcsr().read().g1s(), - Group::Two => T::REGS.iogcsr().read().g2s(), - Group::Three => T::REGS.iogcsr().read().g3s(), - Group::Four => T::REGS.iogcsr().read().g4s(), - Group::Five => T::REGS.iogcsr().read().g5s(), - Group::Six => T::REGS.iogcsr().read().g6s(), + Group::One => T::regs().iogcsr().read().g1s(), + Group::Two => T::regs().iogcsr().read().g2s(), + Group::Three => T::regs().iogcsr().read().g3s(), + Group::Four => T::regs().iogcsr().read().g4s(), + Group::Five => T::regs().iogcsr().read().g5s(), + Group::Six => T::regs().iogcsr().read().g6s(), #[cfg(any(tsc_v2, tsc_v3))] - Group::Seven => T::REGS.iogcsr().read().g7s(), + Group::Seven => T::regs().iogcsr().read().g7s(), #[cfg(tsc_v3)] - Group::Eight => T::REGS.iogcsr().read().g8s(), + Group::Eight => T::regs().iogcsr().read().g8s(), }; match status { true => GroupStatus::Complete, @@ -878,13 +921,13 @@ impl<'d, T: Instance> Tsc<'d, T> { /// Get the count for the acquisiton, valid once group status is set pub fn group_get_value(&mut self, index: Group) -> u16 { - T::REGS.iogcr(index.into()).read().cnt() + T::regs().iogcr(index.into()).read().cnt() } /// Discharge the IOs for subsequent acquisition pub fn discharge_io(&mut self, status: bool) { // Set the touch sensing IOs in low power mode - T::REGS.cr().modify(|w| { + T::regs().cr().modify(|w| { w.set_iodef(!status); }); } @@ -897,20 +940,32 @@ impl<'d, T: Instance> Drop for Tsc<'d, T> { } pub(crate) trait SealedInstance { - const REGS: Regs; + fn regs() -> crate::pac::tsc::Tsc; + fn waker() -> &'static AtomicWaker; } /// TSC instance trait #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + RccPeripheral {} +pub trait Instance: Peripheral

+ SealedInstance + RccPeripheral { + /// Interrupt for this TSC instance + type Interrupt: interrupt::typelevel::Interrupt; +} -foreach_peripheral!( - (tsc, $inst:ident) => { - impl SealedInstance for peripherals::$inst { - const REGS: Regs = crate::pac::$inst; +foreach_interrupt!( + ($inst:ident, tsc, TSC, GLOBAL, $irq:ident) => { + impl Instance for peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; } - impl Instance for peripherals::$inst {} + impl SealedInstance for peripherals::$inst { + fn regs() -> crate::pac::tsc::Tsc { + crate::pac::$inst + } + fn waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } + } }; ); diff --git a/examples/stm32u5/src/bin/tsc.rs b/examples/stm32u5/src/bin/tsc.rs index f5593d1c4..642bbeaca 100644 --- a/examples/stm32u5/src/bin/tsc.rs +++ b/examples/stm32u5/src/bin/tsc.rs @@ -2,10 +2,17 @@ #![no_main] use defmt::*; -use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{ + bind_interrupts, + tsc::{self, *}, +}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + TSC => InterruptHandler; +}); + #[cortex_m_rt::exception] unsafe fn HardFault(_: &cortex_m_rt::ExceptionFrame) -> ! { cortex_m::peripheral::SCB::sys_reset(); @@ -47,6 +54,7 @@ async fn main(_spawner: embassy_executor::Spawner) { let mut touch_controller = tsc::Tsc::new( context.TSC, + Irqs, Some(g1), Some(g2), None, @@ -67,7 +75,7 @@ async fn main(_spawner: embassy_executor::Spawner) { let mut group_seven_val = 0; info!("Starting touch_controller interface"); loop { - touch_controller.poll_for_acquisition(); + touch_controller.pend_for_acquisition().await; touch_controller.discharge_io(true); Timer::after_millis(1).await; From 7eb605d1165eaf4cdf90453d1ed2d6976dd514af Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Sun, 23 Jun 2024 16:54:10 +0200 Subject: [PATCH 06/57] fmt --- embassy-stm32/src/tsc/mod.rs | 9 ++++----- examples/stm32u5/src/bin/tsc.rs | 6 ++---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/embassy-stm32/src/tsc/mod.rs b/embassy-stm32/src/tsc/mod.rs index 6497ff0ee..d5da2529f 100644 --- a/embassy-stm32/src/tsc/mod.rs +++ b/embassy-stm32/src/tsc/mod.rs @@ -65,19 +65,18 @@ /// Enums defined for peripheral parameters pub mod enums; +use core::future::poll_fn; use core::marker::PhantomData; +use core::task::Poll; use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; pub use enums::*; use crate::gpio::{AfType, AnyPin, OutputType, Speed}; -use crate::interrupt; use crate::interrupt::typelevel::Interrupt; use crate::rcc::{self, RccPeripheral}; -use crate::{peripherals, Peripheral}; -use core::future::poll_fn; -use core::task::Poll; -use embassy_sync::waitqueue::AtomicWaker; +use crate::{interrupt, peripherals, Peripheral}; #[cfg(tsc_v1)] const TSC_NUM_GROUPS: u32 = 6; diff --git a/examples/stm32u5/src/bin/tsc.rs b/examples/stm32u5/src/bin/tsc.rs index 642bbeaca..db85fb158 100644 --- a/examples/stm32u5/src/bin/tsc.rs +++ b/examples/stm32u5/src/bin/tsc.rs @@ -2,10 +2,8 @@ #![no_main] use defmt::*; -use embassy_stm32::{ - bind_interrupts, - tsc::{self, *}, -}; +use embassy_stm32::bind_interrupts; +use embassy_stm32::tsc::{self, *}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; From f64dd8228b7b8a570546ffa9b522ae85145cfdef Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 24 Jun 2024 17:09:43 -0700 Subject: [PATCH 07/57] new PR, taking Dirbao's advice to make the DMA impl in a separate struct that consumes Adc to make RingBufferedAdc. Handling overrun similar to RingBufferedUart --- embassy-stm32/build.rs | 6 +- embassy-stm32/src/adc/mod.rs | 2 + embassy-stm32/src/adc/ringbuffered_v2.rs | 376 +++++++++++++++++++++++ embassy-stm32/src/adc/v2.rs | 3 + examples/stm32f4/src/bin/adc_dma.rs | 59 ++++ 5 files changed, 445 insertions(+), 1 deletion(-) create mode 100644 embassy-stm32/src/adc/ringbuffered_v2.rs create mode 100644 examples/stm32f4/src/bin/adc_dma.rs diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 6aedcc228..24e2226a2 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -764,7 +764,7 @@ fn main() { #[rustfmt::skip] let signals: HashMap<_, _> = [ - // (kind, signal) => trait + // (kind, signal) => trait (("ucpd", "CC1"), quote!(crate::ucpd::Cc1Pin)), (("ucpd", "CC2"), quote!(crate::ucpd::Cc2Pin)), (("usart", "TX"), quote!(crate::usart::TxPin)), @@ -1178,6 +1178,10 @@ fn main() { let signals: HashMap<_, _> = [ // (kind, signal) => trait + (("adc", "ADC"), quote!(crate::adc::RxDma)), + (("adc", "ADC1"), quote!(crate::adc::RxDma)), + (("adc", "ADC2"), quote!(crate::adc::RxDma)), + (("adc", "ADC3"), quote!(crate::adc::RxDma)), (("ucpd", "RX"), quote!(crate::ucpd::RxDma)), (("ucpd", "TX"), quote!(crate::ucpd::TxDma)), (("usart", "RX"), quote!(crate::usart::RxDma)), diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs index 0c22a7dae..7a7d7cd8e 100644 --- a/embassy-stm32/src/adc/mod.rs +++ b/embassy-stm32/src/adc/mod.rs @@ -28,6 +28,8 @@ pub use crate::pac::adc::vals::Res as Resolution; pub use crate::pac::adc::vals::SampleTime; use crate::peripherals; +dma_trait!(RxDma, Instance); + /// Analog to Digital driver. pub struct Adc<'d, T: Instance> { #[allow(unused)] diff --git a/embassy-stm32/src/adc/ringbuffered_v2.rs b/embassy-stm32/src/adc/ringbuffered_v2.rs new file mode 100644 index 000000000..e11311b9a --- /dev/null +++ b/embassy-stm32/src/adc/ringbuffered_v2.rs @@ -0,0 +1,376 @@ +use core::marker::PhantomData; +use core::mem; +use core::sync::atomic::{compiler_fence, Ordering}; + +use embassy_hal_internal::{into_ref, Peripheral}; +use stm32_metapac::adc::vals::SampleTime; + +use crate::adc::{Adc, AdcChannel, Instance, RxDma}; +use crate::dma::ringbuffer::OverrunError; +use crate::dma::{Priority, ReadableRingBuffer, TransferOptions}; +use crate::pac::adc::vals; +use crate::rcc; + +fn clear_interrupt_flags(r: crate::pac::adc::Adc) { + r.sr().modify(|regs| { + regs.set_eoc(false); + regs.set_ovr(false); + }); +} + +#[derive(PartialOrd, PartialEq, Debug, Clone, Copy)] +pub enum Sequence { + One, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, + Nine, + Ten, + Eleven, + Twelve, + Thirteen, + Fourteen, + Fifteen, + Sixteen, +} + +impl From for u8 { + fn from(s: Sequence) -> u8 { + match s { + Sequence::One => 0, + Sequence::Two => 1, + Sequence::Three => 2, + Sequence::Four => 3, + Sequence::Five => 4, + Sequence::Six => 5, + Sequence::Seven => 6, + Sequence::Eight => 7, + Sequence::Nine => 8, + Sequence::Ten => 9, + Sequence::Eleven => 10, + Sequence::Twelve => 11, + Sequence::Thirteen => 12, + Sequence::Fourteen => 13, + Sequence::Fifteen => 14, + Sequence::Sixteen => 15, + } + } +} + +impl From for Sequence { + fn from(val: u8) -> Self { + match val { + 0 => Sequence::One, + 1 => Sequence::Two, + 2 => Sequence::Three, + 3 => Sequence::Four, + 4 => Sequence::Five, + 5 => Sequence::Six, + 6 => Sequence::Seven, + 7 => Sequence::Eight, + 8 => Sequence::Nine, + 9 => Sequence::Ten, + 10 => Sequence::Eleven, + 11 => Sequence::Twelve, + 12 => Sequence::Thirteen, + 13 => Sequence::Fourteen, + 14 => Sequence::Fifteen, + 15 => Sequence::Sixteen, + _ => panic!("Invalid sequence number"), + } + } +} + +pub struct RingBufferedAdc<'d, T: Instance> { + _phantom: PhantomData, + ring_buf: ReadableRingBuffer<'d, u16>, +} + +impl<'d, T: Instance> Adc<'d, T> { + pub fn into_ring_buffered( + self, + dma: impl Peripheral

> + 'd, + dma_buf: &'d mut [u16], + ) -> RingBufferedAdc<'d, T> { + assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); + into_ref!(dma); + + let opts: crate::dma::TransferOptions = TransferOptions { + half_transfer_ir: true, + priority: Priority::VeryHigh, + ..Default::default() + }; + + // Safety: we forget the struct before this function returns. + let rx_src = T::regs().dr().as_ptr() as *mut u16; + let request = dma.request(); + + let ring_buf = unsafe { ReadableRingBuffer::new(dma, request, rx_src, dma_buf, opts) }; + + // Don't disable the clock + mem::forget(self); + + RingBufferedAdc { + _phantom: PhantomData, + ring_buf, + } + } +} + +impl<'d, T: Instance> RingBufferedAdc<'d, T> { + fn is_on() -> bool { + T::regs().cr2().read().adon() + } + + fn stop_adc() { + T::regs().cr2().modify(|reg| { + reg.set_adon(false); + }); + } + + fn start_adc() { + T::regs().cr2().modify(|reg| { + reg.set_adon(true); + }); + } + + /// Sets the channel sample time + /// + /// ## SAFETY: + /// - ADON == 0 i.e ADC must not be enabled when this is called. + unsafe fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { + if ch <= 9 { + T::regs().smpr2().modify(|reg| reg.set_smp(ch as _, sample_time)); + } else { + T::regs().smpr1().modify(|reg| reg.set_smp((ch - 10) as _, sample_time)); + } + } + + fn set_channels_sample_time(&mut self, ch: &[u8], sample_time: SampleTime) { + let ch_iter = ch.iter(); + for idx in ch_iter { + unsafe { + Self::set_channel_sample_time(*idx, sample_time); + } + } + } + + pub fn set_sample_sequence( + &mut self, + sequence: Sequence, + channel: &mut impl AdcChannel, + sample_time: SampleTime, + ) { + let was_on = Self::is_on(); + if !was_on { + Self::start_adc(); + } + + //Check the sequence is long enough + T::regs().sqr1().modify(|r| { + let prev: Sequence = r.l().into(); + if prev < sequence { + let new_l: Sequence = sequence; + trace!("Setting sequence length from {:?} to {:?}", prev as u8, new_l as u8); + r.set_l(sequence.into()) + } else { + r.set_l(prev.into()) + } + }); + + //Set this GPIO as an analog input. + channel.setup(); + + //Set the channel in the right sequence field. + match sequence { + Sequence::One => T::regs().sqr3().modify(|w| w.set_sq(0, channel.channel())), + Sequence::Two => T::regs().sqr3().modify(|w| w.set_sq(1, channel.channel())), + Sequence::Three => T::regs().sqr3().modify(|w| w.set_sq(2, channel.channel())), + Sequence::Four => T::regs().sqr3().modify(|w| w.set_sq(3, channel.channel())), + Sequence::Five => T::regs().sqr3().modify(|w| w.set_sq(4, channel.channel())), + Sequence::Six => T::regs().sqr3().modify(|w| w.set_sq(5, channel.channel())), + Sequence::Seven => T::regs().sqr2().modify(|w| w.set_sq(6, channel.channel())), + Sequence::Eight => T::regs().sqr2().modify(|w| w.set_sq(7, channel.channel())), + Sequence::Nine => T::regs().sqr2().modify(|w| w.set_sq(8, channel.channel())), + Sequence::Ten => T::regs().sqr2().modify(|w| w.set_sq(9, channel.channel())), + Sequence::Eleven => T::regs().sqr2().modify(|w| w.set_sq(10, channel.channel())), + Sequence::Twelve => T::regs().sqr2().modify(|w| w.set_sq(11, channel.channel())), + Sequence::Thirteen => T::regs().sqr1().modify(|w| w.set_sq(12, channel.channel())), + Sequence::Fourteen => T::regs().sqr1().modify(|w| w.set_sq(13, channel.channel())), + Sequence::Fifteen => T::regs().sqr1().modify(|w| w.set_sq(14, channel.channel())), + Sequence::Sixteen => T::regs().sqr1().modify(|w| w.set_sq(15, channel.channel())), + }; + + if !was_on { + Self::stop_adc(); + } + + self.set_channels_sample_time(&[channel.channel()], sample_time); + + Self::start_adc(); + } + + pub fn start(&mut self) -> Result<(), OverrunError> { + self.ring_buf.clear(); + + self.setup_adc(); + + Ok(()) + } + + fn stop(&mut self, err: OverrunError) -> Result { + self.teardown_adc(); + Err(err) + } + + pub fn teardown_adc(&mut self) { + // Stop the DMA transfer + self.ring_buf.request_stop(); + + let r = T::regs(); + + // Stop ADC + r.cr2().modify(|reg| { + // Stop ADC + reg.set_swstart(false); + // Stop DMA + reg.set_dma(false); + }); + + r.cr1().modify(|w| { + // Disable interrupt for end of conversion + w.set_eocie(false); + // Disable interrupt for overrun + w.set_ovrie(false); + }); + + clear_interrupt_flags(r); + + compiler_fence(Ordering::SeqCst); + } + + fn setup_adc(&mut self) { + compiler_fence(Ordering::SeqCst); + + self.ring_buf.start(); + + let r = T::regs(); + + //Enable ADC + let was_on = Self::is_on(); + if !was_on { + r.cr2().modify(|reg| { + reg.set_adon(false); + reg.set_swstart(false); + }); + } + + // Clear all interrupts + r.sr().modify(|regs| { + regs.set_eoc(false); + regs.set_ovr(false); + regs.set_strt(false); + }); + + r.cr1().modify(|w| { + // Enable interrupt for end of conversion + w.set_eocie(true); + // Enable interrupt for overrun + w.set_ovrie(true); + // Scanning converisons of multiple channels + w.set_scan(true); + // Continuous conversion mode + w.set_discen(false); + }); + + r.cr2().modify(|w| { + // Enable DMA mode + w.set_dma(true); + // Enable continuous conversions + w.set_cont(vals::Cont::CONTINUOUS); + // DMA requests are issues as long as DMA=1 and data are converted. + w.set_dds(vals::Dds::CONTINUOUS); + // EOC flag is set at the end of each conversion. + w.set_eocs(vals::Eocs::EACHCONVERSION); + }); + + //Being ADC conversions + T::regs().cr2().modify(|reg| { + reg.set_adon(true); + reg.set_swstart(true); + }); + + super::blocking_delay_us(3); + } + + /// Read bytes that are readily available in the ring buffer. + /// If no bytes are currently available in the buffer the call waits until the some + /// bytes are available (at least one byte and at most half the buffer size) + /// + /// Background receive is started if `start()` has not been previously called. + /// + /// Receive in the background is terminated if an error is returned. + /// It must then manually be started again by calling `start()` or by re-calling `read()`. + pub async fn read(&mut self, buf: &mut [u16; N]) -> Result { + let r = T::regs(); + + // Start background receive if it was not already started + if !r.cr2().read().dma() { + self.start()?; + } + + // Clear overrun flag if set. + if r.sr().read().ovr() { + r.sr().modify(|regs| { + regs.set_ovr(false); + // regs.set_eoc(false); + }); + // return self.stop(OverrunError); + } + + loop { + match self.ring_buf.read(buf) { + Ok((0, _)) => {} + Ok((len, _)) => { + return Ok(len); + } + Err(_) => { + return self.stop(OverrunError); + } + } + } + } + + pub async fn read_exact(&mut self, buf: &mut [u16; N]) -> Result { + let r = T::regs(); + + // Start background receive if it was not already started + if !r.cr2().read().dma() { + self.start()?; + } + + // Clear overrun flag if set. + if r.sr().read().ovr() { + r.sr().modify(|regs| { + regs.set_ovr(false); + // regs.set_eoc(false); + }); + // return self.stop(OverrunError); + } + match self.ring_buf.read_exact(buf).await { + Ok(len) => Ok(len), + Err(_) => self.stop(OverrunError), + } + } +} + +impl Drop for RingBufferedAdc<'_, T> { + fn drop(&mut self) { + self.teardown_adc(); + rcc::disable::(); + } +} diff --git a/embassy-stm32/src/adc/v2.rs b/embassy-stm32/src/adc/v2.rs index e3175dff5..ddeb7ea79 100644 --- a/embassy-stm32/src/adc/v2.rs +++ b/embassy-stm32/src/adc/v2.rs @@ -6,6 +6,9 @@ use crate::peripherals::ADC1; use crate::time::Hertz; use crate::{rcc, Peripheral}; +mod ringbuffered_v2; +pub use ringbuffered_v2::{RingBufferedAdc, Sequence}; + /// Default VREF voltage used for sample conversion to millivolts. pub const VREF_DEFAULT_MV: u32 = 3300; /// VREF voltage used for factory calibration of VREFINTCAL register. diff --git a/examples/stm32f4/src/bin/adc_dma.rs b/examples/stm32f4/src/bin/adc_dma.rs new file mode 100644 index 000000000..a2611bb6f --- /dev/null +++ b/examples/stm32f4/src/bin/adc_dma.rs @@ -0,0 +1,59 @@ +#![no_std] +#![no_main] +use cortex_m::singleton; +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::RingBufferedAdc; +use embassy_stm32::adc::{Adc, SampleTime, Sequence}; +use embassy_time::{Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + const ADC_BUF_SIZE: usize = 1024; + let mut p = embassy_stm32::init(Default::default()); + + let adc_data: &mut [u16; ADC_BUF_SIZE] = singleton!(ADCDAT : [u16; ADC_BUF_SIZE] = [0u16; ADC_BUF_SIZE]).unwrap(); + + let adc = Adc::new(p.ADC1); + + let mut adc: RingBufferedAdc = adc.into_ring_buffered(p.DMA2_CH0, adc_data); + + adc.set_sample_sequence(Sequence::One, &mut p.PA0, SampleTime::CYCLES112); + adc.set_sample_sequence(Sequence::Two, &mut p.PA2, SampleTime::CYCLES112); + adc.set_sample_sequence(Sequence::Three, &mut p.PA1, SampleTime::CYCLES112); + adc.set_sample_sequence(Sequence::Four, &mut p.PA3, SampleTime::CYCLES112); + + // Note that overrun is a big consideration in this implementation. Whatever task is running the adc.read() calls absolutely must circle back around + // to the adc.read() call before the DMA buffer is wrapped around > 1 time. At this point, the overrun is so significant that the context of + // what channel is at what index is lost. The buffer must be cleared and reset. This *is* handled here, but allowing this to happen will cause + // a reduction of performance as each time the buffer is reset, the adc & dma buffer must be restarted. + + // An interrupt executor with a higher priority than other tasks may be a good approach here, allowing this task to wake and read the buffer most + // frequently. + let mut tic = Instant::now(); + let mut buffer1: [u16; 256] = [0u16; 256]; + let _ = adc.start(); + loop { + match adc.read(&mut buffer1).await { + Ok(_data) => { + let toc = Instant::now(); + info!( + "\n adc1: {} dt = {}, n = {}", + buffer1[0..16], + (toc - tic).as_micros(), + _data + ); + tic = toc; + } + Err(e) => { + warn!("Error: {:?}", e); + buffer1 = [0u16; 256]; + let _ = adc.start(); + continue; + } + } + + Timer::after_micros(300).await; + } +} From 27b83fdbcfe7e9e64a8b65fdcb8dc82d6dc3e58c Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 24 Jun 2024 17:15:16 -0700 Subject: [PATCH 08/57] fmt --- examples/stm32f4/src/bin/adc_dma.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/stm32f4/src/bin/adc_dma.rs b/examples/stm32f4/src/bin/adc_dma.rs index a2611bb6f..88822a507 100644 --- a/examples/stm32f4/src/bin/adc_dma.rs +++ b/examples/stm32f4/src/bin/adc_dma.rs @@ -3,8 +3,7 @@ use cortex_m::singleton; use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::adc::RingBufferedAdc; -use embassy_stm32::adc::{Adc, SampleTime, Sequence}; +use embassy_stm32::adc::{Adc, RingBufferedAdc, SampleTime, Sequence}; use embassy_time::{Instant, Timer}; use {defmt_rtt as _, panic_probe as _}; @@ -14,7 +13,7 @@ async fn main(_spawner: Spawner) { let mut p = embassy_stm32::init(Default::default()); let adc_data: &mut [u16; ADC_BUF_SIZE] = singleton!(ADCDAT : [u16; ADC_BUF_SIZE] = [0u16; ADC_BUF_SIZE]).unwrap(); - + let adc = Adc::new(p.ADC1); let mut adc: RingBufferedAdc = adc.into_ring_buffered(p.DMA2_CH0, adc_data); From 7056783fa23eb25629e1e57da0021916a073a432 Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 24 Jun 2024 17:53:59 -0700 Subject: [PATCH 09/57] second adc added to example + API todos completed --- embassy-stm32/src/adc/ringbuffered_v2.rs | 14 +++-------- examples/stm32f4/src/bin/adc_dma.rs | 32 +++++++++++++++++++----- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/embassy-stm32/src/adc/ringbuffered_v2.rs b/embassy-stm32/src/adc/ringbuffered_v2.rs index e11311b9a..8c5eb9672 100644 --- a/embassy-stm32/src/adc/ringbuffered_v2.rs +++ b/embassy-stm32/src/adc/ringbuffered_v2.rs @@ -315,7 +315,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { /// /// Receive in the background is terminated if an error is returned. /// It must then manually be started again by calling `start()` or by re-calling `read()`. - pub async fn read(&mut self, buf: &mut [u16; N]) -> Result { + pub fn read(&mut self, buf: &mut [u16; N]) -> Result { let r = T::regs(); // Start background receive if it was not already started @@ -325,11 +325,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { // Clear overrun flag if set. if r.sr().read().ovr() { - r.sr().modify(|regs| { - regs.set_ovr(false); - // regs.set_eoc(false); - }); - // return self.stop(OverrunError); + return self.stop(OverrunError); } loop { @@ -355,11 +351,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { // Clear overrun flag if set. if r.sr().read().ovr() { - r.sr().modify(|regs| { - regs.set_ovr(false); - // regs.set_eoc(false); - }); - // return self.stop(OverrunError); + return self.stop(OverrunError); } match self.ring_buf.read_exact(buf).await { Ok(len) => Ok(len), diff --git a/examples/stm32f4/src/bin/adc_dma.rs b/examples/stm32f4/src/bin/adc_dma.rs index 88822a507..dd19caf1d 100644 --- a/examples/stm32f4/src/bin/adc_dma.rs +++ b/examples/stm32f4/src/bin/adc_dma.rs @@ -13,15 +13,18 @@ async fn main(_spawner: Spawner) { let mut p = embassy_stm32::init(Default::default()); let adc_data: &mut [u16; ADC_BUF_SIZE] = singleton!(ADCDAT : [u16; ADC_BUF_SIZE] = [0u16; ADC_BUF_SIZE]).unwrap(); + let adc_data2: &mut [u16; ADC_BUF_SIZE] = singleton!(ADCDAT2 : [u16; ADC_BUF_SIZE] = [0u16; ADC_BUF_SIZE]).unwrap(); let adc = Adc::new(p.ADC1); + let adc2 = Adc::new(p.ADC2); let mut adc: RingBufferedAdc = adc.into_ring_buffered(p.DMA2_CH0, adc_data); + let mut adc2: RingBufferedAdc = adc2.into_ring_buffered(p.DMA2_CH2, adc_data2); adc.set_sample_sequence(Sequence::One, &mut p.PA0, SampleTime::CYCLES112); adc.set_sample_sequence(Sequence::Two, &mut p.PA2, SampleTime::CYCLES112); - adc.set_sample_sequence(Sequence::Three, &mut p.PA1, SampleTime::CYCLES112); - adc.set_sample_sequence(Sequence::Four, &mut p.PA3, SampleTime::CYCLES112); + adc2.set_sample_sequence(Sequence::One, &mut p.PA1, SampleTime::CYCLES112); + adc2.set_sample_sequence(Sequence::Two, &mut p.PA3, SampleTime::CYCLES112); // Note that overrun is a big consideration in this implementation. Whatever task is running the adc.read() calls absolutely must circle back around // to the adc.read() call before the DMA buffer is wrapped around > 1 time. At this point, the overrun is so significant that the context of @@ -31,10 +34,12 @@ async fn main(_spawner: Spawner) { // An interrupt executor with a higher priority than other tasks may be a good approach here, allowing this task to wake and read the buffer most // frequently. let mut tic = Instant::now(); - let mut buffer1: [u16; 256] = [0u16; 256]; + let mut buffer1 = [0u16; 256]; + let mut buffer2 = [0u16; 256]; let _ = adc.start(); + let _ = adc2.start(); loop { - match adc.read(&mut buffer1).await { + match adc.read_exact(&mut buffer1).await { Ok(_data) => { let toc = Instant::now(); info!( @@ -49,10 +54,25 @@ async fn main(_spawner: Spawner) { warn!("Error: {:?}", e); buffer1 = [0u16; 256]; let _ = adc.start(); - continue; } } - Timer::after_micros(300).await; + match adc2.read_exact(&mut buffer2).await { + Ok(_data) => { + let toc = Instant::now(); + info!( + "\n adc2: {} dt = {}, n = {}", + buffer2[0..16], + (toc - tic).as_micros(), + _data + ); + tic = toc; + } + Err(e) => { + warn!("Error: {:?}", e); + buffer2 = [0u16; 256]; + let _ = adc2.start(); + } + } } } From 6926e9e07154c72d68ef099d5f28178274f86032 Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 24 Jun 2024 23:15:00 -0700 Subject: [PATCH 10/57] CI --- examples/stm32f4/src/bin/adc_dma.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/examples/stm32f4/src/bin/adc_dma.rs b/examples/stm32f4/src/bin/adc_dma.rs index dd19caf1d..992bed573 100644 --- a/examples/stm32f4/src/bin/adc_dma.rs +++ b/examples/stm32f4/src/bin/adc_dma.rs @@ -4,14 +4,19 @@ use cortex_m::singleton; use defmt::*; use embassy_executor::Spawner; use embassy_stm32::adc::{Adc, RingBufferedAdc, SampleTime, Sequence}; -use embassy_time::{Instant, Timer}; +use embassy_stm32::Peripherals; +use embassy_time::Instant; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] -async fn main(_spawner: Spawner) { - const ADC_BUF_SIZE: usize = 1024; - let mut p = embassy_stm32::init(Default::default()); +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + spawner.must_spawn(adc_task(p)); +} +#[embassy_executor::task] +async fn adc_task(mut p: Peripherals) { + const ADC_BUF_SIZE: usize = 1024; let adc_data: &mut [u16; ADC_BUF_SIZE] = singleton!(ADCDAT : [u16; ADC_BUF_SIZE] = [0u16; ADC_BUF_SIZE]).unwrap(); let adc_data2: &mut [u16; ADC_BUF_SIZE] = singleton!(ADCDAT2 : [u16; ADC_BUF_SIZE] = [0u16; ADC_BUF_SIZE]).unwrap(); @@ -34,8 +39,8 @@ async fn main(_spawner: Spawner) { // An interrupt executor with a higher priority than other tasks may be a good approach here, allowing this task to wake and read the buffer most // frequently. let mut tic = Instant::now(); - let mut buffer1 = [0u16; 256]; - let mut buffer2 = [0u16; 256]; + let mut buffer1 = [0u16; 512]; + let mut buffer2 = [0u16; 512]; let _ = adc.start(); let _ = adc2.start(); loop { @@ -52,7 +57,7 @@ async fn main(_spawner: Spawner) { } Err(e) => { warn!("Error: {:?}", e); - buffer1 = [0u16; 256]; + buffer1 = [0u16; 512]; let _ = adc.start(); } } @@ -70,7 +75,7 @@ async fn main(_spawner: Spawner) { } Err(e) => { warn!("Error: {:?}", e); - buffer2 = [0u16; 256]; + buffer2 = [0u16; 512]; let _ = adc2.start(); } } From a0799bf270010f4e91b0c3eebe487d8bc6fb54fc Mon Sep 17 00:00:00 2001 From: Chen Yuheng <1016867898@qq.com> Date: Thu, 27 Jun 2024 17:04:26 +0800 Subject: [PATCH 11/57] Add adc oversampling support --- embassy-stm32/src/adc/v3.rs | 15 +++++++ examples/stm32g0/src/bin/adc_oversampling.rs | 43 ++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 examples/stm32g0/src/bin/adc_oversampling.rs diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index 398c57a92..bf7ad242b 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -277,6 +277,21 @@ impl<'d, T: Instance> Adc<'d, T> { val } + #[cfg(any(adc_g0, adc_u0))] + pub fn set_oversampling_shift(&mut self, shift: u8) { + T::regs().cfgr2().modify(|reg| reg.set_ovss(shift)); + } + + #[cfg(any(adc_g0, adc_u0))] + pub fn set_oversampling_ratio(&mut self, ratio: u8) { + T::regs().cfgr2().modify(|reg| reg.set_ovsr(ratio)); + } + + #[cfg(any(adc_g0, adc_u0))] + pub fn oversampling_enable(&mut self, enable: bool) { + T::regs().cfgr2().modify(|reg| reg.set_ovse(enable)); + } + fn set_channel_sample_time(_ch: u8, sample_time: SampleTime) { cfg_if! { if #[cfg(any(adc_g0, adc_u0))] { diff --git a/examples/stm32g0/src/bin/adc_oversampling.rs b/examples/stm32g0/src/bin/adc_oversampling.rs new file mode 100644 index 000000000..3c31eb206 --- /dev/null +++ b/examples/stm32g0/src/bin/adc_oversampling.rs @@ -0,0 +1,43 @@ +//! adc oversampling example +//! +//! This example uses adc oversampling to achieve 16bit data + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Adc oversample test"); + + let mut adc = Adc::new(p.ADC1); + adc.set_sample_time(SampleTime::CYCLES1_5); + let mut pin = p.PA1; + + // From https://www.st.com/resource/en/reference_manual/rm0444-stm32g0x1-advanced-armbased-32bit-mcus-stmicroelectronics.pdf + // page373 15.8 Oversampler + // Table 76. Maximum output results vs N and M. Grayed values indicates truncation + // 0x00 oversampling ratio X2 + // 0x01 oversampling ratio X4 + // 0x02 oversampling ratio X8 + // 0x03 oversampling ratio X16 + // 0x04 oversampling ratio X32 + // 0x05 oversampling ratio X64 + // 0x06 oversampling ratio X128 + // 0x07 oversampling ratio X256 + adc.set_oversampling_ratio(0x03); + adc.set_oversampling_shift(0b0000); + adc.oversampling_enable(true); + + loop { + let v = adc.read(&mut pin); + info!("--> {} ", v); //max 65520 = 0xFFF0 + Timer::after_millis(100).await; + } +} From 0e84bd8a91095c035b1b2fdf0c0d8d95b736cc9f Mon Sep 17 00:00:00 2001 From: David Haig Date: Thu, 27 Jun 2024 20:13:20 +0100 Subject: [PATCH 12/57] Add support for the stm32 ltdc display peripheral --- embassy-stm32/Cargo.toml | 5 +- embassy-stm32/src/ltdc.rs | 493 +++++++++++++++++++++++++--- examples/stm32h7/Cargo.toml | 2 + examples/stm32h7/src/bin/ferris.bmp | Bin 0 -> 6794 bytes examples/stm32h7/src/bin/ltdc.rs | 484 +++++++++++++++++++++++++++ 5 files changed, 933 insertions(+), 51 deletions(-) create mode 100644 examples/stm32h7/src/bin/ferris.bmp create mode 100644 examples/stm32h7/src/bin/ltdc.rs diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 77b517dba..7a81b705d 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -72,7 +72,8 @@ rand_core = "0.6.3" sdio-host = "0.5.0" critical-section = "1.1" #stm32-metapac = { version = "15" } -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-a8ab0a3421ed0ca4b282f54028a0a2decacd8631" } +#stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-a8ab0a3421ed0ca4b282f54028a0a2decacd8631" } +stm32-metapac = { git = "https://github.com/ninjasource/stm32-data-generated", branch = "stm32-ltdc", commit = "4c902fcd0889619e8af8bc03fa13b45c56fb3540" } vcell = "0.1.3" nb = "1.0.0" @@ -97,7 +98,7 @@ proc-macro2 = "1.0.36" quote = "1.0.15" #stm32-metapac = { version = "15", default-features = false, features = ["metadata"]} -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-a8ab0a3421ed0ca4b282f54028a0a2decacd8631", default-features = false, features = ["metadata"] } +stm32-metapac = { git = "https://github.com/ninjasource/stm32-data-generated", branch = "stm32-ltdc", commit = "4c902fcd0889619e8af8bc03fa13b45c56fb3540", default-features = false, features = ["metadata"] } [features] default = ["rt"] diff --git a/embassy-stm32/src/ltdc.rs b/embassy-stm32/src/ltdc.rs index 481d77843..adc9b8862 100644 --- a/embassy-stm32/src/ltdc.rs +++ b/embassy-stm32/src/ltdc.rs @@ -1,19 +1,184 @@ -//! LTDC -use core::marker::PhantomData; +//! LTDC - LCD-TFT Display Controller +//! See ST application note AN4861: Introduction to LCD-TFT display controller (LTDC) on STM32 MCUs for high level details +//! This module was tested against the stm32h735g-dk using the RM0468 ST reference manual for detailed register information -use crate::rcc::{self, RccPeripheral}; -use crate::{peripherals, Peripheral}; +use crate::{ + gpio::{AfType, OutputType, Speed}, + interrupt::{self, typelevel::Interrupt}, + peripherals, rcc, Peripheral, +}; +use core::{future::poll_fn, marker::PhantomData, task::Poll}; +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use stm32_metapac::ltdc::{ + regs::Dccr, + vals::{Bf1, Bf2, Cfuif, Clif, Crrif, Cterrif, Pf, Vbr}, +}; + +static LTDC_WAKER: AtomicWaker = AtomicWaker::new(); + +/// LTDC error +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// FIFO underrun. Generated when a pixel is requested while the FIFO is empty + FifoUnderrun, + /// Transfer error. Generated when a bus error occurs + TransferError, +} + +/// Display configuration parameters +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct LtdcConfiguration { + /// Active width in pixels + pub active_width: u16, + /// Active height in pixels + pub active_height: u16, + + /// Horizontal back porch (in units of pixel clock period) + pub h_back_porch: u16, + /// Horizontal front porch (in units of pixel clock period) + pub h_front_porch: u16, + /// Vertical back porch (in units of horizontal scan line) + pub v_back_porch: u16, + /// Vertical front porch (in units of horizontal scan line) + pub v_front_porch: u16, + + /// Horizontal synchronization width (in units of pixel clock period) + pub h_sync: u16, + /// Vertical synchronization height (in units of horizontal scan line) + pub v_sync: u16, + + /// Horizontal synchronization polarity: `false`: active low, `true`: active high + pub h_sync_polarity: PolarityActive, + /// Vertical synchronization polarity: `false`: active low, `true`: active high + pub v_sync_polarity: PolarityActive, + /// Data enable polarity: `false`: active low, `true`: active high + pub data_enable_polarity: PolarityActive, + /// Pixel clock polarity: `false`: falling edge, `true`: rising edge + pub pixel_clock_polarity: PolarityEdge, +} + +/// Edge polarity +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PolarityEdge { + /// Falling edge + FallingEdge, + /// Rising edge + RisingEdge, +} + +/// Active polarity +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PolarityActive { + /// Active low + ActiveLow, + /// Active high + ActiveHigh, +} /// LTDC driver. pub struct Ltdc<'d, T: Instance> { - _peri: PhantomData<&'d mut T>, + _peri: PeripheralRef<'d, T>, +} + +/// LTDC interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +/// 24 bit color +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct RgbColor { + /// Red + pub red: u8, + /// Green + pub green: u8, + /// Blue + pub blue: u8, +} + +/// Layer +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct LtdcLayerConfig { + /// Layer number + pub layer: LtdcLayer, + /// Pixel format + pub pixel_format: PixelFormat, + /// Window left x in pixels + pub window_x0: u16, + /// Window right x in pixels + pub window_x1: u16, + /// Window top y in pixels + pub window_y0: u16, + /// Window bottom y in pixels + pub window_y1: u16, +} + +/// Pixel format +#[repr(u8)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PixelFormat { + /// ARGB8888 + ARGB8888 = Pf::ARGB8888 as u8, + /// RGB888 + RGB888 = Pf::RGB888 as u8, + /// RGB565 + RGB565 = Pf::RGB565 as u8, + /// ARGB1555 + ARGB1555 = Pf::ARGB1555 as u8, + /// ARGB4444 + ARGB4444 = Pf::ARGB4444 as u8, + /// L8 (8-bit luminance) + L8 = Pf::L8 as u8, + /// AL44 (4-bit alpha, 4-bit luminance + AL44 = Pf::AL44 as u8, + /// AL88 (8-bit alpha, 8-bit luminance) + AL88 = Pf::AL88 as u8, +} + +impl PixelFormat { + /// Number of bytes per pixel + pub fn bytes_per_pixel(&self) -> usize { + match self { + PixelFormat::ARGB8888 => 4, + PixelFormat::RGB888 => 3, + PixelFormat::RGB565 | PixelFormat::ARGB4444 | PixelFormat::ARGB1555 | PixelFormat::AL88 => 2, + PixelFormat::AL44 | PixelFormat::L8 => 1, + } + } +} + +/// Ltdc Blending Layer +#[repr(usize)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum LtdcLayer { + /// Bottom layer + Layer1 = 0, + /// Top layer + Layer2 = 1, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + cortex_m::asm::dsb(); + Ltdc::::enable_interrupts(false); + LTDC_WAKER.wake(); + } } impl<'d, T: Instance> Ltdc<'d, T> { - /// Note: Full-Duplex modes are not supported at this time + /// Create a new LTDC driver. 8 pins per color channel for blue, green and red pub fn new( - _peri: impl Peripheral

+ 'd, - /* + peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, clk: impl Peripheral

> + 'd, hsync: impl Peripheral

> + 'd, vsync: impl Peripheral

> + 'd, @@ -41,49 +206,274 @@ impl<'d, T: Instance> Ltdc<'d, T> { r5: impl Peripheral

> + 'd, r6: impl Peripheral

> + 'd, r7: impl Peripheral

> + 'd, - */ ) -> Self { - //into_ref!(clk); - - critical_section::with(|_cs| { - // RM says the pllsaidivr should only be changed when pllsai is off. But this could have other unintended side effects. So let's just give it a try like this. - // According to the debugger, this bit gets set, anyway. - #[cfg(stm32f7)] - stm32_metapac::RCC - .dckcfgr1() - .modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2)); - - // It is set to RCC_PLLSAIDIVR_2 in ST's BSP example for the STM32469I-DISCO. - #[cfg(not(any(stm32f7, stm32u5)))] - stm32_metapac::RCC - .dckcfgr() - .modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2)); - }); - rcc::enable_and_reset::(); - //new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + into_ref!(peri); + new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(hsync, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(vsync, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(b0, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(b1, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(b2, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(b3, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(b4, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(b5, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(b6, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(b7, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(g0, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(g1, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(g2, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(g3, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(g4, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(g5, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(g6, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(g7, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(r0, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(r1, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(r2, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(r3, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(r4, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(r5, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(r6, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(r7, AfType::output(OutputType::PushPull, Speed::VeryHigh)); - // Set Tearing Enable pin according to CubeMx example - //te.set_as_af_pull(te.af_num(), AfType::output(OutputType::PushPull, Speed::Low)); - /* - T::regs().wcr().modify(|w| { - w.set_dsien(true); + Self { _peri: peri } + } + + fn clear_interrupt_flags() { + T::regs().icr().write(|w| { + w.set_cfuif(Cfuif::CLEAR); + w.set_clif(Clif::CLEAR); + w.set_crrif(Crrif::CLEAR); + w.set_cterrif(Cterrif::CLEAR); + }); + } + + fn enable_interrupts(enable: bool) { + T::regs().ier().write(|w| { + w.set_fuie(enable); + w.set_lie(false); // we are not interested in the line interrupt enable event + w.set_rrie(enable); + w.set_terrie(enable) + }); + + // enable interrupts for LTDC peripheral + T::Interrupt::unpend(); + if enable { + unsafe { T::Interrupt::enable() }; + } else { + T::Interrupt::disable() + } + } + + /// Set the current buffer. The async function will return when buffer has been completely copied to the LCD screen + /// frame_buffer_addr is a pointer to memory that should not move (best to make it static) + pub async fn set_buffer(&mut self, layer: LtdcLayer, frame_buffer_addr: *const ()) -> Result<(), Error> { + let mut bits = T::regs().isr().read(); + + // if all clear + if !bits.fuif() && !bits.lif() && !bits.rrif() && !bits.terrif() { + // wait for interrupt + poll_fn(|cx| { + // quick check to avoid registration if already done. + let bits = T::regs().isr().read(); + if bits.fuif() || bits.lif() || bits.rrif() || bits.terrif() { + return Poll::Ready(()); + } + + LTDC_WAKER.register(cx.waker()); + Self::clear_interrupt_flags(); // don't poison the request with old flags + Self::enable_interrupts(true); + + // set the new frame buffer address + let layer = T::regs().layer(layer as usize); + layer.cfbar().modify(|w| w.set_cfbadd(frame_buffer_addr as u32)); + + // configure a shadow reload for the next blanking period + T::regs().srcr().write(|w| { + w.set_vbr(Vbr::RELOAD); }); - */ - Self { _peri: PhantomData } + + // need to check condition after register to avoid a race + // condition that would result in lost notifications. + let bits = T::regs().isr().read(); + if bits.fuif() || bits.lif() || bits.rrif() || bits.terrif() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + // re-read the status register after wait. + bits = T::regs().isr().read(); + } + + let result = if bits.fuif() { + Err(Error::FifoUnderrun) + } else if bits.terrif() { + Err(Error::TransferError) + } else if bits.lif() { + panic!("line interrupt event is disabled") + } else if bits.rrif() { + // register reload flag is expected + Ok(()) + } else { + unreachable!("all interrupt status values checked") + }; + + Self::clear_interrupt_flags(); + result } - /// Set the enable bit in the control register and assert that it has been enabled - pub fn enable(&mut self) { - T::regs().gcr().modify(|w| w.set_ltdcen(true)); - assert!(T::regs().gcr().read().ltdcen()) + /// Initialize the display + pub fn init(&mut self, config: &LtdcConfiguration) { + use stm32_metapac::ltdc::vals::{Depol, Hspol, Pcpol, Vspol}; + let ltdc = T::regs(); + + // check bus access + assert!(ltdc.gcr().read().0 == 0x2220); // reset value + + // configure the HS, VS, DE and PC polarity + ltdc.gcr().modify(|w| { + w.set_hspol(match config.h_sync_polarity { + PolarityActive::ActiveHigh => Hspol::ACTIVEHIGH, + PolarityActive::ActiveLow => Hspol::ACTIVELOW, + }); + + w.set_vspol(match config.v_sync_polarity { + PolarityActive::ActiveHigh => Vspol::ACTIVEHIGH, + PolarityActive::ActiveLow => Vspol::ACTIVELOW, + }); + + w.set_depol(match config.data_enable_polarity { + PolarityActive::ActiveHigh => Depol::ACTIVEHIGH, + PolarityActive::ActiveLow => Depol::ACTIVELOW, + }); + + w.set_pcpol(match config.pixel_clock_polarity { + PolarityEdge::RisingEdge => Pcpol::RISINGEDGE, + PolarityEdge::FallingEdge => Pcpol::FALLINGEDGE, + }); + }); + + // set synchronization pulse width + ltdc.sscr().modify(|w| { + w.set_vsh(config.v_sync - 1); + w.set_hsw(config.h_sync - 1); + }); + + // set accumulated back porch + ltdc.bpcr().modify(|w| { + w.set_avbp(config.v_sync + config.v_back_porch - 1); + w.set_ahbp(config.h_sync + config.h_back_porch - 1); + }); + + // set accumulated active width + let aa_height = config.v_sync + config.v_back_porch + config.active_height - 1; + let aa_width = config.h_sync + config.h_back_porch + config.active_width - 1; + ltdc.awcr().modify(|w| { + w.set_aah(aa_height); + w.set_aaw(aa_width); + }); + + // set total width and height + let total_height: u16 = config.v_sync + config.v_back_porch + config.active_height + config.v_front_porch - 1; + let total_width: u16 = config.h_sync + config.h_back_porch + config.active_width + config.h_front_porch - 1; + ltdc.twcr().modify(|w| { + w.set_totalh(total_height); + w.set_totalw(total_width) + }); + + // set the background color value to black + ltdc.bccr().modify(|w| { + w.set_bcred(0); + w.set_bcgreen(0); + w.set_bcblue(0); + }); + + // enable LTDC by setting LTDCEN bit + ltdc.gcr().modify(|w| { + w.set_ltdcen(true); + }); } - /// Unset the enable bit in the control register and assert that it has been disabled - pub fn disable(&mut self) { - T::regs().gcr().modify(|w| w.set_ltdcen(false)); - assert!(!T::regs().gcr().read().ltdcen()) + /// Enable the layer + /// + /// clut - color look-up table applies to L8, AL44 and AL88 pixel format and will default to greyscale if None supplied and these pixel formats are used + pub fn enable_layer(&mut self, layer_config: &LtdcLayerConfig, clut: Option<&[RgbColor]>) { + let ltdc = T::regs(); + let layer = ltdc.layer(layer_config.layer as usize); + + // 256 color look-up table for L8, AL88 and AL88 pixel formats + if let Some(clut) = clut { + assert_eq!(clut.len(), 256, "Color lookup table must be exactly 256 in length"); + for (index, color) in clut.iter().enumerate() { + layer.clutwr().write(|w| { + w.set_clutadd(index as u8); + w.set_red(color.red); + w.set_green(color.green); + w.set_blue(color.blue); + }); + } + } + + // configure the horizontal start and stop position + let h_win_start = layer_config.window_x0 + ltdc.bpcr().read().ahbp() + 1; + let h_win_stop = layer_config.window_x1 + ltdc.bpcr().read().ahbp(); + layer.whpcr().write(|w| { + w.set_whstpos(h_win_start); + w.set_whsppos(h_win_stop); + }); + + // configure the vertical start and stop position + let v_win_start = layer_config.window_y0 + ltdc.bpcr().read().avbp() + 1; + let v_win_stop = layer_config.window_y1 + ltdc.bpcr().read().avbp(); + layer.wvpcr().write(|w| { + w.set_wvstpos(v_win_start); + w.set_wvsppos(v_win_stop) + }); + + // set the pixel format + layer + .pfcr() + .write(|w| w.set_pf(Pf::from_bits(layer_config.pixel_format as u8))); + + // set the default color value to transparent black + layer.dccr().write_value(Dccr::default()); + + // set the global constant alpha value + let alpha = 0xFF; + layer.cacr().write(|w| w.set_consta(alpha)); + + // set the blending factors. + layer.bfcr().modify(|w| { + w.set_bf1(Bf1::PIXEL); + w.set_bf2(Bf2::PIXEL); + }); + + // calculate framebuffer pixel size in bytes + let bytes_per_pixel = layer_config.pixel_format.bytes_per_pixel() as u16; + let width = layer_config.window_x1 - layer_config.window_x0; + let height = layer_config.window_y1 - layer_config.window_y0; + + // framebuffer pitch and line length + layer.cfblr().modify(|w| { + w.set_cfbp(width * bytes_per_pixel); + w.set_cfbll(width * bytes_per_pixel + 7); + }); + + // framebuffer line number + layer.cfblnr().modify(|w| w.set_cfblnbr(height)); + + // enable LTDC_Layer by setting LEN bit + layer.cr().modify(|w| { + if clut.is_some() { + w.set_cluten(true); + } + w.set_len(true); + }); } } @@ -95,9 +485,12 @@ trait SealedInstance: crate::rcc::SealedRccPeripheral { fn regs() -> crate::pac::ltdc::Ltdc; } -/// DSI instance trait. +/// LTDC instance trait. #[allow(private_bounds)] -pub trait Instance: SealedInstance + RccPeripheral + 'static {} +pub trait Instance: SealedInstance + Peripheral

+ crate::rcc::RccPeripheral + 'static + Send { + /// Interrupt for this LTDC instance. + type Interrupt: interrupt::typelevel::Interrupt; +} pin_trait!(ClkPin, Instance); pin_trait!(HsyncPin, Instance); @@ -128,14 +521,16 @@ pin_trait!(B5Pin, Instance); pin_trait!(B6Pin, Instance); pin_trait!(B7Pin, Instance); -foreach_peripheral!( - (ltdc, $inst:ident) => { - impl crate::ltdc::SealedInstance for peripherals::$inst { +foreach_interrupt!( + ($inst:ident, ltdc, LTDC, GLOBAL, $irq:ident) => { + impl Instance for peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; + } + + impl SealedInstance for peripherals::$inst { fn regs() -> crate::pac::ltdc::Ltdc { crate::pac::$inst } } - - impl crate::ltdc::Instance for peripherals::$inst {} }; ); diff --git a/examples/stm32h7/Cargo.toml b/examples/stm32h7/Cargo.toml index 0584f3916..6f3007492 100644 --- a/examples/stm32h7/Cargo.toml +++ b/examples/stm32h7/Cargo.toml @@ -34,6 +34,8 @@ stm32-fmc = "0.3.0" embedded-storage = "0.3.1" static_cell = "2" chrono = { version = "^0.4", default-features = false } +embedded-graphics = { version = "0.8.1" } +tinybmp = { version = "0.5" } # cargo build/run [profile.dev] diff --git a/examples/stm32h7/src/bin/ferris.bmp b/examples/stm32h7/src/bin/ferris.bmp new file mode 100644 index 0000000000000000000000000000000000000000..7a222ab84ddd9f2707ca8dcebad84b1b39c21818 GIT binary patch literal 6794 zcmb`L33OD|8OQ(JNnU0$OJ>O|nIvR^V8})iwgjaFsH`H2vdNBYA}T_u7=r{^1}IQL zRKN`(6x0McMUzp1q-luKI(W`lj>IPOV3HPUtHrL?CG_66&B`R_wD+9%?zjHl_wKvz zzB@B!%1$SQwlfZ>qS6d{RM4Xj)bvc74X|j%gBqCw_y)L=0Aw&fZDYnyTPsSvKa;4T z@qpl<@ILJNN$8_Wgep#pIF%W3oDN*P1&Fr-oDF^AozTRmfFz_+Uk2jTX^2mtRVDNX z)LBT-WCDrVAetPg)VWY44nVwSAh^UlsI_@iFNQX85R4WR`sfM}r!9d_TMSL&FmSpO zBUmpCLzH%8FA+E(3tL``ZUB_CLqx~1qsO$pt4Mb)^ayA z<{99u_d;))OKY5tM9WMh*rq|3JPT^ed^!xXkwkk<@&Y;>55Qtx3blO>wAMv1Sr;PS z{vh=B#V}e|z+!m}oMQ>Jwv|w)EQQIw5<17j&^VXD>{tb>Z4LA(tDsF=frQk@piNna z{K5jL(^f<0e1hKN25_nCk(mBC^t897JdK33C!y`T23lt|3{=i5Do3(o8`SBQu%~W> zE^QMWY1JqmT!O@mDkS&a413xR7&5lNp1u>#)VEqjul+4{QWbMK5@-o;no`<&o4kTqi3rFS)u=RZvx~v08>bDmuSuaALeGta} z`{2wzfW(~VV9z`ZL%%~XQ8}6Rj;z;V&OU;qoR^S7`#VOBq`Vp!2fPAH|D#Buhdui( z=yHz1nNtJ(fM39r`x>mYH{5m{=A4sAp+{2g30MZa4JSQ}d2b>)_Y@oh-h(OsEg14n z!<_p*Y_w0IeNz5AunhbFPI@HgorSTm9;TuOSPRa`>c{k1GvFBdIgG>Fkvyam)}dXPJ@)~m4*3$c z;eUi>=vT0pU4x_iI+BOqfN{hZu$Fy|`HL1{!QzE@qH;afZD_!bT|2R&@^kEdb`O?3 zc>|mGe}&_3oSPBYs&^!;CwOp0 z|76MaR3My?J5mE$D0dt`F5tRH%H`#76;X+e#ylrED+2MVm%VmH3*j!~`H_l;NQ204 z)a8tvGH>n1?a!0Dn%xa`HT$+LpEg4Bx&tGjI?T5yS}=DJeaPtwcc5HupeHK>vLtzf z29DmtX*oTJ`v48~s$~aR9>L&AMWn4zNg(T}cao$AxT9H4b0BIX(?c5}p*quo9>@%) zOV3#0<_IsqB88jY$f80UA=^R?R2!&dkW%Js5QkXi8pQ-Q3{HB|9TxSF^P7ZNJ3=f> znZZEWMf4;xMhF*@x#BC(L~Wy7M)IS!h;*J8rg3?sqftXhU)TLKRq%O*kr2761~Ggc zAwyi1>cNr8Jl~=f3b|8y)q+BrHH34y#uFl}8~kAwCvxRyKa31@xqezG=6Kg#2qbs? zOrX+}smUKOi&8Tot6Z*`WTT+^*xMm+?~nq$oYUoUt@x0sZSx7?_O*2h&3obC{c~q6 zoV8%?y?0L-{*uSbtp3(5D)q}k>UuJsCf+_m~>XFZ`yzK6NPB5AI)^lknUuTSZ||* z^$++7^Rfx&dw~koRU7uda^jS`#dFzzO)Q+9uXSDaw7O3o+q1Q*n%-SK%ig78nOmi? zzre9^*e+rD76Dt=#q<7FS9SFfx5q0?YS|yw`+V(Zk5pHSFwcLBlS#i(g+ITp7(~I}<#~6%e7!cn9p(>H zmoO@AoOCPxc4;NdjeMc~a=Cm9usRGBqISI_Qt5fLnB=#~miY=LBt=l_0DI(h196@5 zHFVQCY%z#+_nv)Yi%Usk{4!9Sp;@un7NJ^84=Y?**{@ z@FOzmCqq~#$;!Yi3JvO<_>b=H?!PjwR-BNplYddm6>2br&onXB=QdKO$R%~5I7Rb+ z{2xmBJLBexciln0qn1BY!=usXLmdm2xWg1sxZwYmQodx7XGHGRhrXefFX*e$7#=I> zcLuof!X72^2BkbfBey8k{e@Zv5wch@gk^cyM_m|)+AvSBdg-fwe;RKpJRG20`ud-b ztI`idA6=IEhQw8etSZ4!LH2n&yuOervDSbNZ)A@9Lk;4bM~zx#s(K0%6;nASv`A%{lCl(;i}$X*|Ar* zsC?lQ<&~MN58;w!{Dsv{Zg$k%7{)uflqJo4&<-G+ON6y3Ck7&_E6N`yq4kP)67^?R zRG?%GY>Jejh{mmm#fav+O~ENbH10T-+Y#%dr@&tmUZipbrwGxw^sC=Aog=-Nbf<}G zD&2);DL6%l#wBA1Gr#;Fj#6Btu$jdTn=CwSCI(tmuIL^t?7teL?!vs{mTqzY6eh*s zrn0enm^dTLRzw_ literal 0 HcmV?d00001 diff --git a/examples/stm32h7/src/bin/ltdc.rs b/examples/stm32h7/src/bin/ltdc.rs new file mode 100644 index 000000000..3bd307012 --- /dev/null +++ b/examples/stm32h7/src/bin/ltdc.rs @@ -0,0 +1,484 @@ +#![no_std] +#![no_main] +#![macro_use] +#![allow(static_mut_refs)] + +/// This example demonstrates the LTDC lcd display peripheral and was tested to run on an stm32h735g-dk (embassy-stm32 feature "stm32h735ig" and probe-rs chip "STM32H735IGKx") +/// Even though the dev kit has 16MB of attached PSRAM this example uses the 320KB of internal AXIS RAM found on the mcu itself to make the example more standalone and portable. +/// For this reason a 256 color lookup table had to be used to keep the memory requirement down to an acceptable level. +/// The example bounces a ferris crab bitmap around the screen while blinking an led on another task +/// +use bouncy_box::BouncyBox; +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::{ + bind_interrupts, + gpio::{Level, Output, Speed}, + ltdc::{self, Ltdc, LtdcConfiguration, LtdcLayer, LtdcLayerConfig, PolarityActive, PolarityEdge}, + peripherals, +}; +use embassy_time::{Duration, Timer}; +use embedded_graphics::{ + draw_target::DrawTarget, + geometry::{OriginDimensions, Point, Size}, + image::Image, + pixelcolor::{raw::RawU24, Rgb888}, + prelude::*, + primitives::Rectangle, + Pixel, +}; +use heapless::{Entry, FnvIndexMap}; +use tinybmp::Bmp; +use {defmt_rtt as _, panic_probe as _}; + +const DISPLAY_WIDTH: usize = 480; +const DISPLAY_HEIGHT: usize = 272; +const MY_TASK_POOL_SIZE: usize = 2; + +// the following two display buffers consume 261120 bytes that just about fits into axis ram found on the mcu +// see memory.x linker script +pub static mut FB1: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT]; +pub static mut FB2: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT]; + +// a basic memory.x linker script for the stm32h735g-dk is as follows: +/* +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 1024K + RAM : ORIGIN = 0x24000000, LENGTH = 320K +} +*/ + +bind_interrupts!(struct Irqs { + LTDC => ltdc::InterruptHandler; +}); + +const NUM_COLORS: usize = 256; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = rcc_setup::stm32h735g_init(); + + // blink the led on another task + let led = Output::new(p.PC3, Level::High, Speed::Low); + unwrap!(spawner.spawn(led_task(led))); + + // numbers from STMicroelectronics/STM32CubeH7 STM32H735G-DK C-based example + const RK043FN48H_HSYNC: u16 = 41; // Horizontal synchronization + const RK043FN48H_HBP: u16 = 13; // Horizontal back porch + const RK043FN48H_HFP: u16 = 32; // Horizontal front porch + const RK043FN48H_VSYNC: u16 = 10; // Vertical synchronization + const RK043FN48H_VBP: u16 = 2; // Vertical back porch + const RK043FN48H_VFP: u16 = 2; // Vertical front porch + + let ltdc_config = LtdcConfiguration { + active_width: DISPLAY_WIDTH as _, + active_height: DISPLAY_HEIGHT as _, + h_back_porch: RK043FN48H_HBP - 11, // -11 from MX_LTDC_Init + h_front_porch: RK043FN48H_HFP, + v_back_porch: RK043FN48H_VBP, + v_front_porch: RK043FN48H_VFP, + h_sync: RK043FN48H_HSYNC, + v_sync: RK043FN48H_VSYNC, + h_sync_polarity: PolarityActive::ActiveLow, + v_sync_polarity: PolarityActive::ActiveLow, + data_enable_polarity: PolarityActive::ActiveHigh, + pixel_clock_polarity: PolarityEdge::FallingEdge, + }; + + info!("init ltdc"); + let mut ltdc = Ltdc::new( + p.LTDC, Irqs, p.PG7, p.PC6, p.PA4, p.PG14, p.PD0, p.PD6, p.PA8, p.PE12, p.PA3, p.PB8, p.PB9, p.PB1, p.PB0, + p.PA6, p.PE11, p.PH15, p.PH4, p.PC7, p.PD3, p.PE0, p.PH3, p.PH8, p.PH9, p.PH10, p.PH11, p.PE1, p.PE15, + ); + ltdc.init(<dc_config); + + // we only need to draw on one layer for this example (not to be confused with the double buffer) + info!("enable bottom layer"); + let layer_config = LtdcLayerConfig { + pixel_format: ltdc::PixelFormat::L8, // 1 byte per pixel + layer: LtdcLayer::Layer1, + window_x0: 0, + window_x1: DISPLAY_WIDTH as _, + window_y0: 0, + window_y1: DISPLAY_HEIGHT as _, + }; + + let ferris_bmp: Bmp = Bmp::from_slice(include_bytes!("./ferris.bmp")).unwrap(); + let color_map = build_color_lookup_map(&ferris_bmp); + let clut = build_clut(&color_map); + + // enable the bottom layer with a 256 color lookup table + ltdc.enable_layer(&layer_config, Some(&clut)); + + // Safety: the DoubleBuffer controls access to the statically allocated frame buffers + // and it is the only thing that mutates their content + let mut double_buffer = DoubleBuffer::new( + unsafe { FB1.as_mut() }, + unsafe { FB2.as_mut() }, + layer_config, + color_map, + ); + + // this allows us to perform some simple animation for every frame + let mut bouncy_box = BouncyBox::new( + ferris_bmp.bounding_box(), + Rectangle::new(Point::zero(), Size::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32)), + 2, + ); + + loop { + // cpu intensive drawing to the buffer that is NOT currently being copied to the LCD screen + double_buffer.clear(); + let position = bouncy_box.next_point(); + let ferris = Image::new(&ferris_bmp, position); + unwrap!(ferris.draw(&mut double_buffer)); + + // perform async dma data transfer to the lcd screen + unwrap!(double_buffer.swap(&mut ltdc).await); + } +} + +/// builds the color look-up table from all unique colors found in the bitmap. This should be a 256 color indexed bitmap to work. +fn build_color_lookup_map(bmp: &Bmp) -> FnvIndexMap { + let mut color_map: FnvIndexMap = heapless::FnvIndexMap::new(); + let mut counter: u8 = 0; + + // add black to position 0 + color_map.insert(Rgb888::new(0, 0, 0).into_storage(), counter).unwrap(); + counter += 1; + + for Pixel(_point, color) in bmp.pixels() { + let raw = color.into_storage(); + if let Entry::Vacant(v) = color_map.entry(raw) { + v.insert(counter).expect("more than 256 colors detected"); + counter += 1; + } + } + color_map +} + +/// builds the color look-up table from the color map provided +fn build_clut(color_map: &FnvIndexMap) -> [ltdc::RgbColor; NUM_COLORS] { + let mut clut = [ltdc::RgbColor::default(); NUM_COLORS]; + for (color, index) in color_map.iter() { + let color = Rgb888::from(RawU24::new(*color)); + clut[*index as usize] = ltdc::RgbColor { + red: color.r(), + green: color.g(), + blue: color.b(), + }; + } + + clut +} + +#[embassy_executor::task(pool_size = MY_TASK_POOL_SIZE)] +async fn led_task(mut led: Output<'static>) { + let mut counter = 0; + loop { + info!("blink: {}", counter); + counter += 1; + + // on + led.set_low(); + Timer::after(Duration::from_millis(50)).await; + + // off + led.set_high(); + Timer::after(Duration::from_millis(450)).await; + } +} + +pub type TargetPixelType = u8; + +// A simple double buffer +pub struct DoubleBuffer { + buf0: &'static mut [TargetPixelType], + buf1: &'static mut [TargetPixelType], + is_buf0: bool, + layer_config: LtdcLayerConfig, + color_map: FnvIndexMap, +} + +impl DoubleBuffer { + pub fn new( + buf0: &'static mut [TargetPixelType], + buf1: &'static mut [TargetPixelType], + layer_config: LtdcLayerConfig, + color_map: FnvIndexMap, + ) -> Self { + Self { + buf0, + buf1, + is_buf0: true, + layer_config, + color_map, + } + } + + pub fn current(&mut self) -> (&FnvIndexMap, &mut [TargetPixelType]) { + if self.is_buf0 { + (&self.color_map, self.buf0) + } else { + (&self.color_map, self.buf1) + } + } + + pub async fn swap(&mut self, ltdc: &mut Ltdc<'_, T>) -> Result<(), ltdc::Error> { + let (_, buf) = self.current(); + let frame_buffer = buf.as_ptr(); + self.is_buf0 = !self.is_buf0; + ltdc.set_buffer(self.layer_config.layer, frame_buffer as *const _).await + } + + /// Clears the buffer + pub fn clear(&mut self) { + let (color_map, buf) = self.current(); + let black = Rgb888::new(0, 0, 0).into_storage(); + let color_index = color_map.get(&black).expect("no black found in the color map"); + + for a in buf.iter_mut() { + *a = *color_index; // solid black + } + } +} + +// Implement DrawTarget for +impl DrawTarget for DoubleBuffer { + type Color = Rgb888; + type Error = (); + + /// Draw a pixel + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + let size = self.size(); + let width = size.width as i32; + let height = size.height as i32; + let (color_map, buf) = self.current(); + + for pixel in pixels { + let Pixel(point, color) = pixel; + + if point.x >= 0 && point.y >= 0 && point.x < width && point.y < height { + let index = point.y * width + point.x; + let raw_color = color.into_storage(); + + match color_map.get(&raw_color) { + Some(x) => { + buf[index as usize] = *x; + } + None => panic!("color not found in color map: {}", raw_color), + }; + } else { + // Ignore invalid points + } + } + + Ok(()) + } +} + +impl OriginDimensions for DoubleBuffer { + /// Return the size of the display + fn size(&self) -> Size { + Size::new( + (self.layer_config.window_x1 - self.layer_config.window_x0) as _, + (self.layer_config.window_y1 - self.layer_config.window_y0) as _, + ) + } +} + +mod rcc_setup { + + use embassy_stm32::{rcc::*, Peripherals}; + use embassy_stm32::{ + rcc::{Hse, HseMode}, + time::Hertz, + Config, + }; + + /// Sets up clocks for the stm32h735g mcu + /// change this if you plan to use a different microcontroller + pub fn stm32h735g_init() -> Peripherals { + /* + https://github.com/STMicroelectronics/STM32CubeH7/blob/master/Projects/STM32H735G-DK/Examples/GPIO/GPIO_EXTI/Src/main.c + @brief System Clock Configuration + The system Clock is configured as follow : + System Clock source = PLL (HSE) + SYSCLK(Hz) = 520000000 (CPU Clock) + HCLK(Hz) = 260000000 (AXI and AHBs Clock) + AHB Prescaler = 2 + D1 APB3 Prescaler = 2 (APB3 Clock 130MHz) + D2 APB1 Prescaler = 2 (APB1 Clock 130MHz) + D2 APB2 Prescaler = 2 (APB2 Clock 130MHz) + D3 APB4 Prescaler = 2 (APB4 Clock 130MHz) + HSE Frequency(Hz) = 25000000 + PLL_M = 5 + PLL_N = 104 + PLL_P = 1 + PLL_Q = 4 + PLL_R = 2 + VDD(V) = 3.3 + Flash Latency(WS) = 3 + */ + + // setup power and clocks for an stm32h735g-dk run from an external 25 Mhz external oscillator + let mut config = Config::default(); + config.rcc.hse = Some(Hse { + freq: Hertz::mhz(25), + mode: HseMode::Oscillator, + }); + config.rcc.hsi = None; + config.rcc.csi = false; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV5, // PLL_M + mul: PllMul::MUL104, // PLL_N + divp: Some(PllDiv::DIV1), + divq: Some(PllDiv::DIV4), + divr: Some(PllDiv::DIV2), + }); + // numbers adapted from Drivers/BSP/STM32H735G-DK/stm32h735g_discovery_ospi.c + // MX_OSPI_ClockConfig + config.rcc.pll2 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV5, // PLL_M + mul: PllMul::MUL80, // PLL_N + divp: Some(PllDiv::DIV5), + divq: Some(PllDiv::DIV2), + divr: Some(PllDiv::DIV2), + }); + // numbers adapted from Drivers/BSP/STM32H735G-DK/stm32h735g_discovery_lcd.c + // MX_LTDC_ClockConfig + config.rcc.pll3 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV5, // PLL_M + mul: PllMul::MUL160, // PLL_N + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV2), + divr: Some(PllDiv::DIV83), + }); + config.rcc.voltage_scale = VoltageScale::Scale0; + config.rcc.supply_config = SupplyConfig::DirectSMPS; + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV2; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.apb3_pre = APBPrescaler::DIV2; + config.rcc.apb4_pre = APBPrescaler::DIV2; + let p = embassy_stm32::init(config); + p + } +} + +mod bouncy_box { + use embedded_graphics::{geometry::Point, primitives::Rectangle}; + + enum Direction { + DownLeft, + DownRight, + UpLeft, + UpRight, + } + + pub struct BouncyBox { + direction: Direction, + child_rect: Rectangle, + parent_rect: Rectangle, + current_point: Point, + move_by: usize, + } + + // This calculates the coordinates of a chile rectangle bounced around inside a parent bounded box + impl BouncyBox { + pub fn new(child_rect: Rectangle, parent_rect: Rectangle, move_by: usize) -> Self { + let center_box = parent_rect.center(); + let center_img = child_rect.center(); + let current_point = Point::new(center_box.x - center_img.x / 2, center_box.y - center_img.y / 2); + Self { + direction: Direction::DownRight, + child_rect, + parent_rect, + current_point, + move_by, + } + } + + pub fn next_point(&mut self) -> Point { + let direction = &self.direction; + let img_height = self.child_rect.size.height as i32; + let box_height = self.parent_rect.size.height as i32; + let img_width = self.child_rect.size.width as i32; + let box_width = self.parent_rect.size.width as i32; + let move_by = self.move_by as i32; + + match direction { + Direction::DownLeft => { + self.current_point.x -= move_by; + self.current_point.y += move_by; + + let x_out_of_bounds = self.current_point.x < 0; + let y_out_of_bounds = (self.current_point.y + img_height) > box_height; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpRight + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::DownRight + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpLeft + } + } + Direction::DownRight => { + self.current_point.x += move_by; + self.current_point.y += move_by; + + let x_out_of_bounds = (self.current_point.x + img_width) > box_width; + let y_out_of_bounds = (self.current_point.y + img_height) > box_height; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpLeft + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::DownLeft + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpRight + } + } + Direction::UpLeft => { + self.current_point.x -= move_by; + self.current_point.y -= move_by; + + let x_out_of_bounds = self.current_point.x < 0; + let y_out_of_bounds = self.current_point.y < 0; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownRight + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::UpRight + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownLeft + } + } + Direction::UpRight => { + self.current_point.x += move_by; + self.current_point.y -= move_by; + + let x_out_of_bounds = (self.current_point.x + img_width) > box_width; + let y_out_of_bounds = self.current_point.y < 0; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownLeft + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::UpLeft + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownRight + } + } + } + + self.current_point + } + } +} From 47c7bb2bb539712de7534a7f58ac9685e9894645 Mon Sep 17 00:00:00 2001 From: David Haig Date: Fri, 28 Jun 2024 11:35:38 +0100 Subject: [PATCH 13/57] Updated metapac dependency to latest for LTDC support --- embassy-stm32/Cargo.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 7a81b705d..d0e7ffd6d 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -72,8 +72,7 @@ rand_core = "0.6.3" sdio-host = "0.5.0" critical-section = "1.1" #stm32-metapac = { version = "15" } -#stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-a8ab0a3421ed0ca4b282f54028a0a2decacd8631" } -stm32-metapac = { git = "https://github.com/ninjasource/stm32-data-generated", branch = "stm32-ltdc", commit = "4c902fcd0889619e8af8bc03fa13b45c56fb3540" } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-cb8af7b3c2cfb88283e0ce979a318657853434c0" } vcell = "0.1.3" nb = "1.0.0" @@ -98,7 +97,7 @@ proc-macro2 = "1.0.36" quote = "1.0.15" #stm32-metapac = { version = "15", default-features = false, features = ["metadata"]} -stm32-metapac = { git = "https://github.com/ninjasource/stm32-data-generated", branch = "stm32-ltdc", commit = "4c902fcd0889619e8af8bc03fa13b45c56fb3540", default-features = false, features = ["metadata"] } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-cb8af7b3c2cfb88283e0ce979a318657853434c0", default-features = false, features = ["metadata"] } [features] default = ["rt"] From ece6203a996207f25db737032d3e0dae3df80b64 Mon Sep 17 00:00:00 2001 From: shufps Date: Fri, 28 Jun 2024 14:42:19 +0200 Subject: [PATCH 14/57] added DDS example --- examples/stm32l0/Cargo.toml | 2 +- examples/stm32l0/src/bin/dds.rs | 117 ++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 examples/stm32l0/src/bin/dds.rs diff --git a/examples/stm32l0/Cargo.toml b/examples/stm32l0/Cargo.toml index 2c599e7a3..5b0519ac4 100644 --- a/examples/stm32l0/Cargo.toml +++ b/examples/stm32l0/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32l072cz to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "time-driver-any", "exti", "memory-x"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "unstable-pac", "time-driver-any", "exti", "memory-x"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } diff --git a/examples/stm32l0/src/bin/dds.rs b/examples/stm32l0/src/bin/dds.rs new file mode 100644 index 000000000..9759fc8f3 --- /dev/null +++ b/examples/stm32l0/src/bin/dds.rs @@ -0,0 +1,117 @@ +#![no_std] +#![no_main] + +use core::option::Option::Some; +use defmt::info; +use defmt_rtt as _; // global logger +use embassy_executor::Spawner; +use embassy_stm32::gpio::OutputType; +use embassy_stm32::interrupt; +use embassy_stm32::pac; +use embassy_stm32::rcc::*; +use embassy_stm32::time::hz; +use embassy_stm32::timer::low_level::Timer as LLTimer; +use embassy_stm32::timer::low_level::*; +use embassy_stm32::timer::simple_pwm::PwmPin; +use embassy_stm32::timer::Channel; +use embassy_stm32::Config; +use panic_probe as _; + +const DDS_SINE_DATA: [u8; 256] = [ + 0x80, 0x83, 0x86, 0x89, 0x8c, 0x8f, 0x92, 0x95, 0x98, 0x9c, 0x9f, 0xa2, 0xa5, 0xa8, 0xab, 0xae, 0xb0, 0xb3, 0xb6, + 0xb9, 0xbc, 0xbf, 0xc1, 0xc4, 0xc7, 0xc9, 0xcc, 0xce, 0xd1, 0xd3, 0xd5, 0xd8, 0xda, 0xdc, 0xde, 0xe0, 0xe2, 0xe4, + 0xe6, 0xe8, 0xea, 0xec, 0xed, 0xef, 0xf0, 0xf2, 0xf3, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfc, 0xfd, + 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfc, 0xfc, 0xfb, + 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf3, 0xf2, 0xf0, 0xef, 0xed, 0xec, 0xea, 0xe8, 0xe6, 0xe4, 0xe2, 0xe0, 0xde, + 0xdc, 0xda, 0xd8, 0xd5, 0xd3, 0xd1, 0xce, 0xcc, 0xc9, 0xc7, 0xc4, 0xc1, 0xbf, 0xbc, 0xb9, 0xb6, 0xb3, 0xb0, 0xae, + 0xab, 0xa8, 0xa5, 0xa2, 0x9f, 0x9c, 0x98, 0x95, 0x92, 0x8f, 0x8c, 0x89, 0x86, 0x83, 0x80, 0x7c, 0x79, 0x76, 0x73, + 0x70, 0x6d, 0x6a, 0x67, 0x63, 0x60, 0x5d, 0x5a, 0x57, 0x54, 0x51, 0x4f, 0x4c, 0x49, 0x46, 0x43, 0x40, 0x3e, 0x3b, + 0x38, 0x36, 0x33, 0x31, 0x2e, 0x2c, 0x2a, 0x27, 0x25, 0x23, 0x21, 0x1f, 0x1d, 0x1b, 0x19, 0x17, 0x15, 0x13, 0x12, + 0x10, 0x0f, 0x0d, 0x0c, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0c, 0x0d, 0x0f, 0x10, 0x12, 0x13, 0x15, 0x17, 0x19, 0x1b, 0x1d, 0x1f, 0x21, 0x23, 0x25, 0x27, 0x2a, 0x2c, + 0x2e, 0x31, 0x33, 0x36, 0x38, 0x3b, 0x3e, 0x40, 0x43, 0x46, 0x49, 0x4c, 0x4f, 0x51, 0x54, 0x57, 0x5a, 0x5d, 0x60, + 0x63, 0x67, 0x6a, 0x6d, 0x70, 0x73, 0x76, 0x79, 0x7c, +]; + +// frequency: 15625/(256/(DDS_INCR/2**24)) = 999,99999Hz +static mut DDS_INCR: u32 = 0x10624DD2; + +// fractional phase accumulator +static mut DDS_AKKU: u32 = 0x00000000; + +#[interrupt] +fn TIM2() { + unsafe { + // get next value of DDS + DDS_AKKU = DDS_AKKU.wrapping_add(DDS_INCR); + let value = (DDS_SINE_DATA[(DDS_AKKU >> 24) as usize] as u16) << 3; + + // set new output compare value + pac::TIM2.ccr(2).modify(|w| w.set_ccr(value)); + + // reset interrupt flag + pac::TIM2.sr().modify(|r| r.set_uif(false)); + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + // configure for 32MHz (HSI16 * 6 / 3) + let mut config = Config::default(); + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + div: PllDiv::DIV3, + mul: PllMul::MUL6, + }); + + let p = embassy_stm32::init(config); + + // setup PWM pin in AF mode + let _ch3 = PwmPin::new_ch3(p.PA2, OutputType::PushPull); + + // initialize timer + let timer = LLTimer::new(p.TIM2); + + // set counting mode + timer.set_counting_mode(CountingMode::EdgeAlignedUp); + + // set pwm sample frequency + timer.set_frequency(hz(15625)); + + // enable outputs + timer.enable_outputs(); + + // start timer + timer.start(); + + // set output compare mode + timer.set_output_compare_mode(Channel::Ch3, OutputCompareMode::PwmMode1); + + // set output compare preload + timer.set_output_compare_preload(Channel::Ch3, true); + + // set output polarity + timer.set_output_polarity(Channel::Ch3, OutputPolarity::ActiveHigh); + + // set compare value + timer.set_compare_value(Channel::Ch3, timer.get_max_compare_value() / 2); + + // enable pwm channel + timer.enable_channel(Channel::Ch3, true); + + // enable timer interrupts + timer.enable_update_interrupt(true); + unsafe { cortex_m::peripheral::NVIC::unmask(interrupt::TIM2) }; + + async { + loop { + embassy_time::Timer::after_millis(5000).await; + } + } + .await; +} From b2a0eb3cb4b9d0f9b4ecc12b6e5437e4616b4aa6 Mon Sep 17 00:00:00 2001 From: shufps Date: Fri, 28 Jun 2024 14:43:39 +0200 Subject: [PATCH 15/57] added comment --- examples/stm32l0/src/bin/dds.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/stm32l0/src/bin/dds.rs b/examples/stm32l0/src/bin/dds.rs index 9759fc8f3..21647a68d 100644 --- a/examples/stm32l0/src/bin/dds.rs +++ b/examples/stm32l0/src/bin/dds.rs @@ -75,6 +75,7 @@ async fn main(_spawner: Spawner) { let _ch3 = PwmPin::new_ch3(p.PA2, OutputType::PushPull); // initialize timer + // we cannot use SimplePWM here because the Time is privately encapsulated let timer = LLTimer::new(p.TIM2); // set counting mode From 2462a22140834e80e63465dabcf8a783e1adb94f Mon Sep 17 00:00:00 2001 From: shufps Date: Fri, 28 Jun 2024 14:52:21 +0200 Subject: [PATCH 16/57] format --- examples/stm32l0/src/bin/dds.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/examples/stm32l0/src/bin/dds.rs b/examples/stm32l0/src/bin/dds.rs index 21647a68d..a54b28a93 100644 --- a/examples/stm32l0/src/bin/dds.rs +++ b/examples/stm32l0/src/bin/dds.rs @@ -2,19 +2,17 @@ #![no_main] use core::option::Option::Some; + use defmt::info; use defmt_rtt as _; // global logger use embassy_executor::Spawner; use embassy_stm32::gpio::OutputType; -use embassy_stm32::interrupt; -use embassy_stm32::pac; use embassy_stm32::rcc::*; use embassy_stm32::time::hz; -use embassy_stm32::timer::low_level::Timer as LLTimer; -use embassy_stm32::timer::low_level::*; +use embassy_stm32::timer::low_level::{Timer as LLTimer, *}; use embassy_stm32::timer::simple_pwm::PwmPin; use embassy_stm32::timer::Channel; -use embassy_stm32::Config; +use embassy_stm32::{interrupt, pac, Config}; use panic_probe as _; const DDS_SINE_DATA: [u8; 256] = [ From 1123e3fd41e7b23dd0507816f1ff67fc0de6b5d1 Mon Sep 17 00:00:00 2001 From: David Haig Date: Fri, 28 Jun 2024 15:12:17 +0100 Subject: [PATCH 17/57] Get dsi_bsp example to compile again --- embassy-stm32/src/ltdc.rs | 224 ++++++++++++++++++------------- examples/stm32h7/src/bin/ltdc.rs | 4 +- 2 files changed, 131 insertions(+), 97 deletions(-) diff --git a/embassy-stm32/src/ltdc.rs b/embassy-stm32/src/ltdc.rs index adc9b8862..64558ee52 100644 --- a/embassy-stm32/src/ltdc.rs +++ b/embassy-stm32/src/ltdc.rs @@ -175,8 +175,31 @@ impl interrupt::typelevel::Handler for InterruptHandl } impl<'d, T: Instance> Ltdc<'d, T> { + // Create a new LTDC driver without specifying color and control pins. This is typically used if you want to drive a display though a DsiHost + /// Note: Full-Duplex modes are not supported at this time + pub fn new(peri: impl Peripheral

+ 'd) -> Self { + critical_section::with(|_cs| { + // RM says the pllsaidivr should only be changed when pllsai is off. But this could have other unintended side effects. So let's just give it a try like this. + // According to the debugger, this bit gets set, anyway. + #[cfg(stm32f7)] + stm32_metapac::RCC + .dckcfgr1() + .modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2)); + + // It is set to RCC_PLLSAIDIVR_2 in ST's BSP example for the STM32469I-DISCO. + #[cfg(not(any(stm32f7, stm32u5)))] + stm32_metapac::RCC + .dckcfgr() + .modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2)); + }); + + rcc::enable_and_reset::(); + into_ref!(peri); + Self { _peri: peri } + } + /// Create a new LTDC driver. 8 pins per color channel for blue, green and red - pub fn new( + pub fn new_with_pins( peri: impl Peripheral

+ 'd, _irq: impl interrupt::typelevel::Binding> + 'd, clk: impl Peripheral

> + 'd, @@ -241,93 +264,7 @@ impl<'d, T: Instance> Ltdc<'d, T> { Self { _peri: peri } } - fn clear_interrupt_flags() { - T::regs().icr().write(|w| { - w.set_cfuif(Cfuif::CLEAR); - w.set_clif(Clif::CLEAR); - w.set_crrif(Crrif::CLEAR); - w.set_cterrif(Cterrif::CLEAR); - }); - } - - fn enable_interrupts(enable: bool) { - T::regs().ier().write(|w| { - w.set_fuie(enable); - w.set_lie(false); // we are not interested in the line interrupt enable event - w.set_rrie(enable); - w.set_terrie(enable) - }); - - // enable interrupts for LTDC peripheral - T::Interrupt::unpend(); - if enable { - unsafe { T::Interrupt::enable() }; - } else { - T::Interrupt::disable() - } - } - - /// Set the current buffer. The async function will return when buffer has been completely copied to the LCD screen - /// frame_buffer_addr is a pointer to memory that should not move (best to make it static) - pub async fn set_buffer(&mut self, layer: LtdcLayer, frame_buffer_addr: *const ()) -> Result<(), Error> { - let mut bits = T::regs().isr().read(); - - // if all clear - if !bits.fuif() && !bits.lif() && !bits.rrif() && !bits.terrif() { - // wait for interrupt - poll_fn(|cx| { - // quick check to avoid registration if already done. - let bits = T::regs().isr().read(); - if bits.fuif() || bits.lif() || bits.rrif() || bits.terrif() { - return Poll::Ready(()); - } - - LTDC_WAKER.register(cx.waker()); - Self::clear_interrupt_flags(); // don't poison the request with old flags - Self::enable_interrupts(true); - - // set the new frame buffer address - let layer = T::regs().layer(layer as usize); - layer.cfbar().modify(|w| w.set_cfbadd(frame_buffer_addr as u32)); - - // configure a shadow reload for the next blanking period - T::regs().srcr().write(|w| { - w.set_vbr(Vbr::RELOAD); - }); - - // need to check condition after register to avoid a race - // condition that would result in lost notifications. - let bits = T::regs().isr().read(); - if bits.fuif() || bits.lif() || bits.rrif() || bits.terrif() { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; - - // re-read the status register after wait. - bits = T::regs().isr().read(); - } - - let result = if bits.fuif() { - Err(Error::FifoUnderrun) - } else if bits.terrif() { - Err(Error::TransferError) - } else if bits.lif() { - panic!("line interrupt event is disabled") - } else if bits.rrif() { - // register reload flag is expected - Ok(()) - } else { - unreachable!("all interrupt status values checked") - }; - - Self::clear_interrupt_flags(); - result - } - - /// Initialize the display + /// Initialise and enable the display pub fn init(&mut self, config: &LtdcConfiguration) { use stm32_metapac::ltdc::vals::{Depol, Hspol, Pcpol, Vspol}; let ltdc = T::regs(); @@ -393,16 +330,27 @@ impl<'d, T: Instance> Ltdc<'d, T> { w.set_bcblue(0); }); - // enable LTDC by setting LTDCEN bit - ltdc.gcr().modify(|w| { - w.set_ltdcen(true); - }); + self.enable(); } - /// Enable the layer + /// Set the enable bit in the control register and assert that it has been enabled /// - /// clut - color look-up table applies to L8, AL44 and AL88 pixel format and will default to greyscale if None supplied and these pixel formats are used - pub fn enable_layer(&mut self, layer_config: &LtdcLayerConfig, clut: Option<&[RgbColor]>) { + /// This does need to be called if init has already been called + pub fn enable(&mut self) { + T::regs().gcr().modify(|w| w.set_ltdcen(true)); + assert!(T::regs().gcr().read().ltdcen()) + } + + /// Unset the enable bit in the control register and assert that it has been disabled + pub fn disable(&mut self) { + T::regs().gcr().modify(|w| w.set_ltdcen(false)); + assert!(!T::regs().gcr().read().ltdcen()) + } + + /// Initialise and enable the layer + /// + /// clut - a 256 length color look-up table applies to L8, AL44 and AL88 pixel format and will default to greyscale if `None` supplied and these pixel formats are used + pub fn init_layer(&mut self, layer_config: &LtdcLayerConfig, clut: Option<&[RgbColor]>) { let ltdc = T::regs(); let layer = ltdc.layer(layer_config.layer as usize); @@ -475,6 +423,92 @@ impl<'d, T: Instance> Ltdc<'d, T> { w.set_len(true); }); } + + /// Set the current buffer. The async function will return when buffer has been completely copied to the LCD screen + /// frame_buffer_addr is a pointer to memory that should not move (best to make it static) + pub async fn set_buffer(&mut self, layer: LtdcLayer, frame_buffer_addr: *const ()) -> Result<(), Error> { + let mut bits = T::regs().isr().read(); + + // if all clear + if !bits.fuif() && !bits.lif() && !bits.rrif() && !bits.terrif() { + // wait for interrupt + poll_fn(|cx| { + // quick check to avoid registration if already done. + let bits = T::regs().isr().read(); + if bits.fuif() || bits.lif() || bits.rrif() || bits.terrif() { + return Poll::Ready(()); + } + + LTDC_WAKER.register(cx.waker()); + Self::clear_interrupt_flags(); // don't poison the request with old flags + Self::enable_interrupts(true); + + // set the new frame buffer address + let layer = T::regs().layer(layer as usize); + layer.cfbar().modify(|w| w.set_cfbadd(frame_buffer_addr as u32)); + + // configure a shadow reload for the next blanking period + T::regs().srcr().write(|w| { + w.set_vbr(Vbr::RELOAD); + }); + + // need to check condition after register to avoid a race + // condition that would result in lost notifications. + let bits = T::regs().isr().read(); + if bits.fuif() || bits.lif() || bits.rrif() || bits.terrif() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + // re-read the status register after wait. + bits = T::regs().isr().read(); + } + + let result = if bits.fuif() { + Err(Error::FifoUnderrun) + } else if bits.terrif() { + Err(Error::TransferError) + } else if bits.lif() { + panic!("line interrupt event is disabled") + } else if bits.rrif() { + // register reload flag is expected + Ok(()) + } else { + unreachable!("all interrupt status values checked") + }; + + Self::clear_interrupt_flags(); + result + } + + fn clear_interrupt_flags() { + T::regs().icr().write(|w| { + w.set_cfuif(Cfuif::CLEAR); + w.set_clif(Clif::CLEAR); + w.set_crrif(Crrif::CLEAR); + w.set_cterrif(Cterrif::CLEAR); + }); + } + + fn enable_interrupts(enable: bool) { + T::regs().ier().write(|w| { + w.set_fuie(enable); + w.set_lie(false); // we are not interested in the line interrupt enable event + w.set_rrie(enable); + w.set_terrie(enable) + }); + + // enable interrupts for LTDC peripheral + T::Interrupt::unpend(); + if enable { + unsafe { T::Interrupt::enable() }; + } else { + T::Interrupt::disable() + } + } } impl<'d, T: Instance> Drop for Ltdc<'d, T> { diff --git a/examples/stm32h7/src/bin/ltdc.rs b/examples/stm32h7/src/bin/ltdc.rs index 3bd307012..3b56bbb6c 100644 --- a/examples/stm32h7/src/bin/ltdc.rs +++ b/examples/stm32h7/src/bin/ltdc.rs @@ -87,7 +87,7 @@ async fn main(spawner: Spawner) { }; info!("init ltdc"); - let mut ltdc = Ltdc::new( + let mut ltdc = Ltdc::new_with_pins( p.LTDC, Irqs, p.PG7, p.PC6, p.PA4, p.PG14, p.PD0, p.PD6, p.PA8, p.PE12, p.PA3, p.PB8, p.PB9, p.PB1, p.PB0, p.PA6, p.PE11, p.PH15, p.PH4, p.PC7, p.PD3, p.PE0, p.PH3, p.PH8, p.PH9, p.PH10, p.PH11, p.PE1, p.PE15, ); @@ -109,7 +109,7 @@ async fn main(spawner: Spawner) { let clut = build_clut(&color_map); // enable the bottom layer with a 256 color lookup table - ltdc.enable_layer(&layer_config, Some(&clut)); + ltdc.init_layer(&layer_config, Some(&clut)); // Safety: the DoubleBuffer controls access to the statically allocated frame buffers // and it is the only thing that mutates their content From 79f00e54cc4d13a28c7ccc9a13345bf2f3730f42 Mon Sep 17 00:00:00 2001 From: David Haig Date: Fri, 28 Jun 2024 18:11:34 +0100 Subject: [PATCH 18/57] Moved ltdc example to its own crate --- ci.sh | 1 + embassy-stm32/src/ltdc.rs | 40 ++++++------ examples/stm32h7/Cargo.toml | 2 - examples/stm32h735/.cargo/config.toml | 8 +++ examples/stm32h735/Cargo.toml | 61 ++++++++++++++++++ examples/stm32h735/build.rs | 35 ++++++++++ examples/stm32h735/memory.x | 5 ++ .../{stm32h7 => stm32h735}/src/bin/ferris.bmp | Bin .../{stm32h7 => stm32h735}/src/bin/ltdc.rs | 15 +---- 9 files changed, 134 insertions(+), 33 deletions(-) create mode 100644 examples/stm32h735/.cargo/config.toml create mode 100644 examples/stm32h735/Cargo.toml create mode 100644 examples/stm32h735/build.rs create mode 100644 examples/stm32h735/memory.x rename examples/{stm32h7 => stm32h735}/src/bin/ferris.bmp (100%) rename examples/{stm32h7 => stm32h735}/src/bin/ltdc.rs (98%) diff --git a/ci.sh b/ci.sh index 04877dcd3..8ac9e1ccd 100755 --- a/ci.sh +++ b/ci.sh @@ -201,6 +201,7 @@ cargo batch \ --- build --release --manifest-path examples/stm32g4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32g4 \ --- build --release --manifest-path examples/stm32h5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32h5 \ --- build --release --manifest-path examples/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7 \ + --- build --release --manifest-path examples/stm32h735/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h735 \ --- build --release --manifest-path examples/stm32h7rs/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7rs \ --- build --release --manifest-path examples/stm32l0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32l0 \ --- build --release --manifest-path examples/stm32l1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32l1 \ diff --git a/embassy-stm32/src/ltdc.rs b/embassy-stm32/src/ltdc.rs index 64558ee52..564236d6f 100644 --- a/embassy-stm32/src/ltdc.rs +++ b/embassy-stm32/src/ltdc.rs @@ -178,27 +178,13 @@ impl<'d, T: Instance> Ltdc<'d, T> { // Create a new LTDC driver without specifying color and control pins. This is typically used if you want to drive a display though a DsiHost /// Note: Full-Duplex modes are not supported at this time pub fn new(peri: impl Peripheral

+ 'd) -> Self { - critical_section::with(|_cs| { - // RM says the pllsaidivr should only be changed when pllsai is off. But this could have other unintended side effects. So let's just give it a try like this. - // According to the debugger, this bit gets set, anyway. - #[cfg(stm32f7)] - stm32_metapac::RCC - .dckcfgr1() - .modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2)); - - // It is set to RCC_PLLSAIDIVR_2 in ST's BSP example for the STM32469I-DISCO. - #[cfg(not(any(stm32f7, stm32u5)))] - stm32_metapac::RCC - .dckcfgr() - .modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2)); - }); - - rcc::enable_and_reset::(); + Self::setup_clocks(); into_ref!(peri); Self { _peri: peri } } /// Create a new LTDC driver. 8 pins per color channel for blue, green and red + #[allow(clippy::too_many_arguments)] pub fn new_with_pins( peri: impl Peripheral

+ 'd, _irq: impl interrupt::typelevel::Binding> + 'd, @@ -230,8 +216,7 @@ impl<'d, T: Instance> Ltdc<'d, T> { r6: impl Peripheral

> + 'd, r7: impl Peripheral

> + 'd, ) -> Self { - rcc::enable_and_reset::(); - + Self::setup_clocks(); into_ref!(peri); new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh)); new_pin!(hsync, AfType::output(OutputType::PushPull, Speed::VeryHigh)); @@ -484,6 +469,25 @@ impl<'d, T: Instance> Ltdc<'d, T> { result } + fn setup_clocks() { + critical_section::with(|_cs| { + // RM says the pllsaidivr should only be changed when pllsai is off. But this could have other unintended side effects. So let's just give it a try like this. + // According to the debugger, this bit gets set, anyway. + #[cfg(stm32f7)] + crate::pac::RCC + .dckcfgr1() + .modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2)); + + // It is set to RCC_PLLSAIDIVR_2 in ST's BSP example for the STM32469I-DISCO. + #[cfg(stm32f4)] + crate::pac::RCC + .dckcfgr() + .modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2)); + }); + + rcc::enable_and_reset::(); + } + fn clear_interrupt_flags() { T::regs().icr().write(|w| { w.set_cfuif(Cfuif::CLEAR); diff --git a/examples/stm32h7/Cargo.toml b/examples/stm32h7/Cargo.toml index 6f3007492..0584f3916 100644 --- a/examples/stm32h7/Cargo.toml +++ b/examples/stm32h7/Cargo.toml @@ -34,8 +34,6 @@ stm32-fmc = "0.3.0" embedded-storage = "0.3.1" static_cell = "2" chrono = { version = "^0.4", default-features = false } -embedded-graphics = { version = "0.8.1" } -tinybmp = { version = "0.5" } # cargo build/run [profile.dev] diff --git a/examples/stm32h735/.cargo/config.toml b/examples/stm32h735/.cargo/config.toml new file mode 100644 index 000000000..95536c6a8 --- /dev/null +++ b/examples/stm32h735/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip STM32H735IGKx' + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) + +[env] +DEFMT_LOG = "trace" diff --git a/examples/stm32h735/Cargo.toml b/examples/stm32h735/Cargo.toml new file mode 100644 index 000000000..fc21cc894 --- /dev/null +++ b/examples/stm32h735/Cargo.toml @@ -0,0 +1,61 @@ +[package] +edition = "2021" +name = "embassy-stm32h735-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h735ig", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +embedded-graphics = { version = "0.8.1" } +tinybmp = { version = "0.5" } + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/examples/stm32h735/build.rs b/examples/stm32h735/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/stm32h735/build.rs @@ -0,0 +1,35 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/stm32h735/memory.x b/examples/stm32h735/memory.x new file mode 100644 index 000000000..3a70d24d2 --- /dev/null +++ b/examples/stm32h735/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 1024K + RAM : ORIGIN = 0x24000000, LENGTH = 320K +} \ No newline at end of file diff --git a/examples/stm32h7/src/bin/ferris.bmp b/examples/stm32h735/src/bin/ferris.bmp similarity index 100% rename from examples/stm32h7/src/bin/ferris.bmp rename to examples/stm32h735/src/bin/ferris.bmp diff --git a/examples/stm32h7/src/bin/ltdc.rs b/examples/stm32h735/src/bin/ltdc.rs similarity index 98% rename from examples/stm32h7/src/bin/ltdc.rs rename to examples/stm32h735/src/bin/ltdc.rs index 3b56bbb6c..5c75a7db1 100644 --- a/examples/stm32h7/src/bin/ltdc.rs +++ b/examples/stm32h735/src/bin/ltdc.rs @@ -36,19 +36,9 @@ const DISPLAY_HEIGHT: usize = 272; const MY_TASK_POOL_SIZE: usize = 2; // the following two display buffers consume 261120 bytes that just about fits into axis ram found on the mcu -// see memory.x linker script pub static mut FB1: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT]; pub static mut FB2: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT]; -// a basic memory.x linker script for the stm32h735g-dk is as follows: -/* -MEMORY -{ - FLASH : ORIGIN = 0x08000000, LENGTH = 1024K - RAM : ORIGIN = 0x24000000, LENGTH = 320K -} -*/ - bind_interrupts!(struct Irqs { LTDC => ltdc::InterruptHandler; }); @@ -110,7 +100,7 @@ async fn main(spawner: Spawner) { // enable the bottom layer with a 256 color lookup table ltdc.init_layer(&layer_config, Some(&clut)); - + // Safety: the DoubleBuffer controls access to the statically allocated frame buffers // and it is the only thing that mutates their content let mut double_buffer = DoubleBuffer::new( @@ -369,8 +359,7 @@ mod rcc_setup { config.rcc.apb2_pre = APBPrescaler::DIV2; config.rcc.apb3_pre = APBPrescaler::DIV2; config.rcc.apb4_pre = APBPrescaler::DIV2; - let p = embassy_stm32::init(config); - p + embassy_stm32::init(config) } } From 6edf7b4688361e165c2ea9af03df9725a89a853e Mon Sep 17 00:00:00 2001 From: David Haig Date: Fri, 28 Jun 2024 18:17:17 +0100 Subject: [PATCH 19/57] Applied formatting --- embassy-stm32/src/ltdc.rs | 21 ++++++++------- examples/stm32h735/src/bin/ltdc.rs | 42 +++++++++++++----------------- 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/embassy-stm32/src/ltdc.rs b/embassy-stm32/src/ltdc.rs index 564236d6f..4c5239971 100644 --- a/embassy-stm32/src/ltdc.rs +++ b/embassy-stm32/src/ltdc.rs @@ -2,18 +2,19 @@ //! See ST application note AN4861: Introduction to LCD-TFT display controller (LTDC) on STM32 MCUs for high level details //! This module was tested against the stm32h735g-dk using the RM0468 ST reference manual for detailed register information -use crate::{ - gpio::{AfType, OutputType, Speed}, - interrupt::{self, typelevel::Interrupt}, - peripherals, rcc, Peripheral, -}; -use core::{future::poll_fn, marker::PhantomData, task::Poll}; +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + use embassy_hal_internal::{into_ref, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -use stm32_metapac::ltdc::{ - regs::Dccr, - vals::{Bf1, Bf2, Cfuif, Clif, Crrif, Cterrif, Pf, Vbr}, -}; +use stm32_metapac::ltdc::regs::Dccr; +use stm32_metapac::ltdc::vals::{Bf1, Bf2, Cfuif, Clif, Crrif, Cterrif, Pf, Vbr}; + +use crate::gpio::{AfType, OutputType, Speed}; +use crate::interrupt::typelevel::Interrupt; +use crate::interrupt::{self}; +use crate::{peripherals, rcc, Peripheral}; static LTDC_WAKER: AtomicWaker = AtomicWaker::new(); diff --git a/examples/stm32h735/src/bin/ltdc.rs b/examples/stm32h735/src/bin/ltdc.rs index 5c75a7db1..a36fdef2c 100644 --- a/examples/stm32h735/src/bin/ltdc.rs +++ b/examples/stm32h735/src/bin/ltdc.rs @@ -11,22 +11,18 @@ use bouncy_box::BouncyBox; use defmt::{info, unwrap}; use embassy_executor::Spawner; -use embassy_stm32::{ - bind_interrupts, - gpio::{Level, Output, Speed}, - ltdc::{self, Ltdc, LtdcConfiguration, LtdcLayer, LtdcLayerConfig, PolarityActive, PolarityEdge}, - peripherals, -}; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::ltdc::{self, Ltdc, LtdcConfiguration, LtdcLayer, LtdcLayerConfig, PolarityActive, PolarityEdge}; +use embassy_stm32::{bind_interrupts, peripherals}; use embassy_time::{Duration, Timer}; -use embedded_graphics::{ - draw_target::DrawTarget, - geometry::{OriginDimensions, Point, Size}, - image::Image, - pixelcolor::{raw::RawU24, Rgb888}, - prelude::*, - primitives::Rectangle, - Pixel, -}; +use embedded_graphics::draw_target::DrawTarget; +use embedded_graphics::geometry::{OriginDimensions, Point, Size}; +use embedded_graphics::image::Image; +use embedded_graphics::pixelcolor::raw::RawU24; +use embedded_graphics::pixelcolor::Rgb888; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::Rectangle; +use embedded_graphics::Pixel; use heapless::{Entry, FnvIndexMap}; use tinybmp::Bmp; use {defmt_rtt as _, panic_probe as _}; @@ -100,7 +96,7 @@ async fn main(spawner: Spawner) { // enable the bottom layer with a 256 color lookup table ltdc.init_layer(&layer_config, Some(&clut)); - + // Safety: the DoubleBuffer controls access to the statically allocated frame buffers // and it is the only thing that mutates their content let mut double_buffer = DoubleBuffer::new( @@ -283,12 +279,9 @@ impl OriginDimensions for DoubleBuffer { mod rcc_setup { - use embassy_stm32::{rcc::*, Peripherals}; - use embassy_stm32::{ - rcc::{Hse, HseMode}, - time::Hertz, - Config, - }; + use embassy_stm32::rcc::{Hse, HseMode, *}; + use embassy_stm32::time::Hertz; + use embassy_stm32::{Config, Peripherals}; /// Sets up clocks for the stm32h735g mcu /// change this if you plan to use a different microcontroller @@ -359,12 +352,13 @@ mod rcc_setup { config.rcc.apb2_pre = APBPrescaler::DIV2; config.rcc.apb3_pre = APBPrescaler::DIV2; config.rcc.apb4_pre = APBPrescaler::DIV2; - embassy_stm32::init(config) + embassy_stm32::init(config) } } mod bouncy_box { - use embedded_graphics::{geometry::Point, primitives::Rectangle}; + use embedded_graphics::geometry::Point; + use embedded_graphics::primitives::Rectangle; enum Direction { DownLeft, From 1d1fc9afeac268a4b6d2cd9b9e5788226c79eca8 Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Sun, 30 Jun 2024 11:03:29 +0200 Subject: [PATCH 20/57] Add async and blocking variants --- embassy-stm32/src/tsc/mod.rs | 203 +++++++++++++++++++++-------------- 1 file changed, 120 insertions(+), 83 deletions(-) diff --git a/embassy-stm32/src/tsc/mod.rs b/embassy-stm32/src/tsc/mod.rs index d5da2529f..cd7ae4a53 100644 --- a/embassy-stm32/src/tsc/mod.rs +++ b/embassy-stm32/src/tsc/mod.rs @@ -93,6 +93,23 @@ pub enum Error { Test, } +/// Async acquisition API marker +pub struct Async; +/// Blocking acquisition API marker +pub struct Blocking; + +trait SealedDriverKind {} + +impl SealedDriverKind for Async {} +impl SealedDriverKind for Blocking {} + +#[allow(private_bounds)] +/// Driver variant marker for the TSC peripheral +pub trait DriverKind: SealedDriverKind {} + +impl DriverKind for Async {} +impl DriverKind for Blocking {} + /// TSC interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, @@ -505,7 +522,7 @@ pub enum G7 {} pub enum G8 {} /// TSC driver -pub struct Tsc<'d, T: Instance> { +pub struct Tsc<'d, T: Instance, K: DriverKind> { _peri: PeripheralRef<'d, T>, _g1: Option>, _g2: Option>, @@ -519,13 +536,103 @@ pub struct Tsc<'d, T: Instance> { _g8: Option>, state: State, config: Config, + _kind: PhantomData, } -impl<'d, T: Instance> Tsc<'d, T> { - /// Create new TSC driver - pub fn new( +impl<'d, T: Instance> Tsc<'d, T, Async> { + /// Create a Tsc instance that can be awaited for completion + pub fn new_async( peri: impl Peripheral

+ 'd, + g1: Option>, + g2: Option>, + g3: Option>, + g4: Option>, + g5: Option>, + g6: Option>, + #[cfg(any(tsc_v2, tsc_v3))] g7: Option>, + #[cfg(tsc_v3)] g8: Option>, + config: Config, _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Self { + // Need to check valid pin configuration input + let g1 = g1.filter(|b| b.check_group().is_ok()); + let g2 = g2.filter(|b| b.check_group().is_ok()); + let g3 = g3.filter(|b| b.check_group().is_ok()); + let g4 = g4.filter(|b| b.check_group().is_ok()); + let g5 = g5.filter(|b| b.check_group().is_ok()); + let g6 = g6.filter(|b| b.check_group().is_ok()); + #[cfg(any(tsc_v2, tsc_v3))] + let g7 = g7.filter(|b| b.check_group().is_ok()); + #[cfg(tsc_v3)] + let g8 = g8.filter(|b| b.check_group().is_ok()); + + match Self::check_shields( + &g1, + &g2, + &g3, + &g4, + &g5, + &g6, + #[cfg(any(tsc_v2, tsc_v3))] + &g7, + #[cfg(tsc_v3)] + &g8, + ) { + Ok(()) => Self::new_inner( + peri, + g1, + g2, + g3, + g4, + g5, + g6, + #[cfg(any(tsc_v2, tsc_v3))] + g7, + #[cfg(tsc_v3)] + g8, + config, + ), + Err(_) => Self::new_inner( + peri, + None, + None, + None, + None, + None, + None, + #[cfg(any(tsc_v2, tsc_v3))] + None, + #[cfg(tsc_v3)] + None, + config, + ), + } + } + /// Asyncronously wait for the end of an acquisition + pub async fn pend_for_acquisition(&mut self) { + poll_fn(|cx| match self.get_state() { + State::Busy => { + T::waker().register(cx.waker()); + T::regs().ier().write(|w| w.set_eoaie(true)); + if self.get_state() != State::Busy { + T::regs().ier().write(|w| w.set_eoaie(false)); + return Poll::Ready(()); + } + Poll::Pending + } + _ => { + T::regs().ier().write(|w| w.set_eoaie(false)); + Poll::Ready(()) + } + }) + .await; + } +} + +impl<'d, T: Instance> Tsc<'d, T, Blocking> { + /// Create a Tsc instance that must be polled for completion + pub fn new_blocking( + peri: impl Peripheral

+ 'd, g1: Option>, g2: Option>, g3: Option>, @@ -590,7 +697,14 @@ impl<'d, T: Instance> Tsc<'d, T> { ), } } + /// Wait for end of acquisition + pub fn poll_for_acquisition(&mut self) { + while self.get_state() == State::Busy {} + } +} +impl<'d, T: Instance, K: DriverKind> Tsc<'d, T, K> { + /// Create new TSC driver fn check_shields( g1: &Option>, g2: &Option>, @@ -754,6 +868,7 @@ impl<'d, T: Instance> Tsc<'d, T> { _g8: g8, state: State::Ready, config, + _kind: PhantomData, } } @@ -784,33 +899,6 @@ impl<'d, T: Instance> Tsc<'d, T> { }); } - /// Start charge transfer acquisition with interrupts enabled - pub fn start_it(&mut self) { - self.state = State::Busy; - - // Enable interrupts - T::regs().ier().modify(|w| { - w.set_eoaie(true); - w.set_mceie(self.config.max_count_interrupt); - }); - - // Clear flags - T::regs().icr().modify(|w| { - w.set_eoaic(true); - w.set_mceic(true); - }); - - // Set the touch sensing IOs not acquired to the default mode - T::regs().cr().modify(|w| { - w.set_iodef(self.config.io_default_mode); - }); - - // Start the acquisition - T::regs().cr().modify(|w| { - w.set_start(true); - }); - } - /// Stop charge transfer acquisition pub fn stop(&mut self) { T::regs().cr().modify(|w| { @@ -831,57 +919,6 @@ impl<'d, T: Instance> Tsc<'d, T> { self.state = State::Ready; } - /// Stop charge transfer acquisition and clear interrupts - pub fn stop_it(&mut self) { - T::regs().cr().modify(|w| { - w.set_start(false); - }); - - // Set the touch sensing IOs in low power mode - T::regs().cr().modify(|w| { - w.set_iodef(false); - }); - - // Disable interrupts - T::regs().ier().modify(|w| { - w.set_eoaie(false); - w.set_mceie(false); - }); - - // Clear flags - T::regs().icr().modify(|w| { - w.set_eoaic(true); - w.set_mceic(true); - }); - - self.state = State::Ready; - } - - /// Wait for end of acquisition - pub fn poll_for_acquisition(&mut self) { - while self.get_state() == State::Busy {} - } - - /// Asyncronously wait for the end of an acquisition - pub async fn pend_for_acquisition(&mut self) { - poll_fn(|cx| match self.get_state() { - State::Busy => { - T::waker().register(cx.waker()); - T::regs().ier().write(|w| w.set_eoaie(true)); - if self.get_state() != State::Busy { - T::regs().ier().write(|w| w.set_eoaie(false)); - return Poll::Ready(()); - } - Poll::Pending - } - _ => { - T::regs().ier().write(|w| w.set_eoaie(false)); - Poll::Ready(()) - } - }) - .await; - } - /// Get current state of acquisition pub fn get_state(&mut self) -> State { if self.state == State::Busy { @@ -932,7 +969,7 @@ impl<'d, T: Instance> Tsc<'d, T> { } } -impl<'d, T: Instance> Drop for Tsc<'d, T> { +impl<'d, T: Instance, K: DriverKind> Drop for Tsc<'d, T, K> { fn drop(&mut self) { rcc::disable::(); } From 67f5b8d974c23854676cff3a6797cf4aaf41a402 Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Sun, 30 Jun 2024 11:06:32 +0200 Subject: [PATCH 21/57] Update example --- examples/stm32u5/src/bin/tsc.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/stm32u5/src/bin/tsc.rs b/examples/stm32u5/src/bin/tsc.rs index db85fb158..eb15d275a 100644 --- a/examples/stm32u5/src/bin/tsc.rs +++ b/examples/stm32u5/src/bin/tsc.rs @@ -50,9 +50,8 @@ async fn main(_spawner: embassy_executor::Spawner) { g7.set_io2(context.PE3, PinType::Sample); g7.set_io3(context.PE4, PinType::Channel); - let mut touch_controller = tsc::Tsc::new( + let mut touch_controller = tsc::Tsc::new_async( context.TSC, - Irqs, Some(g1), Some(g2), None, @@ -62,6 +61,7 @@ async fn main(_spawner: embassy_executor::Spawner) { Some(g7), None, config, + Irqs, ); touch_controller.discharge_io(true); From c4b88b57812da85b6952300509736fd02a4640fa Mon Sep 17 00:00:00 2001 From: Josh Junon Date: Sat, 29 Jun 2024 01:17:27 +0200 Subject: [PATCH 22/57] wiznet: add version check to initialization sequence --- embassy-net-wiznet/src/chip/mod.rs | 7 +++ embassy-net-wiznet/src/chip/w5100s.rs | 3 + embassy-net-wiznet/src/chip/w5500.rs | 3 + embassy-net-wiznet/src/device.rs | 62 ++++++++++++++++++- embassy-net-wiznet/src/lib.rs | 10 +-- .../rp/src/bin/ethernet_w5500_multisocket.rs | 3 +- .../rp/src/bin/ethernet_w5500_tcp_client.rs | 3 +- .../rp/src/bin/ethernet_w5500_tcp_server.rs | 3 +- examples/rp/src/bin/ethernet_w5500_udp.rs | 3 +- examples/stm32f4/src/bin/eth_w5500.rs | 4 +- tests/rp/src/bin/ethernet_w5100s_perf.rs | 3 +- 11 files changed, 93 insertions(+), 11 deletions(-) diff --git a/embassy-net-wiznet/src/chip/mod.rs b/embassy-net-wiznet/src/chip/mod.rs index e1f963d95..2e7a9ed6c 100644 --- a/embassy-net-wiznet/src/chip/mod.rs +++ b/embassy-net-wiznet/src/chip/mod.rs @@ -8,10 +8,17 @@ pub use w5100s::W5100S; pub(crate) trait SealedChip { type Address; + /// The version of the chip as reported by the VERSIONR register. + /// This is used to verify that the chip is supported by the driver, + /// and that SPI communication is working. + const CHIP_VERSION: u8; + const COMMON_MODE: Self::Address; const COMMON_MAC: Self::Address; const COMMON_SOCKET_INTR: Self::Address; const COMMON_PHY_CFG: Self::Address; + const COMMON_VERSION: Self::Address; + const SOCKET_MODE: Self::Address; const SOCKET_COMMAND: Self::Address; const SOCKET_RXBUF_SIZE: Self::Address; diff --git a/embassy-net-wiznet/src/chip/w5100s.rs b/embassy-net-wiznet/src/chip/w5100s.rs index 23ce3ed83..4c4b7ab16 100644 --- a/embassy-net-wiznet/src/chip/w5100s.rs +++ b/embassy-net-wiznet/src/chip/w5100s.rs @@ -11,10 +11,13 @@ impl super::Chip for W5100S {} impl super::SealedChip for W5100S { type Address = u16; + const CHIP_VERSION: u8 = 0x51; + const COMMON_MODE: Self::Address = 0x00; const COMMON_MAC: Self::Address = 0x09; const COMMON_SOCKET_INTR: Self::Address = 0x16; const COMMON_PHY_CFG: Self::Address = 0x3c; + const COMMON_VERSION: Self::Address = 0x80; const SOCKET_MODE: Self::Address = SOCKET_BASE + 0x00; const SOCKET_COMMAND: Self::Address = SOCKET_BASE + 0x01; diff --git a/embassy-net-wiznet/src/chip/w5500.rs b/embassy-net-wiznet/src/chip/w5500.rs index 12e610ea2..5cfcb94e4 100644 --- a/embassy-net-wiznet/src/chip/w5500.rs +++ b/embassy-net-wiznet/src/chip/w5500.rs @@ -15,10 +15,13 @@ impl super::Chip for W5500 {} impl super::SealedChip for W5500 { type Address = (RegisterBlock, u16); + const CHIP_VERSION: u8 = 0x04; + const COMMON_MODE: Self::Address = (RegisterBlock::Common, 0x00); const COMMON_MAC: Self::Address = (RegisterBlock::Common, 0x09); const COMMON_SOCKET_INTR: Self::Address = (RegisterBlock::Common, 0x18); const COMMON_PHY_CFG: Self::Address = (RegisterBlock::Common, 0x2E); + const COMMON_VERSION: Self::Address = (RegisterBlock::Common, 0x39); const SOCKET_MODE: Self::Address = (RegisterBlock::Socket0, 0x00); const SOCKET_COMMAND: Self::Address = (RegisterBlock::Socket0, 0x01); diff --git a/embassy-net-wiznet/src/device.rs b/embassy-net-wiznet/src/device.rs index 43f9512a3..d2b6bb0c3 100644 --- a/embassy-net-wiznet/src/device.rs +++ b/embassy-net-wiznet/src/device.rs @@ -24,9 +24,57 @@ pub(crate) struct WiznetDevice { _phantom: PhantomData, } +/// Error type when initializing a new Wiznet device +pub enum InitError { + /// Error occurred when sending or receiving SPI data + SpiError(SE), + /// The chip returned a version that isn't expected or supported + InvalidChipVersion { + /// The version that is supported + expected: u8, + /// The version that was returned by the chip + actual: u8, + }, +} + +impl From for InitError { + fn from(e: SE) -> Self { + InitError::SpiError(e) + } +} + +impl core::fmt::Debug for InitError +where + SE: core::fmt::Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + InitError::SpiError(e) => write!(f, "SpiError({:?})", e), + InitError::InvalidChipVersion { expected, actual } => { + write!(f, "InvalidChipVersion {{ expected: {}, actual: {} }}", expected, actual) + } + } + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for InitError +where + SE: defmt::Format, +{ + fn format(&self, f: defmt::Formatter) { + match self { + InitError::SpiError(e) => defmt::write!(f, "SpiError({})", e), + InitError::InvalidChipVersion { expected, actual } => { + defmt::write!(f, "InvalidChipVersion {{ expected: {}, actual: {} }}", expected, actual) + } + } + } +} + impl WiznetDevice { /// Create and initialize the driver - pub async fn new(spi: SPI, mac_addr: [u8; 6]) -> Result { + pub async fn new(spi: SPI, mac_addr: [u8; 6]) -> Result> { let mut this = Self { spi, _phantom: PhantomData, @@ -35,6 +83,18 @@ impl WiznetDevice { // Reset device this.bus_write(C::COMMON_MODE, &[0x80]).await?; + // Check the version of the chip + let mut version = [0]; + this.bus_read(C::COMMON_VERSION, &mut version).await?; + if version[0] != C::CHIP_VERSION { + #[cfg(feature = "defmt")] + defmt::error!("invalid chip version: {} (expected {})", version[0], C::CHIP_VERSION); + return Err(InitError::InvalidChipVersion { + actual: version[0], + expected: C::CHIP_VERSION, + }); + } + // Enable interrupt pin this.bus_write(C::COMMON_SOCKET_INTR, &[0x01]).await?; // Enable receive interrupt diff --git a/embassy-net-wiznet/src/lib.rs b/embassy-net-wiznet/src/lib.rs index 90102196a..3fbd4c741 100644 --- a/embassy-net-wiznet/src/lib.rs +++ b/embassy-net-wiznet/src/lib.rs @@ -15,6 +15,7 @@ use embedded_hal_async::digital::Wait; use embedded_hal_async::spi::SpiDevice; use crate::chip::Chip; +pub use crate::device::InitError; use crate::device::WiznetDevice; // If you change this update the docs of State @@ -105,7 +106,7 @@ pub async fn new<'a, const N_RX: usize, const N_TX: usize, C: Chip, SPI: SpiDevi spi_dev: SPI, int: INT, mut reset: RST, -) -> (Device<'a>, Runner<'a, C, SPI, INT, RST>) { +) -> Result<(Device<'a>, Runner<'a, C, SPI, INT, RST>), InitError> { // Reset the chip. reset.set_low().ok(); // Ensure the reset is registered. @@ -116,10 +117,11 @@ pub async fn new<'a, const N_RX: usize, const N_TX: usize, C: Chip, SPI: SpiDevi // Slowest is w5100s which is 100ms, so let's just wait that. Timer::after_millis(100).await; - let mac = WiznetDevice::new(spi_dev, mac_addr).await.unwrap(); + let mac = WiznetDevice::new(spi_dev, mac_addr).await?; let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ethernet(mac_addr)); - ( + + Ok(( device, Runner { ch: runner, @@ -127,5 +129,5 @@ pub async fn new<'a, const N_RX: usize, const N_TX: usize, C: Chip, SPI: SpiDevi int, _reset: reset, }, - ) + )) } diff --git a/examples/rp/src/bin/ethernet_w5500_multisocket.rs b/examples/rp/src/bin/ethernet_w5500_multisocket.rs index bd52cadca..def26b53d 100644 --- a/examples/rp/src/bin/ethernet_w5500_multisocket.rs +++ b/examples/rp/src/bin/ethernet_w5500_multisocket.rs @@ -63,7 +63,8 @@ async fn main(spawner: Spawner) { w5500_int, w5500_reset, ) - .await; + .await + .unwrap(); unwrap!(spawner.spawn(ethernet_task(runner))); // Generate random seed diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs index 3e4fbd2e6..6c4a78361 100644 --- a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs +++ b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs @@ -66,7 +66,8 @@ async fn main(spawner: Spawner) { w5500_int, w5500_reset, ) - .await; + .await + .unwrap(); unwrap!(spawner.spawn(ethernet_task(runner))); // Generate random seed diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs index 5532851f3..30a3a7463 100644 --- a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs +++ b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs @@ -65,7 +65,8 @@ async fn main(spawner: Spawner) { w5500_int, w5500_reset, ) - .await; + .await + .unwrap(); unwrap!(spawner.spawn(ethernet_task(runner))); // Generate random seed diff --git a/examples/rp/src/bin/ethernet_w5500_udp.rs b/examples/rp/src/bin/ethernet_w5500_udp.rs index adb1d8941..1613ed887 100644 --- a/examples/rp/src/bin/ethernet_w5500_udp.rs +++ b/examples/rp/src/bin/ethernet_w5500_udp.rs @@ -63,7 +63,8 @@ async fn main(spawner: Spawner) { w5500_int, w5500_reset, ) - .await; + .await + .unwrap(); unwrap!(spawner.spawn(ethernet_task(runner))); // Generate random seed diff --git a/examples/stm32f4/src/bin/eth_w5500.rs b/examples/stm32f4/src/bin/eth_w5500.rs index c51111110..3c770a873 100644 --- a/examples/stm32f4/src/bin/eth_w5500.rs +++ b/examples/stm32f4/src/bin/eth_w5500.rs @@ -80,7 +80,9 @@ async fn main(spawner: Spawner) -> ! { let mac_addr = [0x02, 234, 3, 4, 82, 231]; static STATE: StaticCell> = StaticCell::new(); let state = STATE.init(State::<2, 2>::new()); - let (device, runner) = embassy_net_wiznet::new(mac_addr, state, spi, w5500_int, w5500_reset).await; + let (device, runner) = embassy_net_wiznet::new(mac_addr, state, spi, w5500_int, w5500_reset) + .await + .unwrap(); unwrap!(spawner.spawn(ethernet_task(runner))); let config = embassy_net::Config::dhcpv4(Default::default()); diff --git a/tests/rp/src/bin/ethernet_w5100s_perf.rs b/tests/rp/src/bin/ethernet_w5100s_perf.rs index 5d5547773..4b04571bd 100644 --- a/tests/rp/src/bin/ethernet_w5100s_perf.rs +++ b/tests/rp/src/bin/ethernet_w5100s_perf.rs @@ -59,7 +59,8 @@ async fn main(spawner: Spawner) { w5500_int, w5500_reset, ) - .await; + .await + .unwrap(); unwrap!(spawner.spawn(ethernet_task(runner))); // Generate random seed From 27652798c72d16cbcf2245ae7d4bd4574001ef1f Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 1 Jul 2024 06:44:47 -0700 Subject: [PATCH 23/57] fix typo, add spaces after // --- embassy-stm32/src/adc/ringbuffered_v2.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/embassy-stm32/src/adc/ringbuffered_v2.rs b/embassy-stm32/src/adc/ringbuffered_v2.rs index 8c5eb9672..fb29d9a8c 100644 --- a/embassy-stm32/src/adc/ringbuffered_v2.rs +++ b/embassy-stm32/src/adc/ringbuffered_v2.rs @@ -170,7 +170,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { Self::start_adc(); } - //Check the sequence is long enough + // Check the sequence is long enough T::regs().sqr1().modify(|r| { let prev: Sequence = r.l().into(); if prev < sequence { @@ -182,10 +182,10 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { } }); - //Set this GPIO as an analog input. + // Set this GPIO as an analog input. channel.setup(); - //Set the channel in the right sequence field. + // Set the channel in the right sequence field. match sequence { Sequence::One => T::regs().sqr3().modify(|w| w.set_sq(0, channel.channel())), Sequence::Two => T::regs().sqr3().modify(|w| w.set_sq(1, channel.channel())), @@ -260,7 +260,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { let r = T::regs(); - //Enable ADC + // Enable ADC let was_on = Self::is_on(); if !was_on { r.cr2().modify(|reg| { @@ -298,7 +298,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { w.set_eocs(vals::Eocs::EACHCONVERSION); }); - //Being ADC conversions + // Begin ADC conversions T::regs().cr2().modify(|reg| { reg.set_adon(true); reg.set_swstart(true); From 7884babb9d1557549e5732d6ab42a4f4c28f25e7 Mon Sep 17 00:00:00 2001 From: Giona Imperatori Date: Mon, 1 Jul 2024 17:20:42 +0200 Subject: [PATCH 24/57] feat(pwm): allow specifying OutputDrive for PWM channels --- embassy-nrf/CHANGELOG.md | 1 + embassy-nrf/src/pwm.rs | 58 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/embassy-nrf/CHANGELOG.md b/embassy-nrf/CHANGELOG.md index 773a1a108..6f07a8c6d 100644 --- a/embassy-nrf/CHANGELOG.md +++ b/embassy-nrf/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - spi: Add support for configuring bit order for bus - pwm: Expose `pwm::PWM_CLK_HZ` and add `is_enabled` method - gpio: Drop GPIO Pin generics (API break) +- pwm: Allow specifying OutputDrive for PWM channels ## 0.1.0 - 2024-01-12 diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index 12057f7dd..a2853de3f 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -6,7 +6,7 @@ use core::sync::atomic::{compiler_fence, Ordering}; use embassy_hal_internal::{into_ref, PeripheralRef}; -use crate::gpio::{AnyPin, Pin as GpioPin, PselBits, SealedPin as _}; +use crate::gpio::{convert_drive, AnyPin, OutputDrive, Pin as GpioPin, PselBits, SealedPin as _}; use crate::ppi::{Event, Task}; use crate::util::slice_in_ram_or; use crate::{interrupt, pac, Peripheral}; @@ -128,19 +128,23 @@ impl<'d, T: Instance> SequencePwm<'d, T> { if let Some(pin) = &ch0 { pin.set_low(); - pin.conf().write(|w| w.dir().output()); + pin.conf() + .write(|w| w.dir().output().drive().variant(convert_drive(config.ch0_drive))); } if let Some(pin) = &ch1 { pin.set_low(); - pin.conf().write(|w| w.dir().output()); + pin.conf() + .write(|w| w.dir().output().drive().variant(convert_drive(config.ch1_drive))); } if let Some(pin) = &ch2 { pin.set_low(); - pin.conf().write(|w| w.dir().output()); + pin.conf() + .write(|w| w.dir().output().drive().variant(convert_drive(config.ch2_drive))); } if let Some(pin) = &ch3 { pin.set_low(); - pin.conf().write(|w| w.dir().output()); + pin.conf() + .write(|w| w.dir().output().drive().variant(convert_drive(config.ch3_drive))); } r.psel.out[0].write(|w| unsafe { w.bits(ch0.psel_bits()) }); @@ -319,6 +323,14 @@ pub struct Config { pub prescaler: Prescaler, /// How a sequence is read from RAM and is spread to the compare register pub sequence_load: SequenceLoad, + /// Drive strength for the channel 0 line. + pub ch0_drive: OutputDrive, + /// Drive strength for the channel 1 line. + pub ch1_drive: OutputDrive, + /// Drive strength for the channel 2 line. + pub ch2_drive: OutputDrive, + /// Drive strength for the channel 3 line. + pub ch3_drive: OutputDrive, } impl Default for Config { @@ -328,6 +340,10 @@ impl Default for Config { max_duty: 1000, prescaler: Prescaler::Div16, sequence_load: SequenceLoad::Common, + ch0_drive: OutputDrive::Standard, + ch1_drive: OutputDrive::Standard, + ch2_drive: OutputDrive::Standard, + ch3_drive: OutputDrive::Standard, } } } @@ -815,6 +831,38 @@ impl<'d, T: Instance> SimplePwm<'d, T> { let max_duty = self.max_duty() as u32; clk / max_duty } + + /// Sets the PWM-Channel0 output drive strength + #[inline(always)] + pub fn set_ch0_drive(&self, drive: OutputDrive) { + if let Some(pin) = &self.ch0 { + pin.conf().write(|w| w.drive().variant(convert_drive(drive))); + } + } + + /// Sets the PWM-Channel1 output drive strength + #[inline(always)] + pub fn set_ch1_drive(&self, drive: OutputDrive) { + if let Some(pin) = &self.ch1 { + pin.conf().write(|w| w.drive().variant(convert_drive(drive))); + } + } + + /// Sets the PWM-Channel2 output drive strength + #[inline(always)] + pub fn set_ch2_drive(&self, drive: OutputDrive) { + if let Some(pin) = &self.ch2 { + pin.conf().write(|w| w.drive().variant(convert_drive(drive))); + } + } + + /// Sets the PWM-Channel3 output drive strength + #[inline(always)] + pub fn set_ch3_drive(&self, drive: OutputDrive) { + if let Some(pin) = &self.ch3 { + pin.conf().write(|w| w.drive().variant(convert_drive(drive))); + } + } } impl<'a, T: Instance> Drop for SimplePwm<'a, T> { From f418006508380fa40058b896a753cdafbf9bdc86 Mon Sep 17 00:00:00 2001 From: Giona Imperatori Date: Mon, 1 Jul 2024 18:06:14 +0200 Subject: [PATCH 25/57] fixup! feat(pwm): allow specifying OutputDrive for PWM channels --- embassy-nrf/src/pwm.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index a2853de3f..8e8f166d7 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -836,7 +836,7 @@ impl<'d, T: Instance> SimplePwm<'d, T> { #[inline(always)] pub fn set_ch0_drive(&self, drive: OutputDrive) { if let Some(pin) = &self.ch0 { - pin.conf().write(|w| w.drive().variant(convert_drive(drive))); + pin.conf().modify(|_, w| w.drive().variant(convert_drive(drive))); } } @@ -844,7 +844,7 @@ impl<'d, T: Instance> SimplePwm<'d, T> { #[inline(always)] pub fn set_ch1_drive(&self, drive: OutputDrive) { if let Some(pin) = &self.ch1 { - pin.conf().write(|w| w.drive().variant(convert_drive(drive))); + pin.conf().modify(|_, w| w.drive().variant(convert_drive(drive))); } } @@ -852,7 +852,7 @@ impl<'d, T: Instance> SimplePwm<'d, T> { #[inline(always)] pub fn set_ch2_drive(&self, drive: OutputDrive) { if let Some(pin) = &self.ch2 { - pin.conf().write(|w| w.drive().variant(convert_drive(drive))); + pin.conf().modify(|_, w| w.drive().variant(convert_drive(drive))); } } @@ -860,7 +860,7 @@ impl<'d, T: Instance> SimplePwm<'d, T> { #[inline(always)] pub fn set_ch3_drive(&self, drive: OutputDrive) { if let Some(pin) = &self.ch3 { - pin.conf().write(|w| w.drive().variant(convert_drive(drive))); + pin.conf().modify(|_, w| w.drive().variant(convert_drive(drive))); } } } From abe7f9921e4b04b8341396c2681730ed3aa59d90 Mon Sep 17 00:00:00 2001 From: trepidacious Date: Mon, 1 Jul 2024 20:59:27 +0100 Subject: [PATCH 26/57] Update `ReadReady` and `WriteReady` implementations Update `ReadReady` for `TcpReader` to match implementation for `TcpSocket` Update `WriteReady` implementations to use `can_recv()` rather than `may_recv()`, since this will check that the transmit buffer is not full. --- embassy-net/src/tcp.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index 4d6dc92de..74eff9dae 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -603,7 +603,7 @@ mod embedded_io_impls { impl<'d> embedded_io_async::WriteReady for TcpSocket<'d> { fn write_ready(&mut self) -> Result { - Ok(self.io.with(|s, _| s.may_send())) + Ok(self.io.with(|s, _| s.can_send())) } } @@ -619,7 +619,7 @@ mod embedded_io_impls { impl<'d> embedded_io_async::ReadReady for TcpReader<'d> { fn read_ready(&mut self) -> Result { - Ok(self.io.with(|s, _| s.can_recv())) + Ok(self.io.with(|s, _| s.can_recv() || !s.may_recv())) } } @@ -639,7 +639,7 @@ mod embedded_io_impls { impl<'d> embedded_io_async::WriteReady for TcpWriter<'d> { fn write_ready(&mut self) -> Result { - Ok(self.io.with(|s, _| s.may_send())) + Ok(self.io.with(|s, _| s.can_send())) } } } From a862334daef1986d41d8873324821dfce37c70b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D0=BC=D0=B0=D0=BD=20=D0=9A=D1=80=D0=B8=D0=B2?= =?UTF-8?q?=D0=B5=D0=BD=D0=BA=D0=BE=D0=B2?= Date: Thu, 23 May 2024 12:42:41 +0400 Subject: [PATCH 27/57] STM32 Half-Duplex: fix sequential reads and writes --- embassy-stm32/src/usart/mod.rs | 45 +++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index 5754f783e..7ed3793a1 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -371,9 +371,12 @@ impl<'d> UartTx<'d, Async> { pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { let r = self.info.regs; - // Disable Receiver for Half-Duplex mode - if r.cr3().read().hdsel() { - r.cr1().modify(|reg| reg.set_re(false)); + // Enable Transmitter and disable Receiver for Half-Duplex mode + let mut cr1 = r.cr1().read(); + if r.cr3().read().hdsel() && !cr1.te() { + cr1.set_te(true); + cr1.set_re(false); + r.cr1().write_value(cr1); } let ch = self.tx_dma.as_mut().unwrap(); @@ -474,9 +477,12 @@ impl<'d, M: Mode> UartTx<'d, M> { pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { let r = self.info.regs; - // Disable Receiver for Half-Duplex mode - if r.cr3().read().hdsel() { - r.cr1().modify(|reg| reg.set_re(false)); + // Enable Transmitter and disable Receiver for Half-Duplex mode + let mut cr1 = r.cr1().read(); + if r.cr3().read().hdsel() && !cr1.te() { + cr1.set_te(true); + cr1.set_re(false); + r.cr1().write_value(cr1); } for &b in buffer { @@ -561,8 +567,9 @@ impl<'d> UartRx<'d, Async> { ) -> Result { let r = self.info.regs; - // Call flush for Half-Duplex mode. It prevents reading of bytes which have just been written. - if r.cr3().read().hdsel() { + // Call flush for Half-Duplex mode if some bytes were written and flush was not called. + // It prevents reading of bytes which have just been written. + if r.cr3().read().hdsel() && r.cr1().read().te() { blocking_flush(self.info)?; } @@ -898,8 +905,9 @@ impl<'d, M: Mode> UartRx<'d, M> { pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { let r = self.info.regs; - // Call flush for Half-Duplex mode. It prevents reading of bytes which have just been written. - if r.cr3().read().hdsel() { + // Call flush for Half-Duplex mode if some bytes were written and flush was not called. + // It prevents reading of bytes which have just been written. + if r.cr3().read().hdsel() && r.cr1().read().te() { blocking_flush(self.info)?; } @@ -1481,10 +1489,19 @@ fn configure( r.cr1().write(|w| { // enable uart w.set_ue(true); - // enable transceiver - w.set_te(enable_tx); - // enable receiver - w.set_re(enable_rx); + + if config.half_duplex { + // The te and re bits will be set by write, read and flush methods. + // Receiver should be enabled by default for Half-Duplex. + w.set_te(false); + w.set_re(true); + } else { + // enable transceiver + w.set_te(enable_tx); + // enable receiver + w.set_re(enable_rx); + } + // configure word size // if using odd or even parity it must be configured to 9bits w.set_m0(if config.parity != Parity::ParityNone { From 8cbb64226b802ed6ad7fc4d40bb002a32389aa3a Mon Sep 17 00:00:00 2001 From: Andres Vahter Date: Mon, 1 Jul 2024 15:07:26 +0300 Subject: [PATCH 28/57] update stm32-metapac --- embassy-stm32/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index d0e7ffd6d..523bacb11 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -72,7 +72,7 @@ rand_core = "0.6.3" sdio-host = "0.5.0" critical-section = "1.1" #stm32-metapac = { version = "15" } -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-cb8af7b3c2cfb88283e0ce979a318657853434c0" } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-e0cfd165fd8fffaa0df66a35eeca83b228496645" } vcell = "0.1.3" nb = "1.0.0" @@ -97,7 +97,7 @@ proc-macro2 = "1.0.36" quote = "1.0.15" #stm32-metapac = { version = "15", default-features = false, features = ["metadata"]} -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-cb8af7b3c2cfb88283e0ce979a318657853434c0", default-features = false, features = ["metadata"] } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-e0cfd165fd8fffaa0df66a35eeca83b228496645", default-features = false, features = ["metadata"] } [features] default = ["rt"] From 1f30ad595b9ac2f3cdf1ecaa24e6d96d16286a16 Mon Sep 17 00:00:00 2001 From: Andres Vahter Date: Tue, 2 Jul 2024 09:21:27 +0300 Subject: [PATCH 29/57] stm32 ringbuffered adc: fix for metapac changes --- embassy-stm32/src/adc/ringbuffered_v2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-stm32/src/adc/ringbuffered_v2.rs b/embassy-stm32/src/adc/ringbuffered_v2.rs index fb29d9a8c..bc8ef2d13 100644 --- a/embassy-stm32/src/adc/ringbuffered_v2.rs +++ b/embassy-stm32/src/adc/ringbuffered_v2.rs @@ -291,7 +291,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { // Enable DMA mode w.set_dma(true); // Enable continuous conversions - w.set_cont(vals::Cont::CONTINUOUS); + w.set_cont(true); // DMA requests are issues as long as DMA=1 and data are converted. w.set_dds(vals::Dds::CONTINUOUS); // EOC flag is set at the end of each conversion. From 8f52ee469eb282d55ed74dc08b71c1e994aa8ba5 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Tue, 2 Jul 2024 12:37:36 +0200 Subject: [PATCH 30/57] make some embassy-boot asserts const --- embassy-boot/src/boot_loader.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/embassy-boot/src/boot_loader.rs b/embassy-boot/src/boot_loader.rs index a38558056..789fa34c1 100644 --- a/embassy-boot/src/boot_loader.rs +++ b/embassy-boot/src/boot_loader.rs @@ -235,12 +235,15 @@ impl BootLoader Result { + const { + assert!(Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32 == 0); + assert!(Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32 == 0); + assert!(Self::PAGE_SIZE % DFU::WRITE_SIZE as u32 == 0); + assert!(Self::PAGE_SIZE % DFU::ERASE_SIZE as u32 == 0); + } + // Ensure we have enough progress pages to store copy progress assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32); - assert_eq!(0, Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32); - assert_eq!(0, Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32); - assert_eq!(0, Self::PAGE_SIZE % DFU::WRITE_SIZE as u32); - assert_eq!(0, Self::PAGE_SIZE % DFU::ERASE_SIZE as u32); assert!(aligned_buf.len() >= STATE::WRITE_SIZE); assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE); assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE); From b88e1a5d7180871d7e8564a3060b50e166602902 Mon Sep 17 00:00:00 2001 From: Andres Vahter Date: Tue, 2 Jul 2024 15:22:50 +0300 Subject: [PATCH 31/57] stm32 ringbuffered adc docs --- embassy-stm32/src/adc/ringbuffered_v2.rs | 70 +++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/adc/ringbuffered_v2.rs b/embassy-stm32/src/adc/ringbuffered_v2.rs index fb29d9a8c..82b67c533 100644 --- a/embassy-stm32/src/adc/ringbuffered_v2.rs +++ b/embassy-stm32/src/adc/ringbuffered_v2.rs @@ -91,6 +91,15 @@ pub struct RingBufferedAdc<'d, T: Instance> { } impl<'d, T: Instance> Adc<'d, T> { + /// Configures the ADC to use a DMA ring buffer for continuous data acquisition. + /// + /// The `dma_buf` should be large enough to prevent buffer overflow, allowing sufficient time to read out measurements. + /// The length of the `dma_buf` should be a multiple of the ADC channel count. + /// For example, if 3 channels are measured, its length can be 3 * 40 = 120 measurements. + /// + /// `read_exact` method is used to read out measurements from the DMA ring buffer, and its buffer should be exactly half of the `dma_buf` length. + /// + /// [`read_exact`]: #method.read_exact pub fn into_ring_buffered( self, dma: impl Peripheral

> + 'd, @@ -214,6 +223,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { Self::start_adc(); } + /// Turns on ADC if it is not already turned on and starts continuous DMA transfer. pub fn start(&mut self) -> Result<(), OverrunError> { self.ring_buf.clear(); @@ -227,6 +237,11 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { Err(err) } + /// Stops DMA transfer. + /// It does not turn off ADC. + /// Calling `start` restarts continuous DMA transfer. + /// + /// [`start`]: #method.start pub fn teardown_adc(&mut self) { // Stop the DMA transfer self.ring_buf.request_stop(); @@ -341,7 +356,58 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { } } - pub async fn read_exact(&mut self, buf: &mut [u16; N]) -> Result { + /// Reads measurements from the DMA ring buffer. + /// + /// This method fills the provided `measurements` array with ADC readings. + /// The length of the `measurements` array should be exactly half of the DMA buffer length. + /// Because interrupts are only generated if half or full DMA transfer completes. + /// + /// Each call to `read_exact` will populate the `measurements` array in the same order as the channels defined with `set_sample_sequence`. + /// There will be many sequences worth of measurements in this array because it only returns if at least half of the DMA buffer is filled. + /// For example if 3 channels are sampled `measurements` contain: `[sq0 sq1 sq3 sq0 sq1 sq3 sq0 sq1 sq3 sq0 sq1 sq3..]`. + /// + /// If an error is returned, it indicates a DMA overrun, and the process must be restarted by calling `start` again. + /// + /// By default, the ADC fills the DMA buffer as quickly as possible. To control the sample rate, call `teardown_adc` after each readout, and then start the DMA again at the desired interval. + /// Note that even if using `teardown_adc` to control sample rate, with each call to `read_exact`, measurements equivalent to half the size of the DMA buffer are still collected. + /// + /// Example: + /// ```rust,ignore + /// const DMA_BUF_LEN: usize = 120; + /// let adc_dma_buf = [0u16; DMA_BUF_LEN]; + /// let mut adc: RingBufferedAdc = adc.into_ring_buffered(p.DMA2_CH0, adc_dma_buf); + /// + /// adc.set_sample_sequence(Sequence::One, &mut p.PA0, SampleTime::CYCLES112); + /// adc.set_sample_sequence(Sequence::Two, &mut p.PA1, SampleTime::CYCLES112); + /// adc.set_sample_sequence(Sequence::Three, &mut p.PA2, SampleTime::CYCLES112); + /// + /// adc.start.unwrap(); + /// let mut measurements = [0u16; DMA_BUF_LEN / 2]; + /// loop { + /// match adc.read_exact(&mut measurements).await { + /// Ok(_) => { + /// defmt::info!("adc1: {}", measurements); + /// // Only needed to manually control sample rate. + /// adc.teardown_adc(); + /// } + /// Err(e) => { + /// defmt::warn!("Error: {:?}", e); + /// // DMA overflow, restart ADC. + /// let _ = adc.start(); + /// } + /// } + /// + /// // Manually control sample rate. + /// Timer::after_millis(100).await; + /// let _ = adc.start(); + /// } + /// ``` + /// + /// + /// [`set_sample_sequence`]: #method.set_sample_sequence + /// [`teardown_adc`]: #method.teardown_adc + /// [`start`]: #method.start + pub async fn read_exact(&mut self, measurements: &mut [u16; N]) -> Result { let r = T::regs(); // Start background receive if it was not already started @@ -353,7 +419,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { if r.sr().read().ovr() { return self.stop(OverrunError); } - match self.ring_buf.read_exact(buf).await { + match self.ring_buf.read_exact(measurements).await { Ok(len) => Ok(len), Err(_) => self.stop(OverrunError), } From dd69efe70804f57c17adb45602798dad617cb41d Mon Sep 17 00:00:00 2001 From: Andres Vahter Date: Tue, 2 Jul 2024 16:56:19 +0300 Subject: [PATCH 32/57] stm32 ringbuffered adc: add buf size assert --- embassy-stm32/src/adc/ringbuffered_v2.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/embassy-stm32/src/adc/ringbuffered_v2.rs b/embassy-stm32/src/adc/ringbuffered_v2.rs index 82b67c533..3f5277a9d 100644 --- a/embassy-stm32/src/adc/ringbuffered_v2.rs +++ b/embassy-stm32/src/adc/ringbuffered_v2.rs @@ -408,6 +408,12 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { /// [`teardown_adc`]: #method.teardown_adc /// [`start`]: #method.start pub async fn read_exact(&mut self, measurements: &mut [u16; N]) -> Result { + assert_eq!( + self.ring_buf.capacity() / 2, + N, + "Buffer size must be half the size of the ring buffer" + ); + let r = T::regs(); // Start background receive if it was not already started From 02b096915fbf138602e2ad8c6e1d85d531882daf Mon Sep 17 00:00:00 2001 From: Alexandros Liarokapis Date: Fri, 21 Jun 2024 23:37:58 +0300 Subject: [PATCH 33/57] add asynchrous sequence read support to adc v4 --- embassy-stm32/build.rs | 1 + embassy-stm32/src/adc/v4.rs | 139 ++++++++++++++++++++++++++++++++---- 2 files changed, 127 insertions(+), 13 deletions(-) diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 24e2226a2..df866b5ff 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -1182,6 +1182,7 @@ fn main() { (("adc", "ADC1"), quote!(crate::adc::RxDma)), (("adc", "ADC2"), quote!(crate::adc::RxDma)), (("adc", "ADC3"), quote!(crate::adc::RxDma)), + (("adc", "ADC4"), quote!(crate::adc::RxDma)), (("ucpd", "RX"), quote!(crate::ucpd::RxDma)), (("ucpd", "TX"), quote!(crate::ucpd::TxDma)), (("usart", "RX"), quote!(crate::usart::RxDma)), diff --git a/embassy-stm32/src/adc/v4.rs b/embassy-stm32/src/adc/v4.rs index 50db646fe..f4b62d80e 100644 --- a/embassy-stm32/src/adc/v4.rs +++ b/embassy-stm32/src/adc/v4.rs @@ -1,8 +1,12 @@ #[allow(unused)] use pac::adc::vals::{Adcaldif, Boost, Difsel, Exten, Pcsel}; +use pac::adc::vals::{Adstp, Dmngt}; use pac::adccommon::vals::Presc; -use super::{blocking_delay_us, Adc, AdcChannel, Instance, Resolution, SampleTime}; +use super::{ + blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, +}; +use crate::dma::Transfer; use crate::time::Hertz; use crate::{pac, rcc, Peripheral}; @@ -34,7 +38,7 @@ const VBAT_CHANNEL: u8 = 17; /// Internal voltage reference channel. pub struct VrefInt; impl AdcChannel for VrefInt {} -impl super::SealedAdcChannel for VrefInt { +impl SealedAdcChannel for VrefInt { fn channel(&self) -> u8 { VREF_CHANNEL } @@ -43,7 +47,7 @@ impl super::SealedAdcChannel for VrefInt { /// Internal temperature channel. pub struct Temperature; impl AdcChannel for Temperature {} -impl super::SealedAdcChannel for Temperature { +impl SealedAdcChannel for Temperature { fn channel(&self) -> u8 { TEMP_CHANNEL } @@ -52,7 +56,7 @@ impl super::SealedAdcChannel for Temperature { /// Internal battery voltage channel. pub struct Vbat; impl AdcChannel for Vbat {} -impl super::SealedAdcChannel for Vbat { +impl SealedAdcChannel for Vbat { fn channel(&self) -> u8 { VBAT_CHANNEL } @@ -247,6 +251,11 @@ impl<'d, T: Instance> Adc<'d, T> { self.sample_time = sample_time; } + /// Get the ADC sample time. + pub fn sample_time(&self) -> SampleTime { + self.sample_time + } + /// Set the ADC resolution. pub fn set_resolution(&mut self, resolution: Resolution) { T::regs().cfgr().modify(|reg| reg.set_res(resolution.into())); @@ -273,25 +282,120 @@ impl<'d, T: Instance> Adc<'d, T> { /// Read an ADC channel. pub fn read(&mut self, channel: &mut impl AdcChannel) -> u16 { - channel.setup(); - - self.read_channel(channel.channel()) + self.read_channel(channel) } - fn read_channel(&mut self, channel: u8) -> u16 { - // Configure channel - Self::set_channel_sample_time(channel, self.sample_time); + /// Asynchronously read from sequence of ADC channels. + pub async fn read_async( + &mut self, + rx_dma: &mut impl RxDma, + sequence: impl ExactSizeIterator, SampleTime)>, + data: &mut [u16], + ) { + assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty"); + + assert!( + sequence.len() <= 16, + "Asynchronous read sequence cannot be more than 16 in length" + ); + + // Ensure no conversions are ongoing + Self::cancel_conversions(); + + // Set sequence length + T::regs().sqr1().modify(|w| { + w.set_l(sequence.len() as u8 - 1); + }); + + // Configure channels and ranks + for (i, (channel, sample_time)) in sequence.enumerate() { + Self::configure_channel(channel, sample_time); + match i { + 0..=3 => { + T::regs().sqr1().modify(|w| { + w.set_sq(i, channel.channel()); + }); + } + 4..=8 => { + T::regs().sqr2().modify(|w| { + w.set_sq(i - 4, channel.channel()); + }); + } + 9..=13 => { + T::regs().sqr3().modify(|w| { + w.set_sq(i - 9, channel.channel()); + }); + } + 14..=15 => { + T::regs().sqr4().modify(|w| { + w.set_sq(i - 14, channel.channel()); + }); + } + _ => unreachable!(), + } + } + + // Set continuous mode with oneshot dma. + // Clear overrun flag before starting transfer. + + T::regs().isr().modify(|reg| { + reg.set_ovr(true); + }); + T::regs().cfgr().modify(|reg| { + reg.set_cont(true); + reg.set_dmngt(Dmngt::DMA_ONESHOT); + }); + + let request = rx_dma.request(); + let transfer = unsafe { + Transfer::new_read( + rx_dma, + request, + T::regs().dr().as_ptr() as *mut u16, + data, + Default::default(), + ) + }; + + // Start conversion + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); + + // Wait for conversion sequence to finish. + transfer.await; + + // Ensure conversions are finished. + Self::cancel_conversions(); + + // Reset configuration. + T::regs().cfgr().modify(|reg| { + reg.set_cont(false); + reg.set_dmngt(Dmngt::from_bits(0)); + }); + } + + fn configure_channel(channel: &mut impl AdcChannel, sample_time: SampleTime) { + channel.setup(); + + let channel = channel.channel(); + + Self::set_channel_sample_time(channel, sample_time); #[cfg(stm32h7)] { T::regs().cfgr2().modify(|w| w.set_lshift(0)); T::regs() .pcsel() - .write(|w| w.set_pcsel(channel as _, Pcsel::PRESELECTED)); + .modify(|w| w.set_pcsel(channel as _, Pcsel::PRESELECTED)); } + } - T::regs().sqr1().write(|reg| { - reg.set_sq(0, channel); + fn read_channel(&mut self, channel: &mut impl AdcChannel) -> u16 { + Self::configure_channel(channel, self.sample_time); + + T::regs().sqr1().modify(|reg| { + reg.set_sq(0, channel.channel()); reg.set_l(0); }); @@ -306,4 +410,13 @@ impl<'d, T: Instance> Adc<'d, T> { T::regs().smpr(1).modify(|reg| reg.set_smp((ch - 10) as _, sample_time)); } } + + fn cancel_conversions() { + if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() { + T::regs().cr().modify(|reg| { + reg.set_adstp(Adstp::STOP); + }); + while T::regs().cr().read().adstart() {} + } + } } From 70061e74b2b4e4ca513bcefa5c3bebbb52538e5d Mon Sep 17 00:00:00 2001 From: Alexandros Liarokapis Date: Wed, 26 Jun 2024 19:06:20 +0300 Subject: [PATCH 34/57] add async dma read example --- examples/stm32h7/src/bin/adc_dma.rs | 76 +++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 examples/stm32h7/src/bin/adc_dma.rs diff --git a/examples/stm32h7/src/bin/adc_dma.rs b/examples/stm32h7/src/bin/adc_dma.rs new file mode 100644 index 000000000..6c0240453 --- /dev/null +++ b/examples/stm32h7/src/bin/adc_dma.rs @@ -0,0 +1,76 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, AdcChannel as _, SampleTime}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".ram_d3"] +static mut DMA_BUF: [u16; 2] = [0; 2]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut read_buffer = unsafe { &mut DMA_BUF[..] }; + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV8), // SPI1 cksel defaults to pll1_q + divr: None, + }); + config.rcc.pll2 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV8), // 100mhz + divq: None, + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + config.rcc.mux.adcsel = mux::Adcsel::PLL2_P; + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let mut adc = Adc::new(p.ADC3); + + let mut dma = p.DMA1_CH1; + let mut vrefint_channel = adc.enable_vrefint().degrade_adc(); + let mut pc0 = p.PC0.degrade_adc(); + + loop { + adc.read_async( + &mut dma, + [ + (&mut vrefint_channel, SampleTime::CYCLES387_5), + (&mut pc0, SampleTime::CYCLES810_5), + ] + .into_iter(), + &mut read_buffer, + ) + .await; + + let vrefint = read_buffer[0]; + let measured = read_buffer[1]; + info!("vrefint: {}", vrefint); + info!("measured: {}", measured); + Timer::after_millis(500).await; + } +} From 5e2fd8623a01e8c026ba2057dd017b4d4bca3acb Mon Sep 17 00:00:00 2001 From: Andres Vahter Date: Wed, 26 Jun 2024 23:50:12 +0300 Subject: [PATCH 35/57] stm32 adc v3 read_async --- embassy-stm32/src/adc/v3.rs | 192 +++++++++++++++++++++++++++++++++--- 1 file changed, 176 insertions(+), 16 deletions(-) diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index 398c57a92..559751555 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -1,8 +1,10 @@ use cfg_if::cfg_if; use embassy_hal_internal::into_ref; -use super::blocking_delay_us; -use crate::adc::{Adc, AdcChannel, Instance, Resolution, SampleTime}; +use super::{ + blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, +}; +use crate::dma::Transfer; use crate::{rcc, Peripheral}; /// Default VREF voltage used for sample conversion to millivolts. @@ -12,7 +14,7 @@ pub const VREF_CALIB_MV: u32 = 3000; pub struct VrefInt; impl AdcChannel for VrefInt {} -impl super::SealedAdcChannel for VrefInt { +impl SealedAdcChannel for VrefInt { fn channel(&self) -> u8 { cfg_if! { if #[cfg(adc_g0)] { @@ -31,7 +33,7 @@ impl super::SealedAdcChannel for VrefInt { pub struct Temperature; impl AdcChannel for Temperature {} -impl super::SealedAdcChannel for Temperature { +impl SealedAdcChannel for Temperature { fn channel(&self) -> u8 { cfg_if! { if #[cfg(adc_g0)] { @@ -50,7 +52,7 @@ impl super::SealedAdcChannel for Temperature { pub struct Vbat; impl AdcChannel for Vbat {} -impl super::SealedAdcChannel for Vbat { +impl SealedAdcChannel for Vbat { fn channel(&self) -> u8 { cfg_if! { if #[cfg(adc_g0)] { @@ -101,6 +103,7 @@ impl<'d, T: Instance> Adc<'d, T> { reg.set_advregen(true); }); + // If this is false then each ADC_CHSELR bit enables an input channel. #[cfg(any(adc_g0, adc_u0))] T::regs().cfgr1().modify(|reg| { reg.set_chselrmod(false); @@ -124,6 +127,28 @@ impl<'d, T: Instance> Adc<'d, T> { } } + // Enable ADC only when it is not already running. + fn enable(&mut self) { + // Make sure bits are off + while T::regs().cr().read().addis() { + // spin + } + + if !T::regs().cr().read().aden() { + // Enable ADC + T::regs().isr().modify(|reg| { + reg.set_adrdy(true); + }); + T::regs().cr().modify(|reg| { + reg.set_aden(true); + }); + + while !T::regs().isr().read().adrdy() { + // spin + } + } + } + pub fn enable_vrefint(&self) -> VrefInt { #[cfg(not(any(adc_g0, adc_u0)))] T::common_regs().ccr().modify(|reg| { @@ -181,10 +206,17 @@ impl<'d, T: Instance> Adc<'d, T> { Vbat {} } + /// Set the ADC sample time. pub fn set_sample_time(&mut self, sample_time: SampleTime) { self.sample_time = sample_time; } + /// Get the ADC sample time. + pub fn sample_time(&self) -> SampleTime { + self.sample_time + } + + /// Set the ADC resolution. pub fn set_resolution(&mut self, resolution: Resolution) { #[cfg(not(any(adc_g0, adc_u0)))] T::regs().cfgr().modify(|reg| reg.set_res(resolution.into())); @@ -220,24 +252,139 @@ impl<'d, T: Instance> Adc<'d, T> { T::regs().dr().read().0 as u16 } + /// Read an ADC channel. pub fn read(&mut self, channel: &mut impl AdcChannel) -> u16 { - // Make sure bits are off - while T::regs().cr().read().addis() { - // spin + self.read_channel(channel) + } + + /// Asynchronously read from sequence of ADC channels. + pub async fn read_async( + &mut self, + rx_dma: &mut impl RxDma, + sequence: impl ExactSizeIterator, SampleTime)>, + data: &mut [u16], + ) { + assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty"); + + assert!( + sequence.len() <= 16, + "Asynchronous read sequence cannot be more than 16 in length" + ); + + // Ensure no conversions are ongoing and ADC is enabled. + Self::cancel_conversions(); + self.enable(); + + // Set sequence length + #[cfg(not(any(adc_g0, adc_u0)))] + T::regs().sqr1().modify(|w| { + w.set_l(sequence.len() as u8 - 1); + }); + + #[cfg(any(adc_g0, adc_u0))] + let mut channel_mask = 0; + + // Configure channels and ranks + for (_i, (channel, sample_time)) in sequence.enumerate() { + Self::configure_channel(channel, sample_time); + + // Each channel is sampled according to sequence + #[cfg(not(any(adc_g0, adc_u0)))] + match _i { + 0..=3 => { + T::regs().sqr1().modify(|w| { + w.set_sq(_i, channel.channel()); + }); + } + 4..=8 => { + T::regs().sqr2().modify(|w| { + w.set_sq(_i - 4, channel.channel()); + }); + } + 9..=13 => { + T::regs().sqr3().modify(|w| { + w.set_sq(_i - 9, channel.channel()); + }); + } + 14..=15 => { + T::regs().sqr4().modify(|w| { + w.set_sq(_i - 14, channel.channel()); + }); + } + _ => unreachable!(), + } + + #[cfg(any(adc_g0, adc_u0))] + { + channel_mask |= 1 << channel.channel(); + } } - // Enable ADC + // On G0 and U0 enabled channels are sampled from 0 to last channel. + // It is possible to add up to 8 sequences if CHSELRMOD = 1. + // However for supporting more than 8 channels alternative CHSELRMOD = 0 approach is used. + #[cfg(any(adc_g0, adc_u0))] + T::regs().chselr().modify(|reg| { + reg.set_chsel(channel_mask); + }); + + // Set continuous mode with oneshot dma. + // Clear overrun flag before starting transfer. T::regs().isr().modify(|reg| { - reg.set_adrdy(true); + reg.set_ovr(true); }); + + #[cfg(not(any(adc_g0, adc_u0)))] + T::regs().cfgr().modify(|reg| { + reg.set_discen(false); + reg.set_cont(true); + // Oneshot mode + reg.set_dmacfg(false); + reg.set_dmaen(true); + }); + #[cfg(any(adc_g0, adc_u0))] + T::regs().cfgr1().modify(|reg| { + reg.set_discen(false); + reg.set_cont(true); + // Oneshot mode + reg.set_dmacfg(false); + reg.set_dmaen(true); + }); + + let request = rx_dma.request(); + let transfer = unsafe { + Transfer::new_read( + rx_dma, + request, + T::regs().dr().as_ptr() as *mut u16, + data, + Default::default(), + ) + }; + + // Start conversion T::regs().cr().modify(|reg| { - reg.set_aden(true); + reg.set_adstart(true); }); - while !T::regs().isr().read().adrdy() { - // spin - } + // Wait for conversion sequence to finish. + transfer.await; + // Ensure conversions are finished. + Self::cancel_conversions(); + + // Reset configuration. + #[cfg(not(any(adc_g0, adc_u0)))] + T::regs().cfgr().modify(|reg| { + reg.set_cont(false); + }); + #[cfg(any(adc_g0, adc_u0))] + T::regs().cfgr1().modify(|reg| { + reg.set_cont(false); + }); + } + + fn configure_channel(channel: &mut impl AdcChannel, sample_time: SampleTime) { // RM0492, RM0481, etc. // "This option bit must be set to 1 when ADCx_INP0 or ADCx_INN1 channel is selected." #[cfg(adc_h5)] @@ -246,7 +393,12 @@ impl<'d, T: Instance> Adc<'d, T> { } // Configure channel - Self::set_channel_sample_time(channel.channel(), self.sample_time); + Self::set_channel_sample_time(channel.channel(), sample_time); + } + + fn read_channel(&mut self, channel: &mut impl AdcChannel) -> u16 { + self.enable(); + Self::configure_channel(channel, self.sample_time); // Select channel #[cfg(not(any(adc_g0, adc_u0)))] @@ -262,7 +414,6 @@ impl<'d, T: Instance> Adc<'d, T> { // STM32G4: Section 2.7.3 #[cfg(any(rcc_l4, rcc_g4))] let _ = self.convert(); - let val = self.convert(); T::regs().cr().modify(|reg| reg.set_addis(true)); @@ -294,4 +445,13 @@ impl<'d, T: Instance> Adc<'d, T> { } } } + + fn cancel_conversions() { + if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() { + T::regs().cr().modify(|reg| { + reg.set_adstp(true); + }); + while T::regs().cr().read().adstart() {} + } + } } From c120efad5bbd7f95615e913100874d27c34cb389 Mon Sep 17 00:00:00 2001 From: Andres Vahter Date: Tue, 2 Jul 2024 14:23:44 +0300 Subject: [PATCH 36/57] stm32 adc read_async: add asserts for buf len --- embassy-stm32/src/adc/v3.rs | 10 +++++++--- embassy-stm32/src/adc/v4.rs | 9 ++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index 559751555..afdfc4e4c 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -262,10 +262,13 @@ impl<'d, T: Instance> Adc<'d, T> { &mut self, rx_dma: &mut impl RxDma, sequence: impl ExactSizeIterator, SampleTime)>, - data: &mut [u16], + readings: &mut [u16], ) { assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty"); - + assert!( + sequence.len() == readings.len(), + "Sequence length must be equal to readings length" + ); assert!( sequence.len() <= 16, "Asynchronous read sequence cannot be more than 16 in length" @@ -357,7 +360,7 @@ impl<'d, T: Instance> Adc<'d, T> { rx_dma, request, T::regs().dr().as_ptr() as *mut u16, - data, + readings, Default::default(), ) }; @@ -431,6 +434,7 @@ impl<'d, T: Instance> Adc<'d, T> { fn set_channel_sample_time(_ch: u8, sample_time: SampleTime) { cfg_if! { if #[cfg(any(adc_g0, adc_u0))] { + // On G0 and U6 all channels use the same sampling time. T::regs().smpr().modify(|reg| reg.set_smp1(sample_time.into())); } else if #[cfg(adc_h5)] { match _ch { diff --git a/embassy-stm32/src/adc/v4.rs b/embassy-stm32/src/adc/v4.rs index f4b62d80e..4261b9b14 100644 --- a/embassy-stm32/src/adc/v4.rs +++ b/embassy-stm32/src/adc/v4.rs @@ -290,10 +290,13 @@ impl<'d, T: Instance> Adc<'d, T> { &mut self, rx_dma: &mut impl RxDma, sequence: impl ExactSizeIterator, SampleTime)>, - data: &mut [u16], + readings: &mut [u16], ) { assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty"); - + assert!( + sequence.len() == readings.len(), + "Sequence length must be equal to readings length" + ); assert!( sequence.len() <= 16, "Asynchronous read sequence cannot be more than 16 in length" @@ -352,7 +355,7 @@ impl<'d, T: Instance> Adc<'d, T> { rx_dma, request, T::regs().dr().as_ptr() as *mut u16, - data, + readings, Default::default(), ) }; From a4e62314af50d58eb42689e095ffc4a117aad35d Mon Sep 17 00:00:00 2001 From: Andres Vahter Date: Tue, 2 Jul 2024 17:15:22 +0300 Subject: [PATCH 37/57] stm32: adc v3: fix for newest pac --- embassy-stm32/src/adc/v3.rs | 9 ++++----- embassy-stm32/src/adc/v4.rs | 3 +-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index afdfc4e4c..6703268ee 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -1,11 +1,12 @@ use cfg_if::cfg_if; use embassy_hal_internal::into_ref; +use pac::adc::vals::Dmacfg; use super::{ blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, }; use crate::dma::Transfer; -use crate::{rcc, Peripheral}; +use crate::{pac, rcc, Peripheral}; /// Default VREF voltage used for sample conversion to millivolts. pub const VREF_DEFAULT_MV: u32 = 3300; @@ -341,16 +342,14 @@ impl<'d, T: Instance> Adc<'d, T> { T::regs().cfgr().modify(|reg| { reg.set_discen(false); reg.set_cont(true); - // Oneshot mode - reg.set_dmacfg(false); + reg.set_dmacfg(Dmacfg::ONESHOT); reg.set_dmaen(true); }); #[cfg(any(adc_g0, adc_u0))] T::regs().cfgr1().modify(|reg| { reg.set_discen(false); reg.set_cont(true); - // Oneshot mode - reg.set_dmacfg(false); + reg.set_dmacfg(Dmacfg::ONESHOT); reg.set_dmaen(true); }); diff --git a/embassy-stm32/src/adc/v4.rs b/embassy-stm32/src/adc/v4.rs index 4261b9b14..344bf89af 100644 --- a/embassy-stm32/src/adc/v4.rs +++ b/embassy-stm32/src/adc/v4.rs @@ -1,6 +1,5 @@ #[allow(unused)] -use pac::adc::vals::{Adcaldif, Boost, Difsel, Exten, Pcsel}; -use pac::adc::vals::{Adstp, Dmngt}; +use pac::adc::vals::{Adcaldif, Adstp, Boost, Difsel, Dmngt, Exten, Pcsel}; use pac::adccommon::vals::Presc; use super::{ From d4ff7616f952bc801390e46bb3163b63cc01828d Mon Sep 17 00:00:00 2001 From: Andres Vahter Date: Tue, 2 Jul 2024 17:35:15 +0300 Subject: [PATCH 38/57] stm32 ringbuffered adc docs improvements --- embassy-stm32/src/adc/ringbuffered_v2.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/embassy-stm32/src/adc/ringbuffered_v2.rs b/embassy-stm32/src/adc/ringbuffered_v2.rs index 3f5277a9d..8df224783 100644 --- a/embassy-stm32/src/adc/ringbuffered_v2.rs +++ b/embassy-stm32/src/adc/ringbuffered_v2.rs @@ -93,11 +93,12 @@ pub struct RingBufferedAdc<'d, T: Instance> { impl<'d, T: Instance> Adc<'d, T> { /// Configures the ADC to use a DMA ring buffer for continuous data acquisition. /// - /// The `dma_buf` should be large enough to prevent buffer overflow, allowing sufficient time to read out measurements. + /// The `dma_buf` should be large enough to prevent DMA buffer overrun. /// The length of the `dma_buf` should be a multiple of the ADC channel count. /// For example, if 3 channels are measured, its length can be 3 * 40 = 120 measurements. /// /// `read_exact` method is used to read out measurements from the DMA ring buffer, and its buffer should be exactly half of the `dma_buf` length. + /// It is critical to call `read_exact` frequently to prevent DMA buffer overrun. /// /// [`read_exact`]: #method.read_exact pub fn into_ring_buffered( @@ -358,18 +359,17 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { /// Reads measurements from the DMA ring buffer. /// - /// This method fills the provided `measurements` array with ADC readings. - /// The length of the `measurements` array should be exactly half of the DMA buffer length. - /// Because interrupts are only generated if half or full DMA transfer completes. + /// This method fills the provided `measurements` array with ADC readings from the DMA buffer. + /// The length of the `measurements` array should be exactly half of the DMA buffer length. Because interrupts are only generated if half or full DMA transfer completes. /// /// Each call to `read_exact` will populate the `measurements` array in the same order as the channels defined with `set_sample_sequence`. /// There will be many sequences worth of measurements in this array because it only returns if at least half of the DMA buffer is filled. /// For example if 3 channels are sampled `measurements` contain: `[sq0 sq1 sq3 sq0 sq1 sq3 sq0 sq1 sq3 sq0 sq1 sq3..]`. /// - /// If an error is returned, it indicates a DMA overrun, and the process must be restarted by calling `start` again. + /// If an error is returned, it indicates a DMA overrun, and the process must be restarted by calling `start` or `read_exact` again. /// /// By default, the ADC fills the DMA buffer as quickly as possible. To control the sample rate, call `teardown_adc` after each readout, and then start the DMA again at the desired interval. - /// Note that even if using `teardown_adc` to control sample rate, with each call to `read_exact`, measurements equivalent to half the size of the DMA buffer are still collected. + /// Note that even if using `teardown_adc` to control the sample rate, with each call to `read_exact`, measurements equivalent to half the size of the DMA buffer are still collected. /// /// Example: /// ```rust,ignore @@ -381,7 +381,6 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { /// adc.set_sample_sequence(Sequence::Two, &mut p.PA1, SampleTime::CYCLES112); /// adc.set_sample_sequence(Sequence::Three, &mut p.PA2, SampleTime::CYCLES112); /// - /// adc.start.unwrap(); /// let mut measurements = [0u16; DMA_BUF_LEN / 2]; /// loop { /// match adc.read_exact(&mut measurements).await { @@ -392,14 +391,12 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { /// } /// Err(e) => { /// defmt::warn!("Error: {:?}", e); - /// // DMA overflow, restart ADC. - /// let _ = adc.start(); + /// // DMA overrun, next call to `read_exact` restart ADC. /// } /// } /// /// // Manually control sample rate. /// Timer::after_millis(100).await; - /// let _ = adc.start(); /// } /// ``` /// From d3f8905e938eecfe7789841c6232cb34b5f43a20 Mon Sep 17 00:00:00 2001 From: Eekle <96976531+Eekle@users.noreply.github.com> Date: Tue, 2 Jul 2024 20:46:05 +0200 Subject: [PATCH 39/57] Use crate level PeriMode --- embassy-stm32/src/tsc/mod.rs | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/embassy-stm32/src/tsc/mod.rs b/embassy-stm32/src/tsc/mod.rs index cd7ae4a53..5cb58e918 100644 --- a/embassy-stm32/src/tsc/mod.rs +++ b/embassy-stm32/src/tsc/mod.rs @@ -75,6 +75,7 @@ pub use enums::*; use crate::gpio::{AfType, AnyPin, OutputType, Speed}; use crate::interrupt::typelevel::Interrupt; +use crate::mode::{Async, Blocking, Mode as PeriMode}; use crate::rcc::{self, RccPeripheral}; use crate::{interrupt, peripherals, Peripheral}; @@ -93,23 +94,6 @@ pub enum Error { Test, } -/// Async acquisition API marker -pub struct Async; -/// Blocking acquisition API marker -pub struct Blocking; - -trait SealedDriverKind {} - -impl SealedDriverKind for Async {} -impl SealedDriverKind for Blocking {} - -#[allow(private_bounds)] -/// Driver variant marker for the TSC peripheral -pub trait DriverKind: SealedDriverKind {} - -impl DriverKind for Async {} -impl DriverKind for Blocking {} - /// TSC interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, @@ -522,7 +506,7 @@ pub enum G7 {} pub enum G8 {} /// TSC driver -pub struct Tsc<'d, T: Instance, K: DriverKind> { +pub struct Tsc<'d, T: Instance, K: PeriMode> { _peri: PeripheralRef<'d, T>, _g1: Option>, _g2: Option>, @@ -703,7 +687,7 @@ impl<'d, T: Instance> Tsc<'d, T, Blocking> { } } -impl<'d, T: Instance, K: DriverKind> Tsc<'d, T, K> { +impl<'d, T: Instance, K: PeriMode> Tsc<'d, T, K> { /// Create new TSC driver fn check_shields( g1: &Option>, @@ -969,7 +953,7 @@ impl<'d, T: Instance, K: DriverKind> Tsc<'d, T, K> { } } -impl<'d, T: Instance, K: DriverKind> Drop for Tsc<'d, T, K> { +impl<'d, T: Instance, K: PeriMode> Drop for Tsc<'d, T, K> { fn drop(&mut self) { rcc::disable::(); } From 9e4e536769e8bf35ecd43efac58d0655a77e5d58 Mon Sep 17 00:00:00 2001 From: Justus K Date: Tue, 2 Jul 2024 16:39:24 +0200 Subject: [PATCH 40/57] Better panic message when peripheral clock is not enabled --- embassy-stm32/build.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 24e2226a2..14db0504b 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -475,13 +475,21 @@ fn main() { } impl<'a> ClockGen<'a> { - fn gen_clock(&mut self, name: &str) -> TokenStream { + fn gen_clock(&mut self, peripheral: &str, name: &str) -> TokenStream { let clock_name = format_ident!("{}", name.to_ascii_lowercase()); self.clock_names.insert(name.to_ascii_lowercase()); - quote!( unsafe { crate::rcc::get_freqs().#clock_name.unwrap() } ) + quote!(unsafe { + unwrap!( + crate::rcc::get_freqs().#clock_name, + "peripheral '{}' is configured to use the '{}' clock, which is not running. \ + Either enable it in 'config.rcc' or change 'config.rcc.mux' to use another clock", + #peripheral, + #name + ) + }) } - fn gen_mux(&mut self, mux: &PeripheralRccRegister) -> TokenStream { + fn gen_mux(&mut self, peripheral: &str, mux: &PeripheralRccRegister) -> TokenStream { let ir = &self.rcc_registers.ir; let fieldset_name = mux.register.to_ascii_lowercase(); let fieldset = ir @@ -506,9 +514,9 @@ fn main() { for v in enumm.variants.iter().filter(|v| v.name != "DISABLE") { let variant_name = format_ident!("{}", v.name); let expr = if let Some(mux) = self.chained_muxes.get(&v.name) { - self.gen_mux(mux) + self.gen_mux(peripheral, mux) } else { - self.gen_clock(v.name) + self.gen_clock(peripheral, v.name) }; match_arms.extend(quote! { crate::pac::rcc::vals::#enum_name::#variant_name => #expr, @@ -586,8 +594,8 @@ fn main() { }; let clock_frequency = match &rcc.kernel_clock { - PeripheralRccKernelClock::Mux(mux) => clock_gen.gen_mux(mux), - PeripheralRccKernelClock::Clock(clock) => clock_gen.gen_clock(clock), + PeripheralRccKernelClock::Mux(mux) => clock_gen.gen_mux(p.name, mux), + PeripheralRccKernelClock::Clock(clock) => clock_gen.gen_clock(p.name, clock), }; // A refcount leak can result if the same field is shared by peripherals with different stop modes From a07702ba7bde8410b224e80afb6aa7a93f1fb375 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Wed, 3 Jul 2024 09:03:25 +0200 Subject: [PATCH 41/57] fix cancellation hanging on event never occuring - The end event is not triggered by stop and might therefor never occur - Clear the end_event after operation is done instead of disabled --- embassy-nrf/src/radio/ble.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/embassy-nrf/src/radio/ble.rs b/embassy-nrf/src/radio/ble.rs index 93003fb19..0fae54f52 100644 --- a/embassy-nrf/src/radio/ble.rs +++ b/embassy-nrf/src/radio/ble.rs @@ -347,12 +347,10 @@ impl<'d, T: Instance> Radio<'d, T> { trace!("radio drop: stopping"); r.intenclr.write(|w| w.end().clear()); - r.events_end.reset(); r.tasks_stop.write(|w| unsafe { w.bits(1) }); - // The docs don't explicitly mention any event to acknowledge the stop task - while r.events_end.read().bits() == 0 {} + r.events_end.reset(); trace!("radio drop: stopped"); }); @@ -382,7 +380,7 @@ impl<'d, T: Instance> Radio<'d, T> { .await; compiler_fence(Ordering::SeqCst); - r.events_disabled.reset(); // ACK + r.events_end.reset(); // ACK // Everthing ends fine, so it disable the drop drop.defuse(); From b07570fce5341b2b16aaa77ab1b1043362ed2e2c Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Wed, 3 Jul 2024 09:04:47 +0200 Subject: [PATCH 42/57] remove unused logging --- embassy-nrf/src/radio/ble.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/embassy-nrf/src/radio/ble.rs b/embassy-nrf/src/radio/ble.rs index 0fae54f52..4f0b0641f 100644 --- a/embassy-nrf/src/radio/ble.rs +++ b/embassy-nrf/src/radio/ble.rs @@ -335,8 +335,6 @@ impl<'d, T: Instance> Radio<'d, T> { } async fn trigger_and_wait_end(&mut self, trigger: impl FnOnce()) { - //self.trace_state(); - let r = T::regs(); let s = T::state(); @@ -366,7 +364,6 @@ impl<'d, T: Instance> Radio<'d, T> { // Trigger the transmission trigger(); - // self.trace_state(); // On poll check if interrupt happen poll_fn(|cx| { From 09cfa28a218a65cf756ffde4403806aa7e2613ed Mon Sep 17 00:00:00 2001 From: Andres Vahter Date: Wed, 3 Jul 2024 10:59:20 +0300 Subject: [PATCH 43/57] stm32g0: add adc_dma example --- examples/stm32g0/src/bin/adc_dma.rs | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 examples/stm32g0/src/bin/adc_dma.rs diff --git a/examples/stm32g0/src/bin/adc_dma.rs b/examples/stm32g0/src/bin/adc_dma.rs new file mode 100644 index 000000000..42d1e729b --- /dev/null +++ b/examples/stm32g0/src/bin/adc_dma.rs @@ -0,0 +1,44 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, AdcChannel as _, SampleTime}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +static mut DMA_BUF: [u16; 2] = [0; 2]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut read_buffer = unsafe { &mut DMA_BUF[..] }; + + let p = embassy_stm32::init(Default::default()); + + info!("Hello World!"); + + let mut adc = Adc::new(p.ADC1); + + let mut dma = p.DMA1_CH1; + let mut vrefint_channel = adc.enable_vrefint().degrade_adc(); + let mut pa0 = p.PA0.degrade_adc(); + + loop { + adc.read_async( + &mut dma, + [ + (&mut vrefint_channel, SampleTime::CYCLES160_5), + (&mut pa0, SampleTime::CYCLES160_5), + ] + .into_iter(), + &mut read_buffer, + ) + .await; + + let vrefint = read_buffer[0]; + let measured = read_buffer[1]; + info!("vrefint: {}", vrefint); + info!("measured: {}", measured); + Timer::after_millis(500).await; + } +} From f851081e09982a3edefd23082bb23143a0172464 Mon Sep 17 00:00:00 2001 From: Andres Vahter Date: Wed, 3 Jul 2024 14:39:10 +0300 Subject: [PATCH 44/57] stm32 adc: introduce blocking_read --- embassy-stm32/src/adc/g4.rs | 2 +- embassy-stm32/src/adc/ringbuffered_v2.rs | 20 ++++++------- embassy-stm32/src/adc/v2.rs | 2 +- embassy-stm32/src/adc/v3.rs | 30 ++++++++++++++++++-- embassy-stm32/src/adc/v4.rs | 30 ++++++++++++++++++-- examples/stm32f4/src/bin/adc.rs | 8 +++--- examples/stm32f4/src/bin/adc_dma.rs | 4 +-- examples/stm32f7/src/bin/adc.rs | 4 +-- examples/stm32g0/src/bin/adc.rs | 4 +-- examples/stm32g0/src/bin/adc_dma.rs | 2 +- examples/stm32g0/src/bin/adc_oversampling.rs | 2 +- examples/stm32g4/src/bin/adc.rs | 2 +- examples/stm32h7/src/bin/adc.rs | 4 +-- examples/stm32h7/src/bin/adc_dma.rs | 2 +- examples/stm32l4/src/bin/adc.rs | 2 +- examples/stm32u0/src/bin/adc.rs | 2 +- tests/stm32/src/bin/dac.rs | 4 +-- 17 files changed, 86 insertions(+), 38 deletions(-) diff --git a/embassy-stm32/src/adc/g4.rs b/embassy-stm32/src/adc/g4.rs index 6569361fe..c1e584f59 100644 --- a/embassy-stm32/src/adc/g4.rs +++ b/embassy-stm32/src/adc/g4.rs @@ -258,7 +258,7 @@ impl<'d, T: Instance> Adc<'d, T> { } /// Read an ADC pin. - pub fn read(&mut self, channel: &mut impl AdcChannel) -> u16 { + pub fn blocking_read(&mut self, channel: &mut impl AdcChannel) -> u16 { channel.setup(); self.read_channel(channel.channel()) diff --git a/embassy-stm32/src/adc/ringbuffered_v2.rs b/embassy-stm32/src/adc/ringbuffered_v2.rs index 92b34c3fd..3b064044e 100644 --- a/embassy-stm32/src/adc/ringbuffered_v2.rs +++ b/embassy-stm32/src/adc/ringbuffered_v2.rs @@ -97,10 +97,10 @@ impl<'d, T: Instance> Adc<'d, T> { /// The length of the `dma_buf` should be a multiple of the ADC channel count. /// For example, if 3 channels are measured, its length can be 3 * 40 = 120 measurements. /// - /// `read_exact` method is used to read out measurements from the DMA ring buffer, and its buffer should be exactly half of the `dma_buf` length. - /// It is critical to call `read_exact` frequently to prevent DMA buffer overrun. + /// `read` method is used to read out measurements from the DMA ring buffer, and its buffer should be exactly half of the `dma_buf` length. + /// It is critical to call `read` frequently to prevent DMA buffer overrun. /// - /// [`read_exact`]: #method.read_exact + /// [`read`]: #method.read pub fn into_ring_buffered( self, dma: impl Peripheral

> + 'd, @@ -331,7 +331,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { /// /// Receive in the background is terminated if an error is returned. /// It must then manually be started again by calling `start()` or by re-calling `read()`. - pub fn read(&mut self, buf: &mut [u16; N]) -> Result { + pub fn blocking_read(&mut self, buf: &mut [u16; N]) -> Result { let r = T::regs(); // Start background receive if it was not already started @@ -362,14 +362,14 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { /// This method fills the provided `measurements` array with ADC readings from the DMA buffer. /// The length of the `measurements` array should be exactly half of the DMA buffer length. Because interrupts are only generated if half or full DMA transfer completes. /// - /// Each call to `read_exact` will populate the `measurements` array in the same order as the channels defined with `set_sample_sequence`. + /// Each call to `read` will populate the `measurements` array in the same order as the channels defined with `set_sample_sequence`. /// There will be many sequences worth of measurements in this array because it only returns if at least half of the DMA buffer is filled. /// For example if 3 channels are sampled `measurements` contain: `[sq0 sq1 sq3 sq0 sq1 sq3 sq0 sq1 sq3 sq0 sq1 sq3..]`. /// - /// If an error is returned, it indicates a DMA overrun, and the process must be restarted by calling `start` or `read_exact` again. + /// If an error is returned, it indicates a DMA overrun, and the process must be restarted by calling `start` or `read` again. /// /// By default, the ADC fills the DMA buffer as quickly as possible. To control the sample rate, call `teardown_adc` after each readout, and then start the DMA again at the desired interval. - /// Note that even if using `teardown_adc` to control the sample rate, with each call to `read_exact`, measurements equivalent to half the size of the DMA buffer are still collected. + /// Note that even if using `teardown_adc` to control the sample rate, with each call to `read`, measurements equivalent to half the size of the DMA buffer are still collected. /// /// Example: /// ```rust,ignore @@ -383,7 +383,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { /// /// let mut measurements = [0u16; DMA_BUF_LEN / 2]; /// loop { - /// match adc.read_exact(&mut measurements).await { + /// match adc.read(&mut measurements).await { /// Ok(_) => { /// defmt::info!("adc1: {}", measurements); /// // Only needed to manually control sample rate. @@ -391,7 +391,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { /// } /// Err(e) => { /// defmt::warn!("Error: {:?}", e); - /// // DMA overrun, next call to `read_exact` restart ADC. + /// // DMA overrun, next call to `read` restarts ADC. /// } /// } /// @@ -404,7 +404,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { /// [`set_sample_sequence`]: #method.set_sample_sequence /// [`teardown_adc`]: #method.teardown_adc /// [`start`]: #method.start - pub async fn read_exact(&mut self, measurements: &mut [u16; N]) -> Result { + pub async fn read(&mut self, measurements: &mut [u16; N]) -> Result { assert_eq!( self.ring_buf.capacity() / 2, N, diff --git a/embassy-stm32/src/adc/v2.rs b/embassy-stm32/src/adc/v2.rs index ddeb7ea79..842a5ee6d 100644 --- a/embassy-stm32/src/adc/v2.rs +++ b/embassy-stm32/src/adc/v2.rs @@ -178,7 +178,7 @@ where T::regs().dr().read().0 as u16 } - pub fn read(&mut self, channel: &mut impl AdcChannel) -> u16 { + pub fn blocking_read(&mut self, channel: &mut impl AdcChannel) -> u16 { channel.setup(); // Configure ADC diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index c876bffea..9441e42ff 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -254,12 +254,36 @@ impl<'d, T: Instance> Adc<'d, T> { } /// Read an ADC channel. - pub fn read(&mut self, channel: &mut impl AdcChannel) -> u16 { + pub fn blocking_read(&mut self, channel: &mut impl AdcChannel) -> u16 { self.read_channel(channel) } - /// Asynchronously read from sequence of ADC channels. - pub async fn read_async( + /// Read one or multiple ADC channels using DMA. + /// + /// `sequence` iterator and `readings` must have the same length. + /// + /// Example + /// ```rust,ignore + /// use embassy_stm32::adc::{Adc, AdcChannel} + /// + /// let mut adc = Adc::new(p.ADC1); + /// let mut adc_pin0 = p.PA0.degrade_adc(); + /// let mut adc_pin1 = p.PA1.degrade_adc(); + /// let mut measurements = [0u16; 2]; + /// + /// adc.read_async( + /// p.DMA1_CH2, + /// [ + /// (&mut *adc_pin0, SampleTime::CYCLES160_5), + /// (&mut *adc_pin1, SampleTime::CYCLES160_5), + /// ] + /// .into_iter(), + /// &mut measurements, + /// ) + /// .await; + /// defmt::info!("measurements: {}", measurements); + /// ``` + pub async fn read( &mut self, rx_dma: &mut impl RxDma, sequence: impl ExactSizeIterator, SampleTime)>, diff --git a/embassy-stm32/src/adc/v4.rs b/embassy-stm32/src/adc/v4.rs index 7db6fa4a2..63b5b58ea 100644 --- a/embassy-stm32/src/adc/v4.rs +++ b/embassy-stm32/src/adc/v4.rs @@ -318,12 +318,36 @@ impl<'d, T: Instance> Adc<'d, T> { } /// Read an ADC channel. - pub fn read(&mut self, channel: &mut impl AdcChannel) -> u16 { + pub fn blocking_read(&mut self, channel: &mut impl AdcChannel) -> u16 { self.read_channel(channel) } - /// Asynchronously read from sequence of ADC channels. - pub async fn read_async( + /// Read one or multiple ADC channels using DMA. + /// + /// `sequence` iterator and `readings` must have the same length. + /// + /// Example + /// ```rust,ignore + /// use embassy_stm32::adc::{Adc, AdcChannel} + /// + /// let mut adc = Adc::new(p.ADC1); + /// let mut adc_pin0 = p.PA0.degrade_adc(); + /// let mut adc_pin2 = p.PA2.degrade_adc(); + /// let mut measurements = [0u16; 2]; + /// + /// adc.read_async( + /// p.DMA2_CH0, + /// [ + /// (&mut *adc_pin0, SampleTime::CYCLES112), + /// (&mut *adc_pin2, SampleTime::CYCLES112), + /// ] + /// .into_iter(), + /// &mut measurements, + /// ) + /// .await; + /// defmt::info!("measurements: {}", measurements); + /// ``` + pub async fn read( &mut self, rx_dma: &mut impl RxDma, sequence: impl ExactSizeIterator, SampleTime)>, diff --git a/examples/stm32f4/src/bin/adc.rs b/examples/stm32f4/src/bin/adc.rs index 9473b7b7f..423d29225 100644 --- a/examples/stm32f4/src/bin/adc.rs +++ b/examples/stm32f4/src/bin/adc.rs @@ -23,7 +23,7 @@ async fn main(_spawner: Spawner) { // Startup delay can be combined to the maximum of either delay.delay_us(Temperature::start_time_us().max(VrefInt::start_time_us())); - let vrefint_sample = adc.read(&mut vrefint); + let vrefint_sample = adc.blocking_read(&mut vrefint); let convert_to_millivolts = |sample| { // From http://www.st.com/resource/en/datasheet/DM00071990.pdf @@ -50,16 +50,16 @@ async fn main(_spawner: Spawner) { loop { // Read pin - let v = adc.read(&mut pin); + let v = adc.blocking_read(&mut pin); info!("PC1: {} ({} mV)", v, convert_to_millivolts(v)); // Read internal temperature - let v = adc.read(&mut temp); + let v = adc.blocking_read(&mut temp); let celcius = convert_to_celcius(v); info!("Internal temp: {} ({} C)", v, celcius); // Read internal voltage reference - let v = adc.read(&mut vrefint); + let v = adc.blocking_read(&mut vrefint); info!("VrefInt: {}", v); Timer::after_millis(100).await; diff --git a/examples/stm32f4/src/bin/adc_dma.rs b/examples/stm32f4/src/bin/adc_dma.rs index 992bed573..43a761e6d 100644 --- a/examples/stm32f4/src/bin/adc_dma.rs +++ b/examples/stm32f4/src/bin/adc_dma.rs @@ -44,7 +44,7 @@ async fn adc_task(mut p: Peripherals) { let _ = adc.start(); let _ = adc2.start(); loop { - match adc.read_exact(&mut buffer1).await { + match adc.read(&mut buffer1).await { Ok(_data) => { let toc = Instant::now(); info!( @@ -62,7 +62,7 @@ async fn adc_task(mut p: Peripherals) { } } - match adc2.read_exact(&mut buffer2).await { + match adc2.read(&mut buffer2).await { Ok(_data) => { let toc = Instant::now(); info!( diff --git a/examples/stm32f7/src/bin/adc.rs b/examples/stm32f7/src/bin/adc.rs index 641157960..6689e3b5d 100644 --- a/examples/stm32f7/src/bin/adc.rs +++ b/examples/stm32f7/src/bin/adc.rs @@ -16,7 +16,7 @@ async fn main(_spawner: Spawner) { let mut pin = p.PA3; let mut vrefint = adc.enable_vrefint(); - let vrefint_sample = adc.read(&mut vrefint); + let vrefint_sample = adc.blocking_read(&mut vrefint); let convert_to_millivolts = |sample| { // From http://www.st.com/resource/en/datasheet/DM00273119.pdf // 6.3.27 Reference voltage @@ -26,7 +26,7 @@ async fn main(_spawner: Spawner) { }; loop { - let v = adc.read(&mut pin); + let v = adc.blocking_read(&mut pin); info!("--> {} - {} mV", v, convert_to_millivolts(v)); Timer::after_millis(100).await; } diff --git a/examples/stm32g0/src/bin/adc.rs b/examples/stm32g0/src/bin/adc.rs index a35119e3d..6c7f3b48a 100644 --- a/examples/stm32g0/src/bin/adc.rs +++ b/examples/stm32g0/src/bin/adc.rs @@ -17,7 +17,7 @@ async fn main(_spawner: Spawner) { let mut pin = p.PA1; let mut vrefint = adc.enable_vrefint(); - let vrefint_sample = adc.read(&mut vrefint); + let vrefint_sample = adc.blocking_read(&mut vrefint); let convert_to_millivolts = |sample| { // From https://www.st.com/resource/en/datasheet/stm32g031g8.pdf // 6.3.3 Embedded internal reference voltage @@ -27,7 +27,7 @@ async fn main(_spawner: Spawner) { }; loop { - let v = adc.read(&mut pin); + let v = adc.blocking_read(&mut pin); info!("--> {} - {} mV", v, convert_to_millivolts(v)); Timer::after_millis(100).await; } diff --git a/examples/stm32g0/src/bin/adc_dma.rs b/examples/stm32g0/src/bin/adc_dma.rs index 42d1e729b..3713e5a21 100644 --- a/examples/stm32g0/src/bin/adc_dma.rs +++ b/examples/stm32g0/src/bin/adc_dma.rs @@ -24,7 +24,7 @@ async fn main(_spawner: Spawner) { let mut pa0 = p.PA0.degrade_adc(); loop { - adc.read_async( + adc.read( &mut dma, [ (&mut vrefint_channel, SampleTime::CYCLES160_5), diff --git a/examples/stm32g0/src/bin/adc_oversampling.rs b/examples/stm32g0/src/bin/adc_oversampling.rs index 3c31eb206..9c5dd872a 100644 --- a/examples/stm32g0/src/bin/adc_oversampling.rs +++ b/examples/stm32g0/src/bin/adc_oversampling.rs @@ -36,7 +36,7 @@ async fn main(_spawner: Spawner) { adc.oversampling_enable(true); loop { - let v = adc.read(&mut pin); + let v = adc.blocking_read(&mut pin); info!("--> {} ", v); //max 65520 = 0xFFF0 Timer::after_millis(100).await; } diff --git a/examples/stm32g4/src/bin/adc.rs b/examples/stm32g4/src/bin/adc.rs index 3de38cbd6..adca846d8 100644 --- a/examples/stm32g4/src/bin/adc.rs +++ b/examples/stm32g4/src/bin/adc.rs @@ -32,7 +32,7 @@ async fn main(_spawner: Spawner) { adc.set_sample_time(SampleTime::CYCLES24_5); loop { - let measured = adc.read(&mut p.PA7); + let measured = adc.blocking_read(&mut p.PA7); info!("measured: {}", measured); Timer::after_millis(500).await; } diff --git a/examples/stm32h7/src/bin/adc.rs b/examples/stm32h7/src/bin/adc.rs index e9a857a74..98504ddf6 100644 --- a/examples/stm32h7/src/bin/adc.rs +++ b/examples/stm32h7/src/bin/adc.rs @@ -51,9 +51,9 @@ async fn main(_spawner: Spawner) { let mut vrefint_channel = adc.enable_vrefint(); loop { - let vrefint = adc.read(&mut vrefint_channel); + let vrefint = adc.blocking_read(&mut vrefint_channel); info!("vrefint: {}", vrefint); - let measured = adc.read(&mut p.PC0); + let measured = adc.blocking_read(&mut p.PC0); info!("measured: {}", measured); Timer::after_millis(500).await; } diff --git a/examples/stm32h7/src/bin/adc_dma.rs b/examples/stm32h7/src/bin/adc_dma.rs index 6c0240453..0b905d227 100644 --- a/examples/stm32h7/src/bin/adc_dma.rs +++ b/examples/stm32h7/src/bin/adc_dma.rs @@ -56,7 +56,7 @@ async fn main(_spawner: Spawner) { let mut pc0 = p.PC0.degrade_adc(); loop { - adc.read_async( + adc.read( &mut dma, [ (&mut vrefint_channel, SampleTime::CYCLES387_5), diff --git a/examples/stm32l4/src/bin/adc.rs b/examples/stm32l4/src/bin/adc.rs index 7a89334e0..c557ac6d7 100644 --- a/examples/stm32l4/src/bin/adc.rs +++ b/examples/stm32l4/src/bin/adc.rs @@ -23,7 +23,7 @@ fn main() -> ! { let mut channel = p.PC0; loop { - let v = adc.read(&mut channel); + let v = adc.blocking_read(&mut channel); info!("--> {}", v); } } diff --git a/examples/stm32u0/src/bin/adc.rs b/examples/stm32u0/src/bin/adc.rs index 4410448f1..c8252e4e1 100644 --- a/examples/stm32u0/src/bin/adc.rs +++ b/examples/stm32u0/src/bin/adc.rs @@ -23,7 +23,7 @@ fn main() -> ! { let mut channel = p.PC0; loop { - let v = adc.read(&mut channel); + let v = adc.blocking_read(&mut channel); info!("--> {}", v); embassy_time::block_for(Duration::from_millis(200)); } diff --git a/tests/stm32/src/bin/dac.rs b/tests/stm32/src/bin/dac.rs index 06501ab14..86a68c530 100644 --- a/tests/stm32/src/bin/dac.rs +++ b/tests/stm32/src/bin/dac.rs @@ -38,7 +38,7 @@ async fn main(_spawner: Spawner) { dac.set(Value::Bit8(0)); // Now wait a little to obtain a stable value Timer::after_millis(30).await; - let offset = adc.read(&mut adc_pin); + let offset = adc.blocking_read(&mut adc_pin); for v in 0..=255 { // First set the DAC output value @@ -49,7 +49,7 @@ async fn main(_spawner: Spawner) { Timer::after_millis(30).await; // Need to steal the peripherals here because PA4 is obviously in use already - let measured = adc.read(&mut unsafe { embassy_stm32::Peripherals::steal() }.PA4); + let measured = adc.blocking_read(&mut unsafe { embassy_stm32::Peripherals::steal() }.PA4); // Calibrate and normalize the measurement to get close to the dac_output_val let measured_normalized = ((measured as i32 - offset as i32) / normalization_factor) as i16; From 4ecdf31f9b887c4427ff06b70744ef438502f989 Mon Sep 17 00:00:00 2001 From: Dickless <37750743+Dicklessgreat@users.noreply.github.com> Date: Fri, 5 Jul 2024 02:33:30 +0900 Subject: [PATCH 45/57] add SAI example --- examples/stm32h7/Cargo.toml | 1 + examples/stm32h7/src/bin/sai.rs | 189 ++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 examples/stm32h7/src/bin/sai.rs diff --git a/examples/stm32h7/Cargo.toml b/examples/stm32h7/Cargo.toml index 0584f3916..78343b74f 100644 --- a/examples/stm32h7/Cargo.toml +++ b/examples/stm32h7/Cargo.toml @@ -34,6 +34,7 @@ stm32-fmc = "0.3.0" embedded-storage = "0.3.1" static_cell = "2" chrono = { version = "^0.4", default-features = false } +grounded = "0.2.0" # cargo build/run [profile.dev] diff --git a/examples/stm32h7/src/bin/sai.rs b/examples/stm32h7/src/bin/sai.rs new file mode 100644 index 000000000..ef8479979 --- /dev/null +++ b/examples/stm32h7/src/bin/sai.rs @@ -0,0 +1,189 @@ +//! Daisy Seed rev.7(with PCM3060 codec) +//! https://electro-smith.com/products/daisy-seed +#![no_std] +#![no_main] + +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32 as hal; +use grounded::uninit::GroundedArrayCell; +use hal::rcc::*; +use hal::sai::*; +use hal::time::Hertz; +use panic_probe as _; + +const BLOCK_LENGTH: usize = 32; // 32 samples +const HALF_DMA_BUFFER_LENGTH: usize = BLOCK_LENGTH * 2; // 2 channels +const DMA_BUFFER_LENGTH: usize = HALF_DMA_BUFFER_LENGTH * 2; // 2 half-blocks +const SAMPLE_RATE: u32 = 48000; + +//DMA buffer must be in special region. Refer https://embassy.dev/book/#_stm32_bdma_only_working_out_of_some_ram_regions +#[link_section = ".sram1_bss"] +static mut TX_BUFFER: GroundedArrayCell = GroundedArrayCell::uninit(); +#[link_section = ".sram1_bss"] +static mut RX_BUFFER: GroundedArrayCell = GroundedArrayCell::uninit(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = hal::Config::default(); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL200, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV5), + divr: Some(PllDiv::DIV2), + }); + config.rcc.pll3 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV6, + mul: PllMul::MUL295, + divp: Some(PllDiv::DIV16), + divq: Some(PllDiv::DIV4), + divr: Some(PllDiv::DIV32), + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.mux.sai1sel = hal::pac::rcc::vals::Saisel::PLL3_P; + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.hse = Some(Hse { + freq: Hertz::mhz(16), + mode: HseMode::Oscillator, + }); + + let p = hal::init(config); + + let (sub_block_tx, sub_block_rx) = hal::sai::split_subblocks(p.SAI1); + let kernel_clock = hal::rcc::frequency::().0; + let mclk_div = mclk_div_from_u8((kernel_clock / (SAMPLE_RATE * 256)) as u8); + + let mut tx_config = hal::sai::Config::default(); + tx_config.mode = Mode::Master; + tx_config.tx_rx = TxRx::Transmitter; + tx_config.sync_output = true; + tx_config.clock_strobe = ClockStrobe::Falling; + tx_config.master_clock_divider = mclk_div; + tx_config.stereo_mono = StereoMono::Stereo; + tx_config.data_size = DataSize::Data24; + tx_config.bit_order = BitOrder::MsbFirst; + tx_config.frame_sync_polarity = FrameSyncPolarity::ActiveHigh; + tx_config.frame_sync_offset = FrameSyncOffset::OnFirstBit; + tx_config.frame_length = 64; + tx_config.frame_sync_active_level_length = embassy_stm32::sai::word::U7(32); + tx_config.fifo_threshold = FifoThreshold::Quarter; + + let mut rx_config = tx_config.clone(); + rx_config.mode = Mode::Slave; + rx_config.tx_rx = TxRx::Receiver; + rx_config.sync_input = SyncInput::Internal; + rx_config.clock_strobe = ClockStrobe::Rising; + rx_config.sync_output = false; + + let tx_buffer: &mut [u32] = unsafe { + TX_BUFFER.initialize_all_copied(0); + let (ptr, len) = TX_BUFFER.get_ptr_len(); + core::slice::from_raw_parts_mut(ptr, len) + }; + + let mut sai_transmitter = Sai::new_asynchronous_with_mclk( + sub_block_tx, + p.PE5, + p.PE6, + p.PE4, + p.PE2, + p.DMA1_CH0, + tx_buffer, + tx_config, + ); + + let rx_buffer: &mut [u32] = unsafe { + RX_BUFFER.initialize_all_copied(0); + let (ptr, len) = RX_BUFFER.get_ptr_len(); + core::slice::from_raw_parts_mut(ptr, len) + }; + + let mut sai_receiver = + Sai::new_synchronous(sub_block_rx, p.PE3, p.DMA1_CH1, rx_buffer, rx_config); + + sai_receiver.start(); + sai_transmitter.start(); + + let mut buf = [0u32; HALF_DMA_BUFFER_LENGTH]; + + loop { + sai_receiver.read(&mut buf).await.unwrap(); + sai_transmitter.write(&buf).await.unwrap(); + } +} + +const fn mclk_div_from_u8(v: u8) -> MasterClockDivider { + match v { + 1 => MasterClockDivider::Div1, + 2 => MasterClockDivider::Div2, + 3 => MasterClockDivider::Div3, + 4 => MasterClockDivider::Div4, + 5 => MasterClockDivider::Div5, + 6 => MasterClockDivider::Div6, + 7 => MasterClockDivider::Div7, + 8 => MasterClockDivider::Div8, + 9 => MasterClockDivider::Div9, + 10 => MasterClockDivider::Div10, + 11 => MasterClockDivider::Div11, + 12 => MasterClockDivider::Div12, + 13 => MasterClockDivider::Div13, + 14 => MasterClockDivider::Div14, + 15 => MasterClockDivider::Div15, + 16 => MasterClockDivider::Div16, + 17 => MasterClockDivider::Div17, + 18 => MasterClockDivider::Div18, + 19 => MasterClockDivider::Div19, + 20 => MasterClockDivider::Div20, + 21 => MasterClockDivider::Div21, + 22 => MasterClockDivider::Div22, + 23 => MasterClockDivider::Div23, + 24 => MasterClockDivider::Div24, + 25 => MasterClockDivider::Div25, + 26 => MasterClockDivider::Div26, + 27 => MasterClockDivider::Div27, + 28 => MasterClockDivider::Div28, + 29 => MasterClockDivider::Div29, + 30 => MasterClockDivider::Div30, + 31 => MasterClockDivider::Div31, + 32 => MasterClockDivider::Div32, + 33 => MasterClockDivider::Div33, + 34 => MasterClockDivider::Div34, + 35 => MasterClockDivider::Div35, + 36 => MasterClockDivider::Div36, + 37 => MasterClockDivider::Div37, + 38 => MasterClockDivider::Div38, + 39 => MasterClockDivider::Div39, + 40 => MasterClockDivider::Div40, + 41 => MasterClockDivider::Div41, + 42 => MasterClockDivider::Div42, + 43 => MasterClockDivider::Div43, + 44 => MasterClockDivider::Div44, + 45 => MasterClockDivider::Div45, + 46 => MasterClockDivider::Div46, + 47 => MasterClockDivider::Div47, + 48 => MasterClockDivider::Div48, + 49 => MasterClockDivider::Div49, + 50 => MasterClockDivider::Div50, + 51 => MasterClockDivider::Div51, + 52 => MasterClockDivider::Div52, + 53 => MasterClockDivider::Div53, + 54 => MasterClockDivider::Div54, + 55 => MasterClockDivider::Div55, + 56 => MasterClockDivider::Div56, + 57 => MasterClockDivider::Div57, + 58 => MasterClockDivider::Div58, + 59 => MasterClockDivider::Div59, + 60 => MasterClockDivider::Div60, + 61 => MasterClockDivider::Div61, + 62 => MasterClockDivider::Div62, + 63 => MasterClockDivider::Div63, + _ => panic!(), + } +} From 49546abfec8dd8a9a610c82ec5ef11adba8b5f04 Mon Sep 17 00:00:00 2001 From: Dickless <37750743+Dicklessgreat@users.noreply.github.com> Date: Fri, 5 Jul 2024 03:17:04 +0900 Subject: [PATCH 46/57] rustfmt --- examples/stm32h7/src/bin/sai.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/stm32h7/src/bin/sai.rs b/examples/stm32h7/src/bin/sai.rs index ef8479979..7f57c4949 100644 --- a/examples/stm32h7/src/bin/sai.rs +++ b/examples/stm32h7/src/bin/sai.rs @@ -105,8 +105,7 @@ async fn main(_spawner: Spawner) { core::slice::from_raw_parts_mut(ptr, len) }; - let mut sai_receiver = - Sai::new_synchronous(sub_block_rx, p.PE3, p.DMA1_CH1, rx_buffer, rx_config); + let mut sai_receiver = Sai::new_synchronous(sub_block_rx, p.PE3, p.DMA1_CH1, rx_buffer, rx_config); sai_receiver.start(); sai_transmitter.start(); From 4f649caa8155c5031592d74c2b5e94f400332a88 Mon Sep 17 00:00:00 2001 From: Dickless <37750743+Dicklessgreat@users.noreply.github.com> Date: Fri, 5 Jul 2024 03:29:25 +0900 Subject: [PATCH 47/57] cargo +nightly fmt --- examples/stm32h7/src/bin/sai.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/stm32h7/src/bin/sai.rs b/examples/stm32h7/src/bin/sai.rs index 7f57c4949..f6735e235 100644 --- a/examples/stm32h7/src/bin/sai.rs +++ b/examples/stm32h7/src/bin/sai.rs @@ -3,14 +3,12 @@ #![no_std] #![no_main] -use defmt_rtt as _; use embassy_executor::Spawner; -use embassy_stm32 as hal; use grounded::uninit::GroundedArrayCell; use hal::rcc::*; use hal::sai::*; use hal::time::Hertz; -use panic_probe as _; +use {defmt_rtt as _, embassy_stm32 as hal, panic_probe as _}; const BLOCK_LENGTH: usize = 32; // 32 samples const HALF_DMA_BUFFER_LENGTH: usize = BLOCK_LENGTH * 2; // 2 channels From b90eef293ba36bfeaba85ec5f8396021fd282130 Mon Sep 17 00:00:00 2001 From: Dickless <37750743+Dicklessgreat@users.noreply.github.com> Date: Sat, 6 Jul 2024 17:36:22 +0900 Subject: [PATCH 48/57] [#2905 #2904] Replaced static raw array with GroundedArrayCell --- examples/stm32h7/src/bin/spi_bdma.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/examples/stm32h7/src/bin/spi_bdma.rs b/examples/stm32h7/src/bin/spi_bdma.rs index b2e941078..65f498506 100644 --- a/examples/stm32h7/src/bin/spi_bdma.rs +++ b/examples/stm32h7/src/bin/spi_bdma.rs @@ -10,18 +10,24 @@ use embassy_executor::Executor; use embassy_stm32::mode::Async; use embassy_stm32::time::mhz; use embassy_stm32::{spi, Config}; +use grounded::uninit::GroundedArrayCell; use heapless::String; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; // Defined in memory.x #[link_section = ".ram_d3"] -static mut RAM_D3: [u8; 64 * 1024] = [0u8; 64 * 1024]; +static mut RAM_D3: GroundedArrayCell = GroundedArrayCell::uninit(); #[embassy_executor::task] async fn main_task(mut spi: spi::Spi<'static, Async>) { - let read_buffer = unsafe { &mut RAM_D3[0..128] }; - let write_buffer = unsafe { &mut RAM_D3[128..256] }; + let (read_buffer, write_buffer) = unsafe { + RAM_D3.initialize_all_copied(0); + ( + RAM_D3.get_subslice_mut_unchecked(0, 128), + RAM_D3.get_subslice_mut_unchecked(128, 128), + ) + }; for n in 0u32.. { let mut write: String<128> = String::new(); From 3408e1ddbf7076e119307ae04dce4a9ddf922f34 Mon Sep 17 00:00:00 2001 From: Dickless <37750743+Dicklessgreat@users.noreply.github.com> Date: Sun, 7 Jul 2024 04:39:39 +0900 Subject: [PATCH 49/57] Fixed to reserve as much space as it uses. --- examples/stm32h7/src/bin/spi_bdma.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/stm32h7/src/bin/spi_bdma.rs b/examples/stm32h7/src/bin/spi_bdma.rs index 65f498506..43fb6b41c 100644 --- a/examples/stm32h7/src/bin/spi_bdma.rs +++ b/examples/stm32h7/src/bin/spi_bdma.rs @@ -17,7 +17,7 @@ use {defmt_rtt as _, panic_probe as _}; // Defined in memory.x #[link_section = ".ram_d3"] -static mut RAM_D3: GroundedArrayCell = GroundedArrayCell::uninit(); +static mut RAM_D3: GroundedArrayCell = GroundedArrayCell::uninit(); #[embassy_executor::task] async fn main_task(mut spi: spi::Spi<'static, Async>) { From 87f66343493a5ae99f0f9b27602b96524111c94a Mon Sep 17 00:00:00 2001 From: kalkyl Date: Mon, 8 Jul 2024 13:58:36 +0200 Subject: [PATCH 50/57] Add example for sharing things between tasks --- examples/rp/src/bin/sharing.rs | 140 +++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 examples/rp/src/bin/sharing.rs diff --git a/examples/rp/src/bin/sharing.rs b/examples/rp/src/bin/sharing.rs new file mode 100644 index 000000000..0761500ef --- /dev/null +++ b/examples/rp/src/bin/sharing.rs @@ -0,0 +1,140 @@ +//! This example shows some common strategies for sharing resources between tasks. + +#![no_std] +#![no_main] + +use core::cell::{Cell, RefCell}; +use core::sync::atomic::{AtomicU32, Ordering}; + +use cortex_m_rt::entry; +use defmt::info; +use embassy_executor::{Executor, InterruptExecutor}; +use embassy_rp::clocks::RoscRng; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_rp::peripherals::UART0; +use embassy_rp::uart::{self, InterruptHandler, UartTx}; +use embassy_rp::{bind_interrupts, interrupt}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::{blocking_mutex, mutex}; +use embassy_time::{Duration, Ticker}; +use rand::RngCore; +use static_cell::{ConstStaticCell, StaticCell}; +use {defmt_rtt as _, panic_probe as _}; + +type UartMutex = mutex::Mutex>; + +struct MyType { + inner: u32, +} + +static EXECUTOR_HI: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +// Use Atomics for simple values +static ATOMIC: AtomicU32 = AtomicU32::new(0); + +// Use blocking Mutex with Cell/RefCell for sharing non-async things +static MUTEX_BLOCKING: blocking_mutex::Mutex> = + blocking_mutex::Mutex::new(RefCell::new(MyType { inner: 0 })); + +bind_interrupts!(struct Irqs { + UART0_IRQ => InterruptHandler; +}); + +#[interrupt] +unsafe fn SWI_IRQ_0() { + EXECUTOR_HI.on_interrupt() +} + +#[entry] +fn main() -> ! { + let p = embassy_rp::init(Default::default()); + info!("Here we go!"); + + let uart = UartTx::new(p.UART0, p.PIN_0, p.DMA_CH0, uart::Config::default()); + // Use the async Mutex for sharing async things (built-in interior mutability) + static UART: StaticCell = StaticCell::new(); + let uart = UART.init(mutex::Mutex::new(uart)); + + // High-priority executor: runs in interrupt mode + interrupt::SWI_IRQ_0.set_priority(Priority::P3); + let spawner = EXECUTOR_HI.start(interrupt::SWI_IRQ_0); + spawner.must_spawn(task_a(uart)); + + // Low priority executor: runs in thread mode + let executor = EXECUTOR_LOW.init(Executor::new()); + executor.run(|spawner| { + // No Mutex needed when sharing between tasks running on the same executor + + // Use Cell for Copy-types + static CELL: ConstStaticCell> = ConstStaticCell::new(Cell::new([0; 4])); + let cell = CELL.take(); + + // Use RefCell for &mut access + static REF_CELL: ConstStaticCell> = ConstStaticCell::new(RefCell::new(MyType { inner: 0 })); + let ref_cell = REF_CELL.take(); + + spawner.must_spawn(task_b(uart, cell, ref_cell)); + spawner.must_spawn(task_c(cell, ref_cell)); + }); +} + +#[embassy_executor::task] +async fn task_a(uart: &'static UartMutex) { + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + let random = RoscRng.next_u32(); + + { + let mut uart = uart.lock().await; + uart.write(b"task a").await.unwrap(); + // The uart lock is released when it goes out of scope + } + + ATOMIC.store(random, Ordering::Relaxed); + + MUTEX_BLOCKING.lock(|x| x.borrow_mut().inner = random); + + ticker.next().await; + } +} + +#[embassy_executor::task] +async fn task_b(uart: &'static UartMutex, cell: &'static Cell<[u8; 4]>, ref_cell: &'static RefCell) { + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + let random = RoscRng.next_u32(); + + uart.lock().await.write(b"task b").await.unwrap(); + + cell.set(random.to_be_bytes()); + + ref_cell.borrow_mut().inner = random; + + ticker.next().await; + } +} + +#[embassy_executor::task] +async fn task_c(cell: &'static Cell<[u8; 4]>, ref_cell: &'static RefCell) { + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + info!("======================="); + + let atomic = ATOMIC.load(Ordering::Relaxed); + info!("atomic: {}", atomic); + + MUTEX_BLOCKING.lock(|x| { + let val = x.borrow().inner; + info!("blocking mutex: {}", val); + }); + + let cell_val = cell.get(); + info!("cell: {:?}", cell_val); + + let ref_cell_val = ref_cell.borrow().inner; + info!("ref_cell: {:?}", ref_cell_val); + + ticker.next().await; + } +} From 028ca55f9ca3bfa2e4aa99b16bc0e0e29241fe70 Mon Sep 17 00:00:00 2001 From: kalkyl Date: Mon, 8 Jul 2024 17:16:35 +0200 Subject: [PATCH 51/57] Add more docs and cross-links --- docs/pages/faq.adoc | 3 ++- docs/pages/sharing_peripherals.adoc | 2 ++ examples/rp/src/bin/sharing.rs | 22 ++++++++++++++++------ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/docs/pages/faq.adoc b/docs/pages/faq.adoc index a2f56a539..fc1316062 100644 --- a/docs/pages/faq.adoc +++ b/docs/pages/faq.adoc @@ -352,7 +352,8 @@ There are two main ways to handle concurrency in Embassy: In general, either of these approaches will work. The main differences of these approaches are: -When using **separate tasks**, each task needs its own RAM allocation, so there's a little overhead for each task, so one task that does three things will likely be a little bit smaller than three tasks that do one thing (not a lot, probably a couple dozen bytes). In contrast, with **multiple futures in one task**, you don't need multiple task allocations, and it will generally be easier to share data, or use borrowed resources, inside of a single task. +When using **separate tasks**, each task needs its own RAM allocation, so there's a little overhead for each task, so one task that does three things will likely be a little bit smaller than three tasks that do one thing (not a lot, probably a couple dozen bytes). In contrast, with **multiple futures in one task**, you don't need multiple task allocations, and it will generally be easier to share data, or use borrowed resources, inside of a single task. +An example showcasing some methods for sharing things between tasks link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/sharing.rs[can be found here]. But when it comes to "waking" tasks, for example when a data transfer is complete or a button is pressed, it's faster to wake a dedicated task, because that task does not need to check which future is actually ready. `join` and `select` must check ALL of the futures they are managing to see which one (or which ones) are ready to do more work. This is because all Rust executors (like Embassy or Tokio) only have the ability to wake tasks, not specific futures. This means you will use slightly less CPU time juggling futures when using dedicated tasks. diff --git a/docs/pages/sharing_peripherals.adoc b/docs/pages/sharing_peripherals.adoc index 6bcd56b01..ebd899c4e 100644 --- a/docs/pages/sharing_peripherals.adoc +++ b/docs/pages/sharing_peripherals.adoc @@ -126,3 +126,5 @@ async fn toggle_led(control: Sender<'static, ThreadModeRawMutex, LedState, 64>, This example replaces the Mutex with a Channel, and uses another task (the main loop) to drive the LED. The advantage of this approach is that only a single task references the peripheral, separating concerns. However, using a Mutex has a lower overhead and might be necessary if you need to ensure that the operation is completed before continuing to do other work in your task. + +An example showcasing more methods for sharing link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/sharing.rs[can be found here]. \ No newline at end of file diff --git a/examples/rp/src/bin/sharing.rs b/examples/rp/src/bin/sharing.rs index 0761500ef..5416e20ce 100644 --- a/examples/rp/src/bin/sharing.rs +++ b/examples/rp/src/bin/sharing.rs @@ -1,4 +1,14 @@ //! This example shows some common strategies for sharing resources between tasks. +//! +//! We demonstrate five different ways of sharing, covering different use cases: +//! - Atomics: This method is used for simple values, such as bool and u8..u32 +//! - Blocking Mutex: This is used for sharing non-async things, using Cell/RefCell for interior mutability. +//! - Async Mutex: This is used for sharing async resources, where you need to hold the lock across await points. +//! The async Mutex has interior mutability built-in, so no RefCell is needed. +//! - Cell: For sharing Copy types between tasks running on the same executor. +//! - RefCell: When you want &mut access to a value shared between tasks running on the same executor. +//! +//! More information: https://embassy.dev/book/#_sharing_peripherals_between_tasks #![no_std] #![no_main] @@ -21,7 +31,7 @@ use rand::RngCore; use static_cell::{ConstStaticCell, StaticCell}; use {defmt_rtt as _, panic_probe as _}; -type UartMutex = mutex::Mutex>; +type UartAsyncMutex = mutex::Mutex>; struct MyType { inner: u32, @@ -53,7 +63,7 @@ fn main() -> ! { let uart = UartTx::new(p.UART0, p.PIN_0, p.DMA_CH0, uart::Config::default()); // Use the async Mutex for sharing async things (built-in interior mutability) - static UART: StaticCell = StaticCell::new(); + static UART: StaticCell = StaticCell::new(); let uart = UART.init(mutex::Mutex::new(uart)); // High-priority executor: runs in interrupt mode @@ -80,7 +90,7 @@ fn main() -> ! { } #[embassy_executor::task] -async fn task_a(uart: &'static UartMutex) { +async fn task_a(uart: &'static UartAsyncMutex) { let mut ticker = Ticker::every(Duration::from_secs(1)); loop { let random = RoscRng.next_u32(); @@ -100,7 +110,7 @@ async fn task_a(uart: &'static UartMutex) { } #[embassy_executor::task] -async fn task_b(uart: &'static UartMutex, cell: &'static Cell<[u8; 4]>, ref_cell: &'static RefCell) { +async fn task_b(uart: &'static UartAsyncMutex, cell: &'static Cell<[u8; 4]>, ref_cell: &'static RefCell) { let mut ticker = Ticker::every(Duration::from_secs(1)); loop { let random = RoscRng.next_u32(); @@ -121,8 +131,8 @@ async fn task_c(cell: &'static Cell<[u8; 4]>, ref_cell: &'static RefCell loop { info!("======================="); - let atomic = ATOMIC.load(Ordering::Relaxed); - info!("atomic: {}", atomic); + let atomic_val = ATOMIC.load(Ordering::Relaxed); + info!("atomic: {}", atomic_val); MUTEX_BLOCKING.lock(|x| { let val = x.borrow().inner; From 376f65e1d388a7eff83e0c12deeb4d556daefea5 Mon Sep 17 00:00:00 2001 From: rafael Date: Mon, 8 Jul 2024 22:07:38 +0200 Subject: [PATCH 52/57] add assign_resources example --- examples/rp/Cargo.toml | 3 + examples/rp/src/bin/assign_resources.rs | 82 +++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 examples/rp/src/bin/assign_resources.rs diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index 9bd403f02..d06ab5e5a 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -29,6 +29,9 @@ reqwless = { version = "0.12.0", features = ["defmt",]} serde = { version = "1.0.203", default-features = false, features = ["derive"] } serde-json-core = "0.5.1" +# for assign resources example +assign-resources = { git = "https://github.com/adamgreig/assign-resources", rev = "94ad10e2729afdf0fd5a77cd12e68409a982f58a" } + #cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m = { version = "0.7.6", features = ["inline-asm"] } cortex-m-rt = "0.7.0" diff --git a/examples/rp/src/bin/assign_resources.rs b/examples/rp/src/bin/assign_resources.rs new file mode 100644 index 000000000..38e730498 --- /dev/null +++ b/examples/rp/src/bin/assign_resources.rs @@ -0,0 +1,82 @@ +//! This example demonstrates how to assign resources to multiple tasks by splitting up the peripherals. +//! It is not about sharing the same resources between tasks, see sharing.rs for that or head to https://embassy.dev/book/#_sharing_peripherals_between_tasks) +//! Of course splitting up resources and sharing resources can be combined, yet this example is only about splitting up resources. +//! +//! There are basically two ways we demonstrate here: +//! 1) Assigning resources to a task by passing parts of the peripherals +//! 2) Assigning resources to a task by passing a struct with the split up peripherals, using the assign-resources macro +//! +//! using four LEDs on Pins 10, 11, 20 and 21 + +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::{ + gpio, + peripherals::{self, PIN_20, PIN_21}, +}; +use embassy_time::Timer; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + // initialize the peripherals + let p = embassy_rp::init(Default::default()); + + // 1) Assigning a resource to a task by passing parts of the peripherals. + spawner + .spawn(double_blinky_manually_assigned(spawner, p.PIN_20, p.PIN_21)) + .unwrap(); + + // 2) Using the assign-resources macro to assign resources to a task. + // we perform the split, see further below for the definition of the resources struct + let r = split_resources!(p); + // and then we can use them + spawner.spawn(double_blinky_macro_assigned(spawner, r.leds)).unwrap(); +} + +// 1) Assigning a resource to a task by passing parts of the peripherals. +#[embassy_executor::task] +async fn double_blinky_manually_assigned(_spawner: Spawner, pin_20: PIN_20, pin_21: PIN_21) { + let mut led_20 = Output::new(pin_20, Level::Low); + let mut led_21 = Output::new(pin_21, Level::High); + + loop { + info!("toggling leds"); + led_20.toggle(); + led_21.toggle(); + Timer::after_secs(1).await; + } +} + +// 2) Using the assign-resources macro to assign resources to a task. +// first we define the resources we want to assign to the task using the assign_resources! macro +// basically this will split up the peripherals struct into smaller structs, that we define here +// naming is up to you, make sure your future self understands what you did here +assign_resources! { + leds: Leds{ + led_10: PIN_10, + led_11: PIN_11, + } + // add more resources to more structs if needed, for example defining one struct for each task +} +// this could be done in another file and imported here, but for the sake of simplicity we do it here +// see https://github.com/adamgreig/assign-resources for more information + +// 2) Using the split resources in a task +#[embassy_executor::task] +async fn double_blinky_macro_assigned(_spawner: Spawner, r: Leds) { + let mut led_10 = Output::new(r.led_10, Level::Low); + let mut led_11 = Output::new(r.led_11, Level::High); + + loop { + info!("toggling leds"); + led_10.toggle(); + led_11.toggle(); + Timer::after_secs(1).await; + } +} From 91e6aa31660d89de0b8a7f382d807e66907aceaa Mon Sep 17 00:00:00 2001 From: rafael Date: Mon, 8 Jul 2024 22:20:23 +0200 Subject: [PATCH 53/57] rustfmt --- examples/rp/src/bin/assign_resources.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rp/src/bin/assign_resources.rs b/examples/rp/src/bin/assign_resources.rs index 38e730498..37b2c20bc 100644 --- a/examples/rp/src/bin/assign_resources.rs +++ b/examples/rp/src/bin/assign_resources.rs @@ -5,7 +5,7 @@ //! There are basically two ways we demonstrate here: //! 1) Assigning resources to a task by passing parts of the peripherals //! 2) Assigning resources to a task by passing a struct with the split up peripherals, using the assign-resources macro -//! +//! //! using four LEDs on Pins 10, 11, 20 and 21 #![no_std] From 52c43f024939956d3b8d45cada70aaaa35dfbebd Mon Sep 17 00:00:00 2001 From: rafael Date: Mon, 8 Jul 2024 22:26:32 +0200 Subject: [PATCH 54/57] rustfmt --- examples/rp/src/bin/assign_resources.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/rp/src/bin/assign_resources.rs b/examples/rp/src/bin/assign_resources.rs index 37b2c20bc..ff6eff4a2 100644 --- a/examples/rp/src/bin/assign_resources.rs +++ b/examples/rp/src/bin/assign_resources.rs @@ -14,12 +14,9 @@ use assign_resources::assign_resources; use defmt::*; use embassy_executor::Spawner; -use embassy_rp::{ - gpio, - peripherals::{self, PIN_20, PIN_21}, -}; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{self, PIN_20, PIN_21}; use embassy_time::Timer; -use gpio::{Level, Output}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] From 2f62376a15b931dcf24708717695107f2c99fb1b Mon Sep 17 00:00:00 2001 From: rafael Date: Mon, 8 Jul 2024 23:27:42 +0200 Subject: [PATCH 55/57] add faq --- docs/pages/faq.adoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/pages/faq.adoc b/docs/pages/faq.adoc index fc1316062..4ab04e2a1 100644 --- a/docs/pages/faq.adoc +++ b/docs/pages/faq.adoc @@ -358,3 +358,7 @@ An example showcasing some methods for sharing things between tasks link:https:/ But when it comes to "waking" tasks, for example when a data transfer is complete or a button is pressed, it's faster to wake a dedicated task, because that task does not need to check which future is actually ready. `join` and `select` must check ALL of the futures they are managing to see which one (or which ones) are ready to do more work. This is because all Rust executors (like Embassy or Tokio) only have the ability to wake tasks, not specific futures. This means you will use slightly less CPU time juggling futures when using dedicated tasks. Practically, there's not a LOT of difference either way - so go with what makes it easier for you and your code first, but there will be some details that are slightly different in each case. + +== splitting peripherals resources between tasks + +There are two ways to split resources between tasks, either manually assigned or by a convenient macro. See link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/assign_resources.rs[this example] \ No newline at end of file From 68b3fbe3481cdbc562d0aa3fdb082514517abde8 Mon Sep 17 00:00:00 2001 From: rafael Date: Wed, 10 Jul 2024 22:54:48 +0200 Subject: [PATCH 56/57] implement read_ready method in BufferedUart --- embassy-rp/src/uart/buffered.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/embassy-rp/src/uart/buffered.rs b/embassy-rp/src/uart/buffered.rs index c94164040..36a11279e 100644 --- a/embassy-rp/src/uart/buffered.rs +++ b/embassy-rp/src/uart/buffered.rs @@ -315,6 +315,12 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { w.set_rtim(true); }); } + + /// we are ready to read if there is data in the buffer + fn read_ready() -> Result{ + let state = T::buffered_state(); + Ok(!state.rx_buf.is_empty()) + } } impl<'d, T: Instance> BufferedUartTx<'d, T> { @@ -621,6 +627,18 @@ impl<'d, T: Instance + 'd> embedded_io_async::Read for BufferedUartRx<'d, T> { } } +impl<'d, T: Instance + 'd> embedded_io_async::ReadReady for BufferedUart<'d, T> { + fn read_ready(&mut self) -> Result { + BufferedUartRx::<'d, T>::read_ready() + } +} + +impl<'d, T: Instance + 'd> embedded_io_async::ReadReady for BufferedUartRx<'d, T> { + fn read_ready(&mut self) -> Result { + Self::read_ready() + } +} + impl<'d, T: Instance + 'd> embedded_io_async::BufRead for BufferedUart<'d, T> { async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { BufferedUartRx::<'d, T>::fill_buf().await From d8821dd7d78223552af38781054b8d9c6a9a25bb Mon Sep 17 00:00:00 2001 From: rafael Date: Wed, 10 Jul 2024 22:56:12 +0200 Subject: [PATCH 57/57] rustfmt --- embassy-rp/src/uart/buffered.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-rp/src/uart/buffered.rs b/embassy-rp/src/uart/buffered.rs index 36a11279e..cfbd82ccf 100644 --- a/embassy-rp/src/uart/buffered.rs +++ b/embassy-rp/src/uart/buffered.rs @@ -316,8 +316,8 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { }); } - /// we are ready to read if there is data in the buffer - fn read_ready() -> Result{ + /// we are ready to read if there is data in the buffer + fn read_ready() -> Result { let state = T::buffered_state(); Ok(!state.rx_buf.is_empty()) }