From 3811c0a401281ece6e7adb24238ebf7ff39a2362 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 17 Dec 2021 12:50:48 +0100 Subject: [PATCH] Add adapter for implementing async traits for blocking types This allows writing drivers relying on async traits, while still functioning with implementations that already implement the embedded-hal traits. Add examples to stm32l4 for using this feature. --- embassy-stm32/Cargo.toml | 1 + embassy-stm32/src/usart/mod.rs | 31 +++- embassy-traits/Cargo.toml | 1 + embassy-traits/src/adapter.rs | 166 ++++++++++++++++++ embassy-traits/src/lib.rs | 1 + .../stm32l4/src/bin/i2c_blocking_async.rs | 29 +++ .../stm32l4/src/bin/spi_blocking_async.rs | 57 ++++++ .../stm32l4/src/bin/usart_blocking_async.rs | 32 ++++ 8 files changed, 316 insertions(+), 2 deletions(-) create mode 100644 embassy-traits/src/adapter.rs create mode 100644 examples/stm32l4/src/bin/i2c_blocking_async.rs create mode 100644 examples/stm32l4/src/bin/spi_blocking_async.rs create mode 100644 examples/stm32l4/src/bin/usart_blocking_async.rs diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 135f9a91..26400585 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -27,6 +27,7 @@ atomic-polyfill = "0.1.5" stm32-metapac = { version = "0.1.0", path = "../stm32-metapac", features = ["rt"] } vcell = { version = "0.1.3", optional = true } bxcan = "0.6.2" +nb = "1.0.0" seq-macro = "0.2.2" diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index b51a728c..7e4df619 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -192,8 +192,35 @@ impl<'d, T: Instance, TxDma, RxDma> Uart<'d, T, TxDma, RxDma> { } } -impl<'d, T: Instance, RxDma> embedded_hal::blocking::serial::Write - for Uart<'d, T, NoDma, RxDma> +impl<'d, T: Instance, TxDma, RxDma> embedded_hal::serial::Read for Uart<'d, T, TxDma, RxDma> { + type Error = Error; + fn read(&mut self) -> Result> { + let r = self.inner.regs(); + unsafe { + let sr = sr(r).read(); + if sr.pe() { + rdr(r).read_volatile(); + Err(nb::Error::Other(Error::Parity)) + } else if sr.fe() { + rdr(r).read_volatile(); + Err(nb::Error::Other(Error::Framing)) + } else if sr.ne() { + rdr(r).read_volatile(); + Err(nb::Error::Other(Error::Noise)) + } else if sr.ore() { + rdr(r).read_volatile(); + Err(nb::Error::Other(Error::Overrun)) + } else if sr.rxne() { + Ok(rdr(r).read_volatile()) + } else { + Err(nb::Error::WouldBlock) + } + } + } +} + +impl<'d, T: Instance, TxDma, RxDma> embedded_hal::blocking::serial::Write + for Uart<'d, T, TxDma, RxDma> { type Error = Error; fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { diff --git a/embassy-traits/Cargo.toml b/embassy-traits/Cargo.toml index c98b583e..a93cb3c5 100644 --- a/embassy-traits/Cargo.toml +++ b/embassy-traits/Cargo.toml @@ -10,3 +10,4 @@ std = [] [dependencies] defmt = { version = "0.3", optional = true } embedded-hal = { version = "0.2.6", features = ["unproven"] } +nb = "1.0.0" diff --git a/embassy-traits/src/adapter.rs b/embassy-traits/src/adapter.rs new file mode 100644 index 00000000..ce7dd411 --- /dev/null +++ b/embassy-traits/src/adapter.rs @@ -0,0 +1,166 @@ +use core::future::Future; +use embedded_hal::blocking; +use embedded_hal::serial; + +/// BlockingAsync is a wrapper that implements async traits using blocking peripherals. This allows +/// driver writers to depend on the async traits while still supporting embedded-hal peripheral implementations. +/// +/// BlockingAsync will implement any async trait that maps to embedded-hal traits implemented for the wrapped driver. +/// +/// Driver users are then free to choose which implementation that is available to them. +pub struct BlockingAsync { + wrapped: T, +} + +impl BlockingAsync { + /// Create a new instance of a wrapper for a given peripheral. + pub fn new(wrapped: T) -> Self { + Self { wrapped } + } +} + +// +// I2C implementatinos +// + +impl crate::i2c::I2c for BlockingAsync +where + E: 'static, + T: blocking::i2c::WriteRead + + blocking::i2c::Read + + blocking::i2c::Write, +{ + type Error = E; + + #[rustfmt::skip] + type WriteFuture<'a> where Self: 'a = impl Future> + 'a; + #[rustfmt::skip] + type ReadFuture<'a> where Self: 'a = impl Future> + 'a; + #[rustfmt::skip] + type WriteReadFuture<'a> where Self: 'a = impl Future> + 'a; + + fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { + async move { self.wrapped.read(address, buffer) } + } + + fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> { + async move { self.wrapped.write(address, bytes) } + } + + fn write_read<'a>( + &'a mut self, + address: u8, + bytes: &'a [u8], + buffer: &'a mut [u8], + ) -> Self::WriteReadFuture<'a> { + async move { self.wrapped.write_read(address, bytes, buffer) } + } +} + +// +// SPI implementatinos +// + +impl crate::spi::Spi for BlockingAsync +where + T: blocking::spi::Write, +{ + type Error = E; +} + +impl crate::spi::FullDuplex for BlockingAsync +where + E: 'static, + Word: Clone, + T: blocking::spi::Transfer + blocking::spi::Write, +{ + #[rustfmt::skip] + type WriteReadFuture<'a> where Word: 'a, Self: 'a = impl Future> + 'a; + + fn read_write<'a>( + &'a mut self, + read: &'a mut [Word], + write: &'a [Word], + ) -> Self::WriteReadFuture<'a> { + async move { + // Ensure we write the expected bytes + for i in 0..core::cmp::min(read.len(), write.len()) { + read[i] = write[i].clone(); + } + self.wrapped.transfer(read)?; + Ok(()) + } + } +} + +impl crate::spi::Write for BlockingAsync +where + E: 'static, + Word: Clone, + T: blocking::spi::Write, +{ + #[rustfmt::skip] + type WriteFuture<'a> where Word: 'a, Self: 'a = impl Future> + 'a; + + fn write<'a>(&'a mut self, data: &'a [Word]) -> Self::WriteFuture<'a> { + async move { self.wrapped.write(data) } + } +} + +impl crate::spi::Read for BlockingAsync +where + E: 'static, + Word: Clone, + T: blocking::spi::Transfer + blocking::spi::Write, +{ + #[rustfmt::skip] + type ReadFuture<'a> where Word: 'a, Self: 'a = impl Future> + 'a; + + fn read<'a>(&'a mut self, data: &'a mut [Word]) -> Self::ReadFuture<'a> { + async move { + self.wrapped.transfer(data)?; + Ok(()) + } + } +} + +// Uart implementatinos +impl crate::uart::Read for BlockingAsync +where + T: serial::Read, +{ + #[rustfmt::skip] + type ReadFuture<'a> where T: 'a = impl Future> + 'a; + fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { + async move { + let mut pos = 0; + while pos < buf.len() { + match self.wrapped.read() { + Err(nb::Error::WouldBlock) => {} + Err(_) => return Err(crate::uart::Error::Other), + Ok(b) => { + buf[pos] = b; + pos += 1; + } + } + } + Ok(()) + } + } +} + +impl crate::uart::Write for BlockingAsync +where + T: blocking::serial::Write, +{ + #[rustfmt::skip] + type WriteFuture<'a> where T: 'a = impl Future> + 'a; + fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { + async move { + self.wrapped + .bwrite_all(buf) + .map_err(|_| crate::uart::Error::Other)?; + self.wrapped.bflush().map_err(|_| crate::uart::Error::Other) + } + } +} diff --git a/embassy-traits/src/lib.rs b/embassy-traits/src/lib.rs index 65fb95bd..a5342b77 100644 --- a/embassy-traits/src/lib.rs +++ b/embassy-traits/src/lib.rs @@ -2,6 +2,7 @@ #![feature(generic_associated_types)] #![feature(type_alias_impl_trait)] +pub mod adapter; pub mod delay; pub mod flash; pub mod gpio; diff --git a/examples/stm32l4/src/bin/i2c_blocking_async.rs b/examples/stm32l4/src/bin/i2c_blocking_async.rs new file mode 100644 index 00000000..0339ed4d --- /dev/null +++ b/examples/stm32l4/src/bin/i2c_blocking_async.rs @@ -0,0 +1,29 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +#[path = "../example_common.rs"] +mod example_common; + +use embassy::executor::Spawner; +use embassy_stm32::dma::NoDma; +use embassy_stm32::i2c::I2c; +use embassy_stm32::interrupt; +use embassy_stm32::time::Hertz; +use embassy_stm32::Peripherals; +use embassy_traits::{adapter::BlockingAsync, i2c::I2c as _}; +use example_common::{info, unwrap}; + +const ADDRESS: u8 = 0x5F; +const WHOAMI: u8 = 0x0F; + +#[embassy::main] +async fn main(_spawner: Spawner, p: Peripherals) -> ! { + let irq = interrupt::take!(I2C2_EV); + let i2c = I2c::new(p.I2C2, p.PB10, p.PB11, irq, NoDma, NoDma, Hertz(100_000)); + let mut i2c = BlockingAsync::new(i2c); + + let mut data = [0u8; 1]; + unwrap!(i2c.write_read(ADDRESS, &[WHOAMI], &mut data).await); + info!("Whoami: {}", data[0]); +} diff --git a/examples/stm32l4/src/bin/spi_blocking_async.rs b/examples/stm32l4/src/bin/spi_blocking_async.rs new file mode 100644 index 00000000..f092706d --- /dev/null +++ b/examples/stm32l4/src/bin/spi_blocking_async.rs @@ -0,0 +1,57 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +#[path = "../example_common.rs"] +mod example_common; + +use embassy::executor::Spawner; +use embassy_stm32::dma::NoDma; +use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_stm32::spi::{Config, Spi}; +use embassy_stm32::time::Hertz; +use embassy_stm32::Peripherals; +use embassy_traits::{adapter::BlockingAsync, spi::FullDuplex}; +use embedded_hal::digital::v2::{InputPin, OutputPin}; +use example_common::*; + +#[embassy::main] +async fn main(_spawner: Spawner, p: Peripherals) { + info!("Hello World!"); + + let spi = Spi::new( + p.SPI3, + p.PC10, + p.PC12, + p.PC11, + NoDma, + NoDma, + Hertz(1_000_000), + Config::default(), + ); + + let mut spi = BlockingAsync::new(spi); + + // These are the pins for the Inventek eS-Wifi SPI Wifi Adapter. + + let _boot = Output::new(p.PB12, Level::Low, Speed::VeryHigh); + let _wake = Output::new(p.PB13, Level::Low, Speed::VeryHigh); + let mut reset = Output::new(p.PE8, Level::Low, Speed::VeryHigh); + let mut cs = Output::new(p.PE0, Level::High, Speed::VeryHigh); + let ready = Input::new(p.PE1, Pull::Up); + + cortex_m::asm::delay(100_000); + unwrap!(reset.set_high()); + cortex_m::asm::delay(100_000); + + while unwrap!(ready.is_low()) { + info!("waiting for ready"); + } + + let write = [0x0A; 10]; + let mut read = [0; 10]; + unwrap!(cs.set_low()); + spi.read_write(&mut read, &write).await.ok(); + unwrap!(cs.set_high()); + info!("xfer {=[u8]:x}", read); +} diff --git a/examples/stm32l4/src/bin/usart_blocking_async.rs b/examples/stm32l4/src/bin/usart_blocking_async.rs new file mode 100644 index 00000000..679d4e0d --- /dev/null +++ b/examples/stm32l4/src/bin/usart_blocking_async.rs @@ -0,0 +1,32 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +#[path = "../example_common.rs"] +mod example_common; + +use embassy::executor::Spawner; +use embassy::traits::{ + adapter::BlockingAsync, + uart::{Read, Write}, +}; +use embassy_stm32::dma::NoDma; +use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::Peripherals; +use example_common::*; + +#[embassy::main] +async fn main(_spawner: Spawner, p: Peripherals) { + let config = Config::default(); + let usart = Uart::new(p.UART4, p.PA1, p.PA0, NoDma, NoDma, config); + let mut usart = BlockingAsync::new(usart); + + unwrap!(usart.write(b"Hello Embassy World!\r\n").await); + info!("wrote Hello, starting echo"); + + let mut buf = [0u8; 1]; + loop { + unwrap!(usart.read(&mut buf).await); + unwrap!(usart.write(&buf).await); + } +}