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:
parent
ad2f469407
commit
3811c0a401
8 changed files with 316 additions and 2 deletions
|
@ -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"
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -10,3 +10,4 @@ std = []
|
|||
[dependencies]
|
||||
defmt = { version = "0.3", optional = true }
|
||||
embedded-hal = { version = "0.2.6", features = ["unproven"] }
|
||||
nb = "1.0.0"
|
||||
|
|
166
embassy-traits/src/adapter.rs
Normal file
166
embassy-traits/src/adapter.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
29
examples/stm32l4/src/bin/i2c_blocking_async.rs
Normal file
29
examples/stm32l4/src/bin/i2c_blocking_async.rs
Normal 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]);
|
||||
}
|
57
examples/stm32l4/src/bin/spi_blocking_async.rs
Normal file
57
examples/stm32l4/src/bin/spi_blocking_async.rs
Normal 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);
|
||||
}
|
32
examples/stm32l4/src/bin/usart_blocking_async.rs
Normal file
32
examples/stm32l4/src/bin/usart_blocking_async.rs
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue