From d34623c957b21f5c41e72636b3edacc13f3a2b25 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 11 Feb 2022 11:17:43 -0800 Subject: [PATCH 1/8] Clear UDRE when setting UCSR A This is suggested in the atmega328p manual and theoretically applies to other AVR boards. It is for future compatibility. --- avr-hal-generic/src/usart.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avr-hal-generic/src/usart.rs b/avr-hal-generic/src/usart.rs index 1e4181d1d9..bf396afe1a 100644 --- a/avr-hal-generic/src/usart.rs +++ b/avr-hal-generic/src/usart.rs @@ -479,7 +479,7 @@ macro_rules! impl_usart_traditional { > for $USART { fn raw_init(&mut self, baudrate: $crate::usart::Baudrate) { self.[].write(|w| unsafe { w.bits(baudrate.ubrr) }); - self.[].write(|w| w.[]().bit(baudrate.u2x)); + self.[].write(|w| w.[]().bit(baudrate.u2x).[]().clear_bit()); // Enable receiver and transmitter but leave interrupts disabled. self.[].write(|w| w From 52aaa226326c25eca0c9a731f2835e5461ce966d Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 11 Feb 2022 11:20:55 -0800 Subject: [PATCH 2/8] Fix DataRegisterEmpty serial event and add TxComplete serial event --- avr-hal-generic/src/usart.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/avr-hal-generic/src/usart.rs b/avr-hal-generic/src/usart.rs index bf396afe1a..453f38eca3 100644 --- a/avr-hal-generic/src/usart.rs +++ b/avr-hal-generic/src/usart.rs @@ -150,6 +150,12 @@ pub enum Event { /// your MCU for details. RxComplete, + /// A complete byte was sent. + /// + /// Corresponds to the `USART_TX` or `USART#_TX` interrupt. Please refer to the datasheet for + /// your MCU for details. + TxComplete, + /// All data from the USART data register was transmitted. /// /// Corresponds to the `USART_UDRE` or `USART#_UDRE` interrupt. Please refer to the datasheet @@ -531,8 +537,10 @@ macro_rules! impl_usart_traditional { match event { $crate::usart::Event::RxComplete => self.[].modify(|_, w| w.[]().bit(state)), - $crate::usart::Event::DataRegisterEmpty => + $crate::usart::Event::TxComplete => self.[].modify(|_, w| w.[]().bit(state)), + $crate::usart::Event::DataRegisterEmpty => + self.[].modify(|_, w| w.[]().bit(state)), } } } From b14a72eec56d459c65b68525e13e36aa0ab211b7 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 11 Feb 2022 11:22:03 -0800 Subject: [PATCH 3/8] Add modified atomic_polyfill and heapless::spsc These currently don't work on AVR and won't be able to work on AVR until rust updates AVR asm. As a work around, manually implement them. --- examples/arduino-uno/Cargo.toml | 1 + examples/arduino-uno/src/atomic.rs | 176 ++++++ examples/arduino-uno/src/lib.rs | 5 + examples/arduino-uno/src/sealed.rs | 61 ++ examples/arduino-uno/src/spsc.rs | 907 +++++++++++++++++++++++++++++ 5 files changed, 1150 insertions(+) create mode 100644 examples/arduino-uno/src/atomic.rs create mode 100644 examples/arduino-uno/src/lib.rs create mode 100644 examples/arduino-uno/src/sealed.rs create mode 100644 examples/arduino-uno/src/spsc.rs diff --git a/examples/arduino-uno/Cargo.toml b/examples/arduino-uno/Cargo.toml index 57eed68486..62d20543da 100644 --- a/examples/arduino-uno/Cargo.toml +++ b/examples/arduino-uno/Cargo.toml @@ -11,6 +11,7 @@ ufmt = "0.1.0" nb = "0.1.2" embedded-hal = "0.2.3" pwm-pca9685 = "0.3.1" +hash32 = "0.2.1" [dependencies.arduino-hal] path = "../../arduino-hal/" diff --git a/examples/arduino-uno/src/atomic.rs b/examples/arduino-uno/src/atomic.rs new file mode 100644 index 0000000000..d651a44195 --- /dev/null +++ b/examples/arduino-uno/src/atomic.rs @@ -0,0 +1,176 @@ +// Copyright (c) 2020 Dario Nieuwenhuis +// Code based of off atomic_polyfill. + +pub use core::sync::atomic::{compiler_fence, fence, Ordering}; + +#[repr(transparent)] +pub struct AtomicUsize { + inner: core::cell::UnsafeCell, +} + +impl From for AtomicUsize { + #[inline] + fn from(v: usize) -> Self { + Self::new(v) + } +} + +impl core::fmt::Debug for AtomicUsize { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(&self.load(Ordering::SeqCst), f) + } +} + +impl AtomicUsize { + pub const fn new(v: usize) -> Self { + Self { + inner: core::cell::UnsafeCell::new(v), + } + } + + pub fn into_inner(self) -> usize { + self.inner.into_inner() + } + + pub fn get_mut(&mut self) -> &mut usize { + self.inner.get_mut() + } + + pub fn load(&self, _order: Ordering) -> usize { + return critical_section(|| unsafe { *self.inner.get() }); + } + + pub fn store(&self, val: usize, _order: Ordering) { + return critical_section(|| unsafe { *self.inner.get() = val }); + } + + fn load_nocs(&self, _order: Ordering) -> usize { + return unsafe { *self.inner.get() }; + } + + fn store_nocs(&self, val: usize, _order: Ordering) { + return unsafe { *self.inner.get() = val }; + } + + pub fn swap(&self, val: usize, order: Ordering) -> usize { + self.op(order, |_| val) + } + + pub fn compare_exchange( + &self, + current: usize, + new: usize, + success: Ordering, + failure: Ordering, + ) -> Result { + self.compare_exchange_weak(current, new, success, failure) + } + + pub fn compare_exchange_weak( + &self, + current: usize, + new: usize, + success: Ordering, + _failure: Ordering, + ) -> Result { + critical_section(|| { + let old = self.load_nocs(load_ordering(success)); + if old == current { + self.store_nocs(new, store_ordering(success)); + Ok(old) + } else { + Err(old) + } + }) + } + + pub fn fetch_add(&self, val: usize, order: Ordering) -> usize { + self.op(order, |old| old.wrapping_add(val)) + } + + pub fn fetch_sub(&self, val: usize, order: Ordering) -> usize { + self.op(order, |old| old.wrapping_sub(val)) + } + + pub fn fetch_and(&self, val: usize, order: Ordering) -> usize { + self.op(order, |old| old & val) + } + + pub fn fetch_nand(&self, val: usize, order: Ordering) -> usize { + self.op(order, |old| !(old & val)) + } + + pub fn fetch_or(&self, val: usize, order: Ordering) -> usize { + self.op(order, |old| old | val) + } + + pub fn fetch_xor(&self, val: usize, order: Ordering) -> usize { + self.op(order, |old| old ^ val) + } + + pub fn fetch_update( + &self, + set_order: Ordering, + _fetch_order: Ordering, + mut f: F, + ) -> Result + where + F: FnMut(usize) -> Option, + { + critical_section(|| { + let old = self.load_nocs(load_ordering(set_order)); + if let Some(new) = f(old) { + self.store_nocs(new, store_ordering(set_order)); + Ok(old) + } else { + Err(old) + } + }) + } + + pub fn fetch_max(&self, val: usize, order: Ordering) -> usize { + self.op(order, |old| old.max(val)) + } + + pub fn fetch_min(&self, val: usize, order: Ordering) -> usize { + self.op(order, |old| old.min(val)) + } + + fn op(&self, order: Ordering, f: impl FnOnce(usize) -> usize) -> usize { + critical_section(|| { + let old = self.load_nocs(load_ordering(order)); + let new = f(old); + self.store_nocs(new, store_ordering(order)); + old + }) + } +} + +#[allow(unused)] +fn load_ordering(order: Ordering) -> Ordering { + match order { + Ordering::Release => Ordering::Relaxed, + Ordering::Relaxed => Ordering::Relaxed, + Ordering::SeqCst => Ordering::SeqCst, + Ordering::Acquire => Ordering::Acquire, + Ordering::AcqRel => Ordering::Acquire, + x => x, + } +} + +#[allow(unused)] +fn store_ordering(order: Ordering) -> Ordering { + match order { + Ordering::Release => Ordering::Release, + Ordering::Relaxed => Ordering::Relaxed, + Ordering::SeqCst => Ordering::SeqCst, + Ordering::Acquire => Ordering::Relaxed, + Ordering::AcqRel => Ordering::Release, + x => x, + } +} + +#[allow(unused)] +fn critical_section(f: impl FnOnce() -> R) -> R { + avr_device::interrupt::free(move |_| f()) +} diff --git a/examples/arduino-uno/src/lib.rs b/examples/arduino-uno/src/lib.rs new file mode 100644 index 0000000000..83ee243f21 --- /dev/null +++ b/examples/arduino-uno/src/lib.rs @@ -0,0 +1,5 @@ +#![no_std] + +pub mod atomic; +mod sealed; +pub mod spsc; diff --git a/examples/arduino-uno/src/sealed.rs b/examples/arduino-uno/src/sealed.rs new file mode 100644 index 0000000000..df699ca276 --- /dev/null +++ b/examples/arduino-uno/src/sealed.rs @@ -0,0 +1,61 @@ +// Copyright (c) 2017 Jorge Aparicio +// Code taken from heapless. + +#[allow(dead_code)] +#[allow(path_statements)] +pub(crate) const fn smaller_than() { + Assert::::LESS; +} + +#[allow(dead_code)] +#[allow(path_statements)] +pub(crate) const fn greater_than_eq_0() { + Assert::::GREATER_EQ; +} + +#[allow(dead_code)] +#[allow(path_statements)] +pub(crate) const fn greater_than_0() { + Assert::::GREATER; +} + +#[allow(dead_code)] +#[allow(path_statements)] +pub(crate) const fn greater_than_1() { + Assert::::GREATER; +} + +#[allow(dead_code)] +#[allow(path_statements)] +pub(crate) const fn power_of_two() { + Assert::::GREATER; + Assert::::POWER_OF_TWO; +} + +#[allow(dead_code)] +/// Const assert hack +pub struct Assert; + +#[allow(dead_code)] +impl Assert { + /// Const assert hack + pub const GREATER_EQ: usize = L - R; + + /// Const assert hack + pub const LESS_EQ: usize = R - L; + + /// Const assert hack + pub const NOT_EQ: isize = 0 / (R as isize - L as isize); + + /// Const assert hack + pub const EQ: usize = (R - L) + (L - R); + + /// Const assert hack + pub const GREATER: usize = L - R - 1; + + /// Const assert hack + pub const LESS: usize = R - L - 1; + + /// Const assert hack + pub const POWER_OF_TWO: usize = 0 - (L & (L - 1)); +} diff --git a/examples/arduino-uno/src/spsc.rs b/examples/arduino-uno/src/spsc.rs new file mode 100644 index 0000000000..6e040eef79 --- /dev/null +++ b/examples/arduino-uno/src/spsc.rs @@ -0,0 +1,907 @@ +// Copyright (c) 2017 Jorge Aparicio +// Code taken from heapless. + +//! Fixed capacity Single Producer Single Consumer (SPSC) queue +//! +//! Implementation based on +//! +//! NOTE: This module is not available on targets that do *not* support atomic loads and are not +//! supported by [`atomic_polyfill`]. (e.g., MSP430). +//! +//! # Examples +//! +//! - `Queue` can be used as a plain queue +//! +//! ``` +//! use heapless::spsc::Queue; +//! +//! let mut rb: Queue = Queue::new(); +//! +//! assert!(rb.enqueue(0).is_ok()); +//! assert!(rb.enqueue(1).is_ok()); +//! assert!(rb.enqueue(2).is_ok()); +//! assert!(rb.enqueue(3).is_err()); // full +//! +//! assert_eq!(rb.dequeue(), Some(0)); +//! ``` +//! +//! - `Queue` can be `split` and then be used in Single Producer Single Consumer mode +//! +//! ``` +//! use heapless::spsc::Queue; +//! +//! // Notice, type signature needs to be explicit for now. +//! // (min_const_eval, does not allow for default type assignments) +//! static mut Q: Queue = Queue::new(); +//! +//! enum Event { A, B } +//! +//! fn main() { +//! // NOTE(unsafe) beware of aliasing the `consumer` end point +//! let mut consumer = unsafe { Q.split().1 }; +//! +//! loop { +//! // `dequeue` is a lockless operation +//! match consumer.dequeue() { +//! Some(Event::A) => { /* .. */ }, +//! Some(Event::B) => { /* .. */ }, +//! None => { /* sleep */ }, +//! } +//! # break +//! } +//! } +//! +//! // this is a different execution context that can preempt `main` +//! fn interrupt_handler() { +//! // NOTE(unsafe) beware of aliasing the `producer` end point +//! let mut producer = unsafe { Q.split().0 }; +//! # let condition = true; +//! +//! // .. +//! +//! if condition { +//! producer.enqueue(Event::A).ok().unwrap(); +//! } else { +//! producer.enqueue(Event::B).ok().unwrap(); +//! } +//! +//! // .. +//! } +//! ``` +//! +//! # Benchmarks +//! +//! Measured on a ARM Cortex-M3 core running at 8 MHz and with zero Flash wait cycles +//! +//! `-C opt-level` |`3`| +//! -----------------------|---| +//! `Consumer::dequeue`| 15| +//! `Queue::dequeue` | 12| +//! `Producer::enqueue`| 16| +//! `Queue::enqueue` | 14| +//! +//! - All execution times are in clock cycles. 1 clock cycle = 125 ns. +//! - Execution time is *dependent* of `mem::size_of::()`. Both operations include one +//! `memcpy(T)` in their successful path. +//! - The optimization level is indicated in the first row. +//! - The numbers reported correspond to the successful path (i.e. `Some` is returned by `dequeue` +//! and `Ok` is returned by `enqueue`). + +use core::{cell::UnsafeCell, fmt, hash, mem::MaybeUninit, ptr}; + +use crate::atomic::{AtomicUsize, Ordering}; + +/// A statically allocated single producer single consumer queue with a capacity of `N - 1` elements +/// +/// *IMPORTANT*: To get better performance use a value for `N` that is a power of 2 (e.g. `16`, `32`, +/// etc.). +pub struct Queue { + // this is from where we dequeue items + pub(crate) head: AtomicUsize, + + // this is where we enqueue new items + pub(crate) tail: AtomicUsize, + + pub(crate) buffer: [UnsafeCell>; N], +} + +impl Queue { + const INIT: UnsafeCell> = UnsafeCell::new(MaybeUninit::uninit()); + + #[inline] + fn increment(val: usize) -> usize { + (val + 1) % N + } + + /// Creates an empty queue with a fixed capacity of `N - 1` + pub const fn new() -> Self { + // Const assert N > 1 + crate::sealed::greater_than_1::(); + + Queue { + head: AtomicUsize::new(0), + tail: AtomicUsize::new(0), + buffer: [Self::INIT; N], + } + } + + /// Returns the maximum number of elements the queue can hold + #[inline] + pub const fn capacity(&self) -> usize { + N - 1 + } + + /// Returns the number of elements in the queue + #[inline] + pub fn len(&self) -> usize { + let current_head = self.head.load(Ordering::Relaxed); + let current_tail = self.tail.load(Ordering::Relaxed); + + current_tail.wrapping_sub(current_head).wrapping_add(N) % N + } + + /// Returns `true` if the queue is empty + #[inline] + pub fn is_empty(&self) -> bool { + self.head.load(Ordering::Relaxed) == self.tail.load(Ordering::Relaxed) + } + + /// Returns `true` if the queue is full + #[inline] + pub fn is_full(&self) -> bool { + Self::increment(self.tail.load(Ordering::Relaxed)) == self.head.load(Ordering::Relaxed) + } + + /// Iterates from the front of the queue to the back + pub fn iter(&self) -> Iter<'_, T, N> { + Iter { + rb: self, + index: 0, + len: self.len(), + } + } + + /// Returns an iterator that allows modifying each value + pub fn iter_mut(&mut self) -> IterMut<'_, T, N> { + let len = self.len(); + IterMut { + rb: self, + index: 0, + len, + } + } + + /// Adds an `item` to the end of the queue + /// + /// Returns back the `item` if the queue is full + #[inline] + pub fn enqueue(&mut self, val: T) -> Result<(), T> { + unsafe { self.inner_enqueue(val) } + } + + /// Returns the item in the front of the queue, or `None` if the queue is empty + #[inline] + pub fn dequeue(&mut self) -> Option { + unsafe { self.inner_dequeue() } + } + + /// Returns a reference to the item in the front of the queue without dequeuing, or + /// `None` if the queue is empty. + /// + /// # Examples + /// ``` + /// use heapless::spsc::Queue; + /// + /// let mut queue: Queue = Queue::new(); + /// let (mut producer, mut consumer) = queue.split(); + /// assert_eq!(None, consumer.peek()); + /// producer.enqueue(1); + /// assert_eq!(Some(&1), consumer.peek()); + /// assert_eq!(Some(1), consumer.dequeue()); + /// assert_eq!(None, consumer.peek()); + /// ``` + pub fn peek(&self) -> Option<&T> { + if !self.is_empty() { + let head = self.head.load(Ordering::Relaxed); + Some(unsafe { &*(self.buffer.get_unchecked(head).get() as *const T) }) + } else { + None + } + } + + // The memory for enqueueing is "owned" by the tail pointer. + // NOTE: This internal function uses internal mutability to allow the [`Producer`] to enqueue + // items without doing pointer arithmetic and accessing internal fields of this type. + unsafe fn inner_enqueue(&self, val: T) -> Result<(), T> { + let current_tail = self.tail.load(Ordering::Relaxed); + let next_tail = Self::increment(current_tail); + + if next_tail != self.head.load(Ordering::Acquire) { + (self.buffer.get_unchecked(current_tail).get()).write(MaybeUninit::new(val)); + self.tail.store(next_tail, Ordering::Release); + + Ok(()) + } else { + Err(val) + } + } + + // The memory for enqueueing is "owned" by the tail pointer. + // NOTE: This internal function uses internal mutability to allow the [`Producer`] to enqueue + // items without doing pointer arithmetic and accessing internal fields of this type. + unsafe fn inner_enqueue_unchecked(&self, val: T) { + let current_tail = self.tail.load(Ordering::Relaxed); + + (self.buffer.get_unchecked(current_tail).get()).write(MaybeUninit::new(val)); + self.tail + .store(Self::increment(current_tail), Ordering::Release); + } + + /// Adds an `item` to the end of the queue, without checking if it's full + /// + /// # Unsafety + /// + /// If the queue is full this operation will leak a value (T's destructor won't run on + /// the value that got overwritten by `item`), *and* will allow the `dequeue` operation + /// to create a copy of `item`, which could result in `T`'s destructor running on `item` + /// twice. + pub unsafe fn enqueue_unchecked(&mut self, val: T) { + self.inner_enqueue_unchecked(val) + } + + // The memory for dequeuing is "owned" by the head pointer,. + // NOTE: This internal function uses internal mutability to allow the [`Consumer`] to dequeue + // items without doing pointer arithmetic and accessing internal fields of this type. + unsafe fn inner_dequeue(&self) -> Option { + let current_head = self.head.load(Ordering::Relaxed); + + if current_head == self.tail.load(Ordering::Acquire) { + None + } else { + let v = (self.buffer.get_unchecked(current_head).get() as *const T).read(); + + self.head + .store(Self::increment(current_head), Ordering::Release); + + Some(v) + } + } + + // The memory for dequeuing is "owned" by the head pointer,. + // NOTE: This internal function uses internal mutability to allow the [`Consumer`] to dequeue + // items without doing pointer arithmetic and accessing internal fields of this type. + unsafe fn inner_dequeue_unchecked(&self) -> T { + let current_head = self.head.load(Ordering::Relaxed); + let v = (self.buffer.get_unchecked(current_head).get() as *const T).read(); + + self.head + .store(Self::increment(current_head), Ordering::Release); + + v + } + + /// Returns the item in the front of the queue, without checking if there is something in the + /// queue + /// + /// # Unsafety + /// + /// If the queue is empty this operation will return uninitialized memory. + pub unsafe fn dequeue_unchecked(&mut self) -> T { + self.inner_dequeue_unchecked() + } + + /// Splits a queue into producer and consumer endpoints + pub fn split(&mut self) -> (Producer<'_, T, N>, Consumer<'_, T, N>) { + (Producer { rb: self }, Consumer { rb: self }) + } +} + +impl Default for Queue { + fn default() -> Self { + Self::new() + } +} + +impl Clone for Queue +where + T: Clone, +{ + fn clone(&self) -> Self { + let mut new: Queue = Queue::new(); + + for s in self.iter() { + unsafe { + // NOTE(unsafe) new.capacity() == self.capacity() >= self.len() + // no overflow possible + new.enqueue_unchecked(s.clone()); + } + } + + new + } +} + +impl PartialEq> for Queue +where + T: PartialEq, +{ + fn eq(&self, other: &Queue) -> bool { + self.len() == other.len() && self.iter().zip(other.iter()).all(|(v1, v2)| v1 == v2) + } +} + +impl Eq for Queue where T: Eq {} + +/// An iterator over the items of a queue +pub struct Iter<'a, T, const N: usize> { + rb: &'a Queue, + index: usize, + len: usize, +} + +impl<'a, T, const N: usize> Clone for Iter<'a, T, N> { + fn clone(&self) -> Self { + Self { + rb: self.rb, + index: self.index, + len: self.len, + } + } +} + +/// A mutable iterator over the items of a queue +pub struct IterMut<'a, T, const N: usize> { + rb: &'a mut Queue, + index: usize, + len: usize, +} + +impl<'a, T, const N: usize> Iterator for Iter<'a, T, N> { + type Item = &'a T; + + fn next(&mut self) -> Option { + if self.index < self.len { + let head = self.rb.head.load(Ordering::Relaxed); + + let i = (head + self.index) % N; + self.index += 1; + + Some(unsafe { &*(self.rb.buffer.get_unchecked(i).get() as *const T) }) + } else { + None + } + } +} + +impl<'a, T, const N: usize> Iterator for IterMut<'a, T, N> { + type Item = &'a mut T; + + fn next(&mut self) -> Option { + if self.index < self.len { + let head = self.rb.head.load(Ordering::Relaxed); + + let i = (head + self.index) % N; + self.index += 1; + + Some(unsafe { &mut *(self.rb.buffer.get_unchecked(i).get() as *mut T) }) + } else { + None + } + } +} + +impl<'a, T, const N: usize> DoubleEndedIterator for Iter<'a, T, N> { + fn next_back(&mut self) -> Option { + if self.index < self.len { + let head = self.rb.head.load(Ordering::Relaxed); + + // self.len > 0, since it's larger than self.index > 0 + let i = (head + self.len - 1) % N; + self.len -= 1; + Some(unsafe { &*(self.rb.buffer.get_unchecked(i).get() as *const T) }) + } else { + None + } + } +} + +impl<'a, T, const N: usize> DoubleEndedIterator for IterMut<'a, T, N> { + fn next_back(&mut self) -> Option { + if self.index < self.len { + let head = self.rb.head.load(Ordering::Relaxed); + + // self.len > 0, since it's larger than self.index > 0 + let i = (head + self.len - 1) % N; + self.len -= 1; + Some(unsafe { &mut *(self.rb.buffer.get_unchecked(i).get() as *mut T) }) + } else { + None + } + } +} + +impl Drop for Queue { + fn drop(&mut self) { + for item in self { + unsafe { + ptr::drop_in_place(item); + } + } + } +} + +impl fmt::Debug for Queue +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.iter()).finish() + } +} + +impl hash::Hash for Queue +where + T: hash::Hash, +{ + fn hash(&self, state: &mut H) { + // iterate over self in order + for t in self.iter() { + hash::Hash::hash(t, state); + } + } +} + +impl hash32::Hash for Queue +where + T: hash32::Hash, +{ + fn hash(&self, state: &mut H) { + // iterate over self in order + for t in self.iter() { + hash32::Hash::hash(t, state); + } + } +} + +impl<'a, T, const N: usize> IntoIterator for &'a Queue { + type Item = &'a T; + type IntoIter = Iter<'a, T, N>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, T, const N: usize> IntoIterator for &'a mut Queue { + type Item = &'a mut T; + type IntoIter = IterMut<'a, T, N>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +/// A queue "consumer"; it can dequeue items from the queue +/// NOTE the consumer semantically owns the `head` pointer of the queue +pub struct Consumer<'a, T, const N: usize> { + rb: &'a Queue, +} + +unsafe impl<'a, T, const N: usize> Send for Consumer<'a, T, N> where T: Send {} + +/// A queue "producer"; it can enqueue items into the queue +/// NOTE the producer semantically owns the `tail` pointer of the queue +pub struct Producer<'a, T, const N: usize> { + rb: &'a Queue, +} + +unsafe impl<'a, T, const N: usize> Send for Producer<'a, T, N> where T: Send {} + +impl<'a, T, const N: usize> Consumer<'a, T, N> { + /// Returns the item in the front of the queue, or `None` if the queue is empty + #[inline] + pub fn dequeue(&mut self) -> Option { + unsafe { self.rb.inner_dequeue() } + } + + /// Returns the item in the front of the queue, without checking if there are elements in the + /// queue + /// + /// See [`Queue::dequeue_unchecked`] for safety + #[inline] + pub unsafe fn dequeue_unchecked(&mut self) -> T { + self.rb.inner_dequeue_unchecked() + } + + /// Returns if there are any items to dequeue. When this returns `true`, at least the + /// first subsequent dequeue will succeed + #[inline] + pub fn ready(&self) -> bool { + !self.rb.is_empty() + } + + /// Returns the number of elements in the queue + #[inline] + pub fn len(&self) -> usize { + self.rb.len() + } + + /// Returns the maximum number of elements the queue can hold + #[inline] + pub fn capacity(&self) -> usize { + self.rb.capacity() + } + + /// Returns the item in the front of the queue without dequeuing, or `None` if the queue is + /// empty + /// + /// # Examples + /// ``` + /// use heapless::spsc::Queue; + /// + /// let mut queue: Queue = Queue::new(); + /// let (mut producer, mut consumer) = queue.split(); + /// assert_eq!(None, consumer.peek()); + /// producer.enqueue(1); + /// assert_eq!(Some(&1), consumer.peek()); + /// assert_eq!(Some(1), consumer.dequeue()); + /// assert_eq!(None, consumer.peek()); + /// ``` + #[inline] + pub fn peek(&self) -> Option<&T> { + self.rb.peek() + } +} + +impl<'a, T, const N: usize> Producer<'a, T, N> { + /// Adds an `item` to the end of the queue, returns back the `item` if the queue is full + #[inline] + pub fn enqueue(&mut self, val: T) -> Result<(), T> { + unsafe { self.rb.inner_enqueue(val) } + } + + /// Adds an `item` to the end of the queue, without checking if the queue is full + /// + /// See [`Queue::enqueue_unchecked`] for safety + #[inline] + pub unsafe fn enqueue_unchecked(&mut self, val: T) { + self.rb.inner_enqueue_unchecked(val) + } + + /// Returns if there is any space to enqueue a new item. When this returns true, at + /// least the first subsequent enqueue will succeed. + #[inline] + pub fn ready(&self) -> bool { + !self.rb.is_full() + } + + /// Returns the number of elements in the queue + #[inline] + pub fn len(&self) -> usize { + self.rb.len() + } + + /// Returns the maximum number of elements the queue can hold + #[inline] + pub fn capacity(&self) -> usize { + self.rb.capacity() + } +} + +#[cfg(test)] +mod tests { + use crate::spsc::Queue; + use hash32::Hasher; + + #[test] + fn full() { + let mut rb: Queue = Queue::new(); + + assert_eq!(rb.is_full(), false); + + rb.enqueue(1).unwrap(); + assert_eq!(rb.is_full(), false); + + rb.enqueue(2).unwrap(); + assert_eq!(rb.is_full(), true); + } + + #[test] + fn empty() { + let mut rb: Queue = Queue::new(); + + assert_eq!(rb.is_empty(), true); + + rb.enqueue(1).unwrap(); + assert_eq!(rb.is_empty(), false); + + rb.enqueue(2).unwrap(); + assert_eq!(rb.is_empty(), false); + } + + #[test] + #[cfg_attr(miri, ignore)] // too slow + fn len() { + let mut rb: Queue = Queue::new(); + + assert_eq!(rb.len(), 0); + + rb.enqueue(1).unwrap(); + assert_eq!(rb.len(), 1); + + rb.enqueue(2).unwrap(); + assert_eq!(rb.len(), 2); + + for _ in 0..1_000_000 { + let v = rb.dequeue().unwrap(); + println!("{}", v); + rb.enqueue(v).unwrap(); + assert_eq!(rb.len(), 2); + } + } + + #[test] + #[cfg_attr(miri, ignore)] // too slow + fn try_overflow() { + const N: usize = 23; + let mut rb: Queue = Queue::new(); + + for i in 0..N as i32 - 1 { + rb.enqueue(i).unwrap(); + } + + for _ in 0..1_000_000 { + for i in 0..N as i32 - 1 { + let d = rb.dequeue().unwrap(); + assert_eq!(d, i); + rb.enqueue(i).unwrap(); + } + } + } + + #[test] + fn sanity() { + let mut rb: Queue = Queue::new(); + + let (mut p, mut c) = rb.split(); + + assert_eq!(p.ready(), true); + + assert_eq!(c.ready(), false); + + assert_eq!(c.dequeue(), None); + + p.enqueue(0).unwrap(); + + assert_eq!(c.dequeue(), Some(0)); + } + + #[test] + fn static_new() { + static mut _Q: Queue = Queue::new(); + } + + #[test] + fn drop() { + struct Droppable; + impl Droppable { + fn new() -> Self { + unsafe { + COUNT += 1; + } + Droppable + } + } + + impl Drop for Droppable { + fn drop(&mut self) { + unsafe { + COUNT -= 1; + } + } + } + + static mut COUNT: i32 = 0; + + { + let mut v: Queue = Queue::new(); + v.enqueue(Droppable::new()).ok().unwrap(); + v.enqueue(Droppable::new()).ok().unwrap(); + v.dequeue().unwrap(); + } + + assert_eq!(unsafe { COUNT }, 0); + + { + let mut v: Queue = Queue::new(); + v.enqueue(Droppable::new()).ok().unwrap(); + v.enqueue(Droppable::new()).ok().unwrap(); + } + + assert_eq!(unsafe { COUNT }, 0); + } + + #[test] + fn iter() { + let mut rb: Queue = Queue::new(); + + rb.enqueue(0).unwrap(); + rb.dequeue().unwrap(); + rb.enqueue(1).unwrap(); + rb.enqueue(2).unwrap(); + rb.enqueue(3).unwrap(); + + let mut items = rb.iter(); + + // assert_eq!(items.next(), Some(&0)); + assert_eq!(items.next(), Some(&1)); + assert_eq!(items.next(), Some(&2)); + assert_eq!(items.next(), Some(&3)); + assert_eq!(items.next(), None); + } + + #[test] + fn iter_double_ended() { + let mut rb: Queue = Queue::new(); + + rb.enqueue(0).unwrap(); + rb.enqueue(1).unwrap(); + rb.enqueue(2).unwrap(); + + let mut items = rb.iter(); + + assert_eq!(items.next(), Some(&0)); + assert_eq!(items.next_back(), Some(&2)); + assert_eq!(items.next(), Some(&1)); + assert_eq!(items.next(), None); + assert_eq!(items.next_back(), None); + } + + #[test] + fn iter_mut() { + let mut rb: Queue = Queue::new(); + + rb.enqueue(0).unwrap(); + rb.enqueue(1).unwrap(); + rb.enqueue(2).unwrap(); + + let mut items = rb.iter_mut(); + + assert_eq!(items.next(), Some(&mut 0)); + assert_eq!(items.next(), Some(&mut 1)); + assert_eq!(items.next(), Some(&mut 2)); + assert_eq!(items.next(), None); + } + + #[test] + fn iter_mut_double_ended() { + let mut rb: Queue = Queue::new(); + + rb.enqueue(0).unwrap(); + rb.enqueue(1).unwrap(); + rb.enqueue(2).unwrap(); + + let mut items = rb.iter_mut(); + + assert_eq!(items.next(), Some(&mut 0)); + assert_eq!(items.next_back(), Some(&mut 2)); + assert_eq!(items.next(), Some(&mut 1)); + assert_eq!(items.next(), None); + assert_eq!(items.next_back(), None); + } + + #[test] + fn wrap_around() { + let mut rb: Queue = Queue::new(); + + rb.enqueue(0).unwrap(); + rb.enqueue(1).unwrap(); + rb.enqueue(2).unwrap(); + rb.dequeue().unwrap(); + rb.dequeue().unwrap(); + rb.dequeue().unwrap(); + rb.enqueue(3).unwrap(); + rb.enqueue(4).unwrap(); + + assert_eq!(rb.len(), 2); + } + + #[test] + fn ready_flag() { + let mut rb: Queue = Queue::new(); + let (mut p, mut c) = rb.split(); + assert_eq!(c.ready(), false); + assert_eq!(p.ready(), true); + + p.enqueue(0).unwrap(); + + assert_eq!(c.ready(), true); + assert_eq!(p.ready(), true); + + p.enqueue(1).unwrap(); + + assert_eq!(c.ready(), true); + assert_eq!(p.ready(), false); + + c.dequeue().unwrap(); + + assert_eq!(c.ready(), true); + assert_eq!(p.ready(), true); + + c.dequeue().unwrap(); + + assert_eq!(c.ready(), false); + assert_eq!(p.ready(), true); + } + + #[test] + fn clone() { + let mut rb1: Queue = Queue::new(); + rb1.enqueue(0).unwrap(); + rb1.enqueue(0).unwrap(); + rb1.dequeue().unwrap(); + rb1.enqueue(0).unwrap(); + let rb2 = rb1.clone(); + assert_eq!(rb1.capacity(), rb2.capacity()); + assert_eq!(rb1.len(), rb2.len()); + assert!(rb1.iter().zip(rb2.iter()).all(|(v1, v2)| v1 == v2)); + } + + #[test] + fn eq() { + // generate two queues with same content + // but different buffer alignment + let mut rb1: Queue = Queue::new(); + rb1.enqueue(0).unwrap(); + rb1.enqueue(0).unwrap(); + rb1.dequeue().unwrap(); + rb1.enqueue(0).unwrap(); + let mut rb2: Queue = Queue::new(); + rb2.enqueue(0).unwrap(); + rb2.enqueue(0).unwrap(); + assert!(rb1 == rb2); + // test for symmetry + assert!(rb2 == rb1); + // test for changes in content + rb1.enqueue(0).unwrap(); + assert!(rb1 != rb2); + rb2.enqueue(1).unwrap(); + assert!(rb1 != rb2); + // test for refexive relation + assert!(rb1 == rb1); + assert!(rb2 == rb2); + } + + #[test] + fn hash_equality() { + // generate two queues with same content + // but different buffer alignment + let rb1 = { + let mut rb1: Queue = Queue::new(); + rb1.enqueue(0).unwrap(); + rb1.enqueue(0).unwrap(); + rb1.dequeue().unwrap(); + rb1.enqueue(0).unwrap(); + rb1 + }; + let rb2 = { + let mut rb2: Queue = Queue::new(); + rb2.enqueue(0).unwrap(); + rb2.enqueue(0).unwrap(); + rb2 + }; + let hash1 = { + let mut hasher1 = hash32::FnvHasher::default(); + hash32::Hash::hash(&rb1, &mut hasher1); + let hash1 = hasher1.finish(); + hash1 + }; + let hash2 = { + let mut hasher2 = hash32::FnvHasher::default(); + hash32::Hash::hash(&rb2, &mut hasher2); + let hash2 = hasher2.finish(); + hash2 + }; + assert_eq!(hash1, hash2); + } +} From 6083f3df49b13a1cef0f199eedfece7b84d1d374 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 11 Feb 2022 11:26:40 -0800 Subject: [PATCH 4/8] Change serial examples to just echo so they don't break by writing too much --- .../src/bin/{diecimila-usart.rs => diecimila-usart-echo.rs} | 4 ++-- .../src/bin/{leonardo-usart.rs => leonardo-usart-echo.rs} | 4 ++-- .../src/bin/{mega2560-usart.rs => mega2560-usart-echo.rs} | 4 ++-- .../arduino-uno/src/bin/{uno-usart.rs => uno-usart-echo.rs} | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) rename examples/arduino-diecimila/src/bin/{diecimila-usart.rs => diecimila-usart-echo.rs} (83%) rename examples/arduino-leonardo/src/bin/{leonardo-usart.rs => leonardo-usart-echo.rs} (82%) rename examples/arduino-mega2560/src/bin/{mega2560-usart.rs => mega2560-usart-echo.rs} (82%) rename examples/arduino-uno/src/bin/{uno-usart.rs => uno-usart-echo.rs} (79%) diff --git a/examples/arduino-diecimila/src/bin/diecimila-usart.rs b/examples/arduino-diecimila/src/bin/diecimila-usart-echo.rs similarity index 83% rename from examples/arduino-diecimila/src/bin/diecimila-usart.rs rename to examples/arduino-diecimila/src/bin/diecimila-usart-echo.rs index aecdd2b8a1..0efda81a22 100644 --- a/examples/arduino-diecimila/src/bin/diecimila-usart.rs +++ b/examples/arduino-diecimila/src/bin/diecimila-usart-echo.rs @@ -18,7 +18,7 @@ fn main() -> ! { // Read a byte from the serial connection default let b = nb::block!(serial.read()).void_unwrap(); - // Answer - ufmt::uwriteln!(&mut serial, "Got {}!\r", b).void_unwrap(); + // Echo the byte back to the serial connection + nb::block!(serial.write(b)).void_unwrap(); } } diff --git a/examples/arduino-leonardo/src/bin/leonardo-usart.rs b/examples/arduino-leonardo/src/bin/leonardo-usart-echo.rs similarity index 82% rename from examples/arduino-leonardo/src/bin/leonardo-usart.rs rename to examples/arduino-leonardo/src/bin/leonardo-usart-echo.rs index 9add152c06..ad342b4299 100644 --- a/examples/arduino-leonardo/src/bin/leonardo-usart.rs +++ b/examples/arduino-leonardo/src/bin/leonardo-usart-echo.rs @@ -18,7 +18,7 @@ fn main() -> ! { // Read a byte from the serial connection let b = nb::block!(serial.read()).void_unwrap(); - // Answer - ufmt::uwriteln!(&mut serial, "Got {}!\r", b).void_unwrap(); + // Echo the byte back to the serial connection + nb::block!(serial.write(b)).void_unwrap(); } } diff --git a/examples/arduino-mega2560/src/bin/mega2560-usart.rs b/examples/arduino-mega2560/src/bin/mega2560-usart-echo.rs similarity index 82% rename from examples/arduino-mega2560/src/bin/mega2560-usart.rs rename to examples/arduino-mega2560/src/bin/mega2560-usart-echo.rs index 9add152c06..ad342b4299 100644 --- a/examples/arduino-mega2560/src/bin/mega2560-usart.rs +++ b/examples/arduino-mega2560/src/bin/mega2560-usart-echo.rs @@ -18,7 +18,7 @@ fn main() -> ! { // Read a byte from the serial connection let b = nb::block!(serial.read()).void_unwrap(); - // Answer - ufmt::uwriteln!(&mut serial, "Got {}!\r", b).void_unwrap(); + // Echo the byte back to the serial connection + nb::block!(serial.write(b)).void_unwrap(); } } diff --git a/examples/arduino-uno/src/bin/uno-usart.rs b/examples/arduino-uno/src/bin/uno-usart-echo.rs similarity index 79% rename from examples/arduino-uno/src/bin/uno-usart.rs rename to examples/arduino-uno/src/bin/uno-usart-echo.rs index c29093c586..8d18157a46 100644 --- a/examples/arduino-uno/src/bin/uno-usart.rs +++ b/examples/arduino-uno/src/bin/uno-usart-echo.rs @@ -7,7 +7,7 @@ use arduino_hal::prelude::*; use panic_halt as _; -use embedded_hal::serial::Read; +use embedded_hal::serial::{Read, Write}; #[arduino_hal::entry] fn main() -> ! { @@ -21,7 +21,7 @@ fn main() -> ! { // Read a byte from the serial connection let b = nb::block!(serial.read()).void_unwrap(); - // Answer - ufmt::uwriteln!(&mut serial, "Got {}!\r", b).void_unwrap(); + // Echo the byte back to the serial connection + nb::block!(serial.write(b)).void_unwrap(); } } From 9619cb5f7cc4f6e5c8f830d25e62df09f06502ae Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 11 Feb 2022 11:27:17 -0800 Subject: [PATCH 5/8] Add robust serial example for Arduino Uno This is based off of how the Arduino C++ library does serial. It uses interrupts and buffers to avoid missing data. --- .../src/bin/uno-usart-interrupt.rs | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 examples/arduino-uno/src/bin/uno-usart-interrupt.rs diff --git a/examples/arduino-uno/src/bin/uno-usart-interrupt.rs b/examples/arduino-uno/src/bin/uno-usart-interrupt.rs new file mode 100644 index 0000000000..1de5d56674 --- /dev/null +++ b/examples/arduino-uno/src/bin/uno-usart-interrupt.rs @@ -0,0 +1,154 @@ +/*! + * A more robust serial example modeled after what the Arduino C++ library does. + * Enables sending and recieving via buffers and interrupts. + */ +#![no_std] +#![no_main] +#![feature(abi_avr_interrupt)] + +use panic_halt as _; + +// TODO: This should really use heapless::spsc::Queue or heapless::Deque. +// The update requires updating atomic_polyfill to support AVR. +// Which requires asm! to work correctly with avr. +use arduino_uno_examples::spsc::{Consumer, Producer, Queue}; +use avr_device::interrupt::Mutex; +use core::cell::RefCell; +use core::ops::DerefMut; +use embedded_hal::serial::{Read, Write}; + +static SERIAL: Mutex< + RefCell< + Option< + arduino_hal::Usart< + arduino_hal::pac::USART0, + arduino_hal::port::Pin, + arduino_hal::port::Pin< + arduino_hal::port::mode::Output, + arduino_hal::hal::port::PD1, + >, + >, + >, + >, +> = Mutex::new(RefCell::new(None)); + +static mut RX_QUEUE: Queue = Queue::new(); +static mut TX_QUEUE: Queue = Queue::new(); +static RX_PRODUCER: Mutex>>> = Mutex::new(RefCell::new(None)); +static RX_CONSUMER: Mutex>>> = Mutex::new(RefCell::new(None)); +static TX_PRODUCER: Mutex>>> = Mutex::new(RefCell::new(None)); +static TX_CONSUMER: Mutex>>> = Mutex::new(RefCell::new(None)); + +// Read into buffer via interupt. +#[avr_device::interrupt(atmega328p)] +unsafe fn USART_RX() { + avr_device::interrupt::free(|cs| { + if let Some(ref mut serial) = SERIAL.borrow(cs).borrow_mut().deref_mut() { + if let Ok(b) = serial.read() { + if let Some(ref mut rx_producer) = RX_PRODUCER.borrow(cs).borrow_mut().deref_mut() { + // Ignore data if the buffer is full. + let _ = rx_producer.enqueue(b); + } + } + }; + }); +} + +// Write to serial from buffer. +#[avr_device::interrupt(atmega328p)] +unsafe fn USART_UDRE() { + avr_device::interrupt::free(|cs| { + if let Some(ref mut serial) = SERIAL.borrow(cs).borrow_mut().deref_mut() { + if let Some(ref mut tx_consumer) = TX_CONSUMER.borrow(cs).borrow_mut().deref_mut() { + if let Some(b) = tx_consumer.dequeue() { + let _ = serial.write(b); + } else { + // Clear interrupt if we are out of data. + serial.unlisten(arduino_hal::hal::usart::Event::DataRegisterEmpty); + } + } + } + }); +} + +// This is a safe wrapper around the serial that avoids locking up the interrupts for too long. +// It also enables use with ufmt. +struct Serial {} + +impl Serial { + fn read(&mut self) -> Option { + avr_device::interrupt::free(|cs| { + if let Some(ref mut rx_consumer) = RX_CONSUMER.borrow(cs).borrow_mut().deref_mut() { + rx_consumer.dequeue() + } else { + None + } + }) + } +} + +impl ufmt::uWrite for Serial { + type Error = core::convert::Infallible; + + fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { + for b in s.as_bytes() { + // Poll until the queue has free space. + // Enable interrupts between each loop to ensure the CPU isn't hogged. + loop { + let ready = avr_device::interrupt::free(|cs| { + if let Some(ref mut tx_producer) = + TX_PRODUCER.borrow(cs).borrow_mut().deref_mut() + { + tx_producer.ready() + } else { + false + } + }); + if ready { + break; + } + } + // Write byte to the queue. + // Also, enable the serial data empty interrupt to start the send. + avr_device::interrupt::free(|cs| { + if let Some(ref mut tx_producer) = TX_PRODUCER.borrow(cs).borrow_mut().deref_mut() { + let _ = tx_producer.enqueue(*b); + } + if let Some(ref mut serial) = SERIAL.borrow(cs).borrow_mut().deref_mut() { + serial.listen(arduino_hal::hal::usart::Event::DataRegisterEmpty); + } + }); + } + Ok(()) + } +} + +#[arduino_hal::entry] +fn main() -> ! { + let dp = arduino_hal::Peripherals::take().unwrap(); + let pins = arduino_hal::pins!(dp); + + let mut serial = arduino_hal::default_serial!(dp, pins, 57600); + avr_device::interrupt::free(|cs| { + serial.listen(arduino_hal::hal::usart::Event::RxComplete); + SERIAL.borrow(cs).replace(Some(serial)); + // This is the only place the queues are ever directly used. + // They must be mutable statics for lifetime reasons. + let (rx_producer, rx_consumer) = unsafe { RX_QUEUE.split() }; + let (tx_producer, tx_consumer) = unsafe { TX_QUEUE.split() }; + RX_PRODUCER.borrow(cs).replace(Some(rx_producer)); + RX_CONSUMER.borrow(cs).replace(Some(rx_consumer)); + TX_CONSUMER.borrow(cs).replace(Some(tx_consumer)); + TX_PRODUCER.borrow(cs).replace(Some(tx_producer)); + }); + unsafe { avr_device::interrupt::enable() }; + + let mut serial = Serial {}; + loop { + // Read a byte from the serial connection + if let Some(b) = serial.read() { + // Answer + ufmt::uwriteln!(&mut serial, "Got {}!\r", b).unwrap(); + } + } +} From 382688374daab855bf0ad8099c60c1a7d8aa6295 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 11 Feb 2022 19:24:17 -0800 Subject: [PATCH 6/8] Remove setting udre to 0. It already resets to 0 --- avr-hal-generic/src/usart.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avr-hal-generic/src/usart.rs b/avr-hal-generic/src/usart.rs index 453f38eca3..4b584bf5f4 100644 --- a/avr-hal-generic/src/usart.rs +++ b/avr-hal-generic/src/usart.rs @@ -485,7 +485,7 @@ macro_rules! impl_usart_traditional { > for $USART { fn raw_init(&mut self, baudrate: $crate::usart::Baudrate) { self.[].write(|w| unsafe { w.bits(baudrate.ubrr) }); - self.[].write(|w| w.[]().bit(baudrate.u2x).[]().clear_bit()); + self.[].write(|w| w.[]().bit(baudrate.u2x)); // Enable receiver and transmitter but leave interrupts disabled. self.[].write(|w| w From e780e5d41df1d6821d518ef28322ac1e0fa5a3bb Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 12 Feb 2022 19:43:55 -0800 Subject: [PATCH 7/8] Fix missed imports in serial echo for other boards --- examples/arduino-diecimila/src/bin/diecimila-usart-echo.rs | 2 +- examples/arduino-leonardo/src/bin/leonardo-usart-echo.rs | 2 +- examples/arduino-mega2560/src/bin/mega2560-usart-echo.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/arduino-diecimila/src/bin/diecimila-usart-echo.rs b/examples/arduino-diecimila/src/bin/diecimila-usart-echo.rs index 0efda81a22..72e92ab060 100644 --- a/examples/arduino-diecimila/src/bin/diecimila-usart-echo.rs +++ b/examples/arduino-diecimila/src/bin/diecimila-usart-echo.rs @@ -4,7 +4,7 @@ use arduino_hal::prelude::*; use panic_halt as _; -use embedded_hal::serial::Read; +use embedded_hal::serial::{Read, Write}; #[arduino_hal::entry] fn main() -> ! { diff --git a/examples/arduino-leonardo/src/bin/leonardo-usart-echo.rs b/examples/arduino-leonardo/src/bin/leonardo-usart-echo.rs index ad342b4299..33413fd4a9 100644 --- a/examples/arduino-leonardo/src/bin/leonardo-usart-echo.rs +++ b/examples/arduino-leonardo/src/bin/leonardo-usart-echo.rs @@ -4,7 +4,7 @@ use arduino_hal::prelude::*; use panic_halt as _; -use embedded_hal::serial::Read; +use embedded_hal::serial::{Read, Write}; #[arduino_hal::entry] fn main() -> ! { diff --git a/examples/arduino-mega2560/src/bin/mega2560-usart-echo.rs b/examples/arduino-mega2560/src/bin/mega2560-usart-echo.rs index ad342b4299..33413fd4a9 100644 --- a/examples/arduino-mega2560/src/bin/mega2560-usart-echo.rs +++ b/examples/arduino-mega2560/src/bin/mega2560-usart-echo.rs @@ -4,7 +4,7 @@ use arduino_hal::prelude::*; use panic_halt as _; -use embedded_hal::serial::Read; +use embedded_hal::serial::{Read, Write}; #[arduino_hal::entry] fn main() -> ! { From 66df2487ad61211865c98a19e141d330efe25403 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 12 Feb 2022 19:45:16 -0800 Subject: [PATCH 8/8] Update missed serial examples for other boards --- .../src/bin/{nano168-usart.rs => nano168-usart-echo.rs} | 6 +++--- .../src/bin/{promicro-usart.rs => promicro-usart-echo.rs} | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) rename examples/nano168/src/bin/{nano168-usart.rs => nano168-usart-echo.rs} (76%) rename examples/sparkfun-promicro/src/bin/{promicro-usart.rs => promicro-usart-echo.rs} (76%) diff --git a/examples/nano168/src/bin/nano168-usart.rs b/examples/nano168/src/bin/nano168-usart-echo.rs similarity index 76% rename from examples/nano168/src/bin/nano168-usart.rs rename to examples/nano168/src/bin/nano168-usart-echo.rs index 9add152c06..33413fd4a9 100644 --- a/examples/nano168/src/bin/nano168-usart.rs +++ b/examples/nano168/src/bin/nano168-usart-echo.rs @@ -4,7 +4,7 @@ use arduino_hal::prelude::*; use panic_halt as _; -use embedded_hal::serial::Read; +use embedded_hal::serial::{Read, Write}; #[arduino_hal::entry] fn main() -> ! { @@ -18,7 +18,7 @@ fn main() -> ! { // Read a byte from the serial connection let b = nb::block!(serial.read()).void_unwrap(); - // Answer - ufmt::uwriteln!(&mut serial, "Got {}!\r", b).void_unwrap(); + // Echo the byte back to the serial connection + nb::block!(serial.write(b)).void_unwrap(); } } diff --git a/examples/sparkfun-promicro/src/bin/promicro-usart.rs b/examples/sparkfun-promicro/src/bin/promicro-usart-echo.rs similarity index 76% rename from examples/sparkfun-promicro/src/bin/promicro-usart.rs rename to examples/sparkfun-promicro/src/bin/promicro-usart-echo.rs index 9add152c06..33413fd4a9 100644 --- a/examples/sparkfun-promicro/src/bin/promicro-usart.rs +++ b/examples/sparkfun-promicro/src/bin/promicro-usart-echo.rs @@ -4,7 +4,7 @@ use arduino_hal::prelude::*; use panic_halt as _; -use embedded_hal::serial::Read; +use embedded_hal::serial::{Read, Write}; #[arduino_hal::entry] fn main() -> ! { @@ -18,7 +18,7 @@ fn main() -> ! { // Read a byte from the serial connection let b = nb::block!(serial.read()).void_unwrap(); - // Answer - ufmt::uwriteln!(&mut serial, "Got {}!\r", b).void_unwrap(); + // Echo the byte back to the serial connection + nb::block!(serial.write(b)).void_unwrap(); } }