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.
This commit is contained in:
Ulf Lilleengen 2021-12-17 12:50:48 +01:00
parent ad2f469407
commit 3811c0a401
8 changed files with 316 additions and 2 deletions

View file

@ -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"

View file

@ -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<u8>
for Uart<'d, T, NoDma, RxDma>
impl<'d, T: Instance, TxDma, RxDma> embedded_hal::serial::Read<u8> for Uart<'d, T, TxDma, RxDma> {
type Error = Error;
fn read(&mut self) -> Result<u8, nb::Error<Self::Error>> {
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<u8>
for Uart<'d, T, TxDma, RxDma>
{
type Error = Error;
fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> {

View file

@ -10,3 +10,4 @@ std = []
[dependencies]
defmt = { version = "0.3", optional = true }
embedded-hal = { version = "0.2.6", features = ["unproven"] }
nb = "1.0.0"

View file

@ -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<T> {
wrapped: T,
}
impl<T> BlockingAsync<T> {
/// Create a new instance of a wrapper for a given peripheral.
pub fn new(wrapped: T) -> Self {
Self { wrapped }
}
}
//
// I2C implementatinos
//
impl<T, E> crate::i2c::I2c for BlockingAsync<T>
where
E: 'static,
T: blocking::i2c::WriteRead<Error = E>
+ blocking::i2c::Read<Error = E>
+ blocking::i2c::Write<Error = E>,
{
type Error = E;
#[rustfmt::skip]
type WriteFuture<'a> where Self: 'a = impl Future<Output = Result<(), Self::Error>> + 'a;
#[rustfmt::skip]
type ReadFuture<'a> where Self: 'a = impl Future<Output = Result<(), Self::Error>> + 'a;
#[rustfmt::skip]
type WriteReadFuture<'a> where Self: 'a = impl Future<Output = Result<(), Self::Error>> + '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<T, E, Word> crate::spi::Spi<Word> for BlockingAsync<T>
where
T: blocking::spi::Write<Word, Error = E>,
{
type Error = E;
}
impl<T, E, Word> crate::spi::FullDuplex<Word> for BlockingAsync<T>
where
E: 'static,
Word: Clone,
T: blocking::spi::Transfer<Word, Error = E> + blocking::spi::Write<Word, Error = E>,
{
#[rustfmt::skip]
type WriteReadFuture<'a> where Word: 'a, Self: 'a = impl Future<Output = Result<(), Self::Error>> + '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<T, E, Word> crate::spi::Write<Word> for BlockingAsync<T>
where
E: 'static,
Word: Clone,
T: blocking::spi::Write<Word, Error = E>,
{
#[rustfmt::skip]
type WriteFuture<'a> where Word: 'a, Self: 'a = impl Future<Output = Result<(), Self::Error>> + 'a;
fn write<'a>(&'a mut self, data: &'a [Word]) -> Self::WriteFuture<'a> {
async move { self.wrapped.write(data) }
}
}
impl<T, E, Word> crate::spi::Read<Word> for BlockingAsync<T>
where
E: 'static,
Word: Clone,
T: blocking::spi::Transfer<Word, Error = E> + blocking::spi::Write<Word, Error = E>,
{
#[rustfmt::skip]
type ReadFuture<'a> where Word: 'a, Self: 'a = impl Future<Output = Result<(), Self::Error>> + 'a;
fn read<'a>(&'a mut self, data: &'a mut [Word]) -> Self::ReadFuture<'a> {
async move {
self.wrapped.transfer(data)?;
Ok(())
}
}
}
// Uart implementatinos
impl<T> crate::uart::Read for BlockingAsync<T>
where
T: serial::Read<u8>,
{
#[rustfmt::skip]
type ReadFuture<'a> where T: 'a = impl Future<Output = Result<(), crate::uart::Error>> + '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<T> crate::uart::Write for BlockingAsync<T>
where
T: blocking::serial::Write<u8>,
{
#[rustfmt::skip]
type WriteFuture<'a> where T: 'a = impl Future<Output = Result<(), crate::uart::Error>> + '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)
}
}
}

View file

@ -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;

View file

@ -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]);
}

View file

@ -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);
}

View file

@ -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);
}
}