From 26acee2902c519075bd3d5f98d2a1a498ee3d1a9 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Tue, 13 Feb 2024 14:50:26 +0100 Subject: [PATCH 001/144] Add initial implementation of `MultiSignal` sync primitive --- embassy-sync/src/lib.rs | 1 + embassy-sync/src/multi_signal.rs | 285 +++++++++++++++++++++++++++++++ 2 files changed, 286 insertions(+) create mode 100644 embassy-sync/src/multi_signal.rs diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs index d88c76db5..f02985564 100644 --- a/embassy-sync/src/lib.rs +++ b/embassy-sync/src/lib.rs @@ -12,6 +12,7 @@ mod ring_buffer; pub mod blocking_mutex; pub mod channel; +pub mod multi_signal; pub mod mutex; pub mod pipe; pub mod priority_channel; diff --git a/embassy-sync/src/multi_signal.rs b/embassy-sync/src/multi_signal.rs new file mode 100644 index 000000000..db858f269 --- /dev/null +++ b/embassy-sync/src/multi_signal.rs @@ -0,0 +1,285 @@ +//! A synchronization primitive for passing the latest value to **multiple** tasks. +use core::{ + cell::RefCell, + marker::PhantomData, + ops::{Deref, DerefMut}, + pin::Pin, + task::{Context, Poll}, +}; + +use futures_util::Future; + +use crate::{ + blocking_mutex::{raw::RawMutex, Mutex}, + waitqueue::MultiWakerRegistration, +}; + +/// A `MultiSignal` is a single-slot signaling primitive, which can awake `N` separate [`Receiver`]s. +/// +/// Similar to a [`Signal`](crate::signal::Signal), except `MultiSignal` allows for multiple tasks to +/// `.await` the latest value, and all receive it. +/// +/// This is similar to a [`PubSubChannel`](crate::pubsub::PubSubChannel) with a buffer size of 1, except +/// "sending" to it (calling [`MultiSignal::write`]) will immediately overwrite the previous value instead +/// of waiting for the receivers to pop the previous value. +/// +/// `MultiSignal` is useful when a single task is responsible for updating a value or "state", which multiple other +/// tasks are interested in getting notified about changes to the latest value of. It is therefore fine for +/// [`Receiver`]s to "lose" stale values. +/// +/// Anyone with a reference to the MultiSignal can update or peek the value. MultiSignals are generally declared +/// as `static`s and then borrowed as required to either [`MultiSignal::peek`] the value or obtain a [`Receiver`] +/// with [`MultiSignal::receiver`] which has async methods. +/// ``` +/// +/// use futures_executor::block_on; +/// use embassy_sync::multi_signal::MultiSignal; +/// use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +/// +/// let f = async { +/// +/// static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); +/// +/// // Obtain Receivers +/// let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); +/// let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); +/// assert!(SOME_SIGNAL.receiver().is_err()); +/// +/// SOME_SIGNAL.write(10); +/// +/// // Receive the new value +/// assert_eq!(rcv0.changed().await, 10); +/// assert_eq!(rcv1.try_changed(), Some(10)); +/// +/// // No update +/// assert_eq!(rcv0.try_changed(), None); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// SOME_SIGNAL.write(20); +/// +/// // Receive new value with predicate +/// assert_eq!(rcv0.changed_and(|x|x>&10).await, 20); +/// assert_eq!(rcv1.try_changed_and(|x|x>&30), None); +/// +/// // Anyone can peek the current value +/// assert_eq!(rcv0.peek(), 20); +/// assert_eq!(rcv1.peek(), 20); +/// assert_eq!(SOME_SIGNAL.peek(), 20); +/// assert_eq!(SOME_SIGNAL.peek_and(|x|x>&30), None); +/// }; +/// block_on(f); +/// ``` +pub struct MultiSignal<'a, M: RawMutex, T: Clone, const N: usize> { + mutex: Mutex>>, + _phantom: PhantomData<&'a ()>, +} + +struct MultiSignalState { + data: T, + current_id: u64, + wakers: MultiWakerRegistration, + receiver_count: usize, +} + +#[derive(Debug)] +/// An error that can occur when a `MultiSignal` returns a `Result`. +pub enum Error { + /// The maximum number of [`Receiver`](crate::multi_signal::Receiver) has been reached. + MaximumReceiversReached, +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal<'a, M, T, N> { + /// Create a new `MultiSignal` initialized with the given value. + pub const fn new(init: T) -> Self { + Self { + mutex: Mutex::new(RefCell::new(MultiSignalState { + data: init, + current_id: 1, + wakers: MultiWakerRegistration::new(), + receiver_count: 0, + })), + _phantom: PhantomData, + } + } + + /// Get a [`Receiver`] for the `MultiSignal`. + pub fn receiver(&'a self) -> Result, Error> { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if s.receiver_count < N { + s.receiver_count += 1; + Ok(Receiver(Rcv::new(self))) + } else { + Err(Error::MaximumReceiversReached) + } + }) + } + + /// Update the value of the `MultiSignal`. + pub fn write(&self, data: T) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = data; + s.current_id += 1; + s.wakers.wake(); + }) + } + + /// Peek the current value of the `MultiSignal`. + pub fn peek(&self) -> T { + self.mutex.lock(|state| state.borrow().data.clone()) + } + + /// Peek the current value of the `MultiSignal` and check if it satisfies the predicate `f`. + pub fn peek_and(&self, f: fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + if f(&s.data) { + Some(s.data.clone()) + } else { + None + } + }) + } + + /// Get the ID of the current value of the `MultiSignal`. + /// This method is mostly for testing purposes. + #[allow(dead_code)] + fn get_id(&self) -> u64 { + self.mutex.lock(|state| state.borrow().current_id) + } + + /// Poll the `MultiSignal` with an optional context. + fn get_with_context(&'a self, waker: &mut Rcv<'a, M, T, N>, cx: Option<&mut Context>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match (s.current_id > waker.at_id, waker.predicate) { + (true, None) => { + waker.at_id = s.current_id; + Poll::Ready(s.data.clone()) + } + (true, Some(f)) if f(&s.data) => { + waker.at_id = s.current_id; + Poll::Ready(s.data.clone()) + } + _ => { + if let Some(cx) = cx { + s.wakers.register(cx.waker()); + } + Poll::Pending + } + } + }) + } +} + +/// A receiver is able to `.await` a changed `MultiSignal` value. +pub struct Rcv<'a, M: RawMutex, T: Clone, const N: usize> { + multi_sig: &'a MultiSignal<'a, M, T, N>, + predicate: Option bool>, + at_id: u64, +} + +// f: Option bool> +impl<'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { + /// Create a new `Receiver` with a reference the given `MultiSignal`. + fn new(multi_sig: &'a MultiSignal<'a, M, T, N>) -> Self { + Self { + multi_sig, + predicate: None, + at_id: 0, + } + } + + /// Wait for a change to the value of the corresponding `MultiSignal`. + pub fn changed<'s>(&'s mut self) -> ReceiverFuture<'s, 'a, M, T, N> { + self.predicate = None; + ReceiverFuture { subscriber: self } + } + + /// Wait for a change to the value of the corresponding `MultiSignal` which matches the predicate `f`. + pub fn changed_and<'s>(&'s mut self, f: fn(&T) -> bool) -> ReceiverFuture<'s, 'a, M, T, N> { + self.predicate = Some(f); + ReceiverFuture { subscriber: self } + } + + /// Try to get a changed value of the corresponding `MultiSignal`. + pub fn try_changed(&mut self) -> Option { + self.multi_sig.mutex.lock(|state| { + let s = state.borrow(); + match s.current_id > self.at_id { + true => { + self.at_id = s.current_id; + Some(s.data.clone()) + } + false => None, + } + }) + } + + /// Try to get a changed value of the corresponding `MultiSignal` which matches the predicate `f`. + pub fn try_changed_and(&mut self, f: fn(&T) -> bool) -> Option { + self.multi_sig.mutex.lock(|state| { + let s = state.borrow(); + match s.current_id > self.at_id && f(&s.data) { + true => { + self.at_id = s.current_id; + Some(s.data.clone()) + } + false => None, + } + }) + } + + /// Peek the current value of the corresponding `MultiSignal`. + pub fn peek(&self) -> T { + self.multi_sig.peek() + } + + /// Peek the current value of the corresponding `MultiSignal` and check if it satisfies the predicate `f`. + pub fn peek_and(&self, f: fn(&T) -> bool) -> Option { + self.multi_sig.peek_and(f) + } + + /// Check if the value of the corresponding `MultiSignal` has changed. + pub fn has_changed(&mut self) -> bool { + self.multi_sig + .mutex + .lock(|state| state.borrow().current_id > self.at_id) + } +} + +/// A `Receiver` is able to `.await` a change to the corresponding [`MultiSignal`] value. +pub struct Receiver<'a, M: RawMutex, T: Clone, const N: usize>(Rcv<'a, M, T, N>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Receiver<'a, M, T, N> { + type Target = Rcv<'a, M, T, N>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Receiver<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Future for the `Receiver` wait action +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct ReceiverFuture<'s, 'a, M: RawMutex, T: Clone, const N: usize> { + subscriber: &'s mut Rcv<'a, M, T, N>, +} + +impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Future for ReceiverFuture<'s, 'a, M, T, N> { + type Output = T; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.subscriber + .multi_sig + .get_with_context(&mut self.subscriber, Some(cx)) + } +} + +impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Unpin for ReceiverFuture<'s, 'a, M, T, N> {} From 410c2d440afa2a500ef1398b5b48e746f77815bd Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Tue, 13 Feb 2024 15:37:07 +0100 Subject: [PATCH 002/144] Change import formatting --- embassy-sync/src/multi_signal.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/embassy-sync/src/multi_signal.rs b/embassy-sync/src/multi_signal.rs index db858f269..bff7ad048 100644 --- a/embassy-sync/src/multi_signal.rs +++ b/embassy-sync/src/multi_signal.rs @@ -1,18 +1,15 @@ //! A synchronization primitive for passing the latest value to **multiple** tasks. -use core::{ - cell::RefCell, - marker::PhantomData, - ops::{Deref, DerefMut}, - pin::Pin, - task::{Context, Poll}, -}; +use core::cell::RefCell; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; +use core::pin::Pin; +use core::task::{Context, Poll}; use futures_util::Future; -use crate::{ - blocking_mutex::{raw::RawMutex, Mutex}, - waitqueue::MultiWakerRegistration, -}; +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::waitqueue::MultiWakerRegistration; /// A `MultiSignal` is a single-slot signaling primitive, which can awake `N` separate [`Receiver`]s. /// From 37f1c9ac27b0542fdf404392e9bb265fa8ec41d3 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Tue, 13 Feb 2024 23:14:16 +0100 Subject: [PATCH 003/144] Removed unused lifetime, change most fn -> FnMut --- embassy-sync/src/multi_signal.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/embassy-sync/src/multi_signal.rs b/embassy-sync/src/multi_signal.rs index bff7ad048..5f724c76b 100644 --- a/embassy-sync/src/multi_signal.rs +++ b/embassy-sync/src/multi_signal.rs @@ -1,6 +1,5 @@ //! A synchronization primitive for passing the latest value to **multiple** tasks. use core::cell::RefCell; -use core::marker::PhantomData; use core::ops::{Deref, DerefMut}; use core::pin::Pin; use core::task::{Context, Poll}; @@ -66,9 +65,8 @@ use crate::waitqueue::MultiWakerRegistration; /// }; /// block_on(f); /// ``` -pub struct MultiSignal<'a, M: RawMutex, T: Clone, const N: usize> { +pub struct MultiSignal { mutex: Mutex>>, - _phantom: PhantomData<&'a ()>, } struct MultiSignalState { @@ -85,7 +83,7 @@ pub enum Error { MaximumReceiversReached, } -impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal<'a, M, T, N> { +impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal { /// Create a new `MultiSignal` initialized with the given value. pub const fn new(init: T) -> Self { Self { @@ -95,7 +93,6 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal<'a, M, T, N> { wakers: MultiWakerRegistration::new(), receiver_count: 0, })), - _phantom: PhantomData, } } @@ -128,7 +125,7 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal<'a, M, T, N> { } /// Peek the current value of the `MultiSignal` and check if it satisfies the predicate `f`. - pub fn peek_and(&self, f: fn(&T) -> bool) -> Option { + pub fn peek_and(&self, mut f: impl FnMut(&T) -> bool) -> Option { self.mutex.lock(|state| { let s = state.borrow(); if f(&s.data) { @@ -147,16 +144,16 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal<'a, M, T, N> { } /// Poll the `MultiSignal` with an optional context. - fn get_with_context(&'a self, waker: &mut Rcv<'a, M, T, N>, cx: Option<&mut Context>) -> Poll { + fn get_with_context(&self, rcv: &mut Rcv<'a, M, T, N>, cx: Option<&mut Context>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); - match (s.current_id > waker.at_id, waker.predicate) { + match (s.current_id > rcv.at_id, rcv.predicate) { (true, None) => { - waker.at_id = s.current_id; + rcv.at_id = s.current_id; Poll::Ready(s.data.clone()) } (true, Some(f)) if f(&s.data) => { - waker.at_id = s.current_id; + rcv.at_id = s.current_id; Poll::Ready(s.data.clone()) } _ => { @@ -172,7 +169,7 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal<'a, M, T, N> { /// A receiver is able to `.await` a changed `MultiSignal` value. pub struct Rcv<'a, M: RawMutex, T: Clone, const N: usize> { - multi_sig: &'a MultiSignal<'a, M, T, N>, + multi_sig: &'a MultiSignal, predicate: Option bool>, at_id: u64, } @@ -180,7 +177,7 @@ pub struct Rcv<'a, M: RawMutex, T: Clone, const N: usize> { // f: Option bool> impl<'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { /// Create a new `Receiver` with a reference the given `MultiSignal`. - fn new(multi_sig: &'a MultiSignal<'a, M, T, N>) -> Self { + fn new(multi_sig: &'a MultiSignal) -> Self { Self { multi_sig, predicate: None, @@ -195,6 +192,7 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { } /// Wait for a change to the value of the corresponding `MultiSignal` which matches the predicate `f`. + // TODO: How do we make this work with a FnMut closure? pub fn changed_and<'s>(&'s mut self, f: fn(&T) -> bool) -> ReceiverFuture<'s, 'a, M, T, N> { self.predicate = Some(f); ReceiverFuture { subscriber: self } @@ -215,7 +213,7 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { } /// Try to get a changed value of the corresponding `MultiSignal` which matches the predicate `f`. - pub fn try_changed_and(&mut self, f: fn(&T) -> bool) -> Option { + pub fn try_changed_and(&mut self, mut f: impl FnMut(&T) -> bool) -> Option { self.multi_sig.mutex.lock(|state| { let s = state.borrow(); match s.current_id > self.at_id && f(&s.data) { @@ -234,7 +232,7 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { } /// Peek the current value of the corresponding `MultiSignal` and check if it satisfies the predicate `f`. - pub fn peek_and(&self, f: fn(&T) -> bool) -> Option { + pub fn peek_and(&self, f: impl FnMut(&T) -> bool) -> Option { self.multi_sig.peek_and(f) } From 24a4379832d387754d407b77ff7aac5e55401eb3 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Wed, 14 Feb 2024 01:23:11 +0100 Subject: [PATCH 004/144] Got closures to work in async, added bunch of tests --- embassy-sync/src/multi_signal.rs | 340 ++++++++++++++++++++++++++----- 1 file changed, 292 insertions(+), 48 deletions(-) diff --git a/embassy-sync/src/multi_signal.rs b/embassy-sync/src/multi_signal.rs index 5f724c76b..1481dc8f8 100644 --- a/embassy-sync/src/multi_signal.rs +++ b/embassy-sync/src/multi_signal.rs @@ -97,7 +97,7 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal { } /// Get a [`Receiver`] for the `MultiSignal`. - pub fn receiver(&'a self) -> Result, Error> { + pub fn receiver<'s>(&'a self) -> Result, Error> { self.mutex.lock(|state| { let mut s = state.borrow_mut(); if s.receiver_count < N { @@ -142,60 +142,36 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal { fn get_id(&self) -> u64 { self.mutex.lock(|state| state.borrow().current_id) } - - /// Poll the `MultiSignal` with an optional context. - fn get_with_context(&self, rcv: &mut Rcv<'a, M, T, N>, cx: Option<&mut Context>) -> Poll { - self.mutex.lock(|state| { - let mut s = state.borrow_mut(); - match (s.current_id > rcv.at_id, rcv.predicate) { - (true, None) => { - rcv.at_id = s.current_id; - Poll::Ready(s.data.clone()) - } - (true, Some(f)) if f(&s.data) => { - rcv.at_id = s.current_id; - Poll::Ready(s.data.clone()) - } - _ => { - if let Some(cx) = cx { - s.wakers.register(cx.waker()); - } - Poll::Pending - } - } - }) - } } /// A receiver is able to `.await` a changed `MultiSignal` value. pub struct Rcv<'a, M: RawMutex, T: Clone, const N: usize> { multi_sig: &'a MultiSignal, - predicate: Option bool>, at_id: u64, } -// f: Option bool> -impl<'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { +impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { /// Create a new `Receiver` with a reference the given `MultiSignal`. fn new(multi_sig: &'a MultiSignal) -> Self { - Self { - multi_sig, - predicate: None, - at_id: 0, - } + Self { multi_sig, at_id: 0 } } /// Wait for a change to the value of the corresponding `MultiSignal`. - pub fn changed<'s>(&'s mut self) -> ReceiverFuture<'s, 'a, M, T, N> { - self.predicate = None; - ReceiverFuture { subscriber: self } + pub async fn changed(&mut self) -> T { + ReceiverWaitFuture { subscriber: self }.await } /// Wait for a change to the value of the corresponding `MultiSignal` which matches the predicate `f`. // TODO: How do we make this work with a FnMut closure? - pub fn changed_and<'s>(&'s mut self, f: fn(&T) -> bool) -> ReceiverFuture<'s, 'a, M, T, N> { - self.predicate = Some(f); - ReceiverFuture { subscriber: self } + pub async fn changed_and(&mut self, f: F) -> T + where + F: FnMut(&T) -> bool, + { + ReceiverPredFuture { + subscriber: self, + predicate: f, + } + .await } /// Try to get a changed value of the corresponding `MultiSignal`. @@ -213,7 +189,10 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { } /// Try to get a changed value of the corresponding `MultiSignal` which matches the predicate `f`. - pub fn try_changed_and(&mut self, mut f: impl FnMut(&T) -> bool) -> Option { + pub fn try_changed_and(&mut self, mut f: F) -> Option + where + F: FnMut(&T) -> bool, + { self.multi_sig.mutex.lock(|state| { let s = state.borrow(); match s.current_id > self.at_id && f(&s.data) { @@ -232,7 +211,10 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { } /// Peek the current value of the corresponding `MultiSignal` and check if it satisfies the predicate `f`. - pub fn peek_and(&self, f: impl FnMut(&T) -> bool) -> Option { + pub fn peek_and(&self, f: F) -> Option + where + F: FnMut(&T) -> bool, + { self.multi_sig.peek_and(f) } @@ -247,7 +229,7 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { /// A `Receiver` is able to `.await` a change to the corresponding [`MultiSignal`] value. pub struct Receiver<'a, M: RawMutex, T: Clone, const N: usize>(Rcv<'a, M, T, N>); -impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Receiver<'a, M, T, N> { +impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Deref for Receiver<'a, M, T, N> { type Target = Rcv<'a, M, T, N>; fn deref(&self) -> &Self::Target { @@ -255,7 +237,7 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Receiver<'a, M, T, N> } } -impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Receiver<'a, M, T, N> { +impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Receiver<'a, M, T, N> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } @@ -263,18 +245,280 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Receiver<'a, M, T, /// Future for the `Receiver` wait action #[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct ReceiverFuture<'s, 'a, M: RawMutex, T: Clone, const N: usize> { +pub struct ReceiverWaitFuture<'s, 'a, M: RawMutex, T: Clone, const N: usize> { subscriber: &'s mut Rcv<'a, M, T, N>, } -impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Future for ReceiverFuture<'s, 'a, M, T, N> { +impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Unpin for ReceiverWaitFuture<'s, 'a, M, T, N> {} +impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Future for ReceiverWaitFuture<'s, 'a, M, T, N> { type Output = T; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.subscriber - .multi_sig - .get_with_context(&mut self.subscriber, Some(cx)) + self.get_with_context(Some(cx)) } } -impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Unpin for ReceiverFuture<'s, 'a, M, T, N> {} +impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> ReceiverWaitFuture<'s, 'a, M, T, N> { + /// Poll the `MultiSignal` with an optional context. + fn get_with_context(&mut self, cx: Option<&mut Context>) -> Poll { + self.subscriber.multi_sig.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match s.current_id > self.subscriber.at_id { + true => { + self.subscriber.at_id = s.current_id; + Poll::Ready(s.data.clone()) + } + _ => { + if let Some(cx) = cx { + s.wakers.register(cx.waker()); + } + Poll::Pending + } + } + }) + } +} + +/// Future for the `Receiver` wait action, with the ability to filter the value with a predicate. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct ReceiverPredFuture<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&'a T) -> bool, const N: usize> { + subscriber: &'s mut Rcv<'a, M, T, N>, + predicate: F, +} + +impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> Unpin for ReceiverPredFuture<'s, 'a, M, T, F, N> {} +impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> Future for ReceiverPredFuture<'s, 'a, M, T, F, N>{ + type Output = T; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.get_with_context_pred(Some(cx)) + } +} + +impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> ReceiverPredFuture<'s, 'a, M, T, F, N> { + /// Poll the `MultiSignal` with an optional context. + fn get_with_context_pred(&mut self, cx: Option<&mut Context>) -> Poll { + self.subscriber.multi_sig.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match s.current_id > self.subscriber.at_id { + true if (self.predicate)(&s.data) => { + self.subscriber.at_id = s.current_id; + Poll::Ready(s.data.clone()) + } + _ => { + if let Some(cx) = cx { + s.wakers.register(cx.waker()); + } + Poll::Pending + } + } + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::blocking_mutex::raw::CriticalSectionRawMutex; + use futures_executor::block_on; + + #[test] + fn multiple_writes() { + let f = async { + static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); + + // Obtain Receivers + let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); + let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); + + SOME_SIGNAL.write(10); + + // Receive the new value + assert_eq!(rcv0.changed().await, 10); + assert_eq!(rcv1.changed().await, 10); + + // No update + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + + SOME_SIGNAL.write(20); + + assert_eq!(rcv0.changed().await, 20); + assert_eq!(rcv1.changed().await, 20); + }; + block_on(f); + } + + #[test] + fn max_receivers() { + let f = async { + static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); + + // Obtain Receivers + let _ = SOME_SIGNAL.receiver().unwrap(); + let _ = SOME_SIGNAL.receiver().unwrap(); + assert!(SOME_SIGNAL.receiver().is_err()); + }; + block_on(f); + } + + // Really weird edge case, but it's possible to have a receiver that never gets a value. + #[test] + fn receive_initial() { + let f = async { + static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); + + // Obtain Receivers + let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); + let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); + + assert_eq!(rcv0.try_changed(), Some(0)); + assert_eq!(rcv1.try_changed(), Some(0)); + + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn count_ids() { + let f = async { + static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); + + // Obtain Receivers + let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); + let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); + + SOME_SIGNAL.write(10); + + assert_eq!(rcv0.changed().await, 10); + assert_eq!(rcv1.changed().await, 10); + + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + + SOME_SIGNAL.write(20); + SOME_SIGNAL.write(20); + SOME_SIGNAL.write(20); + + assert_eq!(rcv0.changed().await, 20); + assert_eq!(rcv1.changed().await, 20); + + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + + assert_eq!(SOME_SIGNAL.get_id(), 5); + }; + block_on(f); + } + + #[test] + fn peek_still_await() { + let f = async { + static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); + + // Obtain Receivers + let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); + let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); + + SOME_SIGNAL.write(10); + + assert_eq!(rcv0.peek(), 10); + assert_eq!(rcv1.peek(), 10); + + assert_eq!(rcv0.changed().await, 10); + assert_eq!(rcv1.changed().await, 10); + }; + block_on(f); + } + + #[test] + fn predicate() { + let f = async { + static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); + + // Obtain Receivers + let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); + let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); + + SOME_SIGNAL.write(20); + + assert_eq!(rcv0.changed_and(|x| x > &10).await, 20); + assert_eq!(rcv1.try_changed_and(|x| x > &30), None); + }; + block_on(f); + } + + #[test] + fn mutable_predicate() { + let f = async { + static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); + + // Obtain Receivers + let mut rcv = SOME_SIGNAL.receiver().unwrap(); + + SOME_SIGNAL.write(10); + + let mut largest = 0; + let mut predicate = |x: &u8| { + if *x > largest { + largest = *x; + } + true + }; + + assert_eq!(rcv.changed_and(&mut predicate).await, 10); + + SOME_SIGNAL.write(20); + + assert_eq!(rcv.changed_and(&mut predicate).await, 20); + + SOME_SIGNAL.write(5); + + assert_eq!(rcv.changed_and(&mut predicate).await, 5); + + assert_eq!(largest, 20) + }; + block_on(f); + } + + #[test] + fn peek_and() { + let f = async { + static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); + + // Obtain Receivers + let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); + let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); + + SOME_SIGNAL.write(20); + + assert_eq!(rcv0.peek_and(|x| x > &10), Some(20)); + assert_eq!(rcv1.peek_and(|x| x > &30), None); + + assert_eq!(rcv0.changed().await, 20); + assert_eq!(rcv1.changed().await, 20); + }; + block_on(f); + } + + #[test] + fn peek_with_static() { + let f = async { + static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); + + // Obtain Receivers + let rcv0 = SOME_SIGNAL.receiver().unwrap(); + let rcv1 = SOME_SIGNAL.receiver().unwrap(); + + SOME_SIGNAL.write(20); + + assert_eq!(rcv0.peek(), 20); + assert_eq!(rcv1.peek(), 20); + assert_eq!(SOME_SIGNAL.peek(), 20); + assert_eq!(SOME_SIGNAL.peek_and(|x| x > &30), None); + }; + block_on(f); + } +} From 2f58d1968a7310335a0dac4d947c6972a7707ed5 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Wed, 14 Feb 2024 01:27:48 +0100 Subject: [PATCH 005/144] Updated formatting --- embassy-sync/src/multi_signal.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/embassy-sync/src/multi_signal.rs b/embassy-sync/src/multi_signal.rs index 1481dc8f8..ff9f72f2e 100644 --- a/embassy-sync/src/multi_signal.rs +++ b/embassy-sync/src/multi_signal.rs @@ -162,7 +162,6 @@ impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { } /// Wait for a change to the value of the corresponding `MultiSignal` which matches the predicate `f`. - // TODO: How do we make this work with a FnMut closure? pub async fn changed_and(&mut self, f: F) -> T where F: FnMut(&T) -> bool, @@ -286,8 +285,13 @@ pub struct ReceiverPredFuture<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&'a T) -> predicate: F, } -impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> Unpin for ReceiverPredFuture<'s, 'a, M, T, F, N> {} -impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> Future for ReceiverPredFuture<'s, 'a, M, T, F, N>{ +impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> Unpin + for ReceiverPredFuture<'s, 'a, M, T, F, N> +{ +} +impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> Future + for ReceiverPredFuture<'s, 'a, M, T, F, N> +{ type Output = T; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -318,9 +322,10 @@ impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> Receiv #[cfg(test)] mod tests { + use futures_executor::block_on; + use super::*; use crate::blocking_mutex::raw::CriticalSectionRawMutex; - use futures_executor::block_on; #[test] fn multiple_writes() { From 6defb4fed98432dee948634f3b2001cb4ea7ec5b Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Wed, 28 Feb 2024 20:59:38 +0100 Subject: [PATCH 006/144] Changed name to `Watch`, added `DynReceiver`, `get`-method and more reworks. --- embassy-sync/src/lib.rs | 2 +- embassy-sync/src/multi_signal.rs | 529 ------------------------------- embassy-sync/src/watch.rs | 515 ++++++++++++++++++++++++++++++ 3 files changed, 516 insertions(+), 530 deletions(-) delete mode 100644 embassy-sync/src/multi_signal.rs create mode 100644 embassy-sync/src/watch.rs diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs index f02985564..8a69541a5 100644 --- a/embassy-sync/src/lib.rs +++ b/embassy-sync/src/lib.rs @@ -12,11 +12,11 @@ mod ring_buffer; pub mod blocking_mutex; pub mod channel; -pub mod multi_signal; pub mod mutex; pub mod pipe; pub mod priority_channel; pub mod pubsub; pub mod signal; pub mod waitqueue; +pub mod watch; pub mod zerocopy_channel; diff --git a/embassy-sync/src/multi_signal.rs b/embassy-sync/src/multi_signal.rs deleted file mode 100644 index ff9f72f2e..000000000 --- a/embassy-sync/src/multi_signal.rs +++ /dev/null @@ -1,529 +0,0 @@ -//! A synchronization primitive for passing the latest value to **multiple** tasks. -use core::cell::RefCell; -use core::ops::{Deref, DerefMut}; -use core::pin::Pin; -use core::task::{Context, Poll}; - -use futures_util::Future; - -use crate::blocking_mutex::raw::RawMutex; -use crate::blocking_mutex::Mutex; -use crate::waitqueue::MultiWakerRegistration; - -/// A `MultiSignal` is a single-slot signaling primitive, which can awake `N` separate [`Receiver`]s. -/// -/// Similar to a [`Signal`](crate::signal::Signal), except `MultiSignal` allows for multiple tasks to -/// `.await` the latest value, and all receive it. -/// -/// This is similar to a [`PubSubChannel`](crate::pubsub::PubSubChannel) with a buffer size of 1, except -/// "sending" to it (calling [`MultiSignal::write`]) will immediately overwrite the previous value instead -/// of waiting for the receivers to pop the previous value. -/// -/// `MultiSignal` is useful when a single task is responsible for updating a value or "state", which multiple other -/// tasks are interested in getting notified about changes to the latest value of. It is therefore fine for -/// [`Receiver`]s to "lose" stale values. -/// -/// Anyone with a reference to the MultiSignal can update or peek the value. MultiSignals are generally declared -/// as `static`s and then borrowed as required to either [`MultiSignal::peek`] the value or obtain a [`Receiver`] -/// with [`MultiSignal::receiver`] which has async methods. -/// ``` -/// -/// use futures_executor::block_on; -/// use embassy_sync::multi_signal::MultiSignal; -/// use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -/// -/// let f = async { -/// -/// static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); -/// -/// // Obtain Receivers -/// let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); -/// let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); -/// assert!(SOME_SIGNAL.receiver().is_err()); -/// -/// SOME_SIGNAL.write(10); -/// -/// // Receive the new value -/// assert_eq!(rcv0.changed().await, 10); -/// assert_eq!(rcv1.try_changed(), Some(10)); -/// -/// // No update -/// assert_eq!(rcv0.try_changed(), None); -/// assert_eq!(rcv1.try_changed(), None); -/// -/// SOME_SIGNAL.write(20); -/// -/// // Receive new value with predicate -/// assert_eq!(rcv0.changed_and(|x|x>&10).await, 20); -/// assert_eq!(rcv1.try_changed_and(|x|x>&30), None); -/// -/// // Anyone can peek the current value -/// assert_eq!(rcv0.peek(), 20); -/// assert_eq!(rcv1.peek(), 20); -/// assert_eq!(SOME_SIGNAL.peek(), 20); -/// assert_eq!(SOME_SIGNAL.peek_and(|x|x>&30), None); -/// }; -/// block_on(f); -/// ``` -pub struct MultiSignal { - mutex: Mutex>>, -} - -struct MultiSignalState { - data: T, - current_id: u64, - wakers: MultiWakerRegistration, - receiver_count: usize, -} - -#[derive(Debug)] -/// An error that can occur when a `MultiSignal` returns a `Result`. -pub enum Error { - /// The maximum number of [`Receiver`](crate::multi_signal::Receiver) has been reached. - MaximumReceiversReached, -} - -impl<'a, M: RawMutex, T: Clone, const N: usize> MultiSignal { - /// Create a new `MultiSignal` initialized with the given value. - pub const fn new(init: T) -> Self { - Self { - mutex: Mutex::new(RefCell::new(MultiSignalState { - data: init, - current_id: 1, - wakers: MultiWakerRegistration::new(), - receiver_count: 0, - })), - } - } - - /// Get a [`Receiver`] for the `MultiSignal`. - pub fn receiver<'s>(&'a self) -> Result, Error> { - self.mutex.lock(|state| { - let mut s = state.borrow_mut(); - if s.receiver_count < N { - s.receiver_count += 1; - Ok(Receiver(Rcv::new(self))) - } else { - Err(Error::MaximumReceiversReached) - } - }) - } - - /// Update the value of the `MultiSignal`. - pub fn write(&self, data: T) { - self.mutex.lock(|state| { - let mut s = state.borrow_mut(); - s.data = data; - s.current_id += 1; - s.wakers.wake(); - }) - } - - /// Peek the current value of the `MultiSignal`. - pub fn peek(&self) -> T { - self.mutex.lock(|state| state.borrow().data.clone()) - } - - /// Peek the current value of the `MultiSignal` and check if it satisfies the predicate `f`. - pub fn peek_and(&self, mut f: impl FnMut(&T) -> bool) -> Option { - self.mutex.lock(|state| { - let s = state.borrow(); - if f(&s.data) { - Some(s.data.clone()) - } else { - None - } - }) - } - - /// Get the ID of the current value of the `MultiSignal`. - /// This method is mostly for testing purposes. - #[allow(dead_code)] - fn get_id(&self) -> u64 { - self.mutex.lock(|state| state.borrow().current_id) - } -} - -/// A receiver is able to `.await` a changed `MultiSignal` value. -pub struct Rcv<'a, M: RawMutex, T: Clone, const N: usize> { - multi_sig: &'a MultiSignal, - at_id: u64, -} - -impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Rcv<'a, M, T, N> { - /// Create a new `Receiver` with a reference the given `MultiSignal`. - fn new(multi_sig: &'a MultiSignal) -> Self { - Self { multi_sig, at_id: 0 } - } - - /// Wait for a change to the value of the corresponding `MultiSignal`. - pub async fn changed(&mut self) -> T { - ReceiverWaitFuture { subscriber: self }.await - } - - /// Wait for a change to the value of the corresponding `MultiSignal` which matches the predicate `f`. - pub async fn changed_and(&mut self, f: F) -> T - where - F: FnMut(&T) -> bool, - { - ReceiverPredFuture { - subscriber: self, - predicate: f, - } - .await - } - - /// Try to get a changed value of the corresponding `MultiSignal`. - pub fn try_changed(&mut self) -> Option { - self.multi_sig.mutex.lock(|state| { - let s = state.borrow(); - match s.current_id > self.at_id { - true => { - self.at_id = s.current_id; - Some(s.data.clone()) - } - false => None, - } - }) - } - - /// Try to get a changed value of the corresponding `MultiSignal` which matches the predicate `f`. - pub fn try_changed_and(&mut self, mut f: F) -> Option - where - F: FnMut(&T) -> bool, - { - self.multi_sig.mutex.lock(|state| { - let s = state.borrow(); - match s.current_id > self.at_id && f(&s.data) { - true => { - self.at_id = s.current_id; - Some(s.data.clone()) - } - false => None, - } - }) - } - - /// Peek the current value of the corresponding `MultiSignal`. - pub fn peek(&self) -> T { - self.multi_sig.peek() - } - - /// Peek the current value of the corresponding `MultiSignal` and check if it satisfies the predicate `f`. - pub fn peek_and(&self, f: F) -> Option - where - F: FnMut(&T) -> bool, - { - self.multi_sig.peek_and(f) - } - - /// Check if the value of the corresponding `MultiSignal` has changed. - pub fn has_changed(&mut self) -> bool { - self.multi_sig - .mutex - .lock(|state| state.borrow().current_id > self.at_id) - } -} - -/// A `Receiver` is able to `.await` a change to the corresponding [`MultiSignal`] value. -pub struct Receiver<'a, M: RawMutex, T: Clone, const N: usize>(Rcv<'a, M, T, N>); - -impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Deref for Receiver<'a, M, T, N> { - type Target = Rcv<'a, M, T, N>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Receiver<'a, M, T, N> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -/// Future for the `Receiver` wait action -#[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct ReceiverWaitFuture<'s, 'a, M: RawMutex, T: Clone, const N: usize> { - subscriber: &'s mut Rcv<'a, M, T, N>, -} - -impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Unpin for ReceiverWaitFuture<'s, 'a, M, T, N> {} -impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> Future for ReceiverWaitFuture<'s, 'a, M, T, N> { - type Output = T; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.get_with_context(Some(cx)) - } -} - -impl<'s, 'a, M: RawMutex, T: Clone, const N: usize> ReceiverWaitFuture<'s, 'a, M, T, N> { - /// Poll the `MultiSignal` with an optional context. - fn get_with_context(&mut self, cx: Option<&mut Context>) -> Poll { - self.subscriber.multi_sig.mutex.lock(|state| { - let mut s = state.borrow_mut(); - match s.current_id > self.subscriber.at_id { - true => { - self.subscriber.at_id = s.current_id; - Poll::Ready(s.data.clone()) - } - _ => { - if let Some(cx) = cx { - s.wakers.register(cx.waker()); - } - Poll::Pending - } - } - }) - } -} - -/// Future for the `Receiver` wait action, with the ability to filter the value with a predicate. -#[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct ReceiverPredFuture<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&'a T) -> bool, const N: usize> { - subscriber: &'s mut Rcv<'a, M, T, N>, - predicate: F, -} - -impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> Unpin - for ReceiverPredFuture<'s, 'a, M, T, F, N> -{ -} -impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> Future - for ReceiverPredFuture<'s, 'a, M, T, F, N> -{ - type Output = T; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.get_with_context_pred(Some(cx)) - } -} - -impl<'s, 'a, M: RawMutex, T: Clone, F: FnMut(&T) -> bool, const N: usize> ReceiverPredFuture<'s, 'a, M, T, F, N> { - /// Poll the `MultiSignal` with an optional context. - fn get_with_context_pred(&mut self, cx: Option<&mut Context>) -> Poll { - self.subscriber.multi_sig.mutex.lock(|state| { - let mut s = state.borrow_mut(); - match s.current_id > self.subscriber.at_id { - true if (self.predicate)(&s.data) => { - self.subscriber.at_id = s.current_id; - Poll::Ready(s.data.clone()) - } - _ => { - if let Some(cx) = cx { - s.wakers.register(cx.waker()); - } - Poll::Pending - } - } - }) - } -} - -#[cfg(test)] -mod tests { - use futures_executor::block_on; - - use super::*; - use crate::blocking_mutex::raw::CriticalSectionRawMutex; - - #[test] - fn multiple_writes() { - let f = async { - static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); - - // Obtain Receivers - let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); - let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); - - SOME_SIGNAL.write(10); - - // Receive the new value - assert_eq!(rcv0.changed().await, 10); - assert_eq!(rcv1.changed().await, 10); - - // No update - assert_eq!(rcv0.try_changed(), None); - assert_eq!(rcv1.try_changed(), None); - - SOME_SIGNAL.write(20); - - assert_eq!(rcv0.changed().await, 20); - assert_eq!(rcv1.changed().await, 20); - }; - block_on(f); - } - - #[test] - fn max_receivers() { - let f = async { - static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); - - // Obtain Receivers - let _ = SOME_SIGNAL.receiver().unwrap(); - let _ = SOME_SIGNAL.receiver().unwrap(); - assert!(SOME_SIGNAL.receiver().is_err()); - }; - block_on(f); - } - - // Really weird edge case, but it's possible to have a receiver that never gets a value. - #[test] - fn receive_initial() { - let f = async { - static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); - - // Obtain Receivers - let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); - let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); - - assert_eq!(rcv0.try_changed(), Some(0)); - assert_eq!(rcv1.try_changed(), Some(0)); - - assert_eq!(rcv0.try_changed(), None); - assert_eq!(rcv1.try_changed(), None); - }; - block_on(f); - } - - #[test] - fn count_ids() { - let f = async { - static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); - - // Obtain Receivers - let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); - let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); - - SOME_SIGNAL.write(10); - - assert_eq!(rcv0.changed().await, 10); - assert_eq!(rcv1.changed().await, 10); - - assert_eq!(rcv0.try_changed(), None); - assert_eq!(rcv1.try_changed(), None); - - SOME_SIGNAL.write(20); - SOME_SIGNAL.write(20); - SOME_SIGNAL.write(20); - - assert_eq!(rcv0.changed().await, 20); - assert_eq!(rcv1.changed().await, 20); - - assert_eq!(rcv0.try_changed(), None); - assert_eq!(rcv1.try_changed(), None); - - assert_eq!(SOME_SIGNAL.get_id(), 5); - }; - block_on(f); - } - - #[test] - fn peek_still_await() { - let f = async { - static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); - - // Obtain Receivers - let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); - let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); - - SOME_SIGNAL.write(10); - - assert_eq!(rcv0.peek(), 10); - assert_eq!(rcv1.peek(), 10); - - assert_eq!(rcv0.changed().await, 10); - assert_eq!(rcv1.changed().await, 10); - }; - block_on(f); - } - - #[test] - fn predicate() { - let f = async { - static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); - - // Obtain Receivers - let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); - let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); - - SOME_SIGNAL.write(20); - - assert_eq!(rcv0.changed_and(|x| x > &10).await, 20); - assert_eq!(rcv1.try_changed_and(|x| x > &30), None); - }; - block_on(f); - } - - #[test] - fn mutable_predicate() { - let f = async { - static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); - - // Obtain Receivers - let mut rcv = SOME_SIGNAL.receiver().unwrap(); - - SOME_SIGNAL.write(10); - - let mut largest = 0; - let mut predicate = |x: &u8| { - if *x > largest { - largest = *x; - } - true - }; - - assert_eq!(rcv.changed_and(&mut predicate).await, 10); - - SOME_SIGNAL.write(20); - - assert_eq!(rcv.changed_and(&mut predicate).await, 20); - - SOME_SIGNAL.write(5); - - assert_eq!(rcv.changed_and(&mut predicate).await, 5); - - assert_eq!(largest, 20) - }; - block_on(f); - } - - #[test] - fn peek_and() { - let f = async { - static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); - - // Obtain Receivers - let mut rcv0 = SOME_SIGNAL.receiver().unwrap(); - let mut rcv1 = SOME_SIGNAL.receiver().unwrap(); - - SOME_SIGNAL.write(20); - - assert_eq!(rcv0.peek_and(|x| x > &10), Some(20)); - assert_eq!(rcv1.peek_and(|x| x > &30), None); - - assert_eq!(rcv0.changed().await, 20); - assert_eq!(rcv1.changed().await, 20); - }; - block_on(f); - } - - #[test] - fn peek_with_static() { - let f = async { - static SOME_SIGNAL: MultiSignal = MultiSignal::new(0); - - // Obtain Receivers - let rcv0 = SOME_SIGNAL.receiver().unwrap(); - let rcv1 = SOME_SIGNAL.receiver().unwrap(); - - SOME_SIGNAL.write(20); - - assert_eq!(rcv0.peek(), 20); - assert_eq!(rcv1.peek(), 20); - assert_eq!(SOME_SIGNAL.peek(), 20); - assert_eq!(SOME_SIGNAL.peek_and(|x| x > &30), None); - }; - block_on(f); - } -} diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs new file mode 100644 index 000000000..7e5e92741 --- /dev/null +++ b/embassy-sync/src/watch.rs @@ -0,0 +1,515 @@ +//! A synchronization primitive for passing the latest value to **multiple** tasks. + +use core::cell::RefCell; +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; +use core::task::{Context, Poll}; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::waitqueue::MultiWakerRegistration; + +/// A `Watch` is a single-slot signaling primitive, which can awake `N` up to separate [`Receiver`]s. +/// +/// Similar to a [`Signal`](crate::signal::Signal), except `Watch` allows for multiple tasks to +/// `.await` the latest value, and all receive it. +/// +/// This is similar to a [`PubSubChannel`](crate::pubsub::PubSubChannel) with a buffer size of 1, except +/// "sending" to it (calling [`Watch::write`]) will immediately overwrite the previous value instead +/// of waiting for the receivers to pop the previous value. +/// +/// `Watch` is useful when a single task is responsible for updating a value or "state", which multiple other +/// tasks are interested in getting notified about changes to the latest value of. It is therefore fine for +/// [`Receiver`]s to "lose" stale values. +/// +/// Anyone with a reference to the Watch can update or peek the value. Watches are generally declared +/// as `static`s and then borrowed as required to either [`Watch::peek`] the value or obtain a [`Receiver`] +/// with [`Watch::receiver`] which has async methods. +/// ``` +/// +/// use futures_executor::block_on; +/// use embassy_sync::watch::Watch; +/// use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +/// +/// let f = async { +/// +/// static WATCH: Watch = Watch::new(); +/// +/// // Obtain Receivers +/// let mut rcv0 = WATCH.receiver().unwrap(); +/// let mut rcv1 = WATCH.receiver().unwrap(); +/// assert!(WATCH.receiver().is_err()); +/// +/// assert_eq!(rcv1.try_changed(), None); +/// +/// WATCH.write(10); +/// assert_eq!(WATCH.try_peek(), Some(10)); +/// +/// +/// // Receive the new value +/// assert_eq!(rcv0.changed().await, 10); +/// assert_eq!(rcv1.try_changed(), Some(10)); +/// +/// // No update +/// assert_eq!(rcv0.try_changed(), None); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// WATCH.write(20); +/// +/// // Defference `between` peek `get`. +/// assert_eq!(rcv0.peek().await, 20); +/// assert_eq!(rcv1.get().await, 20); +/// +/// assert_eq!(rcv0.try_changed(), Some(20)); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// }; +/// block_on(f); +/// ``` +pub struct Watch { + mutex: Mutex>>, +} + +struct WatchState { + data: Option, + current_id: u64, + wakers: MultiWakerRegistration, + receiver_count: usize, +} + +/// A trait representing the 'inner' behavior of the `Watch`. +pub trait WatchBehavior { + /// Poll the `Watch` for the current value, **without** making it as seen. + fn inner_poll_peek(&self, cx: &mut Context<'_>) -> Poll; + + /// Tries to peek the value of the `Watch`, **without** marking it as seen. + fn inner_try_peek(&self) -> Option; + + /// Poll the `Watch` for the current value, making it as seen. + fn inner_poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; + + /// Tries to get the value of the `Watch`, marking it as seen. + fn inner_try_get(&self, id: &mut u64) -> Option; + + /// Poll the `Watch` for a changed value, marking it as seen. + fn inner_poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; + + /// Tries to retrieve the value of the `Watch` if it has changed, marking it as seen. + fn inner_try_changed(&self, id: &mut u64) -> Option; + + /// Checks if the `Watch` is been initialized with a value. + fn inner_contains_value(&self) -> bool; +} + +impl WatchBehavior for Watch { + fn inner_poll_peek(&self, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match &s.data { + Some(data) => Poll::Ready(data.clone()), + None => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn inner_try_peek(&self) -> Option { + self.mutex.lock(|state| state.borrow().data.clone()) + } + + fn inner_poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match &s.data { + Some(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + None => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn inner_try_get(&self, id: &mut u64) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + *id = s.current_id; + state.borrow().data.clone() + }) + } + + fn inner_poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match (&s.data, s.current_id > *id) { + (Some(data), true) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn inner_try_changed(&self, id: &mut u64) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match s.current_id > *id { + true => { + *id = s.current_id; + state.borrow().data.clone() + } + false => None, + } + }) + } + + fn inner_contains_value(&self) -> bool { + self.mutex.lock(|state| state.borrow().data.is_some()) + } +} + +#[derive(Debug)] +/// An error that can occur when a `Watch` returns a `Result::Err(_)`. +pub enum Error { + /// The maximum number of [`Receiver`](crate::watch::Receiver)/[`DynReceiver`](crate::watch::DynReceiver) has been reached. + MaximumReceiversReached, +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Watch { + /// Create a new `Watch` channel. + pub const fn new() -> Self { + Self { + mutex: Mutex::new(RefCell::new(WatchState { + data: None, + current_id: 0, + wakers: MultiWakerRegistration::new(), + receiver_count: 0, + })), + } + } + + /// Write a new value to the `Watch`. + pub fn write(&self, val: T) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = Some(val); + s.current_id += 1; + s.wakers.wake(); + }) + } + + /// Create a new [`Receiver`] for the `Watch`. + pub fn receiver(&self) -> Result, Error> { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if s.receiver_count < N { + s.receiver_count += 1; + Ok(Receiver(Rcv::new(self))) + } else { + Err(Error::MaximumReceiversReached) + } + }) + } + + /// Create a new [`DynReceiver`] for the `Watch`. + pub fn dyn_receiver(&self) -> Result, Error> { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if s.receiver_count < N { + s.receiver_count += 1; + Ok(DynReceiver(Rcv::new(self))) + } else { + Err(Error::MaximumReceiversReached) + } + }) + } + + /// Tries to retrieve the value of the `Watch`. + pub fn try_peek(&self) -> Option { + self.inner_try_peek() + } + + /// Returns true if the `Watch` contains a value. + pub fn contains_value(&self) -> bool { + self.inner_contains_value() + } + + /// Clears the value of the `Watch`. This will cause calls to [`Rcv::get`] and [`Rcv::peek`] to be pending. + pub fn clear(&self) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = None; + }) + } +} + +/// A receiver can `.await` a change in the `Watch` value. +pub struct Rcv<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + at_id: u64, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W) -> Self { + Self { + watch, + at_id: 0, + _phantom: PhantomData, + } + } + + /// Returns the current value of the `Watch` if it is initialized, **without** marking it as seen. + pub async fn peek(&self) -> T { + poll_fn(|cx| self.watch.inner_poll_peek(cx)).await + } + + /// Tries to peek the current value of the `Watch` without waiting, and **without** marking it as seen. + pub fn try_peek(&self) -> Option { + self.watch.inner_try_peek() + } + + /// Returns the current value of the `Watch` if it is initialized, marking it as seen. + pub async fn get(&mut self) -> T { + poll_fn(|cx| self.watch.inner_poll_get(&mut self.at_id, cx)).await + } + + /// Tries to get the current value of the `Watch` without waiting, marking it as seen. + pub fn try_get(&mut self) -> Option { + self.watch.inner_try_get(&mut self.at_id) + } + + /// Waits for the `Watch` to change and returns the new value, marking it as seen. + pub async fn changed(&mut self) -> T { + poll_fn(|cx| self.watch.inner_poll_changed(&mut self.at_id, cx)).await + } + + /// Tries to get the new value of the watch without waiting, marking it as seen. + pub fn try_changed(&mut self) -> Option { + self.watch.inner_try_changed(&mut self.at_id) + } + + /// Checks if the `Watch` contains a value. If this returns true, + /// then awaiting [`Rcv::get`] and [`Rcv::peek`] will return immediately. + pub fn contains_value(&self) -> bool { + self.watch.inner_contains_value() + } +} + +/// A receiver of a `Watch` channel. +pub struct Receiver<'a, M: RawMutex, T: Clone, const N: usize>(Rcv<'a, T, Watch>); + +/// A receiver which holds a **reference** to a `Watch` channel. +/// +/// This is an alternative to [`Receiver`] with a simpler type definition, at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct DynReceiver<'a, T: Clone>(Rcv<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Receiver<'a, M, T, N> { + type Target = Rcv<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Receiver<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'a, T: Clone> Deref for DynReceiver<'a, T> { + type Target = Rcv<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynReceiver<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[cfg(test)] +mod tests { + use futures_executor::block_on; + + use super::*; + use crate::blocking_mutex::raw::CriticalSectionRawMutex; + + #[test] + fn multiple_writes() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain Receivers + let mut rcv0 = WATCH.receiver().unwrap(); + let mut rcv1 = WATCH.dyn_receiver().unwrap(); + + WATCH.write(10); + + // Receive the new value + assert_eq!(rcv0.changed().await, 10); + assert_eq!(rcv1.changed().await, 10); + + // No update + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + + WATCH.write(20); + + assert_eq!(rcv0.changed().await, 20); + assert_eq!(rcv1.changed().await, 20); + }; + block_on(f); + } + + #[test] + fn max_receivers() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain Receivers + let _ = WATCH.receiver().unwrap(); + let _ = WATCH.receiver().unwrap(); + assert!(WATCH.receiver().is_err()); + }; + block_on(f); + } + + #[test] + fn receive_initial() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain Receivers + let mut rcv0 = WATCH.receiver().unwrap(); + let mut rcv1 = WATCH.receiver().unwrap(); + + assert_eq!(rcv0.contains_value(), false); + + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + + WATCH.write(0); + + assert_eq!(rcv0.contains_value(), true); + + assert_eq!(rcv0.try_changed(), Some(0)); + assert_eq!(rcv1.try_changed(), Some(0)); + }; + block_on(f); + } + + #[test] + fn peek_get_changed() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain Receivers + let mut rcv0 = WATCH.receiver().unwrap(); + + WATCH.write(10); + + // Ensure peek does not mark as seen + assert_eq!(rcv0.peek().await, 10); + assert_eq!(rcv0.try_changed(), Some(10)); + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv0.peek().await, 10); + + WATCH.write(20); + + // Ensure get does mark as seen + assert_eq!(rcv0.get().await, 20); + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv0.try_get(), Some(20)); + }; + block_on(f); + } + + #[test] + fn count_ids() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain Receivers + let mut rcv0 = WATCH.receiver().unwrap(); + let mut rcv1 = WATCH.receiver().unwrap(); + + let get_id = || WATCH.mutex.lock(|state| state.borrow().current_id); + + WATCH.write(10); + + assert_eq!(rcv0.changed().await, 10); + assert_eq!(rcv1.changed().await, 10); + + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + + WATCH.write(20); + WATCH.write(20); + WATCH.write(20); + + assert_eq!(rcv0.changed().await, 20); + assert_eq!(rcv1.changed().await, 20); + + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + + assert_eq!(get_id(), 4); + }; + block_on(f); + } + + #[test] + fn peek_still_await() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain Receivers + let mut rcv0 = WATCH.receiver().unwrap(); + let mut rcv1 = WATCH.receiver().unwrap(); + + WATCH.write(10); + + assert_eq!(rcv0.peek().await, 10); + assert_eq!(rcv1.try_peek(), Some(10)); + + assert_eq!(rcv0.changed().await, 10); + assert_eq!(rcv1.changed().await, 10); + }; + block_on(f); + } + + #[test] + fn peek_with_static() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain Receivers + let rcv0 = WATCH.receiver().unwrap(); + let rcv1 = WATCH.receiver().unwrap(); + + WATCH.write(20); + + assert_eq!(rcv0.peek().await, 20); + assert_eq!(rcv1.peek().await, 20); + assert_eq!(WATCH.try_peek(), Some(20)); + }; + block_on(f); + } +} From ae2f10992149279884ea564b00eb18c8bf1f464e Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Thu, 29 Feb 2024 16:45:44 +0100 Subject: [PATCH 007/144] Added sender types, support for dropping receivers, converting to dyn-types, revised tests. --- embassy-sync/src/watch.rs | 537 +++++++++++++++++++++++++++----------- 1 file changed, 382 insertions(+), 155 deletions(-) diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 7e5e92741..3e22b1e7b 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -1,4 +1,4 @@ -//! A synchronization primitive for passing the latest value to **multiple** tasks. +//! A synchronization primitive for passing the latest value to **multiple** receivers. use core::cell::RefCell; use core::future::poll_fn; @@ -10,22 +10,17 @@ use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex; use crate::waitqueue::MultiWakerRegistration; -/// A `Watch` is a single-slot signaling primitive, which can awake `N` up to separate [`Receiver`]s. +/// The `Watch` is a single-slot signaling primitive that allows multiple receivers to concurrently await +/// changes to the value. Unlike a [`Signal`](crate::signal::Signal), `Watch` supports multiple receivers, +/// and unlike a [`PubSubChannel`](crate::pubsub::PubSubChannel), `Watch` immediately overwrites the previous +/// value when a new one is sent, without waiting for all receivers to read the previous value. /// -/// Similar to a [`Signal`](crate::signal::Signal), except `Watch` allows for multiple tasks to -/// `.await` the latest value, and all receive it. +/// This makes `Watch` particularly useful when a single task updates a value or "state", and multiple other tasks +/// need to be notified about changes to this value asynchronously. Receivers may "lose" stale values, as they are +/// always provided with the latest value. /// -/// This is similar to a [`PubSubChannel`](crate::pubsub::PubSubChannel) with a buffer size of 1, except -/// "sending" to it (calling [`Watch::write`]) will immediately overwrite the previous value instead -/// of waiting for the receivers to pop the previous value. -/// -/// `Watch` is useful when a single task is responsible for updating a value or "state", which multiple other -/// tasks are interested in getting notified about changes to the latest value of. It is therefore fine for -/// [`Receiver`]s to "lose" stale values. -/// -/// Anyone with a reference to the Watch can update or peek the value. Watches are generally declared -/// as `static`s and then borrowed as required to either [`Watch::peek`] the value or obtain a [`Receiver`] -/// with [`Watch::receiver`] which has async methods. +/// Typically, `Watch` instances are declared as `static`, and a [`Sender`] and [`Receiver`] +/// (or [`DynSender`] and/or [`DynReceiver`]) are obtained and passed to the relevant parts of the program. /// ``` /// /// use futures_executor::block_on; @@ -36,18 +31,18 @@ use crate::waitqueue::MultiWakerRegistration; /// /// static WATCH: Watch = Watch::new(); /// -/// // Obtain Receivers +/// // Obtain receivers and sender /// let mut rcv0 = WATCH.receiver().unwrap(); -/// let mut rcv1 = WATCH.receiver().unwrap(); -/// assert!(WATCH.receiver().is_err()); +/// let mut rcv1 = WATCH.dyn_receiver().unwrap(); +/// let mut snd = WATCH.sender(); /// +/// // No more receivers, and no update +/// assert!(WATCH.receiver().is_err()); /// assert_eq!(rcv1.try_changed(), None); /// -/// WATCH.write(10); -/// assert_eq!(WATCH.try_peek(), Some(10)); -/// +/// snd.send(10); /// -/// // Receive the new value +/// // Receive the new value (async or try) /// assert_eq!(rcv0.changed().await, 10); /// assert_eq!(rcv1.try_changed(), Some(10)); /// @@ -55,13 +50,14 @@ use crate::waitqueue::MultiWakerRegistration; /// assert_eq!(rcv0.try_changed(), None); /// assert_eq!(rcv1.try_changed(), None); /// -/// WATCH.write(20); +/// snd.send(20); /// -/// // Defference `between` peek `get`. +/// // Peek does not mark the value as seen /// assert_eq!(rcv0.peek().await, 20); -/// assert_eq!(rcv1.get().await, 20); -/// /// assert_eq!(rcv0.try_changed(), Some(20)); +/// +/// // Get marks the value as seen +/// assert_eq!(rcv1.get().await, 20); /// assert_eq!(rcv1.try_changed(), None); /// /// }; @@ -80,30 +76,57 @@ struct WatchState { /// A trait representing the 'inner' behavior of the `Watch`. pub trait WatchBehavior { + /// Sends a new value to the `Watch`. + fn send(&self, val: T); + + /// Clears the value of the `Watch`. + fn clear(&self); + /// Poll the `Watch` for the current value, **without** making it as seen. - fn inner_poll_peek(&self, cx: &mut Context<'_>) -> Poll; + fn poll_peek(&self, cx: &mut Context<'_>) -> Poll; /// Tries to peek the value of the `Watch`, **without** marking it as seen. - fn inner_try_peek(&self) -> Option; + fn try_peek(&self) -> Option; /// Poll the `Watch` for the current value, making it as seen. - fn inner_poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; + fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; /// Tries to get the value of the `Watch`, marking it as seen. - fn inner_try_get(&self, id: &mut u64) -> Option; + fn try_get(&self, id: &mut u64) -> Option; /// Poll the `Watch` for a changed value, marking it as seen. - fn inner_poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; + fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; /// Tries to retrieve the value of the `Watch` if it has changed, marking it as seen. - fn inner_try_changed(&self, id: &mut u64) -> Option; + fn try_changed(&self, id: &mut u64) -> Option; /// Checks if the `Watch` is been initialized with a value. - fn inner_contains_value(&self) -> bool; + fn contains_value(&self) -> bool; + + /// Used when a receiver is dropped to decrement the receiver count. + /// + /// ## This method should not be called by the user. + fn drop_receiver(&self); } impl WatchBehavior for Watch { - fn inner_poll_peek(&self, cx: &mut Context<'_>) -> Poll { + fn send(&self, val: T) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = Some(val); + s.current_id += 1; + s.wakers.wake(); + }) + } + + fn clear(&self) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = None; + }) + } + + fn poll_peek(&self, cx: &mut Context<'_>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); match &s.data { @@ -116,11 +139,11 @@ impl WatchBehavior for Watch }) } - fn inner_try_peek(&self) -> Option { + fn try_peek(&self) -> Option { self.mutex.lock(|state| state.borrow().data.clone()) } - fn inner_poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { + fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); match &s.data { @@ -136,7 +159,7 @@ impl WatchBehavior for Watch }) } - fn inner_try_get(&self, id: &mut u64) -> Option { + fn try_get(&self, id: &mut u64) -> Option { self.mutex.lock(|state| { let s = state.borrow(); *id = s.current_id; @@ -144,7 +167,7 @@ impl WatchBehavior for Watch }) } - fn inner_poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { + fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); match (&s.data, s.current_id > *id) { @@ -160,7 +183,7 @@ impl WatchBehavior for Watch }) } - fn inner_try_changed(&self, id: &mut u64) -> Option { + fn try_changed(&self, id: &mut u64) -> Option { self.mutex.lock(|state| { let s = state.borrow(); match s.current_id > *id { @@ -173,9 +196,16 @@ impl WatchBehavior for Watch }) } - fn inner_contains_value(&self) -> bool { + fn contains_value(&self) -> bool { self.mutex.lock(|state| state.borrow().data.is_some()) } + + fn drop_receiver(&self) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.receiver_count -= 1; + }) + } } #[derive(Debug)] @@ -198,14 +228,14 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Watch { } } - /// Write a new value to the `Watch`. - pub fn write(&self, val: T) { - self.mutex.lock(|state| { - let mut s = state.borrow_mut(); - s.data = Some(val); - s.current_id += 1; - s.wakers.wake(); - }) + /// Create a new [`Receiver`] for the `Watch`. + pub fn sender(&self) -> Sender<'_, M, T, N> { + Sender(Snd::new(self)) + } + + /// Create a new [`DynReceiver`] for the `Watch`. + pub fn dyn_sender(&self) -> DynSender<'_, T> { + DynSender(Snd::new(self)) } /// Create a new [`Receiver`] for the `Watch`. @@ -214,7 +244,7 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Watch { let mut s = state.borrow_mut(); if s.receiver_count < N { s.receiver_count += 1; - Ok(Receiver(Rcv::new(self))) + Ok(Receiver(Rcv::new(self, 0))) } else { Err(Error::MaximumReceiversReached) } @@ -227,29 +257,121 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Watch { let mut s = state.borrow_mut(); if s.receiver_count < N { s.receiver_count += 1; - Ok(DynReceiver(Rcv::new(self))) + Ok(DynReceiver(Rcv::new(self, 0))) } else { Err(Error::MaximumReceiversReached) } }) } +} + +/// A receiver can `.await` a change in the `Watch` value. +pub struct Snd<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Clone for Snd<'a, T, W> { + fn clone(&self) -> Self { + Self { + watch: self.watch, + _phantom: PhantomData, + } + } +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Snd<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W) -> Self { + Self { + watch, + _phantom: PhantomData, + } + } + + /// Sends a new value to the `Watch`. + pub fn send(&self, val: T) { + self.watch.send(val) + } + + /// Clears the value of the `Watch`. + /// This will cause calls to [`Rcv::get`] and [`Rcv::peek`] to be pending. + pub fn clear(&self) { + self.watch.clear() + } /// Tries to retrieve the value of the `Watch`. pub fn try_peek(&self) -> Option { - self.inner_try_peek() + self.watch.try_peek() } /// Returns true if the `Watch` contains a value. pub fn contains_value(&self) -> bool { - self.inner_contains_value() + self.watch.contains_value() } +} - /// Clears the value of the `Watch`. This will cause calls to [`Rcv::get`] and [`Rcv::peek`] to be pending. - pub fn clear(&self) { - self.mutex.lock(|state| { - let mut s = state.borrow_mut(); - s.data = None; - }) +/// A sender of a `Watch` channel. +/// +/// For a simpler type definition, consider [`DynSender`] at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct Sender<'a, M: RawMutex, T: Clone, const N: usize>(Snd<'a, T, Watch>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> Clone for Sender<'a, M, T, N> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Sender<'a, M, T, N> { + /// Converts the `Sender` into a [`DynSender`]. + pub fn as_dyn(self) -> DynSender<'a, T> { + DynSender(Snd::new(self.watch)) + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for Sender<'a, M, T, N> { + fn into(self) -> DynSender<'a, T> { + self.as_dyn() + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Sender<'a, M, T, N> { + type Target = Snd<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Sender<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A sender which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`Sender`] with a simpler type definition, +pub struct DynSender<'a, T: Clone>(Snd<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, T: Clone> Clone for DynSender<'a, T> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl<'a, T: Clone> Deref for DynSender<'a, T> { + type Target = Snd<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynSender<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 } } @@ -262,59 +384,83 @@ pub struct Rcv<'a, T: Clone, W: WatchBehavior + ?Sized> { impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { /// Creates a new `Receiver` with a reference to the `Watch`. - fn new(watch: &'a W) -> Self { + fn new(watch: &'a W, at_id: u64) -> Self { Self { watch, - at_id: 0, + at_id, _phantom: PhantomData, } } - /// Returns the current value of the `Watch` if it is initialized, **without** marking it as seen. + /// Returns the current value of the `Watch` once it is initialized, **without** marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. pub async fn peek(&self) -> T { - poll_fn(|cx| self.watch.inner_poll_peek(cx)).await + poll_fn(|cx| self.watch.poll_peek(cx)).await } /// Tries to peek the current value of the `Watch` without waiting, and **without** marking it as seen. pub fn try_peek(&self) -> Option { - self.watch.inner_try_peek() + self.watch.try_peek() } - /// Returns the current value of the `Watch` if it is initialized, marking it as seen. + /// Returns the current value of the `Watch` once it is initialized, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. pub async fn get(&mut self) -> T { - poll_fn(|cx| self.watch.inner_poll_get(&mut self.at_id, cx)).await + poll_fn(|cx| self.watch.poll_get(&mut self.at_id, cx)).await } /// Tries to get the current value of the `Watch` without waiting, marking it as seen. pub fn try_get(&mut self) -> Option { - self.watch.inner_try_get(&mut self.at_id) + self.watch.try_get(&mut self.at_id) } /// Waits for the `Watch` to change and returns the new value, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. pub async fn changed(&mut self) -> T { - poll_fn(|cx| self.watch.inner_poll_changed(&mut self.at_id, cx)).await + poll_fn(|cx| self.watch.poll_changed(&mut self.at_id, cx)).await } /// Tries to get the new value of the watch without waiting, marking it as seen. pub fn try_changed(&mut self) -> Option { - self.watch.inner_try_changed(&mut self.at_id) + self.watch.try_changed(&mut self.at_id) } /// Checks if the `Watch` contains a value. If this returns true, /// then awaiting [`Rcv::get`] and [`Rcv::peek`] will return immediately. pub fn contains_value(&self) -> bool { - self.watch.inner_contains_value() + self.watch.contains_value() + } +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Drop for Rcv<'a, T, W> { + fn drop(&mut self) { + self.watch.drop_receiver(); } } /// A receiver of a `Watch` channel. pub struct Receiver<'a, M: RawMutex, T: Clone, const N: usize>(Rcv<'a, T, Watch>); -/// A receiver which holds a **reference** to a `Watch` channel. -/// -/// This is an alternative to [`Receiver`] with a simpler type definition, at the expense of -/// some runtime performance due to dynamic dispatch. -pub struct DynReceiver<'a, T: Clone>(Rcv<'a, T, dyn WatchBehavior + 'a>); +impl<'a, M: RawMutex, T: Clone, const N: usize> Receiver<'a, M, T, N> { + /// Converts the `Receiver` into a [`DynReceiver`]. + pub fn as_dyn(self) -> DynReceiver<'a, T> { + // We need to increment the receiver count since the original + // receiver is being dropped, which decrements the count. + self.watch.mutex.lock(|state| { + state.borrow_mut().receiver_count += 1; + }); + DynReceiver(Rcv::new(self.0.watch, self.at_id)) + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for Receiver<'a, M, T, N> { + fn into(self) -> DynReceiver<'a, T> { + self.as_dyn() + } +} impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Receiver<'a, M, T, N> { type Target = Rcv<'a, T, Watch>; @@ -330,6 +476,12 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Receiver<'a, M, T, } } +/// A receiver which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`Receiver`] with a simpler type definition, at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct DynReceiver<'a, T: Clone>(Rcv<'a, T, dyn WatchBehavior + 'a>); + impl<'a, T: Clone> Deref for DynReceiver<'a, T> { type Target = Rcv<'a, T, dyn WatchBehavior + 'a>; @@ -348,167 +500,242 @@ impl<'a, T: Clone> DerefMut for DynReceiver<'a, T> { mod tests { use futures_executor::block_on; - use super::*; + use super::Watch; use crate::blocking_mutex::raw::CriticalSectionRawMutex; #[test] - fn multiple_writes() { + fn multiple_sends() { let f = async { - static WATCH: Watch = Watch::new(); + static WATCH: Watch = Watch::new(); - // Obtain Receivers - let mut rcv0 = WATCH.receiver().unwrap(); - let mut rcv1 = WATCH.dyn_receiver().unwrap(); + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); - WATCH.write(10); + // Not initialized + assert_eq!(rcv.try_changed(), None); // Receive the new value - assert_eq!(rcv0.changed().await, 10); - assert_eq!(rcv1.changed().await, 10); + snd.send(10); + assert_eq!(rcv.changed().await, 10); + + // Receive another value + snd.send(20); + assert_eq!(rcv.try_changed(), Some(20)); // No update - assert_eq!(rcv0.try_changed(), None); - assert_eq!(rcv1.try_changed(), None); - - WATCH.write(20); - - assert_eq!(rcv0.changed().await, 20); - assert_eq!(rcv1.changed().await, 20); + assert_eq!(rcv.try_changed(), None); }; block_on(f); } #[test] - fn max_receivers() { + fn receive_after_create() { let f = async { - static WATCH: Watch = Watch::new(); + static WATCH: Watch = Watch::new(); - // Obtain Receivers - let _ = WATCH.receiver().unwrap(); - let _ = WATCH.receiver().unwrap(); - assert!(WATCH.receiver().is_err()); + // Obtain sender and send value + let snd = WATCH.sender(); + snd.send(10); + + // Obtain receiver and receive value + let mut rcv = WATCH.receiver().unwrap(); + assert_eq!(rcv.try_changed(), Some(10)); }; block_on(f); } #[test] - fn receive_initial() { + fn max_receivers_drop() { let f = async { static WATCH: Watch = Watch::new(); - // Obtain Receivers + // Try to create 3 receivers (only 2 can exist at once) + let rcv0 = WATCH.receiver(); + let rcv1 = WATCH.receiver(); + let rcv2 = WATCH.receiver(); + + // Ensure the first two are successful and the third is not + assert!(rcv0.is_ok()); + assert!(rcv1.is_ok()); + assert!(rcv2.is_err()); + + // Drop the first receiver + drop(rcv0); + + // Create another receiver and ensure it is successful + let rcv3 = WATCH.receiver(); + assert!(rcv3.is_ok()); + }; + block_on(f); + } + + #[test] + fn multiple_receivers() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receivers and sender let mut rcv0 = WATCH.receiver().unwrap(); let mut rcv1 = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); - assert_eq!(rcv0.contains_value(), false); - + // No update for both assert_eq!(rcv0.try_changed(), None); assert_eq!(rcv1.try_changed(), None); - WATCH.write(0); - - assert_eq!(rcv0.contains_value(), true); + // Send a new value + snd.send(0); + // Both receivers receive the new value assert_eq!(rcv0.try_changed(), Some(0)); assert_eq!(rcv1.try_changed(), Some(0)); }; block_on(f); } + #[test] + fn clone_senders() { + let f = async { + // Obtain different ways to send + static WATCH: Watch = Watch::new(); + let snd0 = WATCH.sender(); + let snd1 = snd0.clone(); + + // Obtain Receiver + let mut rcv = WATCH.receiver().unwrap().as_dyn(); + + // Send a value from first sender + snd0.send(10); + assert_eq!(rcv.try_changed(), Some(10)); + + // Send a value from second sender + snd1.send(20); + assert_eq!(rcv.try_changed(), Some(20)); + }; + block_on(f); + } + #[test] fn peek_get_changed() { let f = async { static WATCH: Watch = Watch::new(); - // Obtain Receivers - let mut rcv0 = WATCH.receiver().unwrap(); + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); - WATCH.write(10); + // Send a value + snd.send(10); // Ensure peek does not mark as seen - assert_eq!(rcv0.peek().await, 10); - assert_eq!(rcv0.try_changed(), Some(10)); - assert_eq!(rcv0.try_changed(), None); - assert_eq!(rcv0.peek().await, 10); + assert_eq!(rcv.peek().await, 10); + assert_eq!(rcv.try_changed(), Some(10)); + assert_eq!(rcv.try_changed(), None); + assert_eq!(rcv.try_peek(), Some(10)); - WATCH.write(20); + // Send a value + snd.send(20); // Ensure get does mark as seen - assert_eq!(rcv0.get().await, 20); - assert_eq!(rcv0.try_changed(), None); - assert_eq!(rcv0.try_get(), Some(20)); + assert_eq!(rcv.get().await, 20); + assert_eq!(rcv.try_changed(), None); + assert_eq!(rcv.try_get(), Some(20)); }; block_on(f); } #[test] - fn count_ids() { + fn use_dynamics() { let f = async { static WATCH: Watch = Watch::new(); - // Obtain Receivers - let mut rcv0 = WATCH.receiver().unwrap(); - let mut rcv1 = WATCH.receiver().unwrap(); + // Obtain receiver and sender + let mut dyn_rcv = WATCH.dyn_receiver().unwrap(); + let dyn_snd = WATCH.dyn_sender(); - let get_id = || WATCH.mutex.lock(|state| state.borrow().current_id); + // Send a value + dyn_snd.send(10); - WATCH.write(10); - - assert_eq!(rcv0.changed().await, 10); - assert_eq!(rcv1.changed().await, 10); - - assert_eq!(rcv0.try_changed(), None); - assert_eq!(rcv1.try_changed(), None); - - WATCH.write(20); - WATCH.write(20); - WATCH.write(20); - - assert_eq!(rcv0.changed().await, 20); - assert_eq!(rcv1.changed().await, 20); - - assert_eq!(rcv0.try_changed(), None); - assert_eq!(rcv1.try_changed(), None); - - assert_eq!(get_id(), 4); + // Ensure the dynamic receiver receives the value + assert_eq!(dyn_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), None); }; block_on(f); } #[test] - fn peek_still_await() { + fn convert_to_dyn() { let f = async { static WATCH: Watch = Watch::new(); - // Obtain Receivers - let mut rcv0 = WATCH.receiver().unwrap(); - let mut rcv1 = WATCH.receiver().unwrap(); + // Obtain receiver and sender + let rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); - WATCH.write(10); + // Convert to dynamic + let mut dyn_rcv = rcv.as_dyn(); + let dyn_snd = snd.as_dyn(); - assert_eq!(rcv0.peek().await, 10); - assert_eq!(rcv1.try_peek(), Some(10)); + // Send a value + dyn_snd.send(10); - assert_eq!(rcv0.changed().await, 10); - assert_eq!(rcv1.changed().await, 10); + // Ensure the dynamic receiver receives the value + assert_eq!(dyn_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), None); }; block_on(f); } #[test] - fn peek_with_static() { + fn dynamic_receiver_count() { let f = async { static WATCH: Watch = Watch::new(); - // Obtain Receivers - let rcv0 = WATCH.receiver().unwrap(); - let rcv1 = WATCH.receiver().unwrap(); + // Obtain receiver and sender + let rcv0 = WATCH.receiver(); + let rcv1 = WATCH.receiver(); + let rcv2 = WATCH.receiver(); - WATCH.write(20); + // Ensure the first two are successful and the third is not + assert!(rcv0.is_ok()); + assert!(rcv1.is_ok()); + assert!(rcv2.is_err()); - assert_eq!(rcv0.peek().await, 20); - assert_eq!(rcv1.peek().await, 20); - assert_eq!(WATCH.try_peek(), Some(20)); + // Convert to dynamic + let dyn_rcv0 = rcv0.unwrap().as_dyn(); + + // Drop the (now dynamic) receiver + drop(dyn_rcv0); + + // Create another receiver and ensure it is successful + let rcv3 = WATCH.receiver(); + let rcv4 = WATCH.receiver(); + assert!(rcv3.is_ok()); + assert!(rcv4.is_err()); + }; + block_on(f); + } + + #[test] + fn contains_value() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // check if the watch contains a value + assert_eq!(rcv.contains_value(), false); + assert_eq!(snd.contains_value(), false); + + // Send a value + snd.send(10); + + // check if the watch contains a value + assert_eq!(rcv.contains_value(), true); + assert_eq!(snd.contains_value(), true); }; block_on(f); } From 3208e0fec4717ff1580d1cc0cf93e18cbba2db91 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Thu, 29 Feb 2024 16:56:52 +0100 Subject: [PATCH 008/144] Use Option instead of Result for receiver creation since it is the only way it can fail. --- embassy-sync/src/watch.rs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 3e22b1e7b..2bba93915 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -208,14 +208,7 @@ impl WatchBehavior for Watch } } -#[derive(Debug)] -/// An error that can occur when a `Watch` returns a `Result::Err(_)`. -pub enum Error { - /// The maximum number of [`Receiver`](crate::watch::Receiver)/[`DynReceiver`](crate::watch::DynReceiver) has been reached. - MaximumReceiversReached, -} - -impl<'a, M: RawMutex, T: Clone, const N: usize> Watch { +impl Watch { /// Create a new `Watch` channel. pub const fn new() -> Self { Self { @@ -238,28 +231,30 @@ impl<'a, M: RawMutex, T: Clone, const N: usize> Watch { DynSender(Snd::new(self)) } - /// Create a new [`Receiver`] for the `Watch`. - pub fn receiver(&self) -> Result, Error> { + /// Try to create a new [`Receiver`] for the `Watch`. If the + /// maximum number of receivers has been reached, `None` is returned. + pub fn receiver(&self) -> Option> { self.mutex.lock(|state| { let mut s = state.borrow_mut(); if s.receiver_count < N { s.receiver_count += 1; - Ok(Receiver(Rcv::new(self, 0))) + Some(Receiver(Rcv::new(self, 0))) } else { - Err(Error::MaximumReceiversReached) + None } }) } - /// Create a new [`DynReceiver`] for the `Watch`. - pub fn dyn_receiver(&self) -> Result, Error> { + /// Try to create a new [`DynReceiver`] for the `Watch`. If the + /// maximum number of receivers has been reached, `None` is returned. + pub fn dyn_receiver(&self) -> Option> { self.mutex.lock(|state| { let mut s = state.borrow_mut(); if s.receiver_count < N { s.receiver_count += 1; - Ok(DynReceiver(Rcv::new(self, 0))) + Some(DynReceiver(Rcv::new(self, 0))) } else { - Err(Error::MaximumReceiversReached) + None } }) } From c08e75057a4282a0dfee13a6e181a2077944c1b0 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Thu, 29 Feb 2024 16:58:21 +0100 Subject: [PATCH 009/144] Update tests to reflect changes in previous commit --- embassy-sync/src/watch.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 2bba93915..1301eb817 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -551,16 +551,16 @@ mod tests { let rcv2 = WATCH.receiver(); // Ensure the first two are successful and the third is not - assert!(rcv0.is_ok()); - assert!(rcv1.is_ok()); - assert!(rcv2.is_err()); + assert!(rcv0.is_some()); + assert!(rcv1.is_some()); + assert!(rcv2.is_none()); // Drop the first receiver drop(rcv0); // Create another receiver and ensure it is successful let rcv3 = WATCH.receiver(); - assert!(rcv3.is_ok()); + assert!(rcv3.is_some()); }; block_on(f); } @@ -693,9 +693,9 @@ mod tests { let rcv2 = WATCH.receiver(); // Ensure the first two are successful and the third is not - assert!(rcv0.is_ok()); - assert!(rcv1.is_ok()); - assert!(rcv2.is_err()); + assert!(rcv0.is_some()); + assert!(rcv1.is_some()); + assert!(rcv2.is_none()); // Convert to dynamic let dyn_rcv0 = rcv0.unwrap().as_dyn(); @@ -706,8 +706,8 @@ mod tests { // Create another receiver and ensure it is successful let rcv3 = WATCH.receiver(); let rcv4 = WATCH.receiver(); - assert!(rcv3.is_ok()); - assert!(rcv4.is_err()); + assert!(rcv3.is_some()); + assert!(rcv4.is_none()); }; block_on(f); } From df282aa23d00b2f3116081be2b07ba0c9f810fc3 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Thu, 29 Feb 2024 16:59:58 +0100 Subject: [PATCH 010/144] Forgot to update doc comment --- embassy-sync/src/watch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 1301eb817..01d82def4 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -37,7 +37,7 @@ use crate::waitqueue::MultiWakerRegistration; /// let mut snd = WATCH.sender(); /// /// // No more receivers, and no update -/// assert!(WATCH.receiver().is_err()); +/// assert!(WATCH.receiver().is_none()); /// assert_eq!(rcv1.try_changed(), None); /// /// snd.send(10); From 311ab07a9af0029060813779038220481d1bf1c5 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Sat, 2 Mar 2024 00:14:11 +0100 Subject: [PATCH 011/144] Reintroduce predicate methods. Add ability for sender to modify value in-place. --- embassy-sync/src/watch.rs | 267 +++++++++++++++++++++++++++++++++++++- 1 file changed, 260 insertions(+), 7 deletions(-) diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 01d82def4..520696f7d 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -88,21 +88,48 @@ pub trait WatchBehavior { /// Tries to peek the value of the `Watch`, **without** marking it as seen. fn try_peek(&self) -> Option; + /// Poll the `Watch` for the value if it matches the predicate function + /// `f`, **without** making it as seen. + fn poll_peek_and(&self, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; + + /// Tries to peek the value of the `Watch` if it matches the predicate function `f`, **without** marking it as seen. + fn try_peek_and(&self, f: &mut dyn Fn(&T) -> bool) -> Option; + /// Poll the `Watch` for the current value, making it as seen. fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; /// Tries to get the value of the `Watch`, marking it as seen. fn try_get(&self, id: &mut u64) -> Option; + /// Poll the `Watch` for the value if it matches the predicate function + /// `f`, making it as seen. + fn poll_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; + + /// Tries to get the value of the `Watch` if it matches the predicate function + /// `f`, marking it as seen. + fn try_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option; + /// Poll the `Watch` for a changed value, marking it as seen. fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; /// Tries to retrieve the value of the `Watch` if it has changed, marking it as seen. fn try_changed(&self, id: &mut u64) -> Option; + /// Poll the `Watch` for a changed value that matches the predicate function + /// `f`, marking it as seen. + fn poll_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; + + /// Tries to retrieve the value of the `Watch` if it has changed and matches the + /// predicate function `f`, marking it as seen. + fn try_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option; + /// Checks if the `Watch` is been initialized with a value. fn contains_value(&self) -> bool; + /// Modify the value of the `Watch` using a closure. Returns `false` if the + /// `Watch` does not already contain a value. + fn modify(&self, f: &mut dyn Fn(&mut Option)); + /// Used when a receiver is dropped to decrement the receiver count. /// /// ## This method should not be called by the user. @@ -143,6 +170,29 @@ impl WatchBehavior for Watch self.mutex.lock(|state| state.borrow().data.clone()) } + fn poll_peek_and(&self, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match s.data { + Some(ref data) if f(data) => Poll::Ready(data.clone()), + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn try_peek_and(&self, f: &mut dyn Fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match s.data { + Some(ref data) if f(data) => Some(data.clone()), + _ => None, + } + }) + } + fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); @@ -167,6 +217,35 @@ impl WatchBehavior for Watch }) } + fn poll_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match s.data { + Some(ref data) if f(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn try_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match s.data { + Some(ref data) if f(data) => { + *id = s.current_id; + Some(data.clone()) + } + _ => None, + } + }) + } + fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); @@ -189,13 +268,42 @@ impl WatchBehavior for Watch match s.current_id > *id { true => { *id = s.current_id; - state.borrow().data.clone() + s.data.clone() } false => None, } }) } + fn poll_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match (&s.data, s.current_id > *id) { + (Some(data), true) if f(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn try_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match (&s.data, s.current_id > *id) { + (Some(data), true) if f(data) => { + *id = s.current_id; + s.data.clone() + } + _ => None, + } + }) + } + fn contains_value(&self) -> bool { self.mutex.lock(|state| state.borrow().data.is_some()) } @@ -206,6 +314,15 @@ impl WatchBehavior for Watch s.receiver_count -= 1; }) } + + fn modify(&self, f: &mut dyn Fn(&mut Option)) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + f(&mut s.data); + s.current_id += 1; + s.wakers.wake(); + }) + } } impl Watch { @@ -300,10 +417,27 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Snd<'a, T, W> { self.watch.try_peek() } + /// Tries to peek the current value of the `Watch` if it matches the predicate + /// function `f`. + pub fn try_peek_and(&self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_peek_and(&mut f) + } + /// Returns true if the `Watch` contains a value. pub fn contains_value(&self) -> bool { self.watch.contains_value() } + + /// Modify the value of the `Watch` using a closure. + pub fn modify(&self, mut f: F) + where + F: Fn(&mut Option), + { + self.watch.modify(&mut f) + } } /// A sender of a `Watch` channel. @@ -399,6 +533,26 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { self.watch.try_peek() } + /// Returns the current value of the `Watch` if it matches the predicate function `f`, + /// or waits for it to match, **without** marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn peek_and(&self, mut f: F) -> T + where + F: Fn(&T) -> bool, + { + poll_fn(|cx| self.watch.poll_peek_and(&mut f, cx)).await + } + + /// Tries to peek the current value of the `Watch` if it matches the predicate + /// function `f` without waiting, and **without** marking it as seen. + pub fn try_peek_and(&self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_peek_and(&mut f) + } + /// Returns the current value of the `Watch` once it is initialized, marking it as seen. /// /// **Note**: Futures do nothing unless you `.await` or poll them. @@ -411,6 +565,26 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { self.watch.try_get(&mut self.at_id) } + /// Returns the value of the `Watch` if it matches the predicate function `f`, + /// or waits for it to match, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn get_and(&mut self, mut f: F) -> T + where + F: Fn(&T) -> bool, + { + poll_fn(|cx| self.watch.poll_get_and(&mut self.at_id, &mut f, cx)).await + } + + /// Tries to get the current value of the `Watch` if it matches the predicate + /// function `f` without waiting, marking it as seen. + pub fn try_get_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_get_and(&mut self.at_id, &mut f) + } + /// Waits for the `Watch` to change and returns the new value, marking it as seen. /// /// **Note**: Futures do nothing unless you `.await` or poll them. @@ -423,6 +597,26 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { self.watch.try_changed(&mut self.at_id) } + /// Waits for the `Watch` to change to a value which satisfies the predicate + /// function `f` and returns the new value, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn changed_and(&mut self, mut f: F) -> T + where + F: Fn(&T) -> bool, + { + poll_fn(|cx| self.watch.poll_changed_and(&mut self.at_id, &mut f, cx)).await + } + + /// Tries to get the new value of the watch which satisfies the predicate + /// function `f` and returns the new value without waiting, marking it as seen. + pub fn try_changed_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_changed_and(&mut self.at_id, &mut f) + } + /// Checks if the `Watch` contains a value. If this returns true, /// then awaiting [`Rcv::get`] and [`Rcv::peek`] will return immediately. pub fn contains_value(&self) -> bool { @@ -442,12 +636,9 @@ pub struct Receiver<'a, M: RawMutex, T: Clone, const N: usize>(Rcv<'a, T, Watch< impl<'a, M: RawMutex, T: Clone, const N: usize> Receiver<'a, M, T, N> { /// Converts the `Receiver` into a [`DynReceiver`]. pub fn as_dyn(self) -> DynReceiver<'a, T> { - // We need to increment the receiver count since the original - // receiver is being dropped, which decrements the count. - self.watch.mutex.lock(|state| { - state.borrow_mut().receiver_count += 1; - }); - DynReceiver(Rcv::new(self.0.watch, self.at_id)) + let rcv = DynReceiver(Rcv::new(self.0.watch, self.at_id)); + core::mem::forget(self); // Ensures the destructor is not called + rcv } } @@ -524,6 +715,68 @@ mod tests { block_on(f); } + #[test] + fn sender_modify() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Receive the new value + snd.send(10); + assert_eq!(rcv.try_changed(), Some(10)); + + // Modify the value inplace + snd.modify(|opt|{ + if let Some(inner) = opt { + *inner += 5; + } + }); + + // Get the modified value + assert_eq!(rcv.try_changed(), Some(15)); + assert_eq!(rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn predicate_fn() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + snd.send(10); + assert_eq!(rcv.try_peek_and(|x| x > &5), Some(10)); + assert_eq!(rcv.try_peek_and(|x| x < &5), None); + assert!(rcv.try_changed().is_some()); + + snd.send(15); + assert_eq!(rcv.try_get_and(|x| x > &5), Some(15)); + assert_eq!(rcv.try_get_and(|x| x < &5), None); + assert!(rcv.try_changed().is_none()); + + snd.send(20); + assert_eq!(rcv.try_changed_and(|x| x > &5), Some(20)); + assert_eq!(rcv.try_changed_and(|x| x > &5), None); + + snd.send(25); + assert_eq!(rcv.try_changed_and(|x| x < &5), None); + assert_eq!(rcv.try_changed(), Some(25)); + + snd.send(30); + assert_eq!(rcv.changed_and(|x| x > &5).await, 30); + assert_eq!(rcv.peek_and(|x| x > &5).await, 30); + assert_eq!(rcv.get_and(|x| x > &5).await, 30); + }; + block_on(f); + } + #[test] fn receive_after_create() { let f = async { From e02a987bafd4f0fcf9d80e7c4f6e1504b8b02cec Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Sat, 2 Mar 2024 00:16:17 +0100 Subject: [PATCH 012/144] This one is for cargo fmt --- embassy-sync/src/watch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 520696f7d..298c09d43 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -729,7 +729,7 @@ mod tests { assert_eq!(rcv.try_changed(), Some(10)); // Modify the value inplace - snd.modify(|opt|{ + snd.modify(|opt| { if let Some(inner) = opt { *inner += 5; } From f6d92b76111d8619c92b13aa28e765abe356465c Mon Sep 17 00:00:00 2001 From: Badr Bouslikhin Date: Fri, 30 Aug 2024 18:52:23 +0200 Subject: [PATCH 013/144] fix(stm32): disable transmitter during half-duplex read --- embassy-stm32/src/usart/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index 89d92dda2..af34b548e 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -526,9 +526,12 @@ fn blocking_flush(info: &Info) -> Result<(), Error> { let r = info.regs; while !sr(r).read().tc() {} - // Enable Receiver after transmission complete for Half-Duplex mode + // Disable Transmitter and enable receiver after transmission complete for Half-Duplex mode if r.cr3().read().hdsel() { - r.cr1().modify(|reg| reg.set_re(true)); + r.cr1().modify(|reg| { + reg.set_te(false); + reg.set_re(true); + }); } Ok(()) From 8ac758bdee793496f842f4c267bda2b6f935a0bb Mon Sep 17 00:00:00 2001 From: Grant Miller Date: Fri, 6 Sep 2024 09:07:29 -0500 Subject: [PATCH 014/144] embassy-stm32: Add SimplePwmChannel --- embassy-stm32/src/timer/simple_pwm.rs | 112 ++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index b7771bd64..f94ed1be5 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -51,6 +51,96 @@ channel_impl!(new_ch2, Ch2, Channel2Pin); channel_impl!(new_ch3, Ch3, Channel3Pin); channel_impl!(new_ch4, Ch4, Channel4Pin); +/// A single channel of a pwm, obtained from [`SimplePwm::split`]. +/// +/// It is not possible to change the pwm frequency because +/// the frequency configuration is shared with all four channels. +pub struct SimplePwmChannel<'d, T: GeneralInstance4Channel> { + timer: &'d Timer<'d, T>, + channel: Channel, +} + +// TODO: check for RMW races +impl<'d, T: GeneralInstance4Channel> SimplePwmChannel<'d, T> { + /// Enable the given channel. + pub fn enable(&mut self) { + self.timer.enable_channel(self.channel, true); + } + + /// Disable the given channel. + pub fn disable(&mut self) { + self.timer.enable_channel(self.channel, false); + } + + /// Check whether given channel is enabled + pub fn is_enabled(&self) -> bool { + self.timer.get_channel_enable_state(self.channel) + } + + /// Get max duty value. + /// + /// This value depends on the configured frequency and the timer's clock rate from RCC. + pub fn get_max_duty(&self) -> u32 { + self.timer.get_max_compare_value() + 1 + } + + /// Set the duty for a given channel. + /// + /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. + pub fn set_duty(&mut self, duty: u32) { + assert!(duty <= self.get_max_duty()); + self.timer.set_compare_value(self.channel, duty) + } + + /// Get the duty for a given channel. + /// + /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. + pub fn get_duty(&self) -> u32 { + self.timer.get_compare_value(self.channel) + } + + /// Set the output polarity for a given channel. + pub fn set_polarity(&mut self, polarity: OutputPolarity) { + self.timer.set_output_polarity(self.channel, polarity); + } + + /// Set the output compare mode for a given channel. + pub fn set_output_compare_mode(&mut self, mode: OutputCompareMode) { + self.timer.set_output_compare_mode(self.channel, mode); + } +} + +/// A group of four [`SimplePwmChannel`]s, obtained from [`SimplePwm::split`]. +pub struct SimplePwmChannels<'d, T: GeneralInstance4Channel> { + /// Channel 1 + pub ch1: SimplePwmChannel<'d, T>, + /// Channel 2 + pub ch2: SimplePwmChannel<'d, T>, + /// Channel 3 + pub ch3: SimplePwmChannel<'d, T>, + /// Channel 4 + pub ch4: SimplePwmChannel<'d, T>, +} + +impl<'d, T: GeneralInstance4Channel> embedded_hal_1::pwm::ErrorType for SimplePwmChannel<'d, T> { + type Error = core::convert::Infallible; +} + +impl<'d, T: GeneralInstance4Channel> embedded_hal_1::pwm::SetDutyCycle for SimplePwmChannel<'d, T> { + fn max_duty_cycle(&self) -> u16 { + // TODO: panics if CCR is 0xFFFF + // TODO: rename get_max_duty to max_duty_cycle + unwrap!(self.get_max_duty().try_into()) + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + self.set_duty(duty.into()); + Ok(()) + } + + // TODO: default methods? +} + /// Simple PWM driver. pub struct SimplePwm<'d, T: GeneralInstance4Channel> { inner: Timer<'d, T>, @@ -89,6 +179,28 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { this } + fn channel(&self, channel: Channel) -> SimplePwmChannel<'_, T> { + SimplePwmChannel { + timer: &self.inner, + channel, + } + } + + /// Splits a [`SimplePwm`] into four pwm channels. + /// + /// This returns all four channels, including channels that + /// aren't configured with a [`PwmPin`]. + // TODO: I hate the name "split" + pub fn split(&mut self) -> SimplePwmChannels<'_, T> { + // TODO: pre-enable channels? + SimplePwmChannels { + ch1: self.channel(Channel::Ch1), + ch2: self.channel(Channel::Ch2), + ch3: self.channel(Channel::Ch3), + ch4: self.channel(Channel::Ch4), + } + } + /// Enable the given channel. pub fn enable(&mut self, channel: Channel) { self.inner.enable_channel(channel, true); From f7f062e0a3f303dfc4f976907b2555f2cfb48621 Mon Sep 17 00:00:00 2001 From: Grant Miller Date: Fri, 6 Sep 2024 10:25:29 -0500 Subject: [PATCH 015/144] Deduplicate SimplePwm's channel methods --- embassy-stm32/src/timer/simple_pwm.rs | 119 ++++++++++++++------------ 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index f94ed1be5..7179a7a59 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -51,7 +51,8 @@ channel_impl!(new_ch2, Ch2, Channel2Pin); channel_impl!(new_ch3, Ch3, Channel3Pin); channel_impl!(new_ch4, Ch4, Channel4Pin); -/// A single channel of a pwm, obtained from [`SimplePwm::split`]. +/// A single channel of a pwm, obtained from [`SimplePwm::split`], +/// [`SimplePwm::channel`], [`SimplePwm::ch1`], etc. /// /// It is not possible to change the pwm frequency because /// the frequency configuration is shared with all four channels. @@ -179,13 +180,52 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { this } - fn channel(&self, channel: Channel) -> SimplePwmChannel<'_, T> { + /// Get a single channel + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn channel(&mut self, channel: Channel) -> SimplePwmChannel<'_, T> { SimplePwmChannel { timer: &self.inner, channel, } } + /// Channel 1 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch1(&mut self) -> SimplePwmChannel<'_, T> { + self.channel(Channel::Ch1) + } + + /// Channel 2 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch2(&mut self) -> SimplePwmChannel<'_, T> { + self.channel(Channel::Ch2) + } + + /// Channel 3 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch3(&mut self) -> SimplePwmChannel<'_, T> { + self.channel(Channel::Ch3) + } + + /// Channel 4 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch4(&mut self) -> SimplePwmChannel<'_, T> { + self.channel(Channel::Ch4) + } + /// Splits a [`SimplePwm`] into four pwm channels. /// /// This returns all four channels, including channels that @@ -193,29 +233,21 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { // TODO: I hate the name "split" pub fn split(&mut self) -> SimplePwmChannels<'_, T> { // TODO: pre-enable channels? + + // we can't use self.channel() because that takes &mut self + let ch = |channel| SimplePwmChannel { + timer: &self.inner, + channel, + }; + SimplePwmChannels { - ch1: self.channel(Channel::Ch1), - ch2: self.channel(Channel::Ch2), - ch3: self.channel(Channel::Ch3), - ch4: self.channel(Channel::Ch4), + ch1: ch(Channel::Ch1), + ch2: ch(Channel::Ch2), + ch3: ch(Channel::Ch3), + ch4: ch(Channel::Ch4), } } - /// Enable the given channel. - pub fn enable(&mut self, channel: Channel) { - self.inner.enable_channel(channel, true); - } - - /// Disable the given channel. - pub fn disable(&mut self, channel: Channel) { - self.inner.enable_channel(channel, false); - } - - /// Check whether given channel is enabled - pub fn is_enabled(&self, channel: Channel) -> bool { - self.inner.get_channel_enable_state(channel) - } - /// Set PWM frequency. /// /// Note: when you call this, the max duty value changes, so you will have to @@ -236,31 +268,6 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { self.inner.get_max_compare_value() + 1 } - /// Set the duty for a given channel. - /// - /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. - pub fn set_duty(&mut self, channel: Channel, duty: u32) { - assert!(duty <= self.get_max_duty()); - self.inner.set_compare_value(channel, duty) - } - - /// Get the duty for a given channel. - /// - /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. - pub fn get_duty(&self, channel: Channel) -> u32 { - self.inner.get_compare_value(channel) - } - - /// Set the output polarity for a given channel. - pub fn set_polarity(&mut self, channel: Channel, polarity: OutputPolarity) { - self.inner.set_output_polarity(channel, polarity); - } - - /// Set the output compare mode for a given channel. - pub fn set_output_compare_mode(&mut self, channel: Channel, mode: OutputCompareMode) { - self.inner.set_output_compare_mode(channel, mode); - } - /// Generate a sequence of PWM waveform /// /// Note: @@ -276,8 +283,8 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { #[allow(clippy::let_unit_value)] // eg. stm32f334 let req = dma.request(); - let original_duty_state = self.get_duty(channel); - let original_enable_state = self.is_enabled(channel); + let original_duty_state = self.channel(channel).get_duty(); + let original_enable_state = self.channel(channel).is_enabled(); let original_update_dma_state = self.inner.get_update_dma_state(); if !original_update_dma_state { @@ -285,7 +292,7 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { } if !original_enable_state { - self.enable(channel); + self.channel(channel).enable(); } unsafe { @@ -313,10 +320,10 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { // restore output compare state if !original_enable_state { - self.disable(channel); + self.channel(channel).disable(); } - self.set_duty(channel, original_duty_state); + self.channel(channel).set_duty(original_duty_state); // Since DMA is closed before timer update event trigger DMA is turn off, // this can almost always trigger a DMA FIFO error. @@ -346,8 +353,8 @@ macro_rules! impl_waveform_chx { let cc_channel = Channel::$cc_ch; - let original_duty_state = self.get_duty(cc_channel); - let original_enable_state = self.is_enabled(cc_channel); + let original_duty_state = self.channel(cc_channel).get_duty(); + let original_enable_state = self.channel(cc_channel).is_enabled(); let original_cc_dma_on_update = self.inner.get_cc_dma_selection() == Ccds::ONUPDATE; let original_cc_dma_enabled = self.inner.get_cc_dma_enable_state(cc_channel); @@ -361,7 +368,7 @@ macro_rules! impl_waveform_chx { } if !original_enable_state { - self.enable(cc_channel); + self.channel(cc_channel).enable(); } unsafe { @@ -389,10 +396,10 @@ macro_rules! impl_waveform_chx { // restore output compare state if !original_enable_state { - self.disable(cc_channel); + self.channel(cc_channel).disable(); } - self.set_duty(cc_channel, original_duty_state); + self.channel(cc_channel).set_duty(original_duty_state); // Since DMA is closed before timer Capture Compare Event trigger DMA is turn off, // this can almost always trigger a DMA FIFO error. From cfc76cec711447300e2d957439a32d6ad418a58c Mon Sep 17 00:00:00 2001 From: Grant Miller Date: Fri, 6 Sep 2024 13:29:54 -0500 Subject: [PATCH 016/144] Match embedded-hal api --- embassy-stm32/src/timer/simple_pwm.rs | 105 ++++++++++++++++++-------- 1 file changed, 72 insertions(+), 33 deletions(-) diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 7179a7a59..23c727731 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -81,23 +81,45 @@ impl<'d, T: GeneralInstance4Channel> SimplePwmChannel<'d, T> { /// Get max duty value. /// /// This value depends on the configured frequency and the timer's clock rate from RCC. - pub fn get_max_duty(&self) -> u32 { - self.timer.get_max_compare_value() + 1 + pub fn max_duty_cycle(&self) -> u16 { + unwrap!(self.timer.get_max_compare_value().checked_add(1)) } /// Set the duty for a given channel. /// - /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. - pub fn set_duty(&mut self, duty: u32) { - assert!(duty <= self.get_max_duty()); - self.timer.set_compare_value(self.channel, duty) + /// The value ranges from 0 for 0% duty, to [`max_duty_cycle`](Self::max_duty_cycle) for 100% duty, both included. + pub fn set_duty_cycle(&mut self, duty: u16) { + assert!(duty <= (*self).max_duty_cycle()); + self.timer.set_compare_value(self.channel, duty.into()) + } + + fn set_duty_cycle_fully_off(&mut self) { + self.set_duty_cycle(0); + } + + fn set_duty_cycle_fully_on(&mut self) { + self.set_duty_cycle((*self).max_duty_cycle()); + } + + fn set_duty_cycle_fraction(&mut self, num: u16, denom: u16) { + assert!(denom != 0); + assert!(num <= denom); + let duty = u32::from(num) * u32::from(self.max_duty_cycle()) / u32::from(denom); + + // This is safe because we know that `num <= denom`, so `duty <= self.max_duty_cycle()` (u16) + #[allow(clippy::cast_possible_truncation)] + self.set_duty_cycle(duty as u16); + } + + fn set_duty_cycle_percent(&mut self, percent: u8) { + self.set_duty_cycle_fraction(u16::from(percent), 100) } /// Get the duty for a given channel. /// - /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. - pub fn get_duty(&self) -> u32 { - self.timer.get_compare_value(self.channel) + /// The value ranges from 0 for 0% duty, to [`max_duty_cycle`](Self::max_duty_cycle) for 100% duty, both included. + pub fn get_duty(&self) -> u16 { + unwrap!(self.timer.get_compare_value(self.channel).try_into()) } /// Set the output polarity for a given channel. @@ -123,25 +145,6 @@ pub struct SimplePwmChannels<'d, T: GeneralInstance4Channel> { pub ch4: SimplePwmChannel<'d, T>, } -impl<'d, T: GeneralInstance4Channel> embedded_hal_1::pwm::ErrorType for SimplePwmChannel<'d, T> { - type Error = core::convert::Infallible; -} - -impl<'d, T: GeneralInstance4Channel> embedded_hal_1::pwm::SetDutyCycle for SimplePwmChannel<'d, T> { - fn max_duty_cycle(&self) -> u16 { - // TODO: panics if CCR is 0xFFFF - // TODO: rename get_max_duty to max_duty_cycle - unwrap!(self.get_max_duty().try_into()) - } - - fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { - self.set_duty(duty.into()); - Ok(()) - } - - // TODO: default methods? -} - /// Simple PWM driver. pub struct SimplePwm<'d, T: GeneralInstance4Channel> { inner: Timer<'d, T>, @@ -253,6 +256,7 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { /// Note: when you call this, the max duty value changes, so you will have to /// call `set_duty` on all channels with the duty calculated based on the new max duty. pub fn set_frequency(&mut self, freq: Hertz) { + // TODO: prevent ARR = u16::MAX? let multiplier = if self.inner.get_counting_mode().is_center_aligned() { 2u8 } else { @@ -264,8 +268,8 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { /// Get max duty value. /// /// This value depends on the configured frequency and the timer's clock rate from RCC. - pub fn get_max_duty(&self) -> u32 { - self.inner.get_max_compare_value() + 1 + pub fn max_duty_cycle(&self) -> u16 { + unwrap!(self.inner.get_max_compare_value().checked_add(1)) } /// Generate a sequence of PWM waveform @@ -323,7 +327,7 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { self.channel(channel).disable(); } - self.channel(channel).set_duty(original_duty_state); + self.channel(channel).set_duty_cycle(original_duty_state); // Since DMA is closed before timer update event trigger DMA is turn off, // this can almost always trigger a DMA FIFO error. @@ -399,7 +403,7 @@ macro_rules! impl_waveform_chx { self.channel(cc_channel).disable(); } - self.channel(cc_channel).set_duty(original_duty_state); + self.channel(cc_channel).set_duty_cycle(original_duty_state); // Since DMA is closed before timer Capture Compare Event trigger DMA is turn off, // this can almost always trigger a DMA FIFO error. @@ -423,6 +427,41 @@ impl_waveform_chx!(waveform_ch2, Ch2Dma, Ch2); impl_waveform_chx!(waveform_ch3, Ch3Dma, Ch3); impl_waveform_chx!(waveform_ch4, Ch4Dma, Ch4); +impl<'d, T: GeneralInstance4Channel> embedded_hal_1::pwm::ErrorType for SimplePwmChannel<'d, T> { + type Error = core::convert::Infallible; +} + +impl<'d, T: GeneralInstance4Channel> embedded_hal_1::pwm::SetDutyCycle for SimplePwmChannel<'d, T> { + fn max_duty_cycle(&self) -> u16 { + self.max_duty_cycle() + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + self.set_duty_cycle(duty); + Ok(()) + } + + fn set_duty_cycle_fully_off(&mut self) -> Result<(), Self::Error> { + self.set_duty_cycle_fully_off(); + Ok(()) + } + + fn set_duty_cycle_fully_on(&mut self) -> Result<(), Self::Error> { + self.set_duty_cycle_fully_on(); + Ok(()) + } + + fn set_duty_cycle_fraction(&mut self, num: u16, denom: u16) -> Result<(), Self::Error> { + self.set_duty_cycle_fraction(num, denom); + Ok(()) + } + + fn set_duty_cycle_percent(&mut self, percent: u8) -> Result<(), Self::Error> { + self.set_duty_cycle_percent(percent); + Ok(()) + } +} + impl<'d, T: GeneralInstance4Channel> embedded_hal_02::Pwm for SimplePwm<'d, T> { type Channel = Channel; type Time = Hertz; @@ -449,7 +488,7 @@ impl<'d, T: GeneralInstance4Channel> embedded_hal_02::Pwm for SimplePwm<'d, T> { } fn set_duty(&mut self, channel: Self::Channel, duty: Self::Duty) { - assert!(duty <= self.get_max_duty()); + assert!(duty <= self.max_duty_cycle() as u32); self.inner.set_compare_value(channel, duty) } From 1a8977db7837a5635d3c5e0ae8213d1b3181ffb7 Mon Sep 17 00:00:00 2001 From: Grant Miller Date: Fri, 6 Sep 2024 13:53:49 -0500 Subject: [PATCH 017/144] Update examples --- examples/stm32f4/src/bin/pwm.rs | 18 +++++++++--------- examples/stm32f4/src/bin/ws2812_pwm.rs | 4 ++-- examples/stm32g0/src/bin/input_capture.rs | 8 ++++---- examples/stm32g0/src/bin/pwm_input.rs | 9 ++++----- examples/stm32g4/src/bin/pwm.rs | 18 +++++++++--------- examples/stm32h7/src/bin/pwm.rs | 18 +++++++++--------- 6 files changed, 37 insertions(+), 38 deletions(-) diff --git a/examples/stm32f4/src/bin/pwm.rs b/examples/stm32f4/src/bin/pwm.rs index 8844a9f0e..0cd65a258 100644 --- a/examples/stm32f4/src/bin/pwm.rs +++ b/examples/stm32f4/src/bin/pwm.rs @@ -15,22 +15,22 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); - let ch1 = PwmPin::new_ch1(p.PE9, OutputType::PushPull); - let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(10), Default::default()); - let max = pwm.get_max_duty(); - pwm.enable(Channel::Ch1); + let ch1_pin = PwmPin::new_ch1(p.PE9, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(10), Default::default()); + let mut ch1 = pwm.ch1(); + ch1.enable(); info!("PWM initialized"); - info!("PWM max duty {}", max); + info!("PWM max duty {}", ch1.max_duty_cycle()); loop { - pwm.set_duty(Channel::Ch1, 0); + ch1.set_duty_cycle_fully_off(); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max / 4); + ch1.set_duty_cycle_fraction(1, 4); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max / 2); + ch1.set_dutycycle_fraction(1, 2); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max - 1); + ch1.set_duty_cycle(ch1.max_duty_cycle() - 1); Timer::after_millis(300).await; } } diff --git a/examples/stm32f4/src/bin/ws2812_pwm.rs b/examples/stm32f4/src/bin/ws2812_pwm.rs index cbaff75fc..7a9fa302b 100644 --- a/examples/stm32f4/src/bin/ws2812_pwm.rs +++ b/examples/stm32f4/src/bin/ws2812_pwm.rs @@ -61,7 +61,7 @@ async fn main(_spawner: Spawner) { // construct ws2812 non-return-to-zero (NRZ) code bit by bit // ws2812 only need 24 bits for each LED, but we add one bit more to keep PWM output low - let max_duty = ws2812_pwm.get_max_duty() as u16; + let max_duty = ws2812_pwm.max_duty_cycle(); let n0 = 8 * max_duty / 25; // ws2812 Bit 0 high level timing let n1 = 2 * n0; // ws2812 Bit 1 high level timing @@ -84,7 +84,7 @@ async fn main(_spawner: Spawner) { let pwm_channel = Channel::Ch1; // make sure PWM output keep low on first start - ws2812_pwm.set_duty(pwm_channel, 0); + ws2812_pwm.channel(pwm_channel).set_duty(0); // flip color at 2 Hz let mut ticker = Ticker::every(Duration::from_millis(500)); diff --git a/examples/stm32g0/src/bin/input_capture.rs b/examples/stm32g0/src/bin/input_capture.rs index 69fdae96d..bc814cb13 100644 --- a/examples/stm32g0/src/bin/input_capture.rs +++ b/examples/stm32g0/src/bin/input_capture.rs @@ -47,10 +47,10 @@ async fn main(spawner: Spawner) { unwrap!(spawner.spawn(blinky(p.PB1))); // Connect PB1 and PA8 with a 1k Ohm resistor - let ch1 = PwmPin::new_ch1(p.PA8, OutputType::PushPull); - let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(1), Default::default()); - pwm.enable(Channel::Ch1); - pwm.set_duty(Channel::Ch1, 50); + let ch1_pin = PwmPin::new_ch1(p.PA8, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(1), Default::default()); + pwm.ch1().enable(); + pwm.ch1().set_duty_cycle(50); let ch1 = CapturePin::new_ch1(p.PA0, Pull::None); let mut ic = InputCapture::new(p.TIM2, Some(ch1), None, None, None, Irqs, khz(1000), Default::default()); diff --git a/examples/stm32g0/src/bin/pwm_input.rs b/examples/stm32g0/src/bin/pwm_input.rs index 152ecda86..983705e2f 100644 --- a/examples/stm32g0/src/bin/pwm_input.rs +++ b/examples/stm32g0/src/bin/pwm_input.rs @@ -43,11 +43,10 @@ async fn main(spawner: Spawner) { unwrap!(spawner.spawn(blinky(p.PB1))); // Connect PA8 and PA6 with a 1k Ohm resistor - let ch1 = PwmPin::new_ch1(p.PA8, OutputType::PushPull); - let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(1), Default::default()); - let max = pwm.get_max_duty(); - pwm.set_duty(Channel::Ch1, max / 4); - pwm.enable(Channel::Ch1); + let ch1_pin = PwmPin::new_ch1(p.PA8, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(1), Default::default()); + pwm.ch1().set_duty_cycle_fraction(1, 4); + pwm.ch1().enable(); let mut pwm_input = PwmInput::new(p.TIM2, p.PA0, Pull::None, khz(1000)); pwm_input.enable(); diff --git a/examples/stm32g4/src/bin/pwm.rs b/examples/stm32g4/src/bin/pwm.rs index d4809a481..3833be58f 100644 --- a/examples/stm32g4/src/bin/pwm.rs +++ b/examples/stm32g4/src/bin/pwm.rs @@ -15,22 +15,22 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); - let ch1 = PwmPin::new_ch1(p.PC0, OutputType::PushPull); - let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(10), Default::default()); - let max = pwm.get_max_duty(); - pwm.enable(Channel::Ch1); + let ch1_pin = PwmPin::new_ch1(p.PC0, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(10), Default::default()); + let mut ch1 = pwm.ch1(); + ch1.enable(); info!("PWM initialized"); - info!("PWM max duty {}", max); + info!("PWM max duty {}", ch1.max_duty_cycle()); loop { - pwm.set_duty(Channel::Ch1, 0); + ch1.set_duty_cycle_fully_off(); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max / 4); + ch1.set_duty_cycle_fraction(1, 4); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max / 2); + ch1.set_dutycycle_fraction(1, 2); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max - 1); + ch1.set_duty_cycle(ch1.max_duty_cycle() - 1); Timer::after_millis(300).await; } } diff --git a/examples/stm32h7/src/bin/pwm.rs b/examples/stm32h7/src/bin/pwm.rs index 1e48ba67b..1d0b89e97 100644 --- a/examples/stm32h7/src/bin/pwm.rs +++ b/examples/stm32h7/src/bin/pwm.rs @@ -37,22 +37,22 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(config); info!("Hello World!"); - let ch1 = PwmPin::new_ch1(p.PA6, OutputType::PushPull); - let mut pwm = SimplePwm::new(p.TIM3, Some(ch1), None, None, None, khz(10), Default::default()); - let max = pwm.get_max_duty(); - pwm.enable(Channel::Ch1); + let ch1_pin = PwmPin::new_ch1(p.PA6, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM3, Some(ch1_pin), None, None, None, khz(10), Default::default()); + let mut ch1 = pwm.ch1; + ch1.enable(); info!("PWM initialized"); - info!("PWM max duty {}", max); + info!("PWM max duty {}", ch1.max_duty_cycle()); loop { - pwm.set_duty(Channel::Ch1, 0); + ch1.set_duty_cycle_fully_off(); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max / 4); + ch1.set_duty_cycle_fraction(1, 4); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max / 2); + ch1.set_dutycycle_fraction(1, 2); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max - 1); + ch1.set_duty_cycle(ch1.max_duty_cycle() - 1); Timer::after_millis(300).await; } } From 71e49839fc350c19c2fd7eebda0fe76afbfdb157 Mon Sep 17 00:00:00 2001 From: Grant Miller Date: Fri, 6 Sep 2024 14:01:10 -0500 Subject: [PATCH 018/144] oops --- embassy-stm32/src/timer/simple_pwm.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 23c727731..885abd23f 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -82,7 +82,9 @@ impl<'d, T: GeneralInstance4Channel> SimplePwmChannel<'d, T> { /// /// This value depends on the configured frequency and the timer's clock rate from RCC. pub fn max_duty_cycle(&self) -> u16 { - unwrap!(self.timer.get_max_compare_value().checked_add(1)) + let max = self.timer.get_max_compare_value(); + assert!(max < u16::MAX as u32); + max as u16 + 1 } /// Set the duty for a given channel. @@ -269,7 +271,9 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { /// /// This value depends on the configured frequency and the timer's clock rate from RCC. pub fn max_duty_cycle(&self) -> u16 { - unwrap!(self.inner.get_max_compare_value().checked_add(1)) + let max = self.inner.get_max_compare_value(); + assert!(max < u16::MAX as u32); + max as u16 + 1 } /// Generate a sequence of PWM waveform From f571ab9d600cdb6e3f146a6b2bf464fb817065af Mon Sep 17 00:00:00 2001 From: Grant Miller Date: Fri, 6 Sep 2024 14:04:58 -0500 Subject: [PATCH 019/144] oops again --- embassy-stm32/src/timer/simple_pwm.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 885abd23f..8673bce54 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -95,15 +95,15 @@ impl<'d, T: GeneralInstance4Channel> SimplePwmChannel<'d, T> { self.timer.set_compare_value(self.channel, duty.into()) } - fn set_duty_cycle_fully_off(&mut self) { + pub fn set_duty_cycle_fully_off(&mut self) { self.set_duty_cycle(0); } - fn set_duty_cycle_fully_on(&mut self) { + pub fn set_duty_cycle_fully_on(&mut self) { self.set_duty_cycle((*self).max_duty_cycle()); } - fn set_duty_cycle_fraction(&mut self, num: u16, denom: u16) { + pub fn set_duty_cycle_fraction(&mut self, num: u16, denom: u16) { assert!(denom != 0); assert!(num <= denom); let duty = u32::from(num) * u32::from(self.max_duty_cycle()) / u32::from(denom); @@ -113,7 +113,7 @@ impl<'d, T: GeneralInstance4Channel> SimplePwmChannel<'d, T> { self.set_duty_cycle(duty as u16); } - fn set_duty_cycle_percent(&mut self, percent: u8) { + pub fn set_duty_cycle_percent(&mut self, percent: u8) { self.set_duty_cycle_fraction(u16::from(percent), 100) } From d24c47a3ff4a82786b67f06d876bafb1bdabc163 Mon Sep 17 00:00:00 2001 From: Grant Miller Date: Fri, 6 Sep 2024 14:08:22 -0500 Subject: [PATCH 020/144] Missing docs --- embassy-stm32/src/timer/simple_pwm.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 8673bce54..47188053c 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -95,14 +95,20 @@ impl<'d, T: GeneralInstance4Channel> SimplePwmChannel<'d, T> { self.timer.set_compare_value(self.channel, duty.into()) } + /// Set the duty cycle to 0%, or always inactive. pub fn set_duty_cycle_fully_off(&mut self) { self.set_duty_cycle(0); } + /// Set the duty cycle to 100%, or always active. pub fn set_duty_cycle_fully_on(&mut self) { self.set_duty_cycle((*self).max_duty_cycle()); } + /// Set the duty cycle to `num / denom`. + /// + /// The caller is responsible for ensuring that `num` is less than or equal to `denom`, + /// and that `denom` is not zero. pub fn set_duty_cycle_fraction(&mut self, num: u16, denom: u16) { assert!(denom != 0); assert!(num <= denom); @@ -113,6 +119,9 @@ impl<'d, T: GeneralInstance4Channel> SimplePwmChannel<'d, T> { self.set_duty_cycle(duty as u16); } + /// Set the duty cycle to `percent / 100` + /// + /// The caller is responsible for ensuring that `percent` is less than or equal to 100. pub fn set_duty_cycle_percent(&mut self, percent: u8) { self.set_duty_cycle_fraction(u16::from(percent), 100) } From b8beaba6df08c4455f55780a6e13191d95ad9eec Mon Sep 17 00:00:00 2001 From: Grant Miller Date: Fri, 6 Sep 2024 15:08:58 -0500 Subject: [PATCH 021/144] last oops I promise --- embassy-stm32/src/timer/simple_pwm.rs | 6 +++--- examples/stm32f4/src/bin/pwm.rs | 3 +-- examples/stm32f4/src/bin/ws2812_pwm.rs | 2 +- examples/stm32g0/src/bin/pwm_input.rs | 1 - examples/stm32g4/src/bin/pwm.rs | 3 +-- examples/stm32h7/src/bin/pwm.rs | 5 ++--- 6 files changed, 8 insertions(+), 12 deletions(-) diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 47188053c..7e2e9c202 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -129,7 +129,7 @@ impl<'d, T: GeneralInstance4Channel> SimplePwmChannel<'d, T> { /// Get the duty for a given channel. /// /// The value ranges from 0 for 0% duty, to [`max_duty_cycle`](Self::max_duty_cycle) for 100% duty, both included. - pub fn get_duty(&self) -> u16 { + pub fn current_duty_cycle(&self) -> u16 { unwrap!(self.timer.get_compare_value(self.channel).try_into()) } @@ -300,7 +300,7 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { #[allow(clippy::let_unit_value)] // eg. stm32f334 let req = dma.request(); - let original_duty_state = self.channel(channel).get_duty(); + let original_duty_state = self.channel(channel).current_duty_cycle(); let original_enable_state = self.channel(channel).is_enabled(); let original_update_dma_state = self.inner.get_update_dma_state(); @@ -370,7 +370,7 @@ macro_rules! impl_waveform_chx { let cc_channel = Channel::$cc_ch; - let original_duty_state = self.channel(cc_channel).get_duty(); + let original_duty_state = self.channel(cc_channel).current_duty_cycle(); let original_enable_state = self.channel(cc_channel).is_enabled(); let original_cc_dma_on_update = self.inner.get_cc_dma_selection() == Ccds::ONUPDATE; let original_cc_dma_enabled = self.inner.get_cc_dma_enable_state(cc_channel); diff --git a/examples/stm32f4/src/bin/pwm.rs b/examples/stm32f4/src/bin/pwm.rs index 0cd65a258..04811162b 100644 --- a/examples/stm32f4/src/bin/pwm.rs +++ b/examples/stm32f4/src/bin/pwm.rs @@ -6,7 +6,6 @@ use embassy_executor::Spawner; use embassy_stm32::gpio::OutputType; use embassy_stm32::time::khz; use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; -use embassy_stm32::timer::Channel; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -28,7 +27,7 @@ async fn main(_spawner: Spawner) { Timer::after_millis(300).await; ch1.set_duty_cycle_fraction(1, 4); Timer::after_millis(300).await; - ch1.set_dutycycle_fraction(1, 2); + ch1.set_duty_cycle_fraction(1, 2); Timer::after_millis(300).await; ch1.set_duty_cycle(ch1.max_duty_cycle() - 1); Timer::after_millis(300).await; diff --git a/examples/stm32f4/src/bin/ws2812_pwm.rs b/examples/stm32f4/src/bin/ws2812_pwm.rs index 7a9fa302b..3ab93d6e0 100644 --- a/examples/stm32f4/src/bin/ws2812_pwm.rs +++ b/examples/stm32f4/src/bin/ws2812_pwm.rs @@ -84,7 +84,7 @@ async fn main(_spawner: Spawner) { let pwm_channel = Channel::Ch1; // make sure PWM output keep low on first start - ws2812_pwm.channel(pwm_channel).set_duty(0); + ws2812_pwm.channel(pwm_channel).set_duty_cycle(0); // flip color at 2 Hz let mut ticker = Ticker::every(Duration::from_millis(500)); diff --git a/examples/stm32g0/src/bin/pwm_input.rs b/examples/stm32g0/src/bin/pwm_input.rs index 983705e2f..db9cf4f8a 100644 --- a/examples/stm32g0/src/bin/pwm_input.rs +++ b/examples/stm32g0/src/bin/pwm_input.rs @@ -14,7 +14,6 @@ use embassy_stm32::gpio::{Level, Output, OutputType, Pull, Speed}; use embassy_stm32::time::khz; use embassy_stm32::timer::pwm_input::PwmInput; use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; -use embassy_stm32::timer::Channel; use embassy_stm32::{bind_interrupts, peripherals, timer}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; diff --git a/examples/stm32g4/src/bin/pwm.rs b/examples/stm32g4/src/bin/pwm.rs index 3833be58f..6c965012c 100644 --- a/examples/stm32g4/src/bin/pwm.rs +++ b/examples/stm32g4/src/bin/pwm.rs @@ -6,7 +6,6 @@ use embassy_executor::Spawner; use embassy_stm32::gpio::OutputType; use embassy_stm32::time::khz; use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; -use embassy_stm32::timer::Channel; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -28,7 +27,7 @@ async fn main(_spawner: Spawner) { Timer::after_millis(300).await; ch1.set_duty_cycle_fraction(1, 4); Timer::after_millis(300).await; - ch1.set_dutycycle_fraction(1, 2); + ch1.set_duty_cycle_fraction(1, 2); Timer::after_millis(300).await; ch1.set_duty_cycle(ch1.max_duty_cycle() - 1); Timer::after_millis(300).await; diff --git a/examples/stm32h7/src/bin/pwm.rs b/examples/stm32h7/src/bin/pwm.rs index 1d0b89e97..a1c53fc3f 100644 --- a/examples/stm32h7/src/bin/pwm.rs +++ b/examples/stm32h7/src/bin/pwm.rs @@ -6,7 +6,6 @@ use embassy_executor::Spawner; use embassy_stm32::gpio::OutputType; use embassy_stm32::time::khz; use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; -use embassy_stm32::timer::Channel; use embassy_stm32::Config; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -39,7 +38,7 @@ async fn main(_spawner: Spawner) { let ch1_pin = PwmPin::new_ch1(p.PA6, OutputType::PushPull); let mut pwm = SimplePwm::new(p.TIM3, Some(ch1_pin), None, None, None, khz(10), Default::default()); - let mut ch1 = pwm.ch1; + let mut ch1 = pwm.ch1(); ch1.enable(); info!("PWM initialized"); @@ -50,7 +49,7 @@ async fn main(_spawner: Spawner) { Timer::after_millis(300).await; ch1.set_duty_cycle_fraction(1, 4); Timer::after_millis(300).await; - ch1.set_dutycycle_fraction(1, 2); + ch1.set_duty_cycle_fraction(1, 2); Timer::after_millis(300).await; ch1.set_duty_cycle(ch1.max_duty_cycle() - 1); Timer::after_millis(300).await; From df06c2bbfe51e22e0d3eda3d760839f617ffbd96 Mon Sep 17 00:00:00 2001 From: Grant Miller Date: Sat, 7 Sep 2024 11:13:18 -0500 Subject: [PATCH 022/144] wip: split by value --- embassy-stm32/src/timer/low_level.rs | 9 +++++++++ embassy-stm32/src/timer/mod.rs | 1 + embassy-stm32/src/timer/simple_pwm.rs | 17 +++++++++++------ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/embassy-stm32/src/timer/low_level.rs b/embassy-stm32/src/timer/low_level.rs index e643722aa..6377054c5 100644 --- a/embassy-stm32/src/timer/low_level.rs +++ b/embassy-stm32/src/timer/low_level.rs @@ -6,6 +6,8 @@ //! //! The available functionality depends on the timer type. +use core::mem::ManuallyDrop; + use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; // Re-export useful enums pub use stm32_metapac::timer::vals::{FilterValue, Sms as SlaveMode, Ts as TriggerSource}; @@ -198,6 +200,13 @@ impl<'d, T: CoreInstance> Timer<'d, T> { Self { tim } } + pub(crate) unsafe fn clone_unchecked(&self) -> ManuallyDrop { + // this doesn't work for some reason + // let tim = unsafe { self.tim.clone_unchecked() }; + let tim = todo!(); + ManuallyDrop::new(Self { tim }) + } + /// Get access to the virutal core 16bit timer registers. /// /// Note: This works even if the timer is more capable, because registers diff --git a/embassy-stm32/src/timer/mod.rs b/embassy-stm32/src/timer/mod.rs index 25782ee13..6cf22689b 100644 --- a/embassy-stm32/src/timer/mod.rs +++ b/embassy-stm32/src/timer/mod.rs @@ -2,6 +2,7 @@ use core::marker::PhantomData; +use embassy_hal_internal::Peripheral; use embassy_sync::waitqueue::AtomicWaker; #[cfg(not(stm32l0))] diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 7e2e9c202..9e4a09095 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -1,6 +1,7 @@ //! Simple PWM driver. use core::marker::PhantomData; +use core::mem::ManuallyDrop; use embassy_hal_internal::{into_ref, PeripheralRef}; @@ -57,7 +58,7 @@ channel_impl!(new_ch4, Ch4, Channel4Pin); /// It is not possible to change the pwm frequency because /// the frequency configuration is shared with all four channels. pub struct SimplePwmChannel<'d, T: GeneralInstance4Channel> { - timer: &'d Timer<'d, T>, + timer: ManuallyDrop>, channel: Channel, } @@ -199,7 +200,7 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { /// If you need to use multiple channels, use [`Self::split`]. pub fn channel(&mut self, channel: Channel) -> SimplePwmChannel<'_, T> { SimplePwmChannel { - timer: &self.inner, + timer: unsafe { self.inner.clone_unchecked() }, channel, } } @@ -245,12 +246,16 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { /// This returns all four channels, including channels that /// aren't configured with a [`PwmPin`]. // TODO: I hate the name "split" - pub fn split(&mut self) -> SimplePwmChannels<'_, T> { - // TODO: pre-enable channels? + pub fn split(self) -> SimplePwmChannels<'static, T> + where + // must be static because the timer will never be dropped/disabled + 'd: 'static, + { + // without this, the timer would be disabled at the end of this function + let timer = ManuallyDrop::new(self.inner); - // we can't use self.channel() because that takes &mut self let ch = |channel| SimplePwmChannel { - timer: &self.inner, + timer: unsafe { timer.clone_unchecked() }, channel, }; From 89bad07e817dec482d385f765da5be3b6d2d0e4c Mon Sep 17 00:00:00 2001 From: Nathan Perry Date: Fri, 20 Sep 2024 01:52:59 -0400 Subject: [PATCH 023/144] embassy_sync: `Sink` adapter for `pubsub::Pub` Corresponding to the `Stream` impl for `pubsub::Sub`. Notable difference is that we need a separate adapter type to store the pending item, i.e. we can't `impl Sink for Pub` directly. Instead a method `Pub::sink(&self)` is exposed, which constructs a `PubSink`. --- embassy-sync/Cargo.toml | 3 +- embassy-sync/src/pubsub/mod.rs | 26 +++++++++++ embassy-sync/src/pubsub/publisher.rs | 67 ++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) diff --git a/embassy-sync/Cargo.toml b/embassy-sync/Cargo.toml index 7b7d2bf8e..2f049b6bc 100644 --- a/embassy-sync/Cargo.toml +++ b/embassy-sync/Cargo.toml @@ -27,6 +27,7 @@ turbowakers = [] defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } +futures-sink = { version = "0.3", default-features = false, features = [] } futures-util = { version = "0.3.17", default-features = false } critical-section = "1.1" heapless = "0.8" @@ -37,7 +38,7 @@ embedded-io-async = { version = "0.6.1" } futures-executor = { version = "0.3.17", features = [ "thread-pool" ] } futures-test = "0.3.17" futures-timer = "3.0.2" -futures-util = { version = "0.3.17", features = [ "channel" ] } +futures-util = { version = "0.3.17", features = [ "channel", "sink" ] } # Enable critical-section implementation for std, for tests critical-section = { version = "1.1", features = ["std"] } diff --git a/embassy-sync/src/pubsub/mod.rs b/embassy-sync/src/pubsub/mod.rs index 812302e2b..1c0f68bd0 100644 --- a/embassy-sync/src/pubsub/mod.rs +++ b/embassy-sync/src/pubsub/mod.rs @@ -755,4 +755,30 @@ mod tests { assert_eq!(1, sub0.try_next_message_pure().unwrap().0); assert_eq!(0, sub1.try_next_message_pure().unwrap().0); } + + #[futures_test::test] + async fn publisher_sink() { + use futures_util::{SinkExt, StreamExt}; + + let channel = PubSubChannel::::new(); + + let mut sub = channel.subscriber().unwrap(); + + let publ = channel.publisher().unwrap(); + let mut sink = publ.sink(); + + sink.send(0).await.unwrap(); + assert_eq!(0, sub.try_next_message_pure().unwrap()); + + sink.send(1).await.unwrap(); + assert_eq!(1, sub.try_next_message_pure().unwrap()); + + sink.send_all(&mut futures_util::stream::iter(0..4).map(Ok)) + .await + .unwrap(); + assert_eq!(0, sub.try_next_message_pure().unwrap()); + assert_eq!(1, sub.try_next_message_pure().unwrap()); + assert_eq!(2, sub.try_next_message_pure().unwrap()); + assert_eq!(3, sub.try_next_message_pure().unwrap()); + } } diff --git a/embassy-sync/src/pubsub/publisher.rs b/embassy-sync/src/pubsub/publisher.rs index e66b3b1db..7a1ab66de 100644 --- a/embassy-sync/src/pubsub/publisher.rs +++ b/embassy-sync/src/pubsub/publisher.rs @@ -74,6 +74,12 @@ impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Pub<'a, PSB, T> { pub fn is_full(&self) -> bool { self.channel.is_full() } + + /// Create a [`futures::Sink`] adapter for this publisher. + #[inline] + pub const fn sink(&self) -> PubSink<'a, '_, PSB, T> { + PubSink { publ: self, fut: None } + } } impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Drop for Pub<'a, PSB, T> { @@ -221,6 +227,67 @@ impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: } } +#[must_use = "Sinks do nothing unless polled"] +/// [`futures_sink::Sink`] adapter for [`Pub`]. +pub struct PubSink<'a, 'p, PSB, T> +where + T: Clone, + PSB: PubSubBehavior + ?Sized, +{ + publ: &'p Pub<'a, PSB, T>, + fut: Option>, +} + +impl<'a, 'p, PSB, T> PubSink<'a, 'p, PSB, T> +where + PSB: PubSubBehavior + ?Sized, + T: Clone, +{ + /// Try to make progress on the pending future if we have one. + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + let Some(mut fut) = self.fut.take() else { + return Poll::Ready(()); + }; + + if Pin::new(&mut fut).poll(cx).is_pending() { + self.fut = Some(fut); + return Poll::Pending; + } + + Poll::Ready(()) + } +} + +impl<'a, 'p, PSB, T> futures_sink::Sink for PubSink<'a, 'p, PSB, T> +where + PSB: PubSubBehavior + ?Sized, + T: Clone, +{ + type Error = core::convert::Infallible; + + #[inline] + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll(cx).map(Ok) + } + + #[inline] + fn start_send(mut self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { + self.fut = Some(self.publ.publish(item)); + + Ok(()) + } + + #[inline] + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll(cx).map(Ok) + } + + #[inline] + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll(cx).map(Ok) + } +} + /// Future for the publisher wait action #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct PublisherWaitFuture<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> { From f2646b29a6b0a741fc424f88c5ca3dc25fce9369 Mon Sep 17 00:00:00 2001 From: Grant Miller Date: Sat, 21 Sep 2024 07:52:54 -0500 Subject: [PATCH 024/144] Make clone_unchecked work --- embassy-stm32/src/timer/low_level.rs | 4 +--- embassy-stm32/src/timer/mod.rs | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/embassy-stm32/src/timer/low_level.rs b/embassy-stm32/src/timer/low_level.rs index 6377054c5..3136ea4e9 100644 --- a/embassy-stm32/src/timer/low_level.rs +++ b/embassy-stm32/src/timer/low_level.rs @@ -201,9 +201,7 @@ impl<'d, T: CoreInstance> Timer<'d, T> { } pub(crate) unsafe fn clone_unchecked(&self) -> ManuallyDrop { - // this doesn't work for some reason - // let tim = unsafe { self.tim.clone_unchecked() }; - let tim = todo!(); + let tim = unsafe { self.tim.clone_unchecked() }; ManuallyDrop::new(Self { tim }) } diff --git a/embassy-stm32/src/timer/mod.rs b/embassy-stm32/src/timer/mod.rs index 6cf22689b..aa9dd91d9 100644 --- a/embassy-stm32/src/timer/mod.rs +++ b/embassy-stm32/src/timer/mod.rs @@ -67,7 +67,7 @@ impl State { } } -trait SealedInstance: RccPeripheral { +trait SealedInstance: RccPeripheral + Peripheral

{ /// Async state for this timer fn state() -> &'static State; } From a669611d7c8fb17666f35086ea20c476b6029854 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Mon, 23 Sep 2024 20:09:35 +0200 Subject: [PATCH 025/144] Discontinue peek, add AnonReceiver --- embassy-sync/src/watch.rs | 440 +++++++++++++++++++++++++------------- 1 file changed, 289 insertions(+), 151 deletions(-) diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 298c09d43..1b4a8b589 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -20,7 +20,9 @@ use crate::waitqueue::MultiWakerRegistration; /// always provided with the latest value. /// /// Typically, `Watch` instances are declared as `static`, and a [`Sender`] and [`Receiver`] -/// (or [`DynSender`] and/or [`DynReceiver`]) are obtained and passed to the relevant parts of the program. +/// (or [`DynSender`] and/or [`DynReceiver`]) are obtained where relevant. An [`AnonReceiver`] +/// and [`DynAnonReceiver`] are also available, which do not increase the receiver count for the +/// channel, and unwrapping is therefore not required, but it is not possible to `.await` the channel. /// ``` /// /// use futures_executor::block_on; @@ -41,25 +43,25 @@ use crate::waitqueue::MultiWakerRegistration; /// assert_eq!(rcv1.try_changed(), None); /// /// snd.send(10); -/// +/// /// // Receive the new value (async or try) /// assert_eq!(rcv0.changed().await, 10); /// assert_eq!(rcv1.try_changed(), Some(10)); -/// +/// /// // No update /// assert_eq!(rcv0.try_changed(), None); /// assert_eq!(rcv1.try_changed(), None); /// /// snd.send(20); /// -/// // Peek does not mark the value as seen -/// assert_eq!(rcv0.peek().await, 20); -/// assert_eq!(rcv0.try_changed(), Some(20)); -/// -/// // Get marks the value as seen +/// // Using `get` marks the value as seen /// assert_eq!(rcv1.get().await, 20); /// assert_eq!(rcv1.try_changed(), None); /// +/// // But `get` also returns when unchanged +/// assert_eq!(rcv1.get().await, 20); +/// assert_eq!(rcv1.get().await, 20); +/// /// }; /// block_on(f); /// ``` @@ -82,24 +84,11 @@ pub trait WatchBehavior { /// Clears the value of the `Watch`. fn clear(&self); - /// Poll the `Watch` for the current value, **without** making it as seen. - fn poll_peek(&self, cx: &mut Context<'_>) -> Poll; - - /// Tries to peek the value of the `Watch`, **without** marking it as seen. - fn try_peek(&self) -> Option; - - /// Poll the `Watch` for the value if it matches the predicate function - /// `f`, **without** making it as seen. - fn poll_peek_and(&self, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; - - /// Tries to peek the value of the `Watch` if it matches the predicate function `f`, **without** marking it as seen. - fn try_peek_and(&self, f: &mut dyn Fn(&T) -> bool) -> Option; - /// Poll the `Watch` for the current value, making it as seen. fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; - /// Tries to get the value of the `Watch`, marking it as seen. - fn try_get(&self, id: &mut u64) -> Option; + /// Tries to get the value of the `Watch`, marking it as seen, if an id is given. + fn try_get(&self, id: Option<&mut u64>) -> Option; /// Poll the `Watch` for the value if it matches the predicate function /// `f`, making it as seen. @@ -107,9 +96,9 @@ pub trait WatchBehavior { /// Tries to get the value of the `Watch` if it matches the predicate function /// `f`, marking it as seen. - fn try_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option; + fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option; - /// Poll the `Watch` for a changed value, marking it as seen. + /// Poll the `Watch` for a changed value, marking it as seen, if an id is given. fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; /// Tries to retrieve the value of the `Watch` if it has changed, marking it as seen. @@ -128,7 +117,11 @@ pub trait WatchBehavior { /// Modify the value of the `Watch` using a closure. Returns `false` if the /// `Watch` does not already contain a value. - fn modify(&self, f: &mut dyn Fn(&mut Option)); + fn send_modify(&self, f: &mut dyn Fn(&mut Option)); + + /// Modify the value of the `Watch` using a closure. Returns `false` if the + /// `Watch` does not already contain a value. + fn send_if_modified(&self, f: &mut dyn Fn(&mut Option) -> bool); /// Used when a receiver is dropped to decrement the receiver count. /// @@ -153,46 +146,6 @@ impl WatchBehavior for Watch }) } - fn poll_peek(&self, cx: &mut Context<'_>) -> Poll { - self.mutex.lock(|state| { - let mut s = state.borrow_mut(); - match &s.data { - Some(data) => Poll::Ready(data.clone()), - None => { - s.wakers.register(cx.waker()); - Poll::Pending - } - } - }) - } - - fn try_peek(&self) -> Option { - self.mutex.lock(|state| state.borrow().data.clone()) - } - - fn poll_peek_and(&self, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { - self.mutex.lock(|state| { - let mut s = state.borrow_mut(); - match s.data { - Some(ref data) if f(data) => Poll::Ready(data.clone()), - _ => { - s.wakers.register(cx.waker()); - Poll::Pending - } - } - }) - } - - fn try_peek_and(&self, f: &mut dyn Fn(&T) -> bool) -> Option { - self.mutex.lock(|state| { - let s = state.borrow(); - match s.data { - Some(ref data) if f(data) => Some(data.clone()), - _ => None, - } - }) - } - fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); @@ -209,11 +162,13 @@ impl WatchBehavior for Watch }) } - fn try_get(&self, id: &mut u64) -> Option { + fn try_get(&self, id: Option<&mut u64>) -> Option { self.mutex.lock(|state| { let s = state.borrow(); - *id = s.current_id; - state.borrow().data.clone() + if let Some(id) = id { + *id = s.current_id; + } + s.data.clone() }) } @@ -233,12 +188,14 @@ impl WatchBehavior for Watch }) } - fn try_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option { + fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option { self.mutex.lock(|state| { let s = state.borrow(); match s.data { Some(ref data) if f(data) => { - *id = s.current_id; + if let Some(id) = id { + *id = s.current_id; + } Some(data.clone()) } _ => None, @@ -315,7 +272,7 @@ impl WatchBehavior for Watch }) } - fn modify(&self, f: &mut dyn Fn(&mut Option)) { + fn send_modify(&self, f: &mut dyn Fn(&mut Option)) { self.mutex.lock(|state| { let mut s = state.borrow_mut(); f(&mut s.data); @@ -323,6 +280,16 @@ impl WatchBehavior for Watch s.wakers.wake(); }) } + + fn send_if_modified(&self, f: &mut dyn Fn(&mut Option) -> bool) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if f(&mut s.data) { + s.current_id += 1; + s.wakers.wake(); + } + }) + } } impl Watch { @@ -375,6 +342,60 @@ impl Watch { } }) } + + /// Try to create a new [`AnonReceiver`] for the `Watch`. + pub fn anon_receiver(&self) -> AnonReceiver<'_, M, T, N> { + AnonReceiver(AnonRcv::new(self, 0)) + } + + /// Try to create a new [`DynAnonReceiver`] for the `Watch`. + pub fn dyn_anon_receiver(&self) -> DynAnonReceiver<'_, T> { + DynAnonReceiver(AnonRcv::new(self, 0)) + } + + /// Returns the message ID of the latest message sent to the `Watch`. + /// + /// This counter is monotonic, and is incremented every time a new message is sent. + pub fn get_msg_id(&self) -> u64 { + self.mutex.lock(|state| state.borrow().current_id) + } + + /// Waits for the `Watch` to be initialized with a value using a busy-wait mechanism. + /// + /// This is useful for initialization code where receivers may only be interested in + /// awaiting the value once in the lifetime of the program. It is therefore a temporaryily + /// CPU-inefficient operation, while being more memory efficient than using a `Receiver`. + /// + /// **Note** Be careful about using this within an InterruptExecutor, as it will starve + /// tasks in lower-priority executors. + pub async fn spin_get(&self) -> T { + poll_fn(|cx| { + self.mutex.lock(|state| { + let s = state.borrow(); + match &s.data { + Some(data) => Poll::Ready(data.clone()), + None => { + cx.waker().wake_by_ref(); + Poll::Pending + } + } + }) + }) + .await + } + + /// Tries to get the value of the `Watch`. + pub fn try_get(&self) -> Option { + WatchBehavior::try_get(self, None) + } + + /// Tries to get the value of the `Watch` if it matches the predicate function `f`. + pub fn try_get_and(&self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + WatchBehavior::try_get_and(self, None, &mut f) + } } /// A receiver can `.await` a change in the `Watch` value. @@ -407,23 +428,23 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Snd<'a, T, W> { } /// Clears the value of the `Watch`. - /// This will cause calls to [`Rcv::get`] and [`Rcv::peek`] to be pending. + /// This will cause calls to [`Rcv::get`] to be pending. pub fn clear(&self) { self.watch.clear() } /// Tries to retrieve the value of the `Watch`. - pub fn try_peek(&self) -> Option { - self.watch.try_peek() + pub fn try_get(&self) -> Option { + self.watch.try_get(None) } /// Tries to peek the current value of the `Watch` if it matches the predicate /// function `f`. - pub fn try_peek_and(&self, mut f: F) -> Option + pub fn try_get_and(&self, mut f: F) -> Option where F: Fn(&T) -> bool, { - self.watch.try_peek_and(&mut f) + self.watch.try_get_and(None, &mut f) } /// Returns true if the `Watch` contains a value. @@ -432,11 +453,20 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Snd<'a, T, W> { } /// Modify the value of the `Watch` using a closure. - pub fn modify(&self, mut f: F) + pub fn send_modify(&self, mut f: F) where F: Fn(&mut Option), { - self.watch.modify(&mut f) + self.watch.send_modify(&mut f) + } + + /// Modify the value of the `Watch` using a closure. The closure must return + /// `true` if the value was modified, which notifies all receivers. + pub fn send_if_modified(&self, mut f: F) + where + F: Fn(&mut Option) -> bool, + { + self.watch.send_if_modified(&mut f) } } @@ -521,38 +551,6 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { } } - /// Returns the current value of the `Watch` once it is initialized, **without** marking it as seen. - /// - /// **Note**: Futures do nothing unless you `.await` or poll them. - pub async fn peek(&self) -> T { - poll_fn(|cx| self.watch.poll_peek(cx)).await - } - - /// Tries to peek the current value of the `Watch` without waiting, and **without** marking it as seen. - pub fn try_peek(&self) -> Option { - self.watch.try_peek() - } - - /// Returns the current value of the `Watch` if it matches the predicate function `f`, - /// or waits for it to match, **without** marking it as seen. - /// - /// **Note**: Futures do nothing unless you `.await` or poll them. - pub async fn peek_and(&self, mut f: F) -> T - where - F: Fn(&T) -> bool, - { - poll_fn(|cx| self.watch.poll_peek_and(&mut f, cx)).await - } - - /// Tries to peek the current value of the `Watch` if it matches the predicate - /// function `f` without waiting, and **without** marking it as seen. - pub fn try_peek_and(&self, mut f: F) -> Option - where - F: Fn(&T) -> bool, - { - self.watch.try_peek_and(&mut f) - } - /// Returns the current value of the `Watch` once it is initialized, marking it as seen. /// /// **Note**: Futures do nothing unless you `.await` or poll them. @@ -562,7 +560,7 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { /// Tries to get the current value of the `Watch` without waiting, marking it as seen. pub fn try_get(&mut self) -> Option { - self.watch.try_get(&mut self.at_id) + self.watch.try_get(Some(&mut self.at_id)) } /// Returns the value of the `Watch` if it matches the predicate function `f`, @@ -582,7 +580,7 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { where F: Fn(&T) -> bool, { - self.watch.try_get_and(&mut self.at_id, &mut f) + self.watch.try_get_and(Some(&mut self.at_id), &mut f) } /// Waits for the `Watch` to change and returns the new value, marking it as seen. @@ -618,7 +616,7 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { } /// Checks if the `Watch` contains a value. If this returns true, - /// then awaiting [`Rcv::get`] and [`Rcv::peek`] will return immediately. + /// then awaiting [`Rcv::get`] will return immediately. pub fn contains_value(&self) -> bool { self.watch.contains_value() } @@ -630,6 +628,58 @@ impl<'a, T: Clone, W: WatchBehavior + ?Sized> Drop for Rcv<'a, T, W> { } } +/// A anonymous receiver can NOT `.await` a change in the `Watch` value. +pub struct AnonRcv<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + at_id: u64, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> AnonRcv<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W, at_id: u64) -> Self { + Self { + watch, + at_id, + _phantom: PhantomData, + } + } + + /// Tries to get the current value of the `Watch` without waiting, marking it as seen. + pub fn try_get(&mut self) -> Option { + self.watch.try_get(Some(&mut self.at_id)) + } + + /// Tries to get the current value of the `Watch` if it matches the predicate + /// function `f` without waiting, marking it as seen. + pub fn try_get_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_get_and(Some(&mut self.at_id), &mut f) + } + + /// Tries to get the new value of the watch without waiting, marking it as seen. + pub fn try_changed(&mut self) -> Option { + self.watch.try_changed(&mut self.at_id) + } + + /// Tries to get the new value of the watch which satisfies the predicate + /// function `f` and returns the new value without waiting, marking it as seen. + pub fn try_changed_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_changed_and(&mut self.at_id, &mut f) + } + + /// Checks if the `Watch` contains a value. If this returns true, + /// then awaiting [`Rcv::get`] will return immediately. + pub fn contains_value(&self) -> bool { + self.watch.contains_value() + } +} + /// A receiver of a `Watch` channel. pub struct Receiver<'a, M: RawMutex, T: Clone, const N: usize>(Rcv<'a, T, Watch>); @@ -682,6 +732,58 @@ impl<'a, T: Clone> DerefMut for DynReceiver<'a, T> { } } +/// A receiver of a `Watch` channel that cannot `.await` values. +pub struct AnonReceiver<'a, M: RawMutex, T: Clone, const N: usize>(AnonRcv<'a, T, Watch>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> AnonReceiver<'a, M, T, N> { + /// Converts the `Receiver` into a [`DynReceiver`]. + pub fn as_dyn(self) -> DynAnonReceiver<'a, T> { + let rcv = DynAnonReceiver(AnonRcv::new(self.0.watch, self.at_id)); + core::mem::forget(self); // Ensures the destructor is not called + rcv + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for AnonReceiver<'a, M, T, N> { + fn into(self) -> DynAnonReceiver<'a, T> { + self.as_dyn() + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for AnonReceiver<'a, M, T, N> { + type Target = AnonRcv<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for AnonReceiver<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver that cannot `.await` value, which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`AnonReceiver`] with a simpler type definition, at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct DynAnonReceiver<'a, T: Clone>(AnonRcv<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, T: Clone> Deref for DynAnonReceiver<'a, T> { + type Target = AnonRcv<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynAnonReceiver<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + #[cfg(test)] mod tests { use futures_executor::block_on; @@ -715,6 +817,72 @@ mod tests { block_on(f); } + #[test] + fn all_try_get() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Not initialized + assert_eq!(WATCH.try_get(), None); + assert_eq!(rcv.try_get(), None); + assert_eq!(snd.try_get(), None); + + // Receive the new value + snd.send(10); + assert_eq!(WATCH.try_get(), Some(10)); + assert_eq!(rcv.try_get(), Some(10)); + assert_eq!(snd.try_get(), Some(10)); + + assert_eq!(WATCH.try_get_and(|x| x > &5), Some(10)); + assert_eq!(rcv.try_get_and(|x| x > &5), Some(10)); + assert_eq!(snd.try_get_and(|x| x > &5), Some(10)); + + assert_eq!(WATCH.try_get_and(|x| x < &5), None); + assert_eq!(rcv.try_get_and(|x| x < &5), None); + assert_eq!(snd.try_get_and(|x| x < &5), None); + }; + block_on(f); + } + + #[test] + fn once_lock_like() { + let f = async { + static CONFIG0: u8 = 10; + static CONFIG1: u8 = 20; + + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Not initialized + assert_eq!(rcv.try_changed(), None); + + // Receive the new value + snd.send(&CONFIG0); + let rcv0 = rcv.changed().await; + assert_eq!(rcv0, &10); + + // Receive another value + snd.send(&CONFIG1); + let rcv1 = rcv.try_changed(); + assert_eq!(rcv1, Some(&20)); + + // No update + assert_eq!(rcv.try_changed(), None); + + // Ensure similarity with original static + assert_eq!(rcv0, &CONFIG0); + assert_eq!(rcv1, Some(&CONFIG1)); + }; + block_on(f); + } + #[test] fn sender_modify() { let f = async { @@ -729,7 +897,7 @@ mod tests { assert_eq!(rcv.try_changed(), Some(10)); // Modify the value inplace - snd.modify(|opt| { + snd.send_modify(|opt| { if let Some(inner) = opt { *inner += 5; } @@ -751,11 +919,6 @@ mod tests { let mut rcv = WATCH.receiver().unwrap(); let snd = WATCH.sender(); - snd.send(10); - assert_eq!(rcv.try_peek_and(|x| x > &5), Some(10)); - assert_eq!(rcv.try_peek_and(|x| x < &5), None); - assert!(rcv.try_changed().is_some()); - snd.send(15); assert_eq!(rcv.try_get_and(|x| x > &5), Some(15)); assert_eq!(rcv.try_get_and(|x| x < &5), None); @@ -771,7 +934,6 @@ mod tests { snd.send(30); assert_eq!(rcv.changed_and(|x| x > &5).await, 30); - assert_eq!(rcv.peek_and(|x| x > &5).await, 30); assert_eq!(rcv.get_and(|x| x > &5).await, 30); }; block_on(f); @@ -825,7 +987,7 @@ mod tests { // Obtain receivers and sender let mut rcv0 = WATCH.receiver().unwrap(); - let mut rcv1 = WATCH.receiver().unwrap(); + let mut rcv1 = WATCH.anon_receiver(); let snd = WATCH.sender(); // No update for both @@ -864,41 +1026,13 @@ mod tests { block_on(f); } - #[test] - fn peek_get_changed() { - let f = async { - static WATCH: Watch = Watch::new(); - - // Obtain receiver and sender - let mut rcv = WATCH.receiver().unwrap(); - let snd = WATCH.sender(); - - // Send a value - snd.send(10); - - // Ensure peek does not mark as seen - assert_eq!(rcv.peek().await, 10); - assert_eq!(rcv.try_changed(), Some(10)); - assert_eq!(rcv.try_changed(), None); - assert_eq!(rcv.try_peek(), Some(10)); - - // Send a value - snd.send(20); - - // Ensure get does mark as seen - assert_eq!(rcv.get().await, 20); - assert_eq!(rcv.try_changed(), None); - assert_eq!(rcv.try_get(), Some(20)); - }; - block_on(f); - } - #[test] fn use_dynamics() { let f = async { static WATCH: Watch = Watch::new(); // Obtain receiver and sender + let mut anon_rcv = WATCH.dyn_anon_receiver(); let mut dyn_rcv = WATCH.dyn_receiver().unwrap(); let dyn_snd = WATCH.dyn_sender(); @@ -906,6 +1040,7 @@ mod tests { dyn_snd.send(10); // Ensure the dynamic receiver receives the value + assert_eq!(anon_rcv.try_changed(), Some(10)); assert_eq!(dyn_rcv.try_changed(), Some(10)); assert_eq!(dyn_rcv.try_changed(), None); }; @@ -918,10 +1053,12 @@ mod tests { static WATCH: Watch = Watch::new(); // Obtain receiver and sender + let anon_rcv = WATCH.anon_receiver(); let rcv = WATCH.receiver().unwrap(); let snd = WATCH.sender(); // Convert to dynamic + let mut dyn_anon_rcv = anon_rcv.as_dyn(); let mut dyn_rcv = rcv.as_dyn(); let dyn_snd = snd.as_dyn(); @@ -929,6 +1066,7 @@ mod tests { dyn_snd.send(10); // Ensure the dynamic receiver receives the value + assert_eq!(dyn_anon_rcv.try_changed(), Some(10)); assert_eq!(dyn_rcv.try_changed(), Some(10)); assert_eq!(dyn_rcv.try_changed(), None); }; From 999807f226623669a9cfc8ca218d3c81f0c04a77 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Mon, 23 Sep 2024 20:29:50 +0200 Subject: [PATCH 026/144] Added SealedWatchBehavior to limit access to core functions --- embassy-sync/src/watch.rs | 137 ++++++++++++++++++++------------------ 1 file changed, 71 insertions(+), 66 deletions(-) diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 1b4a8b589..4b7ffa5fc 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -76,28 +76,14 @@ struct WatchState { receiver_count: usize, } -/// A trait representing the 'inner' behavior of the `Watch`. -pub trait WatchBehavior { - /// Sends a new value to the `Watch`. - fn send(&self, val: T); - - /// Clears the value of the `Watch`. - fn clear(&self); - +trait SealedWatchBehavior { /// Poll the `Watch` for the current value, making it as seen. fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; - /// Tries to get the value of the `Watch`, marking it as seen, if an id is given. - fn try_get(&self, id: Option<&mut u64>) -> Option; - /// Poll the `Watch` for the value if it matches the predicate function /// `f`, making it as seen. fn poll_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; - /// Tries to get the value of the `Watch` if it matches the predicate function - /// `f`, marking it as seen. - fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option; - /// Poll the `Watch` for a changed value, marking it as seen, if an id is given. fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; @@ -112,8 +98,16 @@ pub trait WatchBehavior { /// predicate function `f`, marking it as seen. fn try_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option; - /// Checks if the `Watch` is been initialized with a value. - fn contains_value(&self) -> bool; + /// Used when a receiver is dropped to decrement the receiver count. + /// + /// ## This method should not be called by the user. + fn drop_receiver(&self); + + /// Clears the value of the `Watch`. + fn clear(&self); + + /// Sends a new value to the `Watch`. + fn send(&self, val: T); /// Modify the value of the `Watch` using a closure. Returns `false` if the /// `Watch` does not already contain a value. @@ -122,30 +116,23 @@ pub trait WatchBehavior { /// Modify the value of the `Watch` using a closure. Returns `false` if the /// `Watch` does not already contain a value. fn send_if_modified(&self, f: &mut dyn Fn(&mut Option) -> bool); - - /// Used when a receiver is dropped to decrement the receiver count. - /// - /// ## This method should not be called by the user. - fn drop_receiver(&self); } -impl WatchBehavior for Watch { - fn send(&self, val: T) { - self.mutex.lock(|state| { - let mut s = state.borrow_mut(); - s.data = Some(val); - s.current_id += 1; - s.wakers.wake(); - }) - } +/// A trait representing the 'inner' behavior of the `Watch`. +#[allow(private_bounds)] +pub trait WatchBehavior: SealedWatchBehavior { + /// Tries to get the value of the `Watch`, marking it as seen, if an id is given. + fn try_get(&self, id: Option<&mut u64>) -> Option; - fn clear(&self) { - self.mutex.lock(|state| { - let mut s = state.borrow_mut(); - s.data = None; - }) - } + /// Tries to get the value of the `Watch` if it matches the predicate function + /// `f`, marking it as seen. + fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option; + /// Checks if the `Watch` is been initialized with a value. + fn contains_value(&self) -> bool; +} + +impl SealedWatchBehavior for Watch { fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); @@ -162,16 +149,6 @@ impl WatchBehavior for Watch }) } - fn try_get(&self, id: Option<&mut u64>) -> Option { - self.mutex.lock(|state| { - let s = state.borrow(); - if let Some(id) = id { - *id = s.current_id; - } - s.data.clone() - }) - } - fn poll_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); @@ -188,21 +165,6 @@ impl WatchBehavior for Watch }) } - fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option { - self.mutex.lock(|state| { - let s = state.borrow(); - match s.data { - Some(ref data) if f(data) => { - if let Some(id) = id { - *id = s.current_id; - } - Some(data.clone()) - } - _ => None, - } - }) - } - fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { self.mutex.lock(|state| { let mut s = state.borrow_mut(); @@ -261,10 +223,6 @@ impl WatchBehavior for Watch }) } - fn contains_value(&self) -> bool { - self.mutex.lock(|state| state.borrow().data.is_some()) - } - fn drop_receiver(&self) { self.mutex.lock(|state| { let mut s = state.borrow_mut(); @@ -272,6 +230,22 @@ impl WatchBehavior for Watch }) } + fn clear(&self) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = None; + }) + } + + fn send(&self, val: T) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = Some(val); + s.current_id += 1; + s.wakers.wake(); + }) + } + fn send_modify(&self, f: &mut dyn Fn(&mut Option)) { self.mutex.lock(|state| { let mut s = state.borrow_mut(); @@ -292,6 +266,37 @@ impl WatchBehavior for Watch } } +impl WatchBehavior for Watch { + fn try_get(&self, id: Option<&mut u64>) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + if let Some(id) = id { + *id = s.current_id; + } + s.data.clone() + }) + } + + fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match s.data { + Some(ref data) if f(data) => { + if let Some(id) = id { + *id = s.current_id; + } + Some(data.clone()) + } + _ => None, + } + }) + } + + fn contains_value(&self) -> bool { + self.mutex.lock(|state| state.borrow().data.is_some()) + } +} + impl Watch { /// Create a new `Watch` channel. pub const fn new() -> Self { From 5e1912a2d3adea920039dae3622643f34289290b Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Tue, 24 Sep 2024 12:37:32 +0200 Subject: [PATCH 027/144] Reverse generics order, remove spin_get --- embassy-sync/src/watch.rs | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 4b7ffa5fc..336e64ba9 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -66,10 +66,10 @@ use crate::waitqueue::MultiWakerRegistration; /// block_on(f); /// ``` pub struct Watch { - mutex: Mutex>>, + mutex: Mutex>>, } -struct WatchState { +struct WatchState { data: Option, current_id: u64, wakers: MultiWakerRegistration, @@ -365,30 +365,6 @@ impl Watch { self.mutex.lock(|state| state.borrow().current_id) } - /// Waits for the `Watch` to be initialized with a value using a busy-wait mechanism. - /// - /// This is useful for initialization code where receivers may only be interested in - /// awaiting the value once in the lifetime of the program. It is therefore a temporaryily - /// CPU-inefficient operation, while being more memory efficient than using a `Receiver`. - /// - /// **Note** Be careful about using this within an InterruptExecutor, as it will starve - /// tasks in lower-priority executors. - pub async fn spin_get(&self) -> T { - poll_fn(|cx| { - self.mutex.lock(|state| { - let s = state.borrow(); - match &s.data { - Some(data) => Poll::Ready(data.clone()), - None => { - cx.waker().wake_by_ref(); - Poll::Pending - } - } - }) - }) - .await - } - /// Tries to get the value of the `Watch`. pub fn try_get(&self) -> Option { WatchBehavior::try_get(self, None) From ae5b78b27a9c644850c905c197e216a6cd2ab0b9 Mon Sep 17 00:00:00 2001 From: Anthony Grondin <104731965+AnthonyGrondin@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:30:14 -0400 Subject: [PATCH 028/144] feat(embassy-net): Implement `wait_recv_ready()` + `wait_send_ready()` for UdpSocket - Provides `pub async fn wait_recv_ready(&self) -> ()` and `pub fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<()>`. This allows polling / waiting on a socket until it can be read, without dequeuing any packets. - Provides `pub async fn wait_send_ready(&self) -> ()` and `pub fn poll_send_ready(&self, cx: &mut Context<'_> -> Poll<()>` This allows polling / waiting on a socket until it becomes writable. --- embassy-net/src/udp.rs | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/embassy-net/src/udp.rs b/embassy-net/src/udp.rs index 3eb6e2f83..b71037522 100644 --- a/embassy-net/src/udp.rs +++ b/embassy-net/src/udp.rs @@ -103,6 +103,32 @@ impl<'a> UdpSocket<'a> { }) } + /// Wait until the socket becomes readable. + /// + /// A socket is readable when a packet has been received, or when there are queued packets in + /// the buffer. + pub async fn wait_recv_ready(&self) { + poll_fn(move |cx| self.poll_recv_ready(cx)).await + } + + /// Wait until a datagram can be read. + /// + /// When no datagram is readable, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + /// + /// When a datagram is received, this method will return `Poll::Ready`. + pub fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_recv() { + Poll::Ready(()) + } else { + // socket buffer is empty wait until at least one byte has arrived + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) + } + /// Receive a datagram. /// /// This method will wait until a datagram is received. @@ -164,6 +190,33 @@ impl<'a> UdpSocket<'a> { .await } + /// Wait until the socket becomes writable. + /// + /// A socket becomes writable when there is space in the buffer, from initial memory or after + /// dispatching datagrams on a full buffer. + pub async fn wait_send_ready(&self) { + poll_fn(move |cx| self.poll_send_ready(cx)).await + } + + /// Wait until a datagram can be sent. + /// + /// When no datagram can be sent (i.e. the buffer is full), this method will return + /// `Poll::Pending` and register the current task to be notified when + /// space is freed in the buffer after a datagram has been dispatched. + /// + /// When a datagram can be sent, this method will return `Poll::Ready`. + pub fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_send() { + Poll::Ready(()) + } else { + // socket buffer is full wait until a datagram has been dispatched + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + } + /// Send a datagram to the specified remote endpoint. /// /// This method will wait until the datagram has been sent. From e8da38772641ac19e5ded539144467efc9ed5a7b Mon Sep 17 00:00:00 2001 From: Anthony Grondin <104731965+AnthonyGrondin@users.noreply.github.com> Date: Tue, 24 Sep 2024 10:25:10 -0400 Subject: [PATCH 029/144] docs(embassy-net): Update can_send() and may_send() documentation to reflect actual behavior from smoltcp --- embassy-net/src/tcp.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index bcddbc95b..fc66d6192 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -376,11 +376,25 @@ impl<'a> TcpSocket<'a> { self.io.with_mut(|s, _| s.abort()) } - /// Get whether the socket is ready to send data, i.e. whether there is space in the send buffer. + /// Return whether the transmit half of the full-duplex connection is open. + /// + /// This function returns true if it's possible to send data and have it arrive + /// to the remote endpoint. However, it does not make any guarantees about the state + /// of the transmit buffer, and even if it returns true, [write](#method.write) may + /// not be able to enqueue any octets. + /// + /// In terms of the TCP state machine, the socket must be in the `ESTABLISHED` or + /// `CLOSE-WAIT` state. pub fn may_send(&self) -> bool { self.io.with(|s, _| s.may_send()) } + /// Check whether the transmit half of the full-duplex connection is open + /// (see [may_send](#method.may_send)), and the transmit buffer is not full. + pub fn can_send(&self) -> bool { + self.io.with(|s, _| s.can_send()) + } + /// return whether the receive half of the full-duplex connection is open. /// This function returns true if itā€™s possible to receive data from the remote endpoint. /// It will return true while there is data in the receive buffer, and if there isnā€™t, From 712fa08363067d0a0e3ff07b3bd0633bee3ba07e Mon Sep 17 00:00:00 2001 From: Anthony Grondin <104731965+AnthonyGrondin@users.noreply.github.com> Date: Tue, 24 Sep 2024 10:42:06 -0400 Subject: [PATCH 030/144] feat(embassy-net): Implement `wait_read_ready()` + `wait_write_ready()` for TcpSocket --- embassy-net/src/tcp.rs | 46 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index fc66d6192..8fdad01cc 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -10,7 +10,7 @@ use core::future::poll_fn; use core::mem; -use core::task::Poll; +use core::task::{Context, Poll}; use embassy_time::Duration; use smoltcp::iface::{Interface, SocketHandle}; @@ -274,6 +274,16 @@ impl<'a> TcpSocket<'a> { .await } + /// Wait until the socket becomes readable. + /// + /// A socket becomes readable when the receive half of the full-duplex connection is open + /// (see [may_recv](#method.may_recv)), and there is some pending data in the receive buffer. + /// + /// This is the equivalent of [read](#method.read), without buffering any data. + pub async fn wait_read_ready(&self) { + poll_fn(move |cx| self.io.poll_read_ready(cx)).await + } + /// Read data from the socket. /// /// Returns how many bytes were read, or an error. If no data is available, it waits @@ -285,6 +295,16 @@ impl<'a> TcpSocket<'a> { self.io.read(buf).await } + /// Wait until the socket becomes writable. + /// + /// A socket becomes writable when the transmit half of the full-duplex connection is open + /// (see [may_send](#method.may_send)), and the transmit buffer is not full. + /// + /// This is the equivalent of [write](#method.write), without sending any data. + pub async fn wait_write_ready(&self) { + poll_fn(move |cx| self.io.poll_write_ready(cx)).await + } + /// Write data to the socket. /// /// Returns how many bytes were written, or an error. If the socket is not ready to @@ -441,7 +461,7 @@ impl<'d> TcpIo<'d> { }) } - fn with_mut(&mut self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R { + fn with_mut(&self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R { self.stack.with_mut(|i| { let socket = i.sockets.get_mut::(self.handle); let res = f(socket, &mut i.iface); @@ -450,6 +470,17 @@ impl<'d> TcpIo<'d> { }) } + fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_recv() { + Poll::Ready(()) + } else { + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) + } + async fn read(&mut self, buf: &mut [u8]) -> Result { poll_fn(move |cx| { // CAUTION: smoltcp semantics around EOF are different to what you'd expect @@ -478,6 +509,17 @@ impl<'d> TcpIo<'d> { .await } + fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_send() { + Poll::Ready(()) + } else { + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + } + async fn write(&mut self, buf: &[u8]) -> Result { poll_fn(move |cx| { self.with_mut(|s, _| match s.send_slice(buf) { From 6e2c5d0b4500762b5a045a166c91f8b0e59db10e Mon Sep 17 00:00:00 2001 From: Romain Reignier Date: Thu, 26 Sep 2024 13:24:50 +0200 Subject: [PATCH 031/144] rp23: add missing binary info in linker script See https://github.com/rp-rs/rp-hal/issues/853 And https://github.com/rp-rs/rp-hal/pull/854 --- examples/rp23/memory.x | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/rp23/memory.x b/examples/rp23/memory.x index 777492062..c803896f6 100644 --- a/examples/rp23/memory.x +++ b/examples/rp23/memory.x @@ -31,6 +31,7 @@ SECTIONS { { __start_block_addr = .; KEEP(*(.start_block)); + KEEP(*(.boot_info)); } > FLASH } INSERT AFTER .vector_table; From f19718b4f0400dec4e64d32d649c6b0d9eb554e5 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Thu, 26 Sep 2024 15:41:21 +0200 Subject: [PATCH 032/144] Add config option for setting SIM pin --- embassy-net-nrf91/src/context.rs | 12 ++++++++++++ examples/nrf9160/src/bin/modem_tcp_client.rs | 1 + 2 files changed, 13 insertions(+) diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs index 8b45919ef..2dda615c1 100644 --- a/embassy-net-nrf91/src/context.rs +++ b/embassy-net-nrf91/src/context.rs @@ -21,6 +21,8 @@ pub struct Config<'a> { pub auth_prot: AuthProt, /// Credentials. pub auth: Option<(&'a [u8], &'a [u8])>, + /// SIM pin + pub pin: Option<&'a [u8]>, } /// Authentication protocol. @@ -133,6 +135,16 @@ impl<'a> Control<'a> { // info!("RES2: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) }); CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + if let Some(pin) = config.pin { + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CPIN") + .with_string_parameter(pin) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let _ = self.control.at_command(op, &mut buf).await; + // Ignore ERROR which means no pin required + } + Ok(()) } diff --git a/examples/nrf9160/src/bin/modem_tcp_client.rs b/examples/nrf9160/src/bin/modem_tcp_client.rs index 929883884..495ee26dd 100644 --- a/examples/nrf9160/src/bin/modem_tcp_client.rs +++ b/examples/nrf9160/src/bin/modem_tcp_client.rs @@ -163,6 +163,7 @@ async fn main(spawner: Spawner) { apn: b"iot.nat.es", auth_prot: context::AuthProt::Pap, auth: Some((b"orange", b"orange")), + pin: None, }, stack ))); From 3792b14161ded15fe513640d0c1b52f39fb80856 Mon Sep 17 00:00:00 2001 From: Dinu Blanovschi Date: Fri, 27 Sep 2024 10:40:24 +0200 Subject: [PATCH 033/144] fix: change duplicate reference to `FirmwareUpdaterConfig::from_linkerfile_blocking` in rustdoc --- embassy-boot/src/firmware_updater/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-boot/src/firmware_updater/mod.rs b/embassy-boot/src/firmware_updater/mod.rs index 4c4f4f10b..4814786bf 100644 --- a/embassy-boot/src/firmware_updater/mod.rs +++ b/embassy-boot/src/firmware_updater/mod.rs @@ -8,7 +8,7 @@ use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; /// Firmware updater flash configuration holding the two flashes used by the updater /// /// If only a single flash is actually used, then that flash should be partitioned into two partitions before use. -/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile_blocking`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition +/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition /// the provided flash according to symbols defined in the linkerfile. pub struct FirmwareUpdaterConfig { /// The dfu flash partition From bc0180800d751e651c0d15c807285c11cdb4f486 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Tue, 1 Oct 2024 10:51:18 -0400 Subject: [PATCH 034/144] Remove binary_info blocks from most examples. (#3385) --- examples/rp23/src/bin/adc.rs | 12 +----------- examples/rp23/src/bin/adc_dma.rs | 10 ---------- examples/rp23/src/bin/assign_resources.rs | 10 ---------- examples/rp23/src/bin/blinky.rs | 11 +++++++---- examples/rp23/src/bin/blinky_two_channels.rs | 10 ---------- examples/rp23/src/bin/blinky_two_tasks.rs | 10 ---------- examples/rp23/src/bin/button.rs | 10 ---------- examples/rp23/src/bin/debounce.rs | 10 ---------- examples/rp23/src/bin/flash.rs | 10 ---------- examples/rp23/src/bin/gpio_async.rs | 10 ---------- examples/rp23/src/bin/gpout.rs | 10 ---------- examples/rp23/src/bin/i2c_async.rs | 10 ---------- examples/rp23/src/bin/i2c_async_embassy.rs | 10 ---------- examples/rp23/src/bin/i2c_blocking.rs | 10 ---------- examples/rp23/src/bin/i2c_slave.rs | 10 ---------- examples/rp23/src/bin/interrupt.rs | 10 ---------- examples/rp23/src/bin/multicore.rs | 10 ---------- examples/rp23/src/bin/multiprio.rs | 10 ---------- examples/rp23/src/bin/otp.rs | 10 ---------- examples/rp23/src/bin/pio_async.rs | 10 ---------- examples/rp23/src/bin/pio_dma.rs | 10 ---------- examples/rp23/src/bin/pio_hd44780.rs | 10 ---------- examples/rp23/src/bin/pio_i2s.rs | 10 ---------- examples/rp23/src/bin/pio_pwm.rs | 10 ---------- examples/rp23/src/bin/pio_rotary_encoder.rs | 10 ---------- examples/rp23/src/bin/pio_servo.rs | 10 ---------- examples/rp23/src/bin/pio_stepper.rs | 10 ---------- examples/rp23/src/bin/pio_ws2812.rs | 10 ---------- examples/rp23/src/bin/pwm.rs | 10 ---------- examples/rp23/src/bin/pwm_input.rs | 10 ---------- examples/rp23/src/bin/rosc.rs | 10 ---------- examples/rp23/src/bin/shared_bus.rs | 10 ---------- examples/rp23/src/bin/sharing.rs | 10 ---------- examples/rp23/src/bin/spi.rs | 10 ---------- examples/rp23/src/bin/spi_async.rs | 10 ---------- examples/rp23/src/bin/spi_display.rs | 10 ---------- examples/rp23/src/bin/spi_sdmmc.rs | 10 ---------- examples/rp23/src/bin/trng.rs | 10 ---------- examples/rp23/src/bin/uart.rs | 10 ---------- examples/rp23/src/bin/uart_buffered_split.rs | 10 ---------- examples/rp23/src/bin/uart_r503.rs | 10 ---------- examples/rp23/src/bin/uart_unidir.rs | 10 ---------- examples/rp23/src/bin/usb_webusb.rs | 10 ---------- examples/rp23/src/bin/watchdog.rs | 10 ---------- examples/rp23/src/bin/zerocopy.rs | 10 ---------- 45 files changed, 8 insertions(+), 445 deletions(-) diff --git a/examples/rp23/src/bin/adc.rs b/examples/rp23/src/bin/adc.rs index d1f053d39..f7db9653a 100644 --- a/examples/rp23/src/bin/adc.rs +++ b/examples/rp23/src/bin/adc.rs @@ -1,4 +1,4 @@ -//! This example test the ADC (Analog to Digital Conversion) of the RS2040 pin 26, 27 and 28. +//! This example test the ADC (Analog to Digital Conversion) of the RP2350A pins 26, 27 and 28. //! It also reads the temperature sensor in the chip. #![no_std] @@ -17,16 +17,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { ADC_IRQ_FIFO => InterruptHandler; }); diff --git a/examples/rp23/src/bin/adc_dma.rs b/examples/rp23/src/bin/adc_dma.rs index 5046e5530..a6814c23a 100644 --- a/examples/rp23/src/bin/adc_dma.rs +++ b/examples/rp23/src/bin/adc_dma.rs @@ -17,16 +17,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { ADC_IRQ_FIFO => InterruptHandler; }); diff --git a/examples/rp23/src/bin/assign_resources.rs b/examples/rp23/src/bin/assign_resources.rs index 2f9783917..0d4ad8dc3 100644 --- a/examples/rp23/src/bin/assign_resources.rs +++ b/examples/rp23/src/bin/assign_resources.rs @@ -24,16 +24,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(spawner: Spawner) { // initialize the peripherals diff --git a/examples/rp23/src/bin/blinky.rs b/examples/rp23/src/bin/blinky.rs index 9e45679c8..c1ddbb7d2 100644 --- a/examples/rp23/src/bin/blinky.rs +++ b/examples/rp23/src/bin/blinky.rs @@ -17,20 +17,23 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` +// Program metadata for `picotool info`. +// This isn't needed, but it's recomended to have these minimal entries. #[link_section = ".bi_entries"] #[used] pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_program_name!(c"Blinky Example"), + embassy_rp::binary_info::rp_program_description!( + c"This example tests the RP Pico on board LED, connected to gpio 25" + ), embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), embassy_rp::binary_info::rp_program_build_attribute!(), ]; #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); - let mut led = Output::new(p.PIN_2, Level::Low); + let mut led = Output::new(p.PIN_25, Level::Low); loop { info!("led on!"); diff --git a/examples/rp23/src/bin/blinky_two_channels.rs b/examples/rp23/src/bin/blinky_two_channels.rs index 87fc58bbc..ce482858e 100644 --- a/examples/rp23/src/bin/blinky_two_channels.rs +++ b/examples/rp23/src/bin/blinky_two_channels.rs @@ -19,16 +19,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - enum LedState { Toggle, } diff --git a/examples/rp23/src/bin/blinky_two_tasks.rs b/examples/rp23/src/bin/blinky_two_tasks.rs index 40236c53b..5dc62245d 100644 --- a/examples/rp23/src/bin/blinky_two_tasks.rs +++ b/examples/rp23/src/bin/blinky_two_tasks.rs @@ -19,16 +19,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - type LedType = Mutex>>; static LED: LedType = Mutex::new(None); diff --git a/examples/rp23/src/bin/button.rs b/examples/rp23/src/bin/button.rs index fb067a370..85f1bcae3 100644 --- a/examples/rp23/src/bin/button.rs +++ b/examples/rp23/src/bin/button.rs @@ -14,16 +14,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/debounce.rs b/examples/rp23/src/bin/debounce.rs index e672521ec..4c8b80d92 100644 --- a/examples/rp23/src/bin/debounce.rs +++ b/examples/rp23/src/bin/debounce.rs @@ -15,16 +15,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - pub struct Debouncer<'a> { input: Input<'a>, debounce: Duration, diff --git a/examples/rp23/src/bin/flash.rs b/examples/rp23/src/bin/flash.rs index 84011e394..28dec24c9 100644 --- a/examples/rp23/src/bin/flash.rs +++ b/examples/rp23/src/bin/flash.rs @@ -15,16 +15,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Flash"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - const ADDR_OFFSET: u32 = 0x100000; const FLASH_SIZE: usize = 2 * 1024 * 1024; diff --git a/examples/rp23/src/bin/gpio_async.rs b/examples/rp23/src/bin/gpio_async.rs index ff12367bf..bfb9a3f95 100644 --- a/examples/rp23/src/bin/gpio_async.rs +++ b/examples/rp23/src/bin/gpio_async.rs @@ -17,16 +17,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - /// It requires an external signal to be manually triggered on PIN 16. For /// example, this could be accomplished using an external power source with a /// button so that it is possible to toggle the signal from low to high. diff --git a/examples/rp23/src/bin/gpout.rs b/examples/rp23/src/bin/gpout.rs index d2ee55197..3cc2ea938 100644 --- a/examples/rp23/src/bin/gpout.rs +++ b/examples/rp23/src/bin/gpout.rs @@ -16,16 +16,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/i2c_async.rs b/examples/rp23/src/bin/i2c_async.rs index c8d918b56..b30088bae 100644 --- a/examples/rp23/src/bin/i2c_async.rs +++ b/examples/rp23/src/bin/i2c_async.rs @@ -20,16 +20,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { I2C1_IRQ => InterruptHandler; }); diff --git a/examples/rp23/src/bin/i2c_async_embassy.rs b/examples/rp23/src/bin/i2c_async_embassy.rs index cce0abcde..c783a80c5 100644 --- a/examples/rp23/src/bin/i2c_async_embassy.rs +++ b/examples/rp23/src/bin/i2c_async_embassy.rs @@ -15,16 +15,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - // Our anonymous hypotetical temperature sensor could be: // a 12-bit sensor, with 100ms startup time, range of -40*C - 125*C, and precision 0.25*C // It requires no configuration or calibration, works with all i2c bus speeds, diff --git a/examples/rp23/src/bin/i2c_blocking.rs b/examples/rp23/src/bin/i2c_blocking.rs index 85c33bf0d..a68677311 100644 --- a/examples/rp23/src/bin/i2c_blocking.rs +++ b/examples/rp23/src/bin/i2c_blocking.rs @@ -18,16 +18,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[allow(dead_code)] mod mcp23017 { pub const ADDR: u8 = 0x20; // default addr diff --git a/examples/rp23/src/bin/i2c_slave.rs b/examples/rp23/src/bin/i2c_slave.rs index fb5f3cda1..8817538c0 100644 --- a/examples/rp23/src/bin/i2c_slave.rs +++ b/examples/rp23/src/bin/i2c_slave.rs @@ -15,16 +15,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { I2C0_IRQ => i2c::InterruptHandler; I2C1_IRQ => i2c::InterruptHandler; diff --git a/examples/rp23/src/bin/interrupt.rs b/examples/rp23/src/bin/interrupt.rs index ee3d9bfe7..d9b662253 100644 --- a/examples/rp23/src/bin/interrupt.rs +++ b/examples/rp23/src/bin/interrupt.rs @@ -29,16 +29,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - static COUNTER: AtomicU32 = AtomicU32::new(0); static PWM: Mutex>> = Mutex::new(RefCell::new(None)); static ADC: Mutex, adc::Channel)>>> = diff --git a/examples/rp23/src/bin/multicore.rs b/examples/rp23/src/bin/multicore.rs index 9ab43d7a5..d4d470fa2 100644 --- a/examples/rp23/src/bin/multicore.rs +++ b/examples/rp23/src/bin/multicore.rs @@ -20,16 +20,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - static mut CORE1_STACK: Stack<4096> = Stack::new(); static EXECUTOR0: StaticCell = StaticCell::new(); static EXECUTOR1: StaticCell = StaticCell::new(); diff --git a/examples/rp23/src/bin/multiprio.rs b/examples/rp23/src/bin/multiprio.rs index 27cd3656e..787854aa9 100644 --- a/examples/rp23/src/bin/multiprio.rs +++ b/examples/rp23/src/bin/multiprio.rs @@ -70,16 +70,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::task] async fn run_high() { loop { diff --git a/examples/rp23/src/bin/otp.rs b/examples/rp23/src/bin/otp.rs index 106e514ca..c67c9821a 100644 --- a/examples/rp23/src/bin/otp.rs +++ b/examples/rp23/src/bin/otp.rs @@ -14,16 +14,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"OTP Read Example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"OTP Read Example"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let _ = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/pio_async.rs b/examples/rp23/src/bin/pio_async.rs index 231afc80e..896447e28 100644 --- a/examples/rp23/src/bin/pio_async.rs +++ b/examples/rp23/src/bin/pio_async.rs @@ -16,16 +16,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); diff --git a/examples/rp23/src/bin/pio_dma.rs b/examples/rp23/src/bin/pio_dma.rs index 60fbcb83a..b5f754798 100644 --- a/examples/rp23/src/bin/pio_dma.rs +++ b/examples/rp23/src/bin/pio_dma.rs @@ -17,16 +17,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); diff --git a/examples/rp23/src/bin/pio_hd44780.rs b/examples/rp23/src/bin/pio_hd44780.rs index 92aa858f9..5a6d7a9c5 100644 --- a/examples/rp23/src/bin/pio_hd44780.rs +++ b/examples/rp23/src/bin/pio_hd44780.rs @@ -22,16 +22,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(pub struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); diff --git a/examples/rp23/src/bin/pio_i2s.rs b/examples/rp23/src/bin/pio_i2s.rs index d6d2d0ade..46e5eac88 100644 --- a/examples/rp23/src/bin/pio_i2s.rs +++ b/examples/rp23/src/bin/pio_i2s.rs @@ -25,16 +25,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); diff --git a/examples/rp23/src/bin/pio_pwm.rs b/examples/rp23/src/bin/pio_pwm.rs index 587f91ac3..3cffd213d 100644 --- a/examples/rp23/src/bin/pio_pwm.rs +++ b/examples/rp23/src/bin/pio_pwm.rs @@ -18,16 +18,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - const REFRESH_INTERVAL: u64 = 20000; bind_interrupts!(struct Irqs { diff --git a/examples/rp23/src/bin/pio_rotary_encoder.rs b/examples/rp23/src/bin/pio_rotary_encoder.rs index c147351e8..9542d63b7 100644 --- a/examples/rp23/src/bin/pio_rotary_encoder.rs +++ b/examples/rp23/src/bin/pio_rotary_encoder.rs @@ -17,16 +17,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); diff --git a/examples/rp23/src/bin/pio_servo.rs b/examples/rp23/src/bin/pio_servo.rs index 5e8714178..3202ab475 100644 --- a/examples/rp23/src/bin/pio_servo.rs +++ b/examples/rp23/src/bin/pio_servo.rs @@ -18,16 +18,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - const DEFAULT_MIN_PULSE_WIDTH: u64 = 1000; // uncalibrated default, the shortest duty cycle sent to a servo const DEFAULT_MAX_PULSE_WIDTH: u64 = 2000; // uncalibrated default, the longest duty cycle sent to a servo const DEFAULT_MAX_DEGREE_ROTATION: u64 = 160; // 160 degrees is typical diff --git a/examples/rp23/src/bin/pio_stepper.rs b/examples/rp23/src/bin/pio_stepper.rs index 24785443b..5e87da6eb 100644 --- a/examples/rp23/src/bin/pio_stepper.rs +++ b/examples/rp23/src/bin/pio_stepper.rs @@ -21,16 +21,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); diff --git a/examples/rp23/src/bin/pio_ws2812.rs b/examples/rp23/src/bin/pio_ws2812.rs index 00fe5e396..1f1984c4d 100644 --- a/examples/rp23/src/bin/pio_ws2812.rs +++ b/examples/rp23/src/bin/pio_ws2812.rs @@ -23,16 +23,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); diff --git a/examples/rp23/src/bin/pwm.rs b/examples/rp23/src/bin/pwm.rs index bfc2c6f67..15eae09ee 100644 --- a/examples/rp23/src/bin/pwm.rs +++ b/examples/rp23/src/bin/pwm.rs @@ -16,16 +16,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/pwm_input.rs b/examples/rp23/src/bin/pwm_input.rs index b65f2778b..ef87fe8b5 100644 --- a/examples/rp23/src/bin/pwm_input.rs +++ b/examples/rp23/src/bin/pwm_input.rs @@ -15,16 +15,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/rosc.rs b/examples/rp23/src/bin/rosc.rs index f65b236b1..a096f0b7a 100644 --- a/examples/rp23/src/bin/rosc.rs +++ b/examples/rp23/src/bin/rosc.rs @@ -17,16 +17,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut config = embassy_rp::config::Config::default(); diff --git a/examples/rp23/src/bin/shared_bus.rs b/examples/rp23/src/bin/shared_bus.rs index b3fde13e3..2151ccb56 100644 --- a/examples/rp23/src/bin/shared_bus.rs +++ b/examples/rp23/src/bin/shared_bus.rs @@ -23,16 +23,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - type Spi1Bus = Mutex>; type I2c1Bus = Mutex>; diff --git a/examples/rp23/src/bin/sharing.rs b/examples/rp23/src/bin/sharing.rs index 4a3301cfd..68eb5d133 100644 --- a/examples/rp23/src/bin/sharing.rs +++ b/examples/rp23/src/bin/sharing.rs @@ -36,16 +36,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - type UartAsyncMutex = mutex::Mutex>; struct MyType { diff --git a/examples/rp23/src/bin/spi.rs b/examples/rp23/src/bin/spi.rs index 924873e60..aacb8c7db 100644 --- a/examples/rp23/src/bin/spi.rs +++ b/examples/rp23/src/bin/spi.rs @@ -17,16 +17,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/spi_async.rs b/examples/rp23/src/bin/spi_async.rs index 4a74f991c..ac7f02fa8 100644 --- a/examples/rp23/src/bin/spi_async.rs +++ b/examples/rp23/src/bin/spi_async.rs @@ -15,16 +15,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/spi_display.rs b/examples/rp23/src/bin/spi_display.rs index 71dd84658..195db5a97 100644 --- a/examples/rp23/src/bin/spi_display.rs +++ b/examples/rp23/src/bin/spi_display.rs @@ -32,16 +32,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - use crate::my_display_interface::SPIDeviceInterface; use crate::touch::Touch; diff --git a/examples/rp23/src/bin/spi_sdmmc.rs b/examples/rp23/src/bin/spi_sdmmc.rs index dabf41ab8..aa6b44ffa 100644 --- a/examples/rp23/src/bin/spi_sdmmc.rs +++ b/examples/rp23/src/bin/spi_sdmmc.rs @@ -21,16 +21,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - struct DummyTimesource(); impl embedded_sdmmc::TimeSource for DummyTimesource { diff --git a/examples/rp23/src/bin/trng.rs b/examples/rp23/src/bin/trng.rs index e146baa2e..8251ebd8b 100644 --- a/examples/rp23/src/bin/trng.rs +++ b/examples/rp23/src/bin/trng.rs @@ -18,16 +18,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { TRNG_IRQ => embassy_rp::trng::InterruptHandler; }); diff --git a/examples/rp23/src/bin/uart.rs b/examples/rp23/src/bin/uart.rs index 0ffe0b293..fe28bb046 100644 --- a/examples/rp23/src/bin/uart.rs +++ b/examples/rp23/src/bin/uart.rs @@ -16,16 +16,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/uart_buffered_split.rs b/examples/rp23/src/bin/uart_buffered_split.rs index 4e69a20c4..9ed130727 100644 --- a/examples/rp23/src/bin/uart_buffered_split.rs +++ b/examples/rp23/src/bin/uart_buffered_split.rs @@ -22,16 +22,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { UART0_IRQ => BufferedInterruptHandler; }); diff --git a/examples/rp23/src/bin/uart_r503.rs b/examples/rp23/src/bin/uart_r503.rs index 5ac8839e3..9aed42785 100644 --- a/examples/rp23/src/bin/uart_r503.rs +++ b/examples/rp23/src/bin/uart_r503.rs @@ -15,16 +15,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(pub struct Irqs { UART0_IRQ => UARTInterruptHandler; }); diff --git a/examples/rp23/src/bin/uart_unidir.rs b/examples/rp23/src/bin/uart_unidir.rs index 988e44a79..12214c4c2 100644 --- a/examples/rp23/src/bin/uart_unidir.rs +++ b/examples/rp23/src/bin/uart_unidir.rs @@ -21,16 +21,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { UART1_IRQ => InterruptHandler; }); diff --git a/examples/rp23/src/bin/usb_webusb.rs b/examples/rp23/src/bin/usb_webusb.rs index 3ade2226b..15279cabc 100644 --- a/examples/rp23/src/bin/usb_webusb.rs +++ b/examples/rp23/src/bin/usb_webusb.rs @@ -34,16 +34,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { USBCTRL_IRQ => InterruptHandler; }); diff --git a/examples/rp23/src/bin/watchdog.rs b/examples/rp23/src/bin/watchdog.rs index a901c1164..efc24c4e3 100644 --- a/examples/rp23/src/bin/watchdog.rs +++ b/examples/rp23/src/bin/watchdog.rs @@ -18,16 +18,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/zerocopy.rs b/examples/rp23/src/bin/zerocopy.rs index 86fca6f12..d317c4b56 100644 --- a/examples/rp23/src/bin/zerocopy.rs +++ b/examples/rp23/src/bin/zerocopy.rs @@ -23,16 +23,6 @@ use {defmt_rtt as _, panic_probe as _}; #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info::rp_program_name!(c"example"), - embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), - embassy_rp::binary_info::rp_program_build_attribute!(), -]; - type SampleBuffer = [u16; 512]; bind_interrupts!(struct Irqs { From ce701c3e8ef5f3cd354b74c1a06b6d77a9e812c6 Mon Sep 17 00:00:00 2001 From: Paul Fornage <36117326+paulwrath1223@users.noreply.github.com> Date: Wed, 2 Oct 2024 13:35:59 -0700 Subject: [PATCH 035/144] Fixed overflow on `pio_stepper.rs` --- examples/rp/src/bin/pio_stepper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rp/src/bin/pio_stepper.rs b/examples/rp/src/bin/pio_stepper.rs index 4952f4fbd..6ee45a414 100644 --- a/examples/rp/src/bin/pio_stepper.rs +++ b/examples/rp/src/bin/pio_stepper.rs @@ -154,7 +154,7 @@ async fn main(_spawner: Spawner) { stepper.step(1000).await; info!("CCW full steps, drop after 1 sec"); - if let Err(_) = with_timeout(Duration::from_secs(1), stepper.step(i32::MIN)).await { + if let Err(_) = with_timeout(Duration::from_secs(1), stepper.step(-i32::MAX)).await { info!("Time's up!"); Timer::after(Duration::from_secs(1)).await; } From b73b3f2da0940907dfc39bec62ff33c976874017 Mon Sep 17 00:00:00 2001 From: Sebastian Quilitz Date: Sat, 5 Oct 2024 12:18:33 +0200 Subject: [PATCH 036/144] rp: Run RP235x at 150 MHz instead of 125 --- embassy-rp/src/clocks.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index f229a5acd..e82beb0f1 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -109,7 +109,10 @@ impl ClockConfig { sys_pll: Some(PllConfig { refdiv: 1, fbdiv: 125, + #[cfg(feature = "rp2040")] post_div1: 6, + #[cfg(feature = "_rp235x")] + post_div1: 5, post_div2: 2, }), usb_pll: Some(PllConfig { From d643d50f41cc6091eb573e2845fde48eeed9be4c Mon Sep 17 00:00:00 2001 From: rafael Date: Sat, 5 Oct 2024 12:24:17 +0200 Subject: [PATCH 037/144] Add Watch to embassy-sync README --- embassy-sync/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/embassy-sync/README.md b/embassy-sync/README.md index 97a663d6d..3dcd9dcc8 100644 --- a/embassy-sync/README.md +++ b/embassy-sync/README.md @@ -8,6 +8,7 @@ Synchronization primitives and data structures with async support: - [`PriorityChannel`](channel::priority_channel::PriorityChannel) - A Multiple Producer Multiple Consumer (MPMC) channel. Each message is only received by a single consumer. Higher priority items are shifted to the front of the channel. - [`PubSubChannel`](pubsub::PubSubChannel) - A broadcast channel (publish-subscribe) channel. Each message is received by all consumers. - [`Signal`](signal::Signal) - Signalling latest value to a single consumer. +- [`Watch`](watch::Watch) - Signalling latest value to multiple consumers. - [`Mutex`](mutex::Mutex) - Mutex for synchronizing state between asynchronous tasks. - [`Pipe`](pipe::Pipe) - Byte stream implementing `embedded_io` traits. - [`WakerRegistration`](waitqueue::WakerRegistration) - Utility to register and wake a `Waker`. From 383ad72b63b11ed1fc50ad5803534ac69996aff6 Mon Sep 17 00:00:00 2001 From: Oliver Rockstedt Date: Sat, 5 Oct 2024 13:39:27 +0200 Subject: [PATCH 038/144] embassy-sync: add clear, len, is_empty and is_full functions to zerocopy_channel --- embassy-sync/CHANGELOG.md | 1 + embassy-sync/src/zerocopy_channel.rs | 76 ++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/embassy-sync/CHANGELOG.md b/embassy-sync/CHANGELOG.md index 8f2d26fe0..8847e7e88 100644 --- a/embassy-sync/CHANGELOG.md +++ b/embassy-sync/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - Add LazyLock sync primitive. +- Add `clear`, `len`, `is_empty` and `is_full` functions to `zerocopy_channel`. ## 0.6.0 - 2024-05-29 diff --git a/embassy-sync/src/zerocopy_channel.rs b/embassy-sync/src/zerocopy_channel.rs index cfce9a571..a2c763294 100644 --- a/embassy-sync/src/zerocopy_channel.rs +++ b/embassy-sync/src/zerocopy_channel.rs @@ -70,6 +70,28 @@ impl<'a, M: RawMutex, T> Channel<'a, M, T> { pub fn split(&mut self) -> (Sender<'_, M, T>, Receiver<'_, M, T>) { (Sender { channel: self }, Receiver { channel: self }) } + + /// Clears all elements in the channel. + pub fn clear(&mut self) { + self.state.lock(|s| { + s.borrow_mut().clear(); + }); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.state.lock(|s| s.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.state.lock(|s| s.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.state.lock(|s| s.borrow().is_full()) + } } /// Send-only access to a [`Channel`]. @@ -130,6 +152,28 @@ impl<'a, M: RawMutex, T> Sender<'a, M, T> { pub fn send_done(&mut self) { self.channel.state.lock(|s| s.borrow_mut().push_done()) } + + /// Clears all elements in the channel. + pub fn clear(&mut self) { + self.channel.state.lock(|s| { + s.borrow_mut().clear(); + }); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.channel.state.lock(|s| s.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_full()) + } } /// Receive-only access to a [`Channel`]. @@ -190,6 +234,28 @@ impl<'a, M: RawMutex, T> Receiver<'a, M, T> { pub fn receive_done(&mut self) { self.channel.state.lock(|s| s.borrow_mut().pop_done()) } + + /// Clears all elements in the channel. + pub fn clear(&mut self) { + self.channel.state.lock(|s| { + s.borrow_mut().clear(); + }); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.channel.state.lock(|s| s.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_full()) + } } struct State { @@ -217,6 +283,16 @@ impl State { } } + fn clear(&mut self) { + self.front = 0; + self.back = 0; + self.full = false; + } + + fn len(&self) -> usize { + self.len + } + fn is_full(&self) -> bool { self.full } From 67836f955af133fa2ea7427172bbf352c51e4ab2 Mon Sep 17 00:00:00 2001 From: Chris Maniewski Date: Sat, 5 Oct 2024 14:16:00 +0200 Subject: [PATCH 039/144] docs: fix Sender/Receiver typo --- embassy-sync/src/watch.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index 336e64ba9..59798d04f 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -310,12 +310,12 @@ impl Watch { } } - /// Create a new [`Receiver`] for the `Watch`. + /// Create a new [`Sender`] for the `Watch`. pub fn sender(&self) -> Sender<'_, M, T, N> { Sender(Snd::new(self)) } - /// Create a new [`DynReceiver`] for the `Watch`. + /// Create a new [`DynSender`] for the `Watch`. pub fn dyn_sender(&self) -> DynSender<'_, T> { DynSender(Snd::new(self)) } From f3ed0c60265c84ddcc11e4dea980bdc0b8343985 Mon Sep 17 00:00:00 2001 From: Oliver Rockstedt Date: Sun, 6 Oct 2024 17:39:47 +0200 Subject: [PATCH 040/144] embassy-sync: fix len calculation for zerocopy_channel --- embassy-sync/src/zerocopy_channel.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/embassy-sync/src/zerocopy_channel.rs b/embassy-sync/src/zerocopy_channel.rs index a2c763294..a669cbd09 100644 --- a/embassy-sync/src/zerocopy_channel.rs +++ b/embassy-sync/src/zerocopy_channel.rs @@ -290,7 +290,15 @@ impl State { } fn len(&self) -> usize { - self.len + if !self.full { + if self.back >= self.front { + self.back - self.front + } else { + self.len + self.back - self.front + } + } else { + self.len + } } fn is_full(&self) -> bool { From 12e6add058b1bbe69660717bdef3d414a04b8b19 Mon Sep 17 00:00:00 2001 From: Oliver Rockstedt Date: Sun, 6 Oct 2024 17:45:03 +0200 Subject: [PATCH 041/144] embassy-sync: renamed field len to capacity on zerocopy_channel state --- embassy-sync/src/zerocopy_channel.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/embassy-sync/src/zerocopy_channel.rs b/embassy-sync/src/zerocopy_channel.rs index a669cbd09..fabb69bf6 100644 --- a/embassy-sync/src/zerocopy_channel.rs +++ b/embassy-sync/src/zerocopy_channel.rs @@ -53,7 +53,7 @@ impl<'a, M: RawMutex, T> Channel<'a, M, T> { buf: buf.as_mut_ptr(), phantom: PhantomData, state: Mutex::new(RefCell::new(State { - len, + capacity: len, front: 0, back: 0, full: false, @@ -259,7 +259,8 @@ impl<'a, M: RawMutex, T> Receiver<'a, M, T> { } struct State { - len: usize, + /// Maximum number of elements the channel can hold. + capacity: usize, /// Front index. Always 0..=(N-1) front: usize, @@ -276,7 +277,7 @@ struct State { impl State { fn increment(&self, i: usize) -> usize { - if i + 1 == self.len { + if i + 1 == self.capacity { 0 } else { i + 1 @@ -294,10 +295,10 @@ impl State { if self.back >= self.front { self.back - self.front } else { - self.len + self.back - self.front + self.capacity + self.back - self.front } } else { - self.len + self.capacity } } From f6155cf735678fa1e297baa4ace992af3a871ae7 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 6 Oct 2024 23:47:43 +0200 Subject: [PATCH 042/144] Update smoltcp, embedded-nal-async to use the `core::net` IP addr types. --- embassy-net-ppp/Cargo.toml | 2 +- embassy-net/Cargo.toml | 4 ++-- embassy-net/src/dns.rs | 17 ++++++++--------- embassy-net/src/tcp.rs | 12 ++++-------- examples/nrf9160/src/bin/modem_tcp_client.rs | 12 +++++------- examples/rp/Cargo.toml | 4 ++-- examples/rp23/Cargo.toml | 2 -- examples/std/src/bin/net_ppp.rs | 6 +++--- examples/stm32h5/Cargo.toml | 2 +- examples/stm32h7/Cargo.toml | 2 +- examples/stm32h7/src/bin/eth_client.rs | 4 +++- examples/stm32h7/src/bin/eth_client_mii.rs | 4 +++- examples/stm32h755cm4/Cargo.toml | 2 +- examples/stm32h755cm7/Cargo.toml | 2 +- examples/stm32h7rs/Cargo.toml | 2 +- .../stm32l4/src/bin/spe_adin1110_http_server.rs | 2 +- 16 files changed, 37 insertions(+), 42 deletions(-) diff --git a/embassy-net-ppp/Cargo.toml b/embassy-net-ppp/Cargo.toml index f6371f955..d2f43ef45 100644 --- a/embassy-net-ppp/Cargo.toml +++ b/embassy-net-ppp/Cargo.toml @@ -20,7 +20,7 @@ log = { version = "0.4.14", optional = true } embedded-io-async = { version = "0.6.1" } embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } -ppproto = { version = "0.1.2"} +ppproto = { version = "0.2.0"} embassy-sync = { version = "0.6.0", path = "../embassy-sync" } [package.metadata.embassy_docs] diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index 2e21b4231..a33c693fc 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -68,7 +68,7 @@ multicast = ["smoltcp/multicast"] defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } -smoltcp = { git="https://github.com/smoltcp-rs/smoltcp", rev="dd43c8f189178b0ab3bda798ed8578b5b0a6f094", default-features = false, features = [ +smoltcp = { git="https://github.com/smoltcp-rs/smoltcp", rev="b65e1b64dc9b66fa984a2ad34e90685cb0b606de", default-features = false, features = [ "socket", "async", ] } @@ -80,5 +80,5 @@ embedded-io-async = { version = "0.6.1" } managed = { version = "0.8.0", default-features = false, features = [ "map" ] } heapless = { version = "0.8", default-features = false } -embedded-nal-async = { version = "0.7.1" } +embedded-nal-async = "0.8.0" document-features = "0.2.7" diff --git a/embassy-net/src/dns.rs b/embassy-net/src/dns.rs index 1fbaea4f0..dbe73776c 100644 --- a/embassy-net/src/dns.rs +++ b/embassy-net/src/dns.rs @@ -73,8 +73,11 @@ impl<'a> embedded_nal_async::Dns for DnsSocket<'a> { &self, host: &str, addr_type: embedded_nal_async::AddrType, - ) -> Result { - use embedded_nal_async::{AddrType, IpAddr}; + ) -> Result { + use core::net::IpAddr; + + use embedded_nal_async::AddrType; + let (qtype, secondary_qtype) = match addr_type { AddrType::IPv4 => (DnsQueryType::A, None), AddrType::IPv6 => (DnsQueryType::Aaaa, None), @@ -98,20 +101,16 @@ impl<'a> embedded_nal_async::Dns for DnsSocket<'a> { if let Some(first) = addrs.get(0) { Ok(match first { #[cfg(feature = "proto-ipv4")] - IpAddress::Ipv4(addr) => IpAddr::V4(addr.0.into()), + IpAddress::Ipv4(addr) => IpAddr::V4(*addr), #[cfg(feature = "proto-ipv6")] - IpAddress::Ipv6(addr) => IpAddr::V6(addr.0.into()), + IpAddress::Ipv6(addr) => IpAddr::V6(*addr), }) } else { Err(Error::Failed) } } - async fn get_host_by_address( - &self, - _addr: embedded_nal_async::IpAddr, - _result: &mut [u8], - ) -> Result { + async fn get_host_by_address(&self, _addr: core::net::IpAddr, _result: &mut [u8]) -> Result { todo!() } } diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index bcddbc95b..1bd582b65 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -675,10 +675,9 @@ mod embedded_io_impls { pub mod client { use core::cell::{Cell, UnsafeCell}; use core::mem::MaybeUninit; + use core::net::IpAddr; use core::ptr::NonNull; - use embedded_nal_async::IpAddr; - use super::*; /// TCP client connection pool compatible with `embedded-nal-async` traits. @@ -715,17 +714,14 @@ pub mod client { type Error = Error; type Connection<'m> = TcpConnection<'m, N, TX_SZ, RX_SZ> where Self: 'm; - async fn connect<'a>( - &'a self, - remote: embedded_nal_async::SocketAddr, - ) -> Result, Self::Error> { + async fn connect<'a>(&'a self, remote: core::net::SocketAddr) -> Result, Self::Error> { let addr: crate::IpAddress = match remote.ip() { #[cfg(feature = "proto-ipv4")] - IpAddr::V4(addr) => crate::IpAddress::Ipv4(crate::Ipv4Address::from_bytes(&addr.octets())), + IpAddr::V4(addr) => crate::IpAddress::Ipv4(addr), #[cfg(not(feature = "proto-ipv4"))] IpAddr::V4(_) => panic!("ipv4 support not enabled"), #[cfg(feature = "proto-ipv6")] - IpAddr::V6(addr) => crate::IpAddress::Ipv6(crate::Ipv6Address::from_bytes(&addr.octets())), + IpAddr::V6(addr) => crate::IpAddress::Ipv6(addr), #[cfg(not(feature = "proto-ipv6"))] IpAddr::V6(_) => panic!("ipv6 support not enabled"), }; diff --git a/examples/nrf9160/src/bin/modem_tcp_client.rs b/examples/nrf9160/src/bin/modem_tcp_client.rs index 495ee26dd..067ec4276 100644 --- a/examples/nrf9160/src/bin/modem_tcp_client.rs +++ b/examples/nrf9160/src/bin/modem_tcp_client.rs @@ -9,7 +9,7 @@ use core::str::FromStr; use defmt::{info, unwrap, warn}; use embassy_executor::Spawner; -use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net::{Ipv4Cidr, Stack, StackResources}; use embassy_net_nrf91::context::Status; use embassy_net_nrf91::{context, Runner, State, TraceBuffer, TraceReader}; use embassy_nrf::buffered_uarte::{self, BufferedUarteTx}; @@ -70,18 +70,16 @@ fn status_to_config(status: &Status) -> embassy_net::ConfigV4 { let Some(IpAddr::V4(addr)) = status.ip else { panic!("Unexpected IP address"); }; - let addr = Ipv4Address(addr.octets()); - let gateway = if let Some(IpAddr::V4(addr)) = status.gateway { - Some(Ipv4Address(addr.octets())) - } else { - None + let gateway = match status.gateway { + Some(IpAddr::V4(addr)) => Some(addr), + _ => None, }; let mut dns_servers = Vec::new(); for dns in status.dns.iter() { if let IpAddr::V4(ip) = dns { - unwrap!(dns_servers.push(Ipv4Address(ip.octets()))); + unwrap!(dns_servers.push(*ip)); } } diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index 04b4c6317..674d331ab 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -12,7 +12,7 @@ embassy-executor = { version = "0.6.0", path = "../../embassy-executor", feature embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.2.0", path = "../../embassy-rp", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp2040"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "raw", "dhcpv4", "medium-ethernet", "dns"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "raw", "dhcpv4", "medium-ethernet", "dns", "proto-ipv4", "proto-ipv6", "multicast"] } embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-usb-logger = { version = "0.2.0", path = "../../embassy-usb-logger" } @@ -25,7 +25,7 @@ fixed = "1.23.1" fixed-macro = "1.2" # for web request example -reqwless = { version = "0.12.0", features = ["defmt",]} +reqwless = { git="https://github.com/drogue-iot/reqwless", rev="673e8d2cfbaad79254ec51fa50cc8b697531fbff", features = ["defmt",]} serde = { version = "1.0.203", default-features = false, features = ["derive"] } serde-json-core = "0.5.1" diff --git a/examples/rp23/Cargo.toml b/examples/rp23/Cargo.toml index 087f6fd69..08646463c 100644 --- a/examples/rp23/Cargo.toml +++ b/examples/rp23/Cargo.toml @@ -24,8 +24,6 @@ defmt-rtt = "0.4" fixed = "1.23.1" fixed-macro = "1.2" -# for web request example -reqwless = { version = "0.12.0", features = ["defmt",]} serde = { version = "1.0.203", default-features = false, features = ["derive"] } serde-json-core = "0.5.1" diff --git a/examples/std/src/bin/net_ppp.rs b/examples/std/src/bin/net_ppp.rs index 7d0f1327f..ea3fbebef 100644 --- a/examples/std/src/bin/net_ppp.rs +++ b/examples/std/src/bin/net_ppp.rs @@ -16,7 +16,7 @@ use async_io::Async; use clap::Parser; use embassy_executor::{Executor, Spawner}; use embassy_net::tcp::TcpSocket; -use embassy_net::{Config, ConfigV4, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net::{Config, ConfigV4, Ipv4Cidr, Stack, StackResources}; use embassy_net_ppp::Runner; use embedded_io_async::Write; use futures::io::BufReader; @@ -60,10 +60,10 @@ async fn ppp_task(stack: Stack<'static>, mut runner: Runner<'static>, port: Seri }; let mut dns_servers = Vec::new(); for s in ipv4.dns_servers.iter().flatten() { - let _ = dns_servers.push(Ipv4Address::from_bytes(&s.0)); + let _ = dns_servers.push(*s); } let config = ConfigV4::Static(embassy_net::StaticConfigV4 { - address: Ipv4Cidr::new(Ipv4Address::from_bytes(&addr.0), 0), + address: Ipv4Cidr::new(addr, 0), gateway: None, dns_servers, }); diff --git a/examples/stm32h5/Cargo.toml b/examples/stm32h5/Cargo.toml index 30b1d2be9..1aa264ab2 100644 --- a/examples/stm32h5/Cargo.toml +++ b/examples/stm32h5/Cargo.toml @@ -23,7 +23,7 @@ embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } embedded-io-async = { version = "0.6.1" } -embedded-nal-async = { version = "0.7.1" } +embedded-nal-async = "0.8.0" panic-probe = { version = "0.3", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } rand_core = "0.6.3" diff --git a/examples/stm32h7/Cargo.toml b/examples/stm32h7/Cargo.toml index 13fce7dc7..d0f22cf82 100644 --- a/examples/stm32h7/Cargo.toml +++ b/examples/stm32h7/Cargo.toml @@ -23,7 +23,7 @@ cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } -embedded-nal-async = { version = "0.7.1" } +embedded-nal-async = "0.8.0" embedded-io-async = { version = "0.6.1" } panic-probe = { version = "0.3", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } diff --git a/examples/stm32h7/src/bin/eth_client.rs b/examples/stm32h7/src/bin/eth_client.rs index 24983ca85..a1558b079 100644 --- a/examples/stm32h7/src/bin/eth_client.rs +++ b/examples/stm32h7/src/bin/eth_client.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] +use core::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; + use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::client::{TcpClient, TcpClientState}; @@ -12,7 +14,7 @@ use embassy_stm32::rng::Rng; use embassy_stm32::{bind_interrupts, eth, peripherals, rng, Config}; use embassy_time::Timer; use embedded_io_async::Write; -use embedded_nal_async::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpConnect}; +use embedded_nal_async::TcpConnect; use rand_core::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; diff --git a/examples/stm32h7/src/bin/eth_client_mii.rs b/examples/stm32h7/src/bin/eth_client_mii.rs index 768d85993..a352ef444 100644 --- a/examples/stm32h7/src/bin/eth_client_mii.rs +++ b/examples/stm32h7/src/bin/eth_client_mii.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] +use core::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; + use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::client::{TcpClient, TcpClientState}; @@ -12,7 +14,7 @@ use embassy_stm32::rng::Rng; use embassy_stm32::{bind_interrupts, eth, peripherals, rng, Config}; use embassy_time::Timer; use embedded_io_async::Write; -use embedded_nal_async::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpConnect}; +use embedded_nal_async::TcpConnect; use rand_core::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; diff --git a/examples/stm32h755cm4/Cargo.toml b/examples/stm32h755cm4/Cargo.toml index 7a42fbdaa..75de40b9a 100644 --- a/examples/stm32h755cm4/Cargo.toml +++ b/examples/stm32h755cm4/Cargo.toml @@ -23,7 +23,7 @@ cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } -embedded-nal-async = { version = "0.7.1" } +embedded-nal-async = "0.8.0" embedded-io-async = { version = "0.6.1" } panic-probe = { version = "0.3", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } diff --git a/examples/stm32h755cm7/Cargo.toml b/examples/stm32h755cm7/Cargo.toml index 4f0f69c3f..911a4e79b 100644 --- a/examples/stm32h755cm7/Cargo.toml +++ b/examples/stm32h755cm7/Cargo.toml @@ -23,7 +23,7 @@ cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } -embedded-nal-async = { version = "0.7.1" } +embedded-nal-async = "0.8.0" embedded-io-async = { version = "0.6.1" } panic-probe = { version = "0.3", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } diff --git a/examples/stm32h7rs/Cargo.toml b/examples/stm32h7rs/Cargo.toml index f97dfd722..05f638408 100644 --- a/examples/stm32h7rs/Cargo.toml +++ b/examples/stm32h7rs/Cargo.toml @@ -22,7 +22,7 @@ cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } -embedded-nal-async = { version = "0.7.1" } +embedded-nal-async = "0.8.0" embedded-io-async = { version = "0.6.1" } panic-probe = { version = "0.3", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } diff --git a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs index be4270ada..4a7c01f9f 100644 --- a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs +++ b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs @@ -51,7 +51,7 @@ bind_interrupts!(struct Irqs { // MAC-address used by the adin1110 const MAC: [u8; 6] = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]; // Static IP settings -const IP_ADDRESS: Ipv4Cidr = Ipv4Cidr::new(Ipv4Address([192, 168, 1, 5]), 24); +const IP_ADDRESS: Ipv4Cidr = Ipv4Cidr::new(Ipv4Address::new(192, 168, 1, 5), 24); // Listen port for the webserver const HTTP_LISTEN_PORT: u16 = 80; From 9e6e09a8d747ec90aae215df8471dfe349993487 Mon Sep 17 00:00:00 2001 From: Dummyc0m Date: Sun, 6 Oct 2024 23:23:33 -0700 Subject: [PATCH 043/144] executor/spin: introduce an architecture agnostic executor Spin polls the raw executor and never sleeps. It is useful for disabling any power features associated with wfi/wfe-like instructions. When implementing support for the CH32V30x MCU, the wfi instruction had issues interacting with the USB OTG peripheral and appeared to be non-spec-compliant. 1. When sending a USB Data-in packet, the USB peripheral appears to be unable to read the system main memory while in WFI. This manifests in the USB peripheral sending all or partially zeroed DATA packets. Disabling WFI works around this issue. 2. The WFI instruction does not wake up the processor when MIE is disabled. The MCU provides a WFITOWFE bit to emulate the WFE instruction on arm, which, when enabled, ignores the MIE and allows the processor to wake up. This works around the non-compliant WFI implementation. Co-authored-by: Codetector Co-authored-by: Dummyc0m --- embassy-executor-macros/src/lib.rs | 29 +++++++++++ embassy-executor-macros/src/macros/main.rs | 29 ++++++++++- embassy-executor/Cargo.toml | 2 + embassy-executor/src/arch/spin.rs | 58 ++++++++++++++++++++++ embassy-executor/src/lib.rs | 10 +++- 5 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 embassy-executor/src/arch/spin.rs diff --git a/embassy-executor-macros/src/lib.rs b/embassy-executor-macros/src/lib.rs index 5461fe04c..61d388b9e 100644 --- a/embassy-executor-macros/src/lib.rs +++ b/embassy-executor-macros/src/lib.rs @@ -94,6 +94,35 @@ pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream { main::run(&args.meta, f, main::cortex_m()).unwrap_or_else(|x| x).into() } +/// Creates a new `executor` instance and declares an architecture agnostic application entry point spawning +/// the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// A user-defined entry macro must provided via the `entry` argument +/// +/// ## Examples +/// Spawning a task: +/// ``` rust +/// #[embassy_executor::main(entry = "qingke_rt::entry")] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_spin(args: TokenStream, item: TokenStream) -> TokenStream { + let args = syn::parse_macro_input!(args as Args); + let f = syn::parse_macro_input!(item as syn::ItemFn); + main::run(&args.meta, f, main::spin(&args.meta)) + .unwrap_or_else(|x| x) + .into() +} + /// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task. /// /// The following restrictions apply: diff --git a/embassy-executor-macros/src/macros/main.rs b/embassy-executor-macros/src/macros/main.rs index 26dfa2397..66a3965d0 100644 --- a/embassy-executor-macros/src/macros/main.rs +++ b/embassy-executor-macros/src/macros/main.rs @@ -1,5 +1,5 @@ use darling::export::NestedMeta; -use darling::FromMeta; +use darling::{Error, FromMeta}; use proc_macro2::TokenStream; use quote::quote; use syn::{Expr, ReturnType, Type}; @@ -50,6 +50,33 @@ pub fn riscv(args: &[NestedMeta]) -> TokenStream { } } +pub fn spin(args: &[NestedMeta]) -> TokenStream { + let maybe_entry = match Args::from_list(args) { + Ok(args) => args.entry, + Err(e) => return e.write_errors(), + }; + + let entry = match maybe_entry { + Some(str) => str, + None => return Error::missing_field("entry").write_errors(), + }; + let entry = match Expr::from_string(&entry) { + Ok(expr) => expr, + Err(e) => return e.write_errors(), + }; + + quote! { + #[#entry] + fn main() -> ! { + let mut executor = ::embassy_executor::Executor::new(); + let executor = unsafe { __make_static(&mut executor) }; + executor.run(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }) + } + } +} + pub fn cortex_m() -> TokenStream { quote! { #[cortex_m_rt::entry] diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index 01fa28b88..e2fedce3c 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml @@ -82,6 +82,8 @@ arch-riscv32 = ["_arch"] arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys", "critical-section/std"] ## AVR arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"] +## spin (architecture agnostic; never sleeps) +arch-spin = ["_arch"] #! ### Executor diff --git a/embassy-executor/src/arch/spin.rs b/embassy-executor/src/arch/spin.rs new file mode 100644 index 000000000..340023620 --- /dev/null +++ b/embassy-executor/src/arch/spin.rs @@ -0,0 +1,58 @@ +#[cfg(feature = "executor-interrupt")] +compile_error!("`executor-interrupt` is not supported with `arch-spin`."); + +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + use core::marker::PhantomData; + + pub use embassy_executor_macros::main_spin as main; + + use crate::{raw, Spawner}; + + #[export_name = "__pender"] + fn __pender(_context: *mut ()) {} + + /// Spin Executor + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + } + + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + inner: raw::Executor::new(core::ptr::null_mut()), + not_send: PhantomData, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { self.inner.poll() }; + } + } + } +} diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs index 6a2e493a2..d816539ac 100644 --- a/embassy-executor/src/lib.rs +++ b/embassy-executor/src/lib.rs @@ -23,7 +23,14 @@ macro_rules! check_at_most_one { check_at_most_one!(@amo [$($f)*] [$($f)*] []); }; } -check_at_most_one!("arch-avr", "arch-cortex-m", "arch-riscv32", "arch-std", "arch-wasm",); +check_at_most_one!( + "arch-avr", + "arch-cortex-m", + "arch-riscv32", + "arch-std", + "arch-wasm", + "arch-spin", +); #[cfg(feature = "_arch")] #[cfg_attr(feature = "arch-avr", path = "arch/avr.rs")] @@ -31,6 +38,7 @@ check_at_most_one!("arch-avr", "arch-cortex-m", "arch-riscv32", "arch-std", "arc #[cfg_attr(feature = "arch-riscv32", path = "arch/riscv32.rs")] #[cfg_attr(feature = "arch-std", path = "arch/std.rs")] #[cfg_attr(feature = "arch-wasm", path = "arch/wasm.rs")] +#[cfg_attr(feature = "arch-spin", path = "arch/spin.rs")] mod arch; #[cfg(feature = "_arch")] From e7e245eeb77974bf1f374f24cbf5c0bc41f745f1 Mon Sep 17 00:00:00 2001 From: George Cosma Date: Wed, 5 Jun 2024 13:54:00 +0300 Subject: [PATCH 044/144] feat: embassy-lpc55 hal with gpio and pint driver --- ci.sh | 2 + embassy-nxp/Cargo.toml | 17 + embassy-nxp/src/gpio.rs | 361 +++++++++++++++ embassy-nxp/src/lib.rs | 95 ++++ embassy-nxp/src/pac_utils.rs | 323 ++++++++++++++ embassy-nxp/src/pint.rs | 440 +++++++++++++++++++ examples/lpc55s69/.cargo/config.toml | 8 + examples/lpc55s69/Cargo.toml | 22 + examples/lpc55s69/build.rs | 35 ++ examples/lpc55s69/memory.x | 28 ++ examples/lpc55s69/src/bin/blinky_nop.rs | 33 ++ examples/lpc55s69/src/bin/button_executor.rs | 25 ++ 12 files changed, 1389 insertions(+) create mode 100644 embassy-nxp/Cargo.toml create mode 100644 embassy-nxp/src/gpio.rs create mode 100644 embassy-nxp/src/lib.rs create mode 100644 embassy-nxp/src/pac_utils.rs create mode 100644 embassy-nxp/src/pint.rs create mode 100644 examples/lpc55s69/.cargo/config.toml create mode 100644 examples/lpc55s69/Cargo.toml create mode 100644 examples/lpc55s69/build.rs create mode 100644 examples/lpc55s69/memory.x create mode 100644 examples/lpc55s69/src/bin/blinky_nop.rs create mode 100644 examples/lpc55s69/src/bin/button_executor.rs diff --git a/ci.sh b/ci.sh index 8fef731a4..503f8c8dc 100755 --- a/ci.sh +++ b/ci.sh @@ -171,6 +171,7 @@ cargo batch \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u031r8,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u073mb,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u083rc,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-nxp/Cargo.toml --target thumbv8m.main-none-eabihf \ --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features ''\ --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log' \ --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt' \ @@ -227,6 +228,7 @@ cargo batch \ --- build --release --manifest-path examples/stm32wb/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32wb \ --- build --release --manifest-path examples/stm32wba/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32wba \ --- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32wl \ + --- build --release --manifest-path examples/lpc55s69/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/lpc55s69 \ --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,skip-include --out-dir out/examples/boot/nrf52840 \ --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,skip-include --out-dir out/examples/boot/nrf9160 \ --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns,skip-include --out-dir out/examples/boot/nrf9120 \ diff --git a/embassy-nxp/Cargo.toml b/embassy-nxp/Cargo.toml new file mode 100644 index 000000000..df329be66 --- /dev/null +++ b/embassy-nxp/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "embassy-nxp" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = "0.7.7" +cortex-m-rt = "0.7.0" +critical-section = "1.1.2" +embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } +embassy-sync = { version = "0.6.0", path = "../embassy-sync" } +lpc55-pac = "0.5.0" +defmt = "0.3.8" + +[features] +default = ["rt"] +rt = ["lpc55-pac/rt"] \ No newline at end of file diff --git a/embassy-nxp/src/gpio.rs b/embassy-nxp/src/gpio.rs new file mode 100644 index 000000000..d5d04ee69 --- /dev/null +++ b/embassy-nxp/src/gpio.rs @@ -0,0 +1,361 @@ +use embassy_hal_internal::impl_peripheral; + +use crate::pac_utils::*; +use crate::{peripherals, Peripheral, PeripheralRef}; + +pub(crate) fn init() { + // Enable clocks for GPIO, PINT, and IOCON + syscon_reg() + .ahbclkctrl0 + .modify(|_, w| w.gpio0().enable().gpio1().enable().mux().enable().iocon().enable()); +} + +/// The GPIO pin level for pins set on "Digital" mode. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Level { + /// Logical low. Corresponds to 0V. + Low, + /// Logical high. Corresponds to VDD. + High, +} + +/// Pull setting for a GPIO input set on "Digital" mode. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum Pull { + /// No pull. + None, + /// Internal pull-up resistor. + Up, + /// Internal pull-down resistor. + Down, +} + +/// The LPC55 boards have two GPIO banks, each with 32 pins. This enum represents the two banks. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Bank { + Bank0 = 0, + Bank1 = 1, +} + +/// GPIO output driver. Internally, this is a specialized [Flex] pin. +pub struct Output<'d> { + pub(crate) pin: Flex<'d>, +} + +impl<'d> Output<'d> { + /// Create GPIO output driver for a [Pin] with the provided [initial output](Level). + #[inline] + pub fn new(pin: impl Peripheral

+ 'd, initial_output: Level) -> Self { + let mut pin = Flex::new(pin); + pin.set_as_output(); + let mut result = Self { pin }; + + match initial_output { + Level::High => result.set_high(), + Level::Low => result.set_low(), + }; + + result + } + + pub fn set_high(&mut self) { + gpio_reg().set[self.pin.pin_bank() as usize].write(|w| unsafe { w.bits(self.pin.bit()) }) + } + + pub fn set_low(&mut self) { + gpio_reg().clr[self.pin.pin_bank() as usize].write(|w| unsafe { w.bits(self.pin.bit()) }) + } + + pub fn toggle(&mut self) { + gpio_reg().not[self.pin.pin_bank() as usize].write(|w| unsafe { w.bits(self.pin.bit()) }) + } + + /// Get the current output level of the pin. Note that the value returned by this function is + /// the voltage level reported by the pin, not the value set by the output driver. + pub fn level(&self) -> Level { + let bits = gpio_reg().pin[self.pin.pin_bank() as usize].read().bits(); + if bits & self.pin.bit() != 0 { + Level::High + } else { + Level::Low + } + } +} + +/// GPIO input driver. Internally, this is a specialized [Flex] pin. +pub struct Input<'d> { + pub(crate) pin: Flex<'d>, +} + +impl<'d> Input<'d> { + /// Create GPIO output driver for a [Pin] with the provided [Pull]. + #[inline] + pub fn new(pin: impl Peripheral

+ 'd, pull: Pull) -> Self { + let mut pin = Flex::new(pin); + pin.set_as_input(); + let mut result = Self { pin }; + result.set_pull(pull); + + result + } + + /// Set the pull configuration for the pin. To disable the pull, use [Pull::None]. + pub fn set_pull(&mut self, pull: Pull) { + match_iocon!(register, iocon_reg(), self.pin.pin_bank(), self.pin.pin_number(), { + register.modify(|_, w| match pull { + Pull::None => w.mode().inactive(), + Pull::Up => w.mode().pull_up(), + Pull::Down => w.mode().pull_down(), + }); + }); + } + + /// Get the current input level of the pin. + pub fn read(&self) -> Level { + let bits = gpio_reg().pin[self.pin.pin_bank() as usize].read().bits(); + if bits & self.pin.bit() != 0 { + Level::High + } else { + Level::Low + } + } +} + +/// A flexible GPIO (digital mode) pin whose mode is not yet determined. Under the hood, this is a +/// reference to a type-erased pin called ["AnyPin"](AnyPin). +pub struct Flex<'d> { + pub(crate) pin: PeripheralRef<'d, AnyPin>, +} + +impl<'d> Flex<'d> { + /// Wrap the pin in a `Flex`. + /// + /// Note: you cannot assume that the pin will be in Digital mode after this call. + #[inline] + pub fn new(pin: impl Peripheral

+ 'd) -> Self { + Self { + pin: pin.into_ref().map_into(), + } + } + + /// Get the bank of this pin. See also [Bank]. + /// + /// # Example + /// + /// ``` + /// use embassy_nxp::gpio::{Bank, Flex}; + /// + /// let p = embassy_nxp::init(Default::default()); + /// let pin = Flex::new(p.PIO1_15); + /// + /// assert_eq!(pin.pin_bank(), Bank::Bank1); + /// ``` + pub fn pin_bank(&self) -> Bank { + self.pin.pin_bank() + } + + /// Get the number of this pin within its bank. See also [Bank]. + /// + /// # Example + /// + /// ``` + /// use embassy_nxp::gpio::Flex; + /// + /// let p = embassy_nxp::init(Default::default()); + /// let pin = Flex::new(p.PIO1_15); + /// + /// assert_eq!(pin.pin_number(), 15 as u8); + /// ``` + pub fn pin_number(&self) -> u8 { + self.pin.pin_number() + } + + /// Get the bit mask for this pin. Useful for setting or clearing bits in a register. Note: + /// PIOx_0 is bit 0, PIOx_1 is bit 1, etc. + /// + /// # Example + /// + /// ``` + /// use embassy_nxp::gpio::Flex; + /// + /// let p = embassy_nxp::init(Default::default()); + /// let pin = Flex::new(p.PIO1_3); + /// + /// assert_eq!(pin.bit(), 0b0000_1000); + /// ``` + pub fn bit(&self) -> u32 { + 1 << self.pin.pin_number() + } + + /// Set the pin to digital mode. This is required for using a pin as a GPIO pin. The default + /// setting for pins is (usually) non-digital. + fn set_as_digital(&mut self) { + match_iocon!(register, iocon_reg(), self.pin_bank(), self.pin_number(), { + register.modify(|_, w| w.digimode().digital()); + }); + } + + /// Set the pin in output mode. This implies setting the pin to digital mode, which this + /// function handles itself. + pub fn set_as_output(&mut self) { + self.set_as_digital(); + gpio_reg().dirset[self.pin.pin_bank() as usize].write(|w| unsafe { w.dirsetp().bits(self.bit()) }) + } + + pub fn set_as_input(&mut self) { + self.set_as_digital(); + gpio_reg().dirclr[self.pin.pin_bank() as usize].write(|w| unsafe { w.dirclrp().bits(self.bit()) }) + } +} + +/// Sealed trait for pins. This trait is sealed and cannot be implemented outside of this crate. +pub(crate) trait SealedPin: Sized { + fn pin_bank(&self) -> Bank; + fn pin_number(&self) -> u8; +} + +/// Interface for a Pin that can be configured by an [Input] or [Output] driver, or converted to an +/// [AnyPin]. By default, this trait is sealed and cannot be implemented outside of the +/// `embassy-nxp` crate due to the [SealedPin] trait. +#[allow(private_bounds)] +pub trait Pin: Peripheral

+ Into + SealedPin + Sized + 'static { + /// Degrade to a generic pin struct + fn degrade(self) -> AnyPin { + AnyPin { + pin_bank: self.pin_bank(), + pin_number: self.pin_number(), + } + } + + /// Returns the pin number within a bank + #[inline] + fn pin(&self) -> u8 { + self.pin_number() + } + + /// Returns the bank of this pin + #[inline] + fn bank(&self) -> Bank { + self.pin_bank() + } +} + +/// Type-erased GPIO pin. +pub struct AnyPin { + pin_bank: Bank, + pin_number: u8, +} + +impl AnyPin { + /// Unsafely create a new type-erased pin. + /// + /// # Safety + /// + /// You must ensure that youā€™re only using one instance of this type at a time. + pub unsafe fn steal(pin_bank: Bank, pin_number: u8) -> Self { + Self { pin_bank, pin_number } + } +} + +impl_peripheral!(AnyPin); + +impl Pin for AnyPin {} +impl SealedPin for AnyPin { + #[inline] + fn pin_bank(&self) -> Bank { + self.pin_bank + } + + #[inline] + fn pin_number(&self) -> u8 { + self.pin_number + } +} + +macro_rules! impl_pin { + ($name:ident, $bank:expr, $pin_num:expr) => { + impl Pin for peripherals::$name {} + impl SealedPin for peripherals::$name { + #[inline] + fn pin_bank(&self) -> Bank { + $bank + } + + #[inline] + fn pin_number(&self) -> u8 { + $pin_num + } + } + + impl From for crate::gpio::AnyPin { + fn from(val: peripherals::$name) -> Self { + crate::gpio::Pin::degrade(val) + } + } + }; +} + +impl_pin!(PIO0_0, Bank::Bank0, 0); +impl_pin!(PIO0_1, Bank::Bank0, 1); +impl_pin!(PIO0_2, Bank::Bank0, 2); +impl_pin!(PIO0_3, Bank::Bank0, 3); +impl_pin!(PIO0_4, Bank::Bank0, 4); +impl_pin!(PIO0_5, Bank::Bank0, 5); +impl_pin!(PIO0_6, Bank::Bank0, 6); +impl_pin!(PIO0_7, Bank::Bank0, 7); +impl_pin!(PIO0_8, Bank::Bank0, 8); +impl_pin!(PIO0_9, Bank::Bank0, 9); +impl_pin!(PIO0_10, Bank::Bank0, 10); +impl_pin!(PIO0_11, Bank::Bank0, 11); +impl_pin!(PIO0_12, Bank::Bank0, 12); +impl_pin!(PIO0_13, Bank::Bank0, 13); +impl_pin!(PIO0_14, Bank::Bank0, 14); +impl_pin!(PIO0_15, Bank::Bank0, 15); +impl_pin!(PIO0_16, Bank::Bank0, 16); +impl_pin!(PIO0_17, Bank::Bank0, 17); +impl_pin!(PIO0_18, Bank::Bank0, 18); +impl_pin!(PIO0_19, Bank::Bank0, 19); +impl_pin!(PIO0_20, Bank::Bank0, 20); +impl_pin!(PIO0_21, Bank::Bank0, 21); +impl_pin!(PIO0_22, Bank::Bank0, 22); +impl_pin!(PIO0_23, Bank::Bank0, 23); +impl_pin!(PIO0_24, Bank::Bank0, 24); +impl_pin!(PIO0_25, Bank::Bank0, 25); +impl_pin!(PIO0_26, Bank::Bank0, 26); +impl_pin!(PIO0_27, Bank::Bank0, 27); +impl_pin!(PIO0_28, Bank::Bank0, 28); +impl_pin!(PIO0_29, Bank::Bank0, 29); +impl_pin!(PIO0_30, Bank::Bank0, 30); +impl_pin!(PIO0_31, Bank::Bank0, 31); +impl_pin!(PIO1_0, Bank::Bank1, 0); +impl_pin!(PIO1_1, Bank::Bank1, 1); +impl_pin!(PIO1_2, Bank::Bank1, 2); +impl_pin!(PIO1_3, Bank::Bank1, 3); +impl_pin!(PIO1_4, Bank::Bank1, 4); +impl_pin!(PIO1_5, Bank::Bank1, 5); +impl_pin!(PIO1_6, Bank::Bank1, 6); +impl_pin!(PIO1_7, Bank::Bank1, 7); +impl_pin!(PIO1_8, Bank::Bank1, 8); +impl_pin!(PIO1_9, Bank::Bank1, 9); +impl_pin!(PIO1_10, Bank::Bank1, 10); +impl_pin!(PIO1_11, Bank::Bank1, 11); +impl_pin!(PIO1_12, Bank::Bank1, 12); +impl_pin!(PIO1_13, Bank::Bank1, 13); +impl_pin!(PIO1_14, Bank::Bank1, 14); +impl_pin!(PIO1_15, Bank::Bank1, 15); +impl_pin!(PIO1_16, Bank::Bank1, 16); +impl_pin!(PIO1_17, Bank::Bank1, 17); +impl_pin!(PIO1_18, Bank::Bank1, 18); +impl_pin!(PIO1_19, Bank::Bank1, 19); +impl_pin!(PIO1_20, Bank::Bank1, 20); +impl_pin!(PIO1_21, Bank::Bank1, 21); +impl_pin!(PIO1_22, Bank::Bank1, 22); +impl_pin!(PIO1_23, Bank::Bank1, 23); +impl_pin!(PIO1_24, Bank::Bank1, 24); +impl_pin!(PIO1_25, Bank::Bank1, 25); +impl_pin!(PIO1_26, Bank::Bank1, 26); +impl_pin!(PIO1_27, Bank::Bank1, 27); +impl_pin!(PIO1_28, Bank::Bank1, 28); +impl_pin!(PIO1_29, Bank::Bank1, 29); +impl_pin!(PIO1_30, Bank::Bank1, 30); +impl_pin!(PIO1_31, Bank::Bank1, 31); diff --git a/embassy-nxp/src/lib.rs b/embassy-nxp/src/lib.rs new file mode 100644 index 000000000..80fdecb2e --- /dev/null +++ b/embassy-nxp/src/lib.rs @@ -0,0 +1,95 @@ +#![no_std] + +pub mod gpio; +mod pac_utils; +pub mod pint; + +pub use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +pub use lpc55_pac as pac; + +/// Initialize the `embassy-nxp` HAL with the provided configuration. +/// +/// This returns the peripheral singletons that can be used for creating drivers. +/// +/// This should only be called once and at startup, otherwise it panics. +pub fn init(_config: config::Config) -> Peripherals { + gpio::init(); + pint::init(); + + crate::Peripherals::take() +} + +embassy_hal_internal::peripherals! { + // External pins. These are not only GPIOs, they are multi-purpose pins and can be used by other + // peripheral types (e.g. I2C). + PIO0_0, + PIO0_1, + PIO0_2, + PIO0_3, + PIO0_4, + PIO0_5, + PIO0_6, + PIO0_7, + PIO0_8, + PIO0_9, + PIO0_10, + PIO0_11, + PIO0_12, + PIO0_13, + PIO0_14, + PIO0_15, + PIO0_16, + PIO0_17, + PIO0_18, + PIO0_19, + PIO0_20, + PIO0_21, + PIO0_22, + PIO0_23, + PIO0_24, + PIO0_25, + PIO0_26, + PIO0_27, + PIO0_28, + PIO0_29, + PIO0_30, + PIO0_31, + PIO1_0, + PIO1_1, + PIO1_2, + PIO1_3, + PIO1_4, + PIO1_5, + PIO1_6, + PIO1_7, + PIO1_8, + PIO1_9, + PIO1_10, + PIO1_11, + PIO1_12, + PIO1_13, + PIO1_14, + PIO1_15, + PIO1_16, + PIO1_17, + PIO1_18, + PIO1_19, + PIO1_20, + PIO1_21, + PIO1_22, + PIO1_23, + PIO1_24, + PIO1_25, + PIO1_26, + PIO1_27, + PIO1_28, + PIO1_29, + PIO1_30, + PIO1_31, +} + +/// HAL configuration for the NXP board. +pub mod config { + #[derive(Default)] + pub struct Config {} +} diff --git a/embassy-nxp/src/pac_utils.rs b/embassy-nxp/src/pac_utils.rs new file mode 100644 index 000000000..86a807f6c --- /dev/null +++ b/embassy-nxp/src/pac_utils.rs @@ -0,0 +1,323 @@ +/// Get the GPIO register block. This is used to configure all GPIO pins. +/// +/// # Safety +/// Due to the type system of peripherals, access to the settings of a single pin is possible only +/// by a single thread at a time. Read/Write operations on a single registers are NOT atomic. You +/// must ensure that the GPIO registers are not accessed concurrently by multiple threads. +pub(crate) fn gpio_reg() -> &'static lpc55_pac::gpio::RegisterBlock { + unsafe { &*lpc55_pac::GPIO::ptr() } +} + +/// Get the IOCON register block. +/// +/// # Safety +/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO +/// registers are not accessed concurrently by multiple threads. +pub(crate) fn iocon_reg() -> &'static lpc55_pac::iocon::RegisterBlock { + unsafe { &*lpc55_pac::IOCON::ptr() } +} + +/// Get the INPUTMUX register block. +/// +/// # Safety +/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO +/// registers are not accessed concurrently by multiple threads. +pub(crate) fn inputmux_reg() -> &'static lpc55_pac::inputmux::RegisterBlock { + unsafe { &*lpc55_pac::INPUTMUX::ptr() } +} + +/// Get the SYSCON register block. +/// +/// # Safety +/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO +/// registers are not accessed concurrently by multiple threads. +pub(crate) fn syscon_reg() -> &'static lpc55_pac::syscon::RegisterBlock { + unsafe { &*lpc55_pac::SYSCON::ptr() } +} + +/// Get the PINT register block. +/// +/// # Safety +/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO +/// registers are not accessed concurrently by multiple threads. +pub(crate) fn pint_reg() -> &'static lpc55_pac::pint::RegisterBlock { + unsafe { &*lpc55_pac::PINT::ptr() } +} + +/// Match the pin bank and number of a pin to the corresponding IOCON register. +/// +/// # Example +/// ``` +/// use embassy_nxp::gpio::Bank; +/// use embassy_nxp::pac_utils::{iocon_reg, match_iocon}; +/// +/// // Make pin PIO1_6 digital and set it to pull-down mode. +/// match_iocon!(register, iocon_reg(), Bank::Bank1, 6, { +/// register.modify(|_, w| w.mode().pull_down().digimode().digital()); +/// }); +/// ``` +macro_rules! match_iocon { + ($register:ident, $iocon_register:expr, $pin_bank:expr, $pin_number:expr, $action:expr) => { + match ($pin_bank, $pin_number) { + (Bank::Bank0, 0) => { + let $register = &($iocon_register).pio0_0; + $action; + } + (Bank::Bank0, 1) => { + let $register = &($iocon_register).pio0_1; + $action; + } + (Bank::Bank0, 2) => { + let $register = &($iocon_register).pio0_2; + $action; + } + (Bank::Bank0, 3) => { + let $register = &($iocon_register).pio0_3; + $action; + } + (Bank::Bank0, 4) => { + let $register = &($iocon_register).pio0_4; + $action; + } + (Bank::Bank0, 5) => { + let $register = &($iocon_register).pio0_5; + $action; + } + (Bank::Bank0, 6) => { + let $register = &($iocon_register).pio0_6; + $action; + } + (Bank::Bank0, 7) => { + let $register = &($iocon_register).pio0_7; + $action; + } + (Bank::Bank0, 8) => { + let $register = &($iocon_register).pio0_8; + $action; + } + (Bank::Bank0, 9) => { + let $register = &($iocon_register).pio0_9; + $action; + } + (Bank::Bank0, 10) => { + let $register = &($iocon_register).pio0_10; + $action; + } + (Bank::Bank0, 11) => { + let $register = &($iocon_register).pio0_11; + $action; + } + (Bank::Bank0, 12) => { + let $register = &($iocon_register).pio0_12; + $action; + } + (Bank::Bank0, 13) => { + let $register = &($iocon_register).pio0_13; + $action; + } + (Bank::Bank0, 14) => { + let $register = &($iocon_register).pio0_14; + $action; + } + (Bank::Bank0, 15) => { + let $register = &($iocon_register).pio0_15; + $action; + } + (Bank::Bank0, 16) => { + let $register = &($iocon_register).pio0_16; + $action; + } + (Bank::Bank0, 17) => { + let $register = &($iocon_register).pio0_17; + $action; + } + (Bank::Bank0, 18) => { + let $register = &($iocon_register).pio0_18; + $action; + } + (Bank::Bank0, 19) => { + let $register = &($iocon_register).pio0_19; + $action; + } + (Bank::Bank0, 20) => { + let $register = &($iocon_register).pio0_20; + $action; + } + (Bank::Bank0, 21) => { + let $register = &($iocon_register).pio0_21; + $action; + } + (Bank::Bank0, 22) => { + let $register = &($iocon_register).pio0_22; + $action; + } + (Bank::Bank0, 23) => { + let $register = &($iocon_register).pio0_23; + $action; + } + (Bank::Bank0, 24) => { + let $register = &($iocon_register).pio0_24; + $action; + } + (Bank::Bank0, 25) => { + let $register = &($iocon_register).pio0_25; + $action; + } + (Bank::Bank0, 26) => { + let $register = &($iocon_register).pio0_26; + $action; + } + (Bank::Bank0, 27) => { + let $register = &($iocon_register).pio0_27; + $action; + } + (Bank::Bank0, 28) => { + let $register = &($iocon_register).pio0_28; + $action; + } + (Bank::Bank0, 29) => { + let $register = &($iocon_register).pio0_29; + $action; + } + (Bank::Bank0, 30) => { + let $register = &($iocon_register).pio0_30; + $action; + } + (Bank::Bank0, 31) => { + let $register = &($iocon_register).pio0_31; + $action; + } + (Bank::Bank1, 0) => { + let $register = &($iocon_register).pio1_0; + $action; + } + (Bank::Bank1, 1) => { + let $register = &($iocon_register).pio1_1; + $action; + } + (Bank::Bank1, 2) => { + let $register = &($iocon_register).pio1_2; + $action; + } + (Bank::Bank1, 3) => { + let $register = &($iocon_register).pio1_3; + $action; + } + (Bank::Bank1, 4) => { + let $register = &($iocon_register).pio1_4; + $action; + } + (Bank::Bank1, 5) => { + let $register = &($iocon_register).pio1_5; + $action; + } + (Bank::Bank1, 6) => { + let $register = &($iocon_register).pio1_6; + $action; + } + (Bank::Bank1, 7) => { + let $register = &($iocon_register).pio1_7; + $action; + } + (Bank::Bank1, 8) => { + let $register = &($iocon_register).pio1_8; + $action; + } + (Bank::Bank1, 9) => { + let $register = &($iocon_register).pio1_9; + $action; + } + (Bank::Bank1, 10) => { + let $register = &($iocon_register).pio1_10; + $action; + } + (Bank::Bank1, 11) => { + let $register = &($iocon_register).pio1_11; + $action; + } + (Bank::Bank1, 12) => { + let $register = &($iocon_register).pio1_12; + $action; + } + (Bank::Bank1, 13) => { + let $register = &($iocon_register).pio1_13; + $action; + } + (Bank::Bank1, 14) => { + let $register = &($iocon_register).pio1_14; + $action; + } + (Bank::Bank1, 15) => { + let $register = &($iocon_register).pio1_15; + $action; + } + (Bank::Bank1, 16) => { + let $register = &($iocon_register).pio1_16; + $action; + } + (Bank::Bank1, 17) => { + let $register = &($iocon_register).pio1_17; + $action; + } + (Bank::Bank1, 18) => { + let $register = &($iocon_register).pio1_18; + $action; + } + (Bank::Bank1, 19) => { + let $register = &($iocon_register).pio1_19; + $action; + } + (Bank::Bank1, 20) => { + let $register = &($iocon_register).pio1_20; + $action; + } + (Bank::Bank1, 21) => { + let $register = &($iocon_register).pio1_21; + $action; + } + (Bank::Bank1, 22) => { + let $register = &($iocon_register).pio1_22; + $action; + } + (Bank::Bank1, 23) => { + let $register = &($iocon_register).pio1_23; + $action; + } + (Bank::Bank1, 24) => { + let $register = &($iocon_register).pio1_24; + $action; + } + (Bank::Bank1, 25) => { + let $register = &($iocon_register).pio1_25; + $action; + } + (Bank::Bank1, 26) => { + let $register = &($iocon_register).pio1_26; + $action; + } + (Bank::Bank1, 27) => { + let $register = &($iocon_register).pio1_27; + $action; + } + (Bank::Bank1, 28) => { + let $register = &($iocon_register).pio1_28; + $action; + } + (Bank::Bank1, 29) => { + let $register = &($iocon_register).pio1_29; + $action; + } + (Bank::Bank1, 30) => { + let $register = &($iocon_register).pio1_30; + $action; + } + (Bank::Bank1, 31) => { + let $register = &($iocon_register).pio1_31; + $action; + } + _ => unreachable!(), + } + }; +} + +pub(crate) use match_iocon; diff --git a/embassy-nxp/src/pint.rs b/embassy-nxp/src/pint.rs new file mode 100644 index 000000000..3313f91a6 --- /dev/null +++ b/embassy-nxp/src/pint.rs @@ -0,0 +1,440 @@ +//! Pin Interrupt module. +use core::cell::RefCell; +use core::future::Future; +use core::pin::Pin as FuturePin; +use core::task::{Context, Poll}; + +use critical_section::Mutex; +use embassy_hal_internal::{Peripheral, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::gpio::{self, AnyPin, Level, SealedPin}; +use crate::pac::interrupt; +use crate::pac_utils::*; + +struct PinInterrupt { + assigned: bool, + waker: AtomicWaker, + /// If true, the interrupt was triggered due to this PinInterrupt. This is used to determine if + /// an [InputFuture] should return Poll::Ready. + at_fault: bool, +} + +impl PinInterrupt { + pub fn interrupt_active(&self) -> bool { + self.assigned + } + + /// Mark the interrupt as assigned to a pin. + pub fn enable(&mut self) { + self.assigned = true; + self.at_fault = false; + } + + /// Mark the interrupt as available. + pub fn disable(&mut self) { + self.assigned = false; + self.at_fault = false; + } + + /// Returns true if the interrupt was triggered due to this PinInterrupt. + /// + /// If this function returns true, it will also reset the at_fault flag. + pub fn at_fault(&mut self) -> bool { + let val = self.at_fault; + self.at_fault = false; + val + } + + /// Set the at_fault flag to true. + pub fn fault(&mut self) { + self.at_fault = true; + } +} + +const NEW_PIN_INTERRUPT: PinInterrupt = PinInterrupt { + assigned: false, + waker: AtomicWaker::new(), + at_fault: false, +}; +const INTERUPT_COUNT: usize = 8; +static PIN_INTERRUPTS: Mutex> = + Mutex::new(RefCell::new([NEW_PIN_INTERRUPT; INTERUPT_COUNT])); + +fn next_available_interrupt() -> Option { + critical_section::with(|cs| { + for (i, pin_interrupt) in PIN_INTERRUPTS.borrow(cs).borrow().iter().enumerate() { + if !pin_interrupt.interrupt_active() { + return Some(i); + } + } + + None + }) +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Edge { + Rising, + Falling, + Both, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum InterruptOn { + Level(Level), + Edge(Edge), +} + +pub(crate) fn init() { + syscon_reg().ahbclkctrl0.modify(|_, w| w.pint().enable()); + + // Enable interrupts + unsafe { + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT0); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT1); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT2); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT3); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT4); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT5); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT6); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT7); + }; +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct InputFuture<'d> { + #[allow(dead_code)] + pin: PeripheralRef<'d, AnyPin>, + interrupt_number: usize, +} + +impl<'d> InputFuture<'d> { + /// Create a new input future. Returns None if all interrupts are in use. + fn new(pin: impl Peripheral

+ 'd, interrupt_on: InterruptOn) -> Option { + let pin = pin.into_ref().map_into(); + let interrupt_number = next_available_interrupt()?; + + // Clear interrupt, just in case + pint_reg() + .rise + .write(|w| unsafe { w.rdet().bits(1 << interrupt_number) }); + pint_reg() + .fall + .write(|w| unsafe { w.fdet().bits(1 << interrupt_number) }); + + // Enable input multiplexing on pin interrupt register 0 for pin (32*bank + pin_number) + inputmux_reg().pintsel[interrupt_number] + .write(|w| unsafe { w.intpin().bits(32 * pin.pin_bank() as u8 + pin.pin_number()) }); + + match interrupt_on { + InterruptOn::Level(level) => { + // Set pin interrupt register to edge sensitive or level sensitive + // 0 = edge sensitive, 1 = level sensitive + pint_reg() + .isel + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << interrupt_number)) }); + + // Enable level interrupt. + // + // Note: Level sensitive interrupts are enabled by the same register as rising edge + // is activated. + + // 0 = no-op, 1 = enable + pint_reg() + .sienr + .write(|w| unsafe { w.setenrl().bits(1 << interrupt_number) }); + + // Set active level + match level { + Level::Low => { + // 0 = no-op, 1 = select LOW + pint_reg() + .cienf + .write(|w| unsafe { w.cenaf().bits(1 << interrupt_number) }); + } + Level::High => { + // 0 = no-op, 1 = select HIGH + pint_reg() + .sienf + .write(|w| unsafe { w.setenaf().bits(1 << interrupt_number) }); + } + } + } + InterruptOn::Edge(edge) => { + // Set pin interrupt register to edge sensitive or level sensitive + // 0 = edge sensitive, 1 = level sensitive + pint_reg() + .isel + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << interrupt_number)) }); + + // Enable rising/falling edge detection + match edge { + Edge::Rising => { + // 0 = no-op, 1 = enable rising edge + pint_reg() + .sienr + .write(|w| unsafe { w.setenrl().bits(1 << interrupt_number) }); + // 0 = no-op, 1 = disable falling edge + pint_reg() + .cienf + .write(|w| unsafe { w.cenaf().bits(1 << interrupt_number) }); + } + Edge::Falling => { + // 0 = no-op, 1 = enable falling edge + pint_reg() + .sienf + .write(|w| unsafe { w.setenaf().bits(1 << interrupt_number) }); + // 0 = no-op, 1 = disable rising edge + pint_reg() + .cienr + .write(|w| unsafe { w.cenrl().bits(1 << interrupt_number) }); + } + Edge::Both => { + // 0 = no-op, 1 = enable + pint_reg() + .sienr + .write(|w| unsafe { w.setenrl().bits(1 << interrupt_number) }); + pint_reg() + .sienf + .write(|w| unsafe { w.setenaf().bits(1 << interrupt_number) }); + } + } + } + } + + critical_section::with(|cs| { + let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut(); + let pin_interrupt = &mut pin_interrupts[interrupt_number]; + + pin_interrupt.enable(); + }); + + Some(Self { pin, interrupt_number }) + } + + /// Returns true if the interrupt was triggered for this pin. + fn interrupt_triggered(&self) -> bool { + let interrupt_number = self.interrupt_number; + + // Initially, we determine if the interrupt was triggered by this InputFuture by checking + // the flags of the interrupt_number. However, by the time we get to this point, the + // interrupt may have been triggered again, so we needed to clear the cpu flags immediately. + // As a solution, we mark which [PinInterrupt] is responsible for the interrupt ("at fault") + critical_section::with(|cs| { + let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut(); + let pin_interrupt = &mut pin_interrupts[interrupt_number]; + + pin_interrupt.at_fault() + }) + } +} + +impl<'d> Drop for InputFuture<'d> { + fn drop(&mut self) { + let interrupt_number = self.interrupt_number; + + // Disable pin interrupt + // 0 = no-op, 1 = disable + pint_reg() + .cienr + .write(|w| unsafe { w.cenrl().bits(1 << interrupt_number) }); + pint_reg() + .cienf + .write(|w| unsafe { w.cenaf().bits(1 << interrupt_number) }); + + critical_section::with(|cs| { + let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut(); + let pin_interrupt = &mut pin_interrupts[interrupt_number]; + + pin_interrupt.disable(); + }); + } +} + +impl<'d> Future for InputFuture<'d> { + type Output = (); + + fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let interrupt_number = self.interrupt_number; + + critical_section::with(|cs| { + let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut(); + let pin_interrupt = &mut pin_interrupts[interrupt_number]; + + pin_interrupt.waker.register(cx.waker()); + }); + + if self.interrupt_triggered() { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +fn handle_interrupt(interrupt_number: usize) { + pint_reg() + .rise + .write(|w| unsafe { w.rdet().bits(1 << interrupt_number) }); + pint_reg() + .fall + .write(|w| unsafe { w.fdet().bits(1 << interrupt_number) }); + + critical_section::with(|cs| { + let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut(); + let pin_interrupt = &mut pin_interrupts[interrupt_number]; + + pin_interrupt.fault(); + pin_interrupt.waker.wake(); + }); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT0() { + handle_interrupt(0); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT1() { + handle_interrupt(1); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT2() { + handle_interrupt(2); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT3() { + handle_interrupt(3); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT4() { + handle_interrupt(4); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT5() { + handle_interrupt(5); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT6() { + handle_interrupt(6); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT7() { + handle_interrupt(7); +} + +impl gpio::Flex<'_> { + /// Wait for a falling or rising edge on the pin. You can have at most 8 pins waiting. If you + /// try to wait for more than 8 pins, this function will return `None`. + pub async fn wait_for_any_edge(&mut self) -> Option<()> { + InputFuture::new(&mut self.pin, InterruptOn::Edge(Edge::Both))?.await; + Some(()) + } + + /// Wait for a falling edge on the pin. You can have at most 8 pins waiting. If you try to wait + /// for more than 8 pins, this function will return `None`. + pub async fn wait_for_falling_edge(&mut self) -> Option<()> { + InputFuture::new(&mut self.pin, InterruptOn::Edge(Edge::Falling))?.await; + Some(()) + } + + /// Wait for a rising edge on the pin. You can have at most 8 pins waiting. If you try to wait + /// for more than 8 pins, this function will return `None`. + pub async fn wait_for_rising_edge(&mut self) -> Option<()> { + InputFuture::new(&mut self.pin, InterruptOn::Edge(Edge::Rising))?.await; + Some(()) + } + + /// Wait for a low level on the pin. You can have at most 8 pins waiting. If you try to wait for + /// more than 8 pins, this function will return `None`. + pub async fn wait_for_low(&mut self) -> Option<()> { + InputFuture::new(&mut self.pin, InterruptOn::Level(Level::Low))?.await; + Some(()) + } + + /// Wait for a high level on the pin. You can have at most 8 pins waiting. If you try to wait for + /// more than 8 pins, this function will return `None`. + pub async fn wait_for_high(&mut self) -> Option<()> { + InputFuture::new(&mut self.pin, InterruptOn::Level(Level::High))?.await; + Some(()) + } +} + +impl gpio::Input<'_> { + /// Wait for a falling or rising edge on the pin. You can have at most 8 pins waiting. If you + /// try to wait for more than 8 pins, this function will return `None`. + pub async fn wait_for_any_edge(&mut self) -> Option<()> { + self.pin.wait_for_any_edge().await + } + + /// Wait for a falling edge on the pin. You can have at most 8 pins waiting. If you try to wait + /// for more than 8 pins, this function will return `None`. + pub async fn wait_for_falling_edge(&mut self) -> Option<()> { + self.pin.wait_for_falling_edge().await + } + + /// Wait for a rising edge on the pin. You can have at most 8 pins waiting. If you try to wait + /// for more than 8 pins, this function will return `None`. + pub async fn wait_for_rising_edge(&mut self) -> Option<()> { + self.pin.wait_for_rising_edge().await + } + + /// Wait for a low level on the pin. You can have at most 8 pins waiting. If you try to wait for + /// more than 8 pins, this function will return `None`. + pub async fn wait_for_low(&mut self) -> Option<()> { + self.pin.wait_for_low().await + } + + /// Wait for a high level on the pin. You can have at most 8 pins waiting. If you try to wait for + /// more than 8 pins, this function will return `None`. + pub async fn wait_for_high(&mut self) -> Option<()> { + self.pin.wait_for_high().await + } +} + +impl gpio::Output<'_> { + /// Wait for a falling or rising edge on the pin. You can have at most 8 pins waiting. If you + /// try to wait for more than 8 pins, this function will return `None`. + pub async fn wait_for_any_edge(&mut self) -> Option<()> { + self.pin.wait_for_any_edge().await + } + + /// Wait for a falling edge on the pin. You can have at most 8 pins waiting. If you try to wait + /// for more than 8 pins, this function will return `None`. + pub async fn wait_for_falling_edge(&mut self) -> Option<()> { + self.pin.wait_for_falling_edge().await + } + + /// Wait for a rising edge on the pin. You can have at most 8 pins waiting. If you try to wait + /// for more than 8 pins, this function will return `None`. + pub async fn wait_for_rising_edge(&mut self) -> Option<()> { + self.pin.wait_for_rising_edge().await + } + + /// Wait for a low level on the pin. You can have at most 8 pins waiting. If you try to wait for + /// more than 8 pins, this function will return `None`. + pub async fn wait_for_low(&mut self) -> Option<()> { + self.pin.wait_for_low().await + } + + /// Wait for a high level on the pin. You can have at most 8 pins waiting. If you try to wait for + /// more than 8 pins, this function will return `None`. + pub async fn wait_for_high(&mut self) -> Option<()> { + self.pin.wait_for_high().await + } +} diff --git a/examples/lpc55s69/.cargo/config.toml b/examples/lpc55s69/.cargo/config.toml new file mode 100644 index 000000000..9556de72f --- /dev/null +++ b/examples/lpc55s69/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-rs run --chip LPC55S69JBD100" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "debug" diff --git a/examples/lpc55s69/Cargo.toml b/examples/lpc55s69/Cargo.toml new file mode 100644 index 000000000..14ec2d47e --- /dev/null +++ b/examples/lpc55s69/Cargo.toml @@ -0,0 +1,22 @@ +[package] +edition = "2021" +name = "embassy-nxp-lpc55s69-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + + +[dependencies] +embassy-nxp = { version = "0.1.0", path = "../../embassy-nxp", features = ["rt"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-time = { version = "0.3.0", path = "../../embassy-time", features = ["defmt"] } +panic-halt = "0.2.0" +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = {version = "0.7.0"} +defmt = "0.3" +defmt-rtt = "0.4" +panic-probe = { version = "0.3.2", features = ["print-defmt"] } +panic-semihosting = "0.6.0" + +[profile.release] +debug = 2 diff --git a/examples/lpc55s69/build.rs b/examples/lpc55s69/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/lpc55s69/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/lpc55s69/memory.x b/examples/lpc55s69/memory.x new file mode 100644 index 000000000..1483b2fad --- /dev/null +++ b/examples/lpc55s69/memory.x @@ -0,0 +1,28 @@ +/* File originally from lpc55-hal repo: https://github.com/lpc55/lpc55-hal/blob/main/memory.x */ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 512K + + /* for use with standard link.x */ + RAM : ORIGIN = 0x20000000, LENGTH = 256K + + /* would be used with proper link.x */ + /* needs changes to r0 (initialization code) */ + /* SRAM0 : ORIGIN = 0x20000000, LENGTH = 64K */ + /* SRAM1 : ORIGIN = 0x20010000, LENGTH = 64K */ + /* SRAM2 : ORIGIN = 0x20020000, LENGTH = 64K */ + /* SRAM3 : ORIGIN = 0x20030000, LENGTH = 64K */ + + /* CASPER SRAM regions */ + /* SRAMX0: ORIGIN = 0x1400_0000, LENGTH = 4K /1* to 0x1400_0FFF *1/ */ + /* SRAMX1: ORIGIN = 0x1400_4000, LENGTH = 4K /1* to 0x1400_4FFF *1/ */ + + /* USB1 SRAM regin */ + /* USB1_SRAM : ORIGIN = 0x40100000, LENGTH = 16K */ + + /* To define our own USB RAM section in one regular */ + /* RAM, probably easiest to shorten length of RAM */ + /* above, and use this freed RAM section */ + +} + diff --git a/examples/lpc55s69/src/bin/blinky_nop.rs b/examples/lpc55s69/src/bin/blinky_nop.rs new file mode 100644 index 000000000..58e2d9808 --- /dev/null +++ b/examples/lpc55s69/src/bin/blinky_nop.rs @@ -0,0 +1,33 @@ +//! This example has been made with the LPCXpresso55S69 board in mind, which has a built-in LED on PIO1_6. + +#![no_std] +#![no_main] + +use cortex_m::asm::nop; +use defmt::*; +use embassy_executor::Spawner; +use embassy_nxp::gpio::{Level, Output}; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nxp::init(Default::default()); + + let mut led = Output::new(p.PIO1_6, Level::Low); + + loop { + info!("led off!"); + led.set_high(); + + for _ in 0..200_000 { + nop(); + } + + info!("led on!"); + led.set_low(); + + for _ in 0..200_000 { + nop(); + } + } +} diff --git a/examples/lpc55s69/src/bin/button_executor.rs b/examples/lpc55s69/src/bin/button_executor.rs new file mode 100644 index 000000000..836b1c9eb --- /dev/null +++ b/examples/lpc55s69/src/bin/button_executor.rs @@ -0,0 +1,25 @@ +//! This example has been made with the LPCXpresso55S69 board in mind, which has a built-in LED on +//! PIO1_6 and a button (labeled "user") on PIO1_9. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nxp::gpio::{Input, Level, Output, Pull}; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let p = embassy_nxp::init(Default::default()); + + let mut led = Output::new(p.PIO1_6, Level::Low); + let mut button = Input::new(p.PIO1_9, Pull::Up); + + info!("Entered main loop"); + loop { + button.wait_for_rising_edge().await; + info!("Button pressed"); + led.toggle(); + } +} From baef775f6b33b4fd45dd3a4bb1122a52c6d22c67 Mon Sep 17 00:00:00 2001 From: Oliver Rockstedt Date: Mon, 7 Oct 2024 13:30:46 +0200 Subject: [PATCH 045/144] Add capacity, free_capacity, clear, len, is_empty and is_full functions to Channel::{Sender, Receiver} --- embassy-sync/CHANGELOG.md | 1 + embassy-sync/src/channel.rs | 84 +++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/embassy-sync/CHANGELOG.md b/embassy-sync/CHANGELOG.md index 8847e7e88..253a6cc82 100644 --- a/embassy-sync/CHANGELOG.md +++ b/embassy-sync/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add LazyLock sync primitive. - Add `clear`, `len`, `is_empty` and `is_full` functions to `zerocopy_channel`. +- Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `Channel::{Sender, Receiver}`. ## 0.6.0 - 2024-05-29 diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs index 55ac5fb66..18b053111 100644 --- a/embassy-sync/src/channel.rs +++ b/embassy-sync/src/channel.rs @@ -72,6 +72,48 @@ where pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { self.channel.poll_ready_to_send(cx) } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`Channel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`Channel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`Channel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`Channel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`Channel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`Channel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } } /// Send-only access to a [`Channel`] without knowing channel size. @@ -179,6 +221,48 @@ where pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { self.channel.poll_receive(cx) } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`Channel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`Channel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`Channel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`Channel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`Channel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`Channel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } } /// Receive-only access to a [`Channel`] without knowing channel size. From e3fd33d372b96cd32007dbffe5755150c3df22f2 Mon Sep 17 00:00:00 2001 From: Oliver Rockstedt Date: Mon, 7 Oct 2024 13:41:15 +0200 Subject: [PATCH 046/144] Minor changelog fix --- embassy-sync/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-sync/CHANGELOG.md b/embassy-sync/CHANGELOG.md index 253a6cc82..83fd666ac 100644 --- a/embassy-sync/CHANGELOG.md +++ b/embassy-sync/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add LazyLock sync primitive. - Add `clear`, `len`, `is_empty` and `is_full` functions to `zerocopy_channel`. -- Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `Channel::{Sender, Receiver}`. +- Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `channel::{Sender, Receiver}`. ## 0.6.0 - 2024-05-29 From 07748131dde887d214c1d9373ec642907d547dcd Mon Sep 17 00:00:00 2001 From: Oliver Rockstedt Date: Mon, 7 Oct 2024 17:24:56 +0200 Subject: [PATCH 047/144] embassy-sync: fixed link to priority_channel in README --- embassy-sync/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-sync/README.md b/embassy-sync/README.md index 3dcd9dcc8..6871bcabc 100644 --- a/embassy-sync/README.md +++ b/embassy-sync/README.md @@ -5,7 +5,7 @@ An [Embassy](https://embassy.dev) project. Synchronization primitives and data structures with async support: - [`Channel`](channel::Channel) - A Multiple Producer Multiple Consumer (MPMC) channel. Each message is only received by a single consumer. -- [`PriorityChannel`](channel::priority_channel::PriorityChannel) - A Multiple Producer Multiple Consumer (MPMC) channel. Each message is only received by a single consumer. Higher priority items are shifted to the front of the channel. +- [`PriorityChannel`](priority_channel::PriorityChannel) - A Multiple Producer Multiple Consumer (MPMC) channel. Each message is only received by a single consumer. Higher priority items are shifted to the front of the channel. - [`PubSubChannel`](pubsub::PubSubChannel) - A broadcast channel (publish-subscribe) channel. Each message is received by all consumers. - [`Signal`](signal::Signal) - Signalling latest value to a single consumer. - [`Watch`](watch::Watch) - Signalling latest value to multiple consumers. From 2704ac3d289650173b60e1a29d70e8903bea4cf1 Mon Sep 17 00:00:00 2001 From: Oliver Rockstedt Date: Mon, 7 Oct 2024 17:35:11 +0200 Subject: [PATCH 048/144] Add capacity, free_capacity, clear, len, is_empty and is_full functions to priority_channel::{Sender, Receiver} --- embassy-sync/CHANGELOG.md | 1 + embassy-sync/src/priority_channel.rs | 84 ++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/embassy-sync/CHANGELOG.md b/embassy-sync/CHANGELOG.md index 83fd666ac..1668c9319 100644 --- a/embassy-sync/CHANGELOG.md +++ b/embassy-sync/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add LazyLock sync primitive. - Add `clear`, `len`, `is_empty` and `is_full` functions to `zerocopy_channel`. - Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `channel::{Sender, Receiver}`. +- Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `priority_channel::{Sender, Receiver}`. ## 0.6.0 - 2024-05-29 diff --git a/embassy-sync/src/priority_channel.rs b/embassy-sync/src/priority_channel.rs index 24c6c5a7f..1f4d8667c 100644 --- a/embassy-sync/src/priority_channel.rs +++ b/embassy-sync/src/priority_channel.rs @@ -71,6 +71,48 @@ where pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { self.channel.poll_ready_to_send(cx) } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`PriorityChannel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`PriorityChannel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`PriorityChannel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`PriorityChannel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`PriorityChannel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`PriorityChannel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } } impl<'ch, M, T, K, const N: usize> From> for DynamicSender<'ch, T> @@ -146,6 +188,48 @@ where pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { self.channel.poll_receive(cx) } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`PriorityChannel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`PriorityChannel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`PriorityChannel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`PriorityChannel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`PriorityChannel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`PriorityChannel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } } impl<'ch, M, T, K, const N: usize> From> for DynamicReceiver<'ch, T> From bf60b239e87faa0b9905a42013d8ae9a9f4162ea Mon Sep 17 00:00:00 2001 From: Oliver Rockstedt Date: Mon, 7 Oct 2024 18:05:15 +0200 Subject: [PATCH 049/144] embassy-sync: fixed some clippy warnings --- embassy-sync/src/blocking_mutex/mod.rs | 1 + embassy-sync/src/mutex.rs | 2 +- embassy-sync/src/pubsub/mod.rs | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/embassy-sync/src/blocking_mutex/mod.rs b/embassy-sync/src/blocking_mutex/mod.rs index 8a4a4c642..beafdb43d 100644 --- a/embassy-sync/src/blocking_mutex/mod.rs +++ b/embassy-sync/src/blocking_mutex/mod.rs @@ -104,6 +104,7 @@ impl Mutex { impl Mutex { /// Borrows the data + #[allow(clippy::should_implement_trait)] pub fn borrow(&self) -> &T { let ptr = self.data.get() as *const T; unsafe { &*ptr } diff --git a/embassy-sync/src/mutex.rs b/embassy-sync/src/mutex.rs index 8c3a3af9f..08f66e374 100644 --- a/embassy-sync/src/mutex.rs +++ b/embassy-sync/src/mutex.rs @@ -138,7 +138,7 @@ impl From for Mutex { impl Default for Mutex where M: RawMutex, - T: ?Sized + Default, + T: Default, { fn default() -> Self { Self::new(Default::default()) diff --git a/embassy-sync/src/pubsub/mod.rs b/embassy-sync/src/pubsub/mod.rs index 812302e2b..ae5951829 100644 --- a/embassy-sync/src/pubsub/mod.rs +++ b/embassy-sync/src/pubsub/mod.rs @@ -27,8 +27,8 @@ pub use subscriber::{DynSubscriber, Subscriber}; /// /// - With [Pub::publish()] the publisher has to wait until there is space in the internal message queue. /// - With [Pub::publish_immediate()] the publisher doesn't await and instead lets the oldest message -/// in the queue drop if necessary. This will cause any [Subscriber] that missed the message to receive -/// an error to indicate that it has lagged. +/// in the queue drop if necessary. This will cause any [Subscriber] that missed the message to receive +/// an error to indicate that it has lagged. /// /// ## Example /// From 4110cb494fa21184f46dbbc2fd81baaeb3d0dc26 Mon Sep 17 00:00:00 2001 From: Oliver Rockstedt Date: Mon, 7 Oct 2024 18:12:45 +0200 Subject: [PATCH 050/144] embassy-sync: added Watch primitive to changelog --- embassy-sync/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/embassy-sync/CHANGELOG.md b/embassy-sync/CHANGELOG.md index 1668c9319..af5682560 100644 --- a/embassy-sync/CHANGELOG.md +++ b/embassy-sync/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - Add LazyLock sync primitive. +- Add `Watch` sync primitive. - Add `clear`, `len`, `is_empty` and `is_full` functions to `zerocopy_channel`. - Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `channel::{Sender, Receiver}`. - Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `priority_channel::{Sender, Receiver}`. From 592bb5a8ca8138b95ba878cd6e509c0a21d088d5 Mon Sep 17 00:00:00 2001 From: Oliver Rockstedt Date: Mon, 7 Oct 2024 18:16:47 +0200 Subject: [PATCH 051/144] embassy-sync: made changelog formatting more consistent --- embassy-sync/CHANGELOG.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/embassy-sync/CHANGELOG.md b/embassy-sync/CHANGELOG.md index af5682560..a7dd6f66e 100644 --- a/embassy-sync/CHANGELOG.md +++ b/embassy-sync/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -- Add LazyLock sync primitive. +- Add `LazyLock` sync primitive. - Add `Watch` sync primitive. - Add `clear`, `len`, `is_empty` and `is_full` functions to `zerocopy_channel`. - Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `channel::{Sender, Receiver}`. @@ -20,20 +20,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `PubSubChannel`. - Made `PubSubBehavior` sealed - If you called `.publish_immediate(...)` on the queue directly before, then now call `.immediate_publisher().publish_immediate(...)` -- Add OnceLock sync primitive. -- Add constructor for DynamicChannel -- Add ready_to_receive functions to Channel and Receiver. +- Add `OnceLock` sync primitive. +- Add constructor for `DynamicChannel` +- Add ready_to_receive functions to `Channel` and `Receiver`. ## 0.5.0 - 2023-12-04 -- Add a PriorityChannel. -- Remove nightly and unstable-traits features in preparation for 1.75. -- Upgrade heapless to 0.8. -- Upgrade static-cell to 2.0. +- Add a `PriorityChannel`. +- Remove `nightly` and `unstable-traits` features in preparation for 1.75. +- Upgrade `heapless` to 0.8. +- Upgrade `static-cell` to 2.0. ## 0.4.0 - 2023-10-31 -- Re-add impl_trait_projections +- Re-add `impl_trait_projections` - switch to `embedded-io 0.6` ## 0.3.0 - 2023-09-14 From df0fc041981105a19beb690e30c3ab1f532d7298 Mon Sep 17 00:00:00 2001 From: Lena Berlin Date: Wed, 11 Sep 2024 11:36:24 -0400 Subject: [PATCH 052/144] fix: stm32l0 low-power EXTI IRQ handler wiped pending bits before they were checked --- embassy-stm32/src/exti.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/embassy-stm32/src/exti.rs b/embassy-stm32/src/exti.rs index 224d51b84..87512c92d 100644 --- a/embassy-stm32/src/exti.rs +++ b/embassy-stm32/src/exti.rs @@ -41,9 +41,6 @@ fn exticr_regs() -> pac::afio::Afio { } unsafe fn on_irq() { - #[cfg(feature = "low-power")] - crate::low_power::on_wakeup_irq(); - #[cfg(not(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50)))] let bits = EXTI.pr(0).read().0; #[cfg(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50))] @@ -68,6 +65,9 @@ unsafe fn on_irq() { EXTI.rpr(0).write_value(Lines(bits)); EXTI.fpr(0).write_value(Lines(bits)); } + + #[cfg(feature = "low-power")] + crate::low_power::on_wakeup_irq(); } struct BitIter(u32); From 57c1fbf3089e2a2dc9fe5b7d1f1e094596566395 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Wed, 9 Oct 2024 10:04:35 -0400 Subject: [PATCH 053/144] Move pio programs into embassy-rp --- embassy-rp/Cargo.toml | 1 + embassy-rp/src/lib.rs | 1 + embassy-rp/src/pio_programs/hd44780.rs | 203 ++++++++++++++++ embassy-rp/src/pio_programs/i2s.rs | 97 ++++++++ embassy-rp/src/pio_programs/mod.rs | 10 + embassy-rp/src/pio_programs/onewire.rs | 109 +++++++++ embassy-rp/src/pio_programs/pwm.rs | 114 +++++++++ embassy-rp/src/pio_programs/rotary_encoder.rs | 72 ++++++ embassy-rp/src/pio_programs/stepper.rs | 146 ++++++++++++ embassy-rp/src/pio_programs/uart.rs | 186 +++++++++++++++ embassy-rp/src/pio_programs/ws2812.rs | 118 ++++++++++ examples/rp/Cargo.toml | 2 +- examples/rp/src/bin/pio_hd44780.rs | 201 ++-------------- examples/rp/src/bin/pio_i2s.rs | 71 ++---- examples/rp/src/bin/pio_onewire.rs | 98 +------- examples/rp/src/bin/pio_pwm.rs | 90 +------ examples/rp/src/bin/pio_rotary_encoder.rs | 91 +++---- examples/rp/src/bin/pio_servo.rs | 96 +------- examples/rp/src/bin/pio_stepper.rs | 135 +---------- examples/rp/src/bin/pio_uart.rs | 222 ++---------------- examples/rp/src/bin/pio_ws2812.rs | 105 +-------- examples/rp23/src/bin/pio_hd44780.rs | 201 ++-------------- examples/rp23/src/bin/pio_i2s.rs | 77 ++---- examples/rp23/src/bin/pio_onewire.rs | 88 +++++++ examples/rp23/src/bin/pio_pwm.rs | 90 +------ examples/rp23/src/bin/pio_rotary_encoder.rs | 91 +++---- examples/rp23/src/bin/pio_servo.rs | 96 +------- examples/rp23/src/bin/pio_stepper.rs | 135 +---------- examples/rp23/src/bin/pio_uart.rs | 203 ++++++++++++++++ examples/rp23/src/bin/pio_ws2812.rs | 105 +-------- 30 files changed, 1590 insertions(+), 1664 deletions(-) create mode 100644 embassy-rp/src/pio_programs/hd44780.rs create mode 100644 embassy-rp/src/pio_programs/i2s.rs create mode 100644 embassy-rp/src/pio_programs/mod.rs create mode 100644 embassy-rp/src/pio_programs/onewire.rs create mode 100644 embassy-rp/src/pio_programs/pwm.rs create mode 100644 embassy-rp/src/pio_programs/rotary_encoder.rs create mode 100644 embassy-rp/src/pio_programs/stepper.rs create mode 100644 embassy-rp/src/pio_programs/uart.rs create mode 100644 embassy-rp/src/pio_programs/ws2812.rs create mode 100644 examples/rp23/src/bin/pio_onewire.rs create mode 100644 examples/rp23/src/bin/pio_uart.rs diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index 29a8a3c53..54de238b3 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml @@ -144,6 +144,7 @@ rp2040-boot2 = "0.3" document-features = "0.2.7" sha2-const-stable = "0.1" rp-binary-info = { version = "0.1.0", optional = true } +smart-leds = "0.4.0" [dev-dependencies] embassy-executor = { version = "0.6.0", path = "../embassy-executor", features = ["arch-std", "executor-thread"] } diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index d402cf793..7ac18c1f8 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -34,6 +34,7 @@ pub mod i2c_slave; pub mod multicore; #[cfg(feature = "_rp235x")] pub mod otp; +pub mod pio_programs; pub mod pwm; mod reset; pub mod rom_data; diff --git a/embassy-rp/src/pio_programs/hd44780.rs b/embassy-rp/src/pio_programs/hd44780.rs new file mode 100644 index 000000000..9bbf44fc4 --- /dev/null +++ b/embassy-rp/src/pio_programs/hd44780.rs @@ -0,0 +1,203 @@ +//! [HD44780 display driver](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) + +use crate::dma::{AnyChannel, Channel}; +use crate::pio::{ + Common, Config, Direction, FifoJoin, Instance, Irq, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, + StateMachine, +}; +use crate::{into_ref, Peripheral, PeripheralRef}; + +/// This struct represents a HD44780 program that takes command words ( <0:4>) +pub struct PioHD44780CommandWordProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioHD44780CommandWordProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio_proc::pio_asm!( + r#" + .side_set 1 opt + .origin 20 + + loop: + out x, 24 + delay: + jmp x--, delay + out pins, 4 side 1 + out null, 4 side 0 + jmp !osre, loop + irq 0 + "#, + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// This struct represents a HD44780 program that takes command sequences ( , data...) +pub struct PioHD44780CommandSequenceProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioHD44780CommandSequenceProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + // many side sets are only there to free up a delay bit! + let prg = pio_proc::pio_asm!( + r#" + .origin 27 + .side_set 1 + + .wrap_target + pull side 0 + out x 1 side 0 ; !rs + out y 7 side 0 ; #data - 1 + + ; rs/rw to e: >= 60ns + ; e high time: >= 500ns + ; e low time: >= 500ns + ; read data valid after e falling: ~5ns + ; write data hold after e falling: ~10ns + + loop: + pull side 0 + jmp !x data side 0 + command: + set pins 0b00 side 0 + jmp shift side 0 + data: + set pins 0b01 side 0 + shift: + out pins 4 side 1 [9] + nop side 0 [9] + out pins 4 side 1 [9] + mov osr null side 0 [7] + out pindirs 4 side 0 + set pins 0b10 side 0 + busy: + nop side 1 [9] + jmp pin more side 0 [9] + mov osr ~osr side 1 [9] + nop side 0 [4] + out pindirs 4 side 0 + jmp y-- loop side 0 + .wrap + more: + nop side 1 [9] + jmp busy side 0 [9] + "# + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio backed HD44780 driver +pub struct PioHD44780<'l, P: Instance, const S: usize> { + dma: PeripheralRef<'l, AnyChannel>, + sm: StateMachine<'l, P, S>, + + buf: [u8; 40], +} + +impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> { + /// Configure the given state machine to first init, then write data to, a HD44780 display. + pub async fn new( + common: &mut Common<'l, P>, + mut sm: StateMachine<'l, P, S>, + mut irq: Irq<'l, P, S>, + dma: impl Peripheral

+ 'l, + rs: impl PioPin, + rw: impl PioPin, + e: impl PioPin, + db4: impl PioPin, + db5: impl PioPin, + db6: impl PioPin, + db7: impl PioPin, + word_prg: &PioHD44780CommandWordProgram<'l, P>, + seq_prg: &PioHD44780CommandSequenceProgram<'l, P>, + ) -> PioHD44780<'l, P, S> { + into_ref!(dma); + + let rs = common.make_pio_pin(rs); + let rw = common.make_pio_pin(rw); + let e = common.make_pio_pin(e); + let db4 = common.make_pio_pin(db4); + let db5 = common.make_pio_pin(db5); + let db6 = common.make_pio_pin(db6); + let db7 = common.make_pio_pin(db7); + + sm.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]); + + let mut cfg = Config::default(); + cfg.use_program(&word_prg.prg, &[&e]); + cfg.clock_divider = 125u8.into(); + cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); + cfg.shift_out = ShiftConfig { + auto_fill: true, + direction: ShiftDirection::Left, + threshold: 32, + }; + cfg.fifo_join = FifoJoin::TxOnly; + sm.set_config(&cfg); + + sm.set_enable(true); + // init to 8 bit thrice + sm.tx().push((50000 << 8) | 0x30); + sm.tx().push((5000 << 8) | 0x30); + sm.tx().push((200 << 8) | 0x30); + // init 4 bit + sm.tx().push((200 << 8) | 0x20); + // set font and lines + sm.tx().push((50 << 8) | 0x20); + sm.tx().push(0b1100_0000); + + irq.wait().await; + sm.set_enable(false); + + let mut cfg = Config::default(); + cfg.use_program(&seq_prg.prg, &[&e]); + cfg.clock_divider = 8u8.into(); // ~64ns/insn + cfg.set_jmp_pin(&db7); + cfg.set_set_pins(&[&rs, &rw]); + cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); + cfg.shift_out.direction = ShiftDirection::Left; + cfg.fifo_join = FifoJoin::TxOnly; + sm.set_config(&cfg); + + sm.set_enable(true); + + // display on and cursor on and blinking, reset display + sm.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1]).await; + + Self { + dma: dma.map_into(), + sm, + buf: [0x20; 40], + } + } + + /// Write a line to the display + pub async fn add_line(&mut self, s: &[u8]) { + // move cursor to 0:0, prepare 16 characters + self.buf[..3].copy_from_slice(&[0x80, 0x80, 15]); + // move line 2 up + self.buf.copy_within(22..38, 3); + // move cursor to 1:0, prepare 16 characters + self.buf[19..22].copy_from_slice(&[0x80, 0xc0, 15]); + // file line 2 with spaces + self.buf[22..38].fill(0x20); + // copy input line + let len = s.len().min(16); + self.buf[22..22 + len].copy_from_slice(&s[0..len]); + // set cursor to 1:15 + self.buf[38..].copy_from_slice(&[0x80, 0xcf]); + + self.sm.tx().dma_push(self.dma.reborrow(), &self.buf).await; + } +} diff --git a/embassy-rp/src/pio_programs/i2s.rs b/embassy-rp/src/pio_programs/i2s.rs new file mode 100644 index 000000000..3c8ef8bb6 --- /dev/null +++ b/embassy-rp/src/pio_programs/i2s.rs @@ -0,0 +1,97 @@ +//! Pio backed I2s output + +use crate::{ + dma::{AnyChannel, Channel, Transfer}, + into_ref, + pio::{ + Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, + }, + Peripheral, PeripheralRef, +}; +use fixed::traits::ToFixed; + +/// This struct represents an i2s output driver program +pub struct PioI2sOutProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioI2sOutProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio_proc::pio_asm!( + ".side_set 2", + " set x, 14 side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock + "left_data:", + " out pins, 1 side 0b00", + " jmp x-- left_data side 0b01", + " out pins 1 side 0b10", + " set x, 14 side 0b11", + "right_data:", + " out pins 1 side 0b10", + " jmp x-- right_data side 0b11", + " out pins 1 side 0b00", + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio backed I2s output driver +pub struct PioI2sOut<'a, P: Instance, const S: usize> { + dma: PeripheralRef<'a, AnyChannel>, + sm: StateMachine<'a, P, S>, +} + +impl<'a, P: Instance, const S: usize> PioI2sOut<'a, P, S> { + /// Configure a state machine to output I2s + pub fn new( + common: &mut Common<'a, P>, + mut sm: StateMachine<'a, P, S>, + dma: impl Peripheral

+ 'a, + data_pin: impl PioPin, + bit_clock_pin: impl PioPin, + lr_clock_pin: impl PioPin, + sample_rate: u32, + bit_depth: u32, + channels: u32, + program: &PioI2sOutProgram<'a, P>, + ) -> Self { + into_ref!(dma); + + let data_pin = common.make_pio_pin(data_pin); + let bit_clock_pin = common.make_pio_pin(bit_clock_pin); + let left_right_clock_pin = common.make_pio_pin(lr_clock_pin); + + let cfg = { + let mut cfg = Config::default(); + cfg.use_program(&program.prg, &[&bit_clock_pin, &left_right_clock_pin]); + cfg.set_out_pins(&[&data_pin]); + let clock_frequency = sample_rate * bit_depth * channels; + cfg.clock_divider = (125_000_000. / clock_frequency as f64 / 2.).to_fixed(); + cfg.shift_out = ShiftConfig { + threshold: 32, + direction: ShiftDirection::Left, + auto_fill: true, + }; + // join fifos to have twice the time to start the next dma transfer + cfg.fifo_join = FifoJoin::TxOnly; + cfg + }; + sm.set_config(&cfg); + sm.set_pin_dirs(Direction::Out, &[&data_pin, &left_right_clock_pin, &bit_clock_pin]); + + sm.set_enable(true); + + Self { + dma: dma.map_into(), + sm, + } + } + + /// Return an in-prograss dma transfer future. Awaiting it will guarentee a complete transfer. + pub fn write<'b>(&'b mut self, buff: &'b [u32]) -> Transfer<'b, AnyChannel> { + self.sm.tx().dma_push(self.dma.reborrow(), buff) + } +} diff --git a/embassy-rp/src/pio_programs/mod.rs b/embassy-rp/src/pio_programs/mod.rs new file mode 100644 index 000000000..74537825b --- /dev/null +++ b/embassy-rp/src/pio_programs/mod.rs @@ -0,0 +1,10 @@ +//! Pre-built pio programs for common interfaces + +pub mod hd44780; +pub mod i2s; +pub mod onewire; +pub mod pwm; +pub mod rotary_encoder; +pub mod stepper; +pub mod uart; +pub mod ws2812; diff --git a/embassy-rp/src/pio_programs/onewire.rs b/embassy-rp/src/pio_programs/onewire.rs new file mode 100644 index 000000000..f3bc5fcd7 --- /dev/null +++ b/embassy-rp/src/pio_programs/onewire.rs @@ -0,0 +1,109 @@ +//! OneWire pio driver + +use crate::pio::{self, Common, Config, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine}; + +/// This struct represents an onewire driver program +pub struct PioOneWireProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioOneWireProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio_proc::pio_asm!( + r#" + .wrap_target + again: + pull block + mov x, osr + jmp !x, read + write: + set pindirs, 1 + set pins, 0 + loop1: + jmp x--,loop1 + set pindirs, 0 [31] + wait 1 pin 0 [31] + pull block + mov x, osr + bytes1: + pull block + set y, 7 + set pindirs, 1 + bit1: + set pins, 0 [1] + out pins,1 [31] + set pins, 1 [20] + jmp y--,bit1 + jmp x--,bytes1 + set pindirs, 0 [31] + jmp again + read: + pull block + mov x, osr + bytes2: + set y, 7 + bit2: + set pindirs, 1 + set pins, 0 [1] + set pindirs, 0 [5] + in pins,1 [10] + jmp y--,bit2 + jmp x--,bytes2 + .wrap + "#, + ); + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio backed OneWire driver +pub struct PioOneWire<'d, PIO: pio::Instance, const SM: usize> { + sm: StateMachine<'d, PIO, SM>, +} + +impl<'d, PIO: pio::Instance, const SM: usize> PioOneWire<'d, PIO, SM> { + /// Create a new instance the driver + pub fn new( + common: &mut Common<'d, PIO>, + mut sm: StateMachine<'d, PIO, SM>, + pin: impl PioPin, + program: &PioOneWireProgram<'d, PIO>, + ) -> Self { + let pin = common.make_pio_pin(pin); + let mut cfg = Config::default(); + cfg.use_program(&program.prg, &[]); + cfg.set_out_pins(&[&pin]); + cfg.set_in_pins(&[&pin]); + cfg.set_set_pins(&[&pin]); + cfg.shift_in = ShiftConfig { + auto_fill: true, + direction: ShiftDirection::Right, + threshold: 8, + }; + cfg.clock_divider = 255_u8.into(); + sm.set_config(&cfg); + sm.set_enable(true); + Self { sm } + } + + /// Write bytes over the wire + pub async fn write_bytes(&mut self, bytes: &[u8]) { + self.sm.tx().wait_push(250).await; + self.sm.tx().wait_push(bytes.len() as u32 - 1).await; + for b in bytes { + self.sm.tx().wait_push(*b as u32).await; + } + } + + /// Read bytes from the wire + pub async fn read_bytes(&mut self, bytes: &mut [u8]) { + self.sm.tx().wait_push(0).await; + self.sm.tx().wait_push(bytes.len() as u32 - 1).await; + for b in bytes.iter_mut() { + *b = (self.sm.rx().wait_pull().await >> 24) as u8; + } + } +} diff --git a/embassy-rp/src/pio_programs/pwm.rs b/embassy-rp/src/pio_programs/pwm.rs new file mode 100644 index 000000000..5dfd70cc9 --- /dev/null +++ b/embassy-rp/src/pio_programs/pwm.rs @@ -0,0 +1,114 @@ +//! PIO backed PWM driver + +use core::time::Duration; + +use crate::{ + clocks, + gpio::Level, + pio::{Common, Config, Direction, Instance, LoadedProgram, PioPin, StateMachine}, +}; +use pio::InstructionOperands; + +fn to_pio_cycles(duration: Duration) -> u32 { + (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow +} + +/// This struct represents a PWM program loaded into pio instruction memory. +pub struct PioPwmProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioPwmProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio_proc::pio_asm!( + ".side_set 1 opt" + "pull noblock side 0" + "mov x, osr" + "mov y, isr" + "countloop:" + "jmp x!=y noset" + "jmp skip side 1" + "noset:" + "nop" + "skip:" + "jmp y-- countloop" + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio backed PWM output +pub struct PioPwm<'d, T: Instance, const SM: usize> { + sm: StateMachine<'d, T, SM>, +} + +impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> { + /// Configure a state machine as a PWM output + pub fn new( + pio: &mut Common<'d, T>, + mut sm: StateMachine<'d, T, SM>, + pin: impl PioPin, + program: &PioPwmProgram<'d, T>, + ) -> Self { + let pin = pio.make_pio_pin(pin); + sm.set_pins(Level::High, &[&pin]); + sm.set_pin_dirs(Direction::Out, &[&pin]); + + let mut cfg = Config::default(); + cfg.use_program(&program.prg, &[&pin]); + + sm.set_config(&cfg); + + Self { sm } + } + + /// Enable PWM output + pub fn start(&mut self) { + self.sm.set_enable(true); + } + + /// Disable PWM output + pub fn stop(&mut self) { + self.sm.set_enable(false); + } + + /// Set pwm period + pub fn set_period(&mut self, duration: Duration) { + let is_enabled = self.sm.is_enabled(); + while !self.sm.tx().empty() {} // Make sure that the queue is empty + self.sm.set_enable(false); + self.sm.tx().push(to_pio_cycles(duration)); + unsafe { + self.sm.exec_instr( + InstructionOperands::PULL { + if_empty: false, + block: false, + } + .encode(), + ); + self.sm.exec_instr( + InstructionOperands::OUT { + destination: ::pio::OutDestination::ISR, + bit_count: 32, + } + .encode(), + ); + }; + if is_enabled { + self.sm.set_enable(true) // Enable if previously enabled + } + } + + fn set_level(&mut self, level: u32) { + self.sm.tx().push(level); + } + + /// Set the pulse width high time + pub fn write(&mut self, duration: Duration) { + self.set_level(to_pio_cycles(duration)); + } +} diff --git a/embassy-rp/src/pio_programs/rotary_encoder.rs b/embassy-rp/src/pio_programs/rotary_encoder.rs new file mode 100644 index 000000000..323f839bc --- /dev/null +++ b/embassy-rp/src/pio_programs/rotary_encoder.rs @@ -0,0 +1,72 @@ +//! PIO backed quadrature encoder + +use crate::gpio::Pull; +use crate::pio::{self, Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine}; +use fixed::traits::ToFixed; + +/// This struct represents an Encoder program loaded into pio instruction memory. +pub struct PioEncoderProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioEncoderProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio_proc::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio Backed quadrature encoder reader +pub struct PioEncoder<'d, T: Instance, const SM: usize> { + sm: StateMachine<'d, T, SM>, +} + +impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> { + /// Configure a state machine with the loaded [PioEncoderProgram] + pub fn new( + pio: &mut Common<'d, T>, + mut sm: StateMachine<'d, T, SM>, + pin_a: impl PioPin, + pin_b: impl PioPin, + program: &PioEncoderProgram<'d, T>, + ) -> Self { + let mut pin_a = pio.make_pio_pin(pin_a); + let mut pin_b = pio.make_pio_pin(pin_b); + pin_a.set_pull(Pull::Up); + pin_b.set_pull(Pull::Up); + sm.set_pin_dirs(pio::Direction::In, &[&pin_a, &pin_b]); + + let mut cfg = Config::default(); + cfg.set_in_pins(&[&pin_a, &pin_b]); + cfg.fifo_join = FifoJoin::RxOnly; + cfg.shift_in.direction = ShiftDirection::Left; + cfg.clock_divider = 10_000.to_fixed(); + cfg.use_program(&program.prg, &[]); + sm.set_config(&cfg); + sm.set_enable(true); + Self { sm } + } + + /// Read a single count from the encoder + pub async fn read(&mut self) -> Direction { + loop { + match self.sm.rx().wait_pull().await { + 0 => return Direction::CounterClockwise, + 1 => return Direction::Clockwise, + _ => {} + } + } + } +} + +/// Encoder Count Direction +pub enum Direction { + /// Encoder turned clockwise + Clockwise, + /// Encoder turned counter clockwise + CounterClockwise, +} diff --git a/embassy-rp/src/pio_programs/stepper.rs b/embassy-rp/src/pio_programs/stepper.rs new file mode 100644 index 000000000..0ecc4eff0 --- /dev/null +++ b/embassy-rp/src/pio_programs/stepper.rs @@ -0,0 +1,146 @@ +//! Pio Stepper Driver for 5-wire steppers + +use core::mem::{self, MaybeUninit}; + +use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine}; +use fixed::traits::ToFixed; +use fixed::types::extra::U8; +use fixed::FixedU32; + +/// This struct represents a Stepper driver program loaded into pio instruction memory. +pub struct PioStepperProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioStepperProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio_proc::pio_asm!( + "pull block", + "mov x, osr", + "pull block", + "mov y, osr", + "jmp !x end", + "loop:", + "jmp !osre step", + "mov osr, y", + "step:", + "out pins, 4 [31]" + "jmp x-- loop", + "end:", + "irq 0 rel" + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio backed Stepper driver +pub struct PioStepper<'d, T: Instance, const SM: usize> { + irq: Irq<'d, T, SM>, + sm: StateMachine<'d, T, SM>, +} + +impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { + /// Configure a state machine to drive a stepper + pub fn new( + pio: &mut Common<'d, T>, + mut sm: StateMachine<'d, T, SM>, + irq: Irq<'d, T, SM>, + pin0: impl PioPin, + pin1: impl PioPin, + pin2: impl PioPin, + pin3: impl PioPin, + program: &PioStepperProgram<'d, T>, + ) -> Self { + let pin0 = pio.make_pio_pin(pin0); + let pin1 = pio.make_pio_pin(pin1); + let pin2 = pio.make_pio_pin(pin2); + let pin3 = pio.make_pio_pin(pin3); + sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]); + let mut cfg = Config::default(); + cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]); + cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed(); + cfg.use_program(&program.prg, &[]); + sm.set_config(&cfg); + sm.set_enable(true); + Self { irq, sm } + } + + /// Set pulse frequency + pub fn set_frequency(&mut self, freq: u32) { + let clock_divider: FixedU32 = (125_000_000 / (freq * 136)).to_fixed(); + assert!(clock_divider <= 65536, "clkdiv must be <= 65536"); + assert!(clock_divider >= 1, "clkdiv must be >= 1"); + self.sm.set_clock_divider(clock_divider); + self.sm.clkdiv_restart(); + } + + /// Full step, one phase + pub async fn step(&mut self, steps: i32) { + if steps > 0 { + self.run(steps, 0b1000_0100_0010_0001_1000_0100_0010_0001).await + } else { + self.run(-steps, 0b0001_0010_0100_1000_0001_0010_0100_1000).await + } + } + + /// Full step, two phase + pub async fn step2(&mut self, steps: i32) { + if steps > 0 { + self.run(steps, 0b1001_1100_0110_0011_1001_1100_0110_0011).await + } else { + self.run(-steps, 0b0011_0110_1100_1001_0011_0110_1100_1001).await + } + } + + /// Half step + pub async fn step_half(&mut self, steps: i32) { + if steps > 0 { + self.run(steps, 0b1001_1000_1100_0100_0110_0010_0011_0001).await + } else { + self.run(-steps, 0b0001_0011_0010_0110_0100_1100_1000_1001).await + } + } + + async fn run(&mut self, steps: i32, pattern: u32) { + self.sm.tx().wait_push(steps as u32).await; + self.sm.tx().wait_push(pattern).await; + let drop = OnDrop::new(|| { + self.sm.clear_fifos(); + unsafe { + self.sm.exec_instr( + pio::InstructionOperands::JMP { + address: 0, + condition: pio::JmpCondition::Always, + } + .encode(), + ); + } + }); + self.irq.wait().await; + drop.defuse(); + } +} + +struct OnDrop { + f: MaybeUninit, +} + +impl OnDrop { + pub fn new(f: F) -> Self { + Self { f: MaybeUninit::new(f) } + } + + pub fn defuse(self) { + mem::forget(self) + } +} + +impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } +} diff --git a/embassy-rp/src/pio_programs/uart.rs b/embassy-rp/src/pio_programs/uart.rs new file mode 100644 index 000000000..f4b3b204e --- /dev/null +++ b/embassy-rp/src/pio_programs/uart.rs @@ -0,0 +1,186 @@ +//! Pio backed uart drivers + +use crate::{ + clocks::clk_sys_freq, + gpio::Level, + pio::{ + Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, + StateMachine, + }, +}; +use core::convert::Infallible; +use embedded_io_async::{ErrorType, Read, Write}; +use fixed::traits::ToFixed; + +/// This struct represents a uart tx program loaded into pio instruction memory. +pub struct PioUartTxProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioUartTxProgram<'a, PIO> { + /// Load the uart tx program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio_proc::pio_asm!( + r#" + .side_set 1 opt + + ; An 8n1 UART transmit program. + ; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin. + + pull side 1 [7] ; Assert stop bit, or stall with line in idle state + set x, 7 side 0 [7] ; Preload bit counter, assert start bit for 8 clocks + bitloop: ; This loop will run 8 times (8n1 UART) + out pins, 1 ; Shift 1 bit from OSR to the first OUT pin + jmp x-- bitloop [6] ; Each loop iteration is 8 cycles. + "# + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// PIO backed Uart transmitter +pub struct PioUartTx<'a, PIO: Instance, const SM: usize> { + sm_tx: StateMachine<'a, PIO, SM>, +} + +impl<'a, PIO: Instance, const SM: usize> PioUartTx<'a, PIO, SM> { + /// Configure a pio state machine to use the loaded tx program. + pub fn new( + baud: u32, + common: &mut Common<'a, PIO>, + mut sm_tx: StateMachine<'a, PIO, SM>, + tx_pin: impl PioPin, + program: &PioUartTxProgram<'a, PIO>, + ) -> Self { + let tx_pin = common.make_pio_pin(tx_pin); + sm_tx.set_pins(Level::High, &[&tx_pin]); + sm_tx.set_pin_dirs(PioDirection::Out, &[&tx_pin]); + + let mut cfg = Config::default(); + + cfg.set_out_pins(&[&tx_pin]); + cfg.use_program(&program.prg, &[&tx_pin]); + cfg.shift_out.auto_fill = false; + cfg.shift_out.direction = ShiftDirection::Right; + cfg.fifo_join = FifoJoin::TxOnly; + cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed(); + sm_tx.set_config(&cfg); + sm_tx.set_enable(true); + + Self { sm_tx } + } + + /// Write a single u8 + pub async fn write_u8(&mut self, data: u8) { + self.sm_tx.tx().wait_push(data as u32).await; + } +} + +impl ErrorType for PioUartTx<'_, PIO, SM> { + type Error = Infallible; +} + +impl Write for PioUartTx<'_, PIO, SM> { + async fn write(&mut self, buf: &[u8]) -> Result { + for byte in buf { + self.write_u8(*byte).await; + } + Ok(buf.len()) + } +} + +/// This struct represents a Uart Rx program loaded into pio instruction memory. +pub struct PioUartRxProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioUartRxProgram<'a, PIO> { + /// Load the uart rx program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio_proc::pio_asm!( + r#" + ; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and + ; break conditions more gracefully. + ; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX. + + start: + wait 0 pin 0 ; Stall until start bit is asserted + set x, 7 [10] ; Preload bit counter, then delay until halfway through + rx_bitloop: ; the first data bit (12 cycles incl wait, set). + in pins, 1 ; Shift data bit into ISR + jmp x-- rx_bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles + jmp pin good_rx_stop ; Check stop bit (should be high) + + irq 4 rel ; Either a framing error or a break. Set a sticky flag, + wait 1 pin 0 ; and wait for line to return to idle state. + jmp start ; Don't push data if we didn't see good framing. + + good_rx_stop: ; No delay before returning to start; a little slack is + in null 24 + push ; important in case the TX clock is slightly too fast. + "# + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// PIO backed Uart reciever +pub struct PioUartRx<'a, PIO: Instance, const SM: usize> { + sm_rx: StateMachine<'a, PIO, SM>, +} + +impl<'a, PIO: Instance, const SM: usize> PioUartRx<'a, PIO, SM> { + /// Configure a pio state machine to use the loaded rx program. + pub fn new( + baud: u32, + common: &mut Common<'a, PIO>, + mut sm_rx: StateMachine<'a, PIO, SM>, + rx_pin: impl PioPin, + program: &PioUartRxProgram<'a, PIO>, + ) -> Self { + let mut cfg = Config::default(); + cfg.use_program(&program.prg, &[]); + + let rx_pin = common.make_pio_pin(rx_pin); + sm_rx.set_pins(Level::High, &[&rx_pin]); + cfg.set_in_pins(&[&rx_pin]); + cfg.set_jmp_pin(&rx_pin); + sm_rx.set_pin_dirs(PioDirection::In, &[&rx_pin]); + + cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed(); + cfg.shift_in.auto_fill = false; + cfg.shift_in.direction = ShiftDirection::Right; + cfg.shift_in.threshold = 32; + cfg.fifo_join = FifoJoin::RxOnly; + sm_rx.set_config(&cfg); + sm_rx.set_enable(true); + + Self { sm_rx } + } + + /// Wait for a single u8 + pub async fn read_u8(&mut self) -> u8 { + self.sm_rx.rx().wait_pull().await as u8 + } +} + +impl ErrorType for PioUartRx<'_, PIO, SM> { + type Error = Infallible; +} + +impl Read for PioUartRx<'_, PIO, SM> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + let mut i = 0; + while i < buf.len() { + buf[i] = self.read_u8().await; + i += 1; + } + Ok(i) + } +} diff --git a/embassy-rp/src/pio_programs/ws2812.rs b/embassy-rp/src/pio_programs/ws2812.rs new file mode 100644 index 000000000..3fc7e1017 --- /dev/null +++ b/embassy-rp/src/pio_programs/ws2812.rs @@ -0,0 +1,118 @@ +//! [ws2812](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) + +use crate::{ + clocks::clk_sys_freq, + dma::{AnyChannel, Channel}, + into_ref, + pio::{Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine}, + Peripheral, PeripheralRef, +}; +use embassy_time::Timer; +use fixed::types::U24F8; +use smart_leds::RGB8; + +const T1: u8 = 2; // start bit +const T2: u8 = 5; // data bit +const T3: u8 = 3; // stop bit +const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32; + +/// This struct represents a ws2812 program loaded into pio instruction memory. +pub struct PioWs2812Program<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioWs2812Program<'a, PIO> { + /// Load the ws2812 program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let side_set = pio::SideSet::new(false, 1, false); + let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set); + + let mut wrap_target = a.label(); + let mut wrap_source = a.label(); + let mut do_zero = a.label(); + a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0); + a.bind(&mut wrap_target); + // Do stop bit + a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0); + // Do start bit + a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1); + // Do data bit = 1 + a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1); + a.bind(&mut do_zero); + // Do data bit = 0 + a.nop_with_delay_and_side_set(T2 - 1, 0); + a.bind(&mut wrap_source); + + let prg = a.assemble_with_wrap(wrap_source, wrap_target); + let prg = common.load_program(&prg); + + Self { prg } + } +} + +/// Pio backed ws2812 driver +/// Const N is the number of ws2812 leds attached to this pin +pub struct PioWs2812<'d, P: Instance, const S: usize, const N: usize> { + dma: PeripheralRef<'d, AnyChannel>, + sm: StateMachine<'d, P, S>, +} + +impl<'d, P: Instance, const S: usize, const N: usize> PioWs2812<'d, P, S, N> { + /// Configure a pio state machine to use the loaded ws2812 program. + pub fn new( + pio: &mut Common<'d, P>, + mut sm: StateMachine<'d, P, S>, + dma: impl Peripheral

+ 'd, + pin: impl PioPin, + program: &PioWs2812Program<'d, P>, + ) -> Self { + into_ref!(dma); + + // Setup sm0 + let mut cfg = Config::default(); + + // Pin config + let out_pin = pio.make_pio_pin(pin); + cfg.set_out_pins(&[&out_pin]); + cfg.set_set_pins(&[&out_pin]); + + cfg.use_program(&program.prg, &[&out_pin]); + + // Clock config, measured in kHz to avoid overflows + let clock_freq = U24F8::from_num(clk_sys_freq() / 1000); + let ws2812_freq = U24F8::from_num(800); + let bit_freq = ws2812_freq * CYCLES_PER_BIT; + cfg.clock_divider = clock_freq / bit_freq; + + // FIFO config + cfg.fifo_join = FifoJoin::TxOnly; + cfg.shift_out = ShiftConfig { + auto_fill: true, + threshold: 24, + direction: ShiftDirection::Left, + }; + + sm.set_config(&cfg); + sm.set_enable(true); + + Self { + dma: dma.map_into(), + sm, + } + } + + /// Write a buffer of [smart_leds::RGB8] to the ws2812 string + pub async fn write(&mut self, colors: &[RGB8; N]) { + // Precompute the word bytes from the colors + let mut words = [0u32; N]; + for i in 0..N { + let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8); + words[i] = word; + } + + // DMA transfer + self.sm.tx().dma_push(self.dma.reborrow(), &words).await; + + Timer::after_micros(55).await; + } +} diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index 04b4c6317..264b40750 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -42,7 +42,7 @@ embedded-graphics = "0.7.1" st7789 = "0.6.1" display-interface = "0.4.1" byte-slice-cast = { version = "1.2.0", default-features = false } -smart-leds = "0.3.0" +smart-leds = "0.4.0" heapless = "0.8" usbd-hid = "0.8.1" diff --git a/examples/rp/src/bin/pio_hd44780.rs b/examples/rp/src/bin/pio_hd44780.rs index 6c02630e0..164e6f8d3 100644 --- a/examples/rp/src/bin/pio_hd44780.rs +++ b/examples/rp/src/bin/pio_hd44780.rs @@ -7,13 +7,11 @@ use core::fmt::Write; use embassy_executor::Spawner; -use embassy_rp::dma::{AnyChannel, Channel}; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{ - Config, Direction, FifoJoin, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, -}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::hd44780::{PioHD44780, PioHD44780CommandSequenceProgram, PioHD44780CommandWordProgram}; use embassy_rp::pwm::{self, Pwm}; -use embassy_rp::{bind_interrupts, into_ref, Peripheral, PeripheralRef}; use embassy_time::{Instant, Timer}; use {defmt_rtt as _, panic_probe as _}; @@ -43,8 +41,27 @@ async fn main(_spawner: Spawner) { c }); - let mut hd = HD44780::new( - p.PIO0, Irqs, p.DMA_CH3, p.PIN_0, p.PIN_1, p.PIN_2, p.PIN_3, p.PIN_4, p.PIN_5, p.PIN_6, + let Pio { + mut common, sm0, irq0, .. + } = Pio::new(p.PIO0, Irqs); + + let word_prg = PioHD44780CommandWordProgram::new(&mut common); + let seq_prg = PioHD44780CommandSequenceProgram::new(&mut common); + + let mut hd = PioHD44780::new( + &mut common, + sm0, + irq0, + p.DMA_CH3, + p.PIN_0, + p.PIN_1, + p.PIN_2, + p.PIN_3, + p.PIN_4, + p.PIN_5, + p.PIN_6, + &word_prg, + &seq_prg, ) .await; @@ -68,173 +85,3 @@ async fn main(_spawner: Spawner) { Timer::after_secs(1).await; } } - -pub struct HD44780<'l> { - dma: PeripheralRef<'l, AnyChannel>, - sm: StateMachine<'l, PIO0, 0>, - - buf: [u8; 40], -} - -impl<'l> HD44780<'l> { - pub async fn new( - pio: impl Peripheral

+ 'l, - irq: Irqs, - dma: impl Peripheral

+ 'l, - rs: impl PioPin, - rw: impl PioPin, - e: impl PioPin, - db4: impl PioPin, - db5: impl PioPin, - db6: impl PioPin, - db7: impl PioPin, - ) -> HD44780<'l> { - into_ref!(dma); - - let Pio { - mut common, - mut irq0, - mut sm0, - .. - } = Pio::new(pio, irq); - - // takes command words ( <0:4>) - let prg = pio_proc::pio_asm!( - r#" - .side_set 1 opt - .origin 20 - - loop: - out x, 24 - delay: - jmp x--, delay - out pins, 4 side 1 - out null, 4 side 0 - jmp !osre, loop - irq 0 - "#, - ); - - let rs = common.make_pio_pin(rs); - let rw = common.make_pio_pin(rw); - let e = common.make_pio_pin(e); - let db4 = common.make_pio_pin(db4); - let db5 = common.make_pio_pin(db5); - let db6 = common.make_pio_pin(db6); - let db7 = common.make_pio_pin(db7); - - sm0.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]); - - let mut cfg = Config::default(); - cfg.use_program(&common.load_program(&prg.program), &[&e]); - cfg.clock_divider = 125u8.into(); - cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); - cfg.shift_out = ShiftConfig { - auto_fill: true, - direction: ShiftDirection::Left, - threshold: 32, - }; - cfg.fifo_join = FifoJoin::TxOnly; - sm0.set_config(&cfg); - - sm0.set_enable(true); - // init to 8 bit thrice - sm0.tx().push((50000 << 8) | 0x30); - sm0.tx().push((5000 << 8) | 0x30); - sm0.tx().push((200 << 8) | 0x30); - // init 4 bit - sm0.tx().push((200 << 8) | 0x20); - // set font and lines - sm0.tx().push((50 << 8) | 0x20); - sm0.tx().push(0b1100_0000); - - irq0.wait().await; - sm0.set_enable(false); - - // takes command sequences ( , data...) - // many side sets are only there to free up a delay bit! - let prg = pio_proc::pio_asm!( - r#" - .origin 27 - .side_set 1 - - .wrap_target - pull side 0 - out x 1 side 0 ; !rs - out y 7 side 0 ; #data - 1 - - ; rs/rw to e: >= 60ns - ; e high time: >= 500ns - ; e low time: >= 500ns - ; read data valid after e falling: ~5ns - ; write data hold after e falling: ~10ns - - loop: - pull side 0 - jmp !x data side 0 - command: - set pins 0b00 side 0 - jmp shift side 0 - data: - set pins 0b01 side 0 - shift: - out pins 4 side 1 [9] - nop side 0 [9] - out pins 4 side 1 [9] - mov osr null side 0 [7] - out pindirs 4 side 0 - set pins 0b10 side 0 - busy: - nop side 1 [9] - jmp pin more side 0 [9] - mov osr ~osr side 1 [9] - nop side 0 [4] - out pindirs 4 side 0 - jmp y-- loop side 0 - .wrap - more: - nop side 1 [9] - jmp busy side 0 [9] - "# - ); - - let mut cfg = Config::default(); - cfg.use_program(&common.load_program(&prg.program), &[&e]); - cfg.clock_divider = 8u8.into(); // ~64ns/insn - cfg.set_jmp_pin(&db7); - cfg.set_set_pins(&[&rs, &rw]); - cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); - cfg.shift_out.direction = ShiftDirection::Left; - cfg.fifo_join = FifoJoin::TxOnly; - sm0.set_config(&cfg); - - sm0.set_enable(true); - - // display on and cursor on and blinking, reset display - sm0.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1]).await; - - Self { - dma: dma.map_into(), - sm: sm0, - buf: [0x20; 40], - } - } - - pub async fn add_line(&mut self, s: &[u8]) { - // move cursor to 0:0, prepare 16 characters - self.buf[..3].copy_from_slice(&[0x80, 0x80, 15]); - // move line 2 up - self.buf.copy_within(22..38, 3); - // move cursor to 1:0, prepare 16 characters - self.buf[19..22].copy_from_slice(&[0x80, 0xc0, 15]); - // file line 2 with spaces - self.buf[22..38].fill(0x20); - // copy input line - let len = s.len().min(16); - self.buf[22..22 + len].copy_from_slice(&s[0..len]); - // set cursor to 1:15 - self.buf[38..].copy_from_slice(&[0x80, 0xcf]); - - self.sm.tx().dma_push(self.dma.reborrow(), &self.buf).await; - } -} diff --git a/examples/rp/src/bin/pio_i2s.rs b/examples/rp/src/bin/pio_i2s.rs index cf60e5b30..447100ddf 100644 --- a/examples/rp/src/bin/pio_i2s.rs +++ b/examples/rp/src/bin/pio_i2s.rs @@ -13,10 +13,10 @@ use core::mem; use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Config, FifoJoin, InterruptHandler, Pio, ShiftConfig, ShiftDirection}; -use embassy_rp::{bind_interrupts, Peripheral}; -use fixed::traits::ToFixed; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::i2s::{PioI2sOut, PioI2sOutProgram}; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -25,61 +25,32 @@ bind_interrupts!(struct Irqs { }); const SAMPLE_RATE: u32 = 48_000; +const BIT_DEPTH: u32 = 16; +const CHANNELS: u32 = 2; #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut p = embassy_rp::init(Default::default()); // Setup pio state machine for i2s output - let mut pio = Pio::new(p.PIO0, Irqs); - - #[rustfmt::skip] - let pio_program = pio_proc::pio_asm!( - ".side_set 2", - " set x, 14 side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock - "left_data:", - " out pins, 1 side 0b00", - " jmp x-- left_data side 0b01", - " out pins 1 side 0b10", - " set x, 14 side 0b11", - "right_data:", - " out pins 1 side 0b10", - " jmp x-- right_data side 0b11", - " out pins 1 side 0b00", - ); + let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); let bit_clock_pin = p.PIN_18; let left_right_clock_pin = p.PIN_19; let data_pin = p.PIN_20; - let data_pin = pio.common.make_pio_pin(data_pin); - let bit_clock_pin = pio.common.make_pio_pin(bit_clock_pin); - let left_right_clock_pin = pio.common.make_pio_pin(left_right_clock_pin); - - let cfg = { - let mut cfg = Config::default(); - cfg.use_program( - &pio.common.load_program(&pio_program.program), - &[&bit_clock_pin, &left_right_clock_pin], - ); - cfg.set_out_pins(&[&data_pin]); - const BIT_DEPTH: u32 = 16; - const CHANNELS: u32 = 2; - let clock_frequency = SAMPLE_RATE * BIT_DEPTH * CHANNELS; - cfg.clock_divider = (125_000_000. / clock_frequency as f64 / 2.).to_fixed(); - cfg.shift_out = ShiftConfig { - threshold: 32, - direction: ShiftDirection::Left, - auto_fill: true, - }; - // join fifos to have twice the time to start the next dma transfer - cfg.fifo_join = FifoJoin::TxOnly; - cfg - }; - pio.sm0.set_config(&cfg); - pio.sm0.set_pin_dirs( - embassy_rp::pio::Direction::Out, - &[&data_pin, &left_right_clock_pin, &bit_clock_pin], + let program = PioI2sOutProgram::new(&mut common); + let mut i2s = PioI2sOut::new( + &mut common, + sm0, + p.DMA_CH0, + data_pin, + bit_clock_pin, + left_right_clock_pin, + SAMPLE_RATE, + BIT_DEPTH, + CHANNELS, + &program, ); // create two audio buffers (back and front) which will take turns being @@ -90,17 +61,13 @@ async fn main(_spawner: Spawner) { let (mut back_buffer, mut front_buffer) = dma_buffer.split_at_mut(BUFFER_SIZE); // start pio state machine - pio.sm0.set_enable(true); - let tx = pio.sm0.tx(); - let mut dma_ref = p.DMA_CH0.into_ref(); - let mut fade_value: i32 = 0; let mut phase: i32 = 0; loop { // trigger transfer of front buffer data to the pio fifo // but don't await the returned future, yet - let dma_future = tx.dma_push(dma_ref.reborrow(), front_buffer); + let dma_future = i2s.write(front_buffer); // fade in audio when bootsel is pressed let fade_target = if p.BOOTSEL.is_pressed() { i32::MAX } else { 0 }; diff --git a/examples/rp/src/bin/pio_onewire.rs b/examples/rp/src/bin/pio_onewire.rs index 5076101ec..991510851 100644 --- a/examples/rp/src/bin/pio_onewire.rs +++ b/examples/rp/src/bin/pio_onewire.rs @@ -6,7 +6,8 @@ use defmt::*; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{self, Common, Config, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine}; +use embassy_rp::pio::{self, InterruptHandler, Pio}; +use embassy_rp::pio_programs::onewire::{PioOneWire, PioOneWireProgram}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -18,7 +19,11 @@ bind_interrupts!(struct Irqs { async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); let mut pio = Pio::new(p.PIO0, Irqs); - let mut sensor = Ds18b20::new(&mut pio.common, pio.sm0, p.PIN_2); + + let prg = PioOneWireProgram::new(&mut pio.common); + let onewire = PioOneWire::new(&mut pio.common, pio.sm0, p.PIN_2, &prg); + + let mut sensor = Ds18b20::new(onewire); loop { sensor.start().await; // Start a new measurement @@ -33,89 +38,12 @@ async fn main(_spawner: Spawner) { /// DS18B20 temperature sensor driver pub struct Ds18b20<'d, PIO: pio::Instance, const SM: usize> { - sm: StateMachine<'d, PIO, SM>, + wire: PioOneWire<'d, PIO, SM>, } impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> { - /// Create a new instance the driver - pub fn new(common: &mut Common<'d, PIO>, mut sm: StateMachine<'d, PIO, SM>, pin: impl PioPin) -> Self { - let prg = pio_proc::pio_asm!( - r#" - .wrap_target - again: - pull block - mov x, osr - jmp !x, read - write: - set pindirs, 1 - set pins, 0 - loop1: - jmp x--,loop1 - set pindirs, 0 [31] - wait 1 pin 0 [31] - pull block - mov x, osr - bytes1: - pull block - set y, 7 - set pindirs, 1 - bit1: - set pins, 0 [1] - out pins,1 [31] - set pins, 1 [20] - jmp y--,bit1 - jmp x--,bytes1 - set pindirs, 0 [31] - jmp again - read: - pull block - mov x, osr - bytes2: - set y, 7 - bit2: - set pindirs, 1 - set pins, 0 [1] - set pindirs, 0 [5] - in pins,1 [10] - jmp y--,bit2 - jmp x--,bytes2 - .wrap - "#, - ); - - let pin = common.make_pio_pin(pin); - let mut cfg = Config::default(); - cfg.use_program(&common.load_program(&prg.program), &[]); - cfg.set_out_pins(&[&pin]); - cfg.set_in_pins(&[&pin]); - cfg.set_set_pins(&[&pin]); - cfg.shift_in = ShiftConfig { - auto_fill: true, - direction: ShiftDirection::Right, - threshold: 8, - }; - cfg.clock_divider = 255_u8.into(); - sm.set_config(&cfg); - sm.set_enable(true); - Self { sm } - } - - /// Write bytes over the wire - async fn write_bytes(&mut self, bytes: &[u8]) { - self.sm.tx().wait_push(250).await; - self.sm.tx().wait_push(bytes.len() as u32 - 1).await; - for b in bytes { - self.sm.tx().wait_push(*b as u32).await; - } - } - - /// Read bytes from the wire - async fn read_bytes(&mut self, bytes: &mut [u8]) { - self.sm.tx().wait_push(0).await; - self.sm.tx().wait_push(bytes.len() as u32 - 1).await; - for b in bytes.iter_mut() { - *b = (self.sm.rx().wait_pull().await >> 24) as u8; - } + pub fn new(wire: PioOneWire<'d, PIO, SM>) -> Self { + Self { wire } } /// Calculate CRC8 of the data @@ -139,14 +67,14 @@ impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> { /// Start a new measurement. Allow at least 1000ms before getting `temperature`. pub async fn start(&mut self) { - self.write_bytes(&[0xCC, 0x44]).await; + self.wire.write_bytes(&[0xCC, 0x44]).await; } /// Read the temperature. Ensure >1000ms has passed since `start` before calling this. pub async fn temperature(&mut self) -> Result { - self.write_bytes(&[0xCC, 0xBE]).await; + self.wire.write_bytes(&[0xCC, 0xBE]).await; let mut data = [0; 9]; - self.read_bytes(&mut data).await; + self.wire.read_bytes(&mut data).await; match Self::crc8(&data) == 0 { true => Ok(((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.), false => Err(()), diff --git a/examples/rp/src/bin/pio_pwm.rs b/examples/rp/src/bin/pio_pwm.rs index 23d63d435..7eabb2289 100644 --- a/examples/rp/src/bin/pio_pwm.rs +++ b/examples/rp/src/bin/pio_pwm.rs @@ -5,12 +5,11 @@ use core::time::Duration; use embassy_executor::Spawner; -use embassy_rp::gpio::Level; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; -use embassy_rp::{bind_interrupts, clocks}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; use embassy_time::Timer; -use pio::InstructionOperands; use {defmt_rtt as _, panic_probe as _}; const REFRESH_INTERVAL: u64 = 20000; @@ -19,93 +18,14 @@ bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub fn to_pio_cycles(duration: Duration) -> u32 { - (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow -} - -pub struct PwmPio<'d, T: Instance, const SM: usize> { - sm: StateMachine<'d, T, SM>, -} - -impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { - pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self { - let prg = pio_proc::pio_asm!( - ".side_set 1 opt" - "pull noblock side 0" - "mov x, osr" - "mov y, isr" - "countloop:" - "jmp x!=y noset" - "jmp skip side 1" - "noset:" - "nop" - "skip:" - "jmp y-- countloop" - ); - - pio.load_program(&prg.program); - let pin = pio.make_pio_pin(pin); - sm.set_pins(Level::High, &[&pin]); - sm.set_pin_dirs(Direction::Out, &[&pin]); - - let mut cfg = Config::default(); - cfg.use_program(&pio.load_program(&prg.program), &[&pin]); - - sm.set_config(&cfg); - - Self { sm } - } - - pub fn start(&mut self) { - self.sm.set_enable(true); - } - - pub fn stop(&mut self) { - self.sm.set_enable(false); - } - - pub fn set_period(&mut self, duration: Duration) { - let is_enabled = self.sm.is_enabled(); - while !self.sm.tx().empty() {} // Make sure that the queue is empty - self.sm.set_enable(false); - self.sm.tx().push(to_pio_cycles(duration)); - unsafe { - self.sm.exec_instr( - InstructionOperands::PULL { - if_empty: false, - block: false, - } - .encode(), - ); - self.sm.exec_instr( - InstructionOperands::OUT { - destination: ::pio::OutDestination::ISR, - bit_count: 32, - } - .encode(), - ); - }; - if is_enabled { - self.sm.set_enable(true) // Enable if previously enabled - } - } - - pub fn set_level(&mut self, level: u32) { - self.sm.tx().push(level); - } - - pub fn write(&mut self, duration: Duration) { - self.set_level(to_pio_cycles(duration)); - } -} - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); // Note that PIN_25 is the led pin on the Pico - let mut pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_25); + let prg = PioPwmProgram::new(&mut common); + let mut pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_25, &prg); pwm_pio.set_period(Duration::from_micros(REFRESH_INTERVAL)); pwm_pio.start(); diff --git a/examples/rp/src/bin/pio_rotary_encoder.rs b/examples/rp/src/bin/pio_rotary_encoder.rs index 58bdadbc0..a7ecc8d0e 100644 --- a/examples/rp/src/bin/pio_rotary_encoder.rs +++ b/examples/rp/src/bin/pio_rotary_encoder.rs @@ -5,70 +5,20 @@ use defmt::info; use embassy_executor::Spawner; -use embassy_rp::gpio::Pull; use embassy_rp::peripherals::PIO0; -use embassy_rp::{bind_interrupts, pio}; -use fixed::traits::ToFixed; -use pio::{Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftDirection, StateMachine}; +use embassy_rp::{ + bind_interrupts, + pio::{InterruptHandler, Pio}, + pio_programs::rotary_encoder::{Direction, PioEncoder, PioEncoderProgram}, +}; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub struct PioEncoder<'d, T: Instance, const SM: usize> { - sm: StateMachine<'d, T, SM>, -} - -impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> { - pub fn new( - pio: &mut Common<'d, T>, - mut sm: StateMachine<'d, T, SM>, - pin_a: impl PioPin, - pin_b: impl PioPin, - ) -> Self { - let mut pin_a = pio.make_pio_pin(pin_a); - let mut pin_b = pio.make_pio_pin(pin_b); - pin_a.set_pull(Pull::Up); - pin_b.set_pull(Pull::Up); - sm.set_pin_dirs(pio::Direction::In, &[&pin_a, &pin_b]); - - let prg = pio_proc::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",); - - let mut cfg = Config::default(); - cfg.set_in_pins(&[&pin_a, &pin_b]); - cfg.fifo_join = FifoJoin::RxOnly; - cfg.shift_in.direction = ShiftDirection::Left; - cfg.clock_divider = 10_000.to_fixed(); - cfg.use_program(&pio.load_program(&prg.program), &[]); - sm.set_config(&cfg); - sm.set_enable(true); - Self { sm } - } - - pub async fn read(&mut self) -> Direction { - loop { - match self.sm.rx().wait_pull().await { - 0 => return Direction::CounterClockwise, - 1 => return Direction::Clockwise, - _ => {} - } - } - } -} - -pub enum Direction { - Clockwise, - CounterClockwise, -} - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = embassy_rp::init(Default::default()); - let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); - - let mut encoder = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5); - +#[embassy_executor::task] +async fn encoder_0(mut encoder: PioEncoder<'static, PIO0, 0>) { let mut count = 0; loop { info!("Count: {}", count); @@ -78,3 +28,30 @@ async fn main(_spawner: Spawner) { }; } } + +#[embassy_executor::task] +async fn encoder_1(mut encoder: PioEncoder<'static, PIO0, 1>) { + let mut count = 0; + loop { + info!("Count: {}", count); + count += match encoder.read().await { + Direction::Clockwise => 1, + Direction::CounterClockwise => -1, + }; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { + mut common, sm0, sm1, .. + } = Pio::new(p.PIO0, Irqs); + + let prg = PioEncoderProgram::new(&mut common); + let encoder0 = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5, &prg); + let encoder1 = PioEncoder::new(&mut common, sm1, p.PIN_6, p.PIN_7, &prg); + + spawner.must_spawn(encoder_0(encoder0)); + spawner.must_spawn(encoder_1(encoder1)); +} diff --git a/examples/rp/src/bin/pio_servo.rs b/examples/rp/src/bin/pio_servo.rs index a79540479..c52ee7492 100644 --- a/examples/rp/src/bin/pio_servo.rs +++ b/examples/rp/src/bin/pio_servo.rs @@ -5,12 +5,11 @@ use core::time::Duration; use embassy_executor::Spawner; -use embassy_rp::gpio::Level; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; -use embassy_rp::{bind_interrupts, clocks}; +use embassy_rp::pio::{Instance, InterruptHandler, Pio}; +use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; use embassy_time::Timer; -use pio::InstructionOperands; use {defmt_rtt as _, panic_probe as _}; const DEFAULT_MIN_PULSE_WIDTH: u64 = 1000; // uncalibrated default, the shortest duty cycle sent to a servo @@ -22,88 +21,8 @@ bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub fn to_pio_cycles(duration: Duration) -> u32 { - (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow -} - -pub struct PwmPio<'d, T: Instance, const SM: usize> { - sm: StateMachine<'d, T, SM>, -} - -impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { - pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self { - let prg = pio_proc::pio_asm!( - ".side_set 1 opt" - "pull noblock side 0" - "mov x, osr" - "mov y, isr" - "countloop:" - "jmp x!=y noset" - "jmp skip side 1" - "noset:" - "nop" - "skip:" - "jmp y-- countloop" - ); - - pio.load_program(&prg.program); - let pin = pio.make_pio_pin(pin); - sm.set_pins(Level::High, &[&pin]); - sm.set_pin_dirs(Direction::Out, &[&pin]); - - let mut cfg = Config::default(); - cfg.use_program(&pio.load_program(&prg.program), &[&pin]); - - sm.set_config(&cfg); - - Self { sm } - } - - pub fn start(&mut self) { - self.sm.set_enable(true); - } - - pub fn stop(&mut self) { - self.sm.set_enable(false); - } - - pub fn set_period(&mut self, duration: Duration) { - let is_enabled = self.sm.is_enabled(); - while !self.sm.tx().empty() {} // Make sure that the queue is empty - self.sm.set_enable(false); - self.sm.tx().push(to_pio_cycles(duration)); - unsafe { - self.sm.exec_instr( - InstructionOperands::PULL { - if_empty: false, - block: false, - } - .encode(), - ); - self.sm.exec_instr( - InstructionOperands::OUT { - destination: ::pio::OutDestination::ISR, - bit_count: 32, - } - .encode(), - ); - }; - if is_enabled { - self.sm.set_enable(true) // Enable if previously enabled - } - } - - pub fn set_level(&mut self, level: u32) { - self.sm.tx().push(level); - } - - pub fn write(&mut self, duration: Duration) { - self.set_level(to_pio_cycles(duration)); - } -} - pub struct ServoBuilder<'d, T: Instance, const SM: usize> { - pwm: PwmPio<'d, T, SM>, + pwm: PioPwm<'d, T, SM>, period: Duration, min_pulse_width: Duration, max_pulse_width: Duration, @@ -111,7 +30,7 @@ pub struct ServoBuilder<'d, T: Instance, const SM: usize> { } impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { - pub fn new(pwm: PwmPio<'d, T, SM>) -> Self { + pub fn new(pwm: PioPwm<'d, T, SM>) -> Self { Self { pwm, period: Duration::from_micros(REFRESH_INTERVAL), @@ -153,7 +72,7 @@ impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { } pub struct Servo<'d, T: Instance, const SM: usize> { - pwm: PwmPio<'d, T, SM>, + pwm: PioPwm<'d, T, SM>, min_pulse_width: Duration, max_pulse_width: Duration, max_degree_rotation: u64, @@ -190,7 +109,8 @@ async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); - let pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_1); + let prg = PioPwmProgram::new(&mut common); + let pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_1, &prg); let mut servo = ServoBuilder::new(pwm_pio) .set_max_degree_rotation(120) // Example of adjusting values for MG996R servo .set_min_pulse_width(Duration::from_micros(350)) // This value was detemined by a rough experiment. diff --git a/examples/rp/src/bin/pio_stepper.rs b/examples/rp/src/bin/pio_stepper.rs index 6ee45a414..3862c248b 100644 --- a/examples/rp/src/bin/pio_stepper.rs +++ b/examples/rp/src/bin/pio_stepper.rs @@ -3,143 +3,20 @@ #![no_std] #![no_main] -use core::mem::{self, MaybeUninit}; use defmt::info; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Irq, Pio, PioPin, StateMachine}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::stepper::{PioStepper, PioStepperProgram}; use embassy_time::{with_timeout, Duration, Timer}; -use fixed::traits::ToFixed; -use fixed::types::extra::U8; -use fixed::FixedU32; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub struct PioStepper<'d, T: Instance, const SM: usize> { - irq: Irq<'d, T, SM>, - sm: StateMachine<'d, T, SM>, -} - -impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { - pub fn new( - pio: &mut Common<'d, T>, - mut sm: StateMachine<'d, T, SM>, - irq: Irq<'d, T, SM>, - pin0: impl PioPin, - pin1: impl PioPin, - pin2: impl PioPin, - pin3: impl PioPin, - ) -> Self { - let prg = pio_proc::pio_asm!( - "pull block", - "mov x, osr", - "pull block", - "mov y, osr", - "jmp !x end", - "loop:", - "jmp !osre step", - "mov osr, y", - "step:", - "out pins, 4 [31]" - "jmp x-- loop", - "end:", - "irq 0 rel" - ); - let pin0 = pio.make_pio_pin(pin0); - let pin1 = pio.make_pio_pin(pin1); - let pin2 = pio.make_pio_pin(pin2); - let pin3 = pio.make_pio_pin(pin3); - sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]); - let mut cfg = Config::default(); - cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]); - cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed(); - cfg.use_program(&pio.load_program(&prg.program), &[]); - sm.set_config(&cfg); - sm.set_enable(true); - Self { irq, sm } - } - - // Set pulse frequency - pub fn set_frequency(&mut self, freq: u32) { - let clock_divider: FixedU32 = (125_000_000 / (freq * 136)).to_fixed(); - assert!(clock_divider <= 65536, "clkdiv must be <= 65536"); - assert!(clock_divider >= 1, "clkdiv must be >= 1"); - self.sm.set_clock_divider(clock_divider); - self.sm.clkdiv_restart(); - } - - // Full step, one phase - pub async fn step(&mut self, steps: i32) { - if steps > 0 { - self.run(steps, 0b1000_0100_0010_0001_1000_0100_0010_0001).await - } else { - self.run(-steps, 0b0001_0010_0100_1000_0001_0010_0100_1000).await - } - } - - // Full step, two phase - pub async fn step2(&mut self, steps: i32) { - if steps > 0 { - self.run(steps, 0b1001_1100_0110_0011_1001_1100_0110_0011).await - } else { - self.run(-steps, 0b0011_0110_1100_1001_0011_0110_1100_1001).await - } - } - - // Half step - pub async fn step_half(&mut self, steps: i32) { - if steps > 0 { - self.run(steps, 0b1001_1000_1100_0100_0110_0010_0011_0001).await - } else { - self.run(-steps, 0b0001_0011_0010_0110_0100_1100_1000_1001).await - } - } - - async fn run(&mut self, steps: i32, pattern: u32) { - self.sm.tx().wait_push(steps as u32).await; - self.sm.tx().wait_push(pattern).await; - let drop = OnDrop::new(|| { - self.sm.clear_fifos(); - unsafe { - self.sm.exec_instr( - pio::InstructionOperands::JMP { - address: 0, - condition: pio::JmpCondition::Always, - } - .encode(), - ); - } - }); - self.irq.wait().await; - drop.defuse(); - } -} - -struct OnDrop { - f: MaybeUninit, -} - -impl OnDrop { - pub fn new(f: F) -> Self { - Self { f: MaybeUninit::new(f) } - } - - pub fn defuse(self) { - mem::forget(self) - } -} - -impl Drop for OnDrop { - fn drop(&mut self) { - unsafe { self.f.as_ptr().read()() } - } -} - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); @@ -147,14 +24,18 @@ async fn main(_spawner: Spawner) { mut common, irq0, sm0, .. } = Pio::new(p.PIO0, Irqs); - let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7); + let prg = PioStepperProgram::new(&mut common); + let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7, &prg); stepper.set_frequency(120); loop { info!("CW full steps"); stepper.step(1000).await; info!("CCW full steps, drop after 1 sec"); - if let Err(_) = with_timeout(Duration::from_secs(1), stepper.step(-i32::MAX)).await { + if with_timeout(Duration::from_secs(1), stepper.step(-i32::MAX)) + .await + .is_err() + { info!("Time's up!"); Timer::after(Duration::from_secs(1)).await; } diff --git a/examples/rp/src/bin/pio_uart.rs b/examples/rp/src/bin/pio_uart.rs index 53b696309..b9e01b0ac 100644 --- a/examples/rp/src/bin/pio_uart.rs +++ b/examples/rp/src/bin/pio_uart.rs @@ -15,7 +15,8 @@ use embassy_executor::Spawner; use embassy_futures::join::{join, join3}; use embassy_rp::bind_interrupts; use embassy_rp::peripherals::{PIO0, USB}; -use embassy_rp::pio::InterruptHandler as PioInterruptHandler; +use embassy_rp::pio; +use embassy_rp::pio_programs::uart::{PioUartRx, PioUartRxProgram, PioUartTx, PioUartTxProgram}; use embassy_rp::usb::{Driver, Instance, InterruptHandler}; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::pipe::Pipe; @@ -25,13 +26,11 @@ use embassy_usb::{Builder, Config}; use embedded_io_async::{Read, Write}; use {defmt_rtt as _, panic_probe as _}; -use crate::uart::PioUart; -use crate::uart_rx::PioUartRx; -use crate::uart_tx::PioUartTx; +//use crate::uart::PioUart; bind_interrupts!(struct Irqs { USBCTRL_IRQ => InterruptHandler; - PIO0_IRQ_0 => PioInterruptHandler; + PIO0_IRQ_0 => pio::InterruptHandler; }); #[embassy_executor::main] @@ -85,8 +84,15 @@ async fn main(_spawner: Spawner) { let usb_fut = usb.run(); // PIO UART setup - let uart = PioUart::new(9600, p.PIO0, p.PIN_4, p.PIN_5); - let (mut uart_tx, mut uart_rx) = uart.split(); + let pio::Pio { + mut common, sm0, sm1, .. + } = pio::Pio::new(p.PIO0, Irqs); + + let tx_program = PioUartTxProgram::new(&mut common); + let mut uart_tx = PioUartTx::new(9600, &mut common, sm0, p.PIN_4, &tx_program); + + let rx_program = PioUartRxProgram::new(&mut common); + let mut uart_rx = PioUartRx::new(9600, &mut common, sm1, p.PIN_5, &rx_program); // Pipe setup let mut usb_pipe: Pipe = Pipe::new(); @@ -163,8 +169,8 @@ async fn usb_write<'d, T: Instance + 'd>( } /// Read from the UART and write it to the USB TX pipe -async fn uart_read( - uart_rx: &mut PioUartRx<'_>, +async fn uart_read( + uart_rx: &mut PioUartRx<'_, PIO, SM>, usb_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>, ) -> ! { let mut buf = [0; 64]; @@ -180,8 +186,8 @@ async fn uart_read( } /// Read from the UART TX pipe and write it to the UART -async fn uart_write( - uart_tx: &mut PioUartTx<'_>, +async fn uart_write( + uart_tx: &mut PioUartTx<'_, PIO, SM>, uart_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>, ) -> ! { let mut buf = [0; 64]; @@ -192,197 +198,3 @@ async fn uart_write( let _ = uart_tx.write(&data).await; } } - -mod uart { - use embassy_rp::peripherals::PIO0; - use embassy_rp::pio::{Pio, PioPin}; - use embassy_rp::Peripheral; - - use crate::uart_rx::PioUartRx; - use crate::uart_tx::PioUartTx; - use crate::Irqs; - - pub struct PioUart<'a> { - tx: PioUartTx<'a>, - rx: PioUartRx<'a>, - } - - impl<'a> PioUart<'a> { - pub fn new( - baud: u64, - pio: impl Peripheral

+ 'a, - tx_pin: impl PioPin, - rx_pin: impl PioPin, - ) -> PioUart<'a> { - let Pio { - mut common, sm0, sm1, .. - } = Pio::new(pio, Irqs); - - let tx = PioUartTx::new(&mut common, sm0, tx_pin, baud); - let rx = PioUartRx::new(&mut common, sm1, rx_pin, baud); - - PioUart { tx, rx } - } - - pub fn split(self) -> (PioUartTx<'a>, PioUartRx<'a>) { - (self.tx, self.rx) - } - } -} - -mod uart_tx { - use core::convert::Infallible; - - use embassy_rp::gpio::Level; - use embassy_rp::peripherals::PIO0; - use embassy_rp::pio::{Common, Config, Direction, FifoJoin, PioPin, ShiftDirection, StateMachine}; - use embedded_io_async::{ErrorType, Write}; - use fixed::traits::ToFixed; - use fixed_macro::types::U56F8; - - pub struct PioUartTx<'a> { - sm_tx: StateMachine<'a, PIO0, 0>, - } - - impl<'a> PioUartTx<'a> { - pub fn new( - common: &mut Common<'a, PIO0>, - mut sm_tx: StateMachine<'a, PIO0, 0>, - tx_pin: impl PioPin, - baud: u64, - ) -> Self { - let prg = pio_proc::pio_asm!( - r#" - .side_set 1 opt - - ; An 8n1 UART transmit program. - ; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin. - - pull side 1 [7] ; Assert stop bit, or stall with line in idle state - set x, 7 side 0 [7] ; Preload bit counter, assert start bit for 8 clocks - bitloop: ; This loop will run 8 times (8n1 UART) - out pins, 1 ; Shift 1 bit from OSR to the first OUT pin - jmp x-- bitloop [6] ; Each loop iteration is 8 cycles. - "# - ); - let tx_pin = common.make_pio_pin(tx_pin); - sm_tx.set_pins(Level::High, &[&tx_pin]); - sm_tx.set_pin_dirs(Direction::Out, &[&tx_pin]); - - let mut cfg = Config::default(); - - cfg.set_out_pins(&[&tx_pin]); - cfg.use_program(&common.load_program(&prg.program), &[&tx_pin]); - cfg.shift_out.auto_fill = false; - cfg.shift_out.direction = ShiftDirection::Right; - cfg.fifo_join = FifoJoin::TxOnly; - cfg.clock_divider = (U56F8!(125_000_000) / (8 * baud)).to_fixed(); - sm_tx.set_config(&cfg); - sm_tx.set_enable(true); - - Self { sm_tx } - } - - pub async fn write_u8(&mut self, data: u8) { - self.sm_tx.tx().wait_push(data as u32).await; - } - } - - impl ErrorType for PioUartTx<'_> { - type Error = Infallible; - } - - impl Write for PioUartTx<'_> { - async fn write(&mut self, buf: &[u8]) -> Result { - for byte in buf { - self.write_u8(*byte).await; - } - Ok(buf.len()) - } - } -} - -mod uart_rx { - use core::convert::Infallible; - - use embassy_rp::gpio::Level; - use embassy_rp::peripherals::PIO0; - use embassy_rp::pio::{Common, Config, Direction, FifoJoin, PioPin, ShiftDirection, StateMachine}; - use embedded_io_async::{ErrorType, Read}; - use fixed::traits::ToFixed; - use fixed_macro::types::U56F8; - - pub struct PioUartRx<'a> { - sm_rx: StateMachine<'a, PIO0, 1>, - } - - impl<'a> PioUartRx<'a> { - pub fn new( - common: &mut Common<'a, PIO0>, - mut sm_rx: StateMachine<'a, PIO0, 1>, - rx_pin: impl PioPin, - baud: u64, - ) -> Self { - let prg = pio_proc::pio_asm!( - r#" - ; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and - ; break conditions more gracefully. - ; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX. - - start: - wait 0 pin 0 ; Stall until start bit is asserted - set x, 7 [10] ; Preload bit counter, then delay until halfway through - rx_bitloop: ; the first data bit (12 cycles incl wait, set). - in pins, 1 ; Shift data bit into ISR - jmp x-- rx_bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles - jmp pin good_rx_stop ; Check stop bit (should be high) - - irq 4 rel ; Either a framing error or a break. Set a sticky flag, - wait 1 pin 0 ; and wait for line to return to idle state. - jmp start ; Don't push data if we didn't see good framing. - - good_rx_stop: ; No delay before returning to start; a little slack is - in null 24 - push ; important in case the TX clock is slightly too fast. - "# - ); - let mut cfg = Config::default(); - cfg.use_program(&common.load_program(&prg.program), &[]); - - let rx_pin = common.make_pio_pin(rx_pin); - sm_rx.set_pins(Level::High, &[&rx_pin]); - cfg.set_in_pins(&[&rx_pin]); - cfg.set_jmp_pin(&rx_pin); - sm_rx.set_pin_dirs(Direction::In, &[&rx_pin]); - - cfg.clock_divider = (U56F8!(125_000_000) / (8 * baud)).to_fixed(); - cfg.shift_in.auto_fill = false; - cfg.shift_in.direction = ShiftDirection::Right; - cfg.shift_in.threshold = 32; - cfg.fifo_join = FifoJoin::RxOnly; - sm_rx.set_config(&cfg); - sm_rx.set_enable(true); - - Self { sm_rx } - } - - pub async fn read_u8(&mut self) -> u8 { - self.sm_rx.rx().wait_pull().await as u8 - } - } - - impl ErrorType for PioUartRx<'_> { - type Error = Infallible; - } - - impl Read for PioUartRx<'_> { - async fn read(&mut self, buf: &mut [u8]) -> Result { - let mut i = 0; - while i < buf.len() { - buf[i] = self.read_u8().await; - i += 1; - } - Ok(i) - } - } -} diff --git a/examples/rp/src/bin/pio_ws2812.rs b/examples/rp/src/bin/pio_ws2812.rs index ac145933c..d1fcfc471 100644 --- a/examples/rp/src/bin/pio_ws2812.rs +++ b/examples/rp/src/bin/pio_ws2812.rs @@ -6,15 +6,11 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_rp::dma::{AnyChannel, Channel}; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{ - Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, -}; -use embassy_rp::{bind_interrupts, clocks, into_ref, Peripheral, PeripheralRef}; -use embassy_time::{Duration, Ticker, Timer}; -use fixed::types::U24F8; -use fixed_macro::fixed; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::ws2812::{PioWs2812, PioWs2812Program}; +use embassy_time::{Duration, Ticker}; use smart_leds::RGB8; use {defmt_rtt as _, panic_probe as _}; @@ -22,96 +18,6 @@ bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub struct Ws2812<'d, P: Instance, const S: usize, const N: usize> { - dma: PeripheralRef<'d, AnyChannel>, - sm: StateMachine<'d, P, S>, -} - -impl<'d, P: Instance, const S: usize, const N: usize> Ws2812<'d, P, S, N> { - pub fn new( - pio: &mut Common<'d, P>, - mut sm: StateMachine<'d, P, S>, - dma: impl Peripheral

+ 'd, - pin: impl PioPin, - ) -> Self { - into_ref!(dma); - - // Setup sm0 - - // prepare the PIO program - let side_set = pio::SideSet::new(false, 1, false); - let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set); - - const T1: u8 = 2; // start bit - const T2: u8 = 5; // data bit - const T3: u8 = 3; // stop bit - const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32; - - let mut wrap_target = a.label(); - let mut wrap_source = a.label(); - let mut do_zero = a.label(); - a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0); - a.bind(&mut wrap_target); - // Do stop bit - a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0); - // Do start bit - a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1); - // Do data bit = 1 - a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1); - a.bind(&mut do_zero); - // Do data bit = 0 - a.nop_with_delay_and_side_set(T2 - 1, 0); - a.bind(&mut wrap_source); - - let prg = a.assemble_with_wrap(wrap_source, wrap_target); - let mut cfg = Config::default(); - - // Pin config - let out_pin = pio.make_pio_pin(pin); - cfg.set_out_pins(&[&out_pin]); - cfg.set_set_pins(&[&out_pin]); - - cfg.use_program(&pio.load_program(&prg), &[&out_pin]); - - // Clock config, measured in kHz to avoid overflows - // TODO CLOCK_FREQ should come from embassy_rp - let clock_freq = U24F8::from_num(clocks::clk_sys_freq() / 1000); - let ws2812_freq = fixed!(800: U24F8); - let bit_freq = ws2812_freq * CYCLES_PER_BIT; - cfg.clock_divider = clock_freq / bit_freq; - - // FIFO config - cfg.fifo_join = FifoJoin::TxOnly; - cfg.shift_out = ShiftConfig { - auto_fill: true, - threshold: 24, - direction: ShiftDirection::Left, - }; - - sm.set_config(&cfg); - sm.set_enable(true); - - Self { - dma: dma.map_into(), - sm, - } - } - - pub async fn write(&mut self, colors: &[RGB8; N]) { - // Precompute the word bytes from the colors - let mut words = [0u32; N]; - for i in 0..N { - let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8); - words[i] = word; - } - - // DMA transfer - self.sm.tx().dma_push(self.dma.reborrow(), &words).await; - - Timer::after_micros(55).await; - } -} - /// Input a value 0 to 255 to get a color value /// The colours are a transition r - g - b - back to r. fn wheel(mut wheel_pos: u8) -> RGB8 { @@ -142,7 +48,8 @@ async fn main(_spawner: Spawner) { // Common neopixel pins: // Thing plus: 8 // Adafruit Feather: 16; Adafruit Feather+RFM95: 4 - let mut ws2812 = Ws2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16); + let program = PioWs2812Program::new(&mut common); + let mut ws2812 = PioWs2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16, &program); // Loop forever making RGB values and pushing them out to the WS2812. let mut ticker = Ticker::every(Duration::from_millis(10)); diff --git a/examples/rp23/src/bin/pio_hd44780.rs b/examples/rp23/src/bin/pio_hd44780.rs index 5a6d7a9c5..c6f5f6db0 100644 --- a/examples/rp23/src/bin/pio_hd44780.rs +++ b/examples/rp23/src/bin/pio_hd44780.rs @@ -7,14 +7,12 @@ use core::fmt::Write; use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; use embassy_rp::block::ImageDef; -use embassy_rp::dma::{AnyChannel, Channel}; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{ - Config, Direction, FifoJoin, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, -}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::hd44780::{PioHD44780, PioHD44780CommandSequenceProgram, PioHD44780CommandWordProgram}; use embassy_rp::pwm::{self, Pwm}; -use embassy_rp::{bind_interrupts, into_ref, Peripheral, PeripheralRef}; use embassy_time::{Instant, Timer}; use {defmt_rtt as _, panic_probe as _}; @@ -48,8 +46,27 @@ async fn main(_spawner: Spawner) { c }); - let mut hd = HD44780::new( - p.PIO0, Irqs, p.DMA_CH3, p.PIN_0, p.PIN_1, p.PIN_2, p.PIN_3, p.PIN_4, p.PIN_5, p.PIN_6, + let Pio { + mut common, sm0, irq0, .. + } = Pio::new(p.PIO0, Irqs); + + let word_prg = PioHD44780CommandWordProgram::new(&mut common); + let seq_prg = PioHD44780CommandSequenceProgram::new(&mut common); + + let mut hd = PioHD44780::new( + &mut common, + sm0, + irq0, + p.DMA_CH3, + p.PIN_0, + p.PIN_1, + p.PIN_2, + p.PIN_3, + p.PIN_4, + p.PIN_5, + p.PIN_6, + &word_prg, + &seq_prg, ) .await; @@ -73,173 +90,3 @@ async fn main(_spawner: Spawner) { Timer::after_secs(1).await; } } - -pub struct HD44780<'l> { - dma: PeripheralRef<'l, AnyChannel>, - sm: StateMachine<'l, PIO0, 0>, - - buf: [u8; 40], -} - -impl<'l> HD44780<'l> { - pub async fn new( - pio: impl Peripheral

+ 'l, - irq: Irqs, - dma: impl Peripheral

+ 'l, - rs: impl PioPin, - rw: impl PioPin, - e: impl PioPin, - db4: impl PioPin, - db5: impl PioPin, - db6: impl PioPin, - db7: impl PioPin, - ) -> HD44780<'l> { - into_ref!(dma); - - let Pio { - mut common, - mut irq0, - mut sm0, - .. - } = Pio::new(pio, irq); - - // takes command words ( <0:4>) - let prg = pio_proc::pio_asm!( - r#" - .side_set 1 opt - .origin 20 - - loop: - out x, 24 - delay: - jmp x--, delay - out pins, 4 side 1 - out null, 4 side 0 - jmp !osre, loop - irq 0 - "#, - ); - - let rs = common.make_pio_pin(rs); - let rw = common.make_pio_pin(rw); - let e = common.make_pio_pin(e); - let db4 = common.make_pio_pin(db4); - let db5 = common.make_pio_pin(db5); - let db6 = common.make_pio_pin(db6); - let db7 = common.make_pio_pin(db7); - - sm0.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]); - - let mut cfg = Config::default(); - cfg.use_program(&common.load_program(&prg.program), &[&e]); - cfg.clock_divider = 125u8.into(); - cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); - cfg.shift_out = ShiftConfig { - auto_fill: true, - direction: ShiftDirection::Left, - threshold: 32, - }; - cfg.fifo_join = FifoJoin::TxOnly; - sm0.set_config(&cfg); - - sm0.set_enable(true); - // init to 8 bit thrice - sm0.tx().push((50000 << 8) | 0x30); - sm0.tx().push((5000 << 8) | 0x30); - sm0.tx().push((200 << 8) | 0x30); - // init 4 bit - sm0.tx().push((200 << 8) | 0x20); - // set font and lines - sm0.tx().push((50 << 8) | 0x20); - sm0.tx().push(0b1100_0000); - - irq0.wait().await; - sm0.set_enable(false); - - // takes command sequences ( , data...) - // many side sets are only there to free up a delay bit! - let prg = pio_proc::pio_asm!( - r#" - .origin 27 - .side_set 1 - - .wrap_target - pull side 0 - out x 1 side 0 ; !rs - out y 7 side 0 ; #data - 1 - - ; rs/rw to e: >= 60ns - ; e high time: >= 500ns - ; e low time: >= 500ns - ; read data valid after e falling: ~5ns - ; write data hold after e falling: ~10ns - - loop: - pull side 0 - jmp !x data side 0 - command: - set pins 0b00 side 0 - jmp shift side 0 - data: - set pins 0b01 side 0 - shift: - out pins 4 side 1 [9] - nop side 0 [9] - out pins 4 side 1 [9] - mov osr null side 0 [7] - out pindirs 4 side 0 - set pins 0b10 side 0 - busy: - nop side 1 [9] - jmp pin more side 0 [9] - mov osr ~osr side 1 [9] - nop side 0 [4] - out pindirs 4 side 0 - jmp y-- loop side 0 - .wrap - more: - nop side 1 [9] - jmp busy side 0 [9] - "# - ); - - let mut cfg = Config::default(); - cfg.use_program(&common.load_program(&prg.program), &[&e]); - cfg.clock_divider = 8u8.into(); // ~64ns/insn - cfg.set_jmp_pin(&db7); - cfg.set_set_pins(&[&rs, &rw]); - cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); - cfg.shift_out.direction = ShiftDirection::Left; - cfg.fifo_join = FifoJoin::TxOnly; - sm0.set_config(&cfg); - - sm0.set_enable(true); - - // display on and cursor on and blinking, reset display - sm0.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1]).await; - - Self { - dma: dma.map_into(), - sm: sm0, - buf: [0x20; 40], - } - } - - pub async fn add_line(&mut self, s: &[u8]) { - // move cursor to 0:0, prepare 16 characters - self.buf[..3].copy_from_slice(&[0x80, 0x80, 15]); - // move line 2 up - self.buf.copy_within(22..38, 3); - // move cursor to 1:0, prepare 16 characters - self.buf[19..22].copy_from_slice(&[0x80, 0xc0, 15]); - // file line 2 with spaces - self.buf[22..38].fill(0x20); - // copy input line - let len = s.len().min(16); - self.buf[22..22 + len].copy_from_slice(&s[0..len]); - // set cursor to 1:15 - self.buf[38..].copy_from_slice(&[0x80, 0xcf]); - - self.sm.tx().dma_push(self.dma.reborrow(), &self.buf).await; - } -} diff --git a/examples/rp23/src/bin/pio_i2s.rs b/examples/rp23/src/bin/pio_i2s.rs index 46e5eac88..90491fb45 100644 --- a/examples/rp23/src/bin/pio_i2s.rs +++ b/examples/rp23/src/bin/pio_i2s.rs @@ -13,11 +13,11 @@ use core::mem; use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; use embassy_rp::block::ImageDef; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Config, FifoJoin, InterruptHandler, Pio, ShiftConfig, ShiftDirection}; -use embassy_rp::{bind_interrupts, Peripheral}; -use fixed::traits::ToFixed; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::i2s::{PioI2sOut, PioI2sOutProgram}; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -30,61 +30,32 @@ bind_interrupts!(struct Irqs { }); const SAMPLE_RATE: u32 = 48_000; +const BIT_DEPTH: u32 = 16; +const CHANNELS: u32 = 2; #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_rp::init(Default::default()); + let mut p = embassy_rp::init(Default::default()); // Setup pio state machine for i2s output - let mut pio = Pio::new(p.PIO0, Irqs); - - #[rustfmt::skip] - let pio_program = pio_proc::pio_asm!( - ".side_set 2", - " set x, 14 side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock - "left_data:", - " out pins, 1 side 0b00", - " jmp x-- left_data side 0b01", - " out pins 1 side 0b10", - " set x, 14 side 0b11", - "right_data:", - " out pins 1 side 0b10", - " jmp x-- right_data side 0b11", - " out pins 1 side 0b00", - ); + let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); let bit_clock_pin = p.PIN_18; let left_right_clock_pin = p.PIN_19; let data_pin = p.PIN_20; - let data_pin = pio.common.make_pio_pin(data_pin); - let bit_clock_pin = pio.common.make_pio_pin(bit_clock_pin); - let left_right_clock_pin = pio.common.make_pio_pin(left_right_clock_pin); - - let cfg = { - let mut cfg = Config::default(); - cfg.use_program( - &pio.common.load_program(&pio_program.program), - &[&bit_clock_pin, &left_right_clock_pin], - ); - cfg.set_out_pins(&[&data_pin]); - const BIT_DEPTH: u32 = 16; - const CHANNELS: u32 = 2; - let clock_frequency = SAMPLE_RATE * BIT_DEPTH * CHANNELS; - cfg.clock_divider = (125_000_000. / clock_frequency as f64 / 2.).to_fixed(); - cfg.shift_out = ShiftConfig { - threshold: 32, - direction: ShiftDirection::Left, - auto_fill: true, - }; - // join fifos to have twice the time to start the next dma transfer - cfg.fifo_join = FifoJoin::TxOnly; - cfg - }; - pio.sm0.set_config(&cfg); - pio.sm0.set_pin_dirs( - embassy_rp::pio::Direction::Out, - &[&data_pin, &left_right_clock_pin, &bit_clock_pin], + let program = PioI2sOutProgram::new(&mut common); + let mut i2s = PioI2sOut::new( + &mut common, + sm0, + p.DMA_CH0, + data_pin, + bit_clock_pin, + left_right_clock_pin, + SAMPLE_RATE, + BIT_DEPTH, + CHANNELS, + &program, ); // create two audio buffers (back and front) which will take turns being @@ -95,20 +66,16 @@ async fn main(_spawner: Spawner) { let (mut back_buffer, mut front_buffer) = dma_buffer.split_at_mut(BUFFER_SIZE); // start pio state machine - pio.sm0.set_enable(true); - let tx = pio.sm0.tx(); - let mut dma_ref = p.DMA_CH0.into_ref(); - let mut fade_value: i32 = 0; let mut phase: i32 = 0; loop { // trigger transfer of front buffer data to the pio fifo // but don't await the returned future, yet - let dma_future = tx.dma_push(dma_ref.reborrow(), front_buffer); + let dma_future = i2s.write(front_buffer); - // fade in audio - let fade_target = i32::MAX; + // fade in audio when bootsel is pressed + let fade_target = if p.BOOTSEL.is_pressed() { i32::MAX } else { 0 }; // fill back buffer with fresh audio samples before awaiting the dma future for s in back_buffer.iter_mut() { diff --git a/examples/rp23/src/bin/pio_onewire.rs b/examples/rp23/src/bin/pio_onewire.rs new file mode 100644 index 000000000..7f227d04b --- /dev/null +++ b/examples/rp23/src/bin/pio_onewire.rs @@ -0,0 +1,88 @@ +//! This example shows how you can use PIO to read a `DS18B20` one-wire temperature sensor. + +#![no_std] +#![no_main] +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{self, InterruptHandler, Pio}; +use embassy_rp::pio_programs::onewire::{PioOneWire, PioOneWireProgram}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut pio = Pio::new(p.PIO0, Irqs); + + let prg = PioOneWireProgram::new(&mut pio.common); + let onewire = PioOneWire::new(&mut pio.common, pio.sm0, p.PIN_2, &prg); + + let mut sensor = Ds18b20::new(onewire); + + loop { + sensor.start().await; // Start a new measurement + Timer::after_secs(1).await; // Allow 1s for the measurement to finish + match sensor.temperature().await { + Ok(temp) => info!("temp = {:?} deg C", temp), + _ => error!("sensor error"), + } + Timer::after_secs(1).await; + } +} + +/// DS18B20 temperature sensor driver +pub struct Ds18b20<'d, PIO: pio::Instance, const SM: usize> { + wire: PioOneWire<'d, PIO, SM>, +} + +impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> { + pub fn new(wire: PioOneWire<'d, PIO, SM>) -> Self { + Self { wire } + } + + /// Calculate CRC8 of the data + fn crc8(data: &[u8]) -> u8 { + let mut temp; + let mut data_byte; + let mut crc = 0; + for b in data { + data_byte = *b; + for _ in 0..8 { + temp = (crc ^ data_byte) & 0x01; + crc >>= 1; + if temp != 0 { + crc ^= 0x8C; + } + data_byte >>= 1; + } + } + crc + } + + /// Start a new measurement. Allow at least 1000ms before getting `temperature`. + pub async fn start(&mut self) { + self.wire.write_bytes(&[0xCC, 0x44]).await; + } + + /// Read the temperature. Ensure >1000ms has passed since `start` before calling this. + pub async fn temperature(&mut self) -> Result { + self.wire.write_bytes(&[0xCC, 0xBE]).await; + let mut data = [0; 9]; + self.wire.read_bytes(&mut data).await; + match Self::crc8(&data) == 0 { + true => Ok(((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.), + false => Err(()), + } + } +} diff --git a/examples/rp23/src/bin/pio_pwm.rs b/examples/rp23/src/bin/pio_pwm.rs index 3cffd213d..11af62a7a 100644 --- a/examples/rp23/src/bin/pio_pwm.rs +++ b/examples/rp23/src/bin/pio_pwm.rs @@ -5,13 +5,12 @@ use core::time::Duration; use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; use embassy_rp::block::ImageDef; -use embassy_rp::gpio::Level; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; -use embassy_rp::{bind_interrupts, clocks}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; use embassy_time::Timer; -use pio::InstructionOperands; use {defmt_rtt as _, panic_probe as _}; #[link_section = ".start_block"] @@ -24,93 +23,14 @@ bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub fn to_pio_cycles(duration: Duration) -> u32 { - (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow -} - -pub struct PwmPio<'d, T: Instance, const SM: usize> { - sm: StateMachine<'d, T, SM>, -} - -impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { - pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self { - let prg = pio_proc::pio_asm!( - ".side_set 1 opt" - "pull noblock side 0" - "mov x, osr" - "mov y, isr" - "countloop:" - "jmp x!=y noset" - "jmp skip side 1" - "noset:" - "nop" - "skip:" - "jmp y-- countloop" - ); - - pio.load_program(&prg.program); - let pin = pio.make_pio_pin(pin); - sm.set_pins(Level::High, &[&pin]); - sm.set_pin_dirs(Direction::Out, &[&pin]); - - let mut cfg = Config::default(); - cfg.use_program(&pio.load_program(&prg.program), &[&pin]); - - sm.set_config(&cfg); - - Self { sm } - } - - pub fn start(&mut self) { - self.sm.set_enable(true); - } - - pub fn stop(&mut self) { - self.sm.set_enable(false); - } - - pub fn set_period(&mut self, duration: Duration) { - let is_enabled = self.sm.is_enabled(); - while !self.sm.tx().empty() {} // Make sure that the queue is empty - self.sm.set_enable(false); - self.sm.tx().push(to_pio_cycles(duration)); - unsafe { - self.sm.exec_instr( - InstructionOperands::PULL { - if_empty: false, - block: false, - } - .encode(), - ); - self.sm.exec_instr( - InstructionOperands::OUT { - destination: ::pio::OutDestination::ISR, - bit_count: 32, - } - .encode(), - ); - }; - if is_enabled { - self.sm.set_enable(true) // Enable if previously enabled - } - } - - pub fn set_level(&mut self, level: u32) { - self.sm.tx().push(level); - } - - pub fn write(&mut self, duration: Duration) { - self.set_level(to_pio_cycles(duration)); - } -} - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); // Note that PIN_25 is the led pin on the Pico - let mut pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_25); + let prg = PioPwmProgram::new(&mut common); + let mut pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_25, &prg); pwm_pio.set_period(Duration::from_micros(REFRESH_INTERVAL)); pwm_pio.start(); diff --git a/examples/rp23/src/bin/pio_rotary_encoder.rs b/examples/rp23/src/bin/pio_rotary_encoder.rs index 9542d63b7..9a953951f 100644 --- a/examples/rp23/src/bin/pio_rotary_encoder.rs +++ b/examples/rp23/src/bin/pio_rotary_encoder.rs @@ -6,11 +6,12 @@ use defmt::info; use embassy_executor::Spawner; use embassy_rp::block::ImageDef; -use embassy_rp::gpio::Pull; use embassy_rp::peripherals::PIO0; -use embassy_rp::{bind_interrupts, pio}; -use fixed::traits::ToFixed; -use pio::{Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftDirection, StateMachine}; +use embassy_rp::{ + bind_interrupts, + pio::{InterruptHandler, Pio}, + pio_programs::rotary_encoder::{Direction, PioEncoder, PioEncoderProgram}, +}; use {defmt_rtt as _, panic_probe as _}; #[link_section = ".start_block"] @@ -21,59 +22,8 @@ bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub struct PioEncoder<'d, T: Instance, const SM: usize> { - sm: StateMachine<'d, T, SM>, -} - -impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> { - pub fn new( - pio: &mut Common<'d, T>, - mut sm: StateMachine<'d, T, SM>, - pin_a: impl PioPin, - pin_b: impl PioPin, - ) -> Self { - let mut pin_a = pio.make_pio_pin(pin_a); - let mut pin_b = pio.make_pio_pin(pin_b); - pin_a.set_pull(Pull::Up); - pin_b.set_pull(Pull::Up); - sm.set_pin_dirs(pio::Direction::In, &[&pin_a, &pin_b]); - - let prg = pio_proc::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",); - - let mut cfg = Config::default(); - cfg.set_in_pins(&[&pin_a, &pin_b]); - cfg.fifo_join = FifoJoin::RxOnly; - cfg.shift_in.direction = ShiftDirection::Left; - cfg.clock_divider = 10_000.to_fixed(); - cfg.use_program(&pio.load_program(&prg.program), &[]); - sm.set_config(&cfg); - sm.set_enable(true); - Self { sm } - } - - pub async fn read(&mut self) -> Direction { - loop { - match self.sm.rx().wait_pull().await { - 0 => return Direction::CounterClockwise, - 1 => return Direction::Clockwise, - _ => {} - } - } - } -} - -pub enum Direction { - Clockwise, - CounterClockwise, -} - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = embassy_rp::init(Default::default()); - let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); - - let mut encoder = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5); - +#[embassy_executor::task] +async fn encoder_0(mut encoder: PioEncoder<'static, PIO0, 0>) { let mut count = 0; loop { info!("Count: {}", count); @@ -83,3 +33,30 @@ async fn main(_spawner: Spawner) { }; } } + +#[embassy_executor::task] +async fn encoder_1(mut encoder: PioEncoder<'static, PIO0, 1>) { + let mut count = 0; + loop { + info!("Count: {}", count); + count += match encoder.read().await { + Direction::Clockwise => 1, + Direction::CounterClockwise => -1, + }; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { + mut common, sm0, sm1, .. + } = Pio::new(p.PIO0, Irqs); + + let prg = PioEncoderProgram::new(&mut common); + let encoder0 = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5, &prg); + let encoder1 = PioEncoder::new(&mut common, sm1, p.PIN_6, p.PIN_7, &prg); + + spawner.must_spawn(encoder_0(encoder0)); + spawner.must_spawn(encoder_1(encoder1)); +} diff --git a/examples/rp23/src/bin/pio_servo.rs b/examples/rp23/src/bin/pio_servo.rs index 3202ab475..4e94103f1 100644 --- a/examples/rp23/src/bin/pio_servo.rs +++ b/examples/rp23/src/bin/pio_servo.rs @@ -5,13 +5,12 @@ use core::time::Duration; use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; use embassy_rp::block::ImageDef; -use embassy_rp::gpio::Level; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; -use embassy_rp::{bind_interrupts, clocks}; +use embassy_rp::pio::{Instance, InterruptHandler, Pio}; +use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; use embassy_time::Timer; -use pio::InstructionOperands; use {defmt_rtt as _, panic_probe as _}; #[link_section = ".start_block"] @@ -27,88 +26,8 @@ bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub fn to_pio_cycles(duration: Duration) -> u32 { - (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow -} - -pub struct PwmPio<'d, T: Instance, const SM: usize> { - sm: StateMachine<'d, T, SM>, -} - -impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { - pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self { - let prg = pio_proc::pio_asm!( - ".side_set 1 opt" - "pull noblock side 0" - "mov x, osr" - "mov y, isr" - "countloop:" - "jmp x!=y noset" - "jmp skip side 1" - "noset:" - "nop" - "skip:" - "jmp y-- countloop" - ); - - pio.load_program(&prg.program); - let pin = pio.make_pio_pin(pin); - sm.set_pins(Level::High, &[&pin]); - sm.set_pin_dirs(Direction::Out, &[&pin]); - - let mut cfg = Config::default(); - cfg.use_program(&pio.load_program(&prg.program), &[&pin]); - - sm.set_config(&cfg); - - Self { sm } - } - - pub fn start(&mut self) { - self.sm.set_enable(true); - } - - pub fn stop(&mut self) { - self.sm.set_enable(false); - } - - pub fn set_period(&mut self, duration: Duration) { - let is_enabled = self.sm.is_enabled(); - while !self.sm.tx().empty() {} // Make sure that the queue is empty - self.sm.set_enable(false); - self.sm.tx().push(to_pio_cycles(duration)); - unsafe { - self.sm.exec_instr( - InstructionOperands::PULL { - if_empty: false, - block: false, - } - .encode(), - ); - self.sm.exec_instr( - InstructionOperands::OUT { - destination: ::pio::OutDestination::ISR, - bit_count: 32, - } - .encode(), - ); - }; - if is_enabled { - self.sm.set_enable(true) // Enable if previously enabled - } - } - - pub fn set_level(&mut self, level: u32) { - self.sm.tx().push(level); - } - - pub fn write(&mut self, duration: Duration) { - self.set_level(to_pio_cycles(duration)); - } -} - pub struct ServoBuilder<'d, T: Instance, const SM: usize> { - pwm: PwmPio<'d, T, SM>, + pwm: PioPwm<'d, T, SM>, period: Duration, min_pulse_width: Duration, max_pulse_width: Duration, @@ -116,7 +35,7 @@ pub struct ServoBuilder<'d, T: Instance, const SM: usize> { } impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { - pub fn new(pwm: PwmPio<'d, T, SM>) -> Self { + pub fn new(pwm: PioPwm<'d, T, SM>) -> Self { Self { pwm, period: Duration::from_micros(REFRESH_INTERVAL), @@ -158,7 +77,7 @@ impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { } pub struct Servo<'d, T: Instance, const SM: usize> { - pwm: PwmPio<'d, T, SM>, + pwm: PioPwm<'d, T, SM>, min_pulse_width: Duration, max_pulse_width: Duration, max_degree_rotation: u64, @@ -195,7 +114,8 @@ async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); - let pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_1); + let prg = PioPwmProgram::new(&mut common); + let pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_1, &prg); let mut servo = ServoBuilder::new(pwm_pio) .set_max_degree_rotation(120) // Example of adjusting values for MG996R servo .set_min_pulse_width(Duration::from_micros(350)) // This value was detemined by a rough experiment. diff --git a/examples/rp23/src/bin/pio_stepper.rs b/examples/rp23/src/bin/pio_stepper.rs index 5e87da6eb..4fabe78ca 100644 --- a/examples/rp23/src/bin/pio_stepper.rs +++ b/examples/rp23/src/bin/pio_stepper.rs @@ -3,18 +3,15 @@ #![no_std] #![no_main] -use core::mem::{self, MaybeUninit}; use defmt::info; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; use embassy_rp::block::ImageDef; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Irq, Pio, PioPin, StateMachine}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::stepper::{PioStepper, PioStepperProgram}; use embassy_time::{with_timeout, Duration, Timer}; -use fixed::traits::ToFixed; -use fixed::types::extra::U8; -use fixed::FixedU32; use {defmt_rtt as _, panic_probe as _}; #[link_section = ".start_block"] @@ -25,126 +22,6 @@ bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub struct PioStepper<'d, T: Instance, const SM: usize> { - irq: Irq<'d, T, SM>, - sm: StateMachine<'d, T, SM>, -} - -impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { - pub fn new( - pio: &mut Common<'d, T>, - mut sm: StateMachine<'d, T, SM>, - irq: Irq<'d, T, SM>, - pin0: impl PioPin, - pin1: impl PioPin, - pin2: impl PioPin, - pin3: impl PioPin, - ) -> Self { - let prg = pio_proc::pio_asm!( - "pull block", - "mov x, osr", - "pull block", - "mov y, osr", - "jmp !x end", - "loop:", - "jmp !osre step", - "mov osr, y", - "step:", - "out pins, 4 [31]" - "jmp x-- loop", - "end:", - "irq 0 rel" - ); - let pin0 = pio.make_pio_pin(pin0); - let pin1 = pio.make_pio_pin(pin1); - let pin2 = pio.make_pio_pin(pin2); - let pin3 = pio.make_pio_pin(pin3); - sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]); - let mut cfg = Config::default(); - cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]); - cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed(); - cfg.use_program(&pio.load_program(&prg.program), &[]); - sm.set_config(&cfg); - sm.set_enable(true); - Self { irq, sm } - } - - // Set pulse frequency - pub fn set_frequency(&mut self, freq: u32) { - let clock_divider: FixedU32 = (125_000_000 / (freq * 136)).to_fixed(); - assert!(clock_divider <= 65536, "clkdiv must be <= 65536"); - assert!(clock_divider >= 1, "clkdiv must be >= 1"); - self.sm.set_clock_divider(clock_divider); - self.sm.clkdiv_restart(); - } - - // Full step, one phase - pub async fn step(&mut self, steps: i32) { - if steps > 0 { - self.run(steps, 0b1000_0100_0010_0001_1000_0100_0010_0001).await - } else { - self.run(-steps, 0b0001_0010_0100_1000_0001_0010_0100_1000).await - } - } - - // Full step, two phase - pub async fn step2(&mut self, steps: i32) { - if steps > 0 { - self.run(steps, 0b1001_1100_0110_0011_1001_1100_0110_0011).await - } else { - self.run(-steps, 0b0011_0110_1100_1001_0011_0110_1100_1001).await - } - } - - // Half step - pub async fn step_half(&mut self, steps: i32) { - if steps > 0 { - self.run(steps, 0b1001_1000_1100_0100_0110_0010_0011_0001).await - } else { - self.run(-steps, 0b0001_0011_0010_0110_0100_1100_1000_1001).await - } - } - - async fn run(&mut self, steps: i32, pattern: u32) { - self.sm.tx().wait_push(steps as u32).await; - self.sm.tx().wait_push(pattern).await; - let drop = OnDrop::new(|| { - self.sm.clear_fifos(); - unsafe { - self.sm.exec_instr( - pio::InstructionOperands::JMP { - address: 0, - condition: pio::JmpCondition::Always, - } - .encode(), - ); - } - }); - self.irq.wait().await; - drop.defuse(); - } -} - -struct OnDrop { - f: MaybeUninit, -} - -impl OnDrop { - pub fn new(f: F) -> Self { - Self { f: MaybeUninit::new(f) } - } - - pub fn defuse(self) { - mem::forget(self) - } -} - -impl Drop for OnDrop { - fn drop(&mut self) { - unsafe { self.f.as_ptr().read()() } - } -} - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); @@ -152,14 +29,18 @@ async fn main(_spawner: Spawner) { mut common, irq0, sm0, .. } = Pio::new(p.PIO0, Irqs); - let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7); + let prg = PioStepperProgram::new(&mut common); + let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7, &prg); stepper.set_frequency(120); loop { info!("CW full steps"); stepper.step(1000).await; info!("CCW full steps, drop after 1 sec"); - if let Err(_) = with_timeout(Duration::from_secs(1), stepper.step(i32::MIN)).await { + if with_timeout(Duration::from_secs(1), stepper.step(-i32::MAX)) + .await + .is_err() + { info!("Time's up!"); Timer::after(Duration::from_secs(1)).await; } diff --git a/examples/rp23/src/bin/pio_uart.rs b/examples/rp23/src/bin/pio_uart.rs new file mode 100644 index 000000000..6899a5d7c --- /dev/null +++ b/examples/rp23/src/bin/pio_uart.rs @@ -0,0 +1,203 @@ +//! This example shows how to use the PIO module in the RP2040 chip to implement a duplex UART. +//! The PIO module is a very powerful peripheral that can be used to implement many different +//! protocols. It is a very flexible state machine that can be programmed to do almost anything. +//! +//! This example opens up a USB device that implements a CDC ACM serial port. It then uses the +//! PIO module to implement a UART that is connected to the USB serial port. This allows you to +//! communicate with a device connected to the RP2040 over USB serial. + +#![no_std] +#![no_main] +#![allow(async_fn_in_trait)] + +use defmt::{info, panic, trace}; +use embassy_executor::Spawner; +use embassy_futures::join::{join, join3}; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::{PIO0, USB}; +use embassy_rp::pio; +use embassy_rp::pio_programs::uart::{PioUartRx, PioUartRxProgram, PioUartTx, PioUartTxProgram}; +use embassy_rp::usb::{Driver, Instance, InterruptHandler}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::pipe::Pipe; +use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::{Builder, Config}; +use embedded_io_async::{Read, Write}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; + PIO0_IRQ_0 => pio::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello there!"); + + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("PIO UART example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // PIO UART setup + let pio::Pio { + mut common, sm0, sm1, .. + } = pio::Pio::new(p.PIO0, Irqs); + + let tx_program = PioUartTxProgram::new(&mut common); + let mut uart_tx = PioUartTx::new(9600, &mut common, sm0, p.PIN_4, &tx_program); + + let rx_program = PioUartRxProgram::new(&mut common); + let mut uart_rx = PioUartRx::new(9600, &mut common, sm1, p.PIN_5, &rx_program); + + // Pipe setup + let mut usb_pipe: Pipe = Pipe::new(); + let (mut usb_pipe_reader, mut usb_pipe_writer) = usb_pipe.split(); + + let mut uart_pipe: Pipe = Pipe::new(); + let (mut uart_pipe_reader, mut uart_pipe_writer) = uart_pipe.split(); + + let (mut usb_tx, mut usb_rx) = class.split(); + + // Read + write from USB + let usb_future = async { + loop { + info!("Wait for USB connection"); + usb_rx.wait_connection().await; + info!("Connected"); + let _ = join( + usb_read(&mut usb_rx, &mut uart_pipe_writer), + usb_write(&mut usb_tx, &mut usb_pipe_reader), + ) + .await; + info!("Disconnected"); + } + }; + + // Read + write from UART + let uart_future = join( + uart_read(&mut uart_rx, &mut usb_pipe_writer), + uart_write(&mut uart_tx, &mut uart_pipe_reader), + ); + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join3(usb_fut, usb_future, uart_future).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +/// Read from the USB and write it to the UART TX pipe +async fn usb_read<'d, T: Instance + 'd>( + usb_rx: &mut Receiver<'d, Driver<'d, T>>, + uart_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>, +) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = usb_rx.read_packet(&mut buf).await?; + let data = &buf[..n]; + trace!("USB IN: {:x}", data); + (*uart_pipe_writer).write(data).await; + } +} + +/// Read from the USB TX pipe and write it to the USB +async fn usb_write<'d, T: Instance + 'd>( + usb_tx: &mut Sender<'d, Driver<'d, T>>, + usb_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>, +) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = (*usb_pipe_reader).read(&mut buf).await; + let data = &buf[..n]; + trace!("USB OUT: {:x}", data); + usb_tx.write_packet(&data).await?; + } +} + +/// Read from the UART and write it to the USB TX pipe +async fn uart_read( + uart_rx: &mut PioUartRx<'_, PIO, SM>, + usb_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>, +) -> ! { + let mut buf = [0; 64]; + loop { + let n = uart_rx.read(&mut buf).await.expect("UART read error"); + if n == 0 { + continue; + } + let data = &buf[..n]; + trace!("UART IN: {:x}", buf); + (*usb_pipe_writer).write(data).await; + } +} + +/// Read from the UART TX pipe and write it to the UART +async fn uart_write( + uart_tx: &mut PioUartTx<'_, PIO, SM>, + uart_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>, +) -> ! { + let mut buf = [0; 64]; + loop { + let n = (*uart_pipe_reader).read(&mut buf).await; + let data = &buf[..n]; + trace!("UART OUT: {:x}", data); + let _ = uart_tx.write(&data).await; + } +} diff --git a/examples/rp23/src/bin/pio_ws2812.rs b/examples/rp23/src/bin/pio_ws2812.rs index 1f1984c4d..4d258234e 100644 --- a/examples/rp23/src/bin/pio_ws2812.rs +++ b/examples/rp23/src/bin/pio_ws2812.rs @@ -6,16 +6,12 @@ use defmt::*; use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; use embassy_rp::block::ImageDef; -use embassy_rp::dma::{AnyChannel, Channel}; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{ - Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, -}; -use embassy_rp::{bind_interrupts, clocks, into_ref, Peripheral, PeripheralRef}; -use embassy_time::{Duration, Ticker, Timer}; -use fixed::types::U24F8; -use fixed_macro::fixed; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::ws2812::{PioWs2812, PioWs2812Program}; +use embassy_time::{Duration, Ticker}; use smart_leds::RGB8; use {defmt_rtt as _, panic_probe as _}; @@ -27,96 +23,6 @@ bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub struct Ws2812<'d, P: Instance, const S: usize, const N: usize> { - dma: PeripheralRef<'d, AnyChannel>, - sm: StateMachine<'d, P, S>, -} - -impl<'d, P: Instance, const S: usize, const N: usize> Ws2812<'d, P, S, N> { - pub fn new( - pio: &mut Common<'d, P>, - mut sm: StateMachine<'d, P, S>, - dma: impl Peripheral

+ 'd, - pin: impl PioPin, - ) -> Self { - into_ref!(dma); - - // Setup sm0 - - // prepare the PIO program - let side_set = pio::SideSet::new(false, 1, false); - let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set); - - const T1: u8 = 2; // start bit - const T2: u8 = 5; // data bit - const T3: u8 = 3; // stop bit - const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32; - - let mut wrap_target = a.label(); - let mut wrap_source = a.label(); - let mut do_zero = a.label(); - a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0); - a.bind(&mut wrap_target); - // Do stop bit - a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0); - // Do start bit - a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1); - // Do data bit = 1 - a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1); - a.bind(&mut do_zero); - // Do data bit = 0 - a.nop_with_delay_and_side_set(T2 - 1, 0); - a.bind(&mut wrap_source); - - let prg = a.assemble_with_wrap(wrap_source, wrap_target); - let mut cfg = Config::default(); - - // Pin config - let out_pin = pio.make_pio_pin(pin); - cfg.set_out_pins(&[&out_pin]); - cfg.set_set_pins(&[&out_pin]); - - cfg.use_program(&pio.load_program(&prg), &[&out_pin]); - - // Clock config, measured in kHz to avoid overflows - // TODO CLOCK_FREQ should come from embassy_rp - let clock_freq = U24F8::from_num(clocks::clk_sys_freq() / 1000); - let ws2812_freq = fixed!(800: U24F8); - let bit_freq = ws2812_freq * CYCLES_PER_BIT; - cfg.clock_divider = clock_freq / bit_freq; - - // FIFO config - cfg.fifo_join = FifoJoin::TxOnly; - cfg.shift_out = ShiftConfig { - auto_fill: true, - threshold: 24, - direction: ShiftDirection::Left, - }; - - sm.set_config(&cfg); - sm.set_enable(true); - - Self { - dma: dma.map_into(), - sm, - } - } - - pub async fn write(&mut self, colors: &[RGB8; N]) { - // Precompute the word bytes from the colors - let mut words = [0u32; N]; - for i in 0..N { - let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8); - words[i] = word; - } - - // DMA transfer - self.sm.tx().dma_push(self.dma.reborrow(), &words).await; - - Timer::after_micros(55).await; - } -} - /// Input a value 0 to 255 to get a color value /// The colours are a transition r - g - b - back to r. fn wheel(mut wheel_pos: u8) -> RGB8 { @@ -147,7 +53,8 @@ async fn main(_spawner: Spawner) { // Common neopixel pins: // Thing plus: 8 // Adafruit Feather: 16; Adafruit Feather+RFM95: 4 - let mut ws2812 = Ws2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16); + let program = PioWs2812Program::new(&mut common); + let mut ws2812 = PioWs2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16, &program); // Loop forever making RGB values and pushing them out to the WS2812. let mut ticker = Ticker::every(Duration::from_millis(10)); From fc978c2ee9dda07b9fe7113e2aa0f2d3fb33fd1b Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Wed, 9 Oct 2024 11:37:15 -0400 Subject: [PATCH 054/144] Fix rp23 i2s example, boot_sel isn't supported yet. --- examples/rp23/src/bin/pio_i2s.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/rp23/src/bin/pio_i2s.rs b/examples/rp23/src/bin/pio_i2s.rs index 90491fb45..1fd34357b 100644 --- a/examples/rp23/src/bin/pio_i2s.rs +++ b/examples/rp23/src/bin/pio_i2s.rs @@ -15,6 +15,7 @@ use core::mem; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{Input, Pull}; use embassy_rp::peripherals::PIO0; use embassy_rp::pio::{InterruptHandler, Pio}; use embassy_rp::pio_programs::i2s::{PioI2sOut, PioI2sOutProgram}; @@ -35,7 +36,7 @@ const CHANNELS: u32 = 2; #[embassy_executor::main] async fn main(_spawner: Spawner) { - let mut p = embassy_rp::init(Default::default()); + let p = embassy_rp::init(Default::default()); // Setup pio state machine for i2s output let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); @@ -58,6 +59,8 @@ async fn main(_spawner: Spawner) { &program, ); + let fade_input = Input::new(p.PIN_0, Pull::Up); + // create two audio buffers (back and front) which will take turns being // filled with new audio data and being sent to the pio fifo using dma const BUFFER_SIZE: usize = 960; @@ -75,7 +78,7 @@ async fn main(_spawner: Spawner) { let dma_future = i2s.write(front_buffer); // fade in audio when bootsel is pressed - let fade_target = if p.BOOTSEL.is_pressed() { i32::MAX } else { 0 }; + let fade_target = if fade_input.is_low() { i32::MAX } else { 0 }; // fill back buffer with fresh audio samples before awaiting the dma future for s in back_buffer.iter_mut() { From c7f7728eb164edea2f8041d2511a976f9ebc17ca Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Wed, 9 Oct 2024 11:44:58 -0400 Subject: [PATCH 055/144] cargo +nightly fmt --- embassy-rp/src/pio_programs/i2s.rs | 14 ++++++-------- embassy-rp/src/pio_programs/pwm.rs | 9 ++++----- embassy-rp/src/pio_programs/rotary_encoder.rs | 3 ++- embassy-rp/src/pio_programs/stepper.rs | 3 ++- embassy-rp/src/pio_programs/uart.rs | 15 +++++++-------- embassy-rp/src/pio_programs/ws2812.rs | 14 +++++++------- 6 files changed, 28 insertions(+), 30 deletions(-) diff --git a/embassy-rp/src/pio_programs/i2s.rs b/embassy-rp/src/pio_programs/i2s.rs index 3c8ef8bb6..e3f1f89d4 100644 --- a/embassy-rp/src/pio_programs/i2s.rs +++ b/embassy-rp/src/pio_programs/i2s.rs @@ -1,15 +1,13 @@ //! Pio backed I2s output -use crate::{ - dma::{AnyChannel, Channel, Transfer}, - into_ref, - pio::{ - Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, - }, - Peripheral, PeripheralRef, -}; use fixed::traits::ToFixed; +use crate::dma::{AnyChannel, Channel, Transfer}; +use crate::pio::{ + Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, +}; +use crate::{into_ref, Peripheral, PeripheralRef}; + /// This struct represents an i2s output driver program pub struct PioI2sOutProgram<'a, PIO: Instance> { prg: LoadedProgram<'a, PIO>, diff --git a/embassy-rp/src/pio_programs/pwm.rs b/embassy-rp/src/pio_programs/pwm.rs index 5dfd70cc9..8a0f3d5ee 100644 --- a/embassy-rp/src/pio_programs/pwm.rs +++ b/embassy-rp/src/pio_programs/pwm.rs @@ -2,13 +2,12 @@ use core::time::Duration; -use crate::{ - clocks, - gpio::Level, - pio::{Common, Config, Direction, Instance, LoadedProgram, PioPin, StateMachine}, -}; use pio::InstructionOperands; +use crate::clocks; +use crate::gpio::Level; +use crate::pio::{Common, Config, Direction, Instance, LoadedProgram, PioPin, StateMachine}; + fn to_pio_cycles(duration: Duration) -> u32 { (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow } diff --git a/embassy-rp/src/pio_programs/rotary_encoder.rs b/embassy-rp/src/pio_programs/rotary_encoder.rs index 323f839bc..86423fd31 100644 --- a/embassy-rp/src/pio_programs/rotary_encoder.rs +++ b/embassy-rp/src/pio_programs/rotary_encoder.rs @@ -1,8 +1,9 @@ //! PIO backed quadrature encoder +use fixed::traits::ToFixed; + use crate::gpio::Pull; use crate::pio::{self, Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine}; -use fixed::traits::ToFixed; /// This struct represents an Encoder program loaded into pio instruction memory. pub struct PioEncoderProgram<'a, PIO: Instance> { diff --git a/embassy-rp/src/pio_programs/stepper.rs b/embassy-rp/src/pio_programs/stepper.rs index 0ecc4eff0..0d58c754c 100644 --- a/embassy-rp/src/pio_programs/stepper.rs +++ b/embassy-rp/src/pio_programs/stepper.rs @@ -2,11 +2,12 @@ use core::mem::{self, MaybeUninit}; -use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine}; use fixed::traits::ToFixed; use fixed::types::extra::U8; use fixed::FixedU32; +use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine}; + /// This struct represents a Stepper driver program loaded into pio instruction memory. pub struct PioStepperProgram<'a, PIO: Instance> { prg: LoadedProgram<'a, PIO>, diff --git a/embassy-rp/src/pio_programs/uart.rs b/embassy-rp/src/pio_programs/uart.rs index f4b3b204e..c643f1063 100644 --- a/embassy-rp/src/pio_programs/uart.rs +++ b/embassy-rp/src/pio_programs/uart.rs @@ -1,17 +1,16 @@ //! Pio backed uart drivers -use crate::{ - clocks::clk_sys_freq, - gpio::Level, - pio::{ - Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, - StateMachine, - }, -}; use core::convert::Infallible; + use embedded_io_async::{ErrorType, Read, Write}; use fixed::traits::ToFixed; +use crate::clocks::clk_sys_freq; +use crate::gpio::Level; +use crate::pio::{ + Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine, +}; + /// This struct represents a uart tx program loaded into pio instruction memory. pub struct PioUartTxProgram<'a, PIO: Instance> { prg: LoadedProgram<'a, PIO>, diff --git a/embassy-rp/src/pio_programs/ws2812.rs b/embassy-rp/src/pio_programs/ws2812.rs index 3fc7e1017..875f0209f 100644 --- a/embassy-rp/src/pio_programs/ws2812.rs +++ b/embassy-rp/src/pio_programs/ws2812.rs @@ -1,16 +1,16 @@ //! [ws2812](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) -use crate::{ - clocks::clk_sys_freq, - dma::{AnyChannel, Channel}, - into_ref, - pio::{Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine}, - Peripheral, PeripheralRef, -}; use embassy_time::Timer; use fixed::types::U24F8; use smart_leds::RGB8; +use crate::clocks::clk_sys_freq; +use crate::dma::{AnyChannel, Channel}; +use crate::pio::{ + Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, +}; +use crate::{into_ref, Peripheral, PeripheralRef}; + const T1: u8 = 2; // start bit const T2: u8 = 5; // data bit const T3: u8 = 3; // stop bit From e47c031b671555f3fffe6b128cbb9d3f8bfec534 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Wed, 9 Oct 2024 11:47:04 -0400 Subject: [PATCH 056/144] fmt examples too --- examples/rp/src/bin/pio_rotary_encoder.rs | 8 +++----- examples/rp/src/bin/pio_uart.rs | 3 +-- examples/rp23/src/bin/pio_rotary_encoder.rs | 8 +++----- examples/rp23/src/bin/pio_uart.rs | 3 +-- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/examples/rp/src/bin/pio_rotary_encoder.rs b/examples/rp/src/bin/pio_rotary_encoder.rs index a7ecc8d0e..2750f61ae 100644 --- a/examples/rp/src/bin/pio_rotary_encoder.rs +++ b/examples/rp/src/bin/pio_rotary_encoder.rs @@ -5,12 +5,10 @@ use defmt::info; use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::{ - bind_interrupts, - pio::{InterruptHandler, Pio}, - pio_programs::rotary_encoder::{Direction, PioEncoder, PioEncoderProgram}, -}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::rotary_encoder::{Direction, PioEncoder, PioEncoderProgram}; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { diff --git a/examples/rp/src/bin/pio_uart.rs b/examples/rp/src/bin/pio_uart.rs index b9e01b0ac..aaf2a524f 100644 --- a/examples/rp/src/bin/pio_uart.rs +++ b/examples/rp/src/bin/pio_uart.rs @@ -13,11 +13,10 @@ use defmt::{info, panic, trace}; use embassy_executor::Spawner; use embassy_futures::join::{join, join3}; -use embassy_rp::bind_interrupts; use embassy_rp::peripherals::{PIO0, USB}; -use embassy_rp::pio; use embassy_rp::pio_programs::uart::{PioUartRx, PioUartRxProgram, PioUartTx, PioUartTxProgram}; use embassy_rp::usb::{Driver, Instance, InterruptHandler}; +use embassy_rp::{bind_interrupts, pio}; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::pipe::Pipe; use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State}; diff --git a/examples/rp23/src/bin/pio_rotary_encoder.rs b/examples/rp23/src/bin/pio_rotary_encoder.rs index 9a953951f..2bb0e67f9 100644 --- a/examples/rp23/src/bin/pio_rotary_encoder.rs +++ b/examples/rp23/src/bin/pio_rotary_encoder.rs @@ -5,13 +5,11 @@ use defmt::info; use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; use embassy_rp::block::ImageDef; use embassy_rp::peripherals::PIO0; -use embassy_rp::{ - bind_interrupts, - pio::{InterruptHandler, Pio}, - pio_programs::rotary_encoder::{Direction, PioEncoder, PioEncoderProgram}, -}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::rotary_encoder::{Direction, PioEncoder, PioEncoderProgram}; use {defmt_rtt as _, panic_probe as _}; #[link_section = ".start_block"] diff --git a/examples/rp23/src/bin/pio_uart.rs b/examples/rp23/src/bin/pio_uart.rs index 6899a5d7c..f8398c22a 100644 --- a/examples/rp23/src/bin/pio_uart.rs +++ b/examples/rp23/src/bin/pio_uart.rs @@ -13,12 +13,11 @@ use defmt::{info, panic, trace}; use embassy_executor::Spawner; use embassy_futures::join::{join, join3}; -use embassy_rp::bind_interrupts; use embassy_rp::block::ImageDef; use embassy_rp::peripherals::{PIO0, USB}; -use embassy_rp::pio; use embassy_rp::pio_programs::uart::{PioUartRx, PioUartRxProgram, PioUartTx, PioUartTxProgram}; use embassy_rp::usb::{Driver, Instance, InterruptHandler}; +use embassy_rp::{bind_interrupts, pio}; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::pipe::Pipe; use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State}; From 22fe4932577cf261a561e33c2a8378c168fab1dd Mon Sep 17 00:00:00 2001 From: Bjorn <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:12:43 -0700 Subject: [PATCH 057/144] Better docs and adding of release for PioPwm --- embassy-rp/src/pio_programs/pwm.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/embassy-rp/src/pio_programs/pwm.rs b/embassy-rp/src/pio_programs/pwm.rs index 8a0f3d5ee..7b3157877 100644 --- a/embassy-rp/src/pio_programs/pwm.rs +++ b/embassy-rp/src/pio_programs/pwm.rs @@ -1,6 +1,7 @@ //! PIO backed PWM driver use core::time::Duration; +use crate::pio::Pin; use pio::InstructionOperands; @@ -8,6 +9,7 @@ use crate::clocks; use crate::gpio::Level; use crate::pio::{Common, Config, Direction, Instance, LoadedProgram, PioPin, StateMachine}; +/// This converts the duration provided into the number of cycles the PIO needs to run to make it take the same time fn to_pio_cycles(duration: Duration) -> u32 { (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow } @@ -43,6 +45,7 @@ impl<'a, PIO: Instance> PioPwmProgram<'a, PIO> { /// Pio backed PWM output pub struct PioPwm<'d, T: Instance, const SM: usize> { sm: StateMachine<'d, T, SM>, + pin: Pin<'d, T> } impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> { @@ -62,20 +65,20 @@ impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> { sm.set_config(&cfg); - Self { sm } + Self { sm, pin } } - /// Enable PWM output + /// Enable's the PIO program, continuing the wave generation from the PIO program. pub fn start(&mut self) { self.sm.set_enable(true); } - /// Disable PWM output + /// Stops the PIO program, ceasing all signals from the PIN that were generated via PIO. pub fn stop(&mut self) { self.sm.set_enable(false); } - /// Set pwm period + /// Sets the pwm period, which is the length of time for each pio wave until reset. pub fn set_period(&mut self, duration: Duration) { let is_enabled = self.sm.is_enabled(); while !self.sm.tx().empty() {} // Make sure that the queue is empty @@ -102,7 +105,8 @@ impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> { } } - fn set_level(&mut self, level: u32) { + /// Set the number of pio cycles to set the wave on high to. + pub fn set_level(&mut self, level: u32) { self.sm.tx().push(level); } @@ -110,4 +114,9 @@ impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> { pub fn write(&mut self, duration: Duration) { self.set_level(to_pio_cycles(duration)); } + + // Return the state machine and pin. + pub fn release(self) -> (StateMachine<'d, T, SM>, Pin<'d, T>) { + (self.sm, self.pin) + } } From 1b32b7bcb44a9df149bb9194cb83e6c1f370db12 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Wed, 9 Oct 2024 16:51:52 -0400 Subject: [PATCH 058/144] fmt --- embassy-rp/src/pio_programs/pwm.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/embassy-rp/src/pio_programs/pwm.rs b/embassy-rp/src/pio_programs/pwm.rs index 7b3157877..dfc5feb19 100644 --- a/embassy-rp/src/pio_programs/pwm.rs +++ b/embassy-rp/src/pio_programs/pwm.rs @@ -1,13 +1,12 @@ //! PIO backed PWM driver use core::time::Duration; -use crate::pio::Pin; use pio::InstructionOperands; use crate::clocks; use crate::gpio::Level; -use crate::pio::{Common, Config, Direction, Instance, LoadedProgram, PioPin, StateMachine}; +use crate::pio::{Common, Config, Direction, Instance, LoadedProgram, Pin, PioPin, StateMachine}; /// This converts the duration provided into the number of cycles the PIO needs to run to make it take the same time fn to_pio_cycles(duration: Duration) -> u32 { @@ -45,7 +44,7 @@ impl<'a, PIO: Instance> PioPwmProgram<'a, PIO> { /// Pio backed PWM output pub struct PioPwm<'d, T: Instance, const SM: usize> { sm: StateMachine<'d, T, SM>, - pin: Pin<'d, T> + pin: Pin<'d, T>, } impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> { From 70bd158d03afa92edb0ce23d05860bde1651cf61 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Wed, 9 Oct 2024 16:57:02 -0400 Subject: [PATCH 059/144] Make the docs be docs --- embassy-rp/src/pio_programs/pwm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-rp/src/pio_programs/pwm.rs b/embassy-rp/src/pio_programs/pwm.rs index dfc5feb19..c6502387a 100644 --- a/embassy-rp/src/pio_programs/pwm.rs +++ b/embassy-rp/src/pio_programs/pwm.rs @@ -114,7 +114,7 @@ impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> { self.set_level(to_pio_cycles(duration)); } - // Return the state machine and pin. + /// Return the state machine and pin. pub fn release(self) -> (StateMachine<'d, T, SM>, Pin<'d, T>) { (self.sm, self.pin) } From 3870411a4a4546f814140b178609a86d5209d734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Vi=C3=B6l?= Date: Thu, 10 Oct 2024 16:12:51 +0200 Subject: [PATCH 060/144] stm32/i2c: disable pullup instead of pulldown --- embassy-stm32/src/i2c/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/i2c/mod.rs b/embassy-stm32/src/i2c/mod.rs index 739e960b9..1fc91f1ef 100644 --- a/embassy-stm32/src/i2c/mod.rs +++ b/embassy-stm32/src/i2c/mod.rs @@ -88,7 +88,7 @@ impl Config { Speed::Medium, match self.scl_pullup { true => Pull::Up, - false => Pull::Down, + false => Pull::None, }, ); } @@ -102,7 +102,7 @@ impl Config { Speed::Medium, match self.sda_pullup { true => Pull::Up, - false => Pull::Down, + false => Pull::None, }, ); } From 350a15a0cd72dc1d8ef65d2c398f279d571f5193 Mon Sep 17 00:00:00 2001 From: Joost Buijgers Date: Fri, 11 Oct 2024 12:18:04 +0200 Subject: [PATCH 061/144] make bluetooth module public --- cyw43/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cyw43/src/lib.rs b/cyw43/src/lib.rs index 6b71c18e6..3cd0e4988 100644 --- a/cyw43/src/lib.rs +++ b/cyw43/src/lib.rs @@ -9,7 +9,8 @@ pub(crate) mod fmt; #[cfg(feature = "bluetooth")] -mod bluetooth; +/// Bluetooth module. +pub mod bluetooth; mod bus; mod consts; mod control; From 0bf99820f3e5efaba821652fe33e99ed621ece7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beat=20K=C3=BCng?= Date: Sat, 12 Oct 2024 10:35:43 +0200 Subject: [PATCH 062/144] stm32: add RX Pull configuration option to USART --- embassy-stm32/src/usart/buffered.rs | 4 ++-- embassy-stm32/src/usart/mod.rs | 18 +++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/embassy-stm32/src/usart/buffered.rs b/embassy-stm32/src/usart/buffered.rs index 86f56eb7c..4fbe33b2e 100644 --- a/embassy-stm32/src/usart/buffered.rs +++ b/embassy-stm32/src/usart/buffered.rs @@ -210,7 +210,7 @@ impl<'d> BufferedUart<'d> { ) -> Result { Self::new_inner( peri, - new_pin!(rx, AfType::input(Pull::None)), + new_pin!(rx, AfType::input(config.rx_pull)), new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), None, None, @@ -260,7 +260,7 @@ impl<'d> BufferedUart<'d> { ) -> Result { Self::new_inner( peri, - new_pin!(rx, AfType::input(Pull::None)), + new_pin!(rx, AfType::input(config.rx_pull)), new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), None, None, diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index e7f2f890a..5c96e202d 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -167,6 +167,9 @@ pub struct Config { #[cfg(any(usart_v3, usart_v4))] pub invert_rx: bool, + /// Set the pull configuration for the RX pin. + pub rx_pull: Pull, + // private: set by new_half_duplex, not by the user. half_duplex: bool, } @@ -175,7 +178,7 @@ impl Config { fn tx_af(&self) -> AfType { #[cfg(any(usart_v3, usart_v4))] if self.swap_rx_tx { - return AfType::input(Pull::None); + return AfType::input(self.rx_pull); }; AfType::output(OutputType::PushPull, Speed::Medium) } @@ -185,7 +188,7 @@ impl Config { if self.swap_rx_tx { return AfType::output(OutputType::PushPull, Speed::Medium); }; - AfType::input(Pull::None) + AfType::input(self.rx_pull) } } @@ -206,6 +209,7 @@ impl Default for Config { invert_tx: false, #[cfg(any(usart_v3, usart_v4))] invert_rx: false, + rx_pull: Pull::None, half_duplex: false, } } @@ -448,7 +452,7 @@ impl<'d> UartTx<'d, Blocking> { Self::new_inner( peri, new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), - new_pin!(cts, AfType::input(Pull::None)), + new_pin!(cts, AfType::input(config.rx_pull)), None, config, ) @@ -567,7 +571,7 @@ impl<'d> UartRx<'d, Async> { ) -> Result { Self::new_inner( peri, - new_pin!(rx, AfType::input(Pull::None)), + new_pin!(rx, AfType::input(config.rx_pull)), None, new_dma!(rx_dma), config, @@ -585,7 +589,7 @@ impl<'d> UartRx<'d, Async> { ) -> Result { Self::new_inner( peri, - new_pin!(rx, AfType::input(Pull::None)), + new_pin!(rx, AfType::input(config.rx_pull)), new_pin!(rts, AfType::output(OutputType::PushPull, Speed::Medium)), new_dma!(rx_dma), config, @@ -815,7 +819,7 @@ impl<'d> UartRx<'d, Blocking> { rx: impl Peripheral

> + 'd, config: Config, ) -> Result { - Self::new_inner(peri, new_pin!(rx, AfType::input(Pull::None)), None, None, config) + Self::new_inner(peri, new_pin!(rx, AfType::input(config.rx_pull)), None, None, config) } /// Create a new rx-only UART with a request-to-send pin @@ -827,7 +831,7 @@ impl<'d> UartRx<'d, Blocking> { ) -> Result { Self::new_inner( peri, - new_pin!(rx, AfType::input(Pull::None)), + new_pin!(rx, AfType::input(config.rx_pull)), new_pin!(rts, AfType::output(OutputType::PushPull, Speed::Medium)), None, config, From cdcd9de05143827c0c138359e0b43887d64cf98f Mon Sep 17 00:00:00 2001 From: Keisuke Tottori Date: Fri, 27 Sep 2024 17:19:35 +0900 Subject: [PATCH 063/144] Enable FPU for RP235X Core1 --- embassy-rp/src/multicore.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/embassy-rp/src/multicore.rs b/embassy-rp/src/multicore.rs index 9f7d77bf5..7e2e776ea 100644 --- a/embassy-rp/src/multicore.rs +++ b/embassy-rp/src/multicore.rs @@ -169,6 +169,13 @@ where interrupt::SIO_IRQ_FIFO.enable() }; + // Enable FPU + #[cfg(feature = "_rp235x")] + unsafe { + let p = cortex_m::Peripherals::steal(); + p.SCB.cpacr.modify(|cpacr| cpacr | (3 << 20) | (3 << 22)); + } + entry() } From 0222faa8a1f3afbc60eb455989fc581ec9adfac1 Mon Sep 17 00:00:00 2001 From: HaoboGu Date: Mon, 14 Oct 2024 04:32:22 +0800 Subject: [PATCH 064/144] Add octospim support for octospi (#3102) * feat: add octospim to ospi Signed-off-by: Haobo Gu * feat: make octospim behind feature gate Signed-off-by: Haobo Gu * refactor: fix fmt issue Signed-off-by: Haobo Gu * refactor: fix ci failure Signed-off-by: Haobo Gu * feat: add octospim reg writing code Signed-off-by: Haobo Gu * feat(octospi): enable rcc for octospim at the initialization Signed-off-by: Haobo Gu * fix: add octospim feature gate Signed-off-by: Haobo Gu * fix: fix cfg flag Signed-off-by: Haobo Gu * fix: fix rcc register on stm32l4 and stm32u5 Signed-off-by: Haobo Gu * feat(ospi): support OCTOSPI2 in build.rs Signed-off-by: Haobo Gu * feat(ospi): add OCTOSPI2 pin impls Signed-off-by: HaoboGu * feat(ospi): support both ospi instances in stm32 OCTOSPIM Signed-off-by: Haobo Gu --------- Signed-off-by: Haobo Gu Signed-off-by: HaoboGu --- embassy-stm32/build.rs | 33 ++++++++++ embassy-stm32/src/ospi/mod.rs | 118 ++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+) diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 28c619c6b..966dce121 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -1054,6 +1054,30 @@ fn main() { (("octospi", "NCS"), quote!(crate::ospi::NSSPin)), (("octospi", "CLK"), quote!(crate::ospi::SckPin)), (("octospi", "NCLK"), quote!(crate::ospi::NckPin)), + (("octospim", "P1_IO0"), quote!(crate::ospi::D0Pin)), + (("octospim", "P1_IO1"), quote!(crate::ospi::D1Pin)), + (("octospim", "P1_IO2"), quote!(crate::ospi::D2Pin)), + (("octospim", "P1_IO3"), quote!(crate::ospi::D3Pin)), + (("octospim", "P1_IO4"), quote!(crate::ospi::D4Pin)), + (("octospim", "P1_IO5"), quote!(crate::ospi::D5Pin)), + (("octospim", "P1_IO6"), quote!(crate::ospi::D6Pin)), + (("octospim", "P1_IO7"), quote!(crate::ospi::D7Pin)), + (("octospim", "P1_DQS"), quote!(crate::ospi::DQSPin)), + (("octospim", "P1_NCS"), quote!(crate::ospi::NSSPin)), + (("octospim", "P1_CLK"), quote!(crate::ospi::SckPin)), + (("octospim", "P1_NCLK"), quote!(crate::ospi::NckPin)), + (("octospim", "P2_IO0"), quote!(crate::ospi::D0Pin)), + (("octospim", "P2_IO1"), quote!(crate::ospi::D1Pin)), + (("octospim", "P2_IO2"), quote!(crate::ospi::D2Pin)), + (("octospim", "P2_IO3"), quote!(crate::ospi::D3Pin)), + (("octospim", "P2_IO4"), quote!(crate::ospi::D4Pin)), + (("octospim", "P2_IO5"), quote!(crate::ospi::D5Pin)), + (("octospim", "P2_IO6"), quote!(crate::ospi::D6Pin)), + (("octospim", "P2_IO7"), quote!(crate::ospi::D7Pin)), + (("octospim", "P2_DQS"), quote!(crate::ospi::DQSPin)), + (("octospim", "P2_NCS"), quote!(crate::ospi::NSSPin)), + (("octospim", "P2_CLK"), quote!(crate::ospi::SckPin)), + (("octospim", "P2_NCLK"), quote!(crate::ospi::NckPin)), (("tsc", "G1_IO1"), quote!(crate::tsc::G1IO1Pin)), (("tsc", "G1_IO2"), quote!(crate::tsc::G1IO2Pin)), (("tsc", "G1_IO3"), quote!(crate::tsc::G1IO3Pin)), @@ -1111,6 +1135,15 @@ fn main() { peri = format_ident!("{}", pin.signal.replace('_', "")); } + // OCTOSPIM is special + if p.name == "OCTOSPIM" { + peri = format_ident!("{}", "OCTOSPI1"); + g.extend(quote! { + pin_trait_impl!(#tr, #peri, #pin_name, #af); + }); + peri = format_ident!("{}", "OCTOSPI2"); + } + g.extend(quote! { pin_trait_impl!(#tr, #peri, #pin_name, #af); }) diff --git a/embassy-stm32/src/ospi/mod.rs b/embassy-stm32/src/ospi/mod.rs index 289bfa672..48a1ea5e6 100644 --- a/embassy-stm32/src/ospi/mod.rs +++ b/embassy-stm32/src/ospi/mod.rs @@ -16,6 +16,8 @@ use crate::dma::{word, ChannelAndRequest}; use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; use crate::mode::{Async, Blocking, Mode as PeriMode}; use crate::pac::octospi::{vals, Octospi as Regs}; +#[cfg(octospim_v1)] +use crate::pac::octospim::Octospim; use crate::rcc::{self, RccPeripheral}; use crate::{peripherals, Peripheral}; @@ -197,6 +199,83 @@ impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { ) -> Self { into_ref!(peri); + #[cfg(octospim_v1)] + { + // RCC for octospim should be enabled before writing register + #[cfg(stm32l4)] + crate::pac::RCC.ahb2smenr().modify(|w| w.set_octospimsmen(true)); + #[cfg(stm32u5)] + crate::pac::RCC.ahb2enr1().modify(|w| w.set_octospimen(true)); + #[cfg(not(any(stm32l4, stm32u5)))] + crate::pac::RCC.ahb3enr().modify(|w| w.set_iomngren(true)); + + // Disable OctoSPI peripheral first + T::REGS.cr().modify(|w| { + w.set_en(false); + }); + + // OctoSPI IO Manager has been enabled before + T::OCTOSPIM_REGS.cr().modify(|w| { + w.set_muxen(false); + w.set_req2ack_time(0xff); + }); + + // Clear config + T::OCTOSPIM_REGS.p1cr().modify(|w| { + w.set_clksrc(false); + w.set_dqssrc(false); + w.set_ncssrc(false); + w.set_clken(false); + w.set_dqsen(false); + w.set_ncsen(false); + w.set_iolsrc(0); + w.set_iohsrc(0); + }); + + T::OCTOSPIM_REGS.p1cr().modify(|w| { + let octospi_src = if T::OCTOSPI_IDX == 1 { false } else { true }; + w.set_ncsen(true); + w.set_ncssrc(octospi_src); + w.set_clken(true); + w.set_clksrc(octospi_src); + if dqs.is_some() { + w.set_dqsen(true); + w.set_dqssrc(octospi_src); + } + + // Set OCTOSPIM IOL and IOH according to the index of OCTOSPI instance + if T::OCTOSPI_IDX == 1 { + w.set_iolen(true); + w.set_iolsrc(0); + // Enable IOH in octo and dual quad mode + if let OspiWidth::OCTO = width { + w.set_iohen(true); + w.set_iohsrc(0b01); + } else if dual_quad { + w.set_iohen(true); + w.set_iohsrc(0b00); + } else { + w.set_iohen(false); + w.set_iohsrc(0b00); + } + } else { + w.set_iolen(true); + w.set_iolsrc(0b10); + // Enable IOH in octo and dual quad mode + if let OspiWidth::OCTO = width { + w.set_iohen(true); + w.set_iohsrc(0b11); + } else if dual_quad { + w.set_iohen(true); + w.set_iohsrc(0b10); + } else { + w.set_iohen(false); + w.set_iohsrc(0b00); + } + } + }); + } + // System configuration rcc::enable_and_reset::(); while T::REGS.sr().read().busy() {} @@ -1056,11 +1135,25 @@ fn finish_dma(regs: Regs) { }); } +#[cfg(octospim_v1)] +/// OctoSPI I/O manager instance trait. +pub(crate) trait SealedOctospimInstance { + const OCTOSPIM_REGS: Octospim; + const OCTOSPI_IDX: u8; +} + +/// OctoSPI instance trait. pub(crate) trait SealedInstance { const REGS: Regs; } /// OSPI instance trait. +#[cfg(octospim_v1)] +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + RccPeripheral + SealedOctospimInstance {} + +/// OSPI instance trait. +#[cfg(not(octospim_v1))] #[allow(private_bounds)] pub trait Instance: Peripheral

+ SealedInstance + RccPeripheral {} @@ -1078,6 +1171,31 @@ pin_trait!(DQSPin, Instance); pin_trait!(NSSPin, Instance); dma_trait!(OctoDma, Instance); +// Hard-coded the octospi index, for OCTOSPIM +#[cfg(octospim_v1)] +impl SealedOctospimInstance for peripherals::OCTOSPI1 { + const OCTOSPIM_REGS: Octospim = crate::pac::OCTOSPIM; + const OCTOSPI_IDX: u8 = 1; +} + +#[cfg(octospim_v1)] +impl SealedOctospimInstance for peripherals::OCTOSPI2 { + const OCTOSPIM_REGS: Octospim = crate::pac::OCTOSPIM; + const OCTOSPI_IDX: u8 = 2; +} + +#[cfg(octospim_v1)] +foreach_peripheral!( + (octospi, $inst:ident) => { + impl SealedInstance for peripherals::$inst { + const REGS: Regs = crate::pac::$inst; + } + + impl Instance for peripherals::$inst {} + }; +); + +#[cfg(not(octospim_v1))] foreach_peripheral!( (octospi, $inst:ident) => { impl SealedInstance for peripherals::$inst { From a4636d819f4b98a781cc88a05c2e89397c71e1ed Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 13 Oct 2024 21:47:40 +0200 Subject: [PATCH 065/144] rp/multicore: enable fpu on second core only if building for -eabihf targets. --- embassy-rp/build.rs | 22 +++++++++++++++++++++- embassy-rp/src/multicore.rs | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/embassy-rp/build.rs b/embassy-rp/build.rs index 3216a3826..a8d387611 100644 --- a/embassy-rp/build.rs +++ b/embassy-rp/build.rs @@ -1,7 +1,8 @@ use std::env; +use std::ffi::OsStr; use std::fs::File; use std::io::Write; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; fn main() { if env::var("CARGO_FEATURE_RP2040").is_ok() { @@ -16,4 +17,23 @@ fn main() { println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=link-rp.x.in"); } + + // code below taken from https://github.com/rust-embedded/cortex-m/blob/master/cortex-m-rt/build.rs + + let mut target = env::var("TARGET").unwrap(); + + // When using a custom target JSON, `$TARGET` contains the path to that JSON file. By + // convention, these files are named after the actual target triple, eg. + // `thumbv7m-customos-elf.json`, so we extract the file stem here to allow custom target specs. + let path = Path::new(&target); + if path.extension() == Some(OsStr::new("json")) { + target = path + .file_stem() + .map_or(target.clone(), |stem| stem.to_str().unwrap().to_string()); + } + + println!("cargo::rustc-check-cfg=cfg(has_fpu)"); + if target.ends_with("-eabihf") { + println!("cargo:rustc-cfg=has_fpu"); + } } diff --git a/embassy-rp/src/multicore.rs b/embassy-rp/src/multicore.rs index 7e2e776ea..81de84907 100644 --- a/embassy-rp/src/multicore.rs +++ b/embassy-rp/src/multicore.rs @@ -170,7 +170,7 @@ where }; // Enable FPU - #[cfg(feature = "_rp235x")] + #[cfg(all(feature = "_rp235x", has_fpu))] unsafe { let p = cortex_m::Peripherals::steal(); p.SCB.cpacr.modify(|cpacr| cpacr | (3 << 20) | (3 << 22)); From ee669ee5c57851ade034beca7cfaf81825c4c21b Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 14 Oct 2024 00:01:49 +0200 Subject: [PATCH 066/144] Update nighlty, fix warnings. Fixes #2599 --- embassy-rp/src/lib.rs | 6 +++--- embassy-rp/src/multicore.rs | 4 ++-- embassy-stm32-wpan/src/lib.rs | 1 + embassy-stm32-wpan/src/mac/commands.rs | 2 +- embassy-stm32/src/low_power.rs | 3 +++ embassy-stm32/src/rcc/mod.rs | 10 +++++++--- examples/stm32h7/src/bin/sai.rs | 10 ++++++---- examples/stm32h7/src/bin/spi_bdma.rs | 7 ++++--- rust-toolchain-nightly.toml | 2 +- tests/nrf/src/bin/buffered_uart_spam.rs | 2 +- tests/stm32/src/bin/usart_rx_ringbuffered.rs | 3 +-- 11 files changed, 30 insertions(+), 20 deletions(-) diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index 7ac18c1f8..f56cfba27 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -480,7 +480,7 @@ pub fn install_core0_stack_guard() -> Result<(), ()> { #[cfg(all(feature = "rp2040", not(feature = "_test")))] #[inline(always)] -fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { +unsafe fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { let core = unsafe { cortex_m::Peripherals::steal() }; // Fail if MPU is already configured @@ -508,7 +508,7 @@ fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { #[cfg(all(feature = "_rp235x", not(feature = "_test")))] #[inline(always)] -fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { +unsafe fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { let core = unsafe { cortex_m::Peripherals::steal() }; // Fail if MPU is already configured @@ -528,7 +528,7 @@ fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { // so the compile fails when we try to use ARMv8 peripherals. #[cfg(feature = "_test")] #[inline(always)] -fn install_stack_guard(_stack_bottom: *mut usize) -> Result<(), ()> { +unsafe fn install_stack_guard(_stack_bottom: *mut usize) -> Result<(), ()> { Ok(()) } diff --git a/embassy-rp/src/multicore.rs b/embassy-rp/src/multicore.rs index 81de84907..ea0a29a36 100644 --- a/embassy-rp/src/multicore.rs +++ b/embassy-rp/src/multicore.rs @@ -58,7 +58,7 @@ const RESUME_TOKEN: u32 = !0xDEADBEEF; static IS_CORE1_INIT: AtomicBool = AtomicBool::new(false); #[inline(always)] -fn core1_setup(stack_bottom: *mut usize) { +unsafe fn core1_setup(stack_bottom: *mut usize) { if install_stack_guard(stack_bottom).is_err() { // currently only happens if the MPU was already set up, which // would indicate that the core is already in use from outside @@ -148,7 +148,7 @@ where entry: *mut ManuallyDrop, stack_bottom: *mut usize, ) -> ! { - core1_setup(stack_bottom); + unsafe { core1_setup(stack_bottom) }; let entry = unsafe { ManuallyDrop::take(&mut *entry) }; diff --git a/embassy-stm32-wpan/src/lib.rs b/embassy-stm32-wpan/src/lib.rs index f9560d235..fb34d4ba0 100644 --- a/embassy-stm32-wpan/src/lib.rs +++ b/embassy-stm32-wpan/src/lib.rs @@ -2,6 +2,7 @@ #![allow(async_fn_in_trait)] #![doc = include_str!("../README.md")] // #![warn(missing_docs)] +#![allow(static_mut_refs)] // TODO: Fix // This must go FIRST so that all the other modules see its macros. mod fmt; diff --git a/embassy-stm32-wpan/src/mac/commands.rs b/embassy-stm32-wpan/src/mac/commands.rs index c97c609c3..82b9d2772 100644 --- a/embassy-stm32-wpan/src/mac/commands.rs +++ b/embassy-stm32-wpan/src/mac/commands.rs @@ -371,7 +371,7 @@ pub struct DataRequest { } impl DataRequest { - pub fn set_buffer<'a>(&'a mut self, buf: &'a [u8]) -> &mut Self { + pub fn set_buffer<'a>(&'a mut self, buf: &'a [u8]) -> &'a mut Self { self.msdu_ptr = buf as *const _ as *const u8; self.msdu_length = buf.len() as u8; diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index f3e4c6994..2be1a42b7 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs @@ -51,6 +51,9 @@ //! } //! ``` +// TODO: Usage of `static mut` here is unsound. Fix then remove this `allow`.` +#![allow(static_mut_refs)] + use core::arch::asm; use core::marker::PhantomData; use core::sync::atomic::{compiler_fence, Ordering}; diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index 8022a35a4..4f43d3748 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -86,7 +86,7 @@ pub(crate) unsafe fn set_freqs(freqs: Clocks) { #[cfg(not(feature = "_dual-core"))] /// Safety: Reads a mutable global. pub(crate) unsafe fn get_freqs() -> &'static Clocks { - CLOCK_FREQS.assume_init_ref() + (*core::ptr::addr_of_mut!(CLOCK_FREQS)).assume_init_ref() } #[cfg(feature = "_dual-core")] @@ -171,7 +171,9 @@ impl RccInfo { // Use .get_mut instead of []-operator so that we control how bounds checks happen. // Otherwise, core::fmt will be pulled in here in order to format the integer in the // out-of-bounds error. - if let Some(refcount) = unsafe { crate::_generated::REFCOUNTS.get_mut(refcount_idx) } { + if let Some(refcount) = + unsafe { (*core::ptr::addr_of_mut!(crate::_generated::REFCOUNTS)).get_mut(refcount_idx) } + { *refcount += 1; if *refcount > 1 { return; @@ -235,7 +237,9 @@ impl RccInfo { // Use .get_mut instead of []-operator so that we control how bounds checks happen. // Otherwise, core::fmt will be pulled in here in order to format the integer in the // out-of-bounds error. - if let Some(refcount) = unsafe { crate::_generated::REFCOUNTS.get_mut(refcount_idx) } { + if let Some(refcount) = + unsafe { (*core::ptr::addr_of_mut!(crate::_generated::REFCOUNTS)).get_mut(refcount_idx) } + { *refcount -= 1; if *refcount > 0 { return; diff --git a/examples/stm32h7/src/bin/sai.rs b/examples/stm32h7/src/bin/sai.rs index f6735e235..04d14bd6b 100644 --- a/examples/stm32h7/src/bin/sai.rs +++ b/examples/stm32h7/src/bin/sai.rs @@ -81,8 +81,9 @@ async fn main(_spawner: Spawner) { 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(); + let buf = &mut *core::ptr::addr_of_mut!(TX_BUFFER); + buf.initialize_all_copied(0); + let (ptr, len) = buf.get_ptr_len(); core::slice::from_raw_parts_mut(ptr, len) }; @@ -98,8 +99,9 @@ async fn main(_spawner: Spawner) { ); let rx_buffer: &mut [u32] = unsafe { - RX_BUFFER.initialize_all_copied(0); - let (ptr, len) = RX_BUFFER.get_ptr_len(); + let buf = &mut *core::ptr::addr_of_mut!(RX_BUFFER); + buf.initialize_all_copied(0); + let (ptr, len) = buf.get_ptr_len(); core::slice::from_raw_parts_mut(ptr, len) }; diff --git a/examples/stm32h7/src/bin/spi_bdma.rs b/examples/stm32h7/src/bin/spi_bdma.rs index 43fb6b41c..9166fe9b6 100644 --- a/examples/stm32h7/src/bin/spi_bdma.rs +++ b/examples/stm32h7/src/bin/spi_bdma.rs @@ -22,10 +22,11 @@ static mut RAM_D3: GroundedArrayCell = GroundedArrayCell::uninit(); #[embassy_executor::task] async fn main_task(mut spi: spi::Spi<'static, Async>) { let (read_buffer, write_buffer) = unsafe { - RAM_D3.initialize_all_copied(0); + let ram = &mut *core::ptr::addr_of_mut!(RAM_D3); + ram.initialize_all_copied(0); ( - RAM_D3.get_subslice_mut_unchecked(0, 128), - RAM_D3.get_subslice_mut_unchecked(128, 128), + ram.get_subslice_mut_unchecked(0, 128), + ram.get_subslice_mut_unchecked(128, 128), ) }; diff --git a/rust-toolchain-nightly.toml b/rust-toolchain-nightly.toml index 0b10d7194..acab9d615 100644 --- a/rust-toolchain-nightly.toml +++ b/rust-toolchain-nightly.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2024-09-06" +channel = "nightly-2024-10-13" components = [ "rust-src", "rustfmt", "llvm-tools", "miri" ] targets = [ "thumbv7em-none-eabi", diff --git a/tests/nrf/src/bin/buffered_uart_spam.rs b/tests/nrf/src/bin/buffered_uart_spam.rs index e8fca452e..843313537 100644 --- a/tests/nrf/src/bin/buffered_uart_spam.rs +++ b/tests/nrf/src/bin/buffered_uart_spam.rs @@ -55,7 +55,7 @@ async fn main(_spawner: Spawner) { let task = unsafe { Task::new_unchecked(NonNull::new_unchecked(&spam_peri.tasks_starttx as *const _ as _)) }; let mut spam_ppi = Ppi::new_one_to_one(p.PPI_CH2, event, task); spam_ppi.enable(); - let p = unsafe { TX_BUF.as_mut_ptr() }; + let p = unsafe { core::ptr::addr_of_mut!(TX_BUF) } as *mut u8; spam_peri.txd.ptr.write(|w| unsafe { w.ptr().bits(p as u32) }); spam_peri.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(NSPAM as _) }); spam_peri.tasks_starttx.write(|w| unsafe { w.bits(1) }); diff --git a/tests/stm32/src/bin/usart_rx_ringbuffered.rs b/tests/stm32/src/bin/usart_rx_ringbuffered.rs index 98c7ef312..83c0887ac 100644 --- a/tests/stm32/src/bin/usart_rx_ringbuffered.rs +++ b/tests/stm32/src/bin/usart_rx_ringbuffered.rs @@ -43,8 +43,7 @@ async fn main(spawner: Spawner) { let usart = Uart::new(usart, rx, tx, irq, tx_dma, rx_dma, config).unwrap(); let (tx, rx) = usart.split(); static mut DMA_BUF: [u8; DMA_BUF_SIZE] = [0; DMA_BUF_SIZE]; - let dma_buf = unsafe { DMA_BUF.as_mut() }; - let rx = rx.into_ring_buffered(dma_buf); + let rx = rx.into_ring_buffered(unsafe { &mut *core::ptr::addr_of_mut!(DMA_BUF) }); info!("Spawning tasks"); spawner.spawn(transmit_task(tx)).unwrap(); From 9a45d776d870582cda3db0db233e3f5aea15d34e Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 14 Oct 2024 00:12:45 +0200 Subject: [PATCH 067/144] rustfmt for new nightly. --- embassy-net-driver-channel/src/lib.rs | 10 ++++++++-- embassy-net-driver/src/lib.rs | 6 ++++-- embassy-net-enc28j60/src/lib.rs | 6 ++++-- embassy-net-tuntap/src/lib.rs | 10 ++++++++-- embassy-net/src/driver_util.rs | 10 ++++++++-- embassy-net/src/tcp.rs | 5 ++++- embassy-stm32-wpan/src/mac/driver.rs | 10 ++++++++-- embassy-stm32/src/eth/mod.rs | 10 ++++++++-- 8 files changed, 52 insertions(+), 15 deletions(-) diff --git a/embassy-net-driver-channel/src/lib.rs b/embassy-net-driver-channel/src/lib.rs index 7ad4d449e..6390502a8 100644 --- a/embassy-net-driver-channel/src/lib.rs +++ b/embassy-net-driver-channel/src/lib.rs @@ -326,8 +326,14 @@ pub struct Device<'d, const MTU: usize> { } impl<'d, const MTU: usize> embassy_net_driver::Driver for Device<'d, MTU> { - type RxToken<'a> = RxToken<'a, MTU> where Self: 'a ; - type TxToken<'a> = TxToken<'a, MTU> where Self: 'a ; + type RxToken<'a> + = RxToken<'a, MTU> + where + Self: 'a; + type TxToken<'a> + = TxToken<'a, MTU> + where + Self: 'a; fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { if self.rx.poll_receive(cx).is_ready() && self.tx.poll_send(cx).is_ready() { diff --git a/embassy-net-driver/src/lib.rs b/embassy-net-driver/src/lib.rs index 87f9f6ed1..4c847718d 100644 --- a/embassy-net-driver/src/lib.rs +++ b/embassy-net-driver/src/lib.rs @@ -83,10 +83,12 @@ pub trait Driver { } impl Driver for &mut T { - type RxToken<'a> = T::RxToken<'a> + type RxToken<'a> + = T::RxToken<'a> where Self: 'a; - type TxToken<'a> = T::TxToken<'a> + type TxToken<'a> + = T::TxToken<'a> where Self: 'a; diff --git a/embassy-net-enc28j60/src/lib.rs b/embassy-net-enc28j60/src/lib.rs index dda35f498..c1f32719a 100644 --- a/embassy-net-enc28j60/src/lib.rs +++ b/embassy-net-enc28j60/src/lib.rs @@ -635,11 +635,13 @@ where S: SpiDevice, O: OutputPin, { - type RxToken<'a> = RxToken<'a> + type RxToken<'a> + = RxToken<'a> where Self: 'a; - type TxToken<'a> = TxToken<'a, S, O> + type TxToken<'a> + = TxToken<'a, S, O> where Self: 'a; diff --git a/embassy-net-tuntap/src/lib.rs b/embassy-net-tuntap/src/lib.rs index 56f55fba1..2ff23f462 100644 --- a/embassy-net-tuntap/src/lib.rs +++ b/embassy-net-tuntap/src/lib.rs @@ -152,8 +152,14 @@ impl TunTapDevice { } impl Driver for TunTapDevice { - type RxToken<'a> = RxToken where Self: 'a; - type TxToken<'a> = TxToken<'a> where Self: 'a; + type RxToken<'a> + = RxToken + where + Self: 'a; + type TxToken<'a> + = TxToken<'a> + where + Self: 'a; fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { let mut buf = vec![0; self.device.get_ref().mtu]; diff --git a/embassy-net/src/driver_util.rs b/embassy-net/src/driver_util.rs index b2af1d499..f51641425 100644 --- a/embassy-net/src/driver_util.rs +++ b/embassy-net/src/driver_util.rs @@ -18,8 +18,14 @@ impl<'d, 'c, T> phy::Device for DriverAdapter<'d, 'c, T> where T: Driver, { - type RxToken<'a> = RxTokenAdapter> where Self: 'a; - type TxToken<'a> = TxTokenAdapter> where Self: 'a; + type RxToken<'a> + = RxTokenAdapter> + where + Self: 'a; + type TxToken<'a> + = TxTokenAdapter> + where + Self: 'a; fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { self.inner diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index 1bd582b65..043062e06 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -712,7 +712,10 @@ pub mod client { for TcpClient<'d, N, TX_SZ, RX_SZ> { type Error = Error; - type Connection<'m> = TcpConnection<'m, N, TX_SZ, RX_SZ> where Self: 'm; + type Connection<'m> + = TcpConnection<'m, N, TX_SZ, RX_SZ> + where + Self: 'm; async fn connect<'a>(&'a self, remote: core::net::SocketAddr) -> Result, Self::Error> { let addr: crate::IpAddress = match remote.ip() { diff --git a/embassy-stm32-wpan/src/mac/driver.rs b/embassy-stm32-wpan/src/mac/driver.rs index 5b9d5daf4..41cca09e3 100644 --- a/embassy-stm32-wpan/src/mac/driver.rs +++ b/embassy-stm32-wpan/src/mac/driver.rs @@ -23,8 +23,14 @@ impl<'d> Driver<'d> { impl<'d> embassy_net_driver::Driver for Driver<'d> { // type RxToken<'a> = RxToken<'a, 'd> where Self: 'a; // type TxToken<'a> = TxToken<'a, 'd> where Self: 'a; - type RxToken<'a> = RxToken<'d> where Self: 'a; - type TxToken<'a> = TxToken<'d> where Self: 'a; + type RxToken<'a> + = RxToken<'d> + where + Self: 'a; + type TxToken<'a> + = TxToken<'d> + where + Self: 'a; fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { if self.runner.rx_channel.poll_ready_to_receive(cx).is_ready() diff --git a/embassy-stm32/src/eth/mod.rs b/embassy-stm32/src/eth/mod.rs index 6442176da..1b875b71a 100644 --- a/embassy-stm32/src/eth/mod.rs +++ b/embassy-stm32/src/eth/mod.rs @@ -74,8 +74,14 @@ impl PacketQueue { static WAKER: AtomicWaker = AtomicWaker::new(); impl<'d, T: Instance, P: PHY> embassy_net_driver::Driver for Ethernet<'d, T, P> { - type RxToken<'a> = RxToken<'a, 'd> where Self: 'a; - type TxToken<'a> = TxToken<'a, 'd> where Self: 'a; + type RxToken<'a> + = RxToken<'a, 'd> + where + Self: 'a; + type TxToken<'a> + = TxToken<'a, 'd> + where + Self: 'a; fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { WAKER.register(cx.waker()); From 6862ac56cb1c1a29bad3e872b45386774c8edd58 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: Mon, 16 Sep 2024 11:02:27 +0400 Subject: [PATCH 068/144] Stm32: implement async flush for UART --- embassy-stm32/src/usart/mod.rs | 71 ++++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 12 deletions(-) diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index 6f838cce5..333e01e36 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -69,6 +69,12 @@ unsafe fn on_interrupt(r: Regs, s: &'static State) { // disable idle line detection w.set_idleie(false); }); + } else if cr1.tcie() && sr.tc() { + // Transmission complete detected + r.cr1().modify(|w| { + // disable Transmission complete interrupt + w.set_tcie(false); + }); } else if cr1.rxneie() { // We cannot check the RXNE flag as it is auto-cleared by the DMA controller @@ -420,7 +426,7 @@ impl<'d> UartTx<'d, Async> { /// Wait until transmission complete pub async fn flush(&mut self) -> Result<(), Error> { - self.blocking_flush() + flush(&self.info, &self.state).await } } @@ -531,16 +537,40 @@ impl<'d, M: Mode> UartTx<'d, M> { } } +/// Wait until transmission complete +async fn flush(info: &Info, state: &State) -> Result<(), Error> { + let r = info.regs; + if r.cr1().read().te() && !sr(r).read().tc() { + r.cr1().modify(|w| { + // enable Transmission Complete interrupt + w.set_tcie(true); + }); + + compiler_fence(Ordering::SeqCst); + + // future which completes when Transmission complete is detected + let abort = poll_fn(move |cx| { + state.rx_waker.register(cx.waker()); + + let sr = sr(r).read(); + if sr.tc() { + // Transmission complete detected + return Poll::Ready(()); + } + + Poll::Pending + }); + + abort.await; + } + + Ok(()) +} + fn blocking_flush(info: &Info) -> Result<(), Error> { let r = info.regs; - while !sr(r).read().tc() {} - - // Disable Transmitter and enable receiver after transmission complete for Half-Duplex mode - if r.cr3().read().hdsel() { - r.cr1().modify(|reg| { - reg.set_te(false); - reg.set_re(true); - }); + if r.cr1().read().te() { + while !sr(r).read().tc() {} } Ok(()) @@ -621,7 +651,13 @@ impl<'d> UartRx<'d, Async> { // 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)?; + flush(&self.info, &self.state).await?; + + // Disable Transmitter and enable Receiver after flush + r.cr1().modify(|reg| { + reg.set_re(true); + reg.set_te(false); + }); } // make sure USART state is restored to neutral state when this future is dropped @@ -960,6 +996,12 @@ impl<'d, M: Mode> UartRx<'d, M> { // It prevents reading of bytes which have just been written. if r.cr3().read().hdsel() && r.cr1().read().te() { blocking_flush(self.info)?; + + // Disable Transmitter and enable Receiver after flush + r.cr1().modify(|reg| { + reg.set_re(true); + reg.set_te(false); + }); } for b in buffer { @@ -1155,6 +1197,11 @@ impl<'d> Uart<'d, Async> { self.tx.write(buffer).await } + /// Wait until transmission complete + pub async fn flush(&mut self) -> Result<(), Error> { + self.tx.flush().await + } + /// Perform an asynchronous read into `buffer` pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { self.rx.read(buffer).await @@ -1733,7 +1780,7 @@ impl embedded_io_async::Write for Uart<'_, Async> { } async fn flush(&mut self) -> Result<(), Self::Error> { - self.blocking_flush() + self.flush().await } } @@ -1744,7 +1791,7 @@ impl embedded_io_async::Write for UartTx<'_, Async> { } async fn flush(&mut self) -> Result<(), Self::Error> { - self.blocking_flush() + self.flush().await } } From ad5f7bf6f72f6e1ff512b29eaa843b0dae58d931 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 14 Oct 2024 12:43:38 +0200 Subject: [PATCH 069/144] tests: remove deprecated -Cinline-threshold. --- tests/rp/.cargo/config.toml | 1 - tests/stm32/.cargo/config.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/rp/.cargo/config.toml b/tests/rp/.cargo/config.toml index de7bb0e56..4337924cc 100644 --- a/tests/rp/.cargo/config.toml +++ b/tests/rp/.cargo/config.toml @@ -11,7 +11,6 @@ runner = "teleprobe client run" rustflags = [ # Code-size optimizations. #"-Z", "trap-unreachable=no", - "-C", "inline-threshold=5", "-C", "no-vectorize-loops", ] diff --git a/tests/stm32/.cargo/config.toml b/tests/stm32/.cargo/config.toml index 8752da59b..d94342598 100644 --- a/tests/stm32/.cargo/config.toml +++ b/tests/stm32/.cargo/config.toml @@ -9,7 +9,6 @@ runner = "teleprobe client run" rustflags = [ # Code-size optimizations. #"-Z", "trap-unreachable=no", - "-C", "inline-threshold=5", "-C", "no-vectorize-loops", ] From 014583aaa5dd10d4580aa24ec9b9f2ddb963559a Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 14 Oct 2024 12:43:59 +0200 Subject: [PATCH 070/144] tests/stm32: add uart async and blocking flush test. --- tests/stm32/src/bin/usart.rs | 7 +++++++ tests/stm32/src/bin/usart_dma.rs | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/tests/stm32/src/bin/usart.rs b/tests/stm32/src/bin/usart.rs index 53da30fff..2f601ad0e 100644 --- a/tests/stm32/src/bin/usart.rs +++ b/tests/stm32/src/bin/usart.rs @@ -33,6 +33,13 @@ async fn main(_spawner: Spawner) { let mut buf = [0; 2]; usart.blocking_read(&mut buf).unwrap(); assert_eq!(buf, data); + + // Test flush doesn't hang. + usart.blocking_write(&data).unwrap(); + usart.blocking_flush().unwrap(); + + // Test flush doesn't hang if there's nothing to flush + usart.blocking_flush().unwrap(); } // Test error handling with with an overflow error diff --git a/tests/stm32/src/bin/usart_dma.rs b/tests/stm32/src/bin/usart_dma.rs index 266b81809..a34498376 100644 --- a/tests/stm32/src/bin/usart_dma.rs +++ b/tests/stm32/src/bin/usart_dma.rs @@ -51,6 +51,23 @@ async fn main(_spawner: Spawner) { assert_eq!(tx_buf, rx_buf); } + // Test flush doesn't hang. Check multiple combinations of async+blocking. + tx.write(&tx_buf).await.unwrap(); + tx.flush().await.unwrap(); + tx.flush().await.unwrap(); + + tx.write(&tx_buf).await.unwrap(); + tx.blocking_flush().unwrap(); + tx.flush().await.unwrap(); + + tx.blocking_write(&tx_buf).unwrap(); + tx.blocking_flush().unwrap(); + tx.flush().await.unwrap(); + + tx.blocking_write(&tx_buf).unwrap(); + tx.flush().await.unwrap(); + tx.blocking_flush().unwrap(); + info!("Test OK"); cortex_m::asm::bkpt(); } From 2b10caafd488987b6cf9a2cd69820c81d0a0826e Mon Sep 17 00:00:00 2001 From: Alexandros Liarokapis Date: Sun, 15 Sep 2024 18:47:33 +0300 Subject: [PATCH 071/144] stm32: initial support for alternative ringbuffer implementation --- embassy-stm32/Cargo.toml | 2 + embassy-stm32/src/dma/dma_bdma.rs | 4 - embassy-stm32/src/dma/ringbuffer.rs | 668 ------------------ embassy-stm32/src/dma/ringbuffer/mod.rs | 293 ++++++++ embassy-stm32/src/dma/ringbuffer/tests/mod.rs | 165 +++++ .../src/dma/ringbuffer/tests/prop_test/mod.rs | 50 ++ .../dma/ringbuffer/tests/prop_test/reader.rs | 122 ++++ .../dma/ringbuffer/tests/prop_test/writer.rs | 121 ++++ 8 files changed, 753 insertions(+), 672 deletions(-) delete mode 100644 embassy-stm32/src/dma/ringbuffer.rs create mode 100644 embassy-stm32/src/dma/ringbuffer/mod.rs create mode 100644 embassy-stm32/src/dma/ringbuffer/tests/mod.rs create mode 100644 embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs create mode 100644 embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs create mode 100644 embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 8fc8da006..53ec1b27f 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -93,6 +93,8 @@ aligned = "0.4.1" [dev-dependencies] critical-section = { version = "1.1", features = ["std"] } +proptest = "1.5.0" +proptest-state-machine = "0.3.0" [build-dependencies] proc-macro2 = "1.0.36" diff --git a/embassy-stm32/src/dma/dma_bdma.rs b/embassy-stm32/src/dma/dma_bdma.rs index d10b5554f..a43137c6e 100644 --- a/embassy-stm32/src/dma/dma_bdma.rs +++ b/embassy-stm32/src/dma/dma_bdma.rs @@ -763,10 +763,6 @@ impl<'a> DmaCtrl for DmaCtrlImpl<'a> { self.0.get_remaining_transfers() as _ } - fn get_complete_count(&self) -> usize { - STATE[self.0.id as usize].complete_count.load(Ordering::Acquire) - } - fn reset_complete_count(&mut self) -> usize { let state = &STATE[self.0.id as usize]; #[cfg(not(armv6m))] diff --git a/embassy-stm32/src/dma/ringbuffer.rs b/embassy-stm32/src/dma/ringbuffer.rs deleted file mode 100644 index 23f1d67d5..000000000 --- a/embassy-stm32/src/dma/ringbuffer.rs +++ /dev/null @@ -1,668 +0,0 @@ -#![cfg_attr(gpdma, allow(unused))] - -use core::future::poll_fn; -use core::ops::Range; -use core::sync::atomic::{compiler_fence, Ordering}; -use core::task::{Poll, Waker}; - -use super::word::Word; - -/// A "read-only" ring-buffer to be used together with the DMA controller which -/// writes in a circular way, "uncontrolled" to the buffer. -/// -/// A snapshot of the ring buffer state can be attained by setting the `ndtr` field -/// to the current register value. `ndtr` describes the current position of the DMA -/// write. -/// -/// # Buffer layout -/// -/// ```text -/// Without wraparound: With wraparound: -/// -/// + buf +--- NDTR ---+ + buf +---------- NDTR ----------+ -/// | | | | | | -/// v v v v v v -/// +-----------------------------------------+ +-----------------------------------------+ -/// |oooooooooooXXXXXXXXXXXXXXXXoooooooooooooo| |XXXXXXXXXXXXXooooooooooooXXXXXXXXXXXXXXXX| -/// +-----------------------------------------+ +-----------------------------------------+ -/// ^ ^ ^ ^ ^ ^ -/// | | | | | | -/// +- start --+ | +- end ------+ | -/// | | | | -/// +- end --------------------+ +- start ----------------+ -/// ``` -pub struct ReadableDmaRingBuffer<'a, W: Word> { - pub(crate) dma_buf: &'a mut [W], - start: usize, -} - -#[derive(Debug, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct OverrunError; - -pub trait DmaCtrl { - /// Get the NDTR register value, i.e. the space left in the underlying - /// buffer until the dma writer wraps. - fn get_remaining_transfers(&self) -> usize; - - /// Get the transfer completed counter. - /// This counter is incremented by the dma controller when NDTR is reloaded, - /// i.e. when the writing wraps. - fn get_complete_count(&self) -> usize; - - /// Reset the transfer completed counter to 0 and return the value just prior to the reset. - fn reset_complete_count(&mut self) -> usize; - - /// Set the waker for a running poll_fn - fn set_waker(&mut self, waker: &Waker); -} - -impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { - pub fn new(dma_buf: &'a mut [W]) -> Self { - Self { dma_buf, start: 0 } - } - - /// Reset the ring buffer to its initial state - pub fn clear(&mut self, dma: &mut impl DmaCtrl) { - self.start = 0; - dma.reset_complete_count(); - } - - /// The capacity of the ringbuffer - pub const fn cap(&self) -> usize { - self.dma_buf.len() - } - - /// The current position of the ringbuffer - fn pos(&self, dma: &mut impl DmaCtrl) -> usize { - self.cap() - dma.get_remaining_transfers() - } - - /// Read an exact number of elements from the ringbuffer. - /// - /// Returns the remaining number of elements available for immediate reading. - /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. - /// - /// Async/Wake Behavior: - /// The underlying DMA peripheral only can wake us when its buffer pointer has reached the halfway point, - /// and when it wraps around. This means that when called with a buffer of length 'M', when this - /// ring buffer was created with a buffer of size 'N': - /// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source. - /// - Otherwise, this function may need up to N/2 extra elements to arrive before returning. - pub async fn read_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &mut [W]) -> Result { - let mut read_data = 0; - let buffer_len = buffer.len(); - - poll_fn(|cx| { - dma.set_waker(cx.waker()); - - compiler_fence(Ordering::SeqCst); - - match self.read(dma, &mut buffer[read_data..buffer_len]) { - Ok((len, remaining)) => { - read_data += len; - if read_data == buffer_len { - Poll::Ready(Ok(remaining)) - } else { - Poll::Pending - } - } - Err(e) => Poll::Ready(Err(e)), - } - }) - .await - } - - /// Read elements from the ring buffer - /// Return a tuple of the length read and the length remaining in the buffer - /// If not all of the elements were read, then there will be some elements in the buffer remaining - /// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read - /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. - pub fn read(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { - /* - This algorithm is optimistic: we assume we haven't overrun more than a full buffer and then check - after we've done our work to see we have. This is because on stm32, an interrupt is not guaranteed - to fire in the same clock cycle that a register is read, so checking get_complete_count early does - not yield relevant information. - - Therefore, the only variable we really need to know is ndtr. If the dma has overrun by more than a full - buffer, we will do a bit more work than we have to, but algorithms should not be optimized for error - conditions. - - After we've done our work, we confirm that we haven't overrun more than a full buffer, and also that - the dma has not overrun within the data we could have copied. We check the data we could have copied - rather than the data we actually copied because it costs nothing and confirms an error condition - earlier. - */ - let end = self.pos(dma); - if self.start == end && dma.get_complete_count() == 0 { - // No elements are available in the buffer - Ok((0, self.cap())) - } else if self.start < end { - // The available, unread portion in the ring buffer DOES NOT wrap - // Copy out the elements from the dma buffer - let len = self.copy_to(buf, self.start..end); - - compiler_fence(Ordering::SeqCst); - - /* - first, check if the dma has wrapped at all if it's after end - or more than once if it's before start - - this is in a critical section to try to reduce mushy behavior. - it's not ideal but it's the best we can do - - then, get the current position of of the dma write and check - if it's inside data we could have copied - */ - let (pos, complete_count) = critical_section::with(|_| (self.pos(dma), dma.get_complete_count())); - if (pos >= self.start && pos < end) || (complete_count > 0 && pos >= end) || complete_count > 1 { - Err(OverrunError) - } else { - self.start = (self.start + len) % self.cap(); - - Ok((len, self.cap() - self.start)) - } - } else if self.start + buf.len() < self.cap() { - // The available, unread portion in the ring buffer DOES wrap - // The DMA writer has wrapped since we last read and is currently - // writing (or the next byte added will be) in the beginning of the ring buffer. - - // The provided read buffer is not large enough to include all elements from the tail of the dma buffer. - - // Copy out from the dma buffer - let len = self.copy_to(buf, self.start..self.cap()); - - compiler_fence(Ordering::SeqCst); - - /* - first, check if the dma has wrapped around more than once - - then, get the current position of of the dma write and check - if it's inside data we could have copied - */ - let pos = self.pos(dma); - if pos > self.start || pos < end || dma.get_complete_count() > 1 { - Err(OverrunError) - } else { - self.start = (self.start + len) % self.cap(); - - Ok((len, self.start + end)) - } - } else { - // The available, unread portion in the ring buffer DOES wrap - // The DMA writer has wrapped since we last read and is currently - // writing (or the next byte added will be) in the beginning of the ring buffer. - - // The provided read buffer is large enough to include all elements from the tail of the dma buffer, - // so the next read will not have any unread tail elements in the ring buffer. - - // Copy out from the dma buffer - let tail = self.copy_to(buf, self.start..self.cap()); - let head = self.copy_to(&mut buf[tail..], 0..end); - - compiler_fence(Ordering::SeqCst); - - /* - first, check if the dma has wrapped around more than once - - then, get the current position of of the dma write and check - if it's inside data we could have copied - */ - let pos = self.pos(dma); - if pos > self.start || pos < end || dma.reset_complete_count() > 1 { - Err(OverrunError) - } else { - self.start = head; - Ok((tail + head, self.cap() - self.start)) - } - } - } - /// Copy from the dma buffer at `data_range` into `buf` - fn copy_to(&mut self, buf: &mut [W], data_range: Range) -> usize { - // Limit the number of elements that can be copied - let length = usize::min(data_range.len(), buf.len()); - - // Copy from dma buffer into read buffer - // We need to do it like this instead of a simple copy_from_slice() because - // reading from a part of memory that may be simultaneously written to is unsafe - unsafe { - let dma_buf = self.dma_buf.as_ptr(); - - for i in 0..length { - buf[i] = core::ptr::read_volatile(dma_buf.offset((data_range.start + i) as isize)); - } - } - - length - } -} - -pub struct WritableDmaRingBuffer<'a, W: Word> { - pub(crate) dma_buf: &'a mut [W], - end: usize, -} - -impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { - pub fn new(dma_buf: &'a mut [W]) -> Self { - Self { dma_buf, end: 0 } - } - - /// Reset the ring buffer to its initial state - pub fn clear(&mut self, dma: &mut impl DmaCtrl) { - self.end = 0; - dma.reset_complete_count(); - } - - /// The capacity of the ringbuffer - pub const fn cap(&self) -> usize { - self.dma_buf.len() - } - - /// The current position of the ringbuffer - fn pos(&self, dma: &mut impl DmaCtrl) -> usize { - self.cap() - dma.get_remaining_transfers() - } - - /// Write elements directly to the buffer. This must be done before the DMA is started - /// or after the buffer has been cleared using `clear()`. - pub fn write_immediate(&mut self, buffer: &[W]) -> Result<(usize, usize), OverrunError> { - if self.end != 0 { - return Err(OverrunError); - } - let written = self.copy_from(buffer, 0..self.cap()); - self.end = written % self.cap(); - Ok((written, self.cap() - written)) - } - - /// Write an exact number of elements to the ringbuffer. - pub async fn write_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &[W]) -> Result { - let mut written_data = 0; - let buffer_len = buffer.len(); - - poll_fn(|cx| { - dma.set_waker(cx.waker()); - - compiler_fence(Ordering::SeqCst); - - match self.write(dma, &buffer[written_data..buffer_len]) { - Ok((len, remaining)) => { - written_data += len; - if written_data == buffer_len { - Poll::Ready(Ok(remaining)) - } else { - Poll::Pending - } - } - Err(e) => Poll::Ready(Err(e)), - } - }) - .await - } - - /// Write elements from the ring buffer - /// Return a tuple of the length written and the capacity remaining to be written in the buffer - pub fn write(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), OverrunError> { - let start = self.pos(dma); - if start > self.end { - // The occupied portion in the ring buffer DOES wrap - let len = self.copy_from(buf, self.end..start); - - compiler_fence(Ordering::SeqCst); - - // Confirm that the DMA is not inside data we could have written - let (pos, complete_count) = critical_section::with(|_| (self.pos(dma), dma.get_complete_count())); - if (pos >= self.end && pos < start) || (complete_count > 0 && pos >= start) || complete_count > 1 { - Err(OverrunError) - } else { - self.end = (self.end + len) % self.cap(); - - Ok((len, self.cap() - (start - self.end))) - } - } else if start == self.end && dma.get_complete_count() == 0 { - Ok((0, 0)) - } else if start <= self.end && self.end + buf.len() < self.cap() { - // The occupied portion in the ring buffer DOES NOT wrap - // and copying elements into the buffer WILL NOT cause it to - - // Copy into the dma buffer - let len = self.copy_from(buf, self.end..self.cap()); - - compiler_fence(Ordering::SeqCst); - - // Confirm that the DMA is not inside data we could have written - let pos = self.pos(dma); - if pos > self.end || pos < start || dma.get_complete_count() > 1 { - Err(OverrunError) - } else { - self.end = (self.end + len) % self.cap(); - - Ok((len, self.cap() - (self.end - start))) - } - } else { - // The occupied portion in the ring buffer DOES NOT wrap - // and copying elements into the buffer WILL cause it to - - let tail = self.copy_from(buf, self.end..self.cap()); - let head = self.copy_from(&buf[tail..], 0..start); - - compiler_fence(Ordering::SeqCst); - - // Confirm that the DMA is not inside data we could have written - let pos = self.pos(dma); - if pos > self.end || pos < start || dma.reset_complete_count() > 1 { - Err(OverrunError) - } else { - self.end = head; - - Ok((tail + head, self.cap() - (start - self.end))) - } - } - } - /// Copy into the dma buffer at `data_range` from `buf` - fn copy_from(&mut self, buf: &[W], data_range: Range) -> usize { - // Limit the number of elements that can be copied - let length = usize::min(data_range.len(), buf.len()); - - // Copy into dma buffer from read buffer - // We need to do it like this instead of a simple copy_from_slice() because - // reading from a part of memory that may be simultaneously written to is unsafe - unsafe { - let dma_buf = self.dma_buf.as_mut_ptr(); - - for i in 0..length { - core::ptr::write_volatile(dma_buf.offset((data_range.start + i) as isize), buf[i]); - } - } - - length - } -} -#[cfg(test)] -mod tests { - use core::array; - use std::{cell, vec}; - - use super::*; - - #[allow(dead_code)] - #[derive(PartialEq, Debug)] - enum TestCircularTransferRequest { - GetCompleteCount(usize), - ResetCompleteCount(usize), - PositionRequest(usize), - } - - struct TestCircularTransfer { - len: usize, - requests: cell::RefCell>, - } - - impl DmaCtrl for TestCircularTransfer { - fn get_remaining_transfers(&self) -> usize { - match self.requests.borrow_mut().pop().unwrap() { - TestCircularTransferRequest::PositionRequest(pos) => { - let len = self.len; - - assert!(len >= pos); - - len - pos - } - _ => unreachable!(), - } - } - - fn get_complete_count(&self) -> usize { - match self.requests.borrow_mut().pop().unwrap() { - TestCircularTransferRequest::GetCompleteCount(complete_count) => complete_count, - _ => unreachable!(), - } - } - - fn reset_complete_count(&mut self) -> usize { - match self.requests.get_mut().pop().unwrap() { - TestCircularTransferRequest::ResetCompleteCount(complete_count) => complete_count, - _ => unreachable!(), - } - } - - fn set_waker(&mut self, waker: &Waker) {} - } - - impl TestCircularTransfer { - pub fn new(len: usize) -> Self { - Self { - requests: cell::RefCell::new(vec![]), - len, - } - } - - pub fn setup(&self, mut requests: vec::Vec) { - requests.reverse(); - self.requests.replace(requests); - } - } - - #[test] - fn empty_and_read_not_started() { - let mut dma_buf = [0u8; 16]; - let ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf); - - assert_eq!(0, ringbuf.start); - } - - #[test] - fn can_read() { - let mut dma = TestCircularTransfer::new(16); - - let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 - let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf); - - assert_eq!(0, ringbuf.start); - assert_eq!(16, ringbuf.cap()); - - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(8), - TestCircularTransferRequest::PositionRequest(10), - TestCircularTransferRequest::GetCompleteCount(0), - ]); - let mut buf = [0; 2]; - assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!([0, 1], buf); - assert_eq!(2, ringbuf.start); - - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(10), - TestCircularTransferRequest::PositionRequest(12), - TestCircularTransferRequest::GetCompleteCount(0), - ]); - let mut buf = [0; 2]; - assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!([2, 3], buf); - assert_eq!(4, ringbuf.start); - - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(12), - TestCircularTransferRequest::PositionRequest(14), - TestCircularTransferRequest::GetCompleteCount(0), - ]); - let mut buf = [0; 8]; - assert_eq!(8, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!([4, 5, 6, 7, 8, 9], buf[..6]); - assert_eq!(12, ringbuf.start); - } - - #[test] - fn can_read_with_wrap() { - let mut dma = TestCircularTransfer::new(16); - - let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 - let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf); - - assert_eq!(0, ringbuf.start); - assert_eq!(16, ringbuf.cap()); - - /* - Read to close to the end of the buffer - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(14), - TestCircularTransferRequest::PositionRequest(16), - TestCircularTransferRequest::GetCompleteCount(0), - ]); - let mut buf = [0; 14]; - assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!(14, ringbuf.start); - - /* - Now, read around the buffer - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(6), - TestCircularTransferRequest::PositionRequest(8), - TestCircularTransferRequest::ResetCompleteCount(1), - ]); - let mut buf = [0; 6]; - assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!(4, ringbuf.start); - } - - #[test] - fn can_read_when_dma_writer_is_wrapped_and_read_does_not_wrap() { - let mut dma = TestCircularTransfer::new(16); - - let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 - let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf); - - assert_eq!(0, ringbuf.start); - assert_eq!(16, ringbuf.cap()); - - /* - Read to close to the end of the buffer - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(14), - TestCircularTransferRequest::PositionRequest(16), - TestCircularTransferRequest::GetCompleteCount(0), - ]); - let mut buf = [0; 14]; - assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!(14, ringbuf.start); - - /* - Now, read to the end of the buffer - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(6), - TestCircularTransferRequest::PositionRequest(8), - TestCircularTransferRequest::ResetCompleteCount(1), - ]); - let mut buf = [0; 2]; - assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!(0, ringbuf.start); - } - - #[test] - fn can_read_when_dma_writer_wraps_once_with_same_ndtr() { - let mut dma = TestCircularTransfer::new(16); - - let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 - let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf); - - assert_eq!(0, ringbuf.start); - assert_eq!(16, ringbuf.cap()); - - /* - Read to about the middle of the buffer - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(6), - TestCircularTransferRequest::PositionRequest(6), - TestCircularTransferRequest::GetCompleteCount(0), - ]); - let mut buf = [0; 6]; - assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!(6, ringbuf.start); - - /* - Now, wrap the DMA controller around - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(6), - TestCircularTransferRequest::GetCompleteCount(1), - TestCircularTransferRequest::PositionRequest(6), - TestCircularTransferRequest::GetCompleteCount(1), - ]); - let mut buf = [0; 6]; - assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!(12, ringbuf.start); - } - - #[test] - fn cannot_read_when_dma_writer_overwrites_during_not_wrapping_read() { - let mut dma = TestCircularTransfer::new(16); - - let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 - let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf); - - assert_eq!(0, ringbuf.start); - assert_eq!(16, ringbuf.cap()); - - /* - Read a few bytes - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(2), - TestCircularTransferRequest::PositionRequest(2), - TestCircularTransferRequest::GetCompleteCount(0), - ]); - let mut buf = [0; 6]; - assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!(2, ringbuf.start); - - /* - Now, overtake the reader - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(4), - TestCircularTransferRequest::PositionRequest(6), - TestCircularTransferRequest::GetCompleteCount(1), - ]); - let mut buf = [0; 6]; - assert_eq!(OverrunError, ringbuf.read(&mut dma, &mut buf).unwrap_err()); - } - - #[test] - fn cannot_read_when_dma_writer_overwrites_during_wrapping_read() { - let mut dma = TestCircularTransfer::new(16); - - let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 - let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf); - - assert_eq!(0, ringbuf.start); - assert_eq!(16, ringbuf.cap()); - - /* - Read to close to the end of the buffer - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(14), - TestCircularTransferRequest::PositionRequest(16), - TestCircularTransferRequest::GetCompleteCount(0), - ]); - let mut buf = [0; 14]; - assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!(14, ringbuf.start); - - /* - Now, overtake the reader - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(8), - TestCircularTransferRequest::PositionRequest(10), - TestCircularTransferRequest::ResetCompleteCount(2), - ]); - let mut buf = [0; 6]; - assert_eq!(OverrunError, ringbuf.read(&mut dma, &mut buf).unwrap_err()); - } -} diff --git a/embassy-stm32/src/dma/ringbuffer/mod.rs b/embassy-stm32/src/dma/ringbuffer/mod.rs new file mode 100644 index 000000000..10a9ff975 --- /dev/null +++ b/embassy-stm32/src/dma/ringbuffer/mod.rs @@ -0,0 +1,293 @@ +#![cfg_attr(gpdma, allow(unused))] + +use core::future::poll_fn; +use core::task::{Poll, Waker}; + +use crate::dma::word::Word; + +pub trait DmaCtrl { + /// Get the NDTR register value, i.e. the space left in the underlying + /// buffer until the dma writer wraps. + fn get_remaining_transfers(&self) -> usize; + + /// Reset the transfer completed counter to 0 and return the value just prior to the reset. + fn reset_complete_count(&mut self) -> usize; + + /// Set the waker for a running poll_fn + fn set_waker(&mut self, waker: &Waker); +} + +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OverrunError; + +#[derive(Debug, Clone, Copy, Default)] +struct DmaIndex { + completion_count: usize, + pos: usize, +} + +fn pos(cap: usize, dma: &impl DmaCtrl) -> usize { + cap - dma.get_remaining_transfers() +} + +impl DmaIndex { + fn reset(&mut self) { + self.pos = 0; + self.completion_count = 0; + } + + fn as_index(&self, cap: usize, offset: usize) -> usize { + (self.pos + offset) % cap + } + + fn dma_sync(&mut self, cap: usize, dma: &mut impl DmaCtrl) { + let fst_pos = pos(cap, dma); + let fst_count = dma.reset_complete_count(); + let pos = pos(cap, dma); + + let wrap_count = if pos >= fst_pos { + fst_count + } else { + fst_count + dma.reset_complete_count() + }; + + self.pos = pos; + self.completion_count += wrap_count; + } + + fn advance(&mut self, cap: usize, steps: usize) { + let next = self.pos + steps; + self.completion_count += next / cap; + self.pos = next % cap; + } + + fn normalize(lhs: &mut DmaIndex, rhs: &mut DmaIndex) { + let min_count = lhs.completion_count.min(rhs.completion_count); + lhs.completion_count -= min_count; + rhs.completion_count -= min_count; + } + + fn diff(&mut self, cap: usize, rhs: &mut DmaIndex) -> isize { + Self::normalize(self, rhs); + (self.completion_count * cap + self.pos) as isize - (rhs.completion_count * cap + rhs.pos) as isize + } +} + +pub struct ReadableDmaRingBuffer<'a, W: Word> { + dma_buf: &'a mut [W], + write_index: DmaIndex, + read_index: DmaIndex, +} + +impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { + /// Construct an empty buffer. + pub fn new(dma_buf: &'a mut [W]) -> Self { + Self { + dma_buf, + write_index: Default::default(), + read_index: Default::default(), + } + } + + /// Reset the ring buffer to its initial state + pub fn clear(&mut self, dma: &mut impl DmaCtrl) { + dma.reset_complete_count(); + self.write_index.reset(); + self.update_dma_index(dma); + self.read_index = self.write_index; + } + + /// The capacity of the ringbuffer + pub const fn cap(&self) -> usize { + self.dma_buf.len() + } + + /// Read elements from the ring buffer + /// Return a tuple of the length read and the length remaining in the buffer + /// If not all of the elements were read, then there will be some elements in the buffer remaining + /// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read + /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. + pub fn read(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { + let readable = self.margin(dma)?.min(buf.len()); + for i in 0..readable { + buf[i] = self.read_buf(i); + } + let available = self.margin(dma)?; + self.read_index.advance(self.cap(), readable); + Ok((readable, available - readable)) + } + + /// Read an exact number of elements from the ringbuffer. + /// + /// Returns the remaining number of elements available for immediate reading. + /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. + /// + /// Async/Wake Behavior: + /// The underlying DMA peripheral only can wake us when its buffer pointer has reached the halfway point, + /// and when it wraps around. This means that when called with a buffer of length 'M', when this + /// ring buffer was created with a buffer of size 'N': + /// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source. + /// - Otherwise, this function may need up to N/2 extra elements to arrive before returning. + pub async fn read_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &mut [W]) -> Result { + let mut read_data = 0; + let buffer_len = buffer.len(); + + poll_fn(|cx| { + dma.set_waker(cx.waker()); + + match self.read(dma, &mut buffer[read_data..buffer_len]) { + Ok((len, remaining)) => { + read_data += len; + if read_data == buffer_len { + Poll::Ready(Ok(remaining)) + } else { + Poll::Pending + } + } + Err(e) => Poll::Ready(Err(e)), + } + }) + .await + } + + fn update_dma_index(&mut self, dma: &mut impl DmaCtrl) { + self.write_index.dma_sync(self.cap(), dma) + } + + fn read_buf(&self, offset: usize) -> W { + unsafe { + core::ptr::read_volatile( + self.dma_buf + .as_ptr() + .offset(self.read_index.as_index(self.cap(), offset) as isize), + ) + } + } + + /// Returns available dma samples + fn margin(&mut self, dma: &mut impl DmaCtrl) -> Result { + self.update_dma_index(dma); + + let diff: usize = self + .write_index + .diff(self.cap(), &mut self.read_index) + .try_into() + .unwrap(); + + if diff > self.cap() { + Err(OverrunError) + } else { + Ok(diff) + } + } +} + +pub struct WritableDmaRingBuffer<'a, W: Word> { + dma_buf: &'a mut [W], + read_index: DmaIndex, + write_index: DmaIndex, +} + +impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { + /// Construct a ringbuffer filled with the given buffer data. + pub fn new(dma_buf: &'a mut [W]) -> Self { + let len = dma_buf.len(); + Self { + dma_buf, + read_index: Default::default(), + write_index: DmaIndex { + completion_count: 0, + pos: len, + }, + } + } + + /// Reset the ring buffer to its initial state. The buffer after the reset will be full. + pub fn clear(&mut self, dma: &mut impl DmaCtrl) { + dma.reset_complete_count(); + self.read_index.reset(); + self.update_dma_index(dma); + self.write_index = self.read_index; + self.write_index.advance(self.cap(), self.cap()); + } + + /// Get the capacity of the ringbuffer. + pub const fn cap(&self) -> usize { + self.dma_buf.len() + } + + /// Append data to the ring buffer. + /// Returns a tuple of the data written and the remaining write capacity in the buffer. + pub fn write(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), OverrunError> { + let writable = self.margin(dma)?.min(buf.len()); + for i in 0..writable { + self.write_buf(i, buf[i]); + } + let available = self.margin(dma)?; + self.write_index.advance(self.cap(), writable); + Ok((writable, available - writable)) + } + + /// Write elements directly to the buffer. + pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), OverrunError> { + for (i, data) in buf.iter().enumerate() { + self.write_buf(i, *data) + } + let written = buf.len().min(self.cap()); + Ok((written, self.cap() - written)) + } + + /// Write an exact number of elements to the ringbuffer. + pub async fn write_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &[W]) -> Result { + let mut written_data = 0; + let buffer_len = buffer.len(); + + poll_fn(|cx| { + dma.set_waker(cx.waker()); + + match self.write(dma, &buffer[written_data..buffer_len]) { + Ok((len, remaining)) => { + written_data += len; + if written_data == buffer_len { + Poll::Ready(Ok(remaining)) + } else { + Poll::Pending + } + } + Err(e) => Poll::Ready(Err(e)), + } + }) + .await + } + + fn update_dma_index(&mut self, dma: &mut impl DmaCtrl) { + self.read_index.dma_sync(self.cap(), dma); + } + + fn write_buf(&mut self, offset: usize, value: W) { + unsafe { + core::ptr::write_volatile( + self.dma_buf + .as_mut_ptr() + .offset(self.write_index.as_index(self.cap(), offset) as isize), + value, + ) + } + } + + fn margin(&mut self, dma: &mut impl DmaCtrl) -> Result { + self.update_dma_index(dma); + + let diff = self.write_index.diff(self.cap(), &mut self.read_index); + + if diff < 0 { + Err(OverrunError) + } else { + Ok(self.cap().saturating_sub(diff as usize)) + } + } +} + +#[cfg(test)] +mod tests; diff --git a/embassy-stm32/src/dma/ringbuffer/tests/mod.rs b/embassy-stm32/src/dma/ringbuffer/tests/mod.rs new file mode 100644 index 000000000..9768e1df8 --- /dev/null +++ b/embassy-stm32/src/dma/ringbuffer/tests/mod.rs @@ -0,0 +1,165 @@ +use std::{cell, vec}; + +use super::*; + +#[allow(dead_code)] +#[derive(PartialEq, Debug)] +enum TestCircularTransferRequest { + ResetCompleteCount(usize), + PositionRequest(usize), +} + +struct TestCircularTransfer { + len: usize, + requests: cell::RefCell>, +} + +impl DmaCtrl for TestCircularTransfer { + fn get_remaining_transfers(&self) -> usize { + match self.requests.borrow_mut().pop().unwrap() { + TestCircularTransferRequest::PositionRequest(pos) => { + let len = self.len; + + assert!(len >= pos); + + len - pos + } + _ => unreachable!(), + } + } + + fn reset_complete_count(&mut self) -> usize { + match self.requests.get_mut().pop().unwrap() { + TestCircularTransferRequest::ResetCompleteCount(complete_count) => complete_count, + _ => unreachable!(), + } + } + + fn set_waker(&mut self, _waker: &Waker) {} +} + +impl TestCircularTransfer { + pub fn new(len: usize) -> Self { + Self { + requests: cell::RefCell::new(vec![]), + len, + } + } + + pub fn setup(&self, mut requests: vec::Vec) { + requests.reverse(); + self.requests.replace(requests); + } +} + +const CAP: usize = 16; + +#[test] +fn dma_index_dma_sync_syncs_position_to_last_read_if_sync_takes_place_on_same_dma_cycle() { + let mut dma = TestCircularTransfer::new(CAP); + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(4), + TestCircularTransferRequest::ResetCompleteCount(0), + TestCircularTransferRequest::PositionRequest(7), + ]); + let mut index = DmaIndex::default(); + index.dma_sync(CAP, &mut dma); + assert_eq!(index.completion_count, 0); + assert_eq!(index.pos, 7); +} + +#[test] +fn dma_index_dma_sync_updates_completion_count_properly_if_sync_takes_place_on_same_dma_cycle() { + let mut dma = TestCircularTransfer::new(CAP); + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(4), + TestCircularTransferRequest::ResetCompleteCount(2), + TestCircularTransferRequest::PositionRequest(7), + ]); + let mut index = DmaIndex::default(); + index.completion_count = 1; + index.dma_sync(CAP, &mut dma); + assert_eq!(index.completion_count, 3); + assert_eq!(index.pos, 7); +} + +#[test] +fn dma_index_dma_sync_syncs_to_last_position_if_reads_occur_on_different_dma_cycles() { + let mut dma = TestCircularTransfer::new(CAP); + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(10), + TestCircularTransferRequest::ResetCompleteCount(1), + TestCircularTransferRequest::PositionRequest(5), + TestCircularTransferRequest::ResetCompleteCount(0), + ]); + let mut index = DmaIndex::default(); + index.dma_sync(CAP, &mut dma); + assert_eq!(index.completion_count, 1); + assert_eq!(index.pos, 5); +} + +#[test] +fn dma_index_dma_sync_detects_new_cycle_if_later_position_is_less_than_first_and_first_completion_count_occurs_on_first_cycle( +) { + let mut dma = TestCircularTransfer::new(CAP); + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(10), + TestCircularTransferRequest::ResetCompleteCount(1), + TestCircularTransferRequest::PositionRequest(5), + TestCircularTransferRequest::ResetCompleteCount(1), + ]); + let mut index = DmaIndex::default(); + index.completion_count = 1; + index.dma_sync(CAP, &mut dma); + assert_eq!(index.completion_count, 3); + assert_eq!(index.pos, 5); +} + +#[test] +fn dma_index_dma_sync_detects_new_cycle_if_later_position_is_less_than_first_and_first_completion_count_occurs_on_later_cycle( +) { + let mut dma = TestCircularTransfer::new(CAP); + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(10), + TestCircularTransferRequest::ResetCompleteCount(2), + TestCircularTransferRequest::PositionRequest(5), + TestCircularTransferRequest::ResetCompleteCount(0), + ]); + let mut index = DmaIndex::default(); + index.completion_count = 1; + index.dma_sync(CAP, &mut dma); + assert_eq!(index.completion_count, 3); + assert_eq!(index.pos, 5); +} + +#[test] +fn dma_index_as_index_returns_index_mod_cap_by_default() { + let index = DmaIndex::default(); + assert_eq!(index.as_index(CAP, 0), 0); + assert_eq!(index.as_index(CAP, 1), 1); + assert_eq!(index.as_index(CAP, 2), 2); + assert_eq!(index.as_index(CAP, 3), 3); + assert_eq!(index.as_index(CAP, 4), 4); + assert_eq!(index.as_index(CAP, CAP), 0); + assert_eq!(index.as_index(CAP, CAP + 1), 1); +} + +#[test] +fn dma_index_advancing_increases_as_index() { + let mut index = DmaIndex::default(); + assert_eq!(index.as_index(CAP, 0), 0); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 1); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 2); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 3); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 4); + index.advance(CAP, CAP - 4); + assert_eq!(index.as_index(CAP, 0), 0); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 1); +} + +mod prop_test; diff --git a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs new file mode 100644 index 000000000..661fb1728 --- /dev/null +++ b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs @@ -0,0 +1,50 @@ +use std::task::Waker; + +use proptest::prop_oneof; +use proptest::strategy::{self, BoxedStrategy, Strategy as _}; +use proptest_state_machine::{prop_state_machine, ReferenceStateMachine, StateMachineTest}; + +use super::*; + +const CAP: usize = 128; + +#[derive(Debug, Default)] +struct DmaMock { + pos: usize, + wraps: usize, +} + +impl DmaMock { + pub fn advance(&mut self, steps: usize) { + let next = self.pos + steps; + self.pos = next % CAP; + self.wraps += next / CAP; + } +} + +impl DmaCtrl for DmaMock { + fn get_remaining_transfers(&self) -> usize { + CAP - self.pos + } + + fn reset_complete_count(&mut self) -> usize { + core::mem::replace(&mut self.wraps, 0) + } + + fn set_waker(&mut self, _waker: &Waker) {} +} + +#[derive(Debug, Clone)] +enum Status { + Available(usize), + Failed, +} + +impl Status { + pub fn new(capacity: usize) -> Self { + Self::Available(capacity) + } +} + +mod reader; +mod writer; diff --git a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs new file mode 100644 index 000000000..6555ebfb0 --- /dev/null +++ b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs @@ -0,0 +1,122 @@ +use core::fmt::Debug; + +use super::*; + +#[derive(Debug, Clone)] +enum ReaderTransition { + Write(usize), + Clear, + ReadUpTo(usize), +} + +struct ReaderSM; + +impl ReferenceStateMachine for ReaderSM { + type State = Status; + type Transition = ReaderTransition; + + fn init_state() -> BoxedStrategy { + strategy::Just(Status::new(0)).boxed() + } + + fn transitions(_state: &Self::State) -> BoxedStrategy { + prop_oneof![ + (1..50_usize).prop_map(ReaderTransition::Write), + (1..50_usize).prop_map(ReaderTransition::ReadUpTo), + strategy::Just(ReaderTransition::Clear), + ] + .boxed() + } + + fn apply(status: Self::State, transition: &Self::Transition) -> Self::State { + match (status, transition) { + (_, ReaderTransition::Clear) => Status::Available(0), + (Status::Available(x), ReaderTransition::Write(y)) => { + if x + y > CAP { + Status::Failed + } else { + Status::Available(x + y) + } + } + (Status::Available(x), ReaderTransition::ReadUpTo(y)) => Status::Available(x.saturating_sub(*y)), + (Status::Failed, _) => Status::Failed, + } + } +} + +struct ReaderSut { + status: Status, + buffer: *mut [u8], + producer: DmaMock, + consumer: ReadableDmaRingBuffer<'static, u8>, +} + +impl Debug for ReaderSut { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + ::fmt(&self.producer, f) + } +} + +struct ReaderTest; + +impl StateMachineTest for ReaderTest { + type SystemUnderTest = ReaderSut; + type Reference = ReaderSM; + + fn init_test(ref_status: &::State) -> Self::SystemUnderTest { + let buffer = Box::into_raw(Box::new([0; CAP])); + ReaderSut { + status: ref_status.clone(), + buffer, + producer: DmaMock::default(), + consumer: ReadableDmaRingBuffer::new(unsafe { &mut *buffer }), + } + } + + fn teardown(state: Self::SystemUnderTest) { + unsafe { + let _ = Box::from_raw(state.buffer); + }; + } + + fn apply( + mut sut: Self::SystemUnderTest, + ref_state: &::State, + transition: ::Transition, + ) -> Self::SystemUnderTest { + match transition { + ReaderTransition::Write(x) => sut.producer.advance(x), + ReaderTransition::Clear => { + sut.consumer.clear(&mut sut.producer); + } + ReaderTransition::ReadUpTo(x) => { + let status = sut.status; + let ReaderSut { + ref mut producer, + ref mut consumer, + .. + } = sut; + let mut buf = vec![0; x]; + let res = consumer.read(producer, &mut buf); + match status { + Status::Available(n) => { + let readable = x.min(n); + + assert_eq!(res.unwrap().0, readable); + } + Status::Failed => assert!(res.is_err()), + } + } + } + + ReaderSut { + status: ref_state.clone(), + ..sut + } + } +} + +prop_state_machine! { + #[test] + fn reader_state_test(sequential 1..20 => ReaderTest); +} diff --git a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs new file mode 100644 index 000000000..15f54c672 --- /dev/null +++ b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs @@ -0,0 +1,121 @@ +use core::fmt::Debug; + +use super::*; + +#[derive(Debug, Clone)] +enum WriterTransition { + Read(usize), + WriteUpTo(usize), + Clear, +} + +struct WriterSM; + +impl ReferenceStateMachine for WriterSM { + type State = Status; + type Transition = WriterTransition; + + fn init_state() -> BoxedStrategy { + strategy::Just(Status::new(CAP)).boxed() + } + + fn transitions(_state: &Self::State) -> BoxedStrategy { + prop_oneof![ + (1..50_usize).prop_map(WriterTransition::Read), + (1..50_usize).prop_map(WriterTransition::WriteUpTo), + strategy::Just(WriterTransition::Clear), + ] + .boxed() + } + + fn apply(status: Self::State, transition: &Self::Transition) -> Self::State { + match (status, transition) { + (_, WriterTransition::Clear) => Status::Available(CAP), + (Status::Available(x), WriterTransition::Read(y)) => { + if x < *y { + Status::Failed + } else { + Status::Available(x - y) + } + } + (Status::Available(x), WriterTransition::WriteUpTo(y)) => Status::Available((x + *y).min(CAP)), + (Status::Failed, _) => Status::Failed, + } + } +} + +struct WriterSut { + status: Status, + buffer: *mut [u8], + producer: WritableDmaRingBuffer<'static, u8>, + consumer: DmaMock, +} + +impl Debug for WriterSut { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + ::fmt(&self.consumer, f) + } +} + +struct WriterTest; + +impl StateMachineTest for WriterTest { + type SystemUnderTest = WriterSut; + type Reference = WriterSM; + + fn init_test(ref_status: &::State) -> Self::SystemUnderTest { + let buffer = Box::into_raw(Box::new([0; CAP])); + WriterSut { + status: ref_status.clone(), + buffer, + producer: WritableDmaRingBuffer::new(unsafe { &mut *buffer }), + consumer: DmaMock::default(), + } + } + + fn teardown(state: Self::SystemUnderTest) { + unsafe { + let _ = Box::from_raw(state.buffer); + }; + } + + fn apply( + mut sut: Self::SystemUnderTest, + ref_status: &::State, + transition: ::Transition, + ) -> Self::SystemUnderTest { + match transition { + WriterTransition::Read(x) => sut.consumer.advance(x), + WriterTransition::Clear => { + sut.producer.clear(&mut sut.consumer); + } + WriterTransition::WriteUpTo(x) => { + let status = sut.status; + let WriterSut { + ref mut producer, + ref mut consumer, + .. + } = sut; + let mut buf = vec![0; x]; + let res = producer.write(consumer, &mut buf); + match status { + Status::Available(n) => { + let writable = x.min(CAP - n.min(CAP)); + assert_eq!(res.unwrap().0, writable); + } + Status::Failed => assert!(res.is_err()), + } + } + } + + WriterSut { + status: ref_status.clone(), + ..sut + } + } +} + +prop_state_machine! { + #[test] + fn writer_state_test(sequential 1..20 => WriterTest); +} From f4ec0cb4d4d16bd4e1c242864c5ca828745704c0 Mon Sep 17 00:00:00 2001 From: Alexandros Liarokapis Date: Mon, 23 Sep 2024 02:01:44 +0300 Subject: [PATCH 072/144] simplify and rename ringbuffer methods, make len available --- embassy-stm32/src/dma/ringbuffer/mod.rs | 92 ++++++++++++------------- 1 file changed, 43 insertions(+), 49 deletions(-) diff --git a/embassy-stm32/src/dma/ringbuffer/mod.rs b/embassy-stm32/src/dma/ringbuffer/mod.rs index 10a9ff975..d4d119a69 100644 --- a/embassy-stm32/src/dma/ringbuffer/mod.rs +++ b/embassy-stm32/src/dma/ringbuffer/mod.rs @@ -27,10 +27,6 @@ struct DmaIndex { pos: usize, } -fn pos(cap: usize, dma: &impl DmaCtrl) -> usize { - cap - dma.get_remaining_transfers() -} - impl DmaIndex { fn reset(&mut self) { self.pos = 0; @@ -42,9 +38,9 @@ impl DmaIndex { } fn dma_sync(&mut self, cap: usize, dma: &mut impl DmaCtrl) { - let fst_pos = pos(cap, dma); + let fst_pos = cap - dma.get_remaining_transfers(); let fst_count = dma.reset_complete_count(); - let pos = pos(cap, dma); + let pos = cap - dma.get_remaining_transfers(); let wrap_count = if pos >= fst_pos { fst_count @@ -68,8 +64,7 @@ impl DmaIndex { rhs.completion_count -= min_count; } - fn diff(&mut self, cap: usize, rhs: &mut DmaIndex) -> isize { - Self::normalize(self, rhs); + fn diff(&self, cap: usize, rhs: &DmaIndex) -> isize { (self.completion_count * cap + self.pos) as isize - (rhs.completion_count * cap + rhs.pos) as isize } } @@ -94,26 +89,39 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { pub fn clear(&mut self, dma: &mut impl DmaCtrl) { dma.reset_complete_count(); self.write_index.reset(); - self.update_dma_index(dma); + self.write_index.dma_sync(self.cap(), dma); self.read_index = self.write_index; } - /// The capacity of the ringbuffer + /// Get the full ringbuffer capacity. pub const fn cap(&self) -> usize { self.dma_buf.len() } + /// Get the available readable dma samples. + pub fn len(&self) -> Result { + let diff: usize = self.write_index.diff(self.cap(), &self.read_index).try_into().unwrap(); + + if diff > self.cap() { + Err(OverrunError) + } else { + Ok(diff) + } + } + /// Read elements from the ring buffer /// Return a tuple of the length read and the length remaining in the buffer /// If not all of the elements were read, then there will be some elements in the buffer remaining /// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. pub fn read(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { - let readable = self.margin(dma)?.min(buf.len()); + self.sync_write_index(dma); + let readable = self.len()?.min(buf.len()); for i in 0..readable { buf[i] = self.read_buf(i); } - let available = self.margin(dma)?; + self.sync_write_index(dma); + let available = self.len()?; self.read_index.advance(self.cap(), readable); Ok((readable, available - readable)) } @@ -151,10 +159,6 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { .await } - fn update_dma_index(&mut self, dma: &mut impl DmaCtrl) { - self.write_index.dma_sync(self.cap(), dma) - } - fn read_buf(&self, offset: usize) -> W { unsafe { core::ptr::read_volatile( @@ -165,21 +169,9 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { } } - /// Returns available dma samples - fn margin(&mut self, dma: &mut impl DmaCtrl) -> Result { - self.update_dma_index(dma); - - let diff: usize = self - .write_index - .diff(self.cap(), &mut self.read_index) - .try_into() - .unwrap(); - - if diff > self.cap() { - Err(OverrunError) - } else { - Ok(diff) - } + fn sync_write_index(&mut self, dma: &mut impl DmaCtrl) { + self.write_index.dma_sync(self.cap(), dma); + DmaIndex::normalize(&mut self.write_index, &mut self.read_index); } } @@ -207,12 +199,23 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { pub fn clear(&mut self, dma: &mut impl DmaCtrl) { dma.reset_complete_count(); self.read_index.reset(); - self.update_dma_index(dma); + self.read_index.dma_sync(self.cap(), dma); self.write_index = self.read_index; self.write_index.advance(self.cap(), self.cap()); } - /// Get the capacity of the ringbuffer. + /// Get the remaining writable dma samples. + pub fn len(&self) -> Result { + let diff = self.write_index.diff(self.cap(), &self.read_index); + + if diff < 0 { + Err(OverrunError) + } else { + Ok(self.cap().saturating_sub(diff as usize)) + } + } + + /// Get the full ringbuffer capacity. pub const fn cap(&self) -> usize { self.dma_buf.len() } @@ -220,11 +223,13 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { /// Append data to the ring buffer. /// Returns a tuple of the data written and the remaining write capacity in the buffer. pub fn write(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), OverrunError> { - let writable = self.margin(dma)?.min(buf.len()); + self.sync_read_index(dma); + let writable = self.len()?.min(buf.len()); for i in 0..writable { self.write_buf(i, buf[i]); } - let available = self.margin(dma)?; + self.sync_read_index(dma); + let available = self.len()?; self.write_index.advance(self.cap(), writable); Ok((writable, available - writable)) } @@ -261,10 +266,6 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { .await } - fn update_dma_index(&mut self, dma: &mut impl DmaCtrl) { - self.read_index.dma_sync(self.cap(), dma); - } - fn write_buf(&mut self, offset: usize, value: W) { unsafe { core::ptr::write_volatile( @@ -276,16 +277,9 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { } } - fn margin(&mut self, dma: &mut impl DmaCtrl) -> Result { - self.update_dma_index(dma); - - let diff = self.write_index.diff(self.cap(), &mut self.read_index); - - if diff < 0 { - Err(OverrunError) - } else { - Ok(self.cap().saturating_sub(diff as usize)) - } + fn sync_read_index(&mut self, dma: &mut impl DmaCtrl) { + self.read_index.dma_sync(self.cap(), dma); + DmaIndex::normalize(&mut self.read_index, &mut self.write_index); } } From 85fb890b0025db386459f8b6a573c29f00bf3ed1 Mon Sep 17 00:00:00 2001 From: Alexandros Liarokapis Date: Mon, 23 Sep 2024 02:49:05 +0300 Subject: [PATCH 073/144] add auto-clear functionality to ringbuffer --- embassy-stm32/src/dma/ringbuffer/mod.rs | 56 ++++++++++++------- .../dma/ringbuffer/tests/prop_test/reader.rs | 3 +- .../dma/ringbuffer/tests/prop_test/writer.rs | 3 +- 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/embassy-stm32/src/dma/ringbuffer/mod.rs b/embassy-stm32/src/dma/ringbuffer/mod.rs index d4d119a69..abc01e3cc 100644 --- a/embassy-stm32/src/dma/ringbuffer/mod.rs +++ b/embassy-stm32/src/dma/ringbuffer/mod.rs @@ -85,7 +85,7 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { } } - /// Reset the ring buffer to its initial state + /// Reset the ring buffer to its initial state. pub fn clear(&mut self, dma: &mut impl DmaCtrl) { dma.reset_complete_count(); self.write_index.reset(); @@ -113,17 +113,12 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { /// Return a tuple of the length read and the length remaining in the buffer /// If not all of the elements were read, then there will be some elements in the buffer remaining /// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read - /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. + /// OverrunError is returned if the portion to be read was overwritten by the DMA controller, + /// in which case the rinbuffer will automatically clear itself. pub fn read(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { - self.sync_write_index(dma); - let readable = self.len()?.min(buf.len()); - for i in 0..readable { - buf[i] = self.read_buf(i); - } - self.sync_write_index(dma); - let available = self.len()?; - self.read_index.advance(self.cap(), readable); - Ok((readable, available - readable)) + self.read_raw(dma, buf).inspect_err(|_e| { + self.clear(dma); + }) } /// Read an exact number of elements from the ringbuffer. @@ -159,6 +154,18 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { .await } + fn read_raw(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { + self.sync_write_index(dma); + let readable = self.len()?.min(buf.len()); + for i in 0..readable { + buf[i] = self.read_buf(i); + } + self.sync_write_index(dma); + let available = self.len()?; + self.read_index.advance(self.cap(), readable); + Ok((readable, available - readable)) + } + fn read_buf(&self, offset: usize) -> W { unsafe { core::ptr::read_volatile( @@ -222,16 +229,13 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { /// Append data to the ring buffer. /// Returns a tuple of the data written and the remaining write capacity in the buffer. + /// OverrunError is returned if the portion to be written was previously read by the DMA controller. + /// In this case, the ringbuffer will automatically reset itself, giving a full buffer worth of + /// leeway between the write index and the DMA. pub fn write(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), OverrunError> { - self.sync_read_index(dma); - let writable = self.len()?.min(buf.len()); - for i in 0..writable { - self.write_buf(i, buf[i]); - } - self.sync_read_index(dma); - let available = self.len()?; - self.write_index.advance(self.cap(), writable); - Ok((writable, available - writable)) + self.write_raw(dma, buf).inspect_err(|_e| { + self.clear(dma); + }) } /// Write elements directly to the buffer. @@ -266,6 +270,18 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { .await } + pub fn write_raw(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), OverrunError> { + self.sync_read_index(dma); + let writable = self.len()?.min(buf.len()); + for i in 0..writable { + self.write_buf(i, buf[i]); + } + self.sync_read_index(dma); + let available = self.len()?; + self.write_index.advance(self.cap(), writable); + Ok((writable, available - writable)) + } + fn write_buf(&mut self, offset: usize, value: W) { unsafe { core::ptr::write_volatile( diff --git a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs index 6555ebfb0..6e640a813 100644 --- a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs +++ b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs @@ -38,8 +38,9 @@ impl ReferenceStateMachine for ReaderSM { Status::Available(x + y) } } + (Status::Failed, ReaderTransition::Write(_)) => Status::Failed, (Status::Available(x), ReaderTransition::ReadUpTo(y)) => Status::Available(x.saturating_sub(*y)), - (Status::Failed, _) => Status::Failed, + (Status::Failed, ReaderTransition::ReadUpTo(_)) => Status::Available(0), } } } diff --git a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs index 15f54c672..c1b3859b2 100644 --- a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs +++ b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs @@ -38,8 +38,9 @@ impl ReferenceStateMachine for WriterSM { Status::Available(x - y) } } + (Status::Failed, WriterTransition::Read(_)) => Status::Failed, (Status::Available(x), WriterTransition::WriteUpTo(y)) => Status::Available((x + *y).min(CAP)), - (Status::Failed, _) => Status::Failed, + (Status::Failed, WriterTransition::WriteUpTo(_)) => Status::Available(CAP), } } } From 82712252166a274c1499c3680b63f86cdfbb8dfb Mon Sep 17 00:00:00 2001 From: Alexandros Liarokapis Date: Mon, 23 Sep 2024 16:50:26 +0300 Subject: [PATCH 074/144] make len method take mut self and remove sync index calls --- embassy-stm32/src/dma/ringbuffer/mod.rs | 32 ++++++++++--------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/embassy-stm32/src/dma/ringbuffer/mod.rs b/embassy-stm32/src/dma/ringbuffer/mod.rs index abc01e3cc..76f7f36ec 100644 --- a/embassy-stm32/src/dma/ringbuffer/mod.rs +++ b/embassy-stm32/src/dma/ringbuffer/mod.rs @@ -99,7 +99,10 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { } /// Get the available readable dma samples. - pub fn len(&self) -> Result { + pub fn len(&mut self, dma: &mut impl DmaCtrl) -> Result { + self.write_index.dma_sync(self.cap(), dma); + DmaIndex::normalize(&mut self.write_index, &mut self.read_index); + let diff: usize = self.write_index.diff(self.cap(), &self.read_index).try_into().unwrap(); if diff > self.cap() { @@ -155,13 +158,11 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { } fn read_raw(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { - self.sync_write_index(dma); - let readable = self.len()?.min(buf.len()); + let readable = self.len(dma)?.min(buf.len()); for i in 0..readable { buf[i] = self.read_buf(i); } - self.sync_write_index(dma); - let available = self.len()?; + let available = self.len(dma)?; self.read_index.advance(self.cap(), readable); Ok((readable, available - readable)) } @@ -175,11 +176,6 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { ) } } - - fn sync_write_index(&mut self, dma: &mut impl DmaCtrl) { - self.write_index.dma_sync(self.cap(), dma); - DmaIndex::normalize(&mut self.write_index, &mut self.read_index); - } } pub struct WritableDmaRingBuffer<'a, W: Word> { @@ -212,7 +208,10 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { } /// Get the remaining writable dma samples. - pub fn len(&self) -> Result { + pub fn len(&mut self, dma: &mut impl DmaCtrl) -> Result { + self.read_index.dma_sync(self.cap(), dma); + DmaIndex::normalize(&mut self.read_index, &mut self.write_index); + let diff = self.write_index.diff(self.cap(), &self.read_index); if diff < 0 { @@ -271,13 +270,11 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { } pub fn write_raw(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), OverrunError> { - self.sync_read_index(dma); - let writable = self.len()?.min(buf.len()); + let writable = self.len(dma)?.min(buf.len()); for i in 0..writable { self.write_buf(i, buf[i]); } - self.sync_read_index(dma); - let available = self.len()?; + let available = self.len(dma)?; self.write_index.advance(self.cap(), writable); Ok((writable, available - writable)) } @@ -292,11 +289,6 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { ) } } - - fn sync_read_index(&mut self, dma: &mut impl DmaCtrl) { - self.read_index.dma_sync(self.cap(), dma); - DmaIndex::normalize(&mut self.read_index, &mut self.write_index); - } } #[cfg(test)] From 9c7b296432af40ac44e5e65a3742c691f1414444 Mon Sep 17 00:00:00 2001 From: Alexandros Liarokapis Date: Tue, 24 Sep 2024 00:59:34 +0300 Subject: [PATCH 075/144] overrun at invalid diffs, rename clear to reset, simplify dma_sync method --- embassy-stm32/src/adc/ringbuffered_v2.rs | 3 +- embassy-stm32/src/dma/dma_bdma.rs | 4 +- embassy-stm32/src/dma/ringbuffer/mod.rs | 53 +++++++++---------- embassy-stm32/src/dma/ringbuffer/tests/mod.rs | 22 ++++---- .../dma/ringbuffer/tests/prop_test/reader.rs | 10 ++-- .../dma/ringbuffer/tests/prop_test/writer.rs | 10 ++-- 6 files changed, 49 insertions(+), 53 deletions(-) diff --git a/embassy-stm32/src/adc/ringbuffered_v2.rs b/embassy-stm32/src/adc/ringbuffered_v2.rs index 3b064044e..9fc233222 100644 --- a/embassy-stm32/src/adc/ringbuffered_v2.rs +++ b/embassy-stm32/src/adc/ringbuffered_v2.rs @@ -226,9 +226,8 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { /// 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(); - self.setup_adc(); + self.ring_buf.clear(); Ok(()) } diff --git a/embassy-stm32/src/dma/dma_bdma.rs b/embassy-stm32/src/dma/dma_bdma.rs index a43137c6e..59ad4d988 100644 --- a/embassy-stm32/src/dma/dma_bdma.rs +++ b/embassy-stm32/src/dma/dma_bdma.rs @@ -833,7 +833,7 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> { /// Clear all data in the ring buffer. pub fn clear(&mut self) { - self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow())); + self.ringbuf.reset(&mut DmaCtrlImpl(self.channel.reborrow())); } /// Read elements from the ring buffer @@ -980,7 +980,7 @@ impl<'a, W: Word> WritableRingBuffer<'a, W> { /// Clear all data in the ring buffer. pub fn clear(&mut self) { - self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow())); + self.ringbuf.reset(&mut DmaCtrlImpl(self.channel.reborrow())); } /// Write elements directly to the raw buffer. diff --git a/embassy-stm32/src/dma/ringbuffer/mod.rs b/embassy-stm32/src/dma/ringbuffer/mod.rs index 76f7f36ec..eb64ad016 100644 --- a/embassy-stm32/src/dma/ringbuffer/mod.rs +++ b/embassy-stm32/src/dma/ringbuffer/mod.rs @@ -23,14 +23,14 @@ pub struct OverrunError; #[derive(Debug, Clone, Copy, Default)] struct DmaIndex { - completion_count: usize, + complete_count: usize, pos: usize, } impl DmaIndex { fn reset(&mut self) { self.pos = 0; - self.completion_count = 0; + self.complete_count = 0; } fn as_index(&self, cap: usize, offset: usize) -> usize { @@ -38,34 +38,31 @@ impl DmaIndex { } fn dma_sync(&mut self, cap: usize, dma: &mut impl DmaCtrl) { - let fst_pos = cap - dma.get_remaining_transfers(); - let fst_count = dma.reset_complete_count(); - let pos = cap - dma.get_remaining_transfers(); + let first_pos = cap - dma.get_remaining_transfers(); + self.complete_count += dma.reset_complete_count(); + self.pos = cap - dma.get_remaining_transfers(); - let wrap_count = if pos >= fst_pos { - fst_count - } else { - fst_count + dma.reset_complete_count() - }; - - self.pos = pos; - self.completion_count += wrap_count; + // If the latter call to get_remaining_transfers() returned a smaller value than the first, the dma + // has wrapped around between calls and we must check if the complete count also incremented. + if self.pos < first_pos { + self.complete_count += dma.reset_complete_count(); + } } fn advance(&mut self, cap: usize, steps: usize) { let next = self.pos + steps; - self.completion_count += next / cap; + self.complete_count += next / cap; self.pos = next % cap; } fn normalize(lhs: &mut DmaIndex, rhs: &mut DmaIndex) { - let min_count = lhs.completion_count.min(rhs.completion_count); - lhs.completion_count -= min_count; - rhs.completion_count -= min_count; + let min_count = lhs.complete_count.min(rhs.complete_count); + lhs.complete_count -= min_count; + rhs.complete_count -= min_count; } fn diff(&self, cap: usize, rhs: &DmaIndex) -> isize { - (self.completion_count * cap + self.pos) as isize - (rhs.completion_count * cap + rhs.pos) as isize + (self.complete_count * cap + self.pos) as isize - (rhs.complete_count * cap + rhs.pos) as isize } } @@ -86,7 +83,7 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { } /// Reset the ring buffer to its initial state. - pub fn clear(&mut self, dma: &mut impl DmaCtrl) { + pub fn reset(&mut self, dma: &mut impl DmaCtrl) { dma.reset_complete_count(); self.write_index.reset(); self.write_index.dma_sync(self.cap(), dma); @@ -103,12 +100,12 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { self.write_index.dma_sync(self.cap(), dma); DmaIndex::normalize(&mut self.write_index, &mut self.read_index); - let diff: usize = self.write_index.diff(self.cap(), &self.read_index).try_into().unwrap(); + let diff = self.write_index.diff(self.cap(), &self.read_index); - if diff > self.cap() { + if diff < 0 || diff > self.cap() as isize { Err(OverrunError) } else { - Ok(diff) + Ok(diff as usize) } } @@ -117,10 +114,10 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { /// If not all of the elements were read, then there will be some elements in the buffer remaining /// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read /// OverrunError is returned if the portion to be read was overwritten by the DMA controller, - /// in which case the rinbuffer will automatically clear itself. + /// in which case the rinbuffer will automatically reset itself. pub fn read(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { self.read_raw(dma, buf).inspect_err(|_e| { - self.clear(dma); + self.reset(dma); }) } @@ -192,14 +189,14 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { dma_buf, read_index: Default::default(), write_index: DmaIndex { - completion_count: 0, + complete_count: 0, pos: len, }, } } /// Reset the ring buffer to its initial state. The buffer after the reset will be full. - pub fn clear(&mut self, dma: &mut impl DmaCtrl) { + pub fn reset(&mut self, dma: &mut impl DmaCtrl) { dma.reset_complete_count(); self.read_index.reset(); self.read_index.dma_sync(self.cap(), dma); @@ -214,7 +211,7 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { let diff = self.write_index.diff(self.cap(), &self.read_index); - if diff < 0 { + if diff < 0 || diff > self.cap() as isize { Err(OverrunError) } else { Ok(self.cap().saturating_sub(diff as usize)) @@ -233,7 +230,7 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { /// leeway between the write index and the DMA. pub fn write(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), OverrunError> { self.write_raw(dma, buf).inspect_err(|_e| { - self.clear(dma); + self.reset(dma); }) } diff --git a/embassy-stm32/src/dma/ringbuffer/tests/mod.rs b/embassy-stm32/src/dma/ringbuffer/tests/mod.rs index 9768e1df8..1800eae69 100644 --- a/embassy-stm32/src/dma/ringbuffer/tests/mod.rs +++ b/embassy-stm32/src/dma/ringbuffer/tests/mod.rs @@ -64,12 +64,12 @@ fn dma_index_dma_sync_syncs_position_to_last_read_if_sync_takes_place_on_same_dm ]); let mut index = DmaIndex::default(); index.dma_sync(CAP, &mut dma); - assert_eq!(index.completion_count, 0); + assert_eq!(index.complete_count, 0); assert_eq!(index.pos, 7); } #[test] -fn dma_index_dma_sync_updates_completion_count_properly_if_sync_takes_place_on_same_dma_cycle() { +fn dma_index_dma_sync_updates_complete_count_properly_if_sync_takes_place_on_same_dma_cycle() { let mut dma = TestCircularTransfer::new(CAP); dma.setup(vec![ TestCircularTransferRequest::PositionRequest(4), @@ -77,9 +77,9 @@ fn dma_index_dma_sync_updates_completion_count_properly_if_sync_takes_place_on_s TestCircularTransferRequest::PositionRequest(7), ]); let mut index = DmaIndex::default(); - index.completion_count = 1; + index.complete_count = 1; index.dma_sync(CAP, &mut dma); - assert_eq!(index.completion_count, 3); + assert_eq!(index.complete_count, 3); assert_eq!(index.pos, 7); } @@ -94,12 +94,12 @@ fn dma_index_dma_sync_syncs_to_last_position_if_reads_occur_on_different_dma_cyc ]); let mut index = DmaIndex::default(); index.dma_sync(CAP, &mut dma); - assert_eq!(index.completion_count, 1); + assert_eq!(index.complete_count, 1); assert_eq!(index.pos, 5); } #[test] -fn dma_index_dma_sync_detects_new_cycle_if_later_position_is_less_than_first_and_first_completion_count_occurs_on_first_cycle( +fn dma_index_dma_sync_detects_new_cycle_if_later_position_is_less_than_first_and_first_complete_count_occurs_on_first_cycle( ) { let mut dma = TestCircularTransfer::new(CAP); dma.setup(vec![ @@ -109,14 +109,14 @@ fn dma_index_dma_sync_detects_new_cycle_if_later_position_is_less_than_first_and TestCircularTransferRequest::ResetCompleteCount(1), ]); let mut index = DmaIndex::default(); - index.completion_count = 1; + index.complete_count = 1; index.dma_sync(CAP, &mut dma); - assert_eq!(index.completion_count, 3); + assert_eq!(index.complete_count, 3); assert_eq!(index.pos, 5); } #[test] -fn dma_index_dma_sync_detects_new_cycle_if_later_position_is_less_than_first_and_first_completion_count_occurs_on_later_cycle( +fn dma_index_dma_sync_detects_new_cycle_if_later_position_is_less_than_first_and_first_complete_count_occurs_on_later_cycle( ) { let mut dma = TestCircularTransfer::new(CAP); dma.setup(vec![ @@ -126,9 +126,9 @@ fn dma_index_dma_sync_detects_new_cycle_if_later_position_is_less_than_first_and TestCircularTransferRequest::ResetCompleteCount(0), ]); let mut index = DmaIndex::default(); - index.completion_count = 1; + index.complete_count = 1; index.dma_sync(CAP, &mut dma); - assert_eq!(index.completion_count, 3); + assert_eq!(index.complete_count, 3); assert_eq!(index.pos, 5); } diff --git a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs index 6e640a813..4f3957a68 100644 --- a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs +++ b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs @@ -5,7 +5,7 @@ use super::*; #[derive(Debug, Clone)] enum ReaderTransition { Write(usize), - Clear, + Reset, ReadUpTo(usize), } @@ -23,14 +23,14 @@ impl ReferenceStateMachine for ReaderSM { prop_oneof![ (1..50_usize).prop_map(ReaderTransition::Write), (1..50_usize).prop_map(ReaderTransition::ReadUpTo), - strategy::Just(ReaderTransition::Clear), + strategy::Just(ReaderTransition::Reset), ] .boxed() } fn apply(status: Self::State, transition: &Self::Transition) -> Self::State { match (status, transition) { - (_, ReaderTransition::Clear) => Status::Available(0), + (_, ReaderTransition::Reset) => Status::Available(0), (Status::Available(x), ReaderTransition::Write(y)) => { if x + y > CAP { Status::Failed @@ -87,8 +87,8 @@ impl StateMachineTest for ReaderTest { ) -> Self::SystemUnderTest { match transition { ReaderTransition::Write(x) => sut.producer.advance(x), - ReaderTransition::Clear => { - sut.consumer.clear(&mut sut.producer); + ReaderTransition::Reset => { + sut.consumer.reset(&mut sut.producer); } ReaderTransition::ReadUpTo(x) => { let status = sut.status; diff --git a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs index c1b3859b2..15433c0ee 100644 --- a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs +++ b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs @@ -6,7 +6,7 @@ use super::*; enum WriterTransition { Read(usize), WriteUpTo(usize), - Clear, + Reset, } struct WriterSM; @@ -23,14 +23,14 @@ impl ReferenceStateMachine for WriterSM { prop_oneof![ (1..50_usize).prop_map(WriterTransition::Read), (1..50_usize).prop_map(WriterTransition::WriteUpTo), - strategy::Just(WriterTransition::Clear), + strategy::Just(WriterTransition::Reset), ] .boxed() } fn apply(status: Self::State, transition: &Self::Transition) -> Self::State { match (status, transition) { - (_, WriterTransition::Clear) => Status::Available(CAP), + (_, WriterTransition::Reset) => Status::Available(CAP), (Status::Available(x), WriterTransition::Read(y)) => { if x < *y { Status::Failed @@ -87,8 +87,8 @@ impl StateMachineTest for WriterTest { ) -> Self::SystemUnderTest { match transition { WriterTransition::Read(x) => sut.consumer.advance(x), - WriterTransition::Clear => { - sut.producer.clear(&mut sut.consumer); + WriterTransition::Reset => { + sut.producer.reset(&mut sut.consumer); } WriterTransition::WriteUpTo(x) => { let status = sut.status; From c991ddb76662cc7da5e847f33c1a446f17822887 Mon Sep 17 00:00:00 2001 From: Alexandros Liarokapis Date: Tue, 24 Sep 2024 17:46:53 +0300 Subject: [PATCH 076/144] use request_pause instead of request_stop at adc shutdown --- 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 9fc233222..9c114e463 100644 --- a/embassy-stm32/src/adc/ringbuffered_v2.rs +++ b/embassy-stm32/src/adc/ringbuffered_v2.rs @@ -244,7 +244,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { /// [`start`]: #method.start pub fn teardown_adc(&mut self) { // Stop the DMA transfer - self.ring_buf.request_stop(); + self.ring_buf.request_pause(); let r = T::regs(); From f0d2ebdc7ead41307155b083790b8450ca2b7eac Mon Sep 17 00:00:00 2001 From: Alexandros Liarokapis Date: Tue, 1 Oct 2024 17:59:04 +0300 Subject: [PATCH 077/144] stm32: fix ringbugger overrun errors due to bad dma wrap-around behavior --- embassy-stm32/src/dma/dma_bdma.rs | 30 ++++--- embassy-stm32/src/dma/ringbuffer/mod.rs | 65 +++++++++------ embassy-stm32/src/dma/ringbuffer/tests/mod.rs | 83 +------------------ embassy-stm32/src/sai/mod.rs | 10 ++- embassy-stm32/src/usart/ringbuffered.rs | 1 - 5 files changed, 72 insertions(+), 117 deletions(-) diff --git a/embassy-stm32/src/dma/dma_bdma.rs b/embassy-stm32/src/dma/dma_bdma.rs index 59ad4d988..3f047a9a4 100644 --- a/embassy-stm32/src/dma/dma_bdma.rs +++ b/embassy-stm32/src/dma/dma_bdma.rs @@ -6,7 +6,7 @@ use core::task::{Context, Poll, Waker}; use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -use super::ringbuffer::{DmaCtrl, OverrunError, ReadableDmaRingBuffer, WritableDmaRingBuffer}; +use super::ringbuffer::{DmaCtrl, Error, ReadableDmaRingBuffer, WritableDmaRingBuffer}; use super::word::{Word, WordSize}; use super::{AnyChannel, Channel, Dir, Request, STATE}; use crate::interrupt::typelevel::Interrupt; @@ -299,7 +299,6 @@ impl AnyChannel { } else { return; } - state.waker.wake(); } #[cfg(bdma)] @@ -828,7 +827,8 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> { /// /// You must call this after creating it for it to work. pub fn start(&mut self) { - self.channel.start() + self.channel.start(); + self.clear(); } /// Clear all data in the ring buffer. @@ -840,15 +840,15 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> { /// Return a tuple of the length read and the length remaining in the buffer /// If not all of the elements were read, then there will be some elements in the buffer remaining /// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read - /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. - pub fn read(&mut self, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { + /// Error is returned if the portion to be read was overwritten by the DMA controller. + pub fn read(&mut self, buf: &mut [W]) -> Result<(usize, usize), Error> { self.ringbuf.read(&mut DmaCtrlImpl(self.channel.reborrow()), buf) } /// Read an exact number of elements from the ringbuffer. /// /// Returns the remaining number of elements available for immediate reading. - /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. + /// Error is returned if the portion to be read was overwritten by the DMA controller. /// /// Async/Wake Behavior: /// The underlying DMA peripheral only can wake us when its buffer pointer has reached the halfway point, @@ -856,12 +856,17 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> { /// ring buffer was created with a buffer of size 'N': /// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source. /// - Otherwise, this function may need up to N/2 extra elements to arrive before returning. - pub async fn read_exact(&mut self, buffer: &mut [W]) -> Result { + pub async fn read_exact(&mut self, buffer: &mut [W]) -> Result { self.ringbuf .read_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) .await } + /// The current length of the ringbuffer + pub fn len(&mut self) -> Result { + Ok(self.ringbuf.len(&mut DmaCtrlImpl(self.channel.reborrow()))?) + } + /// The capacity of the ringbuffer pub const fn capacity(&self) -> usize { self.ringbuf.cap() @@ -986,23 +991,28 @@ impl<'a, W: Word> WritableRingBuffer<'a, W> { /// Write elements directly to the raw buffer. /// This can be used to fill the buffer before starting the DMA transfer. #[allow(dead_code)] - pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), OverrunError> { + pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), Error> { self.ringbuf.write_immediate(buf) } /// Write elements from the ring buffer /// Return a tuple of the length written and the length remaining in the buffer - pub fn write(&mut self, buf: &[W]) -> Result<(usize, usize), OverrunError> { + pub fn write(&mut self, buf: &[W]) -> Result<(usize, usize), Error> { self.ringbuf.write(&mut DmaCtrlImpl(self.channel.reborrow()), buf) } /// Write an exact number of elements to the ringbuffer. - pub async fn write_exact(&mut self, buffer: &[W]) -> Result { + pub async fn write_exact(&mut self, buffer: &[W]) -> Result { self.ringbuf .write_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) .await } + /// The current length of the ringbuffer + pub fn len(&mut self) -> Result { + Ok(self.ringbuf.len(&mut DmaCtrlImpl(self.channel.reborrow()))?) + } + /// The capacity of the ringbuffer pub const fn capacity(&self) -> usize { self.ringbuf.cap() diff --git a/embassy-stm32/src/dma/ringbuffer/mod.rs b/embassy-stm32/src/dma/ringbuffer/mod.rs index eb64ad016..a257faa5b 100644 --- a/embassy-stm32/src/dma/ringbuffer/mod.rs +++ b/embassy-stm32/src/dma/ringbuffer/mod.rs @@ -19,9 +19,13 @@ pub trait DmaCtrl { #[derive(Debug, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct OverrunError; +pub enum Error { + Overrun, + DmaUnsynced, +} #[derive(Debug, Clone, Copy, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] struct DmaIndex { complete_count: usize, pos: usize, @@ -38,15 +42,19 @@ impl DmaIndex { } fn dma_sync(&mut self, cap: usize, dma: &mut impl DmaCtrl) { - let first_pos = cap - dma.get_remaining_transfers(); - self.complete_count += dma.reset_complete_count(); - self.pos = cap - dma.get_remaining_transfers(); + // Important! + // The ordering of the first two lines matters! + // If changed, the code will detect a wrong +capacity + // jump at wrap-around. + let count_diff = dma.reset_complete_count(); + let pos = cap - dma.get_remaining_transfers(); + self.pos = if pos < self.pos && count_diff == 0 { + cap - 1 + } else { + pos + }; - // If the latter call to get_remaining_transfers() returned a smaller value than the first, the dma - // has wrapped around between calls and we must check if the complete count also incremented. - if self.pos < first_pos { - self.complete_count += dma.reset_complete_count(); - } + self.complete_count += count_diff; } fn advance(&mut self, cap: usize, steps: usize) { @@ -96,14 +104,18 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { } /// Get the available readable dma samples. - pub fn len(&mut self, dma: &mut impl DmaCtrl) -> Result { + pub fn len(&mut self, dma: &mut impl DmaCtrl) -> Result { self.write_index.dma_sync(self.cap(), dma); DmaIndex::normalize(&mut self.write_index, &mut self.read_index); let diff = self.write_index.diff(self.cap(), &self.read_index); - if diff < 0 || diff > self.cap() as isize { - Err(OverrunError) + if diff < 0 { + return Err(Error::DmaUnsynced); + } + + if diff > self.cap() as isize { + Err(Error::Overrun) } else { Ok(diff as usize) } @@ -113,9 +125,9 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { /// Return a tuple of the length read and the length remaining in the buffer /// If not all of the elements were read, then there will be some elements in the buffer remaining /// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read - /// OverrunError is returned if the portion to be read was overwritten by the DMA controller, + /// Error is returned if the portion to be read was overwritten by the DMA controller, /// in which case the rinbuffer will automatically reset itself. - pub fn read(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { + pub fn read(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), Error> { self.read_raw(dma, buf).inspect_err(|_e| { self.reset(dma); }) @@ -124,7 +136,7 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { /// Read an exact number of elements from the ringbuffer. /// /// Returns the remaining number of elements available for immediate reading. - /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. + /// Error is returned if the portion to be read was overwritten by the DMA controller. /// /// Async/Wake Behavior: /// The underlying DMA peripheral only can wake us when its buffer pointer has reached the halfway point, @@ -132,7 +144,7 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { /// ring buffer was created with a buffer of size 'N': /// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source. /// - Otherwise, this function may need up to N/2 extra elements to arrive before returning. - pub async fn read_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &mut [W]) -> Result { + pub async fn read_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &mut [W]) -> Result { let mut read_data = 0; let buffer_len = buffer.len(); @@ -154,7 +166,7 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { .await } - fn read_raw(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { + fn read_raw(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), Error> { let readable = self.len(dma)?.min(buf.len()); for i in 0..readable { buf[i] = self.read_buf(i); @@ -205,14 +217,17 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { } /// Get the remaining writable dma samples. - pub fn len(&mut self, dma: &mut impl DmaCtrl) -> Result { + pub fn len(&mut self, dma: &mut impl DmaCtrl) -> Result { self.read_index.dma_sync(self.cap(), dma); DmaIndex::normalize(&mut self.read_index, &mut self.write_index); let diff = self.write_index.diff(self.cap(), &self.read_index); - if diff < 0 || diff > self.cap() as isize { - Err(OverrunError) + if diff > self.cap() as isize { + return Err(Error::DmaUnsynced); + } + if diff < 0 { + Err(Error::Overrun) } else { Ok(self.cap().saturating_sub(diff as usize)) } @@ -225,17 +240,17 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { /// Append data to the ring buffer. /// Returns a tuple of the data written and the remaining write capacity in the buffer. - /// OverrunError is returned if the portion to be written was previously read by the DMA controller. + /// Error is returned if the portion to be written was previously read by the DMA controller. /// In this case, the ringbuffer will automatically reset itself, giving a full buffer worth of /// leeway between the write index and the DMA. - pub fn write(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), OverrunError> { + pub fn write(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), Error> { self.write_raw(dma, buf).inspect_err(|_e| { self.reset(dma); }) } /// Write elements directly to the buffer. - pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), OverrunError> { + pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), Error> { for (i, data) in buf.iter().enumerate() { self.write_buf(i, *data) } @@ -244,7 +259,7 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { } /// Write an exact number of elements to the ringbuffer. - pub async fn write_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &[W]) -> Result { + pub async fn write_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &[W]) -> Result { let mut written_data = 0; let buffer_len = buffer.len(); @@ -266,7 +281,7 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { .await } - pub fn write_raw(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), OverrunError> { + fn write_raw(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), Error> { let writable = self.len(dma)?.min(buf.len()); for i in 0..writable { self.write_buf(i, buf[i]); diff --git a/embassy-stm32/src/dma/ringbuffer/tests/mod.rs b/embassy-stm32/src/dma/ringbuffer/tests/mod.rs index 1800eae69..6fabedb83 100644 --- a/embassy-stm32/src/dma/ringbuffer/tests/mod.rs +++ b/embassy-stm32/src/dma/ringbuffer/tests/mod.rs @@ -2,13 +2,14 @@ use std::{cell, vec}; use super::*; -#[allow(dead_code)] +#[allow(unused)] #[derive(PartialEq, Debug)] enum TestCircularTransferRequest { ResetCompleteCount(usize), PositionRequest(usize), } +#[allow(unused)] struct TestCircularTransfer { len: usize, requests: cell::RefCell>, @@ -39,6 +40,7 @@ impl DmaCtrl for TestCircularTransfer { } impl TestCircularTransfer { + #[allow(unused)] pub fn new(len: usize) -> Self { Self { requests: cell::RefCell::new(vec![]), @@ -46,6 +48,7 @@ impl TestCircularTransfer { } } + #[allow(unused)] pub fn setup(&self, mut requests: vec::Vec) { requests.reverse(); self.requests.replace(requests); @@ -54,84 +57,6 @@ impl TestCircularTransfer { const CAP: usize = 16; -#[test] -fn dma_index_dma_sync_syncs_position_to_last_read_if_sync_takes_place_on_same_dma_cycle() { - let mut dma = TestCircularTransfer::new(CAP); - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(4), - TestCircularTransferRequest::ResetCompleteCount(0), - TestCircularTransferRequest::PositionRequest(7), - ]); - let mut index = DmaIndex::default(); - index.dma_sync(CAP, &mut dma); - assert_eq!(index.complete_count, 0); - assert_eq!(index.pos, 7); -} - -#[test] -fn dma_index_dma_sync_updates_complete_count_properly_if_sync_takes_place_on_same_dma_cycle() { - let mut dma = TestCircularTransfer::new(CAP); - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(4), - TestCircularTransferRequest::ResetCompleteCount(2), - TestCircularTransferRequest::PositionRequest(7), - ]); - let mut index = DmaIndex::default(); - index.complete_count = 1; - index.dma_sync(CAP, &mut dma); - assert_eq!(index.complete_count, 3); - assert_eq!(index.pos, 7); -} - -#[test] -fn dma_index_dma_sync_syncs_to_last_position_if_reads_occur_on_different_dma_cycles() { - let mut dma = TestCircularTransfer::new(CAP); - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(10), - TestCircularTransferRequest::ResetCompleteCount(1), - TestCircularTransferRequest::PositionRequest(5), - TestCircularTransferRequest::ResetCompleteCount(0), - ]); - let mut index = DmaIndex::default(); - index.dma_sync(CAP, &mut dma); - assert_eq!(index.complete_count, 1); - assert_eq!(index.pos, 5); -} - -#[test] -fn dma_index_dma_sync_detects_new_cycle_if_later_position_is_less_than_first_and_first_complete_count_occurs_on_first_cycle( -) { - let mut dma = TestCircularTransfer::new(CAP); - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(10), - TestCircularTransferRequest::ResetCompleteCount(1), - TestCircularTransferRequest::PositionRequest(5), - TestCircularTransferRequest::ResetCompleteCount(1), - ]); - let mut index = DmaIndex::default(); - index.complete_count = 1; - index.dma_sync(CAP, &mut dma); - assert_eq!(index.complete_count, 3); - assert_eq!(index.pos, 5); -} - -#[test] -fn dma_index_dma_sync_detects_new_cycle_if_later_position_is_less_than_first_and_first_complete_count_occurs_on_later_cycle( -) { - let mut dma = TestCircularTransfer::new(CAP); - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(10), - TestCircularTransferRequest::ResetCompleteCount(2), - TestCircularTransferRequest::PositionRequest(5), - TestCircularTransferRequest::ResetCompleteCount(0), - ]); - let mut index = DmaIndex::default(); - index.complete_count = 1; - index.dma_sync(CAP, &mut dma); - assert_eq!(index.complete_count, 3); - assert_eq!(index.pos, 5); -} - #[test] fn dma_index_as_index_returns_index_mod_cap_by_default() { let index = DmaIndex::default(); diff --git a/embassy-stm32/src/sai/mod.rs b/embassy-stm32/src/sai/mod.rs index 63f48ace0..7d2f071de 100644 --- a/embassy-stm32/src/sai/mod.rs +++ b/embassy-stm32/src/sai/mod.rs @@ -27,8 +27,14 @@ pub enum Error { } #[cfg(not(gpdma))] -impl From for Error { - fn from(_: ringbuffer::OverrunError) -> Self { +impl From for Error { + fn from(#[allow(unused)] err: ringbuffer::Error) -> Self { + #[cfg(feature = "defmt")] + { + if err == ringbuffer::Error::DmaUnsynced { + defmt::error!("Ringbuffer broken invariants detected!"); + } + } Self::Overrun } } diff --git a/embassy-stm32/src/usart/ringbuffered.rs b/embassy-stm32/src/usart/ringbuffered.rs index 75834bf37..eb2399d9c 100644 --- a/embassy-stm32/src/usart/ringbuffered.rs +++ b/embassy-stm32/src/usart/ringbuffered.rs @@ -83,7 +83,6 @@ impl<'d> RingBufferedUartRx<'d> { // Clear the buffer so that it is ready to receive data compiler_fence(Ordering::SeqCst); self.ring_buf.start(); - self.ring_buf.clear(); let r = self.info.regs; // clear all interrupts and DMA Rx Request From 2ec05da5ddadad9b34c72e8ef1a57a7662a6f0e0 Mon Sep 17 00:00:00 2001 From: Alexandros Liarokapis Date: Wed, 2 Oct 2024 13:10:07 +0300 Subject: [PATCH 078/144] simplify if/else handling on ringbuffer --- embassy-stm32/src/dma/ringbuffer/mod.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/embassy-stm32/src/dma/ringbuffer/mod.rs b/embassy-stm32/src/dma/ringbuffer/mod.rs index a257faa5b..ac4be3e18 100644 --- a/embassy-stm32/src/dma/ringbuffer/mod.rs +++ b/embassy-stm32/src/dma/ringbuffer/mod.rs @@ -111,10 +111,8 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { let diff = self.write_index.diff(self.cap(), &self.read_index); if diff < 0 { - return Err(Error::DmaUnsynced); - } - - if diff > self.cap() as isize { + Err(Error::DmaUnsynced) + } else if diff > self.cap() as isize { Err(Error::Overrun) } else { Ok(diff as usize) @@ -223,11 +221,10 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { let diff = self.write_index.diff(self.cap(), &self.read_index); - if diff > self.cap() as isize { - return Err(Error::DmaUnsynced); - } if diff < 0 { Err(Error::Overrun) + } else if diff > self.cap() as isize { + Err(Error::DmaUnsynced) } else { Ok(self.cap().saturating_sub(diff as usize)) } From d280b234283d3c65e7ba6087e52005160a538ad2 Mon Sep 17 00:00:00 2001 From: Alexandros Liarokapis Date: Thu, 3 Oct 2024 13:04:15 +0300 Subject: [PATCH 079/144] fix adc/ringbuffered_v2.rs --- embassy-stm32/src/adc/ringbuffered_v2.rs | 4 +++- embassy-stm32/src/dma/ringbuffer/mod.rs | 3 ++- embassy-stm32/src/lib.rs | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/adc/ringbuffered_v2.rs b/embassy-stm32/src/adc/ringbuffered_v2.rs index 9c114e463..3f0c1a57a 100644 --- a/embassy-stm32/src/adc/ringbuffered_v2.rs +++ b/embassy-stm32/src/adc/ringbuffered_v2.rs @@ -6,11 +6,13 @@ 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; +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OverrunError; + fn clear_interrupt_flags(r: crate::pac::adc::Adc) { r.sr().modify(|regs| { regs.set_eoc(false); diff --git a/embassy-stm32/src/dma/ringbuffer/mod.rs b/embassy-stm32/src/dma/ringbuffer/mod.rs index ac4be3e18..12d418414 100644 --- a/embassy-stm32/src/dma/ringbuffer/mod.rs +++ b/embassy-stm32/src/dma/ringbuffer/mod.rs @@ -119,7 +119,8 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { } } - /// Read elements from the ring buffer + /// Read elements from the ring buffer. + /// /// Return a tuple of the length read and the length remaining in the buffer /// If not all of the elements were read, then there will be some elements in the buffer remaining /// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 451f595e0..5f103e652 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -296,6 +296,9 @@ mod dual_core { /// It cannot be initialized by the user. The intended use is: /// /// ``` + /// use core::mem::MaybeUninit; + /// use embassy_stm32::{init_secondary, SharedData}; + /// /// #[link_section = ".ram_d3"] /// static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); /// From 4f810e47f5244b0cb7780ae1eebdba108d9e88c9 Mon Sep 17 00:00:00 2001 From: Alexandros Liarokapis Date: Thu, 3 Oct 2024 21:54:47 +0300 Subject: [PATCH 080/144] enable ci usart tests --- ci.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ci.sh b/ci.sh index ee47ee1c5..aa078be31 100755 --- a/ci.sh +++ b/ci.sh @@ -305,11 +305,6 @@ rm out/tests/stm32f207zg/eth # doesn't work, gives "noise error", no idea why. usart_dma does pass. rm out/tests/stm32u5a5zj/usart -# flaky, probably due to bad ringbuffered dma code. -rm out/tests/stm32l152re/usart_rx_ringbuffered -rm out/tests/stm32f207zg/usart_rx_ringbuffered -rm out/tests/stm32wl55jc/usart_rx_ringbuffered - if [[ -z "${TELEPROBE_TOKEN-}" ]]; then echo No teleprobe token found, skipping running HIL tests exit From 28d03537e958213dab345ad4502dad2b32256135 Mon Sep 17 00:00:00 2001 From: Alexandros Liarokapis Date: Mon, 7 Oct 2024 23:12:35 +0300 Subject: [PATCH 081/144] stm32: Automatically clear on WritableRingBuffer start --- embassy-stm32/src/dma/dma_bdma.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/embassy-stm32/src/dma/dma_bdma.rs b/embassy-stm32/src/dma/dma_bdma.rs index 3f047a9a4..cdc603e2c 100644 --- a/embassy-stm32/src/dma/dma_bdma.rs +++ b/embassy-stm32/src/dma/dma_bdma.rs @@ -980,7 +980,8 @@ impl<'a, W: Word> WritableRingBuffer<'a, W> { /// /// You must call this after creating it for it to work. pub fn start(&mut self) { - self.channel.start() + self.channel.start(); + self.clear(); } /// Clear all data in the ring buffer. From e350ca836a985829b7548b8ac3009f38573b4215 Mon Sep 17 00:00:00 2001 From: Dion Dokter Date: Tue, 15 Oct 2024 13:23:57 +0200 Subject: [PATCH 082/144] STM32 QSPI typo (#3418) --- embassy-stm32/src/qspi/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-stm32/src/qspi/mod.rs b/embassy-stm32/src/qspi/mod.rs index 308947e99..1dfd0e918 100644 --- a/embassy-stm32/src/qspi/mod.rs +++ b/embassy-stm32/src/qspi/mod.rs @@ -18,7 +18,7 @@ use crate::{peripherals, Peripheral}; /// QSPI transfer configuration. pub struct TransferConfig { - /// Instraction width (IMODE) + /// Instruction width (IMODE) pub iwidth: QspiWidth, /// Address width (ADMODE) pub awidth: QspiWidth, From 8af52488e7668897ecdf6a77f76a0003978d04e4 Mon Sep 17 00:00:00 2001 From: Tu Nguyen Date: Wed, 16 Oct 2024 17:45:40 +0700 Subject: [PATCH 083/144] add RTR flag if it is remote frame --- embassy-stm32/src/can/bxcan/registers.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/embassy-stm32/src/can/bxcan/registers.rs b/embassy-stm32/src/can/bxcan/registers.rs index c5de1c683..9798a058b 100644 --- a/embassy-stm32/src/can/bxcan/registers.rs +++ b/embassy-stm32/src/can/bxcan/registers.rs @@ -2,7 +2,7 @@ use core::cmp::Ordering; use core::convert::Infallible; pub use embedded_can::{ExtendedId, Id, StandardId}; -use stm32_metapac::can::vals::Lec; +use stm32_metapac::can::vals::{Lec, Rtr}; use super::{Mailbox, TransmitStatus}; use crate::can::enums::BusError; @@ -306,6 +306,9 @@ impl Registers { mb.tir().write(|w| { w.0 = id.0; w.set_txrq(true); + if frame.header().rtr() { + w.set_rtr(Rtr::REMOTE); + } }); } From 9f1b6b47916126fba7b5968106b5fd25006fc4c2 Mon Sep 17 00:00:00 2001 From: Alexandros Liarokapis Date: Mon, 20 May 2024 16:15:41 +0300 Subject: [PATCH 084/144] Revise I2S interface to ring-buffered. --- embassy-stm32/src/i2s.rs | 337 ++++++++++++++++++++++------ embassy-stm32/src/spi/mod.rs | 13 +- examples/stm32f4/src/bin/i2s_dma.rs | 13 +- 3 files changed, 278 insertions(+), 85 deletions(-) diff --git a/embassy-stm32/src/i2s.rs b/embassy-stm32/src/i2s.rs index 094de2461..f11371f98 100644 --- a/embassy-stm32/src/i2s.rs +++ b/embassy-stm32/src/i2s.rs @@ -1,12 +1,13 @@ //! Inter-IC Sound (I2S) +use embassy_futures::join::join; use embassy_hal_internal::into_ref; +use stm32_metapac::spi::vals; -use crate::dma::ChannelAndRequest; +use crate::dma::{ringbuffer, ChannelAndRequest, ReadableRingBuffer, TransferOptions, WritableRingBuffer}; use crate::gpio::{AfType, AnyPin, OutputType, SealedPin, Speed}; use crate::mode::Async; -use crate::pac::spi::vals; -use crate::spi::{Config as SpiConfig, *}; +use crate::spi::{Config as SpiConfig, RegsExt as _, *}; use crate::time::Hertz; use crate::{Peripheral, PeripheralRef}; @@ -19,6 +20,19 @@ pub enum Mode { Slave, } +/// I2S function +#[derive(Copy, Clone)] +#[allow(dead_code)] +enum Function { + /// Transmit audio data + Transmit, + /// Receive audio data + Receive, + #[cfg(spi_v3)] + /// Transmit and Receive audio data + FullDuplex, +} + /// I2C standard #[derive(Copy, Clone)] pub enum Standard { @@ -34,6 +48,30 @@ pub enum Standard { PcmShortSync, } +/// SAI error +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// `write` called on a SAI in receive mode. + NotATransmitter, + /// `read` called on a SAI in transmit mode. + NotAReceiver, + /// Overrun + Overrun, +} + +impl From for Error { + fn from(#[allow(unused)] err: ringbuffer::Error) -> Self { + #[cfg(feature = "defmt")] + { + if err == ringbuffer::Error::DmaUnsynced { + defmt::error!("Ringbuffer broken invariants detected!"); + } + } + Self::Overrun + } +} + impl Standard { #[cfg(any(spi_v1, spi_v3, spi_f1))] const fn i2sstd(&self) -> vals::I2sstd { @@ -142,31 +180,62 @@ impl Default for Config { } } +/// I2S driver writer. Useful for moving write functionality across tasks. +pub struct Writer<'s, 'd, W: Word>(&'s mut WritableRingBuffer<'d, W>); + +impl<'s, 'd, W: Word> Writer<'s, 'd, W> { + /// Write data to the I2S ringbuffer. + /// This appends the data to the buffer and returns immediately. The data will be transmitted in the background. + /// If thfreā€™s no space in the buffer, this waits until there is. + pub async fn write(&mut self, data: &[W]) -> Result<(), Error> { + self.0.write_exact(data).await?; + Ok(()) + } + + /// Reset the ring buffer to its initial state. + /// Can be used to recover from overrun. + /// The ringbuffer will always auto-reset on Overrun in any case. + pub fn reset(&mut self) { + self.0.clear(); + } +} + +/// I2S driver reader. Useful for moving read functionality across tasks. +pub struct Reader<'s, 'd, W: Word>(&'s mut ReadableRingBuffer<'d, W>); + +impl<'s, 'd, W: Word> Reader<'s, 'd, W> { + /// Read data from the I2S ringbuffer. + /// SAI is always receiving data in the background. This function pops already-received data from the buffer. + /// If thereā€™s less than data.len() data in the buffer, this waits until there is. + pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> { + self.0.read_exact(data).await?; + Ok(()) + } + + /// Reset the ring buffer to its initial state. + /// Can be used to prevent overrun. + /// The ringbuffer will always auto-reset on Overrun in any case. + pub fn reset(&mut self) { + self.0.clear(); + } +} + /// I2S driver. -pub struct I2S<'d> { - _peri: Spi<'d, Async>, +pub struct I2S<'d, W: Word> { + #[allow(dead_code)] + mode: Mode, + spi: Spi<'d, Async>, txsd: Option>, rxsd: Option>, ws: Option>, ck: Option>, mck: Option>, + tx_ring_buffer: Option>, + rx_ring_buffer: Option>, } -/// I2S function -#[derive(Copy, Clone)] -#[allow(dead_code)] -enum Function { - /// Transmit audio data - Transmit, - /// Receive audio data - Receive, - #[cfg(spi_v3)] - /// Transmit and Receive audio data - FullDuplex, -} - -impl<'d> I2S<'d> { - /// Create a transmitter driver +impl<'d, W: Word> I2S<'d, W> { + /// Create a transmitter driver. pub fn new_txonly( peri: impl Peripheral

+ 'd, sd: impl Peripheral

> + 'd, @@ -174,18 +243,18 @@ impl<'d> I2S<'d> { ck: impl Peripheral

> + 'd, mck: impl Peripheral

> + 'd, txdma: impl Peripheral

> + 'd, + txdma_buf: &'d mut [W], freq: Hertz, config: Config, ) -> Self { - into_ref!(sd); Self::new_inner( peri, new_pin!(sd, AfType::output(OutputType::PushPull, Speed::VeryHigh)), None, ws, ck, - mck, - new_dma!(txdma), + new_pin!(mck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_dma!(txdma).map(|d| (d, txdma_buf)), None, freq, config, @@ -193,42 +262,61 @@ impl<'d> I2S<'d> { ) } - /// Create a receiver driver + /// Create a transmitter driver without a master clock pin. + pub fn new_txonly_nomck( + peri: impl Peripheral

+ 'd, + sd: impl Peripheral

> + 'd, + ws: impl Peripheral

> + 'd, + ck: impl Peripheral

> + 'd, + txdma: impl Peripheral

> + 'd, + txdma_buf: &'d mut [W], + freq: Hertz, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(sd, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + ws, + ck, + None, + new_dma!(txdma).map(|d| (d, txdma_buf)), + None, + freq, + config, + Function::Transmit, + ) + } + + /// Create a receiver driver. pub fn new_rxonly( peri: impl Peripheral

+ 'd, sd: impl Peripheral

> + 'd, ws: impl Peripheral

> + 'd, ck: impl Peripheral

> + 'd, mck: impl Peripheral

> + 'd, - #[cfg(not(spi_v3))] txdma: impl Peripheral

> + 'd, rxdma: impl Peripheral

> + 'd, + rxdma_buf: &'d mut [W], freq: Hertz, config: Config, ) -> Self { - into_ref!(sd); Self::new_inner( peri, None, new_pin!(sd, AfType::output(OutputType::PushPull, Speed::VeryHigh)), ws, ck, - mck, - #[cfg(not(spi_v3))] - new_dma!(txdma), - #[cfg(spi_v3)] + new_pin!(mck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), None, - new_dma!(rxdma), + new_dma!(rxdma).map(|d| (d, rxdma_buf)), freq, config, - #[cfg(not(spi_v3))] - Function::Transmit, - #[cfg(spi_v3)] Function::Receive, ) } #[cfg(spi_v3)] - /// Create a full duplex transmitter driver + /// Create a full duplex driver. pub fn new_full_duplex( peri: impl Peripheral

+ 'd, txsd: impl Peripheral

> + 'd, @@ -237,44 +325,144 @@ impl<'d> I2S<'d> { ck: impl Peripheral

> + 'd, mck: impl Peripheral

> + 'd, txdma: impl Peripheral

> + 'd, + txdma_buf: &'d mut [W], rxdma: impl Peripheral

> + 'd, + rxdma_buf: &'d mut [W], freq: Hertz, config: Config, ) -> Self { - into_ref!(txsd, rxsd); Self::new_inner( peri, new_pin!(txsd, AfType::output(OutputType::PushPull, Speed::VeryHigh)), new_pin!(rxsd, AfType::output(OutputType::PushPull, Speed::VeryHigh)), ws, ck, - mck, - new_dma!(txdma), - new_dma!(rxdma), + new_pin!(mck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_dma!(txdma).map(|d| (d, txdma_buf)), + new_dma!(rxdma).map(|d| (d, rxdma_buf)), freq, config, Function::FullDuplex, ) } - /// Write audio data. - pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> { - self._peri.read(data).await + /// Start I2S driver. + pub fn start(&mut self) { + self.spi.info.regs.cr1().modify(|w| { + w.set_spe(false); + }); + self.spi.set_word_size(W::CONFIG); + if let Some(tx_ring_buffer) = &mut self.tx_ring_buffer { + tx_ring_buffer.start(); + + set_txdmaen(self.spi.info.regs, true); + } + if let Some(rx_ring_buffer) = &mut self.rx_ring_buffer { + rx_ring_buffer.start(); + // SPIv3 clears rxfifo on SPE=0 + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] + flush_rx_fifo(self.spi.info.regs); + + set_rxdmaen(self.spi.info.regs, true); + } + self.spi.info.regs.cr1().modify(|w| { + w.set_spe(true); + }); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + self.spi.info.regs.cr1().modify(|w| { + w.set_cstart(true); + }); } - /// Write audio data. - pub async fn write(&mut self, data: &[W]) -> Result<(), Error> { - self._peri.write(data).await + /// Reset the ring buffer to its initial state. + /// Can be used to recover from overrun. + pub fn clear(&mut self) { + if let Some(rx_ring_buffer) = &mut self.rx_ring_buffer { + rx_ring_buffer.clear(); + } + if let Some(tx_ring_buffer) = &mut self.tx_ring_buffer { + tx_ring_buffer.clear(); + } } - /// Transfer audio data. - pub async fn transfer(&mut self, read: &mut [W], write: &[W]) -> Result<(), Error> { - self._peri.transfer(read, write).await + /// Stop I2S driver. + pub async fn stop(&mut self) { + let regs = self.spi.info.regs; + + let tx_f = async { + if let Some(tx_ring_buffer) = &mut self.tx_ring_buffer { + tx_ring_buffer.stop().await; + + set_txdmaen(regs, false); + } + }; + + let rx_f = async { + if let Some(rx_ring_buffer) = &mut self.rx_ring_buffer { + rx_ring_buffer.stop().await; + + set_rxdmaen(regs, false); + } + }; + + join(rx_f, tx_f).await; + + #[cfg(any(spi_v3, spi_v4, spi_v5))] + { + if let Mode::Master = self.mode { + regs.cr1().modify(|w| { + w.set_csusp(true); + }); + + while regs.cr1().read().cstart() {} + } + } + + regs.cr1().modify(|w| { + w.set_spe(false); + }); + + self.clear(); } - /// Transfer audio data in place. - pub async fn transfer_in_place(&mut self, data: &mut [W]) -> Result<(), Error> { - self._peri.transfer_in_place(data).await + /// Split the driver into a Reader/Writer pair. + /// Useful for splitting the reader/writer functionality across tasks or + /// for calling the read/write methods in parallel. + pub fn split<'s>(&'s mut self) -> Result<(Reader<'s, 'd, W>, Writer<'s, 'd, W>), Error> { + match (&mut self.rx_ring_buffer, &mut self.tx_ring_buffer) { + (None, _) => Err(Error::NotAReceiver), + (_, None) => Err(Error::NotATransmitter), + (Some(rx_ring), Some(tx_ring)) => Ok((Reader(rx_ring), Writer(tx_ring))), + } + } + + /// Read data from the I2S ringbuffer. + /// SAI is always receiving data in the background. This function pops already-received data from the buffer. + /// If thereā€™s less than data.len() data in the buffer, this waits until there is. + pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> { + match &mut self.rx_ring_buffer { + Some(ring) => Reader(ring).read(data).await, + _ => Err(Error::NotAReceiver), + } + } + + /// Write data to the I2S ringbuffer. + /// This appends the data to the buffer and returns immediately. The data will be transmitted in the background. + /// If thfreā€™s no space in the buffer, this waits until there is. + pub async fn write(&mut self, data: &[W]) -> Result<(), Error> { + match &mut self.tx_ring_buffer { + Some(ring) => Writer(ring).write(data).await, + _ => Err(Error::NotATransmitter), + } + } + + /// Write data directly to the raw I2S ringbuffer. + /// This can be used to fill the buffer before starting the DMA transfer. + pub async fn write_immediate(&mut self, data: &mut [W]) -> Result<(usize, usize), Error> { + match &mut self.tx_ring_buffer { + Some(ring) => Ok(ring.write_immediate(data)?), + _ => return Err(Error::NotATransmitter), + } } fn new_inner( @@ -283,23 +471,23 @@ impl<'d> I2S<'d> { rxsd: Option>, ws: impl Peripheral

> + 'd, ck: impl Peripheral

> + 'd, - mck: impl Peripheral

> + 'd, - txdma: Option>, - rxdma: Option>, + mck: Option>, + txdma: Option<(ChannelAndRequest<'d>, &'d mut [W])>, + rxdma: Option<(ChannelAndRequest<'d>, &'d mut [W])>, freq: Hertz, config: Config, function: Function, ) -> Self { - into_ref!(ws, ck, mck); + into_ref!(ws, ck); ws.set_as_af(ws.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); ck.set_as_af(ck.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); - mck.set_as_af(mck.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); - let mut spi_cfg = SpiConfig::default(); - spi_cfg.frequency = freq; - - let spi = Spi::new_internal(peri, txdma, rxdma, spi_cfg); + let spi = Spi::new_internal(peri, None, None, { + let mut config = SpiConfig::default(); + config.frequency = freq; + config + }); let regs = T::info().regs; @@ -390,22 +578,29 @@ impl<'d> I2S<'d> { w.set_i2se(true); }); - #[cfg(spi_v3)] - regs.cr1().modify(|w| w.set_spe(true)); - } + let mut opts = TransferOptions::default(); + opts.half_transfer_ir = true; - Self { - _peri: spi, - txsd: txsd.map(|w| w.map_into()), - rxsd: rxsd.map(|w| w.map_into()), - ws: Some(ws.map_into()), - ck: Some(ck.map_into()), - mck: Some(mck.map_into()), + Self { + mode: config.mode, + spi, + txsd: txsd.map(|w| w.map_into()), + rxsd: rxsd.map(|w| w.map_into()), + ws: Some(ws.map_into()), + ck: Some(ck.map_into()), + mck: mck.map(|w| w.map_into()), + tx_ring_buffer: txdma.map(|(ch, buf)| unsafe { + WritableRingBuffer::new(ch.channel, ch.request, regs.tx_ptr(), buf, opts) + }), + rx_ring_buffer: rxdma.map(|(ch, buf)| unsafe { + ReadableRingBuffer::new(ch.channel, ch.request, regs.rx_ptr(), buf, opts) + }), + } } } } -impl<'d> Drop for I2S<'d> { +impl<'d, W: Word> Drop for I2S<'d, W> { fn drop(&mut self) { self.txsd.as_ref().map(|x| x.set_as_disconnected()); self.rxsd.as_ref().map(|x| x.set_as_disconnected()); diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs index d034c028e..a65b0cc64 100644 --- a/embassy-stm32/src/spi/mod.rs +++ b/embassy-stm32/src/spi/mod.rs @@ -311,8 +311,7 @@ impl<'d, M: PeriMode> Spi<'d, M> { } } - /// Set SPI word size. Disables SPI if needed, you have to enable it back yourself. - fn set_word_size(&mut self, word_size: word_impl::Config) { + pub(crate) fn set_word_size(&mut self, word_size: word_impl::Config) { if self.current_word_size == word_size { return; } @@ -895,7 +894,7 @@ fn compute_frequency(kernel_clock: Hertz, br: Br) -> Hertz { kernel_clock / div } -trait RegsExt { +pub(crate) trait RegsExt { fn tx_ptr(&self) -> *mut W; fn rx_ptr(&self) -> *mut W; } @@ -983,7 +982,7 @@ fn spin_until_rx_ready(regs: Regs) -> Result<(), Error> { } } -fn flush_rx_fifo(regs: Regs) { +pub(crate) fn flush_rx_fifo(regs: Regs) { #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] while regs.sr().read().rxne() { #[cfg(not(spi_v2))] @@ -997,7 +996,7 @@ fn flush_rx_fifo(regs: Regs) { } } -fn set_txdmaen(regs: Regs, val: bool) { +pub(crate) fn set_txdmaen(regs: Regs, val: bool) { #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] regs.cr2().modify(|reg| { reg.set_txdmaen(val); @@ -1008,7 +1007,7 @@ fn set_txdmaen(regs: Regs, val: bool) { }); } -fn set_rxdmaen(regs: Regs, val: bool) { +pub(crate) fn set_rxdmaen(regs: Regs, val: bool) { #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] regs.cr2().modify(|reg| { reg.set_rxdmaen(val); @@ -1169,7 +1168,7 @@ impl<'d, W: Word> embedded_hal_async::spi::SpiBus for Spi<'d, Async> { } } -trait SealedWord { +pub(crate) trait SealedWord { const CONFIG: word_impl::Config; } diff --git a/examples/stm32f4/src/bin/i2s_dma.rs b/examples/stm32f4/src/bin/i2s_dma.rs index 27b165f1b..68392847b 100644 --- a/examples/stm32f4/src/bin/i2s_dma.rs +++ b/examples/stm32f4/src/bin/i2s_dma.rs @@ -1,13 +1,10 @@ #![no_std] #![no_main] -use core::fmt::Write; - use defmt::*; use embassy_executor::Spawner; use embassy_stm32::i2s::{Config, I2S}; use embassy_stm32::time::Hertz; -use heapless::String; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -15,6 +12,8 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); + let mut dma_buffer = [0x00_u16; 128]; + let mut i2s = I2S::new_txonly( p.SPI2, p.PC3, // sd @@ -22,13 +21,13 @@ async fn main(_spawner: Spawner) { p.PB10, // ck p.PC6, // mck p.DMA1_CH4, + &mut dma_buffer, Hertz(1_000_000), Config::default(), ); - for n in 0u32.. { - let mut write: String<128> = String::new(); - core::write!(&mut write, "Hello DMA World {}!\r\n", n).unwrap(); - i2s.write(&mut write.as_bytes()).await.ok(); + for i in 0_u16.. { + i2s.write(&mut [i * 2; 64]).await.ok(); + i2s.write(&mut [i * 2 + 1; 64]).await.ok(); } } From 9cf75d7eac11cacf545f1848d06808fc9fb745e1 Mon Sep 17 00:00:00 2001 From: Christian Enderle Date: Thu, 17 Oct 2024 19:40:27 +0200 Subject: [PATCH 085/144] stm32/flash: add support for l5 --- embassy-stm32/src/flash/l.rs | 134 ++++++++++++++++++++++++++------- embassy-stm32/src/flash/mod.rs | 7 +- 2 files changed, 110 insertions(+), 31 deletions(-) diff --git a/embassy-stm32/src/flash/l.rs b/embassy-stm32/src/flash/l.rs index a0bfeb395..ea00bf499 100644 --- a/embassy-stm32/src/flash/l.rs +++ b/embassy-stm32/src/flash/l.rs @@ -23,6 +23,9 @@ pub(crate) unsafe fn lock() { w.set_prglock(true); w.set_pelock(true); }); + + #[cfg(any(flash_l5))] + pac::FLASH.nscr().modify(|w| w.set_nslock(true)); } pub(crate) unsafe fn unlock() { @@ -46,6 +49,14 @@ pub(crate) unsafe fn unlock() { pac::FLASH.prgkeyr().write_value(0x1314_1516); } } + + #[cfg(any(flash_l5))] + { + if pac::FLASH.nscr().read().nslock() { + pac::FLASH.nskeyr().write_value(0x4567_0123); + pac::FLASH.nskeyr().write_value(0xCDEF_89AB); + } + } } pub(crate) unsafe fn enable_blocking_write() { @@ -53,11 +64,17 @@ pub(crate) unsafe fn enable_blocking_write() { #[cfg(any(flash_wl, flash_wb, flash_l4))] pac::FLASH.cr().write(|w| w.set_pg(true)); + + #[cfg(any(flash_l5))] + pac::FLASH.nscr().write(|w| w.set_nspg(true)); } pub(crate) unsafe fn disable_blocking_write() { #[cfg(any(flash_wl, flash_wb, flash_l4))] pac::FLASH.cr().write(|w| w.set_pg(false)); + + #[cfg(any(flash_l5))] + pac::FLASH.nscr().write(|w| w.set_nspg(false)); } pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { @@ -84,13 +101,25 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E write_volatile(sector.start as *mut u32, 0xFFFFFFFF); } - #[cfg(any(flash_wl, flash_wb, flash_l4))] + #[cfg(any(flash_wl, flash_wb, flash_l4, flash_l5))] { let idx = (sector.start - super::FLASH_BASE as u32) / super::BANK1_REGION.erase_size as u32; #[cfg(flash_l4)] let (idx, bank) = if idx > 255 { (idx - 256, true) } else { (idx, false) }; + #[cfg(flash_l5)] + let (idx, bank) = if pac::FLASH.optr().read().dbank() { + if idx > 255 { + (idx - 256, Some(true)) + } else { + (idx, Some(false)) + } + } else { + (idx, None) + }; + + #[cfg(not(flash_l5))] pac::FLASH.cr().modify(|w| { w.set_per(true); w.set_pnb(idx as u8); @@ -101,6 +130,16 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E #[cfg(any(flash_l4))] w.set_bker(bank); }); + + #[cfg(flash_l5)] + pac::FLASH.nscr().modify(|w| { + w.set_nsper(true); + w.set_nspnb(idx as u8); + if let Some(bank) = bank { + w.set_nsbker(bank); + } + w.set_nsstrt(true); + }); } let ret: Result<(), Error> = wait_ready_blocking(); @@ -108,6 +147,9 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E #[cfg(any(flash_wl, flash_wb, flash_l4))] pac::FLASH.cr().modify(|w| w.set_per(false)); + #[cfg(any(flash_l5))] + pac::FLASH.nscr().modify(|w| w.set_nsper(false)); + #[cfg(any(flash_l0, flash_l1))] pac::FLASH.pecr().modify(|w| { w.set_erase(false); @@ -121,42 +163,78 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E pub(crate) unsafe fn clear_all_err() { // read and write back the same value. // This clears all "write 1 to clear" bits. + #[cfg(not(flash_l5))] pac::FLASH.sr().modify(|_| {}); + + #[cfg(flash_l5)] + pac::FLASH.nssr().modify(|_| {}); } unsafe fn wait_ready_blocking() -> Result<(), Error> { loop { - let sr = pac::FLASH.sr().read(); + #[cfg(not(flash_l5))] + { + let sr = pac::FLASH.sr().read(); - if !sr.bsy() { - #[cfg(any(flash_wl, flash_wb, flash_l4))] - if sr.progerr() { - return Err(Error::Prog); + if !sr.bsy() { + #[cfg(any(flash_wl, flash_wb, flash_l4))] + if sr.progerr() { + return Err(Error::Prog); + } + + if sr.wrperr() { + return Err(Error::Protected); + } + + if sr.pgaerr() { + return Err(Error::Unaligned); + } + + if sr.sizerr() { + return Err(Error::Size); + } + + #[cfg(any(flash_wl, flash_wb, flash_l4))] + if sr.miserr() { + return Err(Error::Miss); + } + + #[cfg(any(flash_wl, flash_wb, flash_l4))] + if sr.pgserr() { + return Err(Error::Seq); + } + + return Ok(()); } + } - if sr.wrperr() { - return Err(Error::Protected); + #[cfg(flash_l5)] + { + let nssr = pac::FLASH.nssr().read(); + + if !nssr.nsbsy() { + if nssr.nsprogerr() { + return Err(Error::Prog); + } + + if nssr.nswrperr() { + return Err(Error::Protected); + } + + if nssr.nspgaerr() { + return Err(Error::Unaligned); + } + + if nssr.nssizerr() { + return Err(Error::Size); + } + + if nssr.nspgserr() { + return Err(Error::Seq); + } + + return Ok(()); } - - if sr.pgaerr() { - return Err(Error::Unaligned); - } - - if sr.sizerr() { - return Err(Error::Size); - } - - #[cfg(any(flash_wl, flash_wb, flash_l4))] - if sr.miserr() { - return Err(Error::Miss); - } - - #[cfg(any(flash_wl, flash_wb, flash_l4))] - if sr.pgserr() { - return Err(Error::Seq); - } - - return Ok(()); } } } diff --git a/embassy-stm32/src/flash/mod.rs b/embassy-stm32/src/flash/mod.rs index bce638db0..d64a1c28a 100644 --- a/embassy-stm32/src/flash/mod.rs +++ b/embassy-stm32/src/flash/mod.rs @@ -91,7 +91,7 @@ pub enum FlashBank { Bank2 = 1, } -#[cfg_attr(any(flash_l0, flash_l1, flash_l4, flash_wl, flash_wb), path = "l.rs")] +#[cfg_attr(any(flash_l0, flash_l1, flash_l4, flash_l5, flash_wl, flash_wb), path = "l.rs")] #[cfg_attr(flash_f0, path = "f0.rs")] #[cfg_attr(any(flash_f1, flash_f3), path = "f1f3.rs")] #[cfg_attr(flash_f2, path = "f2.rs")] @@ -105,8 +105,9 @@ pub enum FlashBank { #[cfg_attr(flash_u0, path = "u0.rs")] #[cfg_attr( not(any( - flash_l0, flash_l1, flash_l4, flash_wl, flash_wb, flash_f0, flash_f1, flash_f2, flash_f3, flash_f4, flash_f7, - flash_g0, flash_g4c2, flash_g4c3, flash_g4c4, flash_h7, flash_h7ab, flash_u5, flash_h50, flash_u0 + flash_l0, flash_l1, flash_l4, flash_l5, flash_wl, flash_wb, flash_f0, flash_f1, flash_f2, flash_f3, flash_f4, + flash_f7, flash_g0, flash_g0, flash_g4c2, flash_g4c3, flash_g4c4, flash_h7, flash_h7ab, flash_u5, flash_h50, + flash_u0 )), path = "other.rs" )] From dcdc0638b6fbec5b68055a5592d71458f83da875 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 18 Oct 2024 00:31:42 +0200 Subject: [PATCH 086/144] Update to rust 1.82. --- rust-toolchain.toml | 2 +- tests/nrf/src/bin/buffered_uart_spam.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 80acb3b5e..69a964040 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.81" +channel = "1.82" components = [ "rust-src", "rustfmt", "llvm-tools" ] targets = [ "thumbv7em-none-eabi", diff --git a/tests/nrf/src/bin/buffered_uart_spam.rs b/tests/nrf/src/bin/buffered_uart_spam.rs index 843313537..45daaae0c 100644 --- a/tests/nrf/src/bin/buffered_uart_spam.rs +++ b/tests/nrf/src/bin/buffered_uart_spam.rs @@ -55,7 +55,7 @@ async fn main(_spawner: Spawner) { let task = unsafe { Task::new_unchecked(NonNull::new_unchecked(&spam_peri.tasks_starttx as *const _ as _)) }; let mut spam_ppi = Ppi::new_one_to_one(p.PPI_CH2, event, task); spam_ppi.enable(); - let p = unsafe { core::ptr::addr_of_mut!(TX_BUF) } as *mut u8; + let p = (&raw mut TX_BUF) as *mut u8; spam_peri.txd.ptr.write(|w| unsafe { w.ptr().bits(p as u32) }); spam_peri.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(NSPAM as _) }); spam_peri.tasks_starttx.write(|w| unsafe { w.bits(1) }); From 1f58e0efd05e5c96409db301e4d481098038aedf Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 18 Oct 2024 03:18:59 +0200 Subject: [PATCH 087/144] executor: fix unsoundness due to `impl Trait`, improve macro error handling. (#3425) * executor-macros: don't parse function bodies. * executor-macros: refactor for better recovery and ide-friendliness on errors. * executor-macros: disallow `impl Trait` in task arguments. Fixes #3420 * Fix example using `impl Trait` in tasks. --- embassy-executor-macros/Cargo.toml | 2 +- embassy-executor-macros/src/lib.rs | 50 +---- embassy-executor-macros/src/macros/main.rs | 240 ++++++++++----------- embassy-executor-macros/src/macros/task.rs | 155 ++++++++----- embassy-executor-macros/src/util.rs | 74 +++++++ embassy-executor-macros/src/util/ctxt.rs | 72 ------- embassy-executor-macros/src/util/mod.rs | 1 - examples/stm32h7/src/bin/i2c_shared.rs | 6 +- 8 files changed, 309 insertions(+), 291 deletions(-) create mode 100644 embassy-executor-macros/src/util.rs delete mode 100644 embassy-executor-macros/src/util/ctxt.rs delete mode 100644 embassy-executor-macros/src/util/mod.rs diff --git a/embassy-executor-macros/Cargo.toml b/embassy-executor-macros/Cargo.toml index 218e820ce..ef509c3f9 100644 --- a/embassy-executor-macros/Cargo.toml +++ b/embassy-executor-macros/Cargo.toml @@ -13,7 +13,7 @@ categories = [ ] [dependencies] -syn = { version = "2.0.15", features = ["full", "extra-traits"] } +syn = { version = "2.0.15", features = ["full", "visit"] } quote = "1.0.9" darling = "0.20.1" proc-macro2 = "1.0.29" diff --git a/embassy-executor-macros/src/lib.rs b/embassy-executor-macros/src/lib.rs index 61d388b9e..5f2182f10 100644 --- a/embassy-executor-macros/src/lib.rs +++ b/embassy-executor-macros/src/lib.rs @@ -1,28 +1,11 @@ #![doc = include_str!("../README.md")] extern crate proc_macro; -use darling::ast::NestedMeta; use proc_macro::TokenStream; mod macros; mod util; use macros::*; -use syn::parse::{Parse, ParseBuffer}; -use syn::punctuated::Punctuated; -use syn::Token; - -struct Args { - meta: Vec, -} - -impl Parse for Args { - fn parse(input: &ParseBuffer) -> syn::Result { - let meta = Punctuated::::parse_terminated(input)?; - Ok(Args { - meta: meta.into_iter().collect(), - }) - } -} /// Declares an async task that can be run by `embassy-executor`. The optional `pool_size` parameter can be used to specify how /// many concurrent tasks can be spawned (default is 1) for the function. @@ -56,17 +39,12 @@ impl Parse for Args { /// ``` #[proc_macro_attribute] pub fn task(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as Args); - let f = syn::parse_macro_input!(item as syn::ItemFn); - - task::run(&args.meta, f).unwrap_or_else(|x| x).into() + task::run(args.into(), item.into()).into() } #[proc_macro_attribute] pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as Args); - let f = syn::parse_macro_input!(item as syn::ItemFn); - main::run(&args.meta, f, main::avr()).unwrap_or_else(|x| x).into() + main::run(args.into(), item.into(), &main::ARCH_AVR).into() } /// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task. @@ -89,9 +67,7 @@ pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream { /// ``` #[proc_macro_attribute] pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as Args); - let f = syn::parse_macro_input!(item as syn::ItemFn); - main::run(&args.meta, f, main::cortex_m()).unwrap_or_else(|x| x).into() + main::run(args.into(), item.into(), &main::ARCH_CORTEX_M).into() } /// Creates a new `executor` instance and declares an architecture agnostic application entry point spawning @@ -116,11 +92,7 @@ pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream { /// ``` #[proc_macro_attribute] pub fn main_spin(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as Args); - let f = syn::parse_macro_input!(item as syn::ItemFn); - main::run(&args.meta, f, main::spin(&args.meta)) - .unwrap_or_else(|x| x) - .into() + main::run(args.into(), item.into(), &main::ARCH_SPIN).into() } /// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task. @@ -153,11 +125,7 @@ pub fn main_spin(args: TokenStream, item: TokenStream) -> TokenStream { /// ``` #[proc_macro_attribute] pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as Args); - let f = syn::parse_macro_input!(item as syn::ItemFn); - main::run(&args.meta, f, main::riscv(&args.meta)) - .unwrap_or_else(|x| x) - .into() + main::run(args.into(), item.into(), &main::ARCH_RISCV).into() } /// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task. @@ -180,9 +148,7 @@ pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream { /// ``` #[proc_macro_attribute] pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as Args); - let f = syn::parse_macro_input!(item as syn::ItemFn); - main::run(&args.meta, f, main::std()).unwrap_or_else(|x| x).into() + main::run(args.into(), item.into(), &main::ARCH_STD).into() } /// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task. @@ -205,7 +171,5 @@ pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream { /// ``` #[proc_macro_attribute] pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as Args); - let f = syn::parse_macro_input!(item as syn::ItemFn); - main::run(&args.meta, f, main::wasm()).unwrap_or_else(|x| x).into() + main::run(args.into(), item.into(), &main::ARCH_WASM).into() } diff --git a/embassy-executor-macros/src/macros/main.rs b/embassy-executor-macros/src/macros/main.rs index 66a3965d0..14b1d9de2 100644 --- a/embassy-executor-macros/src/macros/main.rs +++ b/embassy-executor-macros/src/macros/main.rs @@ -1,152 +1,107 @@ +use std::str::FromStr; + use darling::export::NestedMeta; -use darling::{Error, FromMeta}; +use darling::FromMeta; use proc_macro2::TokenStream; use quote::quote; -use syn::{Expr, ReturnType, Type}; +use syn::{ReturnType, Type}; -use crate::util::ctxt::Ctxt; +use crate::util::*; -#[derive(Debug, FromMeta)] +enum Flavor { + Standard, + Wasm, +} + +pub(crate) struct Arch { + default_entry: Option<&'static str>, + flavor: Flavor, +} + +pub static ARCH_AVR: Arch = Arch { + default_entry: Some("avr_device::entry"), + flavor: Flavor::Standard, +}; + +pub static ARCH_RISCV: Arch = Arch { + default_entry: Some("riscv_rt::entry"), + flavor: Flavor::Standard, +}; + +pub static ARCH_CORTEX_M: Arch = Arch { + default_entry: Some("cortex_m_rt::entry"), + flavor: Flavor::Standard, +}; + +pub static ARCH_SPIN: Arch = Arch { + default_entry: None, + flavor: Flavor::Standard, +}; + +pub static ARCH_STD: Arch = Arch { + default_entry: None, + flavor: Flavor::Standard, +}; + +pub static ARCH_WASM: Arch = Arch { + default_entry: Some("wasm_bindgen::prelude::wasm_bindgen(start)"), + flavor: Flavor::Wasm, +}; + +#[derive(Debug, FromMeta, Default)] struct Args { #[darling(default)] entry: Option, } -pub fn avr() -> TokenStream { - quote! { - #[avr_device::entry] - fn main() -> ! { - let mut executor = ::embassy_executor::Executor::new(); - let executor = unsafe { __make_static(&mut executor) }; +pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream { + let mut errors = TokenStream::new(); - executor.run(|spawner| { - spawner.must_spawn(__embassy_main(spawner)); - }) - } - } -} - -pub fn riscv(args: &[NestedMeta]) -> TokenStream { - let maybe_entry = match Args::from_list(args) { - Ok(args) => args.entry, - Err(e) => return e.write_errors(), + // If any of the steps for this macro fail, we still want to expand to an item that is as close + // to the expected output as possible. This helps out IDEs such that completions and other + // related features keep working. + let f: ItemFn = match syn::parse2(item.clone()) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), }; - let entry = maybe_entry.unwrap_or("riscv_rt::entry".into()); - let entry = match Expr::from_string(&entry) { - Ok(expr) => expr, - Err(e) => return e.write_errors(), + let args = match NestedMeta::parse_meta_list(args) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), }; - quote! { - #[#entry] - fn main() -> ! { - let mut executor = ::embassy_executor::Executor::new(); - let executor = unsafe { __make_static(&mut executor) }; - executor.run(|spawner| { - spawner.must_spawn(__embassy_main(spawner)); - }) + let args = match Args::from_list(&args) { + Ok(x) => x, + Err(e) => { + errors.extend(e.write_errors()); + Args::default() } - } -} - -pub fn spin(args: &[NestedMeta]) -> TokenStream { - let maybe_entry = match Args::from_list(args) { - Ok(args) => args.entry, - Err(e) => return e.write_errors(), }; - let entry = match maybe_entry { - Some(str) => str, - None => return Error::missing_field("entry").write_errors(), - }; - let entry = match Expr::from_string(&entry) { - Ok(expr) => expr, - Err(e) => return e.write_errors(), - }; - - quote! { - #[#entry] - fn main() -> ! { - let mut executor = ::embassy_executor::Executor::new(); - let executor = unsafe { __make_static(&mut executor) }; - executor.run(|spawner| { - spawner.must_spawn(__embassy_main(spawner)); - }) - } - } -} - -pub fn cortex_m() -> TokenStream { - quote! { - #[cortex_m_rt::entry] - fn main() -> ! { - let mut executor = ::embassy_executor::Executor::new(); - let executor = unsafe { __make_static(&mut executor) }; - executor.run(|spawner| { - spawner.must_spawn(__embassy_main(spawner)); - }) - } - } -} - -pub fn wasm() -> TokenStream { - quote! { - #[wasm_bindgen::prelude::wasm_bindgen(start)] - pub fn main() -> Result<(), wasm_bindgen::JsValue> { - let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new())); - - executor.start(|spawner| { - spawner.must_spawn(__embassy_main(spawner)); - }); - - Ok(()) - } - } -} - -pub fn std() -> TokenStream { - quote! { - fn main() -> ! { - let mut executor = ::embassy_executor::Executor::new(); - let executor = unsafe { __make_static(&mut executor) }; - - executor.run(|spawner| { - spawner.must_spawn(__embassy_main(spawner)); - }) - } - } -} - -pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result { - #[allow(unused_variables)] - let args = Args::from_list(args).map_err(|e| e.write_errors())?; - let fargs = f.sig.inputs.clone(); - let ctxt = Ctxt::new(); - if f.sig.asyncness.is_none() { - ctxt.error_spanned_by(&f.sig, "main function must be async"); + error(&mut errors, &f.sig, "main function must be async"); } if !f.sig.generics.params.is_empty() { - ctxt.error_spanned_by(&f.sig, "main function must not be generic"); + error(&mut errors, &f.sig, "main function must not be generic"); } if !f.sig.generics.where_clause.is_none() { - ctxt.error_spanned_by(&f.sig, "main function must not have `where` clauses"); + error(&mut errors, &f.sig, "main function must not have `where` clauses"); } if !f.sig.abi.is_none() { - ctxt.error_spanned_by(&f.sig, "main function must not have an ABI qualifier"); + error(&mut errors, &f.sig, "main function must not have an ABI qualifier"); } if !f.sig.variadic.is_none() { - ctxt.error_spanned_by(&f.sig, "main function must not be variadic"); + error(&mut errors, &f.sig, "main function must not be variadic"); } match &f.sig.output { ReturnType::Default => {} ReturnType::Type(_, ty) => match &**ty { Type::Tuple(tuple) if tuple.elems.is_empty() => {} Type::Never(_) => {} - _ => ctxt.error_spanned_by( + _ => error( + &mut errors, &f.sig, "main function must either not return a value, return `()` or return `!`", ), @@ -154,26 +109,69 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result TokenStream::new(), + Some(x) => match TokenStream::from_str(x) { + Ok(x) => quote!(#[#x]), + Err(e) => { + error(&mut errors, &f.sig, e); + TokenStream::new() + } + }, + }; - let f_body = f.block; + let f_body = f.body; let out = &f.sig.output; + let (main_ret, mut main_body) = match arch.flavor { + Flavor::Standard => ( + quote!(!), + quote! { + unsafe fn __make_static(t: &mut T) -> &'static mut T { + ::core::mem::transmute(t) + } + + let mut executor = ::embassy_executor::Executor::new(); + let executor = unsafe { __make_static(&mut executor) }; + executor.run(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }) + }, + ), + Flavor::Wasm => ( + quote!(Result<(), wasm_bindgen::JsValue>), + quote! { + let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new())); + + executor.start(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }); + + Ok(()) + }, + ), + }; + + if !errors.is_empty() { + main_body = quote! {loop{}}; + } + let result = quote! { #[::embassy_executor::task()] async fn __embassy_main(#fargs) #out { #f_body } - unsafe fn __make_static(t: &mut T) -> &'static mut T { - ::core::mem::transmute(t) + #entry + fn main() -> #main_ret { + #main_body } - #main + #errors }; - Ok(result) + result } diff --git a/embassy-executor-macros/src/macros/task.rs b/embassy-executor-macros/src/macros/task.rs index 96c6267b2..0404dba64 100644 --- a/embassy-executor-macros/src/macros/task.rs +++ b/embassy-executor-macros/src/macros/task.rs @@ -2,47 +2,68 @@ use darling::export::NestedMeta; use darling::FromMeta; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; -use syn::{parse_quote, Expr, ExprLit, ItemFn, Lit, LitInt, ReturnType, Type}; +use syn::visit::Visit; +use syn::{Expr, ExprLit, Lit, LitInt, ReturnType, Type}; -use crate::util::ctxt::Ctxt; +use crate::util::*; -#[derive(Debug, FromMeta)] +#[derive(Debug, FromMeta, Default)] struct Args { #[darling(default)] pool_size: Option, } -pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result { - let args = Args::from_list(args).map_err(|e| e.write_errors())?; +pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { + let mut errors = TokenStream::new(); + + // If any of the steps for this macro fail, we still want to expand to an item that is as close + // to the expected output as possible. This helps out IDEs such that completions and other + // related features keep working. + let f: ItemFn = match syn::parse2(item.clone()) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), + }; + + let args = match NestedMeta::parse_meta_list(args) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), + }; + + let args = match Args::from_list(&args) { + Ok(x) => x, + Err(e) => { + errors.extend(e.write_errors()); + Args::default() + } + }; let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit { attrs: vec![], lit: Lit::Int(LitInt::new("1", Span::call_site())), })); - let ctxt = Ctxt::new(); - if f.sig.asyncness.is_none() { - ctxt.error_spanned_by(&f.sig, "task functions must be async"); + error(&mut errors, &f.sig, "task functions must be async"); } if !f.sig.generics.params.is_empty() { - ctxt.error_spanned_by(&f.sig, "task functions must not be generic"); + error(&mut errors, &f.sig, "task functions must not be generic"); } if !f.sig.generics.where_clause.is_none() { - ctxt.error_spanned_by(&f.sig, "task functions must not have `where` clauses"); + error(&mut errors, &f.sig, "task functions must not have `where` clauses"); } if !f.sig.abi.is_none() { - ctxt.error_spanned_by(&f.sig, "task functions must not have an ABI qualifier"); + error(&mut errors, &f.sig, "task functions must not have an ABI qualifier"); } if !f.sig.variadic.is_none() { - ctxt.error_spanned_by(&f.sig, "task functions must not be variadic"); + error(&mut errors, &f.sig, "task functions must not be variadic"); } match &f.sig.output { ReturnType::Default => {} ReturnType::Type(_, ty) => match &**ty { Type::Tuple(tuple) if tuple.elems.is_empty() => {} Type::Never(_) => {} - _ => ctxt.error_spanned_by( + _ => error( + &mut errors, &f.sig, "task functions must either not return a value, return `()` or return `!`", ), @@ -55,26 +76,31 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result { - ctxt.error_spanned_by(arg, "task functions must not have receiver arguments"); + error(&mut errors, arg, "task functions must not have receiver arguments"); } - syn::FnArg::Typed(t) => match t.pat.as_mut() { - syn::Pat::Ident(id) => { - id.mutability = None; - args.push((id.clone(), t.attrs.clone())); + syn::FnArg::Typed(t) => { + check_arg_ty(&mut errors, &t.ty); + match t.pat.as_mut() { + syn::Pat::Ident(id) => { + id.mutability = None; + args.push((id.clone(), t.attrs.clone())); + } + _ => { + error( + &mut errors, + arg, + "pattern matching in task arguments is not yet supported", + ); + } } - _ => { - ctxt.error_spanned_by(arg, "pattern matching in task arguments is not yet supported"); - } - }, + } } } - ctxt.check()?; - let task_ident = f.sig.ident.clone(); let task_inner_ident = format_ident!("__{}_task", task_ident); - let mut task_inner = f; + let mut task_inner = f.clone(); let visibility = task_inner.vis.clone(); task_inner.vis = syn::Visibility::Inherited; task_inner.sig.ident = task_inner_ident.clone(); @@ -91,35 +117,43 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result ::embassy_executor::SpawnToken { - trait _EmbassyInternalTaskTrait { - type Fut: ::core::future::Future + 'static; - fn construct(#fargs) -> Self::Fut; - } - - impl _EmbassyInternalTaskTrait for () { - type Fut = impl core::future::Future + 'static; - fn construct(#fargs) -> Self::Fut { - #task_inner_ident(#(#full_args,)*) - } - } - - const POOL_SIZE: usize = #pool_size; - static POOL: ::embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = ::embassy_executor::raw::TaskPool::new(); - unsafe { POOL._spawn_async_fn(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) } + let mut task_outer_body = quote! { + trait _EmbassyInternalTaskTrait { + type Fut: ::core::future::Future + 'static; + fn construct(#fargs) -> Self::Fut; } + + impl _EmbassyInternalTaskTrait for () { + type Fut = impl core::future::Future + 'static; + fn construct(#fargs) -> Self::Fut { + #task_inner_ident(#(#full_args,)*) + } + } + + const POOL_SIZE: usize = #pool_size; + static POOL: ::embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = ::embassy_executor::raw::TaskPool::new(); + unsafe { POOL._spawn_async_fn(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) } }; #[cfg(not(feature = "nightly"))] - let mut task_outer: ItemFn = parse_quote! { - #visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken { - const POOL_SIZE: usize = #pool_size; - static POOL: ::embassy_executor::_export::TaskPoolRef = ::embassy_executor::_export::TaskPoolRef::new(); - unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) } - } + let mut task_outer_body = quote! { + const POOL_SIZE: usize = #pool_size; + static POOL: ::embassy_executor::_export::TaskPoolRef = ::embassy_executor::_export::TaskPoolRef::new(); + unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) } }; - task_outer.attrs.append(&mut task_inner.attrs.clone()); + let task_outer_attrs = task_inner.attrs.clone(); + + if !errors.is_empty() { + task_outer_body = quote! { + #![allow(unused_variables, unreachable_code)] + let _x: ::embassy_executor::SpawnToken<()> = ::core::todo!(); + _x + }; + } + + // Copy the generics + where clause to avoid more spurious errors. + let generics = &f.sig.generics; + let where_clause = &f.sig.generics.where_clause; let result = quote! { // This is the user's task function, renamed. @@ -129,8 +163,27 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result ::embassy_executor::SpawnToken #where_clause{ + #task_outer_body + } + + #errors }; - Ok(result) + result +} + +fn check_arg_ty(errors: &mut TokenStream, ty: &Type) { + struct Visitor<'a> { + errors: &'a mut TokenStream, + } + + impl<'a, 'ast> Visit<'ast> for Visitor<'a> { + fn visit_type_impl_trait(&mut self, i: &'ast syn::TypeImplTrait) { + error(self.errors, i, "`impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic."); + } + } + + Visit::visit_type(&mut Visitor { errors }, ty); } diff --git a/embassy-executor-macros/src/util.rs b/embassy-executor-macros/src/util.rs new file mode 100644 index 000000000..ebd032a62 --- /dev/null +++ b/embassy-executor-macros/src/util.rs @@ -0,0 +1,74 @@ +use std::fmt::Display; + +use proc_macro2::{TokenStream, TokenTree}; +use quote::{ToTokens, TokenStreamExt}; +use syn::parse::{Parse, ParseStream}; +use syn::{braced, bracketed, token, AttrStyle, Attribute, Signature, Token, Visibility}; + +pub fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream { + tokens.extend(error.into_compile_error()); + tokens +} + +pub fn error(s: &mut TokenStream, obj: A, msg: T) { + s.extend(syn::Error::new_spanned(obj.into_token_stream(), msg).into_compile_error()) +} + +/// Function signature and body. +/// +/// Same as `syn`'s `ItemFn` except we keep the body as a TokenStream instead of +/// parsing it. This makes the macro not error if there's a syntax error in the body, +/// which helps IDE autocomplete work better. +#[derive(Debug, Clone)] +pub struct ItemFn { + pub attrs: Vec, + pub vis: Visibility, + pub sig: Signature, + pub brace_token: token::Brace, + pub body: TokenStream, +} + +impl Parse for ItemFn { + fn parse(input: ParseStream) -> syn::Result { + let mut attrs = input.call(Attribute::parse_outer)?; + let vis: Visibility = input.parse()?; + let sig: Signature = input.parse()?; + + let content; + let brace_token = braced!(content in input); + while content.peek(Token![#]) && content.peek2(Token![!]) { + let content2; + attrs.push(Attribute { + pound_token: content.parse()?, + style: AttrStyle::Inner(content.parse()?), + bracket_token: bracketed!(content2 in content), + meta: content2.parse()?, + }); + } + + let mut body = Vec::new(); + while !content.is_empty() { + body.push(content.parse::()?); + } + let body = body.into_iter().collect(); + + Ok(ItemFn { + attrs, + vis, + sig, + brace_token, + body, + }) + } +} + +impl ToTokens for ItemFn { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append_all(self.attrs.iter().filter(|a| matches!(a.style, AttrStyle::Outer))); + self.vis.to_tokens(tokens); + self.sig.to_tokens(tokens); + self.brace_token.surround(tokens, |tokens| { + tokens.append_all(self.body.clone()); + }); + } +} diff --git a/embassy-executor-macros/src/util/ctxt.rs b/embassy-executor-macros/src/util/ctxt.rs deleted file mode 100644 index 9c78cda01..000000000 --- a/embassy-executor-macros/src/util/ctxt.rs +++ /dev/null @@ -1,72 +0,0 @@ -// nifty utility borrowed from serde :) -// https://github.com/serde-rs/serde/blob/master/serde_derive/src/internals/ctxt.rs - -use std::cell::RefCell; -use std::fmt::Display; -use std::thread; - -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; - -/// A type to collect errors together and format them. -/// -/// Dropping this object will cause a panic. It must be consumed using `check`. -/// -/// References can be shared since this type uses run-time exclusive mut checking. -#[derive(Default)] -pub struct Ctxt { - // The contents will be set to `None` during checking. This is so that checking can be - // enforced. - errors: RefCell>>, -} - -impl Ctxt { - /// Create a new context object. - /// - /// This object contains no errors, but will still trigger a panic if it is not `check`ed. - pub fn new() -> Self { - Ctxt { - errors: RefCell::new(Some(Vec::new())), - } - } - - /// Add an error to the context object with a tokenenizable object. - /// - /// The object is used for spanning in error messages. - pub fn error_spanned_by(&self, obj: A, msg: T) { - self.errors - .borrow_mut() - .as_mut() - .unwrap() - // Curb monomorphization from generating too many identical methods. - .push(syn::Error::new_spanned(obj.into_token_stream(), msg)); - } - - /// Add one of Syn's parse errors. - #[allow(unused)] - pub fn syn_error(&self, err: syn::Error) { - self.errors.borrow_mut().as_mut().unwrap().push(err); - } - - /// Consume this object, producing a formatted error string if there are errors. - pub fn check(self) -> Result<(), TokenStream> { - let errors = self.errors.borrow_mut().take().unwrap(); - match errors.len() { - 0 => Ok(()), - _ => Err(to_compile_errors(errors)), - } - } -} - -fn to_compile_errors(errors: Vec) -> proc_macro2::TokenStream { - let compile_errors = errors.iter().map(syn::Error::to_compile_error); - quote!(#(#compile_errors)*) -} - -impl Drop for Ctxt { - fn drop(&mut self) { - if !thread::panicking() && self.errors.borrow().is_some() { - panic!("forgot to check for errors"); - } - } -} diff --git a/embassy-executor-macros/src/util/mod.rs b/embassy-executor-macros/src/util/mod.rs deleted file mode 100644 index 28702809e..000000000 --- a/embassy-executor-macros/src/util/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod ctxt; diff --git a/examples/stm32h7/src/bin/i2c_shared.rs b/examples/stm32h7/src/bin/i2c_shared.rs index 6f4815582..136b91eeb 100644 --- a/examples/stm32h7/src/bin/i2c_shared.rs +++ b/examples/stm32h7/src/bin/i2c_shared.rs @@ -10,8 +10,10 @@ use embassy_stm32::i2c::{self, I2c}; use embassy_stm32::mode::Async; use embassy_stm32::time::Hertz; use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::blocking_mutex::NoopMutex; use embassy_time::{Duration, Timer}; +use embedded_hal_1::i2c::I2c as _; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -31,7 +33,7 @@ bind_interrupts!(struct Irqs { }); #[embassy_executor::task] -async fn temperature(mut i2c: impl embedded_hal_1::i2c::I2c + 'static) { +async fn temperature(mut i2c: I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>) { let mut data = [0u8; 2]; loop { @@ -48,7 +50,7 @@ async fn temperature(mut i2c: impl embedded_hal_1::i2c::I2c + 'static) { } #[embassy_executor::task] -async fn humidity(mut i2c: impl embedded_hal_1::i2c::I2c + 'static) { +async fn humidity(mut i2c: I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>) { let mut data = [0u8; 6]; loop { From f21b19164be17df930aa557a90b3c5ab1dc39b9e Mon Sep 17 00:00:00 2001 From: Christian Enderle Date: Sat, 19 Oct 2024 15:36:56 +0200 Subject: [PATCH 088/144] embassy-futures: add select 5 and 6 --- embassy-futures/src/select.rs | 163 ++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/embassy-futures/src/select.rs b/embassy-futures/src/select.rs index 57f0cb41f..bb175253b 100644 --- a/embassy-futures/src/select.rs +++ b/embassy-futures/src/select.rs @@ -188,6 +188,169 @@ where // ==================================================================== +/// Result for [`select5`]. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Either5 { + /// First future finished first. + First(A), + /// Second future finished first. + Second(B), + /// Third future finished first. + Third(C), + /// Fourth future finished first. + Fourth(D), + /// Fifth future finished first. + Fifth(E), +} + +/// Same as [`select`], but with more futures. +pub fn select5(a: A, b: B, c: C, d: D, e: E) -> Select5 +where + A: Future, + B: Future, + C: Future, + D: Future, + E: Future, +{ + Select5 { a, b, c, d, e } +} + +/// Future for the [`select5`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Select5 { + a: A, + b: B, + c: C, + d: D, + e: E, +} + +impl Future for Select5 +where + A: Future, + B: Future, + C: Future, + D: Future, + E: Future, +{ + type Output = Either5; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let a = unsafe { Pin::new_unchecked(&mut this.a) }; + let b = unsafe { Pin::new_unchecked(&mut this.b) }; + let c = unsafe { Pin::new_unchecked(&mut this.c) }; + let d = unsafe { Pin::new_unchecked(&mut this.d) }; + let e = unsafe { Pin::new_unchecked(&mut this.e) }; + if let Poll::Ready(x) = a.poll(cx) { + return Poll::Ready(Either5::First(x)); + } + if let Poll::Ready(x) = b.poll(cx) { + return Poll::Ready(Either5::Second(x)); + } + if let Poll::Ready(x) = c.poll(cx) { + return Poll::Ready(Either5::Third(x)); + } + if let Poll::Ready(x) = d.poll(cx) { + return Poll::Ready(Either5::Fourth(x)); + } + if let Poll::Ready(x) = e.poll(cx) { + return Poll::Ready(Either5::Fifth(x)); + } + Poll::Pending + } +} + +// ==================================================================== + +/// Result for [`select6`]. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Either6 { + /// First future finished first. + First(A), + /// Second future finished first. + Second(B), + /// Third future finished first. + Third(C), + /// Fourth future finished first. + Fourth(D), + /// Fifth future finished first. + Fifth(E), + /// Sixth future finished first. + Sixth(F), +} + +/// Same as [`select`], but with more futures. +pub fn select6(a: A, b: B, c: C, d: D, e: E, f: F) -> Select6 +where + A: Future, + B: Future, + C: Future, + D: Future, + E: Future, + F: Future, +{ + Select6 { a, b, c, d, e, f } +} + +/// Future for the [`select6`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Select6 { + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, +} + +impl Future for Select6 +where + A: Future, + B: Future, + C: Future, + D: Future, + E: Future, + F: Future, +{ + type Output = Either6; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let a = unsafe { Pin::new_unchecked(&mut this.a) }; + let b = unsafe { Pin::new_unchecked(&mut this.b) }; + let c = unsafe { Pin::new_unchecked(&mut this.c) }; + let d = unsafe { Pin::new_unchecked(&mut this.d) }; + let e = unsafe { Pin::new_unchecked(&mut this.e) }; + let f = unsafe { Pin::new_unchecked(&mut this.f) }; + if let Poll::Ready(x) = a.poll(cx) { + return Poll::Ready(Either6::First(x)); + } + if let Poll::Ready(x) = b.poll(cx) { + return Poll::Ready(Either6::Second(x)); + } + if let Poll::Ready(x) = c.poll(cx) { + return Poll::Ready(Either6::Third(x)); + } + if let Poll::Ready(x) = d.poll(cx) { + return Poll::Ready(Either6::Fourth(x)); + } + if let Poll::Ready(x) = e.poll(cx) { + return Poll::Ready(Either6::Fifth(x)); + } + if let Poll::Ready(x) = f.poll(cx) { + return Poll::Ready(Either6::Sixth(x)); + } + Poll::Pending + } +} + +// ==================================================================== + /// Future for the [`select_array`] function. #[derive(Debug)] #[must_use = "futures do nothing unless you `.await` or poll them"] From 7fc09f89e8e4b611a868bc986104762b1c5ba81a Mon Sep 17 00:00:00 2001 From: rafael Date: Sun, 20 Oct 2024 23:28:47 +0200 Subject: [PATCH 089/144] embassy_rp: implement pwm traits from embedded_hal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ā€¢ Update crate versions ā€¢ Implement embedded-hal PWM traits ā€¢ Add TB6612FNG motor driver example --- embassy-rp/Cargo.toml | 8 +- embassy-rp/src/pwm.rs | 42 +++++++ examples/rp23/Cargo.toml | 3 + .../src/bin/pwm_tb6612fng_motor_driver.rs | 114 ++++++++++++++++++ 4 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index 54de238b3..baa92398b 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml @@ -118,18 +118,18 @@ embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" } atomic-polyfill = "1.0.1" defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } -nb = "1.0.0" +nb = "1.1.0" cfg-if = "1.0.0" cortex-m-rt = ">=0.6.15,<0.8" cortex-m = "0.7.6" -critical-section = "1.1" +critical-section = "1.2.0" chrono = { version = "0.4", default-features = false, optional = true } embedded-io = { version = "0.6.1" } embedded-io-async = { version = "0.6.1" } embedded-storage = { version = "0.3" } embedded-storage-async = { version = "0.4.1" } rand_core = "0.6.4" -fixed = "1.23.1" +fixed = "1.28.0" rp-pac = { git = "https://github.com/embassy-rs/rp-pac.git", rev = "a7f42d25517f7124ad3b4ed492dec8b0f50a0e6c", feature = ["rt"] } @@ -141,7 +141,7 @@ embedded-hal-nb = { version = "1.0" } pio-proc = {version= "0.2" } pio = {version= "0.2.1" } rp2040-boot2 = "0.3" -document-features = "0.2.7" +document-features = "0.2.10" sha2-const-stable = "0.1" rp-binary-info = { version = "0.1.0", optional = true } smart-leds = "0.4.0" diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs index 027f5504e..79e626802 100644 --- a/embassy-rp/src/pwm.rs +++ b/embassy-rp/src/pwm.rs @@ -1,6 +1,7 @@ //! Pulse Width Modulation (PWM) use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use embedded_hal_1::pwm::{Error, ErrorKind, ErrorType, SetDutyCycle}; use fixed::traits::ToFixed; use fixed::FixedU16; use pac::pwm::regs::{ChDiv, Intr}; @@ -80,6 +81,21 @@ impl From for Divmode { } } +/// PWM error. +#[derive(Debug)] +pub enum PwmError { + /// Invalid Duty Cycle. + InvalidDutyCycle, +} + +impl Error for PwmError { + fn kind(&self) -> ErrorKind { + match self { + PwmError::InvalidDutyCycle => ErrorKind::Other, + } + } +} + /// PWM driver. pub struct Pwm<'d> { pin_a: Option>, @@ -87,6 +103,32 @@ pub struct Pwm<'d> { slice: usize, } +impl<'d> ErrorType for Pwm<'d> { + type Error = PwmError; +} + +impl<'d> SetDutyCycle for Pwm<'d> { + fn max_duty_cycle(&self) -> u16 { + pac::PWM.ch(self.slice).top().read().top() + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + info!("duty {}",&duty); + let max_duty = self.max_duty_cycle(); + info!("max duty {}", &max_duty); + if duty > max_duty { + return Err(PwmError::InvalidDutyCycle); + } + + let p = pac::PWM.ch(self.slice); + p.cc().modify(|w| { + w.set_a(duty); + w.set_b(duty); + }); + Ok(()) + } +} + impl<'d> Pwm<'d> { fn new_inner( slice: usize, diff --git a/examples/rp23/Cargo.toml b/examples/rp23/Cargo.toml index 08646463c..f35e3a11d 100644 --- a/examples/rp23/Cargo.toml +++ b/examples/rp23/Cargo.toml @@ -30,6 +30,9 @@ serde-json-core = "0.5.1" # for assign resources example assign-resources = { git = "https://github.com/adamgreig/assign-resources", rev = "94ad10e2729afdf0fd5a77cd12e68409a982f58a" } +# for TB6612FNG example +tb6612fng = "1.0.0" + #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/rp23/src/bin/pwm_tb6612fng_motor_driver.rs b/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs new file mode 100644 index 000000000..92c1ff6ba --- /dev/null +++ b/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs @@ -0,0 +1,114 @@ +//! # PWM TB6612FNG motor driver +//! +//! This example shows the use of a TB6612FNG motor driver. The driver is built on top of embedded_hal and the example demonstrates how embassy_rp can be used to interact with ist. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::config::Config; +use embassy_rp::gpio::Output; +use embassy_rp::peripherals; +use embassy_rp::gpio; +use embassy_rp::pwm; +use embassy_time::Duration; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; +use tb6612fng::{DriveCommand, Motor, Tb6612fng}; +use assign_resources::assign_resources; + +/// Maximum PWM value (fully on) +const PWM_MAX: u16 = 50000; + +/// Minimum PWM value (fully off) +const PWM_MIN: u16 = 0; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +assign_resources! { + motor: MotorResources { + standby_pin: PIN_22, + left_slice: PWM_SLICE6, + left_pwm_pin: PIN_28, + left_forward_pin: PIN_21, + left_backward_pin: PIN_20, + right_slice: PWM_SLICE5, + right_pwm_pin: PIN_27, + right_forward_pin: PIN_19, + right_backward_pin: PIN_18, + }, +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Config::default()); + let s = split_resources!(p); + let r = s.motor; + + // we need a standby output and two motors to construct a full TB6612FNG + + // standby pin + let stby = Output::new(r.standby_pin, gpio::Level::Low); + + // motor A, here defined to be the left motor + let left_fwd = gpio::Output::new(r.left_forward_pin, gpio::Level::Low); + let left_bckw = gpio::Output::new(r.left_backward_pin, gpio::Level::Low); + let mut left_speed = pwm::Config::default(); + left_speed.top = PWM_MAX; + left_speed.compare_a = PWM_MIN; + let left_pwm = pwm::Pwm::new_output_a(r.left_slice, r.left_pwm_pin, left_speed); + let left_motor = Motor::new(left_fwd, left_bckw, left_pwm).unwrap(); + + // motor B, here defined to be the right motor + let right_fwd = gpio::Output::new(r.right_forward_pin, gpio::Level::Low); + let right_bckw = gpio::Output::new(r.right_backward_pin, gpio::Level::Low); + let mut right_speed = pwm::Config::default(); + right_speed.top = PWM_MAX; + right_speed.compare_b = PWM_MIN; + let right_pwm = pwm::Pwm::new_output_b(r.right_slice, r.right_pwm_pin, right_speed); + let right_motor = Motor::new(right_fwd, right_bckw, right_pwm).unwrap(); + + // construct the motor driver + let mut control = Tb6612fng::new(left_motor, right_motor, stby).unwrap(); + + loop { + // wake up the motor driver + info!("end standby"); + control.disable_standby().unwrap(); + Timer::after(Duration::from_millis(100)).await; + + // drive a straight line forward at 20% speed for 5s + info!("drive straight"); + control.motor_a.drive(DriveCommand::Forward(20)).unwrap(); + control.motor_b.drive(DriveCommand::Forward(20)).unwrap(); + Timer::after(Duration::from_secs(5)).await; + + // coast for 2s + info!("coast"); + control.motor_a.drive(DriveCommand::Stop).unwrap(); + control.motor_b.drive(DriveCommand::Stop).unwrap(); + Timer::after(Duration::from_secs(2)).await; + + // actively brake + info!("brake"); + control.motor_a.drive(DriveCommand::Brake).unwrap(); + control.motor_b.drive(DriveCommand::Brake).unwrap(); + Timer::after(Duration::from_secs(1)).await; + + // slowly turn for 3s + info!( "turn"); + control.motor_a.drive(DriveCommand::Backward(10)).unwrap(); + control.motor_b.drive(DriveCommand::Forward(10)).unwrap(); + Timer::after(Duration::from_secs(3)).await; + + // and put the driver in standby mode and wait for 5s + info!( "standby"); + control.enable_standby().unwrap(); + Timer::after(Duration::from_secs(5)).await; + } +} + From 8baf88f8f4668bdb54c1415bdb8ad62cf2fa5f24 Mon Sep 17 00:00:00 2001 From: rafael Date: Sun, 20 Oct 2024 23:31:53 +0200 Subject: [PATCH 090/144] rustfmt --- .../rp23/src/bin/pwm_tb6612fng_motor_driver.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs b/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs index 92c1ff6ba..36b8d9620 100644 --- a/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs +++ b/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs @@ -1,23 +1,23 @@ //! # PWM TB6612FNG motor driver -//! +//! //! This example shows the use of a TB6612FNG motor driver. The driver is built on top of embedded_hal and the example demonstrates how embassy_rp can be used to interact with ist. #![no_std] #![no_main] +use assign_resources::assign_resources; use defmt::*; use embassy_executor::Spawner; use embassy_rp::block::ImageDef; use embassy_rp::config::Config; +use embassy_rp::gpio; use embassy_rp::gpio::Output; use embassy_rp::peripherals; -use embassy_rp::gpio; use embassy_rp::pwm; use embassy_time::Duration; use embassy_time::Timer; -use {defmt_rtt as _, panic_probe as _}; use tb6612fng::{DriveCommand, Motor, Tb6612fng}; -use assign_resources::assign_resources; +use {defmt_rtt as _, panic_probe as _}; /// Maximum PWM value (fully on) const PWM_MAX: u16 = 50000; @@ -94,21 +94,20 @@ async fn main(_spawner: Spawner) { Timer::after(Duration::from_secs(2)).await; // actively brake - info!("brake"); + info!("brake"); control.motor_a.drive(DriveCommand::Brake).unwrap(); control.motor_b.drive(DriveCommand::Brake).unwrap(); Timer::after(Duration::from_secs(1)).await; // slowly turn for 3s - info!( "turn"); + info!("turn"); control.motor_a.drive(DriveCommand::Backward(10)).unwrap(); control.motor_b.drive(DriveCommand::Forward(10)).unwrap(); Timer::after(Duration::from_secs(3)).await; // and put the driver in standby mode and wait for 5s - info!( "standby"); + info!("standby"); control.enable_standby().unwrap(); Timer::after(Duration::from_secs(5)).await; } } - From f0de0493084759a5e7310c816919996b201f0bc4 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 20 Oct 2024 23:45:10 +0200 Subject: [PATCH 091/144] executor-macros: improve error messages. --- embassy-executor-macros/src/macros/task.rs | 26 ++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/embassy-executor-macros/src/macros/task.rs b/embassy-executor-macros/src/macros/task.rs index 0404dba64..2f2aeda76 100644 --- a/embassy-executor-macros/src/macros/task.rs +++ b/embassy-executor-macros/src/macros/task.rs @@ -2,7 +2,7 @@ use darling::export::NestedMeta; use darling::FromMeta; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; -use syn::visit::Visit; +use syn::visit::{self, Visit}; use syn::{Expr, ExprLit, Lit, LitInt, ReturnType, Type}; use crate::util::*; @@ -76,7 +76,7 @@ pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { for arg in fargs.iter_mut() { match arg { syn::FnArg::Receiver(_) => { - error(&mut errors, arg, "task functions must not have receiver arguments"); + error(&mut errors, arg, "task functions must not have `self` arguments"); } syn::FnArg::Typed(t) => { check_arg_ty(&mut errors, &t.ty); @@ -180,6 +180,28 @@ fn check_arg_ty(errors: &mut TokenStream, ty: &Type) { } impl<'a, 'ast> Visit<'ast> for Visitor<'a> { + fn visit_type_reference(&mut self, i: &'ast syn::TypeReference) { + // only check for elided lifetime here. If not elided, it's checked by `visit_lifetime`. + if i.lifetime.is_none() { + error( + self.errors, + i.and_token, + "Arguments for tasks must live forever. Try using the `'static` lifetime.", + ) + } + visit::visit_type_reference(self, i); + } + + fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) { + if i.ident.to_string() != "static" { + error( + self.errors, + i, + "Arguments for tasks must live forever. Try using the `'static` lifetime.", + ) + } + } + fn visit_type_impl_trait(&mut self, i: &'ast syn::TypeImplTrait) { error(self.errors, i, "`impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic."); } From 8f9826872332fb0d2abd3ffc3889ff4c0e1c3909 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 20 Oct 2024 23:48:58 +0200 Subject: [PATCH 092/144] executor: add compile-fail / ui tests. --- .github/ci/test-nightly.sh | 3 +++ .github/ci/test.sh | 1 + embassy-executor/Cargo.toml | 2 +- embassy-executor/tests/ui.rs | 23 +++++++++++++++++++ embassy-executor/tests/ui/abi.rs | 8 +++++++ embassy-executor/tests/ui/abi.stderr | 5 ++++ embassy-executor/tests/ui/bad_return.rs | 10 ++++++++ embassy-executor/tests/ui/bad_return.stderr | 5 ++++ embassy-executor/tests/ui/generics.rs | 8 +++++++ embassy-executor/tests/ui/generics.stderr | 5 ++++ embassy-executor/tests/ui/impl_trait.rs | 6 +++++ embassy-executor/tests/ui/impl_trait.stderr | 5 ++++ .../tests/ui/impl_trait_nested.rs | 8 +++++++ .../tests/ui/impl_trait_nested.stderr | 5 ++++ .../tests/ui/impl_trait_static.rs | 6 +++++ .../tests/ui/impl_trait_static.stderr | 5 ++++ .../tests/ui/nonstatic_ref_anon.rs | 6 +++++ .../tests/ui/nonstatic_ref_anon.stderr | 5 ++++ .../tests/ui/nonstatic_ref_anon_nested.rs | 6 +++++ .../tests/ui/nonstatic_ref_anon_nested.stderr | 5 ++++ .../tests/ui/nonstatic_ref_elided.rs | 6 +++++ .../tests/ui/nonstatic_ref_elided.stderr | 5 ++++ .../tests/ui/nonstatic_ref_generic.rs | 6 +++++ .../tests/ui/nonstatic_ref_generic.stderr | 11 +++++++++ .../tests/ui/nonstatic_struct_anon.rs | 8 +++++++ .../tests/ui/nonstatic_struct_anon.stderr | 5 ++++ .../tests/ui/nonstatic_struct_elided.rs | 8 +++++++ .../tests/ui/nonstatic_struct_elided.stderr | 10 ++++++++ .../tests/ui/nonstatic_struct_generic.rs | 8 +++++++ .../tests/ui/nonstatic_struct_generic.stderr | 11 +++++++++ embassy-executor/tests/ui/not_async.rs | 8 +++++++ embassy-executor/tests/ui/not_async.stderr | 5 ++++ embassy-executor/tests/ui/self.rs | 8 +++++++ embassy-executor/tests/ui/self.stderr | 13 +++++++++++ embassy-executor/tests/ui/self_ref.rs | 8 +++++++ embassy-executor/tests/ui/self_ref.stderr | 13 +++++++++++ embassy-executor/tests/ui/where_clause.rs | 12 ++++++++++ embassy-executor/tests/ui/where_clause.stderr | 7 ++++++ 38 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 embassy-executor/tests/ui.rs create mode 100644 embassy-executor/tests/ui/abi.rs create mode 100644 embassy-executor/tests/ui/abi.stderr create mode 100644 embassy-executor/tests/ui/bad_return.rs create mode 100644 embassy-executor/tests/ui/bad_return.stderr create mode 100644 embassy-executor/tests/ui/generics.rs create mode 100644 embassy-executor/tests/ui/generics.stderr create mode 100644 embassy-executor/tests/ui/impl_trait.rs create mode 100644 embassy-executor/tests/ui/impl_trait.stderr create mode 100644 embassy-executor/tests/ui/impl_trait_nested.rs create mode 100644 embassy-executor/tests/ui/impl_trait_nested.stderr create mode 100644 embassy-executor/tests/ui/impl_trait_static.rs create mode 100644 embassy-executor/tests/ui/impl_trait_static.stderr create mode 100644 embassy-executor/tests/ui/nonstatic_ref_anon.rs create mode 100644 embassy-executor/tests/ui/nonstatic_ref_anon.stderr create mode 100644 embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs create mode 100644 embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr create mode 100644 embassy-executor/tests/ui/nonstatic_ref_elided.rs create mode 100644 embassy-executor/tests/ui/nonstatic_ref_elided.stderr create mode 100644 embassy-executor/tests/ui/nonstatic_ref_generic.rs create mode 100644 embassy-executor/tests/ui/nonstatic_ref_generic.stderr create mode 100644 embassy-executor/tests/ui/nonstatic_struct_anon.rs create mode 100644 embassy-executor/tests/ui/nonstatic_struct_anon.stderr create mode 100644 embassy-executor/tests/ui/nonstatic_struct_elided.rs create mode 100644 embassy-executor/tests/ui/nonstatic_struct_elided.stderr create mode 100644 embassy-executor/tests/ui/nonstatic_struct_generic.rs create mode 100644 embassy-executor/tests/ui/nonstatic_struct_generic.stderr create mode 100644 embassy-executor/tests/ui/not_async.rs create mode 100644 embassy-executor/tests/ui/not_async.stderr create mode 100644 embassy-executor/tests/ui/self.rs create mode 100644 embassy-executor/tests/ui/self.stderr create mode 100644 embassy-executor/tests/ui/self_ref.rs create mode 100644 embassy-executor/tests/ui/self_ref.stderr create mode 100644 embassy-executor/tests/ui/where_clause.rs create mode 100644 embassy-executor/tests/ui/where_clause.stderr diff --git a/.github/ci/test-nightly.sh b/.github/ci/test-nightly.sh index 1724ffe89..a03b55e8d 100755 --- a/.github/ci/test-nightly.sh +++ b/.github/ci/test-nightly.sh @@ -9,6 +9,9 @@ export CARGO_HOME=/ci/cache/cargo export CARGO_TARGET_DIR=/ci/cache/target mv rust-toolchain-nightly.toml rust-toolchain.toml +cargo test --manifest-path ./embassy-executor/Cargo.toml +cargo test --manifest-path ./embassy-executor/Cargo.toml --features nightly + MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml --features nightly MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-sync/Cargo.toml diff --git a/.github/ci/test.sh b/.github/ci/test.sh index 0de265049..0fe088bfe 100755 --- a/.github/ci/test.sh +++ b/.github/ci/test.sh @@ -12,6 +12,7 @@ export CARGO_TARGET_DIR=/ci/cache/target # used when pointing stm32-metapac to a CI-built one. export CARGO_NET_GIT_FETCH_WITH_CLI=true +cargo test --manifest-path ./embassy-executor/Cargo.toml cargo test --manifest-path ./embassy-futures/Cargo.toml cargo test --manifest-path ./embassy-sync/Cargo.toml cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index e2fedce3c..e138f93b0 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml @@ -55,7 +55,7 @@ avr-device = { version = "0.5.3", optional = true } [dev-dependencies] critical-section = { version = "1.1", features = ["std"] } - +trybuild = "1.0" [features] diff --git a/embassy-executor/tests/ui.rs b/embassy-executor/tests/ui.rs new file mode 100644 index 000000000..be4679485 --- /dev/null +++ b/embassy-executor/tests/ui.rs @@ -0,0 +1,23 @@ +#[cfg(not(miri))] +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/abi.rs"); + t.compile_fail("tests/ui/bad_return.rs"); + t.compile_fail("tests/ui/generics.rs"); + t.compile_fail("tests/ui/impl_trait_nested.rs"); + t.compile_fail("tests/ui/impl_trait.rs"); + t.compile_fail("tests/ui/impl_trait_static.rs"); + t.compile_fail("tests/ui/nonstatic_ref_anon_nested.rs"); + t.compile_fail("tests/ui/nonstatic_ref_anon.rs"); + t.compile_fail("tests/ui/nonstatic_ref_elided.rs"); + t.compile_fail("tests/ui/nonstatic_ref_generic.rs"); + t.compile_fail("tests/ui/nonstatic_struct_anon.rs"); + #[cfg(not(feature = "nightly"))] // we can't catch this case with the macro, so the output changes on nightly. + t.compile_fail("tests/ui/nonstatic_struct_elided.rs"); + t.compile_fail("tests/ui/nonstatic_struct_generic.rs"); + t.compile_fail("tests/ui/not_async.rs"); + t.compile_fail("tests/ui/self_ref.rs"); + t.compile_fail("tests/ui/self.rs"); + t.compile_fail("tests/ui/where_clause.rs"); +} diff --git a/embassy-executor/tests/ui/abi.rs b/embassy-executor/tests/ui/abi.rs new file mode 100644 index 000000000..fd52f7e41 --- /dev/null +++ b/embassy-executor/tests/ui/abi.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async extern "C" fn task() {} + +fn main() {} diff --git a/embassy-executor/tests/ui/abi.stderr b/embassy-executor/tests/ui/abi.stderr new file mode 100644 index 000000000..e264e371a --- /dev/null +++ b/embassy-executor/tests/ui/abi.stderr @@ -0,0 +1,5 @@ +error: task functions must not have an ABI qualifier + --> tests/ui/abi.rs:6:1 + | +6 | async extern "C" fn task() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/bad_return.rs b/embassy-executor/tests/ui/bad_return.rs new file mode 100644 index 000000000..f09a5205b --- /dev/null +++ b/embassy-executor/tests/ui/bad_return.rs @@ -0,0 +1,10 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task() -> u32 { + 5 +} + +fn main() {} diff --git a/embassy-executor/tests/ui/bad_return.stderr b/embassy-executor/tests/ui/bad_return.stderr new file mode 100644 index 000000000..e9d94dff8 --- /dev/null +++ b/embassy-executor/tests/ui/bad_return.stderr @@ -0,0 +1,5 @@ +error: task functions must either not return a value, return `()` or return `!` + --> tests/ui/bad_return.rs:6:1 + | +6 | async fn task() -> u32 { + | ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/generics.rs b/embassy-executor/tests/ui/generics.rs new file mode 100644 index 000000000..b83123bb1 --- /dev/null +++ b/embassy-executor/tests/ui/generics.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(_t: T) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/generics.stderr b/embassy-executor/tests/ui/generics.stderr new file mode 100644 index 000000000..197719a7b --- /dev/null +++ b/embassy-executor/tests/ui/generics.stderr @@ -0,0 +1,5 @@ +error: task functions must not be generic + --> tests/ui/generics.rs:6:1 + | +6 | async fn task(_t: T) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/impl_trait.rs b/embassy-executor/tests/ui/impl_trait.rs new file mode 100644 index 000000000..a21402aa0 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: impl Sized) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/impl_trait.stderr b/embassy-executor/tests/ui/impl_trait.stderr new file mode 100644 index 000000000..099b1828f --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait.stderr @@ -0,0 +1,5 @@ +error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic. + --> tests/ui/impl_trait.rs:4:18 + | +4 | async fn foo(_x: impl Sized) {} + | ^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/impl_trait_nested.rs b/embassy-executor/tests/ui/impl_trait_nested.rs new file mode 100644 index 000000000..07442b8fa --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_nested.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo(T); + +#[embassy_executor::task] +async fn foo(_x: Foo) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/impl_trait_nested.stderr b/embassy-executor/tests/ui/impl_trait_nested.stderr new file mode 100644 index 000000000..39592f958 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_nested.stderr @@ -0,0 +1,5 @@ +error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic. + --> tests/ui/impl_trait_nested.rs:6:22 + | +6 | async fn foo(_x: Foo) {} + | ^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/impl_trait_static.rs b/embassy-executor/tests/ui/impl_trait_static.rs new file mode 100644 index 000000000..272470f98 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_static.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: impl Sized + 'static) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/impl_trait_static.stderr b/embassy-executor/tests/ui/impl_trait_static.stderr new file mode 100644 index 000000000..0032a20c9 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_static.stderr @@ -0,0 +1,5 @@ +error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic. + --> tests/ui/impl_trait_static.rs:4:18 + | +4 | async fn foo(_x: impl Sized + 'static) {} + | ^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon.rs b/embassy-executor/tests/ui/nonstatic_ref_anon.rs new file mode 100644 index 000000000..417c360a1 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: &'_ u32) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon.stderr b/embassy-executor/tests/ui/nonstatic_ref_anon.stderr new file mode 100644 index 000000000..0544de843 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon.stderr @@ -0,0 +1,5 @@ +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_ref_anon.rs:4:19 + | +4 | async fn foo(_x: &'_ u32) {} + | ^^ diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs new file mode 100644 index 000000000..175ebccc1 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: &'static &'_ u32) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr new file mode 100644 index 000000000..79f262e6b --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr @@ -0,0 +1,5 @@ +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_ref_anon_nested.rs:4:28 + | +4 | async fn foo(_x: &'static &'_ u32) {} + | ^^ diff --git a/embassy-executor/tests/ui/nonstatic_ref_elided.rs b/embassy-executor/tests/ui/nonstatic_ref_elided.rs new file mode 100644 index 000000000..cf49ad709 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_elided.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: &u32) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_ref_elided.stderr b/embassy-executor/tests/ui/nonstatic_ref_elided.stderr new file mode 100644 index 000000000..7e2b9eb7c --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_elided.stderr @@ -0,0 +1,5 @@ +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_ref_elided.rs:4:18 + | +4 | async fn foo(_x: &u32) {} + | ^ diff --git a/embassy-executor/tests/ui/nonstatic_ref_generic.rs b/embassy-executor/tests/ui/nonstatic_ref_generic.rs new file mode 100644 index 000000000..3f8a26cf8 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_generic.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo<'a>(_x: &'a u32) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_ref_generic.stderr b/embassy-executor/tests/ui/nonstatic_ref_generic.stderr new file mode 100644 index 000000000..af8491ad7 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_generic.stderr @@ -0,0 +1,11 @@ +error: task functions must not be generic + --> tests/ui/nonstatic_ref_generic.rs:4:1 + | +4 | async fn foo<'a>(_x: &'a u32) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_ref_generic.rs:4:23 + | +4 | async fn foo<'a>(_x: &'a u32) {} + | ^^ diff --git a/embassy-executor/tests/ui/nonstatic_struct_anon.rs b/embassy-executor/tests/ui/nonstatic_struct_anon.rs new file mode 100644 index 000000000..ba95d1459 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_anon.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(_x: Foo<'_>) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_struct_anon.stderr b/embassy-executor/tests/ui/nonstatic_struct_anon.stderr new file mode 100644 index 000000000..5df2a6e06 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_anon.stderr @@ -0,0 +1,5 @@ +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_struct_anon.rs:6:23 + | +6 | async fn task(_x: Foo<'_>) {} + | ^^ diff --git a/embassy-executor/tests/ui/nonstatic_struct_elided.rs b/embassy-executor/tests/ui/nonstatic_struct_elided.rs new file mode 100644 index 000000000..4cfe2966a --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_elided.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(_x: Foo) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_struct_elided.stderr b/embassy-executor/tests/ui/nonstatic_struct_elided.stderr new file mode 100644 index 000000000..099ef8b4e --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_elided.stderr @@ -0,0 +1,10 @@ +error[E0726]: implicit elided lifetime not allowed here + --> tests/ui/nonstatic_struct_elided.rs:6:19 + | +6 | async fn task(_x: Foo) {} + | ^^^ expected lifetime parameter + | +help: indicate the anonymous lifetime + | +6 | async fn task(_x: Foo<'_>) {} + | ++++ diff --git a/embassy-executor/tests/ui/nonstatic_struct_generic.rs b/embassy-executor/tests/ui/nonstatic_struct_generic.rs new file mode 100644 index 000000000..ec3d908f6 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_generic.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task<'a>(_x: Foo<'a>) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_struct_generic.stderr b/embassy-executor/tests/ui/nonstatic_struct_generic.stderr new file mode 100644 index 000000000..61d5231bc --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_generic.stderr @@ -0,0 +1,11 @@ +error: task functions must not be generic + --> tests/ui/nonstatic_struct_generic.rs:6:1 + | +6 | async fn task<'a>(_x: Foo<'a>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_struct_generic.rs:6:27 + | +6 | async fn task<'a>(_x: Foo<'a>) {} + | ^^ diff --git a/embassy-executor/tests/ui/not_async.rs b/embassy-executor/tests/ui/not_async.rs new file mode 100644 index 000000000..f3f7e9bd2 --- /dev/null +++ b/embassy-executor/tests/ui/not_async.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +fn task() {} + +fn main() {} diff --git a/embassy-executor/tests/ui/not_async.stderr b/embassy-executor/tests/ui/not_async.stderr new file mode 100644 index 000000000..27f040d9c --- /dev/null +++ b/embassy-executor/tests/ui/not_async.stderr @@ -0,0 +1,5 @@ +error: task functions must be async + --> tests/ui/not_async.rs:6:1 + | +6 | fn task() {} + | ^^^^^^^^^ diff --git a/embassy-executor/tests/ui/self.rs b/embassy-executor/tests/ui/self.rs new file mode 100644 index 000000000..f83a962d1 --- /dev/null +++ b/embassy-executor/tests/ui/self.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(self) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/self.stderr b/embassy-executor/tests/ui/self.stderr new file mode 100644 index 000000000..aaf031573 --- /dev/null +++ b/embassy-executor/tests/ui/self.stderr @@ -0,0 +1,13 @@ +error: task functions must not have `self` arguments + --> tests/ui/self.rs:6:15 + | +6 | async fn task(self) {} + | ^^^^ + +error: `self` parameter is only allowed in associated functions + --> tests/ui/self.rs:6:15 + | +6 | async fn task(self) {} + | ^^^^ not semantically valid as function parameter + | + = note: associated functions are those in `impl` or `trait` definitions diff --git a/embassy-executor/tests/ui/self_ref.rs b/embassy-executor/tests/ui/self_ref.rs new file mode 100644 index 000000000..5e49bba5e --- /dev/null +++ b/embassy-executor/tests/ui/self_ref.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(&mut self) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/self_ref.stderr b/embassy-executor/tests/ui/self_ref.stderr new file mode 100644 index 000000000..dd2052977 --- /dev/null +++ b/embassy-executor/tests/ui/self_ref.stderr @@ -0,0 +1,13 @@ +error: task functions must not have `self` arguments + --> tests/ui/self_ref.rs:6:15 + | +6 | async fn task(&mut self) {} + | ^^^^^^^^^ + +error: `self` parameter is only allowed in associated functions + --> tests/ui/self_ref.rs:6:15 + | +6 | async fn task(&mut self) {} + | ^^^^^^^^^ not semantically valid as function parameter + | + = note: associated functions are those in `impl` or `trait` definitions diff --git a/embassy-executor/tests/ui/where_clause.rs b/embassy-executor/tests/ui/where_clause.rs new file mode 100644 index 000000000..848d78149 --- /dev/null +++ b/embassy-executor/tests/ui/where_clause.rs @@ -0,0 +1,12 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task() +where + (): Sized, +{ +} + +fn main() {} diff --git a/embassy-executor/tests/ui/where_clause.stderr b/embassy-executor/tests/ui/where_clause.stderr new file mode 100644 index 000000000..eba45af40 --- /dev/null +++ b/embassy-executor/tests/ui/where_clause.stderr @@ -0,0 +1,7 @@ +error: task functions must not have `where` clauses + --> tests/ui/where_clause.rs:6:1 + | +6 | / async fn task() +7 | | where +8 | | (): Sized, + | |______________^ From 1a24b4f018cd6e807a02a5b55343d33a9213c8ab Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 21 Oct 2024 01:26:02 +0200 Subject: [PATCH 093/144] Release embassy-executor v0.6.1, embassy-executor-macros v0.6.1 --- embassy-executor-macros/Cargo.toml | 2 +- embassy-executor/CHANGELOG.md | 11 ++++++++++- embassy-executor/Cargo.toml | 4 ++-- embassy-rp/Cargo.toml | 2 +- embassy-stm32/Cargo.toml | 2 +- embassy-time/Cargo.toml | 2 +- examples/boot/application/nrf/Cargo.toml | 2 +- examples/boot/application/rp/Cargo.toml | 2 +- examples/boot/application/stm32f3/Cargo.toml | 2 +- examples/boot/application/stm32f7/Cargo.toml | 2 +- examples/boot/application/stm32h7/Cargo.toml | 2 +- examples/boot/application/stm32l0/Cargo.toml | 2 +- examples/boot/application/stm32l1/Cargo.toml | 2 +- examples/boot/application/stm32l4/Cargo.toml | 2 +- examples/boot/application/stm32wb-dfu/Cargo.toml | 2 +- examples/boot/application/stm32wl/Cargo.toml | 2 +- examples/lpc55s69/Cargo.toml | 2 +- examples/nrf-rtos-trace/Cargo.toml | 2 +- examples/nrf51/Cargo.toml | 2 +- examples/nrf52810/Cargo.toml | 2 +- examples/nrf52840/Cargo.toml | 2 +- examples/nrf5340/Cargo.toml | 2 +- examples/nrf9151/ns/Cargo.toml | 2 +- examples/nrf9151/s/Cargo.toml | 2 +- examples/nrf9160/Cargo.toml | 2 +- examples/rp/Cargo.toml | 2 +- examples/rp23/Cargo.toml | 2 +- examples/std/Cargo.toml | 2 +- examples/stm32c0/Cargo.toml | 2 +- examples/stm32f0/Cargo.toml | 2 +- examples/stm32f1/Cargo.toml | 2 +- examples/stm32f2/Cargo.toml | 2 +- examples/stm32f3/Cargo.toml | 2 +- examples/stm32f334/Cargo.toml | 2 +- examples/stm32f4/Cargo.toml | 2 +- examples/stm32f469/Cargo.toml | 2 +- examples/stm32f7/Cargo.toml | 2 +- examples/stm32g0/Cargo.toml | 2 +- examples/stm32g4/Cargo.toml | 2 +- examples/stm32h5/Cargo.toml | 2 +- examples/stm32h7/Cargo.toml | 2 +- examples/stm32h735/Cargo.toml | 2 +- examples/stm32h755cm4/Cargo.toml | 2 +- examples/stm32h755cm7/Cargo.toml | 2 +- examples/stm32h7rs/Cargo.toml | 2 +- examples/stm32l0/Cargo.toml | 2 +- examples/stm32l1/Cargo.toml | 2 +- examples/stm32l4/Cargo.toml | 2 +- examples/stm32l5/Cargo.toml | 2 +- examples/stm32u0/Cargo.toml | 2 +- examples/stm32u5/Cargo.toml | 2 +- examples/stm32wb/Cargo.toml | 2 +- examples/stm32wba/Cargo.toml | 2 +- examples/stm32wl/Cargo.toml | 2 +- examples/wasm/Cargo.toml | 2 +- tests/nrf/Cargo.toml | 2 +- tests/riscv32/Cargo.toml | 2 +- tests/rp/Cargo.toml | 2 +- tests/stm32/Cargo.toml | 2 +- 59 files changed, 69 insertions(+), 60 deletions(-) diff --git a/embassy-executor-macros/Cargo.toml b/embassy-executor-macros/Cargo.toml index ef509c3f9..306cf8352 100644 --- a/embassy-executor-macros/Cargo.toml +++ b/embassy-executor-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-executor-macros" -version = "0.5.0" +version = "0.6.1" edition = "2021" license = "MIT OR Apache-2.0" description = "macros for creating the entry point and tasks for embassy-executor" diff --git a/embassy-executor/CHANGELOG.md b/embassy-executor/CHANGELOG.md index 5582b56ec..ac1c8cb0c 100644 --- a/embassy-executor/CHANGELOG.md +++ b/embassy-executor/CHANGELOG.md @@ -7,10 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## 0.6.1 - 2024-10-21 + +- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature, + and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types + for the `impl Trait`. Affected versions are 0.4.x, 0.5.x and 0.6.0, which have been yanked. +- Add an architecture-agnostic executor that spins waiting for tasks to run, enabled with the `arch-spin` feature. +- Update for breaking change in the nightly waker_getters API. The `nightly` feature now requires`nightly-2024-09-06` or newer. +- Improve macro error messages. + ## 0.6.0 - 2024-08-05 - Add collapse_debuginfo to fmt.rs macros. -- initial support for avr +- initial support for AVR - use nightly waker_getters APIs ## 0.5.0 - 2024-01-11 diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index e138f93b0..22a176621 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-executor" -version = "0.6.0" +version = "0.6.1" edition = "2021" license = "MIT OR Apache-2.0" description = "async/await executor designed for embedded usage" @@ -33,7 +33,7 @@ defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } rtos-trace = { version = "0.1.2", optional = true } -embassy-executor-macros = { version = "0.5.0", path = "../embassy-executor-macros" } +embassy-executor-macros = { version = "0.6.1", path = "../embassy-executor-macros" } embassy-time-driver = { version = "0.1.0", path = "../embassy-time-driver", optional = true } embassy-time-queue-driver = { version = "0.1.0", path = "../embassy-time-queue-driver", optional = true } critical-section = "1.1" diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index 54de238b3..547d64e43 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml @@ -147,5 +147,5 @@ rp-binary-info = { version = "0.1.0", optional = true } smart-leds = "0.4.0" [dev-dependencies] -embassy-executor = { version = "0.6.0", path = "../embassy-executor", features = ["arch-std", "executor-thread"] } +embassy-executor = { version = "0.6.1", path = "../embassy-executor", features = ["arch-std", "executor-thread"] } static_cell = { version = "2" } diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 53ec1b27f..9fdc799d8 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -51,7 +51,7 @@ embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal", def embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" } embassy-usb-synopsys-otg = {version = "0.1.0", path = "../embassy-usb-synopsys-otg" } -embassy-executor = { version = "0.6.0", path = "../embassy-executor", optional = true } +embassy-executor = { version = "0.6.1", path = "../embassy-executor", optional = true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "1.0" } diff --git a/embassy-time/Cargo.toml b/embassy-time/Cargo.toml index c3b4e4e3a..dc3561145 100644 --- a/embassy-time/Cargo.toml +++ b/embassy-time/Cargo.toml @@ -431,4 +431,4 @@ wasm-timer = { version = "0.2.5", optional = true } [dev-dependencies] serial_test = "0.9" critical-section = { version = "1.1", features = ["std"] } -embassy-executor = { version = "0.6.0", path = "../embassy-executor" } +embassy-executor = { version = "0.6.1", path = "../embassy-executor" } diff --git a/examples/boot/application/nrf/Cargo.toml b/examples/boot/application/nrf/Cargo.toml index 93e49faef..3815ac196 100644 --- a/examples/boot/application/nrf/Cargo.toml +++ b/examples/boot/application/nrf/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [] } embassy-nrf = { version = "0.2.0", path = "../../../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", ] } embassy-boot = { version = "0.3.0", path = "../../../../embassy-boot", features = [] } diff --git a/examples/boot/application/rp/Cargo.toml b/examples/boot/application/rp/Cargo.toml index 8bb8afdfe..86ea9fc2a 100644 --- a/examples/boot/application/rp/Cargo.toml +++ b/examples/boot/application/rp/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [] } embassy-rp = { version = "0.2.0", path = "../../../../embassy-rp", features = ["time-driver", "rp2040"] } embassy-boot-rp = { version = "0.3.0", path = "../../../../embassy-boot-rp", features = [] } diff --git a/examples/boot/application/stm32f3/Cargo.toml b/examples/boot/application/stm32f3/Cargo.toml index 1c2934298..29049e58c 100644 --- a/examples/boot/application/stm32f3/Cargo.toml +++ b/examples/boot/application/stm32f3/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32f303re", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32" } diff --git a/examples/boot/application/stm32f7/Cargo.toml b/examples/boot/application/stm32f7/Cargo.toml index 09e34c7df..ed102b073 100644 --- a/examples/boot/application/stm32f7/Cargo.toml +++ b/examples/boot/application/stm32f7/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32f767zi", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } diff --git a/examples/boot/application/stm32h7/Cargo.toml b/examples/boot/application/stm32h7/Cargo.toml index 5e7f4d5e7..cc705b1d6 100644 --- a/examples/boot/application/stm32h7/Cargo.toml +++ b/examples/boot/application/stm32h7/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32h743zi", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } diff --git a/examples/boot/application/stm32l0/Cargo.toml b/examples/boot/application/stm32l0/Cargo.toml index 60fdcfafb..17fc1d7d0 100644 --- a/examples/boot/application/stm32l0/Cargo.toml +++ b/examples/boot/application/stm32l0/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l072cz", "time-driver-any", "exti", "memory-x"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } diff --git a/examples/boot/application/stm32l1/Cargo.toml b/examples/boot/application/stm32l1/Cargo.toml index fe3ab2c04..fd2c19f99 100644 --- a/examples/boot/application/stm32l1/Cargo.toml +++ b/examples/boot/application/stm32l1/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l151cb-a", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } diff --git a/examples/boot/application/stm32l4/Cargo.toml b/examples/boot/application/stm32l4/Cargo.toml index 169856358..3852261ac 100644 --- a/examples/boot/application/stm32l4/Cargo.toml +++ b/examples/boot/application/stm32l4/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l475vg", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } diff --git a/examples/boot/application/stm32wb-dfu/Cargo.toml b/examples/boot/application/stm32wb-dfu/Cargo.toml index 7cef8fe0d..2d89f6d42 100644 --- a/examples/boot/application/stm32wb-dfu/Cargo.toml +++ b/examples/boot/application/stm32wb-dfu/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32wb55rg", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } diff --git a/examples/boot/application/stm32wl/Cargo.toml b/examples/boot/application/stm32wl/Cargo.toml index 860a835a9..e07d97d86 100644 --- a/examples/boot/application/stm32wl/Cargo.toml +++ b/examples/boot/application/stm32wl/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32wl55jc-cm4", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } diff --git a/examples/lpc55s69/Cargo.toml b/examples/lpc55s69/Cargo.toml index 14ec2d47e..a69007a2c 100644 --- a/examples/lpc55s69/Cargo.toml +++ b/examples/lpc55s69/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-nxp = { version = "0.1.0", path = "../../embassy-nxp", features = ["rt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } embassy-time = { version = "0.3.0", path = "../../embassy-time", features = ["defmt"] } panic-halt = "0.2.0" diff --git a/examples/nrf-rtos-trace/Cargo.toml b/examples/nrf-rtos-trace/Cargo.toml index 98a678815..2358ced56 100644 --- a/examples/nrf-rtos-trace/Cargo.toml +++ b/examples/nrf-rtos-trace/Cargo.toml @@ -16,7 +16,7 @@ log = [ [dependencies] embassy-sync = { version = "0.6.0", path = "../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "rtos-trace", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "rtos-trace", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time" } embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } diff --git a/examples/nrf51/Cargo.toml b/examples/nrf51/Cargo.toml index 93a19bea7..f94026158 100644 --- a/examples/nrf51/Cargo.toml +++ b/examples/nrf51/Cargo.toml @@ -5,7 +5,7 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-4096", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-4096", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf51", "gpiote", "time-driver-rtc1", "unstable-pac", "time", "rt"] } diff --git a/examples/nrf52810/Cargo.toml b/examples/nrf52810/Cargo.toml index 0e3e81c3f..867ead6d8 100644 --- a/examples/nrf52810/Cargo.toml +++ b/examples/nrf52810/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf52810", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index 17fa6234d..cb05b7204 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } diff --git a/examples/nrf5340/Cargo.toml b/examples/nrf5340/Cargo.toml index 0da85be07..2387eaa58 100644 --- a/examples/nrf5340/Cargo.toml +++ b/examples/nrf5340/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf5340-app-s", "time-driver-rtc1", "gpiote", "unstable-pac"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } diff --git a/examples/nrf9151/ns/Cargo.toml b/examples/nrf9151/ns/Cargo.toml index 17fe27b67..78260dae3 100644 --- a/examples/nrf9151/ns/Cargo.toml +++ b/examples/nrf9151/ns/Cargo.toml @@ -5,7 +5,7 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.6.0", path = "../../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.2.0", path = "../../../embassy-nrf", features = ["defmt", "nrf9120-ns", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } diff --git a/examples/nrf9151/s/Cargo.toml b/examples/nrf9151/s/Cargo.toml index 7253fc4be..17ead4ab4 100644 --- a/examples/nrf9151/s/Cargo.toml +++ b/examples/nrf9151/s/Cargo.toml @@ -5,7 +5,7 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.6.0", path = "../../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.2.0", path = "../../../embassy-nrf", features = ["defmt", "nrf9120-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } diff --git a/examples/nrf9160/Cargo.toml b/examples/nrf9160/Cargo.toml index 9aeb99317..2572f3353 100644 --- a/examples/nrf9160/Cargo.toml +++ b/examples/nrf9160/Cargo.toml @@ -5,7 +5,7 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf9160-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } embassy-net-nrf91 = { version = "0.1.0", path = "../../embassy-net-nrf91", features = ["defmt"] } diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index a220b9a77..8e5f81b7e 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal", features = ["defmt"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.2.0", path = "../../embassy-rp", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp2040"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } diff --git a/examples/rp23/Cargo.toml b/examples/rp23/Cargo.toml index 08646463c..9ae75e16c 100644 --- a/examples/rp23/Cargo.toml +++ b/examples/rp23/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal", features = ["defmt"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.2.0", path = "../../embassy-rp", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp235xa", "binary-info"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 87491b1d2..27d0062fb 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["log"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-std", "executor-thread", "log", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-std", "executor-thread", "log", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["log", "std", ] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features=[ "std", "log", "medium-ethernet", "medium-ip", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6"] } embassy-net-tuntap = { version = "0.1.0", path = "../../embassy-net-tuntap" } diff --git a/examples/stm32c0/Cargo.toml b/examples/stm32c0/Cargo.toml index 9102467eb..46a25cc4d 100644 --- a/examples/stm32c0/Cargo.toml +++ b/examples/stm32c0/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32c031c6 to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32c031c6", "memory-x", "unstable-pac", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } defmt = "0.3" diff --git a/examples/stm32f0/Cargo.toml b/examples/stm32f0/Cargo.toml index 724efdaff..93d9ab7ce 100644 --- a/examples/stm32f0/Cargo.toml +++ b/examples/stm32f0/Cargo.toml @@ -13,7 +13,7 @@ defmt = "0.3" defmt-rtt = "0.4" panic-probe = { version = "0.3", features = ["print-defmt"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } static_cell = "2" portable-atomic = { version = "1.5", features = ["unsafe-assume-single-core"] } diff --git a/examples/stm32f1/Cargo.toml b/examples/stm32f1/Cargo.toml index 0084651a3..a75d0fe4c 100644 --- a/examples/stm32f1/Cargo.toml +++ b/examples/stm32f1/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32f103c8 to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f103c8", "unstable-pac", "memory-x", "time-driver-any" ] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/examples/stm32f2/Cargo.toml b/examples/stm32f2/Cargo.toml index 60eb0eb93..166729211 100644 --- a/examples/stm32f2/Cargo.toml +++ b/examples/stm32f2/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32f207zg to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f207zg", "unstable-pac", "memory-x", "time-driver-any", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } defmt = "0.3" diff --git a/examples/stm32f3/Cargo.toml b/examples/stm32f3/Cargo.toml index 7fda410d9..e60ea80c6 100644 --- a/examples/stm32f3/Cargo.toml +++ b/examples/stm32f3/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32f303ze to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f303ze", "unstable-pac", "memory-x", "time-driver-any", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/examples/stm32f334/Cargo.toml b/examples/stm32f334/Cargo.toml index 1cc0a97da..4ba993009 100644 --- a/examples/stm32f334/Cargo.toml +++ b/examples/stm32f334/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f334r8", "unstable-pac", "memory-x", "time-driver-any", "exti"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } diff --git a/examples/stm32f4/Cargo.toml b/examples/stm32f4/Cargo.toml index b85361596..975e47398 100644 --- a/examples/stm32f4/Cargo.toml +++ b/examples/stm32f4/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32f429zi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt" ] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", ] } diff --git a/examples/stm32f469/Cargo.toml b/examples/stm32f469/Cargo.toml index 6a5bd0b29..4e1a778cd 100644 --- a/examples/stm32f469/Cargo.toml +++ b/examples/stm32f469/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" [dependencies] # Specific examples only for stm32f469 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f469ni", "unstable-pac", "memory-x", "time-driver-any", "exti", "chrono"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } defmt = "0.3" diff --git a/examples/stm32f7/Cargo.toml b/examples/stm32f7/Cargo.toml index 8c591ebd2..879b05d91 100644 --- a/examples/stm32f7/Cargo.toml +++ b/examples/stm32f7/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32f777zi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f777zi", "memory-x", "unstable-pac", "time-driver-any", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } embedded-io-async = { version = "0.6.1" } diff --git a/examples/stm32g0/Cargo.toml b/examples/stm32g0/Cargo.toml index a50074ce0..77f70e57d 100644 --- a/examples/stm32g0/Cargo.toml +++ b/examples/stm32g0/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32g0b1re to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32g0b1re", "memory-x", "unstable-pac", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", default-features = false, features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/examples/stm32g4/Cargo.toml b/examples/stm32g4/Cargo.toml index 2768147a1..86d29f94e 100644 --- a/examples/stm32g4/Cargo.toml +++ b/examples/stm32g4/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32g491re to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32g491re", "memory-x", "unstable-pac", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/examples/stm32h5/Cargo.toml b/examples/stm32h5/Cargo.toml index 1aa264ab2..9ca8bc969 100644 --- a/examples/stm32h5/Cargo.toml +++ b/examples/stm32h5/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32h563zi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h563zi", "memory-x", "time-driver-any", "exti", "unstable-pac", "low-power"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } diff --git a/examples/stm32h7/Cargo.toml b/examples/stm32h7/Cargo.toml index d0f22cf82..f2f207395 100644 --- a/examples/stm32h7/Cargo.toml +++ b/examples/stm32h7/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h743bi", "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.2.0", path = "../../embassy-embedded-hal" } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } diff --git a/examples/stm32h735/Cargo.toml b/examples/stm32h735/Cargo.toml index 93e9575b6..bac76ddfb 100644 --- a/examples/stm32h735/Cargo.toml +++ b/examples/stm32h735/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" 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.2.0", path = "../../embassy-embedded-hal" } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/examples/stm32h755cm4/Cargo.toml b/examples/stm32h755cm4/Cargo.toml index 75de40b9a..1fbceddde 100644 --- a/examples/stm32h755cm4/Cargo.toml +++ b/examples/stm32h755cm4/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h755zi-cm4", "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.2.0", path = "../../embassy-embedded-hal" } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", 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-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } diff --git a/examples/stm32h755cm7/Cargo.toml b/examples/stm32h755cm7/Cargo.toml index 911a4e79b..8b864169b 100644 --- a/examples/stm32h755cm7/Cargo.toml +++ b/examples/stm32h755cm7/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h755zi-cm7", "time-driver-tim3", "exti", "memory-x", "unstable-pac", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", 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-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } diff --git a/examples/stm32h7rs/Cargo.toml b/examples/stm32h7rs/Cargo.toml index 05f638408..29881bf8b 100644 --- a/examples/stm32h7rs/Cargo.toml +++ b/examples/stm32h7rs/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32h743bi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h7s3l8", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } diff --git a/examples/stm32l0/Cargo.toml b/examples/stm32l0/Cargo.toml index 2577f19e0..273cdce1d 100644 --- a/examples/stm32l0/Cargo.toml +++ b/examples/stm32l0/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32l072cz to your chip name, if necessary. 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.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } defmt = "0.3" diff --git a/examples/stm32l1/Cargo.toml b/examples/stm32l1/Cargo.toml index 062044f32..af4775a74 100644 --- a/examples/stm32l1/Cargo.toml +++ b/examples/stm32l1/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32l151cb-a", "time-driver-any", "memory-x"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } diff --git a/examples/stm32l4/Cargo.toml b/examples/stm32l4/Cargo.toml index c5478b17b..70c5c5e26 100644 --- a/examples/stm32l4/Cargo.toml +++ b/examples/stm32l4/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32l4s5vi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l4s5qi", "memory-x", "time-driver-any", "exti", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768", ] } embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } diff --git a/examples/stm32l5/Cargo.toml b/examples/stm32l5/Cargo.toml index 16c184de2..78cb79eac 100644 --- a/examples/stm32l5/Cargo.toml +++ b/examples/stm32l5/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32l552ze to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l552ze", "time-driver-any", "exti", "memory-x", "low-power"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } diff --git a/examples/stm32u0/Cargo.toml b/examples/stm32u0/Cargo.toml index 2e890cdb5..931409b51 100644 --- a/examples/stm32u0/Cargo.toml +++ b/examples/stm32u0/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32u083rc to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32u083rc", "memory-x", "unstable-pac", "exti", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", default-features = false, features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/examples/stm32u5/Cargo.toml b/examples/stm32u5/Cargo.toml index 20d64c6f7..ad7db4c32 100644 --- a/examples/stm32u5/Cargo.toml +++ b/examples/stm32u5/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32u585ai to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "unstable-pac", "stm32u585ai", "time-driver-any", "memory-x" ] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/examples/stm32wb/Cargo.toml b/examples/stm32wb/Cargo.toml index 1e1a0efe2..8cea2ace8 100644 --- a/examples/stm32wb/Cargo.toml +++ b/examples/stm32wb/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32wb55rg", "time-driver-any", "memory-x", "exti"] } embassy-stm32-wpan = { version = "0.1.0", path = "../../embassy-stm32-wpan", features = ["defmt", "stm32wb55rg"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "udp", "proto-ipv6", "medium-ieee802154", ], optional=true } diff --git a/examples/stm32wba/Cargo.toml b/examples/stm32wba/Cargo.toml index 401281c0b..4261564dd 100644 --- a/examples/stm32wba/Cargo.toml +++ b/examples/stm32wba/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32wba52cg", "time-driver-any", "memory-x", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "udp", "proto-ipv6", "medium-ieee802154", ], optional=true } diff --git a/examples/stm32wl/Cargo.toml b/examples/stm32wl/Cargo.toml index 46af5218c..f8d5afb86 100644 --- a/examples/stm32wl/Cargo.toml +++ b/examples/stm32wl/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # Change stm32wl55jc-cm4 to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "unstable-pac", "exti", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-4096", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-4096", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } diff --git a/examples/wasm/Cargo.toml b/examples/wasm/Cargo.toml index 75de079b7..f8db71233 100644 --- a/examples/wasm/Cargo.toml +++ b/examples/wasm/Cargo.toml @@ -9,7 +9,7 @@ crate-type = ["cdylib"] [dependencies] embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["log"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-wasm", "executor-thread", "log", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-wasm", "executor-thread", "log", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["log", "wasm", ] } wasm-logger = "0.2.0" diff --git a/tests/nrf/Cargo.toml b/tests/nrf/Cargo.toml index f666634d5..d36ab9c67 100644 --- a/tests/nrf/Cargo.toml +++ b/tests/nrf/Cargo.toml @@ -9,7 +9,7 @@ teleprobe-meta = "1" embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt", ] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "task-arena-size-16384", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "task-arena-size-16384", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "time-driver-rtc1", "gpiote", "unstable-pac"] } embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } diff --git a/tests/riscv32/Cargo.toml b/tests/riscv32/Cargo.toml index ae2b0e180..0ee7f5a00 100644 --- a/tests/riscv32/Cargo.toml +++ b/tests/riscv32/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" [dependencies] critical-section = { version = "1.1.1", features = ["restore-state-bool"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-riscv32", "executor-thread"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-riscv32", "executor-thread"] } embassy-time = { version = "0.3.2", path = "../../embassy-time" } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/tests/rp/Cargo.toml b/tests/rp/Cargo.toml index 12f1ec3ce..ce812af78 100644 --- a/tests/rp/Cargo.toml +++ b/tests/rp/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" teleprobe-meta = "1.1" embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", ] } embassy-rp = { version = "0.2.0", path = "../../embassy-rp", features = [ "defmt", "unstable-pac", "time-driver", "critical-section-impl", "intrinsics", "rom-v2-intrinsics", "run-from-ram", "rp2040"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index 2eac636e5..468d2ed54 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -60,7 +60,7 @@ cm0 = ["portable-atomic/unsafe-assume-single-core"] teleprobe-meta = "1" embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "tick-hz-131_072", "defmt-timestamp-uptime"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "memory-x", "time-driver-any"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } From ecac24a1c7283ab3e3a10df801270eea5491ef3a Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 21 Oct 2024 01:10:55 +0200 Subject: [PATCH 094/144] stm32: Fix build for chips with octospim but not octospi2. --- ci.sh | 1 + embassy-stm32/build.rs | 14 +++++++++----- embassy-stm32/src/ospi/mod.rs | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/ci.sh b/ci.sh index aa078be31..b04a4b288 100755 --- a/ci.sh +++ b/ci.sh @@ -166,6 +166,7 @@ cargo batch \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32h562ag,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32wba50ke,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32wba55ug,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u5f9zj,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u5g9nj,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb35ce,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u031r8,defmt,exti,time-driver-any,time \ diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 966dce121..71bfb3747 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -55,7 +55,7 @@ fn main() { let mut singletons: Vec = Vec::new(); for p in METADATA.peripherals { if let Some(r) = &p.registers { - if r.kind == "adccommon" || r.kind == "sai" || r.kind == "ucpd" || r.kind == "otg" { + if r.kind == "adccommon" || r.kind == "sai" || r.kind == "ucpd" || r.kind == "otg" || r.kind == "octospi" { // TODO: should we emit this for all peripherals? if so, we will need a list of all // possible peripherals across all chips, so that we can declare the configs // (replacing the hard-coded list of `peri_*` cfgs below) @@ -113,6 +113,7 @@ fn main() { "peri_ucpd2", "peri_usb_otg_fs", "peri_usb_otg_hs", + "peri_octospi2", ]); cfgs.declare_all(&["mco", "mco1", "mco2"]); @@ -1137,11 +1138,14 @@ fn main() { // OCTOSPIM is special if p.name == "OCTOSPIM" { + // Some chips have OCTOSPIM but not OCTOSPI2. + if METADATA.peripherals.iter().any(|p| p.name == "OCTOSPI2") { + peri = format_ident!("{}", "OCTOSPI2"); + g.extend(quote! { + pin_trait_impl!(#tr, #peri, #pin_name, #af); + }); + } peri = format_ident!("{}", "OCTOSPI1"); - g.extend(quote! { - pin_trait_impl!(#tr, #peri, #pin_name, #af); - }); - peri = format_ident!("{}", "OCTOSPI2"); } g.extend(quote! { diff --git a/embassy-stm32/src/ospi/mod.rs b/embassy-stm32/src/ospi/mod.rs index 48a1ea5e6..e4adc4b09 100644 --- a/embassy-stm32/src/ospi/mod.rs +++ b/embassy-stm32/src/ospi/mod.rs @@ -1178,7 +1178,7 @@ impl SealedOctospimInstance for peripherals::OCTOSPI1 { const OCTOSPI_IDX: u8 = 1; } -#[cfg(octospim_v1)] +#[cfg(all(octospim_v1, peri_octospi2))] impl SealedOctospimInstance for peripherals::OCTOSPI2 { const OCTOSPIM_REGS: Octospim = crate::pac::OCTOSPIM; const OCTOSPI_IDX: u8 = 2; From b4ee17fb4f1a77c26fc08e9fe9e0d343d1c059b4 Mon Sep 17 00:00:00 2001 From: Fan Jiang Date: Sun, 20 Oct 2024 12:29:38 -0400 Subject: [PATCH 095/144] net: Add flush for UDP and Raw sockets. --- embassy-net/Cargo.toml | 2 +- embassy-net/src/raw.rs | 17 +++++++++++++++++ embassy-net/src/udp.rs | 17 +++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index a33c693fc..8a29aca79 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -68,7 +68,7 @@ multicast = ["smoltcp/multicast"] defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } -smoltcp = { git="https://github.com/smoltcp-rs/smoltcp", rev="b65e1b64dc9b66fa984a2ad34e90685cb0b606de", default-features = false, features = [ +smoltcp = { git="https://github.com/smoltcp-rs/smoltcp", rev="fe0b4d102253465850cd1cf39cd33d4721a4a8d5", default-features = false, features = [ "socket", "async", ] } diff --git a/embassy-net/src/raw.rs b/embassy-net/src/raw.rs index 1098dc208..ace325a46 100644 --- a/embassy-net/src/raw.rs +++ b/embassy-net/src/raw.rs @@ -108,6 +108,23 @@ impl<'a> RawSocket<'a> { } }) } + + /// Flush the socket. + /// + /// This method will wait until the socket is flushed. + pub async fn flush(&mut self) { + poll_fn(move |cx| { + self.with_mut(|s, _| { + if s.send_queue() == 0 { + Poll::Ready(()) + } else { + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + }) + .await + } } impl Drop for RawSocket<'_> { diff --git a/embassy-net/src/udp.rs b/embassy-net/src/udp.rs index 3eb6e2f83..ac650364e 100644 --- a/embassy-net/src/udp.rs +++ b/embassy-net/src/udp.rs @@ -241,6 +241,23 @@ impl<'a> UdpSocket<'a> { .await } + /// Flush the socket. + /// + /// This method will wait until the socket is flushed. + pub async fn flush(&mut self) { + poll_fn(move |cx| { + self.with_mut(|s, _| { + if s.send_queue() == 0 { + Poll::Ready(()) + } else { + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + }) + .await + } + /// Returns the local endpoint of the socket. pub fn endpoint(&self) -> IpListenEndpoint { self.with(|s, _| s.endpoint()) From 1d395fc2b6e5d3d870afb90593de9f03d47b0efa Mon Sep 17 00:00:00 2001 From: Lucas Martins Date: Tue, 3 Sep 2024 14:38:22 -0300 Subject: [PATCH 096/144] stm32/flash: add stm32f2, stm32h5 flash driver --- embassy-stm32/src/flash/h5.rs | 177 +++++++++++++++++++++++++++++++++ embassy-stm32/src/flash/mod.rs | 5 +- 2 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 embassy-stm32/src/flash/h5.rs diff --git a/embassy-stm32/src/flash/h5.rs b/embassy-stm32/src/flash/h5.rs new file mode 100644 index 000000000..9e131ca2b --- /dev/null +++ b/embassy-stm32/src/flash/h5.rs @@ -0,0 +1,177 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{fence, Ordering}; + +use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; + +pub(crate) const fn is_default_layout() -> bool { + true +} + +// const fn is_dual_bank() -> bool { +// FLASH_REGIONS.len() >= 2 +// } + +pub(crate) fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + if !pac::FLASH.nscr().read().lock() { + pac::FLASH.nscr().modify(|r| { + r.set_lock(true); + }); + } +} + +pub(crate) unsafe fn unlock() { + // TODO: check locked first + while pac::FLASH.nssr().read().bsy() { + #[cfg(feature = "defmt")] + defmt::trace!("busy") + } + + // only unlock if locked to begin with + if pac::FLASH.nscr().read().lock() { + pac::FLASH.nskeyr().write_value(0x4567_0123); + pac::FLASH.nskeyr().write_value(0xCDEF_89AB); + } +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); +} + +pub(crate) unsafe fn disable_blocking_write() {} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + // // We cannot have the write setup sequence in begin_write as it depends on the address + // let bank = if start_address < BANK1_REGION.end() { + // pac::FLASH.bank(0) + // } else { + // pac::FLASH.bank(1) + // }; + + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + + clear_all_err(); + + pac::FLASH.nscr().write(|w| { + w.set_pg(true); + // w.set_psize(2); // 32 bits at once + }); + + let mut res = None; + let mut address = start_address; + // TODO: see write size + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into()))); + address += val.len() as u32; + + res = Some(blocking_wait_ready().map_err(|e| { + error!("write err"); + e + })); + pac::FLASH.nssr().modify(|w| { + if w.eop() { + w.set_eop(true); + } + }); + if unwrap!(res).is_err() { + break; + } + } + + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + + pac::FLASH.nscr().write(|w| w.set_pg(false)); + + unwrap!(res) +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + // pac::FLASH.wrp2r_cur().read().wrpsg() + // TODO: write protection check + if pac::FLASH.nscr().read().lock() == true { + error!("flash locked"); + } + + loop { + let sr = pac::FLASH.nssr().read(); + if !sr.bsy() && !sr.dbne() { + break; + } + } + clear_all_err(); + + pac::FLASH.nscr().modify(|r| { + // TODO: later check bank swap + r.set_bksel(match sector.bank { + crate::flash::FlashBank::Bank1 => stm32_metapac::flash::vals::NscrBksel::B_0X0, + crate::flash::FlashBank::Bank2 => stm32_metapac::flash::vals::NscrBksel::B_0X1, + }); + r.set_snb(sector.index_in_bank); + r.set_ser(true); + }); + + pac::FLASH.nscr().modify(|r| { + r.set_strt(true); + }); + + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + + let ret: Result<(), Error> = blocking_wait_ready().map_err(|e| { + error!("erase err"); + e + }); + + pac::FLASH.nscr().modify(|w| w.set_ser(false)); + clear_all_err(); + ret +} + +pub(crate) unsafe fn clear_all_err() { + pac::FLASH.nssr().modify(|_| {}) +} + +unsafe fn blocking_wait_ready() -> Result<(), Error> { + loop { + let sr = pac::FLASH.nssr().read(); + + if !sr.bsy() { + if sr.optchangeerr() { + error!("optchangeerr"); + return Err(Error::Prog); + } + if sr.obkwerr() { + error!("obkwerr"); + return Err(Error::Seq); + } + if sr.obkerr() { + error!("obkerr"); + return Err(Error::Seq); + } + if sr.incerr() { + error!("incerr"); + return Err(Error::Unaligned); + } + if sr.strberr() { + error!("strberr"); + return Err(Error::Parallelism); + } + if sr.wrperr() { + error!("protected"); + return Err(Error::Protected); + } + + return Ok(()); + } + } +} diff --git a/embassy-stm32/src/flash/mod.rs b/embassy-stm32/src/flash/mod.rs index d64a1c28a..88fe6a291 100644 --- a/embassy-stm32/src/flash/mod.rs +++ b/embassy-stm32/src/flash/mod.rs @@ -101,13 +101,14 @@ pub enum FlashBank { #[cfg_attr(flash_h7, path = "h7.rs")] #[cfg_attr(flash_h7ab, path = "h7.rs")] #[cfg_attr(flash_u5, path = "u5.rs")] +#[cfg_attr(flash_h5, path = "h5.rs")] #[cfg_attr(flash_h50, path = "h50.rs")] #[cfg_attr(flash_u0, path = "u0.rs")] #[cfg_attr( not(any( flash_l0, flash_l1, flash_l4, flash_l5, flash_wl, flash_wb, flash_f0, flash_f1, flash_f2, flash_f3, flash_f4, - flash_f7, flash_g0, flash_g0, flash_g4c2, flash_g4c3, flash_g4c4, flash_h7, flash_h7ab, flash_u5, flash_h50, - flash_u0 + flash_f7, flash_g0, flash_g4c2, flash_g4c3, flash_g4c4, flash_h7, flash_h7ab, flash_u5, flash_h50, flash_u0, + flash_h5, )), path = "other.rs" )] From 693bd8c6ded003f78ae89fa2700ba7282fee64d0 Mon Sep 17 00:00:00 2001 From: rafael Date: Mon, 21 Oct 2024 11:54:17 +0200 Subject: [PATCH 097/144] re-export SetDutyCycle for user convenience --- embassy-rp/src/pwm.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs index 79e626802..de2c2f81e 100644 --- a/embassy-rp/src/pwm.rs +++ b/embassy-rp/src/pwm.rs @@ -1,7 +1,7 @@ //! Pulse Width Modulation (PWM) use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; -use embedded_hal_1::pwm::{Error, ErrorKind, ErrorType, SetDutyCycle}; +use embedded_hal_1::pwm::{Error, ErrorKind, ErrorType}; use fixed::traits::ToFixed; use fixed::FixedU16; use pac::pwm::regs::{ChDiv, Intr}; @@ -10,6 +10,8 @@ use pac::pwm::vals::Divmode; use crate::gpio::{AnyPin, Pin as GpioPin, Pull, SealedPin as _}; use crate::{pac, peripherals, RegExt}; +pub use embedded_hal_1::pwm::SetDutyCycle; + /// The configuration of a PWM slice. /// Note the period in clock cycles of a slice can be computed as: /// `(top + 1) * (phase_correct ? 1 : 2) * divider` @@ -113,9 +115,7 @@ impl<'d> SetDutyCycle for Pwm<'d> { } fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { - info!("duty {}",&duty); let max_duty = self.max_duty_cycle(); - info!("max duty {}", &max_duty); if duty > max_duty { return Err(PwmError::InvalidDutyCycle); } From f32b0fbc3b614ab79b7756b49e44a380e5e60192 Mon Sep 17 00:00:00 2001 From: rafael Date: Mon, 21 Oct 2024 11:55:10 +0200 Subject: [PATCH 098/144] change existing pwm example to reflect both existing ways to use pwm output --- examples/rp23/src/bin/pwm.rs | 55 +++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/examples/rp23/src/bin/pwm.rs b/examples/rp23/src/bin/pwm.rs index 15eae09ee..7314ee637 100644 --- a/examples/rp23/src/bin/pwm.rs +++ b/examples/rp23/src/bin/pwm.rs @@ -1,6 +1,8 @@ -//! This example shows how to use PWM (Pulse Width Modulation) in the RP2040 chip. +//! This example shows how to use PWM (Pulse Width Modulation) in the RP235x chip. //! -//! The LED on the RP Pico W board is connected differently. Add a LED and resistor to another pin. +//! We demonstrate two ways of using PWM: +//! 1. Via config +//! 2. Via setting a duty cycle #![no_std] #![no_main] @@ -8,8 +10,10 @@ use defmt::*; use embassy_executor::Spawner; use embassy_rp::block::ImageDef; -use embassy_rp::pwm::{Config, Pwm}; +use embassy_rp::peripherals::{PIN_25, PIN_4, PWM_SLICE2, PWM_SLICE4}; +use embassy_rp::pwm::{Config, Pwm, SetDutyCycle}; use embassy_time::Timer; +// use embedded_hal_1::pwm::SetDutyCycle; use {defmt_rtt as _, panic_probe as _}; #[link_section = ".start_block"] @@ -17,13 +21,22 @@ use {defmt_rtt as _, panic_probe as _}; pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); #[embassy_executor::main] -async fn main(_spawner: Spawner) { +async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); + spawner.spawn(pwm_set_config(p.PWM_SLICE4, p.PIN_25)).unwrap(); + spawner.spawn(pwm_set_dutycycle(p.PWM_SLICE2, p.PIN_4)).unwrap(); +} - let mut c: Config = Default::default(); - c.top = 0x8000; +/// Demonstrate PWM by modifying & applying the config +/// +/// Using the onboard led, if You are using a different Board than plain Pico2 (i.e. W variant) +/// you must use another slice & pin and an appropriate resistor. +#[embassy_executor::task] +async fn pwm_set_config(slice4: PWM_SLICE4, pin25: PIN_25) { + let mut c = Config::default(); + c.top = 32_768; c.compare_b = 8; - let mut pwm = Pwm::new_output_b(p.PWM_SLICE4, p.PIN_25, c.clone()); + let mut pwm = Pwm::new_output_b(slice4, pin25, c.clone()); loop { info!("current LED duty cycle: {}/32768", c.compare_b); @@ -32,3 +45,31 @@ async fn main(_spawner: Spawner) { pwm.set_config(&c); } } + +/// Demonstrate PWM by setting duty cycle +/// +/// Using GP4 in Slice2, make sure to use an appropriate resistor. +#[embassy_executor::task] +async fn pwm_set_dutycycle(slice2: PWM_SLICE2, pin4: PIN_4) { + let mut c = Config::default(); + c.top = 32_768; + let mut pwm = Pwm::new_output_a(slice2, pin4, c.clone()); + + loop { + // 100% duty cycle, fully on + pwm.set_duty_cycle_fully_on().unwrap(); + Timer::after_secs(1).await; + + // 50% duty cycle, half on. Expressed as simple percentage. + pwm.set_duty_cycle_percent(50).unwrap(); + Timer::after_secs(1).await; + + // 25% duty cycle, quarter on. Expressed as (duty / max_duty) + pwm.set_duty_cycle(8_192 / c.top).unwrap(); + Timer::after_secs(1).await; + + // 0% duty cycle, fully off. + pwm.set_duty_cycle_fully_off().unwrap(); + Timer::after_secs(1).await; + } +} From d7db8fbab9a2bc363520505757386500d5710735 Mon Sep 17 00:00:00 2001 From: rafael Date: Mon, 21 Oct 2024 11:59:03 +0200 Subject: [PATCH 099/144] rustfmt --- embassy-rp/src/pwm.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs index de2c2f81e..cfb99c569 100644 --- a/embassy-rp/src/pwm.rs +++ b/embassy-rp/src/pwm.rs @@ -1,6 +1,7 @@ //! Pulse Width Modulation (PWM) use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +pub use embedded_hal_1::pwm::SetDutyCycle; use embedded_hal_1::pwm::{Error, ErrorKind, ErrorType}; use fixed::traits::ToFixed; use fixed::FixedU16; @@ -10,8 +11,6 @@ use pac::pwm::vals::Divmode; use crate::gpio::{AnyPin, Pin as GpioPin, Pull, SealedPin as _}; use crate::{pac, peripherals, RegExt}; -pub use embedded_hal_1::pwm::SetDutyCycle; - /// The configuration of a PWM slice. /// Note the period in clock cycles of a slice can be computed as: /// `(top + 1) * (phase_correct ? 1 : 2) * divider` From d92fb002ecc3ff4dcac51d8e74927d977b2343b0 Mon Sep 17 00:00:00 2001 From: rafael Date: Mon, 21 Oct 2024 12:19:10 +0200 Subject: [PATCH 100/144] rustfmt --- examples/rp23/src/bin/pwm.rs | 1 - examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/examples/rp23/src/bin/pwm.rs b/examples/rp23/src/bin/pwm.rs index 7314ee637..1dd5ca3de 100644 --- a/examples/rp23/src/bin/pwm.rs +++ b/examples/rp23/src/bin/pwm.rs @@ -13,7 +13,6 @@ use embassy_rp::block::ImageDef; use embassy_rp::peripherals::{PIN_25, PIN_4, PWM_SLICE2, PWM_SLICE4}; use embassy_rp::pwm::{Config, Pwm, SetDutyCycle}; use embassy_time::Timer; -// use embedded_hal_1::pwm::SetDutyCycle; use {defmt_rtt as _, panic_probe as _}; #[link_section = ".start_block"] diff --git a/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs b/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs index 36b8d9620..6c3a8998c 100644 --- a/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs +++ b/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs @@ -10,12 +10,9 @@ use defmt::*; use embassy_executor::Spawner; use embassy_rp::block::ImageDef; use embassy_rp::config::Config; -use embassy_rp::gpio; use embassy_rp::gpio::Output; -use embassy_rp::peripherals; -use embassy_rp::pwm; -use embassy_time::Duration; -use embassy_time::Timer; +use embassy_rp::{gpio, peripherals, pwm}; +use embassy_time::{Duration, Timer}; use tb6612fng::{DriveCommand, Motor, Tb6612fng}; use {defmt_rtt as _, panic_probe as _}; From e782eabff743175b34489401574d1988c370f06f Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 21 Oct 2024 16:20:40 +0200 Subject: [PATCH 101/144] Changelog executor v0.5.1 release. --- embassy-executor/CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/embassy-executor/CHANGELOG.md b/embassy-executor/CHANGELOG.md index ac1c8cb0c..3d8c48676 100644 --- a/embassy-executor/CHANGELOG.md +++ b/embassy-executor/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature, and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types - for the `impl Trait`. Affected versions are 0.4.x, 0.5.x and 0.6.0, which have been yanked. + for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked. - Add an architecture-agnostic executor that spins waiting for tasks to run, enabled with the `arch-spin` feature. - Update for breaking change in the nightly waker_getters API. The `nightly` feature now requires`nightly-2024-09-06` or newer. - Improve macro error messages. @@ -22,6 +22,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - initial support for AVR - use nightly waker_getters APIs +## 0.5.1 - 2024-10-21 + +- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature, + and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types + for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked. + ## 0.5.0 - 2024-01-11 - Updated to `embassy-time-driver 0.1`, `embassy-time-queue-driver 0.1`, compatible with `embassy-time v0.3` and higher. From 61f55be92ae11805ae233d9c2526b41b605b3f8e Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Mon, 21 Oct 2024 19:41:28 +0200 Subject: [PATCH 102/144] Use released version of reqwless for examples --- examples/rp/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index 8e5f81b7e..6a2c99716 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -25,7 +25,7 @@ fixed = "1.23.1" fixed-macro = "1.2" # for web request example -reqwless = { git="https://github.com/drogue-iot/reqwless", rev="673e8d2cfbaad79254ec51fa50cc8b697531fbff", features = ["defmt",]} +reqwless = { version = "0.13.0", features = ["defmt"] } serde = { version = "1.0.203", default-features = false, features = ["derive"] } serde-json-core = "0.5.1" From c0addc005bb3859436cb1c2931b41a7065703077 Mon Sep 17 00:00:00 2001 From: Lucas Martins Date: Mon, 21 Oct 2024 14:48:57 -0300 Subject: [PATCH 103/144] add uart permutations usefull for rs485 --- embassy-stm32/src/usart/buffered.rs | 49 +++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/embassy-stm32/src/usart/buffered.rs b/embassy-stm32/src/usart/buffered.rs index 4fbe33b2e..03c0934f9 100644 --- a/embassy-stm32/src/usart/buffered.rs +++ b/embassy-stm32/src/usart/buffered.rs @@ -246,6 +246,55 @@ impl<'d> BufferedUart<'d> { ) } + + /// Create a new bidirectional buffered UART driver with only RTS pin as the DE pin + pub fn new_with_rts_as_de( + peri: impl Peripheral

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

> + 'd, + tx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, AfType::input(Pull::None)), + new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), + None, + None, + new_pin!(rts, AfType::input(Pull::None)), // RTS mapped used as DE + tx_buffer, + rx_buffer, + config, + ) + } + + /// Create a new bidirectional buffered UART driver with only the request-to-send pin + pub fn new_with_rts( + peri: impl Peripheral

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

> + 'd, + tx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, AfType::input(Pull::None)), + new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(rts, AfType::input(Pull::None)), + None, // no RTS + None, // no DE + tx_buffer, + rx_buffer, + config, + ) + } + /// Create a new bidirectional buffered UART driver with a driver-enable pin #[cfg(not(any(usart_v1, usart_v2)))] pub fn new_with_de( From 2c9b528edde491ca1ed02234805212c78e2a6bd7 Mon Sep 17 00:00:00 2001 From: Lucas Martins Date: Mon, 21 Oct 2024 15:06:06 -0300 Subject: [PATCH 104/144] fmt --- embassy-stm32/src/usart/buffered.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/embassy-stm32/src/usart/buffered.rs b/embassy-stm32/src/usart/buffered.rs index 03c0934f9..edaf41be9 100644 --- a/embassy-stm32/src/usart/buffered.rs +++ b/embassy-stm32/src/usart/buffered.rs @@ -246,7 +246,6 @@ impl<'d> BufferedUart<'d> { ) } - /// Create a new bidirectional buffered UART driver with only RTS pin as the DE pin pub fn new_with_rts_as_de( peri: impl Peripheral

+ 'd, From 8dfc9ba1a3e3f69aedf5bce748783fb6a8f5e92e Mon Sep 17 00:00:00 2001 From: rafael Date: Mon, 21 Oct 2024 21:14:49 +0200 Subject: [PATCH 105/144] also add to the rp pwm example --- examples/rp/src/bin/pwm.rs | 52 +++++++++++++++++++++++++++++++----- examples/rp23/src/bin/pwm.rs | 8 +++--- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/examples/rp/src/bin/pwm.rs b/examples/rp/src/bin/pwm.rs index 26e233260..862a7da22 100644 --- a/examples/rp/src/bin/pwm.rs +++ b/examples/rp/src/bin/pwm.rs @@ -1,24 +1,36 @@ //! This example shows how to use PWM (Pulse Width Modulation) in the RP2040 chip. //! -//! The LED on the RP Pico W board is connected differently. Add a LED and resistor to another pin. +//! We demonstrate two ways of using PWM: +//! 1. Via config +//! 2. Via setting a duty cycle #![no_std] #![no_main] use defmt::*; use embassy_executor::Spawner; -use embassy_rp::pwm::{Config, Pwm}; +use embassy_rp::peripherals::{PIN_25, PIN_4, PWM_SLICE2, PWM_SLICE4}; +use embassy_rp::pwm::{Config, Pwm, SetDutyCycle}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] -async fn main(_spawner: Spawner) { +async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); + spawner.spawn(pwm_set_config(p.PWM_SLICE4, p.PIN_25)).unwrap(); + spawner.spawn(pwm_set_dutycycle(p.PWM_SLICE2, p.PIN_4)).unwrap(); +} - let mut c: Config = Default::default(); - c.top = 0x8000; +/// Demonstrate PWM by modifying & applying the config +/// +/// Using the onboard led, if You are using a different Board than plain Pico2 (i.e. W variant) +/// you must use another slice & pin and an appropriate resistor. +#[embassy_executor::task] +async fn pwm_set_config(slice4: PWM_SLICE4, pin25: PIN_25) { + let mut c = Config::default(); + c.top = 32_768; c.compare_b = 8; - let mut pwm = Pwm::new_output_b(p.PWM_SLICE4, p.PIN_25, c.clone()); + let mut pwm = Pwm::new_output_b(slice4, pin25, c.clone()); loop { info!("current LED duty cycle: {}/32768", c.compare_b); @@ -27,3 +39,31 @@ async fn main(_spawner: Spawner) { pwm.set_config(&c); } } + +/// Demonstrate PWM by setting duty cycle +/// +/// Using GP4 in Slice2, make sure to use an appropriate resistor. +#[embassy_executor::task] +async fn pwm_set_dutycycle(slice2: PWM_SLICE2, pin4: PIN_4) { + let mut c = Config::default(); + c.top = 32_768; + let mut pwm = Pwm::new_output_a(slice2, pin4, c.clone()); + + loop { + // 100% duty cycle, fully on + pwm.set_duty_cycle_fully_on().unwrap(); + Timer::after_secs(1).await; + + // 66% duty cycle. Expressed as simple percentage. + pwm.set_duty_cycle_percent(66).unwrap(); + Timer::after_secs(1).await; + + // 25% duty cycle. Expressed as 32768/4 = 8192. + pwm.set_duty_cycle(8_192).unwrap(); + Timer::after_secs(1).await; + + // 0% duty cycle, fully off. + pwm.set_duty_cycle_fully_off().unwrap(); + Timer::after_secs(1).await; + } +} diff --git a/examples/rp23/src/bin/pwm.rs b/examples/rp23/src/bin/pwm.rs index 1dd5ca3de..838eee625 100644 --- a/examples/rp23/src/bin/pwm.rs +++ b/examples/rp23/src/bin/pwm.rs @@ -59,12 +59,12 @@ async fn pwm_set_dutycycle(slice2: PWM_SLICE2, pin4: PIN_4) { pwm.set_duty_cycle_fully_on().unwrap(); Timer::after_secs(1).await; - // 50% duty cycle, half on. Expressed as simple percentage. - pwm.set_duty_cycle_percent(50).unwrap(); + // 66% duty cycle. Expressed as simple percentage. + pwm.set_duty_cycle_percent(66).unwrap(); Timer::after_secs(1).await; - // 25% duty cycle, quarter on. Expressed as (duty / max_duty) - pwm.set_duty_cycle(8_192 / c.top).unwrap(); + // 25% duty cycle. Expressed as 32768/4 = 8192. + pwm.set_duty_cycle(8_192).unwrap(); Timer::after_secs(1).await; // 0% duty cycle, fully off. From 14e69309ebe25a76f67c38411c7a80a4d83da5ed Mon Sep 17 00:00:00 2001 From: rafael Date: Mon, 21 Oct 2024 22:42:18 +0200 Subject: [PATCH 106/144] add pwm frequency to examples --- examples/rp/src/bin/pwm.rs | 10 ++++++++-- examples/rp23/src/bin/pwm.rs | 10 ++++++++-- .../rp23/src/bin/pwm_tb6612fng_motor_driver.rs | 17 +++++++---------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/examples/rp/src/bin/pwm.rs b/examples/rp/src/bin/pwm.rs index 862a7da22..791b88b5b 100644 --- a/examples/rp/src/bin/pwm.rs +++ b/examples/rp/src/bin/pwm.rs @@ -45,8 +45,14 @@ async fn pwm_set_config(slice4: PWM_SLICE4, pin25: PIN_25) { /// Using GP4 in Slice2, make sure to use an appropriate resistor. #[embassy_executor::task] async fn pwm_set_dutycycle(slice2: PWM_SLICE2, pin4: PIN_4) { + // If we aim for a specific frequency, here is how we can calculate the top value. + // The top value sets the period of the PWM cycle, so a counter goes from 0 to top and then wraps around to 0. + // Every such wraparound is one PWM cycle. So here is how we get 25KHz: let mut c = Config::default(); - c.top = 32_768; + let pwm_freq = 25_000; // Hz, our desired frequency + let clock_freq = embassy_rp::clocks::clk_sys_freq(); + c.top = (clock_freq / pwm_freq) as u16 - 1; + let mut pwm = Pwm::new_output_a(slice2, pin4, c.clone()); loop { @@ -59,7 +65,7 @@ async fn pwm_set_dutycycle(slice2: PWM_SLICE2, pin4: PIN_4) { Timer::after_secs(1).await; // 25% duty cycle. Expressed as 32768/4 = 8192. - pwm.set_duty_cycle(8_192).unwrap(); + pwm.set_duty_cycle(c.top / 4).unwrap(); Timer::after_secs(1).await; // 0% duty cycle, fully off. diff --git a/examples/rp23/src/bin/pwm.rs b/examples/rp23/src/bin/pwm.rs index 838eee625..5a4457158 100644 --- a/examples/rp23/src/bin/pwm.rs +++ b/examples/rp23/src/bin/pwm.rs @@ -50,8 +50,14 @@ async fn pwm_set_config(slice4: PWM_SLICE4, pin25: PIN_25) { /// Using GP4 in Slice2, make sure to use an appropriate resistor. #[embassy_executor::task] async fn pwm_set_dutycycle(slice2: PWM_SLICE2, pin4: PIN_4) { + // If we aim for a specific frequency, here is how we can calculate the top value. + // The top value sets the period of the PWM cycle, so a counter goes from 0 to top and then wraps around to 0. + // Every such wraparound is one PWM cycle. So here is how we get 25KHz: let mut c = Config::default(); - c.top = 32_768; + let pwm_freq = 25_000; // Hz, our desired frequency + let clock_freq = embassy_rp::clocks::clk_sys_freq(); + c.top = (clock_freq / pwm_freq) as u16 - 1; + let mut pwm = Pwm::new_output_a(slice2, pin4, c.clone()); loop { @@ -64,7 +70,7 @@ async fn pwm_set_dutycycle(slice2: PWM_SLICE2, pin4: PIN_4) { Timer::after_secs(1).await; // 25% duty cycle. Expressed as 32768/4 = 8192. - pwm.set_duty_cycle(8_192).unwrap(); + pwm.set_duty_cycle(c.top / 4).unwrap(); Timer::after_secs(1).await; // 0% duty cycle, fully off. diff --git a/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs b/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs index 6c3a8998c..263b551de 100644 --- a/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs +++ b/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs @@ -16,12 +16,6 @@ use embassy_time::{Duration, Timer}; use tb6612fng::{DriveCommand, Motor, Tb6612fng}; use {defmt_rtt as _, panic_probe as _}; -/// Maximum PWM value (fully on) -const PWM_MAX: u16 = 50000; - -/// Minimum PWM value (fully off) -const PWM_MIN: u16 = 0; - #[link_section = ".start_block"] #[used] pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); @@ -46,6 +40,11 @@ async fn main(_spawner: Spawner) { let s = split_resources!(p); let r = s.motor; + // we want a PWM frequency of 25KHz + let pwm_freq = 25_000; // Hz, our desired frequency + let clock_freq = embassy_rp::clocks::clk_sys_freq(); + let period = (clock_freq / pwm_freq) as u16 - 1; + // we need a standby output and two motors to construct a full TB6612FNG // standby pin @@ -55,8 +54,7 @@ async fn main(_spawner: Spawner) { let left_fwd = gpio::Output::new(r.left_forward_pin, gpio::Level::Low); let left_bckw = gpio::Output::new(r.left_backward_pin, gpio::Level::Low); let mut left_speed = pwm::Config::default(); - left_speed.top = PWM_MAX; - left_speed.compare_a = PWM_MIN; + left_speed.top = period; let left_pwm = pwm::Pwm::new_output_a(r.left_slice, r.left_pwm_pin, left_speed); let left_motor = Motor::new(left_fwd, left_bckw, left_pwm).unwrap(); @@ -64,8 +62,7 @@ async fn main(_spawner: Spawner) { let right_fwd = gpio::Output::new(r.right_forward_pin, gpio::Level::Low); let right_bckw = gpio::Output::new(r.right_backward_pin, gpio::Level::Low); let mut right_speed = pwm::Config::default(); - right_speed.top = PWM_MAX; - right_speed.compare_b = PWM_MIN; + right_speed.top = period; let right_pwm = pwm::Pwm::new_output_b(r.right_slice, r.right_pwm_pin, right_speed); let right_motor = Motor::new(right_fwd, right_bckw, right_pwm).unwrap(); From 82772e3a8f36b31052ca46af3947ba253458bef1 Mon Sep 17 00:00:00 2001 From: Lucas Martins Date: Mon, 21 Oct 2024 17:50:05 -0300 Subject: [PATCH 107/144] :memo: fix wrong comment --- embassy-stm32/src/usart/buffered.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/usart/buffered.rs b/embassy-stm32/src/usart/buffered.rs index edaf41be9..7ba209063 100644 --- a/embassy-stm32/src/usart/buffered.rs +++ b/embassy-stm32/src/usart/buffered.rs @@ -246,7 +246,7 @@ impl<'d> BufferedUart<'d> { ) } - /// Create a new bidirectional buffered UART driver with only RTS pin as the DE pin + /// Create a new bidirectional buffered UART driver with only the RTS pin as the DE pin pub fn new_with_rts_as_de( peri: impl Peripheral

+ 'd, _irq: impl interrupt::typelevel::Binding> + 'd, @@ -286,7 +286,7 @@ impl<'d> BufferedUart<'d> { new_pin!(rx, AfType::input(Pull::None)), new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), new_pin!(rts, AfType::input(Pull::None)), - None, // no RTS + None, // no CTS None, // no DE tx_buffer, rx_buffer, From 548f11d5aef37d5ab1edac6970ceb7bee4300f4f Mon Sep 17 00:00:00 2001 From: rafael Date: Mon, 21 Oct 2024 23:19:45 +0200 Subject: [PATCH 108/144] cheaper motors need lower pwm frequency --- examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs b/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs index 263b551de..3fad2928c 100644 --- a/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs +++ b/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs @@ -40,8 +40,8 @@ async fn main(_spawner: Spawner) { let s = split_resources!(p); let r = s.motor; - // we want a PWM frequency of 25KHz - let pwm_freq = 25_000; // Hz, our desired frequency + // we want a PWM frequency of 1KHz, especially cheaper motors do not respond well to higher frequencies + let pwm_freq = 1_000; // Hz, our desired frequency let clock_freq = embassy_rp::clocks::clk_sys_freq(); let period = (clock_freq / pwm_freq) as u16 - 1; @@ -77,8 +77,8 @@ async fn main(_spawner: Spawner) { // drive a straight line forward at 20% speed for 5s info!("drive straight"); - control.motor_a.drive(DriveCommand::Forward(20)).unwrap(); - control.motor_b.drive(DriveCommand::Forward(20)).unwrap(); + control.motor_a.drive(DriveCommand::Forward(80)).unwrap(); + control.motor_b.drive(DriveCommand::Forward(80)).unwrap(); Timer::after(Duration::from_secs(5)).await; // coast for 2s @@ -95,8 +95,8 @@ async fn main(_spawner: Spawner) { // slowly turn for 3s info!("turn"); - control.motor_a.drive(DriveCommand::Backward(10)).unwrap(); - control.motor_b.drive(DriveCommand::Forward(10)).unwrap(); + control.motor_a.drive(DriveCommand::Backward(50)).unwrap(); + control.motor_b.drive(DriveCommand::Forward(50)).unwrap(); Timer::after(Duration::from_secs(3)).await; // and put the driver in standby mode and wait for 5s From c94c5516d1e9681c7b8222b9ee30f416b4e84c7d Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 22 Oct 2024 03:34:18 +0200 Subject: [PATCH 109/144] net: automatically enable defmt/ip_in_core --- embassy-net/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index 8a29aca79..1090892e0 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -27,7 +27,7 @@ default = [] std = [] ## Enable defmt -defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt", "heapless/defmt-03"] +defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt", "heapless/defmt-03", "defmt?/ip_in_core"] ## Trace all raw received and transmitted packets using defmt or log. packet-trace = [] @@ -65,7 +65,7 @@ multicast = ["smoltcp/multicast"] [dependencies] -defmt = { version = "0.3", optional = true } +defmt = { version = "0.3.8", optional = true } log = { version = "0.4.14", optional = true } smoltcp = { git="https://github.com/smoltcp-rs/smoltcp", rev="fe0b4d102253465850cd1cf39cd33d4721a4a8d5", default-features = false, features = [ From 3279c19eeeb942d15805c7780bf3bcad6159286e Mon Sep 17 00:00:00 2001 From: Dinu Blanovschi Date: Tue, 22 Oct 2024 12:30:37 +0200 Subject: [PATCH 110/144] feat(stm32): allow `bind_interrupts!` to accept conditional compilation attrs --- embassy-stm32/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 451f595e0..3ff9dcb7e 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -165,7 +165,7 @@ pub use crate::_generated::interrupt; // developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. #[macro_export] macro_rules! bind_interrupts { - ($vis:vis struct $name:ident { $($irq:ident => $($handler:ty),*;)* }) => { + ($vis:vis struct $name:ident { $($(#[cfg($cond:meta)])? $irq:ident => $($handler:ty),*;)* }) => { #[derive(Copy, Clone)] $vis struct $name; @@ -174,11 +174,13 @@ macro_rules! bind_interrupts { #[no_mangle] unsafe extern "C" fn $irq() { $( + $(#[cfg($cond)])? <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); )* } $( + $(#[cfg($cond)])? unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} )* )* From 66822f1000f542cac767be77345cb527956ada04 Mon Sep 17 00:00:00 2001 From: Brad Gibson Date: Tue, 22 Oct 2024 05:29:11 -0700 Subject: [PATCH 111/144] update path to cyw43 firmware in `wifi_blinky.rs` comments --- examples/rp/src/bin/wifi_blinky.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/rp/src/bin/wifi_blinky.rs b/examples/rp/src/bin/wifi_blinky.rs index 04a61bbd5..0107a2326 100644 --- a/examples/rp/src/bin/wifi_blinky.rs +++ b/examples/rp/src/bin/wifi_blinky.rs @@ -33,8 +33,8 @@ async fn main(spawner: Spawner) { // To make flashing faster for development, you may want to flash the firmwares independently // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: - // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10100000 - // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10140000 + // probe-rs download ../../cyw43-firmware/43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10100000 + // probe-rs download ../../cyw43-firmware/43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10140000 //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) }; //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; From ccd635f0dc5d9a21f7cec87e18368d70482b93ca Mon Sep 17 00:00:00 2001 From: Dinu Blanovschi Date: Tue, 22 Oct 2024 15:43:28 +0200 Subject: [PATCH 112/144] fix + allow both conditions on the irq and the handlers --- embassy-stm32/src/lib.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 3ff9dcb7e..2ce18c3c0 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -165,22 +165,31 @@ pub use crate::_generated::interrupt; // developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. #[macro_export] macro_rules! bind_interrupts { - ($vis:vis struct $name:ident { $($(#[cfg($cond:meta)])? $irq:ident => $($handler:ty),*;)* }) => { + ($vis:vis struct $name:ident { + $( + $(#[cfg($cond_irq:meta)])? + $irq:ident => $( + $(#[cfg($cond_handler:meta)])? + $handler:ty + ),*; + )* + }) => { #[derive(Copy, Clone)] $vis struct $name; $( #[allow(non_snake_case)] #[no_mangle] + $(#[cfg($cond_irq)])? unsafe extern "C" fn $irq() { $( - $(#[cfg($cond)])? + $(#[cfg($cond_handler)])? <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); )* } $( - $(#[cfg($cond)])? + $(#[cfg(all($cond_irq, $cond_handler))])? unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} )* )* From 82a438a0379b70b6df4bb72750267fa51ed787ab Mon Sep 17 00:00:00 2001 From: Dinu Blanovschi Date: Tue, 22 Oct 2024 15:51:05 +0200 Subject: [PATCH 113/144] fix --- embassy-stm32/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 2ce18c3c0..c59174988 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -189,7 +189,8 @@ macro_rules! bind_interrupts { } $( - $(#[cfg(all($cond_irq, $cond_handler))])? + $(#[cfg($cond_irq)])? + $(#[cfg($cond_handler)])? unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} )* )* From 5c23c789ee2776ea022957b82480dad6d0d0f660 Mon Sep 17 00:00:00 2001 From: Dinu Blanovschi Date: Tue, 22 Oct 2024 16:23:17 +0200 Subject: [PATCH 114/144] fix --- embassy-stm32/src/lib.rs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index c59174988..de68c54dc 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -188,13 +188,30 @@ macro_rules! bind_interrupts { )* } - $( - $(#[cfg($cond_irq)])? + $crate::bind_interrupts!(@__generate_impls $name $(#[cfg($cond_irq)])? $irq => $( $(#[cfg($cond_handler)])? - unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} - )* + $handler; + )*); )* }; + + (@__generate_single_impl $name:ident $(#[cfg($cond_irq:meta)])? $irq:ident => $(#[cfg($cond_handler:meta)])? $handler:ty;) => { + #[cfg(all( + $($cond_irq,)? + $($cond_handler,)? + ))] + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} + }; + + (@__generate_impls $name:ident $(#[cfg($cond_irq:meta)])? $irq:ident => $(#[cfg($cond_handler:meta)])? $handler:ty;) => { + $crate::bind_interrupts!(@__generate_single_impl $name $(#[cfg($cond_irq)])? $irq => $(#[cfg($cond_handler)])? $handler;); + }; + + (@__generate_impls $name:ident $(#[cfg($cond_irq:meta)])? $irq:ident => $(#[cfg($cond_handler:meta)])? $handler:ty; $(tail:tt)*) => { + $crate::bind_interrupts!(@__generate_single_impl $name $(#[cfg($cond_irq)])? $irq => $(#[cfg($cond_handler)])? $handler;); + + $crate::bind_interrupts!(@__generate_impls $name $(#[cfg($cond_irq)])? $irq => $(tail)*); + }; } // Reexports From e9f2e63796989c4e248418e341a442e0eff5946d Mon Sep 17 00:00:00 2001 From: Dinu Blanovschi Date: Tue, 22 Oct 2024 16:50:10 +0200 Subject: [PATCH 115/144] fix --- embassy-stm32/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index de68c54dc..4154a7275 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -207,10 +207,10 @@ macro_rules! bind_interrupts { $crate::bind_interrupts!(@__generate_single_impl $name $(#[cfg($cond_irq)])? $irq => $(#[cfg($cond_handler)])? $handler;); }; - (@__generate_impls $name:ident $(#[cfg($cond_irq:meta)])? $irq:ident => $(#[cfg($cond_handler:meta)])? $handler:ty; $(tail:tt)*) => { + (@__generate_impls $name:ident $(#[cfg($cond_irq:meta)])? $irq:ident => $(#[cfg($cond_handler:meta)])? $handler:ty; $($(#[cfg($cond_rest:meta)])? $handler_rest:ty;)+) => { $crate::bind_interrupts!(@__generate_single_impl $name $(#[cfg($cond_irq)])? $irq => $(#[cfg($cond_handler)])? $handler;); - $crate::bind_interrupts!(@__generate_impls $name $(#[cfg($cond_irq)])? $irq => $(tail)*); + $crate::bind_interrupts!(@__generate_impls $name $(#[cfg($cond_irq)])? $irq => $($(#[cfg($cond_rest)])? $handler_rest;)+); }; } From c79791552563746e9f62e3d2647d787d3947c249 Mon Sep 17 00:00:00 2001 From: Dinu Blanovschi Date: Tue, 22 Oct 2024 16:56:05 +0200 Subject: [PATCH 116/144] fix: review comments --- embassy-nrf/src/lib.rs | 19 ++++++++++++++----- embassy-rp/src/lib.rs | 19 ++++++++++++++----- embassy-stm32/src/lib.rs | 26 +++----------------------- 3 files changed, 31 insertions(+), 33 deletions(-) diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 13623dd1c..bd53664a2 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -177,22 +177,31 @@ mod chip; // developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. #[macro_export] macro_rules! bind_interrupts { - ($vis:vis struct $name:ident { $($irq:ident => $($handler:ty),*;)* }) => { + ($vis:vis struct $name:ident { + $( + $(#[cfg($cond_irq:meta)])? + $irq:ident => $( + $(#[cfg($cond_handler:meta)])? + $handler:ty + ),*; + )* + }) => { #[derive(Copy, Clone)] $vis struct $name; $( #[allow(non_snake_case)] #[no_mangle] + $(#[cfg($cond_irq)])? unsafe extern "C" fn $irq() { $( + $(#[cfg($cond_handler)])? <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + + $(#[cfg($cond_handler)])? + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} )* } - - $( - unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} - )* )* }; } diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index d402cf793..a72cebdd8 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -165,22 +165,31 @@ embassy_hal_internal::interrupt_mod!( // developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. #[macro_export] macro_rules! bind_interrupts { - ($vis:vis struct $name:ident { $($irq:ident => $($handler:ty),*;)* }) => { + ($vis:vis struct $name:ident { + $( + $(#[cfg($cond_irq:meta)])? + $irq:ident => $( + $(#[cfg($cond_handler:meta)])? + $handler:ty + ),*; + )* + }) => { #[derive(Copy, Clone)] $vis struct $name; $( #[allow(non_snake_case)] #[no_mangle] + $(#[cfg($cond_irq)])? unsafe extern "C" fn $irq() { $( + $(#[cfg($cond_handler)])? <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + + $(#[cfg($cond_handler)])? + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} )* } - - $( - unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} - )* )* }; } diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 4154a7275..65f02d62a 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -185,33 +185,13 @@ macro_rules! bind_interrupts { $( $(#[cfg($cond_handler)])? <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + + $(#[cfg($cond_handler)])? + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} )* } - - $crate::bind_interrupts!(@__generate_impls $name $(#[cfg($cond_irq)])? $irq => $( - $(#[cfg($cond_handler)])? - $handler; - )*); )* }; - - (@__generate_single_impl $name:ident $(#[cfg($cond_irq:meta)])? $irq:ident => $(#[cfg($cond_handler:meta)])? $handler:ty;) => { - #[cfg(all( - $($cond_irq,)? - $($cond_handler,)? - ))] - unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} - }; - - (@__generate_impls $name:ident $(#[cfg($cond_irq:meta)])? $irq:ident => $(#[cfg($cond_handler:meta)])? $handler:ty;) => { - $crate::bind_interrupts!(@__generate_single_impl $name $(#[cfg($cond_irq)])? $irq => $(#[cfg($cond_handler)])? $handler;); - }; - - (@__generate_impls $name:ident $(#[cfg($cond_irq:meta)])? $irq:ident => $(#[cfg($cond_handler:meta)])? $handler:ty; $($(#[cfg($cond_rest:meta)])? $handler_rest:ty;)+) => { - $crate::bind_interrupts!(@__generate_single_impl $name $(#[cfg($cond_irq)])? $irq => $(#[cfg($cond_handler)])? $handler;); - - $crate::bind_interrupts!(@__generate_impls $name $(#[cfg($cond_irq)])? $irq => $($(#[cfg($cond_rest)])? $handler_rest;)+); - }; } // Reexports From e5bc2666547d0320635fede91c76d936c56a1a90 Mon Sep 17 00:00:00 2001 From: Haobo Gu Date: Wed, 23 Oct 2024 12:54:50 +0800 Subject: [PATCH 117/144] feat: set ospi memory mapped mode Signed-off-by: Haobo Gu --- embassy-stm32/src/ospi/mod.rs | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/embassy-stm32/src/ospi/mod.rs b/embassy-stm32/src/ospi/mod.rs index e4adc4b09..0574f7816 100644 --- a/embassy-stm32/src/ospi/mod.rs +++ b/embassy-stm32/src/ospi/mod.rs @@ -179,6 +179,43 @@ pub struct Ospi<'d, T: Instance, M: PeriMode> { } impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { + pub fn enable_memory_mapped_mode(&mut self) { + let reg = T::REGS; + while reg.sr().read().busy() { + info!("wait ospi busy"); + } + + reg.ccr().modify(|r| { + r.set_isize(crate::ospi::vals::SizeInBits::_8BIT); + r.set_adsize(crate::ospi::vals::SizeInBits::_24BIT); + r.set_admode(crate::ospi::vals::PhaseMode::ONELINE); + r.set_imode(crate::ospi::vals::PhaseMode::ONELINE); + r.set_dmode(crate::ospi::vals::PhaseMode::FOURLINES); + }); + + reg.cr().modify(|r| { + r.set_fmode(crate::ospi::vals::FunctionalMode::MEMORYMAPPED); + r.set_dmaen(false); + r.set_en(true); + }); + + // reg.tcr().modify(|r| { + // r.set_dcyc(6); + // }); + + } + pub fn disable_memory_mapped_mode(&mut self) { + let reg = T::REGS; + while reg.sr().read().busy() { + info!("wait ospi busy"); + } + reg.cr().modify(|r| { + r.set_fmode(crate::ospi::vals::FunctionalMode::INDIRECTWRITE); + r.set_dmaen(false); + r.set_en(true); + }); + } + fn new_inner( peri: impl Peripheral

+ 'd, d0: Option>, From 528a3e43550b5d2ce4a5f44d890feaa3e56c113a Mon Sep 17 00:00:00 2001 From: Alex Moon Date: Wed, 14 Aug 2024 17:07:57 -0400 Subject: [PATCH 118/144] Add support for transactions to Twim in embassy-nrf --- embassy-nrf/src/twim.rs | 653 ++++++++++++++++++++++++---------------- 1 file changed, 393 insertions(+), 260 deletions(-) diff --git a/embassy-nrf/src/twim.rs b/embassy-nrf/src/twim.rs index c64743ecc..d0518807c 100644 --- a/embassy-nrf/src/twim.rs +++ b/embassy-nrf/src/twim.rs @@ -4,6 +4,7 @@ use core::future::{poll_fn, Future}; use core::marker::PhantomData; +use core::mem::MaybeUninit; use core::sync::atomic::compiler_fence; use core::sync::atomic::Ordering::SeqCst; use core::task::Poll; @@ -13,11 +14,12 @@ use embassy_hal_internal::{into_ref, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; #[cfg(feature = "time")] use embassy_time::{Duration, Instant}; +use embedded_hal_1::i2c::Operation; use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE}; use crate::gpio::Pin as GpioPin; use crate::interrupt::typelevel::Interrupt; -use crate::util::{slice_in_ram, slice_in_ram_or}; +use crate::util::slice_in_ram; use crate::{gpio, interrupt, pac, Peripheral}; /// TWI frequency @@ -103,6 +105,18 @@ impl interrupt::typelevel::Handler for InterruptHandl let r = T::regs(); let s = T::state(); + // Workaround for lack of LASTRX_SUSPEND short in some nRF chips + // Do this first to minimize latency + #[cfg(any(feature = "nrf52832", feature = "_nrf5340", feature = "_nrf9120"))] + if r.events_lastrx.read().bits() != 0 { + r.tasks_suspend.write(|w| unsafe { w.bits(1) }); + r.events_lastrx.reset(); + } + + if r.events_suspended.read().bits() != 0 { + s.end_waker.wake(); + r.intenclr.write(|w| w.suspended().clear()); + } if r.events_stopped.read().bits() != 0 { s.end_waker.wake(); r.intenclr.write(|w| w.stopped().clear()); @@ -182,8 +196,22 @@ impl<'d, T: Instance> Twim<'d, T> { } /// Set TX buffer, checking that it is in RAM and has suitable length. - unsafe fn set_tx_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> { - slice_in_ram_or(buffer, Error::BufferNotInRAM)?; + unsafe fn set_tx_buffer( + &mut self, + buffer: &[u8], + ram_buffer: Option<&mut [MaybeUninit; FORCE_COPY_BUFFER_SIZE]>, + ) -> Result<(), Error> { + let buffer = if slice_in_ram(buffer) { + buffer + } else { + let ram_buffer = ram_buffer.ok_or(Error::BufferNotInRAM)?; + trace!("Copying TWIM tx buffer into RAM for DMA"); + let ram_buffer = &mut ram_buffer[..buffer.len()]; + // Inline implementation of the nightly API MaybeUninit::copy_from_slice(ram_buffer, buffer) + let uninit_src: &[MaybeUninit] = unsafe { core::mem::transmute(buffer) }; + ram_buffer.copy_from_slice(uninit_src); + unsafe { &*(ram_buffer as *const [MaybeUninit] as *const [u8]) } + }; if buffer.len() > EASY_DMA_SIZE { return Err(Error::TxBufferTooLong); @@ -290,7 +318,7 @@ impl<'d, T: Instance> Twim<'d, T> { fn blocking_wait(&mut self) { let r = T::regs(); loop { - if r.events_stopped.read().bits() != 0 { + if r.events_suspended.read().bits() != 0 || r.events_stopped.read().bits() != 0 { r.events_stopped.reset(); break; } @@ -307,7 +335,7 @@ impl<'d, T: Instance> Twim<'d, T> { let r = T::regs(); let deadline = Instant::now() + timeout; loop { - if r.events_stopped.read().bits() != 0 { + if r.events_suspended.read().bits() != 0 || r.events_stopped.read().bits() != 0 { r.events_stopped.reset(); break; } @@ -331,7 +359,7 @@ impl<'d, T: Instance> Twim<'d, T> { let s = T::state(); s.end_waker.register(cx.waker()); - if r.events_stopped.read().bits() != 0 { + if r.events_suspended.read().bits() != 0 || r.events_stopped.read().bits() != 0 { r.events_stopped.reset(); return Poll::Ready(()); @@ -347,75 +375,13 @@ impl<'d, T: Instance> Twim<'d, T> { }) } - fn setup_write_from_ram(&mut self, address: u8, buffer: &[u8], inten: bool) -> Result<(), Error> { - let r = T::regs(); - - compiler_fence(SeqCst); - - r.address.write(|w| unsafe { w.address().bits(address) }); - - // Set up the DMA write. - unsafe { self.set_tx_buffer(buffer)? }; - - // Clear events - r.events_stopped.reset(); - r.events_error.reset(); - r.events_lasttx.reset(); - self.clear_errorsrc(); - - if inten { - r.intenset.write(|w| w.stopped().set().error().set()); - } else { - r.intenclr.write(|w| w.stopped().clear().error().clear()); - } - - // Start write operation. - r.shorts.write(|w| w.lasttx_stop().enabled()); - r.tasks_starttx.write(|w| unsafe { w.bits(1) }); - if buffer.is_empty() { - // With a zero-length buffer, LASTTX doesn't fire (because there's no last byte!), so do the STOP ourselves. - r.tasks_stop.write(|w| unsafe { w.bits(1) }); - } - Ok(()) - } - - fn setup_read(&mut self, address: u8, buffer: &mut [u8], inten: bool) -> Result<(), Error> { - let r = T::regs(); - - compiler_fence(SeqCst); - - r.address.write(|w| unsafe { w.address().bits(address) }); - - // Set up the DMA read. - unsafe { self.set_rx_buffer(buffer)? }; - - // Clear events - r.events_stopped.reset(); - r.events_error.reset(); - self.clear_errorsrc(); - - if inten { - r.intenset.write(|w| w.stopped().set().error().set()); - } else { - r.intenclr.write(|w| w.stopped().clear().error().clear()); - } - - // Start read operation. - r.shorts.write(|w| w.lastrx_stop().enabled()); - r.tasks_startrx.write(|w| unsafe { w.bits(1) }); - if buffer.is_empty() { - // With a zero-length buffer, LASTRX doesn't fire (because there's no last byte!), so do the STOP ourselves. - r.tasks_stop.write(|w| unsafe { w.bits(1) }); - } - Ok(()) - } - - fn setup_write_read_from_ram( + fn setup_operations( &mut self, address: u8, - wr_buffer: &[u8], - rd_buffer: &mut [u8], + operations: &mut [Operation<'_>], + tx_ram_buffer: Option<&mut [MaybeUninit; FORCE_COPY_BUFFER_SIZE]>, inten: bool, + stop: bool, ) -> Result<(), Error> { let r = T::regs(); @@ -423,92 +389,343 @@ impl<'d, T: Instance> Twim<'d, T> { r.address.write(|w| unsafe { w.address().bits(address) }); - // Set up DMA buffers. - unsafe { - self.set_tx_buffer(wr_buffer)?; - self.set_rx_buffer(rd_buffer)?; - } - - // Clear events + let was_suspended = r.events_suspended.read().bits() != 0; + r.events_suspended.reset(); r.events_stopped.reset(); r.events_error.reset(); self.clear_errorsrc(); if inten { - r.intenset.write(|w| w.stopped().set().error().set()); + r.intenset.write(|w| w.suspended().set().stopped().set().error().set()); } else { - r.intenclr.write(|w| w.stopped().clear().error().clear()); + r.intenclr + .write(|w| w.suspended().clear().stopped().clear().error().clear()); } + #[cfg(any(feature = "nrf52832", feature = "_nrf5340", feature = "_nrf9120"))] + r.intenclr.write(|w| w.lastrx().clear()); - // Start write+read operation. - r.shorts.write(|w| { - w.lasttx_startrx().enabled(); - w.lastrx_stop().enabled(); - w - }); - r.tasks_starttx.write(|w| unsafe { w.bits(1) }); - if wr_buffer.is_empty() && rd_buffer.is_empty() { - // With a zero-length buffer, LASTRX/LASTTX doesn't fire (because there's no last byte!), so do the STOP ourselves. - // TODO handle when only one of the buffers is zero length - r.tasks_stop.write(|w| unsafe { w.bits(1) }); + match operations { + [Operation::Read(rd_buffer), Operation::Write(wr_buffer), rest @ ..] + if !rd_buffer.is_empty() && !wr_buffer.is_empty() => + { + let stop = stop && rest.is_empty(); + + // Set up DMA buffers. + unsafe { + self.set_tx_buffer(wr_buffer, tx_ram_buffer)?; + self.set_rx_buffer(rd_buffer)?; + } + + r.shorts.write(|w| { + w.lastrx_starttx().enabled(); + if stop { + w.lasttx_stop().enabled(); + } else { + w.lasttx_suspend().enabled(); + } + w + }); + + // Start read+write operation. + r.tasks_startrx.write(|w| unsafe { w.bits(1) }); + + if was_suspended { + r.tasks_resume.write(|w| unsafe { w.bits(1) }); + } + } + [Operation::Write(wr_buffer), Operation::Read(rd_buffer), rest @ ..] + if !wr_buffer.is_empty() && !rd_buffer.is_empty() => + { + let stop = stop && rest.is_empty(); + + // Set up DMA buffers. + unsafe { + self.set_tx_buffer(wr_buffer, tx_ram_buffer)?; + self.set_rx_buffer(rd_buffer)?; + } + + // Start write+read operation. + r.shorts.write(|w| { + w.lasttx_startrx().enabled(); + if stop { + w.lastrx_stop().enabled(); + } else { + #[cfg(not(any(feature = "nrf52832", feature = "_nrf5340", feature = "_nrf9120")))] + w.lastrx_suspend().enabled(); + } + w + }); + #[cfg(any(feature = "nrf52832", feature = "_nrf5340", feature = "_nrf9120"))] + if !stop { + r.intenset.write(|w| w.lastrx().set()); + } + + r.tasks_starttx.write(|w| unsafe { w.bits(1) }); + + if was_suspended { + r.tasks_resume.write(|w| unsafe { w.bits(1) }); + } + } + [Operation::Read(buffer), rest @ ..] => { + let stop = stop && rest.is_empty(); + + // Set up DMA buffers. + unsafe { + self.set_rx_buffer(buffer)?; + } + + // Start read operation. + r.shorts.write(|w| { + if stop { + w.lastrx_stop().enabled(); + } else { + #[cfg(not(any(feature = "nrf52832", feature = "_nrf5340", feature = "_nrf9120")))] + w.lastrx_suspend().enabled(); + } + w + }); + #[cfg(any(feature = "nrf52832", feature = "_nrf5340", feature = "_nrf9120"))] + if !stop { + r.intenset.write(|w| w.lastrx().set()); + } + + r.tasks_startrx.write(|w| unsafe { w.bits(1) }); + + if was_suspended { + r.tasks_resume.write(|w| unsafe { w.bits(1) }); + } + + if buffer.is_empty() { + // With a zero-length buffer, LASTRX doesn't fire (because there's no last byte!), so do the STOP/SUSPEND ourselves. + if stop { + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + } else { + r.tasks_suspend.write(|w| unsafe { w.bits(1) }); + } + } + } + [Operation::Write(buffer), rest @ ..] => { + let stop = stop && rest.is_empty(); + + // Set up DMA buffers. + unsafe { + self.set_tx_buffer(buffer, tx_ram_buffer)?; + } + + // Start write operation. + r.shorts.write(|w| { + if stop { + w.lasttx_stop().enabled(); + } else { + w.lasttx_suspend().enabled(); + } + w + }); + + r.tasks_starttx.write(|w| unsafe { w.bits(1) }); + + if was_suspended { + r.tasks_resume.write(|w| unsafe { w.bits(1) }); + } + + if buffer.is_empty() { + // With a zero-length buffer, LASTTX doesn't fire (because there's no last byte!), so do the STOP/SUSPEND ourselves. + if stop { + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + } else { + r.tasks_suspend.write(|w| unsafe { w.bits(1) }); + } + } + } + [] => { + if stop { + if was_suspended { + r.tasks_resume.write(|w| unsafe { w.bits(1) }); + } + + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + } + } } Ok(()) } - fn setup_write_read( - &mut self, - address: u8, - wr_buffer: &[u8], - rd_buffer: &mut [u8], - inten: bool, - ) -> Result<(), Error> { - match self.setup_write_read_from_ram(address, wr_buffer, rd_buffer, inten) { - Ok(_) => Ok(()), - Err(Error::BufferNotInRAM) => { - trace!("Copying TWIM tx buffer into RAM for DMA"); - let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()]; - tx_ram_buf.copy_from_slice(wr_buffer); - self.setup_write_read_from_ram(address, tx_ram_buf, rd_buffer, inten) + fn check_operations(&mut self, operations: &mut [Operation<'_>]) -> Result { + compiler_fence(SeqCst); + self.check_errorsrc()?; + + match operations { + [Operation::Read(rd_buffer), Operation::Write(wr_buffer), ..] + | [Operation::Write(wr_buffer), Operation::Read(rd_buffer), ..] + if !rd_buffer.is_empty() && !wr_buffer.is_empty() => + { + self.check_tx(wr_buffer.len())?; + self.check_rx(rd_buffer.len())?; + Ok(2) } - Err(error) => Err(error), + [Operation::Read(buffer), ..] => { + self.check_rx(buffer.len())?; + Ok(1) + } + [Operation::Write(buffer), ..] => { + self.check_tx(buffer.len())?; + Ok(1) + } + [] => Ok(0), } } - fn setup_write(&mut self, address: u8, wr_buffer: &[u8], inten: bool) -> Result<(), Error> { - match self.setup_write_from_ram(address, wr_buffer, inten) { - Ok(_) => Ok(()), - Err(Error::BufferNotInRAM) => { - trace!("Copying TWIM tx buffer into RAM for DMA"); - let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()]; - tx_ram_buf.copy_from_slice(wr_buffer); - self.setup_write_from_ram(address, tx_ram_buf, inten) - } - Err(error) => Err(error), + // =========================================== + + /// Execute the provided operations on the I2C bus. + /// + /// Each buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + /// + /// If `stop` is set, the transaction will be terminated with a STOP + /// condition and the Twim will be stopped. Otherwise, the bus will be + /// left busy via clock stretching and Twim will be suspended. + /// + /// The nrf52832, nrf5340, and nrf9120 do not have hardware support for + /// suspending following a read operation therefore it is emulated by the + /// interrupt handler. If the latency of servicing that interrupt is + /// longer than a byte worth of clocks on the bus, the SCL clock will + /// continue to run for one or more additional bytes. This applies to + /// consecutive read operations, certain write-read-write sequences, or + /// any sequence of operations ending in a read when `stop == false`. + pub fn blocking_transaction( + &mut self, + address: u8, + mut operations: &mut [Operation<'_>], + stop: bool, + ) -> Result<(), Error> { + let mut tx_ram_buffer = [MaybeUninit::uninit(); FORCE_COPY_BUFFER_SIZE]; + while !operations.is_empty() { + self.setup_operations(address, operations, Some(&mut tx_ram_buffer), false, stop)?; + self.blocking_wait(); + let consumed = self.check_operations(operations)?; + operations = &mut operations[consumed..]; } + Ok(()) } + /// Same as [`blocking_transaction`](Twim::blocking_transaction) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + pub fn blocking_transaction_from_ram( + &mut self, + address: u8, + mut operations: &mut [Operation<'_>], + stop: bool, + ) -> Result<(), Error> { + while !operations.is_empty() { + self.setup_operations(address, operations, None, false, stop)?; + self.blocking_wait(); + let consumed = self.check_operations(operations)?; + operations = &mut operations[consumed..]; + } + Ok(()) + } + + /// Execute the provided operations on the I2C bus with timeout. + /// + /// See [`blocking_transaction`]. + #[cfg(feature = "time")] + pub fn blocking_transaction_timeout( + &mut self, + address: u8, + mut operations: &mut [Operation<'_>], + stop: bool, + timeout: Duration, + ) -> Result<(), Error> { + let mut tx_ram_buffer = [MaybeUninit::uninit(); FORCE_COPY_BUFFER_SIZE]; + while !operations.is_empty() { + self.setup_operations(address, operations, Some(&mut tx_ram_buffer), false, stop)?; + self.blocking_wait_timeout(timeout)?; + let consumed = self.check_operations(operations)?; + operations = &mut operations[consumed..]; + } + Ok(()) + } + + /// Same as [`blocking_transaction_timeout`](Twim::blocking_transaction_timeout) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + #[cfg(feature = "time")] + pub fn blocking_transaction_from_ram_timeout( + &mut self, + address: u8, + mut operations: &mut [Operation<'_>], + stop: bool, + timeout: Duration, + ) -> Result<(), Error> { + while !operations.is_empty() { + self.setup_operations(address, operations, None, false, stop)?; + self.blocking_wait_timeout(timeout)?; + let consumed = self.check_operations(operations)?; + operations = &mut operations[consumed..]; + } + Ok(()) + } + + /// Execute the provided operations on the I2C bus. + /// + /// Each buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + /// + /// If `stop` is set, the transaction will be terminated with a STOP + /// condition and the Twim will be stopped. Otherwise, the bus will be + /// left busy via clock stretching and Twim will be suspended. + /// + /// The nrf52832, nrf5340, and nrf9120 do not have hardware support for + /// suspending following a read operation therefore it is emulated by the + /// interrupt handler. If the latency of servicing that interrupt is + /// longer than a byte worth of clocks on the bus, the SCL clock will + /// continue to run for one or more additional bytes. This applies to + /// consecutive read operations, certain write-read-write sequences, or + /// any sequence of operations ending in a read when `stop == false`. + pub async fn transaction( + &mut self, + address: u8, + mut operations: &mut [Operation<'_>], + stop: bool, + ) -> Result<(), Error> { + let mut tx_ram_buffer = [MaybeUninit::uninit(); FORCE_COPY_BUFFER_SIZE]; + while !operations.is_empty() { + self.setup_operations(address, operations, Some(&mut tx_ram_buffer), true, stop)?; + self.async_wait().await; + let consumed = self.check_operations(operations)?; + operations = &mut operations[consumed..]; + } + Ok(()) + } + + /// Same as [`transaction`](Twim::transaction) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + pub async fn transaction_from_ram( + &mut self, + address: u8, + mut operations: &mut [Operation<'_>], + stop: bool, + ) -> Result<(), Error> { + while !operations.is_empty() { + self.setup_operations(address, operations, None, true, stop)?; + self.async_wait().await; + let consumed = self.check_operations(operations)?; + operations = &mut operations[consumed..]; + } + Ok(()) + } + + // =========================================== + /// Write to an I2C slave. /// /// The buffer must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. pub fn blocking_write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { - self.setup_write(address, buffer, false)?; - self.blocking_wait(); - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(buffer.len())?; - Ok(()) + self.blocking_transaction(address, &mut [Operation::Write(buffer)], true) } /// Same as [`blocking_write`](Twim::blocking_write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. pub fn blocking_write_from_ram(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { - self.setup_write_from_ram(address, buffer, false)?; - self.blocking_wait(); - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(buffer.len())?; - Ok(()) + self.blocking_transaction_from_ram(address, &mut [Operation::Write(buffer)], true) } /// Read from an I2C slave. @@ -516,12 +733,7 @@ impl<'d, T: Instance> Twim<'d, T> { /// The buffer must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. pub fn blocking_read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { - self.setup_read(address, buffer, false)?; - self.blocking_wait(); - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_rx(buffer.len())?; - Ok(()) + self.blocking_transaction(address, &mut [Operation::Read(buffer)], true) } /// Write data to an I2C slave, then read data from the slave without @@ -530,13 +742,11 @@ impl<'d, T: Instance> Twim<'d, T> { /// The buffers must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. pub fn blocking_write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Error> { - self.setup_write_read(address, wr_buffer, rd_buffer, false)?; - self.blocking_wait(); - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(wr_buffer.len())?; - self.check_rx(rd_buffer.len())?; - Ok(()) + self.blocking_transaction( + address, + &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)], + true, + ) } /// Same as [`blocking_write_read`](Twim::blocking_write_read) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. @@ -546,13 +756,11 @@ impl<'d, T: Instance> Twim<'d, T> { wr_buffer: &[u8], rd_buffer: &mut [u8], ) -> Result<(), Error> { - self.setup_write_read_from_ram(address, wr_buffer, rd_buffer, false)?; - self.blocking_wait(); - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(wr_buffer.len())?; - self.check_rx(rd_buffer.len())?; - Ok(()) + self.blocking_transaction_from_ram( + address, + &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)], + true, + ) } // =========================================== @@ -562,12 +770,7 @@ impl<'d, T: Instance> Twim<'d, T> { /// See [`blocking_write`]. #[cfg(feature = "time")] pub fn blocking_write_timeout(&mut self, address: u8, buffer: &[u8], timeout: Duration) -> Result<(), Error> { - self.setup_write(address, buffer, false)?; - self.blocking_wait_timeout(timeout)?; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(buffer.len())?; - Ok(()) + self.blocking_transaction_timeout(address, &mut [Operation::Write(buffer)], true, timeout) } /// Same as [`blocking_write`](Twim::blocking_write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. @@ -578,12 +781,7 @@ impl<'d, T: Instance> Twim<'d, T> { buffer: &[u8], timeout: Duration, ) -> Result<(), Error> { - self.setup_write_from_ram(address, buffer, false)?; - self.blocking_wait_timeout(timeout)?; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(buffer.len())?; - Ok(()) + self.blocking_transaction_from_ram_timeout(address, &mut [Operation::Write(buffer)], true, timeout) } /// Read from an I2C slave. @@ -592,12 +790,7 @@ impl<'d, T: Instance> Twim<'d, T> { /// and at most 65535 bytes on the nRF52840. #[cfg(feature = "time")] pub fn blocking_read_timeout(&mut self, address: u8, buffer: &mut [u8], timeout: Duration) -> Result<(), Error> { - self.setup_read(address, buffer, false)?; - self.blocking_wait_timeout(timeout)?; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_rx(buffer.len())?; - Ok(()) + self.blocking_transaction_timeout(address, &mut [Operation::Read(buffer)], true, timeout) } /// Write data to an I2C slave, then read data from the slave without @@ -613,13 +806,12 @@ impl<'d, T: Instance> Twim<'d, T> { rd_buffer: &mut [u8], timeout: Duration, ) -> Result<(), Error> { - self.setup_write_read(address, wr_buffer, rd_buffer, false)?; - self.blocking_wait_timeout(timeout)?; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(wr_buffer.len())?; - self.check_rx(rd_buffer.len())?; - Ok(()) + self.blocking_transaction_timeout( + address, + &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)], + true, + timeout, + ) } /// Same as [`blocking_write_read`](Twim::blocking_write_read) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. @@ -631,13 +823,12 @@ impl<'d, T: Instance> Twim<'d, T> { rd_buffer: &mut [u8], timeout: Duration, ) -> Result<(), Error> { - self.setup_write_read_from_ram(address, wr_buffer, rd_buffer, false)?; - self.blocking_wait_timeout(timeout)?; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(wr_buffer.len())?; - self.check_rx(rd_buffer.len())?; - Ok(()) + self.blocking_transaction_from_ram_timeout( + address, + &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)], + true, + timeout, + ) } // =========================================== @@ -647,12 +838,7 @@ impl<'d, T: Instance> Twim<'d, T> { /// The buffer must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { - self.setup_read(address, buffer, true)?; - self.async_wait().await; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_rx(buffer.len())?; - Ok(()) + self.transaction(address, &mut [Operation::Read(buffer)], true).await } /// Write to an I2C slave. @@ -660,22 +846,13 @@ impl<'d, T: Instance> Twim<'d, T> { /// The buffer must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. pub async fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { - self.setup_write(address, buffer, true)?; - self.async_wait().await; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(buffer.len())?; - Ok(()) + self.transaction(address, &mut [Operation::Write(buffer)], true).await } /// Same as [`write`](Twim::write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. pub async fn write_from_ram(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { - self.setup_write_from_ram(address, buffer, true)?; - self.async_wait().await; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(buffer.len())?; - Ok(()) + self.transaction_from_ram(address, &mut [Operation::Write(buffer)], true) + .await } /// Write data to an I2C slave, then read data from the slave without @@ -684,13 +861,12 @@ impl<'d, T: Instance> Twim<'d, T> { /// The buffers must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. pub async fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Error> { - self.setup_write_read(address, wr_buffer, rd_buffer, true)?; - self.async_wait().await; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(wr_buffer.len())?; - self.check_rx(rd_buffer.len())?; - Ok(()) + self.transaction( + address, + &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)], + true, + ) + .await } /// Same as [`write_read`](Twim::write_read) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. @@ -700,13 +876,12 @@ impl<'d, T: Instance> Twim<'d, T> { wr_buffer: &[u8], rd_buffer: &mut [u8], ) -> Result<(), Error> { - self.setup_write_read_from_ram(address, wr_buffer, rd_buffer, true)?; - self.async_wait().await; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(wr_buffer.len())?; - self.check_rx(rd_buffer.len())?; - Ok(()) + self.transaction_from_ram( + address, + &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)], + true, + ) + .await } } @@ -777,16 +952,7 @@ mod eh02 { type Error = Error; fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { - if slice_in_ram(bytes) { - self.blocking_write(addr, bytes) - } else { - let buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..]; - for chunk in bytes.chunks(FORCE_COPY_BUFFER_SIZE) { - buf[..chunk.len()].copy_from_slice(chunk); - self.blocking_write(addr, &buf[..chunk.len()])?; - } - Ok(()) - } + self.blocking_write(addr, bytes) } } @@ -832,47 +998,14 @@ impl<'d, T: Instance> embedded_hal_1::i2c::ErrorType for Twim<'d, T> { } impl<'d, T: Instance> embedded_hal_1::i2c::I2c for Twim<'d, T> { - fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { - self.blocking_read(address, buffer) - } - - fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(address, buffer) - } - - fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Self::Error> { - self.blocking_write_read(address, wr_buffer, rd_buffer) - } - - fn transaction( - &mut self, - _address: u8, - _operations: &mut [embedded_hal_1::i2c::Operation<'_>], - ) -> Result<(), Self::Error> { - todo!(); + fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> { + self.blocking_transaction(address, operations, true) } } impl<'d, T: Instance> embedded_hal_async::i2c::I2c for Twim<'d, T> { - async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { - self.read(address, read).await - } - - async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { - self.write(address, write).await - } - async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { - self.write_read(address, write, read).await - } - - async fn transaction( - &mut self, - address: u8, - operations: &mut [embedded_hal_1::i2c::Operation<'_>], - ) -> Result<(), Self::Error> { - let _ = address; - let _ = operations; - todo!() + async fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> { + self.transaction(address, operations, true).await } } From 110d87bbd2c4e5b60ed99c0a259b8366e5164d41 Mon Sep 17 00:00:00 2001 From: Alex Moon Date: Mon, 19 Aug 2024 16:35:13 -0400 Subject: [PATCH 119/144] Remove support for consecutive Read operations Due to Twim hardware limitations --- embassy-nrf/src/twim.rs | 189 ++++++++++++---------------------------- 1 file changed, 57 insertions(+), 132 deletions(-) diff --git a/embassy-nrf/src/twim.rs b/embassy-nrf/src/twim.rs index d0518807c..4a239e8b5 100644 --- a/embassy-nrf/src/twim.rs +++ b/embassy-nrf/src/twim.rs @@ -105,14 +105,6 @@ impl interrupt::typelevel::Handler for InterruptHandl let r = T::regs(); let s = T::state(); - // Workaround for lack of LASTRX_SUSPEND short in some nRF chips - // Do this first to minimize latency - #[cfg(any(feature = "nrf52832", feature = "_nrf5340", feature = "_nrf9120"))] - if r.events_lastrx.read().bits() != 0 { - r.tasks_suspend.write(|w| unsafe { w.bits(1) }); - r.events_lastrx.reset(); - } - if r.events_suspended.read().bits() != 0 { s.end_waker.wake(); r.intenclr.write(|w| w.suspended().clear()); @@ -381,7 +373,6 @@ impl<'d, T: Instance> Twim<'d, T> { operations: &mut [Operation<'_>], tx_ram_buffer: Option<&mut [MaybeUninit; FORCE_COPY_BUFFER_SIZE]>, inten: bool, - stop: bool, ) -> Result<(), Error> { let r = T::regs(); @@ -401,14 +392,12 @@ impl<'d, T: Instance> Twim<'d, T> { r.intenclr .write(|w| w.suspended().clear().stopped().clear().error().clear()); } - #[cfg(any(feature = "nrf52832", feature = "_nrf5340", feature = "_nrf9120"))] - r.intenclr.write(|w| w.lastrx().clear()); match operations { [Operation::Read(rd_buffer), Operation::Write(wr_buffer), rest @ ..] if !rd_buffer.is_empty() && !wr_buffer.is_empty() => { - let stop = stop && rest.is_empty(); + let stop = rest.is_empty(); // Set up DMA buffers. unsafe { @@ -433,11 +422,9 @@ impl<'d, T: Instance> Twim<'d, T> { r.tasks_resume.write(|w| unsafe { w.bits(1) }); } } - [Operation::Write(wr_buffer), Operation::Read(rd_buffer), rest @ ..] + [Operation::Write(wr_buffer), Operation::Read(rd_buffer)] if !wr_buffer.is_empty() && !rd_buffer.is_empty() => { - let stop = stop && rest.is_empty(); - // Set up DMA buffers. unsafe { self.set_tx_buffer(wr_buffer, tx_ram_buffer)?; @@ -447,18 +434,9 @@ impl<'d, T: Instance> Twim<'d, T> { // Start write+read operation. r.shorts.write(|w| { w.lasttx_startrx().enabled(); - if stop { - w.lastrx_stop().enabled(); - } else { - #[cfg(not(any(feature = "nrf52832", feature = "_nrf5340", feature = "_nrf9120")))] - w.lastrx_suspend().enabled(); - } + w.lastrx_stop().enabled(); w }); - #[cfg(any(feature = "nrf52832", feature = "_nrf5340", feature = "_nrf9120"))] - if !stop { - r.intenset.write(|w| w.lastrx().set()); - } r.tasks_starttx.write(|w| unsafe { w.bits(1) }); @@ -466,9 +444,7 @@ impl<'d, T: Instance> Twim<'d, T> { r.tasks_resume.write(|w| unsafe { w.bits(1) }); } } - [Operation::Read(buffer), rest @ ..] => { - let stop = stop && rest.is_empty(); - + [Operation::Read(buffer)] => { // Set up DMA buffers. unsafe { self.set_rx_buffer(buffer)?; @@ -476,18 +452,9 @@ impl<'d, T: Instance> Twim<'d, T> { // Start read operation. r.shorts.write(|w| { - if stop { - w.lastrx_stop().enabled(); - } else { - #[cfg(not(any(feature = "nrf52832", feature = "_nrf5340", feature = "_nrf9120")))] - w.lastrx_suspend().enabled(); - } + w.lastrx_stop().enabled(); w }); - #[cfg(any(feature = "nrf52832", feature = "_nrf5340", feature = "_nrf9120"))] - if !stop { - r.intenset.write(|w| w.lastrx().set()); - } r.tasks_startrx.write(|w| unsafe { w.bits(1) }); @@ -496,16 +463,15 @@ impl<'d, T: Instance> Twim<'d, T> { } if buffer.is_empty() { - // With a zero-length buffer, LASTRX doesn't fire (because there's no last byte!), so do the STOP/SUSPEND ourselves. - if stop { - r.tasks_stop.write(|w| unsafe { w.bits(1) }); - } else { - r.tasks_suspend.write(|w| unsafe { w.bits(1) }); - } + // With a zero-length buffer, LASTRX doesn't fire (because there's no last byte!), so do the STOP ourselves. + r.tasks_stop.write(|w| unsafe { w.bits(1) }); } } + [Operation::Read(_), ..] => { + panic!("Suspending after a read is not supported!"); + } [Operation::Write(buffer), rest @ ..] => { - let stop = stop && rest.is_empty(); + let stop = rest.is_empty(); // Set up DMA buffers. unsafe { @@ -538,13 +504,11 @@ impl<'d, T: Instance> Twim<'d, T> { } } [] => { - if stop { - if was_suspended { - r.tasks_resume.write(|w| unsafe { w.bits(1) }); - } - - r.tasks_stop.write(|w| unsafe { w.bits(1) }); + if was_suspended { + r.tasks_resume.write(|w| unsafe { w.bits(1) }); } + + r.tasks_stop.write(|w| unsafe { w.bits(1) }); } } @@ -557,17 +521,18 @@ impl<'d, T: Instance> Twim<'d, T> { match operations { [Operation::Read(rd_buffer), Operation::Write(wr_buffer), ..] - | [Operation::Write(wr_buffer), Operation::Read(rd_buffer), ..] + | [Operation::Write(wr_buffer), Operation::Read(rd_buffer)] if !rd_buffer.is_empty() && !wr_buffer.is_empty() => { self.check_tx(wr_buffer.len())?; self.check_rx(rd_buffer.len())?; Ok(2) } - [Operation::Read(buffer), ..] => { + [Operation::Read(buffer)] => { self.check_rx(buffer.len())?; Ok(1) } + [Operation::Read(_), ..] => unreachable!(), [Operation::Write(buffer), ..] => { self.check_tx(buffer.len())?; Ok(1) @@ -583,26 +548,17 @@ impl<'d, T: Instance> Twim<'d, T> { /// Each buffer must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. /// - /// If `stop` is set, the transaction will be terminated with a STOP - /// condition and the Twim will be stopped. Otherwise, the bus will be - /// left busy via clock stretching and Twim will be suspended. - /// - /// The nrf52832, nrf5340, and nrf9120 do not have hardware support for - /// suspending following a read operation therefore it is emulated by the - /// interrupt handler. If the latency of servicing that interrupt is - /// longer than a byte worth of clocks on the bus, the SCL clock will - /// continue to run for one or more additional bytes. This applies to - /// consecutive read operations, certain write-read-write sequences, or - /// any sequence of operations ending in a read when `stop == false`. - pub fn blocking_transaction( - &mut self, - address: u8, - mut operations: &mut [Operation<'_>], - stop: bool, - ) -> Result<(), Error> { + /// Consecutive `Read` operations are not supported because the Twim + /// hardware does not support suspending after a read operation. (Setting + /// the SUSPEND task in response to a LASTRX event causes the final byte of + /// the operation to be ACKed instead of NAKed. When the TWIM is resumed, + /// one more byte will be read before the new operation is started, leading + /// to an Overrun error if the RXD has not been updated, or an extraneous + /// byte read into the new buffer if the RXD has been updated.) + pub fn blocking_transaction(&mut self, address: u8, mut operations: &mut [Operation<'_>]) -> Result<(), Error> { let mut tx_ram_buffer = [MaybeUninit::uninit(); FORCE_COPY_BUFFER_SIZE]; while !operations.is_empty() { - self.setup_operations(address, operations, Some(&mut tx_ram_buffer), false, stop)?; + self.setup_operations(address, operations, Some(&mut tx_ram_buffer), false)?; self.blocking_wait(); let consumed = self.check_operations(operations)?; operations = &mut operations[consumed..]; @@ -615,10 +571,9 @@ impl<'d, T: Instance> Twim<'d, T> { &mut self, address: u8, mut operations: &mut [Operation<'_>], - stop: bool, ) -> Result<(), Error> { while !operations.is_empty() { - self.setup_operations(address, operations, None, false, stop)?; + self.setup_operations(address, operations, None, false)?; self.blocking_wait(); let consumed = self.check_operations(operations)?; operations = &mut operations[consumed..]; @@ -634,12 +589,11 @@ impl<'d, T: Instance> Twim<'d, T> { &mut self, address: u8, mut operations: &mut [Operation<'_>], - stop: bool, timeout: Duration, ) -> Result<(), Error> { let mut tx_ram_buffer = [MaybeUninit::uninit(); FORCE_COPY_BUFFER_SIZE]; while !operations.is_empty() { - self.setup_operations(address, operations, Some(&mut tx_ram_buffer), false, stop)?; + self.setup_operations(address, operations, Some(&mut tx_ram_buffer), false)?; self.blocking_wait_timeout(timeout)?; let consumed = self.check_operations(operations)?; operations = &mut operations[consumed..]; @@ -653,11 +607,10 @@ impl<'d, T: Instance> Twim<'d, T> { &mut self, address: u8, mut operations: &mut [Operation<'_>], - stop: bool, timeout: Duration, ) -> Result<(), Error> { while !operations.is_empty() { - self.setup_operations(address, operations, None, false, stop)?; + self.setup_operations(address, operations, None, false)?; self.blocking_wait_timeout(timeout)?; let consumed = self.check_operations(operations)?; operations = &mut operations[consumed..]; @@ -670,26 +623,17 @@ impl<'d, T: Instance> Twim<'d, T> { /// Each buffer must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. /// - /// If `stop` is set, the transaction will be terminated with a STOP - /// condition and the Twim will be stopped. Otherwise, the bus will be - /// left busy via clock stretching and Twim will be suspended. - /// - /// The nrf52832, nrf5340, and nrf9120 do not have hardware support for - /// suspending following a read operation therefore it is emulated by the - /// interrupt handler. If the latency of servicing that interrupt is - /// longer than a byte worth of clocks on the bus, the SCL clock will - /// continue to run for one or more additional bytes. This applies to - /// consecutive read operations, certain write-read-write sequences, or - /// any sequence of operations ending in a read when `stop == false`. - pub async fn transaction( - &mut self, - address: u8, - mut operations: &mut [Operation<'_>], - stop: bool, - ) -> Result<(), Error> { + /// Consecutive `Read` operations are not supported because the Twim + /// hardware does not support suspending after a read operation. (Setting + /// the SUSPEND task in response to a LASTRX event causes the final byte of + /// the operation to be ACKed instead of NAKed. When the TWIM is resumed, + /// one more byte will be read before the new operation is started, leading + /// to an Overrun error if the RXD has not been updated, or an extraneous + /// byte read into the new buffer if the RXD has been updated.) + pub async fn transaction(&mut self, address: u8, mut operations: &mut [Operation<'_>]) -> Result<(), Error> { let mut tx_ram_buffer = [MaybeUninit::uninit(); FORCE_COPY_BUFFER_SIZE]; while !operations.is_empty() { - self.setup_operations(address, operations, Some(&mut tx_ram_buffer), true, stop)?; + self.setup_operations(address, operations, Some(&mut tx_ram_buffer), true)?; self.async_wait().await; let consumed = self.check_operations(operations)?; operations = &mut operations[consumed..]; @@ -702,10 +646,9 @@ impl<'d, T: Instance> Twim<'d, T> { &mut self, address: u8, mut operations: &mut [Operation<'_>], - stop: bool, ) -> Result<(), Error> { while !operations.is_empty() { - self.setup_operations(address, operations, None, true, stop)?; + self.setup_operations(address, operations, None, true)?; self.async_wait().await; let consumed = self.check_operations(operations)?; operations = &mut operations[consumed..]; @@ -720,12 +663,12 @@ impl<'d, T: Instance> Twim<'d, T> { /// The buffer must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. pub fn blocking_write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { - self.blocking_transaction(address, &mut [Operation::Write(buffer)], true) + self.blocking_transaction(address, &mut [Operation::Write(buffer)]) } /// Same as [`blocking_write`](Twim::blocking_write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. pub fn blocking_write_from_ram(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { - self.blocking_transaction_from_ram(address, &mut [Operation::Write(buffer)], true) + self.blocking_transaction_from_ram(address, &mut [Operation::Write(buffer)]) } /// Read from an I2C slave. @@ -733,7 +676,7 @@ impl<'d, T: Instance> Twim<'d, T> { /// The buffer must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. pub fn blocking_read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { - self.blocking_transaction(address, &mut [Operation::Read(buffer)], true) + self.blocking_transaction(address, &mut [Operation::Read(buffer)]) } /// Write data to an I2C slave, then read data from the slave without @@ -742,11 +685,7 @@ impl<'d, T: Instance> Twim<'d, T> { /// The buffers must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. pub fn blocking_write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Error> { - self.blocking_transaction( - address, - &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)], - true, - ) + self.blocking_transaction(address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)]) } /// Same as [`blocking_write_read`](Twim::blocking_write_read) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. @@ -756,11 +695,7 @@ impl<'d, T: Instance> Twim<'d, T> { wr_buffer: &[u8], rd_buffer: &mut [u8], ) -> Result<(), Error> { - self.blocking_transaction_from_ram( - address, - &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)], - true, - ) + self.blocking_transaction_from_ram(address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)]) } // =========================================== @@ -770,7 +705,7 @@ impl<'d, T: Instance> Twim<'d, T> { /// See [`blocking_write`]. #[cfg(feature = "time")] pub fn blocking_write_timeout(&mut self, address: u8, buffer: &[u8], timeout: Duration) -> Result<(), Error> { - self.blocking_transaction_timeout(address, &mut [Operation::Write(buffer)], true, timeout) + self.blocking_transaction_timeout(address, &mut [Operation::Write(buffer)], timeout) } /// Same as [`blocking_write`](Twim::blocking_write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. @@ -781,7 +716,7 @@ impl<'d, T: Instance> Twim<'d, T> { buffer: &[u8], timeout: Duration, ) -> Result<(), Error> { - self.blocking_transaction_from_ram_timeout(address, &mut [Operation::Write(buffer)], true, timeout) + self.blocking_transaction_from_ram_timeout(address, &mut [Operation::Write(buffer)], timeout) } /// Read from an I2C slave. @@ -790,7 +725,7 @@ impl<'d, T: Instance> Twim<'d, T> { /// and at most 65535 bytes on the nRF52840. #[cfg(feature = "time")] pub fn blocking_read_timeout(&mut self, address: u8, buffer: &mut [u8], timeout: Duration) -> Result<(), Error> { - self.blocking_transaction_timeout(address, &mut [Operation::Read(buffer)], true, timeout) + self.blocking_transaction_timeout(address, &mut [Operation::Read(buffer)], timeout) } /// Write data to an I2C slave, then read data from the slave without @@ -809,7 +744,6 @@ impl<'d, T: Instance> Twim<'d, T> { self.blocking_transaction_timeout( address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)], - true, timeout, ) } @@ -826,7 +760,6 @@ impl<'d, T: Instance> Twim<'d, T> { self.blocking_transaction_from_ram_timeout( address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)], - true, timeout, ) } @@ -838,7 +771,7 @@ impl<'d, T: Instance> Twim<'d, T> { /// The buffer must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { - self.transaction(address, &mut [Operation::Read(buffer)], true).await + self.transaction(address, &mut [Operation::Read(buffer)]).await } /// Write to an I2C slave. @@ -846,12 +779,12 @@ impl<'d, T: Instance> Twim<'d, T> { /// The buffer must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. pub async fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { - self.transaction(address, &mut [Operation::Write(buffer)], true).await + self.transaction(address, &mut [Operation::Write(buffer)]).await } /// Same as [`write`](Twim::write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. pub async fn write_from_ram(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { - self.transaction_from_ram(address, &mut [Operation::Write(buffer)], true) + self.transaction_from_ram(address, &mut [Operation::Write(buffer)]) .await } @@ -861,12 +794,8 @@ impl<'d, T: Instance> Twim<'d, T> { /// The buffers must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. pub async fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Error> { - self.transaction( - address, - &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)], - true, - ) - .await + self.transaction(address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)]) + .await } /// Same as [`write_read`](Twim::write_read) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. @@ -876,12 +805,8 @@ impl<'d, T: Instance> Twim<'d, T> { wr_buffer: &[u8], rd_buffer: &mut [u8], ) -> Result<(), Error> { - self.transaction_from_ram( - address, - &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)], - true, - ) - .await + self.transaction_from_ram(address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)]) + .await } } @@ -999,13 +924,13 @@ impl<'d, T: Instance> embedded_hal_1::i2c::ErrorType for Twim<'d, T> { impl<'d, T: Instance> embedded_hal_1::i2c::I2c for Twim<'d, T> { fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> { - self.blocking_transaction(address, operations, true) + self.blocking_transaction(address, operations) } } impl<'d, T: Instance> embedded_hal_async::i2c::I2c for Twim<'d, T> { async fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> { - self.transaction(address, operations, true).await + self.transaction(address, operations).await } } From 55805de05b4be39546c0b4c5e2ebc97cd3b1f75c Mon Sep 17 00:00:00 2001 From: Alex Moon Date: Wed, 21 Aug 2024 10:57:14 -0400 Subject: [PATCH 120/144] Refactoring and cleanup --- embassy-nrf/src/twim.rs | 183 +++++++++++++++++++++------------------- 1 file changed, 96 insertions(+), 87 deletions(-) diff --git a/embassy-nrf/src/twim.rs b/embassy-nrf/src/twim.rs index 4a239e8b5..187fce021 100644 --- a/embassy-nrf/src/twim.rs +++ b/embassy-nrf/src/twim.rs @@ -311,6 +311,7 @@ impl<'d, T: Instance> Twim<'d, T> { let r = T::regs(); loop { if r.events_suspended.read().bits() != 0 || r.events_stopped.read().bits() != 0 { + r.events_suspended.reset(); r.events_stopped.reset(); break; } @@ -372,15 +373,15 @@ impl<'d, T: Instance> Twim<'d, T> { address: u8, operations: &mut [Operation<'_>], tx_ram_buffer: Option<&mut [MaybeUninit; FORCE_COPY_BUFFER_SIZE]>, + last_op: Option<&Operation<'_>>, inten: bool, - ) -> Result<(), Error> { + ) -> Result { let r = T::regs(); compiler_fence(SeqCst); r.address.write(|w| unsafe { w.address().bits(address) }); - let was_suspended = r.events_suspended.read().bits() != 0; r.events_suspended.reset(); r.events_stopped.reset(); r.events_error.reset(); @@ -393,10 +394,12 @@ impl<'d, T: Instance> Twim<'d, T> { .write(|w| w.suspended().clear().stopped().clear().error().clear()); } + assert!(!operations.is_empty()); match operations { - [Operation::Read(rd_buffer), Operation::Write(wr_buffer), rest @ ..] - if !rd_buffer.is_empty() && !wr_buffer.is_empty() => - { + [Operation::Read(_), Operation::Read(_), ..] => { + panic!("Consecutive read operations are not supported!") + } + [Operation::Read(rd_buffer), Operation::Write(wr_buffer), rest @ ..] => { let stop = rest.is_empty(); // Set up DMA buffers. @@ -417,10 +420,38 @@ impl<'d, T: Instance> Twim<'d, T> { // Start read+write operation. r.tasks_startrx.write(|w| unsafe { w.bits(1) }); - - if was_suspended { + if last_op.is_some() { r.tasks_resume.write(|w| unsafe { w.bits(1) }); } + + // TODO: Handle empty write buffer + if rd_buffer.is_empty() { + // With a zero-length buffer, LASTRX doesn't fire (because there's no last byte!), so do the STARTTX ourselves. + r.tasks_starttx.write(|w| unsafe { w.bits(1) }); + } + + Ok(2) + } + [Operation::Read(buffer)] => { + // Set up DMA buffers. + unsafe { + self.set_rx_buffer(buffer)?; + } + + r.shorts.write(|w| w.lastrx_stop().enabled()); + + // Start read operation. + r.tasks_startrx.write(|w| unsafe { w.bits(1) }); + if last_op.is_some() { + r.tasks_resume.write(|w| unsafe { w.bits(1) }); + } + + if buffer.is_empty() { + // With a zero-length buffer, LASTRX doesn't fire (because there's no last byte!), so do the STOP ourselves. + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + } + + Ok(1) } [Operation::Write(wr_buffer), Operation::Read(rd_buffer)] if !wr_buffer.is_empty() && !rd_buffer.is_empty() => @@ -439,36 +470,11 @@ impl<'d, T: Instance> Twim<'d, T> { }); r.tasks_starttx.write(|w| unsafe { w.bits(1) }); - - if was_suspended { - r.tasks_resume.write(|w| unsafe { w.bits(1) }); - } - } - [Operation::Read(buffer)] => { - // Set up DMA buffers. - unsafe { - self.set_rx_buffer(buffer)?; - } - - // Start read operation. - r.shorts.write(|w| { - w.lastrx_stop().enabled(); - w - }); - - r.tasks_startrx.write(|w| unsafe { w.bits(1) }); - - if was_suspended { + if last_op.is_some() { r.tasks_resume.write(|w| unsafe { w.bits(1) }); } - if buffer.is_empty() { - // With a zero-length buffer, LASTRX doesn't fire (because there's no last byte!), so do the STOP ourselves. - r.tasks_stop.write(|w| unsafe { w.bits(1) }); - } - } - [Operation::Read(_), ..] => { - panic!("Suspending after a read is not supported!"); + Ok(2) } [Operation::Write(buffer), rest @ ..] => { let stop = rest.is_empty(); @@ -489,8 +495,7 @@ impl<'d, T: Instance> Twim<'d, T> { }); r.tasks_starttx.write(|w| unsafe { w.bits(1) }); - - if was_suspended { + if last_op.is_some() { r.tasks_resume.write(|w| unsafe { w.bits(1) }); } @@ -502,43 +507,33 @@ impl<'d, T: Instance> Twim<'d, T> { r.tasks_suspend.write(|w| unsafe { w.bits(1) }); } } - } - [] => { - if was_suspended { - r.tasks_resume.write(|w| unsafe { w.bits(1) }); - } - r.tasks_stop.write(|w| unsafe { w.bits(1) }); + Ok(1) } + [] => unreachable!(), } - - Ok(()) } - fn check_operations(&mut self, operations: &mut [Operation<'_>]) -> Result { + fn check_operations(&mut self, operations: &[Operation<'_>]) -> Result<(), Error> { compiler_fence(SeqCst); self.check_errorsrc()?; + assert!(operations.len() == 1 || operations.len() == 2); match operations { - [Operation::Read(rd_buffer), Operation::Write(wr_buffer), ..] - | [Operation::Write(wr_buffer), Operation::Read(rd_buffer)] - if !rd_buffer.is_empty() && !wr_buffer.is_empty() => - { - self.check_tx(wr_buffer.len())?; + [Operation::Read(rd_buffer), Operation::Write(wr_buffer)] + | [Operation::Write(wr_buffer), Operation::Read(rd_buffer)] => { self.check_rx(rd_buffer.len())?; - Ok(2) + self.check_tx(wr_buffer.len())?; } [Operation::Read(buffer)] => { self.check_rx(buffer.len())?; - Ok(1) } - [Operation::Read(_), ..] => unreachable!(), [Operation::Write(buffer), ..] => { self.check_tx(buffer.len())?; - Ok(1) } - [] => Ok(0), + _ => unreachable!(), } + Ok(()) } // =========================================== @@ -548,20 +543,21 @@ impl<'d, T: Instance> Twim<'d, T> { /// Each buffer must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. /// - /// Consecutive `Read` operations are not supported because the Twim - /// hardware does not support suspending after a read operation. (Setting - /// the SUSPEND task in response to a LASTRX event causes the final byte of - /// the operation to be ACKed instead of NAKed. When the TWIM is resumed, - /// one more byte will be read before the new operation is started, leading - /// to an Overrun error if the RXD has not been updated, or an extraneous - /// byte read into the new buffer if the RXD has been updated.) + /// Consecutive `Operation::Read`s are not supported due to hardware + /// limitations. + /// + /// An `Operation::Write` following an `Operation::Read` must have a + /// non-empty buffer. pub fn blocking_transaction(&mut self, address: u8, mut operations: &mut [Operation<'_>]) -> Result<(), Error> { let mut tx_ram_buffer = [MaybeUninit::uninit(); FORCE_COPY_BUFFER_SIZE]; + let mut last_op = None; while !operations.is_empty() { - self.setup_operations(address, operations, Some(&mut tx_ram_buffer), false)?; + let ops = self.setup_operations(address, operations, Some(&mut tx_ram_buffer), last_op, false)?; + let (in_progress, rest) = operations.split_at_mut(ops); self.blocking_wait(); - let consumed = self.check_operations(operations)?; - operations = &mut operations[consumed..]; + self.check_operations(in_progress)?; + last_op = in_progress.last(); + operations = rest; } Ok(()) } @@ -572,11 +568,14 @@ impl<'d, T: Instance> Twim<'d, T> { address: u8, mut operations: &mut [Operation<'_>], ) -> Result<(), Error> { + let mut last_op = None; while !operations.is_empty() { - self.setup_operations(address, operations, None, false)?; + let ops = self.setup_operations(address, operations, None, last_op, false)?; + let (in_progress, rest) = operations.split_at_mut(ops); self.blocking_wait(); - let consumed = self.check_operations(operations)?; - operations = &mut operations[consumed..]; + self.check_operations(in_progress)?; + last_op = in_progress.last(); + operations = rest; } Ok(()) } @@ -592,11 +591,14 @@ impl<'d, T: Instance> Twim<'d, T> { timeout: Duration, ) -> Result<(), Error> { let mut tx_ram_buffer = [MaybeUninit::uninit(); FORCE_COPY_BUFFER_SIZE]; + let mut last_op = None; while !operations.is_empty() { - self.setup_operations(address, operations, Some(&mut tx_ram_buffer), false)?; + let ops = self.setup_operations(address, operations, Some(&mut tx_ram_buffer), last_op, false)?; + let (in_progress, rest) = operations.split_at_mut(ops); self.blocking_wait_timeout(timeout)?; - let consumed = self.check_operations(operations)?; - operations = &mut operations[consumed..]; + self.check_operations(in_progress)?; + last_op = in_progress.last(); + operations = rest; } Ok(()) } @@ -609,11 +611,14 @@ impl<'d, T: Instance> Twim<'d, T> { mut operations: &mut [Operation<'_>], timeout: Duration, ) -> Result<(), Error> { + let mut last_op = None; while !operations.is_empty() { - self.setup_operations(address, operations, None, false)?; + let ops = self.setup_operations(address, operations, None, last_op, false)?; + let (in_progress, rest) = operations.split_at_mut(ops); self.blocking_wait_timeout(timeout)?; - let consumed = self.check_operations(operations)?; - operations = &mut operations[consumed..]; + self.check_operations(in_progress)?; + last_op = in_progress.last(); + operations = rest; } Ok(()) } @@ -623,20 +628,21 @@ impl<'d, T: Instance> Twim<'d, T> { /// Each buffer must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. /// - /// Consecutive `Read` operations are not supported because the Twim - /// hardware does not support suspending after a read operation. (Setting - /// the SUSPEND task in response to a LASTRX event causes the final byte of - /// the operation to be ACKed instead of NAKed. When the TWIM is resumed, - /// one more byte will be read before the new operation is started, leading - /// to an Overrun error if the RXD has not been updated, or an extraneous - /// byte read into the new buffer if the RXD has been updated.) + /// Consecutive `Operation::Read`s are not supported due to hardware + /// limitations. + /// + /// An `Operation::Write` following an `Operation::Read` must have a + /// non-empty buffer. pub async fn transaction(&mut self, address: u8, mut operations: &mut [Operation<'_>]) -> Result<(), Error> { let mut tx_ram_buffer = [MaybeUninit::uninit(); FORCE_COPY_BUFFER_SIZE]; + let mut last_op = None; while !operations.is_empty() { - self.setup_operations(address, operations, Some(&mut tx_ram_buffer), true)?; + let ops = self.setup_operations(address, operations, Some(&mut tx_ram_buffer), last_op, true)?; + let (in_progress, rest) = operations.split_at_mut(ops); self.async_wait().await; - let consumed = self.check_operations(operations)?; - operations = &mut operations[consumed..]; + self.check_operations(in_progress)?; + last_op = in_progress.last(); + operations = rest; } Ok(()) } @@ -647,11 +653,14 @@ impl<'d, T: Instance> Twim<'d, T> { address: u8, mut operations: &mut [Operation<'_>], ) -> Result<(), Error> { + let mut last_op = None; while !operations.is_empty() { - self.setup_operations(address, operations, None, true)?; + let ops = self.setup_operations(address, operations, None, last_op, true)?; + let (in_progress, rest) = operations.split_at_mut(ops); self.async_wait().await; - let consumed = self.check_operations(operations)?; - operations = &mut operations[consumed..]; + self.check_operations(in_progress)?; + last_op = in_progress.last(); + operations = rest; } Ok(()) } From 1fed8ac5dbb239e53cdc51b10c27a0c58ea92aeb Mon Sep 17 00:00:00 2001 From: Vincenzo Marturano Date: Thu, 24 Oct 2024 15:12:04 +0200 Subject: [PATCH 121/144] Allow separate control of duty cycle for each channel in a pwm slice by splitting the Pwm driver. --- embassy-rp/src/pwm.rs | 52 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs index cfb99c569..4f11eb8f7 100644 --- a/embassy-rp/src/pwm.rs +++ b/embassy-rp/src/pwm.rs @@ -344,6 +344,58 @@ impl<'d> Pwm<'d> { fn bit(&self) -> u32 { 1 << self.slice as usize } + + #[inline] + /// Split Pwm driver to allow separate duty cycle control of each channel + pub fn split(&self) -> (PwmOutput,PwmOutput){ + (PwmOutput::new(PwmChannel::A,self.slice.clone()),PwmOutput::new(PwmChannel::B,self.slice.clone())) + } +} + +enum PwmChannel{ + A, + B +} + +/// Single channel of Pwm driver. +pub struct PwmOutput { + channel: PwmChannel, + slice: usize +} + +impl PwmOutput { + fn new(channel: PwmChannel,slice: usize) -> Self{ + Self { channel, slice } + } +} + +impl SetDutyCycle for PwmOutput { + fn max_duty_cycle(&self) -> u16 { + pac::PWM.ch(self.slice).top().read().top() + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + let max_duty = self.max_duty_cycle(); + if duty > max_duty { + return Err(PwmError::InvalidDutyCycle); + } + + let p = pac::PWM.ch(self.slice); + match self.channel { + PwmChannel::A => { + p.cc().modify(|w| { + w.set_a(duty); + }); + }, + PwmChannel::B => { + p.cc().modify(|w| { + w.set_b(duty); + }); + }, + } + + Ok(()) + } } /// Batch representation of PWM slices. From 2596de52bba32b0d1e755ab18909c10a9e663aad Mon Sep 17 00:00:00 2001 From: Vincenzo Marturano Date: Thu, 24 Oct 2024 15:39:22 +0200 Subject: [PATCH 122/144] Fixed missing trait implementation for PwmOutput. --- embassy-rp/src/pwm.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs index 4f11eb8f7..66f0dc7f2 100644 --- a/embassy-rp/src/pwm.rs +++ b/embassy-rp/src/pwm.rs @@ -369,6 +369,10 @@ impl PwmOutput { } } +impl ErrorType for PwmOutput { + type Error = PwmError; +} + impl SetDutyCycle for PwmOutput { fn max_duty_cycle(&self) -> u16 { pac::PWM.ch(self.slice).top().read().top() From 31662eaeef0762a7c7b9c95aee61a9066d7e447a Mon Sep 17 00:00:00 2001 From: Vincenzo Marturano Date: Thu, 24 Oct 2024 16:04:32 +0200 Subject: [PATCH 123/144] Add new() method to PwmBatch so it can be istantiated. --- embassy-rp/src/pwm.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs index 66f0dc7f2..4da5e5e28 100644 --- a/embassy-rp/src/pwm.rs +++ b/embassy-rp/src/pwm.rs @@ -406,6 +406,10 @@ impl SetDutyCycle for PwmOutput { pub struct PwmBatch(u32); impl PwmBatch { + #[inline] + pub fn new() -> Self{ + Self(0) + } #[inline] /// Enable a PWM slice in this batch. pub fn enable(&mut self, pwm: &Pwm<'_>) { From 052463212b6b6c1647f517ce38272dd4dd3d4353 Mon Sep 17 00:00:00 2001 From: Vincenzo Marturano Date: Thu, 24 Oct 2024 16:20:28 +0200 Subject: [PATCH 124/144] Revert "Add new() method to PwmBatch so it can be istantiated." This reverts commit 31662eaeef0762a7c7b9c95aee61a9066d7e447a. --- embassy-rp/src/pwm.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs index 4da5e5e28..66f0dc7f2 100644 --- a/embassy-rp/src/pwm.rs +++ b/embassy-rp/src/pwm.rs @@ -406,10 +406,6 @@ impl SetDutyCycle for PwmOutput { pub struct PwmBatch(u32); impl PwmBatch { - #[inline] - pub fn new() -> Self{ - Self(0) - } #[inline] /// Enable a PWM slice in this batch. pub fn enable(&mut self, pwm: &Pwm<'_>) { From 336ef01b05e87e6b2b3c9b98e9bdca0c7d78a6af Mon Sep 17 00:00:00 2001 From: Vincenzo Marturano Date: Thu, 24 Oct 2024 19:36:54 +0200 Subject: [PATCH 125/144] Implemented owned split. --- embassy-rp/src/pwm.rs | 47 ++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs index 66f0dc7f2..5fea0901a 100644 --- a/embassy-rp/src/pwm.rs +++ b/embassy-rp/src/pwm.rs @@ -347,25 +347,36 @@ impl<'d> Pwm<'d> { #[inline] /// Split Pwm driver to allow separate duty cycle control of each channel - pub fn split(&self) -> (PwmOutput,PwmOutput){ - (PwmOutput::new(PwmChannel::A,self.slice.clone()),PwmOutput::new(PwmChannel::B,self.slice.clone())) + pub fn split(self) -> (Option, Option) { + + let pwm_output_a = if let Some(pin_a) = self.pin_a { + Some(PwmOutput::new(PwmChannelPin::A(pin_a), self.slice.clone())) + }; + + let pwm_output_b = if let Some(pin_b) = self.pin_b { + Some(PwmOutput::new(PwmChannelPin::B(pin_b), self.slice.clone())) + }; + + (pwm_output_a,pwm_output_b) } + } -enum PwmChannel{ - A, - B +enum PwmChannelPin<'d> { + A(PeripheralRef<'d, AnyPin>), + B(PeripheralRef<'d, AnyPin>) } /// Single channel of Pwm driver. -pub struct PwmOutput { - channel: PwmChannel, - slice: usize +pub struct PwmOutput<'d> { + //pin that can be ether ChannelAPin or ChannelBPin + channel_pin: PwmChannelPin<'d> , + slice: usize, } -impl PwmOutput { - fn new(channel: PwmChannel,slice: usize) -> Self{ - Self { channel, slice } +impl <'d> PwmOutput<'d> { + fn new(channel_pin: PwmChannelPin<'d>, slice: usize) -> Self { + Self { channel_pin ,slice } } } @@ -373,7 +384,7 @@ impl ErrorType for PwmOutput { type Error = PwmError; } -impl SetDutyCycle for PwmOutput { +impl<'d> SetDutyCycle for PwmOutput<'d> { fn max_duty_cycle(&self) -> u16 { pac::PWM.ch(self.slice).top().read().top() } @@ -385,19 +396,19 @@ impl SetDutyCycle for PwmOutput { } let p = pac::PWM.ch(self.slice); - match self.channel { - PwmChannel::A => { + match self.channel_pin { + PwmChannelPin::A => { p.cc().modify(|w| { w.set_a(duty); }); - }, - PwmChannel::B => { + } + PwmChannelPin::B => { p.cc().modify(|w| { w.set_b(duty); }); - }, + } } - + Ok(()) } } From 354ff3bac31a910598a6e350499262b3163d50ef Mon Sep 17 00:00:00 2001 From: Vincenzo Marturano Date: Thu, 24 Oct 2024 19:46:23 +0200 Subject: [PATCH 126/144] Fix missing lifetime --- embassy-rp/src/pwm.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs index 5fea0901a..2df2cfad3 100644 --- a/embassy-rp/src/pwm.rs +++ b/embassy-rp/src/pwm.rs @@ -347,7 +347,7 @@ impl<'d> Pwm<'d> { #[inline] /// Split Pwm driver to allow separate duty cycle control of each channel - pub fn split(self) -> (Option, Option) { + pub fn split(self) -> (Option>, Option>) { let pwm_output_a = if let Some(pin_a) = self.pin_a { Some(PwmOutput::new(PwmChannelPin::A(pin_a), self.slice.clone())) @@ -380,7 +380,7 @@ impl <'d> PwmOutput<'d> { } } -impl ErrorType for PwmOutput { +impl<'d> ErrorType for PwmOutput<'d> { type Error = PwmError; } From 874dbec5a4d11c57c3683a27ac09584e728694c2 Mon Sep 17 00:00:00 2001 From: Vincenzo Marturano Date: Thu, 24 Oct 2024 19:52:09 +0200 Subject: [PATCH 127/144] Fixed mistakes. --- embassy-rp/src/pwm.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs index 2df2cfad3..443308158 100644 --- a/embassy-rp/src/pwm.rs +++ b/embassy-rp/src/pwm.rs @@ -351,10 +351,14 @@ impl<'d> Pwm<'d> { let pwm_output_a = if let Some(pin_a) = self.pin_a { Some(PwmOutput::new(PwmChannelPin::A(pin_a), self.slice.clone())) + }else{ + None }; let pwm_output_b = if let Some(pin_b) = self.pin_b { Some(PwmOutput::new(PwmChannelPin::B(pin_b), self.slice.clone())) + }else { + None }; (pwm_output_a,pwm_output_b) @@ -397,12 +401,12 @@ impl<'d> SetDutyCycle for PwmOutput<'d> { let p = pac::PWM.ch(self.slice); match self.channel_pin { - PwmChannelPin::A => { + PwmChannelPin::A(_) => { p.cc().modify(|w| { w.set_a(duty); }); } - PwmChannelPin::B => { + PwmChannelPin::B(_) => { p.cc().modify(|w| { w.set_b(duty); }); From 7b62d70d184ca9e8e7c6864d6ed39b4e8f75f02f Mon Sep 17 00:00:00 2001 From: Haobo Gu Date: Fri, 25 Oct 2024 18:27:48 +0800 Subject: [PATCH 128/144] feat(ospi): add memory mapped mode Signed-off-by: Haobo Gu --- embassy-stm32/src/ospi/mod.rs | 63 ++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/embassy-stm32/src/ospi/mod.rs b/embassy-stm32/src/ospi/mod.rs index 0574f7816..7ccafe361 100644 --- a/embassy-stm32/src/ospi/mod.rs +++ b/embassy-stm32/src/ospi/mod.rs @@ -179,39 +179,72 @@ pub struct Ospi<'d, T: Instance, M: PeriMode> { } impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { - pub fn enable_memory_mapped_mode(&mut self) { + /// Enter memory mode. + /// The Input `TransferConfig` is used to configure the read operation in memory mode + pub fn enable_memory_mapped_mode( + &mut self, + read_config: TransferConfig, + write_config: TransferConfig, + ) -> Result<(), OspiError> { + // Use configure command to set read config + self.configure_command(&read_config, None)?; + let reg = T::REGS; while reg.sr().read().busy() { info!("wait ospi busy"); } - + reg.ccr().modify(|r| { - r.set_isize(crate::ospi::vals::SizeInBits::_8BIT); - r.set_adsize(crate::ospi::vals::SizeInBits::_24BIT); - r.set_admode(crate::ospi::vals::PhaseMode::ONELINE); - r.set_imode(crate::ospi::vals::PhaseMode::ONELINE); - r.set_dmode(crate::ospi::vals::PhaseMode::FOURLINES); + r.set_dqse(false); + r.set_sioo(true); }); - + + // Set wrting configurations, there are separate registers for write configurations in memory mapped mode + reg.wccr().modify(|w| { + w.set_imode(PhaseMode::from_bits(write_config.iwidth.into())); + w.set_idtr(write_config.idtr); + w.set_isize(SizeInBits::from_bits(write_config.isize.into())); + + w.set_admode(PhaseMode::from_bits(write_config.adwidth.into())); + w.set_addtr(write_config.idtr); + w.set_adsize(SizeInBits::from_bits(write_config.adsize.into())); + + w.set_dmode(PhaseMode::from_bits(write_config.dwidth.into())); + w.set_ddtr(write_config.ddtr); + + w.set_abmode(PhaseMode::from_bits(write_config.abwidth.into())); + w.set_dqse(true); + }); + + reg.wtcr().modify(|w| w.set_dcyc(write_config.dummy.into())); + + // Enable memory mapped mode reg.cr().modify(|r| { r.set_fmode(crate::ospi::vals::FunctionalMode::MEMORYMAPPED); - r.set_dmaen(false); - r.set_en(true); + r.set_tcen(false); }); - - // reg.tcr().modify(|r| { - // r.set_dcyc(6); - // }); - + Ok(()) } + + /// Quit from memory mapped mode pub fn disable_memory_mapped_mode(&mut self) { let reg = T::REGS; while reg.sr().read().busy() { info!("wait ospi busy"); } + reg.cr().modify(|r| { r.set_fmode(crate::ospi::vals::FunctionalMode::INDIRECTWRITE); + r.set_abort(true); r.set_dmaen(false); + r.set_en(false); + }); + + // Clear transfer complete flag + reg.fcr().write(|w| w.set_ctcf(true)); + + // Re-enable ospi + reg.cr().modify(|r| { r.set_en(true); }); } From 71fe8a7b90abc7b625d0f5b822508b350f3444d2 Mon Sep 17 00:00:00 2001 From: Vincenzo Marturano Date: Fri, 25 Oct 2024 12:54:06 +0200 Subject: [PATCH 129/144] Fixed owned split and implemented split_by_ref. --- embassy-rp/src/pwm.rs | 80 ++++++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 20 deletions(-) diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs index 443308158..18d476ed4 100644 --- a/embassy-rp/src/pwm.rs +++ b/embassy-rp/src/pwm.rs @@ -347,40 +347,80 @@ impl<'d> Pwm<'d> { #[inline] /// Split Pwm driver to allow separate duty cycle control of each channel - pub fn split(self) -> (Option>, Option>) { - - let pwm_output_a = if let Some(pin_a) = self.pin_a { - Some(PwmOutput::new(PwmChannelPin::A(pin_a), self.slice.clone())) - }else{ - None - }; - - let pwm_output_b = if let Some(pin_b) = self.pin_b { - Some(PwmOutput::new(PwmChannelPin::B(pin_b), self.slice.clone())) - }else { - None - }; - - (pwm_output_a,pwm_output_b) + pub fn split(mut self) -> (Option>, Option>) { + ( + self.pin_a + .take() + .map(|pin| PwmOutput::new(PwmChannelPin::A(pin), self.slice.clone(), true)), + self.pin_b + .take() + .map(|pin| PwmOutput::new(PwmChannelPin::B(pin), self.slice.clone(), true)), + ) } + pub fn split_by_ref(&mut self) -> (Option>, Option>) { + ( + self.pin_a + .as_mut() + .map(|pin| PwmOutput::new(PwmChannelPin::A(pin.reborrow()), self.slice.clone(), false)), + self.pin_b + .as_mut() + .map(|pin| PwmOutput::new(PwmChannelPin::B(pin.reborrow()), self.slice.clone(), false)), + ) + } } enum PwmChannelPin<'d> { A(PeripheralRef<'d, AnyPin>), - B(PeripheralRef<'d, AnyPin>) + B(PeripheralRef<'d, AnyPin>), } /// Single channel of Pwm driver. pub struct PwmOutput<'d> { //pin that can be ether ChannelAPin or ChannelBPin - channel_pin: PwmChannelPin<'d> , + channel_pin: PwmChannelPin<'d>, slice: usize, + is_owned: bool, } -impl <'d> PwmOutput<'d> { - fn new(channel_pin: PwmChannelPin<'d>, slice: usize) -> Self { - Self { channel_pin ,slice } +impl<'d> PwmOutput<'d> { + fn new(channel_pin: PwmChannelPin<'d>, slice: usize, is_owned: bool) -> Self { + Self { + channel_pin, + slice, + is_owned, + } + } +} + +impl<'d> Drop for PwmOutput<'d> { + fn drop(&mut self) { + if self.is_owned { + let p = pac::PWM.ch(self.slice); + match &self.channel_pin { + PwmChannelPin::A(pin) => { + p.cc().modify(|w| { + w.set_a(0); + }); + + pin.gpio().ctrl().write(|w| w.set_funcsel(31)); + ///Enable pin PULL-DOWN + pin.pad_ctrl().modify(|w| { + w.set_pde(true); + }); + } + PwmChannelPin::B(pin) => { + p.cc().modify(|w| { + w.set_b(0); + }); + pin.gpio().ctrl().write(|w| w.set_funcsel(31)); + ///Enable pin PULL-DOWN + pin.pad_ctrl().modify(|w| { + w.set_pde(true); + }); + } + } + } } } From 9690bed5a6f9a37bc926c1e86585bc28eb667ebf Mon Sep 17 00:00:00 2001 From: Vincenzo Marturano Date: Fri, 25 Oct 2024 13:12:24 +0200 Subject: [PATCH 130/144] Fix documentation. --- embassy-rp/src/pwm.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs index 18d476ed4..4fb8ade12 100644 --- a/embassy-rp/src/pwm.rs +++ b/embassy-rp/src/pwm.rs @@ -345,8 +345,8 @@ impl<'d> Pwm<'d> { 1 << self.slice as usize } + /// Splits the PWM driver into separate `PwmOutput` instances for channels A and B. #[inline] - /// Split Pwm driver to allow separate duty cycle control of each channel pub fn split(mut self) -> (Option>, Option>) { ( self.pin_a @@ -357,7 +357,9 @@ impl<'d> Pwm<'d> { .map(|pin| PwmOutput::new(PwmChannelPin::B(pin), self.slice.clone(), true)), ) } - + /// Splits the PWM driver by reference to allow for separate duty cycle control + /// of each channel (A and B) without taking ownership of the PWM instance. + #[inline] pub fn split_by_ref(&mut self) -> (Option>, Option>) { ( self.pin_a @@ -404,7 +406,7 @@ impl<'d> Drop for PwmOutput<'d> { }); pin.gpio().ctrl().write(|w| w.set_funcsel(31)); - ///Enable pin PULL-DOWN + //Enable pin PULL-DOWN pin.pad_ctrl().modify(|w| { w.set_pde(true); }); @@ -414,7 +416,7 @@ impl<'d> Drop for PwmOutput<'d> { w.set_b(0); }); pin.gpio().ctrl().write(|w| w.set_funcsel(31)); - ///Enable pin PULL-DOWN + //Enable pin PULL-DOWN pin.pad_ctrl().modify(|w| { w.set_pde(true); }); From 45e7a7a55aa6b5b8d41d81949b75b1b4a154f9bd Mon Sep 17 00:00:00 2001 From: William <174336620+williams-one@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:03:26 +0200 Subject: [PATCH 131/144] Update CFBLR configuration As per section "43.7.23 LTDC layer x color frame buffer length register (LTDC_LxCFBLR)" of Reference manual for STM32U5 RM0456, CFBLL has to be set to the length of one pixel line plus 3 (instead of plus 7 as for H7) --- embassy-stm32/src/ltdc.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/embassy-stm32/src/ltdc.rs b/embassy-stm32/src/ltdc.rs index 4c5239971..e25c4f3fb 100644 --- a/embassy-stm32/src/ltdc.rs +++ b/embassy-stm32/src/ltdc.rs @@ -395,7 +395,10 @@ impl<'d, T: Instance> Ltdc<'d, T> { // framebuffer pitch and line length layer.cfblr().modify(|w| { w.set_cfbp(width * bytes_per_pixel); + #[cfg(not(stm32u5))] w.set_cfbll(width * bytes_per_pixel + 7); + #[cfg(stm32u5)] + w.set_cfbll(width * bytes_per_pixel + 3); }); // framebuffer line number From be5222421177b632cd120272e08156b64cc0b886 Mon Sep 17 00:00:00 2001 From: William <174336620+williams-one@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:40:18 +0200 Subject: [PATCH 132/144] Add LTDC example for STM32U5G9J-DK2 demo board --- examples/stm32u5/Cargo.toml | 2 + examples/stm32u5/src/bin/ferris.bmp | Bin 0 -> 6794 bytes examples/stm32u5/src/bin/ltdc.rs | 462 ++++++++++++++++++++++++++++ 3 files changed, 464 insertions(+) create mode 100644 examples/stm32u5/src/bin/ferris.bmp create mode 100644 examples/stm32u5/src/bin/ltdc.rs diff --git a/examples/stm32u5/Cargo.toml b/examples/stm32u5/Cargo.toml index ad7db4c32..f594ad71a 100644 --- a/examples/stm32u5/Cargo.toml +++ b/examples/stm32u5/Cargo.toml @@ -21,6 +21,8 @@ cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } +embedded-graphics = { version = "0.8.1" } +tinybmp = { version = "0.6.0" } micromath = "2.0.0" diff --git a/examples/stm32u5/src/bin/ferris.bmp b/examples/stm32u5/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/stm32u5/src/bin/ltdc.rs b/examples/stm32u5/src/bin/ltdc.rs new file mode 100644 index 000000000..5bf2cd67d --- /dev/null +++ b/examples/stm32u5/src/bin/ltdc.rs @@ -0,0 +1,462 @@ +#![no_std] +#![no_main] +#![macro_use] +#![allow(static_mut_refs)] + +/// This example was derived from examples\stm32h735\src\bin\ltdc.rs +/// It demonstrates the LTDC lcd display peripheral and was tested on an STM32U5G9J-DK2 demo board (embassy-stm32 feature "stm32u5g9zj" and probe-rs chip "STM32U5G9ZJTxQ") +/// +use bouncy_box::BouncyBox; +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +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; +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 _}; + +const DISPLAY_WIDTH: usize = 800; +const DISPLAY_HEIGHT: usize = 480; +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 +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]; + +bind_interrupts!(struct Irqs { + LTDC => ltdc::InterruptHandler; +}); + +const NUM_COLORS: usize = 256; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = rcc_setup::stm32u5g9zj_init(); + + // enable ICACHE + embassy_stm32::pac::ICACHE.cr().write(|w| { + w.set_en(true); + }); + + // blink the led on another task + let led = Output::new(p.PD2, Level::High, Speed::Low); + unwrap!(spawner.spawn(led_task(led))); + + // numbers from STM32U5G9J-DK2.ioc + const RK050HR18H_HSYNC: u16 = 5; // Horizontal synchronization + const RK050HR18H_HBP: u16 = 8; // Horizontal back porch + const RK050HR18H_HFP: u16 = 8; // Horizontal front porch + const RK050HR18H_VSYNC: u16 = 5; // Vertical synchronization + const RK050HR18H_VBP: u16 = 8; // Vertical back porch + const RK050HR18H_VFP: u16 = 8; // Vertical front porch + + // NOTE: all polarities have to be reversed with respect to the STM32U5G9J-DK2 CubeMX parametrization + let ltdc_config = LtdcConfiguration { + active_width: DISPLAY_WIDTH as _, + active_height: DISPLAY_HEIGHT as _, + h_back_porch: RK050HR18H_HBP, + h_front_porch: RK050HR18H_HFP, + v_back_porch: RK050HR18H_VBP, + v_front_porch: RK050HR18H_VFP, + h_sync: RK050HR18H_HSYNC, + v_sync: RK050HR18H_VSYNC, + h_sync_polarity: PolarityActive::ActiveHigh, + v_sync_polarity: PolarityActive::ActiveHigh, + data_enable_polarity: PolarityActive::ActiveHigh, + pixel_clock_polarity: PolarityEdge::RisingEdge, + }; + + info!("init ltdc"); + let mut ltdc_de = Output::new(p.PD6, Level::Low, Speed::High); + let mut ltdc_disp_ctrl = Output::new(p.PE4, Level::Low, Speed::High); + let mut ltdc_bl_ctrl = Output::new(p.PE6, Level::Low, Speed::High); + let mut ltdc = Ltdc::new_with_pins( + p.LTDC, // PERIPHERAL + Irqs, // IRQS + p.PD3, // CLK + p.PE0, // HSYNC + p.PD13, // VSYNC + p.PB9, // B0 + p.PB2, // B1 + p.PD14, // B2 + p.PD15, // B3 + p.PD0, // B4 + p.PD1, // B5 + p.PE7, // B6 + p.PE8, // B7 + p.PC8, // G0 + p.PC9, // G1 + p.PE9, // G2 + p.PE10, // G3 + p.PE11, // G4 + p.PE12, // G5 + p.PE13, // G6 + p.PE14, // G7 + p.PC6, // R0 + p.PC7, // R1 + p.PE15, // R2 + p.PD8, // R3 + p.PD9, // R4 + p.PD10, // R5 + p.PD11, // R6 + p.PD12, // R7 + ); + ltdc.init(<dc_config); + ltdc_de.set_low(); + ltdc_bl_ctrl.set_high(); + ltdc_disp_ctrl.set_high(); + + // 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.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( + 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; + use embassy_stm32::time::Hertz; + use embassy_stm32::{Config, Peripherals}; + + /// Sets up clocks for the stm32u5g9zj mcu + /// change this if you plan to use a different microcontroller + pub fn stm32u5g9zj_init() -> Peripherals { + // setup power and clocks for an STM32U5G9J-DK2 run from an external 16 Mhz external oscillator + let mut config = Config::default(); + config.rcc.hse = Some(rcc::Hse { + freq: Hertz(16_000_000), + mode: rcc::HseMode::Oscillator, + }); + config.rcc.pll1 = Some(rcc::Pll { + source: rcc::PllSource::HSE, + prediv: rcc::PllPreDiv::DIV1, + mul: rcc::PllMul::MUL10, + divp: None, + divq: None, + divr: Some(rcc::PllDiv::DIV1), + }); + config.rcc.sys = rcc::Sysclk::PLL1_R; // 160 Mhz + config.rcc.pll3 = Some(rcc::Pll { + source: rcc::PllSource::HSE, + prediv: rcc::PllPreDiv::DIV4, // PLL_M + mul: rcc::PllMul::MUL125, // PLL_N + divp: None, + divq: None, + divr: Some(rcc::PllDiv::DIV20), + }); + config.rcc.mux.ltdcsel = rcc::mux::Ltdcsel::PLL3_R; // 25 MHz + embassy_stm32::init(config) + } +} + +mod bouncy_box { + use embedded_graphics::geometry::Point; + use embedded_graphics::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 d1db7d90434e7cf8d82361818427942c19923726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Fri, 25 Oct 2024 18:53:59 +0200 Subject: [PATCH 133/144] Explain how to keep the executor awake --- docs/pages/faq.adoc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/pages/faq.adoc b/docs/pages/faq.adoc index 8eb947b5e..d4f49bfc1 100644 --- a/docs/pages/faq.adoc +++ b/docs/pages/faq.adoc @@ -372,3 +372,16 @@ Issues like these while implementing drivers often fall into one of the followin 3. Some kind of hardware errata, or some hardware misconfiguration like wrong clock speeds 4. Some issue with an interrupt handler, either enabling, disabling, or re-enabling of interrupts when necessary 5. Some kind of async issue, like not registering wakers fully before checking flags, or not registering or pending wakers at the right time + +== How can I prevent the thread-mode executor from going to sleep? == + +In some cases you might want to prevent the thread-mode executor from going to sleep, for example when doing so would result in current spikes that reduce analog performance. +As a workaround, you can spawn a task that yields in a loop, preventing the executor from going to sleep. Note that this may increase power consumption. + +[source,rust] +---- +#[embassy_executor::task] +async fn idle() { + loop { embassy_futures::yield_now().await; } +} +---- From 6545dfee6d370cd37701fbbbf85d46c1a8e34521 Mon Sep 17 00:00:00 2001 From: ckrenslehner Date: Fri, 25 Oct 2024 19:22:20 +0200 Subject: [PATCH 134/144] Update overview.adoc --- docs/pages/overview.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pages/overview.adoc b/docs/pages/overview.adoc index 2ebc85f6d..8f97d937f 100644 --- a/docs/pages/overview.adoc +++ b/docs/pages/overview.adoc @@ -52,7 +52,7 @@ link:https://github.com/embassy-rs/embassy/tree/main/embassy-boot[embassy-boot] == What is DMA? -For most I/O in embedded devices, the peripheral doesn't directly support the transmission of multiple bits at once, with CAN being a notable exception. Instead, the MCU must write each byte, one at a time, and then wait until the peripheral is ready to send the next. For high I/O rates, this can pose a problem if the MCU must devote an increasing portion of its time handling each byte. The solution to this problem is to use the Direct Memory Access controller. +For most I/O in embedded devices, the peripheral doesn't directly support the transmission of multiple bytes at once, with CAN being a notable exception. Instead, the MCU must write each byte, one at a time, and then wait until the peripheral is ready to send the next. For high I/O rates, this can pose a problem if the MCU must devote an increasing portion of its time handling each byte. The solution to this problem is to use the Direct Memory Access controller. The Direct Memory Access controller (DMA) is a controller that is present in MCUs that Embassy supports, including stm32 and nrf. The DMA allows the MCU to set up a transfer, either send or receive, and then wait for the transfer to complete. With DMA, once started, no MCU intervention is required until the transfer is complete, meaning that the MCU can perform other computation, or set up other I/O while the transfer is in progress. For high I/O rates, DMA can cut the time that the MCU spends handling I/O by over half. However, because DMA is more complex to set-up, it is less widely used in the embedded community. Embassy aims to change that by making DMA the first choice rather than the last. Using Embassy, there's no additional tuning required once I/O rates increase because your application is already set-up to handle them. @@ -80,4 +80,4 @@ For more reading material on async Rust and Embassy: Videos: -* link:https://www.youtube.com/watch?v=wni5h5vIPhU[From Zero to Async in Embedded Rust] \ No newline at end of file +* link:https://www.youtube.com/watch?v=wni5h5vIPhU[From Zero to Async in Embedded Rust] From 2c05ac5262eb2beaa043818f02d82b656f272b4f Mon Sep 17 00:00:00 2001 From: ckrenslehner Date: Fri, 25 Oct 2024 20:36:46 +0200 Subject: [PATCH 135/144] Update embassy Cargo.toml section in new_project.adoc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Embassy is already published to crates.io šŸ¾ --- docs/pages/new_project.adoc | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/pages/new_project.adoc b/docs/pages/new_project.adoc index 821bcbd27..fa4c2e359 100644 --- a/docs/pages/new_project.adoc +++ b/docs/pages/new_project.adoc @@ -73,16 +73,28 @@ Now that cargo knows what target to compile for (and probe-rs knows what chip to Looking in `examples/stm32g4/Cargo.toml`, we can see that the examples require a number of embassy crates. For blinky, weā€™ll only need three of them: `embassy-stm32`, `embassy-executor` and `embassy-time`. -At the time of writing, the latest version of embassy isnā€˜t available on crates.io, so we need to install it straight from the git repository. The recommended way of doing so is as follows: + +At the time of writing, embassy is already published to crates.io. Therefore, dependencies can easily added via Cargo.toml. + +[source,toml] +---- +[dependencies] +embassy-stm32 = { version = "0.1.0", features = ["defmt", "time-driver-any", "stm32g474re", "memory-x", "unstable-pac", "exti"] } +embassy-executor = { version = "0.6.1", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +---- + +Prior embassy needed to be installed straight from the git repository. This can is still useful, if you want to checkout a specic revision of an embassy crate which is not yet published. +The recommended way of doing so is as follows: * Copy the required `embassy-*` lines from the example `Cargo.toml` * Make any necessary changes to `features`, e.g. requiring the `stm32g474re` feature of `embassy-stm32` * Remove the `path = ""` keys in the `embassy-*` entries * Create a `[patch.crates-io]` section, with entries for each embassy crate we need. These should all contain identical values: a link to the git repository, and a reference to the commit weā€™re checking out. Assuming you want the latest commit, you can find it by running `git ls-remote https://github.com/embassy-rs/embassy.git HEAD` -NOTE: When using this method, itā€™s necessary that the `version` keys in `[dependencies]` match up with the versions defined in each crateā€™s `Cargo.toml` in the specificed `rev` under `[patch.crates.io]`. This means that when updating, you have to a pick a new revision, change everything in `[patch.crates.io]` to match it, and then correct any versions under `[dependencies]` which have changed. Hopefully this will no longer be necessary once embassy is released on crates.io! +NOTE: When using this method, itā€™s necessary that the `version` keys in `[dependencies]` match up with the versions defined in each crateā€™s `Cargo.toml` in the specificed `rev` under `[patch.crates.io]`. This means that when updating, you have to a pick a new revision, change everything in `[patch.crates.io]` to match it, and then correct any versions under `[dependencies]` which have changed. -At the time of writing, this method produces the following results: +An example Cargo.toml file might look as follows: [source,toml] ---- From 3d0921ffc4afb49b5d28277a02cee55fcec08881 Mon Sep 17 00:00:00 2001 From: ckrenslehner Date: Fri, 25 Oct 2024 20:41:51 +0200 Subject: [PATCH 136/144] fix spelling --- docs/pages/new_project.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/new_project.adoc b/docs/pages/new_project.adoc index fa4c2e359..38cea044b 100644 --- a/docs/pages/new_project.adoc +++ b/docs/pages/new_project.adoc @@ -84,7 +84,7 @@ embassy-executor = { version = "0.6.1", features = ["nightly", "arch-cortex-m", embassy-time = { version = "0.3.2", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } ---- -Prior embassy needed to be installed straight from the git repository. This can is still useful, if you want to checkout a specic revision of an embassy crate which is not yet published. +Prior, embassy needed to be installed straight from the git repository. Installing from git is still useful, if you want to checkout a specic revision of an embassy crate which is not yet published. The recommended way of doing so is as follows: * Copy the required `embassy-*` lines from the example `Cargo.toml` From ca6bcb4250fc294c2294265fbc83bf00398e089c Mon Sep 17 00:00:00 2001 From: Haobo Gu Date: Sat, 26 Oct 2024 23:41:30 +0800 Subject: [PATCH 137/144] feat(ospi): add ospi example Signed-off-by: Haobo Gu --- embassy-stm32/src/ospi/mod.rs | 9 +- .../stm32h7/src/bin/ospi_memory_mapped.rs | 435 ++++++++++++++++++ 2 files changed, 437 insertions(+), 7 deletions(-) create mode 100644 examples/stm32h7/src/bin/ospi_memory_mapped.rs diff --git a/embassy-stm32/src/ospi/mod.rs b/embassy-stm32/src/ospi/mod.rs index 7ccafe361..f8ef66216 100644 --- a/embassy-stm32/src/ospi/mod.rs +++ b/embassy-stm32/src/ospi/mod.rs @@ -180,7 +180,7 @@ pub struct Ospi<'d, T: Instance, M: PeriMode> { impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { /// Enter memory mode. - /// The Input `TransferConfig` is used to configure the read operation in memory mode + /// The Input `read_config` is used to configure the read operation in memory mode pub fn enable_memory_mapped_mode( &mut self, read_config: TransferConfig, @@ -190,9 +190,7 @@ impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { self.configure_command(&read_config, None)?; let reg = T::REGS; - while reg.sr().read().busy() { - info!("wait ospi busy"); - } + while reg.sr().read().busy() {} reg.ccr().modify(|r| { r.set_dqse(false); @@ -229,9 +227,6 @@ impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { /// Quit from memory mapped mode pub fn disable_memory_mapped_mode(&mut self) { let reg = T::REGS; - while reg.sr().read().busy() { - info!("wait ospi busy"); - } reg.cr().modify(|r| { r.set_fmode(crate::ospi::vals::FunctionalMode::INDIRECTWRITE); diff --git a/examples/stm32h7/src/bin/ospi_memory_mapped.rs b/examples/stm32h7/src/bin/ospi_memory_mapped.rs new file mode 100644 index 000000000..40f39f751 --- /dev/null +++ b/examples/stm32h7/src/bin/ospi_memory_mapped.rs @@ -0,0 +1,435 @@ +#![no_main] +#![no_std] + +// Tested on weact stm32h7b0 board + w25q64 spi flash + +use defmt::info; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::{ + gpio::{Level, Output, Speed}, + mode::Blocking, + ospi::{AddressSize, DummyCycles, Instance, Ospi, OspiWidth, TransferConfig}, + ospi::{ChipSelectHighTime, FIFOThresholdLevel, MemorySize, MemoryType, WrapSize}, + time::Hertz, + Config, +}; +use embassy_time::Timer; +use panic_probe as _; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // RCC config + let mut config = Config::default(); + info!("START"); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + // Needed for USB + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); + // External oscillator 25MHZ + config.rcc.hse = Some(Hse { + freq: Hertz(25_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV5, + mul: PllMul::MUL112, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV2), + divr: Some(PllDiv::DIV2), + }); + 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; + config.rcc.voltage_scale = VoltageScale::Scale0; + } + + // Initialize peripherals + let p = embassy_stm32::init(config); + + let qspi_config = embassy_stm32::ospi::Config { + fifo_threshold: FIFOThresholdLevel::_16Bytes, + memory_type: MemoryType::Micron, + device_size: MemorySize::_8MiB, + chip_select_high_time: ChipSelectHighTime::_1Cycle, + free_running_clock: false, + clock_mode: false, + wrap_size: WrapSize::None, + clock_prescaler: 2, + sample_shifting: true, + delay_hold_quarter_cycle: false, + chip_select_boundary: 0, + delay_block_bypass: true, + max_transfer: 0, + refresh: 0, + }; + let ospi = embassy_stm32::ospi::Ospi::new_blocking_quadspi( + p.OCTOSPI1, + p.PB2, + p.PD11, + p.PD12, + p.PE2, + p.PD13, + p.PB6, + qspi_config, + ); + + let mut flash = FlashMemory::new(ospi).await; + + let flash_id = flash.read_id(); + info!("FLASH ID: {=[u8]:x}", flash_id); + let mut wr_buf = [0u8; 8]; + for i in 0..8 { + wr_buf[i] = i as u8; + } + let mut rd_buf = [0u8; 8]; + flash.erase_sector(0).await; + flash.write_memory(0, &wr_buf, true).await; + flash.read_memory(0, &mut rd_buf, true); + info!("WRITE BUF: {=[u8]:#X}", wr_buf); + info!("READ BUF: {=[u8]:#X}", rd_buf); + info!("Enabling memory mapped mode"); + flash.enable_mm().await; + + let first_u32 = unsafe { *(0x90000000 as *const u32) }; + assert_eq!(first_u32, 0x03020100); + + let second_u32 = unsafe { *(0x90000004 as *const u32) }; + assert_eq!(second_u32, 0x07060504); + flash.disable_mm().await; + + info!("DONE"); + // Output pin PE3 + let mut led = Output::new(p.PE3, Level::Low, Speed::Low); + + loop { + led.toggle(); + Timer::after_millis(1000).await; + } +} + +const MEMORY_PAGE_SIZE: usize = 8; + +const CMD_QUAD_READ: u8 = 0x6B; + +const CMD_QUAD_WRITE_PG: u8 = 0x32; + +const CMD_READ_ID: u8 = 0x9F; +const CMD_READ_UUID: u8 = 0x4B; + +const CMD_ENABLE_RESET: u8 = 0x66; +const CMD_RESET: u8 = 0x99; + +const CMD_WRITE_ENABLE: u8 = 0x06; + +const CMD_CHIP_ERASE: u8 = 0xC7; +const CMD_SECTOR_ERASE: u8 = 0x20; +const CMD_BLOCK_ERASE_32K: u8 = 0x52; +const CMD_BLOCK_ERASE_64K: u8 = 0xD8; + +const CMD_READ_SR: u8 = 0x05; +const CMD_READ_CR: u8 = 0x35; + +const CMD_WRITE_SR: u8 = 0x01; +const CMD_WRITE_CR: u8 = 0x31; + +/// Implementation of access to flash chip. +/// Chip commands are hardcoded as it depends on used chip. +/// This implementation is using chip GD25Q64C from Giga Device +pub struct FlashMemory { + ospi: Ospi<'static, I, Blocking>, +} + +impl FlashMemory { + pub async fn new(ospi: Ospi<'static, I, Blocking>) -> Self { + let mut memory = Self { ospi }; + + memory.reset_memory().await; + memory.enable_quad(); + memory + } + + async fn qpi_mode(&mut self) { + // Enter qpi mode + self.exec_command(0x38).await; + + // Set read param + let transaction = TransferConfig { + iwidth: OspiWidth::QUAD, + dwidth: OspiWidth::QUAD, + instruction: Some(0xC0), + ..Default::default() + }; + self.enable_write().await; + self.ospi.blocking_write(&[0x30_u8], transaction).unwrap(); + self.wait_write_finish(); + } + + pub async fn disable_mm(&mut self) { + self.ospi.disable_memory_mapped_mode(); + } + + pub async fn enable_mm(&mut self) { + self.qpi_mode().await; + + let read_config = TransferConfig { + iwidth: OspiWidth::QUAD, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::QUAD, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::QUAD, + instruction: Some(0x0B), // Fast read in QPI mode + dummy: DummyCycles::_8, + ..Default::default() + }; + + let write_config = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::QUAD, + instruction: Some(0x32), // Write config + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.enable_memory_mapped_mode(read_config, write_config).unwrap(); + } + + fn enable_quad(&mut self) { + let cr = self.read_cr(); + // info!("Read cr: {:x}", cr); + self.write_cr(cr | 0x02); + // info!("Read cr after writing: {:x}", cr); + } + + pub fn disable_quad(&mut self) { + let cr = self.read_cr(); + self.write_cr(cr & (!(0x02))); + } + + async fn exec_command_4(&mut self, cmd: u8) { + let transaction = TransferConfig { + iwidth: OspiWidth::QUAD, + adwidth: OspiWidth::NONE, + // adsize: AddressSize::_24bit, + dwidth: OspiWidth::NONE, + instruction: Some(cmd as u32), + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.command(&transaction).await.unwrap(); + } + + async fn exec_command(&mut self, cmd: u8) { + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adwidth: OspiWidth::NONE, + // adsize: AddressSize::_24bit, + dwidth: OspiWidth::NONE, + instruction: Some(cmd as u32), + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + // info!("Excuting command: {:x}", transaction.instruction); + self.ospi.command(&transaction).await.unwrap(); + } + + pub async fn reset_memory(&mut self) { + self.exec_command_4(CMD_ENABLE_RESET).await; + self.exec_command_4(CMD_RESET).await; + self.exec_command(CMD_ENABLE_RESET).await; + self.exec_command(CMD_RESET).await; + self.wait_write_finish(); + } + + pub async fn enable_write(&mut self) { + self.exec_command(CMD_WRITE_ENABLE).await; + } + + pub fn read_id(&mut self) -> [u8; 3] { + let mut buffer = [0; 3]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::NONE, + // adsize: AddressSize::_24bit, + dwidth: OspiWidth::SING, + instruction: Some(CMD_READ_ID as u32), + ..Default::default() + }; + // info!("Reading id: 0x{:X}", transaction.instruction); + self.ospi.blocking_read(&mut buffer, transaction).unwrap(); + buffer + } + + pub fn read_id_4(&mut self) -> [u8; 3] { + let mut buffer = [0; 3]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::NONE, + dwidth: OspiWidth::QUAD, + instruction: Some(CMD_READ_ID as u32), + ..Default::default() + }; + info!("Reading id: 0x{:X}", transaction.instruction); + self.ospi.blocking_read(&mut buffer, transaction).unwrap(); + buffer + } + + pub fn read_memory(&mut self, addr: u32, buffer: &mut [u8], use_dma: bool) { + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::QUAD, + instruction: Some(CMD_QUAD_READ as u32), + address: Some(addr), + dummy: DummyCycles::_8, + ..Default::default() + }; + if use_dma { + self.ospi.blocking_read(buffer, transaction).unwrap(); + } else { + self.ospi.blocking_read(buffer, transaction).unwrap(); + } + } + + fn wait_write_finish(&mut self) { + while (self.read_sr() & 0x01) != 0 {} + } + + async fn perform_erase(&mut self, addr: u32, cmd: u8) { + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::NONE, + instruction: Some(cmd as u32), + address: Some(addr), + dummy: DummyCycles::_0, + ..Default::default() + }; + self.enable_write().await; + self.ospi.command(&transaction).await.unwrap(); + self.wait_write_finish(); + } + + pub async fn erase_sector(&mut self, addr: u32) { + self.perform_erase(addr, CMD_SECTOR_ERASE).await; + } + + pub async fn erase_block_32k(&mut self, addr: u32) { + self.perform_erase(addr, CMD_BLOCK_ERASE_32K).await; + } + + pub async fn erase_block_64k(&mut self, addr: u32) { + self.perform_erase(addr, CMD_BLOCK_ERASE_64K).await; + } + + pub async fn erase_chip(&mut self) { + self.exec_command(CMD_CHIP_ERASE).await; + } + + async fn write_page(&mut self, addr: u32, buffer: &[u8], len: usize, use_dma: bool) { + assert!( + (len as u32 + (addr & 0x000000ff)) <= MEMORY_PAGE_SIZE as u32, + "write_page(): page write length exceeds page boundary (len = {}, addr = {:X}", + len, + addr + ); + + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + adwidth: OspiWidth::SING, + dwidth: OspiWidth::QUAD, + instruction: Some(CMD_QUAD_WRITE_PG as u32), + address: Some(addr), + dummy: DummyCycles::_0, + ..Default::default() + }; + self.enable_write().await; + if use_dma { + self.ospi.blocking_write(buffer, transaction).unwrap(); + } else { + self.ospi.blocking_write(buffer, transaction).unwrap(); + } + self.wait_write_finish(); + } + + pub async fn write_memory(&mut self, addr: u32, buffer: &[u8], use_dma: bool) { + let mut left = buffer.len(); + let mut place = addr; + let mut chunk_start = 0; + + while left > 0 { + let max_chunk_size = MEMORY_PAGE_SIZE - (place & 0x000000ff) as usize; + let chunk_size = if left >= max_chunk_size { max_chunk_size } else { left }; + let chunk = &buffer[chunk_start..(chunk_start + chunk_size)]; + self.write_page(place, chunk, chunk_size, use_dma).await; + place += chunk_size as u32; + left -= chunk_size; + chunk_start += chunk_size; + } + } + + fn read_register(&mut self, cmd: u8) -> u8 { + let mut buffer = [0; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::NONE, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::SING, + instruction: Some(cmd as u32), + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.blocking_read(&mut buffer, transaction).unwrap(); + // info!("Read w25q64 register: 0x{:x}", buffer[0]); + buffer[0] + } + + fn write_register(&mut self, cmd: u8, value: u8) { + let buffer = [value; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + instruction: Some(cmd as u32), + adsize: AddressSize::_24bit, + adwidth: OspiWidth::NONE, + dwidth: OspiWidth::SING, + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.blocking_write(&buffer, transaction).unwrap(); + } + + pub fn read_sr(&mut self) -> u8 { + self.read_register(CMD_READ_SR) + } + + pub fn read_cr(&mut self) -> u8 { + self.read_register(CMD_READ_CR) + } + + pub fn write_sr(&mut self, value: u8) { + self.write_register(CMD_WRITE_SR, value); + } + + pub fn write_cr(&mut self, value: u8) { + self.write_register(CMD_WRITE_CR, value); + } +} From 04c9130d326990dc92577f2ed4b2dc927efe2c13 Mon Sep 17 00:00:00 2001 From: Haobo Gu Date: Sat, 26 Oct 2024 23:50:16 +0800 Subject: [PATCH 138/144] feat(example): move ospi memory mapped example for stm32h7b0 to separate folder Signed-off-by: Haobo Gu --- examples/stm32h7b0/.cargo/config.toml | 8 ++ examples/stm32h7b0/Cargo.toml | 74 +++++++++++++++++++ examples/stm32h7b0/build.rs | 35 +++++++++ examples/stm32h7b0/memory.x | 5 ++ .../src/bin/ospi_memory_mapped.rs | 5 +- 5 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 examples/stm32h7b0/.cargo/config.toml create mode 100644 examples/stm32h7b0/Cargo.toml create mode 100644 examples/stm32h7b0/build.rs create mode 100644 examples/stm32h7b0/memory.x rename examples/{stm32h7 => stm32h7b0}/src/bin/ospi_memory_mapped.rs (99%) diff --git a/examples/stm32h7b0/.cargo/config.toml b/examples/stm32h7b0/.cargo/config.toml new file mode 100644 index 000000000..870849a27 --- /dev/null +++ b/examples/stm32h7b0/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip STM32H7B0VBTx' + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) + +[env] +DEFMT_LOG = "trace" diff --git a/examples/stm32h7b0/Cargo.toml b/examples/stm32h7b0/Cargo.toml new file mode 100644 index 000000000..02c620443 --- /dev/null +++ b/examples/stm32h7b0/Cargo.toml @@ -0,0 +1,74 @@ +[package] +edition = "2021" +name = "embassy-stm32h7b0-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h7b0vb", "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.2.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +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" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-nal-async = "0.8.0" +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +micromath = "2.0.0" +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] +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/stm32h7b0/build.rs b/examples/stm32h7b0/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/stm32h7b0/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/stm32h7b0/memory.x b/examples/stm32h7b0/memory.x new file mode 100644 index 000000000..6eb1bb7c1 --- /dev/null +++ b/examples/stm32h7b0/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 128K /* BANK_1 */ + RAM : ORIGIN = 0x24000000, LENGTH = 512K /* SRAM */ +} \ No newline at end of file diff --git a/examples/stm32h7/src/bin/ospi_memory_mapped.rs b/examples/stm32h7b0/src/bin/ospi_memory_mapped.rs similarity index 99% rename from examples/stm32h7/src/bin/ospi_memory_mapped.rs rename to examples/stm32h7b0/src/bin/ospi_memory_mapped.rs index 40f39f751..e32a22e41 100644 --- a/examples/stm32h7/src/bin/ospi_memory_mapped.rs +++ b/examples/stm32h7b0/src/bin/ospi_memory_mapped.rs @@ -61,7 +61,7 @@ async fn main(_spawner: Spawner) { free_running_clock: false, clock_mode: false, wrap_size: WrapSize::None, - clock_prescaler: 2, + clock_prescaler: 4, sample_shifting: true, delay_hold_quarter_cycle: false, chip_select_boundary: 0, @@ -94,8 +94,8 @@ async fn main(_spawner: Spawner) { flash.read_memory(0, &mut rd_buf, true); info!("WRITE BUF: {=[u8]:#X}", wr_buf); info!("READ BUF: {=[u8]:#X}", rd_buf); - info!("Enabling memory mapped mode"); flash.enable_mm().await; + info!("Enabled memory mapped mode"); let first_u32 = unsafe { *(0x90000000 as *const u32) }; assert_eq!(first_u32, 0x03020100); @@ -121,7 +121,6 @@ const CMD_QUAD_READ: u8 = 0x6B; const CMD_QUAD_WRITE_PG: u8 = 0x32; const CMD_READ_ID: u8 = 0x9F; -const CMD_READ_UUID: u8 = 0x4B; const CMD_ENABLE_RESET: u8 = 0x66; const CMD_RESET: u8 = 0x99; From 3b5284d99d7054ac42e5d3e065fc1d27527f823a Mon Sep 17 00:00:00 2001 From: Haobo Gu Date: Sat, 26 Oct 2024 23:51:38 +0800 Subject: [PATCH 139/144] fix: fmt code Signed-off-by: Haobo Gu --- .../stm32h7b0/src/bin/ospi_memory_mapped.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/examples/stm32h7b0/src/bin/ospi_memory_mapped.rs b/examples/stm32h7b0/src/bin/ospi_memory_mapped.rs index e32a22e41..9c397e507 100644 --- a/examples/stm32h7b0/src/bin/ospi_memory_mapped.rs +++ b/examples/stm32h7b0/src/bin/ospi_memory_mapped.rs @@ -4,18 +4,17 @@ // Tested on weact stm32h7b0 board + w25q64 spi flash use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; -use embassy_stm32::{ - gpio::{Level, Output, Speed}, - mode::Blocking, - ospi::{AddressSize, DummyCycles, Instance, Ospi, OspiWidth, TransferConfig}, - ospi::{ChipSelectHighTime, FIFOThresholdLevel, MemorySize, MemoryType, WrapSize}, - time::Hertz, - Config, +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::mode::Blocking; +use embassy_stm32::ospi::{ + AddressSize, ChipSelectHighTime, DummyCycles, FIFOThresholdLevel, Instance, MemorySize, MemoryType, Ospi, + OspiWidth, TransferConfig, WrapSize, }; +use embassy_stm32::time::Hertz; +use embassy_stm32::Config; use embassy_time::Timer; -use panic_probe as _; +use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { From 917f1d1f4de6f7e25f483561001a9b5b36cbce7e Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sun, 27 Oct 2024 09:55:00 +0100 Subject: [PATCH 140/144] This fixes 2 issues where STM32 BXCAN would hang 1. If one received frames under an `interrupt_free` section, in my case `init` in RTIC, the RX IRQ will fire and clear it's enable bit after `interrupt_free` is complete. There is no frame to read so RX is now unconditionally disabled forever. 2. On clearing of RX IRQ, TX stops silently. This happens due to the use of `write` instead of `modify` when modifying IRQ enable bits. Solution 1: Enable RX IRQs on every call to `try_read` that return no data. This solution also solves the issue of very delayed handling of the RX IRQ which would cause the same issue. Solution 2: Use `modify` instead of `write`. --- embassy-stm32/src/can/bxcan/mod.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/embassy-stm32/src/can/bxcan/mod.rs b/embassy-stm32/src/can/bxcan/mod.rs index baa4bee79..19f1cea29 100644 --- a/embassy-stm32/src/can/bxcan/mod.rs +++ b/embassy-stm32/src/can/bxcan/mod.rs @@ -893,7 +893,7 @@ impl RxMode { RxFifo::Fifo0 => 0usize, RxFifo::Fifo1 => 1usize, }; - T::regs().ier().write(|w| { + T::regs().ier().modify(|w| { w.set_fmpie(fifo_idx, false); }); waker.wake(); @@ -936,18 +936,22 @@ impl RxMode { Self::NonBuffered(_) => { let registers = &info.regs; if let Some(msg) = registers.receive_fifo(RxFifo::Fifo0) { - registers.0.ier().write(|w| { + registers.0.ier().modify(|w| { w.set_fmpie(0, true); }); Ok(msg) } else if let Some(msg) = registers.receive_fifo(RxFifo::Fifo1) { - registers.0.ier().write(|w| { + registers.0.ier().modify(|w| { w.set_fmpie(1, true); }); Ok(msg) } else if let Some(err) = registers.curr_error() { Err(TryReadError::BusError(err)) } else { + registers.0.ier().modify(|w| { + w.set_fmpie(0, true); + w.set_fmpie(1, true); + }); Err(TryReadError::Empty) } } From edd36382958f006667c32627c2afae24f1861892 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 27 Oct 2024 19:21:38 +0100 Subject: [PATCH 141/144] Add examples/stm32h7b0 to CI --- ci.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/ci.sh b/ci.sh index b04a4b288..9f7a7a037 100755 --- a/ci.sh +++ b/ci.sh @@ -216,6 +216,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/stm32h7b0/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7b0 \ --- build --release --manifest-path examples/stm32h735/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h735 \ --- build --release --manifest-path examples/stm32h755cm4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h755cm4 \ --- build --release --manifest-path examples/stm32h755cm7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h755cm7 \ From ca8e885dbb65541a380e66c678ae148c24782e8e Mon Sep 17 00:00:00 2001 From: Connor <10554880+meigs2@users.noreply.github.com> Date: Sun, 27 Oct 2024 17:41:15 -0500 Subject: [PATCH 142/144] Add tx_dma to async spi --- embassy-rp/src/spi.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/embassy-rp/src/spi.rs b/embassy-rp/src/spi.rs index b89df74a2..a8f4e72c7 100644 --- a/embassy-rp/src/spi.rs +++ b/embassy-rp/src/spi.rs @@ -359,17 +359,18 @@ impl<'d, T: Instance> Spi<'d, T, Async> { inner: impl Peripheral

+ 'd, clk: impl Peripheral

+ 'd> + 'd, miso: impl Peripheral

+ 'd> + 'd, + tx_dma: impl Peripheral

+ 'd, rx_dma: impl Peripheral

+ 'd, config: Config, ) -> Self { - into_ref!(rx_dma, clk, miso); + into_ref!(tx_dma, rx_dma, clk, miso); Self::new_inner( inner, Some(clk.map_into()), None, Some(miso.map_into()), None, - None, + Some(tx_dma.map_into()), Some(rx_dma.map_into()), config, ) From bfff50a3619be3a9fbc052a70b2e43c95cc5dc42 Mon Sep 17 00:00:00 2001 From: William <174336620+williams-one@users.noreply.github.com> Date: Mon, 28 Oct 2024 08:40:38 +0100 Subject: [PATCH 143/144] Fix format --- examples/stm32u5/src/bin/ltdc.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/stm32u5/src/bin/ltdc.rs b/examples/stm32u5/src/bin/ltdc.rs index 5bf2cd67d..bd59a9148 100644 --- a/examples/stm32u5/src/bin/ltdc.rs +++ b/examples/stm32u5/src/bin/ltdc.rs @@ -316,9 +316,8 @@ impl OriginDimensions for DoubleBuffer { mod rcc_setup { - use embassy_stm32::rcc; use embassy_stm32::time::Hertz; - use embassy_stm32::{Config, Peripherals}; + use embassy_stm32::{rcc, Config, Peripherals}; /// Sets up clocks for the stm32u5g9zj mcu /// change this if you plan to use a different microcontroller From 76606b6fe040d0735ab8a0e9ac99894de06264c4 Mon Sep 17 00:00:00 2001 From: William <174336620+williams-one@users.noreply.github.com> Date: Mon, 28 Oct 2024 08:46:07 +0100 Subject: [PATCH 144/144] Update chip from stm32u585ai to stm32u5g9zj and fix pinout --- examples/stm32u5/.cargo/config.toml | 4 ++-- examples/stm32u5/Cargo.toml | 4 ++-- examples/stm32u5/src/bin/i2c.rs | 2 +- examples/stm32u5/src/bin/usb_serial.rs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/stm32u5/.cargo/config.toml b/examples/stm32u5/.cargo/config.toml index 36c5b63a6..bdbd86354 100644 --- a/examples/stm32u5/.cargo/config.toml +++ b/examples/stm32u5/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace STM32U585AIIx with your chip as listed in `probe-rs chip list` -runner = "probe-rs run --chip STM32U585AIIx" +# replace STM32U5G9ZJTxQ with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32U5G9ZJTxQ" [build] target = "thumbv8m.main-none-eabihf" diff --git a/examples/stm32u5/Cargo.toml b/examples/stm32u5/Cargo.toml index f594ad71a..8b576425c 100644 --- a/examples/stm32u5/Cargo.toml +++ b/examples/stm32u5/Cargo.toml @@ -5,8 +5,8 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -# Change stm32u585ai to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "unstable-pac", "stm32u585ai", "time-driver-any", "memory-x" ] } +# Change stm32u5g9zj to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "unstable-pac", "stm32u5g9zj", "time-driver-any", "memory-x" ] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } diff --git a/examples/stm32u5/src/bin/i2c.rs b/examples/stm32u5/src/bin/i2c.rs index 19a78eac9..d5f5d6f60 100644 --- a/examples/stm32u5/src/bin/i2c.rs +++ b/examples/stm32u5/src/bin/i2c.rs @@ -13,7 +13,7 @@ const WHOAMI: u8 = 0x0F; #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let mut i2c = I2c::new_blocking(p.I2C2, p.PH4, p.PH5, Hertz(100_000), Default::default()); + let mut i2c = I2c::new_blocking(p.I2C2, p.PF1, p.PF0, Hertz(100_000), Default::default()); let mut data = [0u8; 1]; unwrap!(i2c.blocking_write_read(HTS221_ADDRESS, &[WHOAMI], &mut data)); diff --git a/examples/stm32u5/src/bin/usb_serial.rs b/examples/stm32u5/src/bin/usb_serial.rs index 4d56395da..4bb1a6079 100644 --- a/examples/stm32u5/src/bin/usb_serial.rs +++ b/examples/stm32u5/src/bin/usb_serial.rs @@ -13,7 +13,7 @@ use embassy_usb::Builder; use panic_probe as _; bind_interrupts!(struct Irqs { - OTG_FS => usb::InterruptHandler; + OTG_HS => usb::InterruptHandler; }); #[embassy_executor::main] @@ -48,7 +48,7 @@ async fn main(_spawner: Spawner) { // to enable vbus_detection to comply with the USB spec. If you enable it, the board // has to support it or USB won't work at all. See docs on `vbus_detection` for details. config.vbus_detection = false; - let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + let driver = Driver::new_hs(p.USB_OTG_HS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); // Create embassy-usb Config let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);