summaryrefslogtreecommitdiff
path: root/embassy-embedded-hal
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-embedded-hal')
-rw-r--r--embassy-embedded-hal/Cargo.toml17
-rw-r--r--embassy-embedded-hal/src/adapter.rs248
-rw-r--r--embassy-embedded-hal/src/lib.rs6
-rw-r--r--embassy-embedded-hal/src/shared_bus/i2c.rs120
-rw-r--r--embassy-embedded-hal/src/shared_bus/mod.rs4
-rw-r--r--embassy-embedded-hal/src/shared_bus/spi.rs110
6 files changed, 505 insertions, 0 deletions
diff --git a/embassy-embedded-hal/Cargo.toml b/embassy-embedded-hal/Cargo.toml
new file mode 100644
index 00000000..14b73dce
--- /dev/null
+++ b/embassy-embedded-hal/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "embassy-embedded-hal"
+version = "0.1.0"
+authors = ["Dario Nieuwenhuis <dirbaio@dirbaio.net>"]
+edition = "2018"
+
+[features]
+std = []
+
+[dependencies]
+embassy = { version = "0.1.0", path = "../embassy" }
+embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
+embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8" }
+embedded-hal-async = { version = "0.1.0-alpha.1" }
+embedded-storage = "0.3.0"
+embedded-storage-async = "0.3.0"
+nb = "1.0.0" \ No newline at end of file
diff --git a/embassy-embedded-hal/src/adapter.rs b/embassy-embedded-hal/src/adapter.rs
new file mode 100644
index 00000000..033efb7e
--- /dev/null
+++ b/embassy-embedded-hal/src/adapter.rs
@@ -0,0 +1,248 @@
+use core::future::Future;
+use embedded_hal_02::blocking;
+use embedded_hal_02::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 implementations
+//
+impl<T, E> embedded_hal_1::i2c::ErrorType for BlockingAsync<T>
+where
+ E: embedded_hal_1::i2c::Error + 'static,
+ T: blocking::i2c::WriteRead<Error = E>
+ + blocking::i2c::Read<Error = E>
+ + blocking::i2c::Write<Error = E>,
+{
+ type Error = E;
+}
+
+impl<T, E> embedded_hal_async::i2c::I2c for BlockingAsync<T>
+where
+ E: embedded_hal_1::i2c::Error + 'static,
+ T: blocking::i2c::WriteRead<Error = E>
+ + blocking::i2c::Read<Error = E>
+ + blocking::i2c::Write<Error = E>,
+{
+ type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
+ type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
+ type WriteReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: '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) }
+ }
+
+ type TransactionFuture<'a, 'b> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a, 'b: 'a;
+
+ fn transaction<'a, 'b>(
+ &'a mut self,
+ address: u8,
+ operations: &'a mut [embedded_hal_async::i2c::Operation<'b>],
+ ) -> Self::TransactionFuture<'a, 'b> {
+ let _ = address;
+ let _ = operations;
+ async move { todo!() }
+ }
+}
+
+//
+// SPI implementatinos
+//
+
+impl<T, E> embedded_hal_async::spi::ErrorType for BlockingAsync<T>
+where
+ E: embedded_hal_1::spi::Error,
+ T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
+{
+ type Error = E;
+}
+
+impl<T, E> embedded_hal_async::spi::SpiBus<u8> for BlockingAsync<T>
+where
+ E: embedded_hal_1::spi::Error + 'static,
+ T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
+{
+ type TransferFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
+
+ fn transfer<'a>(&'a mut self, read: &'a mut [u8], write: &'a [u8]) -> Self::TransferFuture<'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(())
+ }
+ }
+
+ type TransferInPlaceFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
+
+ fn transfer_in_place<'a>(&'a mut self, _: &'a mut [u8]) -> Self::TransferInPlaceFuture<'a> {
+ async move { todo!() }
+ }
+}
+
+impl<T, E> embedded_hal_async::spi::SpiBusFlush for BlockingAsync<T>
+where
+ E: embedded_hal_1::spi::Error + 'static,
+ T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
+{
+ type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
+
+ fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> {
+ async move { Ok(()) }
+ }
+}
+
+impl<T, E> embedded_hal_async::spi::SpiBusWrite<u8> for BlockingAsync<T>
+where
+ E: embedded_hal_1::spi::Error + 'static,
+ T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
+{
+ type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
+
+ fn write<'a>(&'a mut self, data: &'a [u8]) -> Self::WriteFuture<'a> {
+ async move {
+ self.wrapped.write(data)?;
+ Ok(())
+ }
+ }
+}
+
+impl<T, E> embedded_hal_async::spi::SpiBusRead<u8> for BlockingAsync<T>
+where
+ E: embedded_hal_1::spi::Error + 'static,
+ T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
+{
+ type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
+
+ fn read<'a>(&'a mut self, data: &'a mut [u8]) -> Self::ReadFuture<'a> {
+ async move {
+ self.wrapped.transfer(data)?;
+ Ok(())
+ }
+ }
+}
+
+// Uart implementatinos
+impl<T, E> embedded_hal_1::serial::ErrorType for BlockingAsync<T>
+where
+ T: serial::Read<u8, Error = E>,
+ E: embedded_hal_1::serial::Error + 'static,
+{
+ type Error = E;
+}
+
+#[cfg(feature = "_todo_embedded_hal_serial")]
+impl<T, E> embedded_hal_async::serial::Read for BlockingAsync<T>
+where
+ T: serial::Read<u8, Error = E>,
+ E: embedded_hal_1::serial::Error + 'static,
+{
+ type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where T: '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(nb::Error::Other(e)) => return Err(e),
+ Ok(b) => {
+ buf[pos] = b;
+ pos += 1;
+ }
+ }
+ }
+ Ok(())
+ }
+ }
+}
+
+#[cfg(feature = "_todo_embedded_hal_serial")]
+impl<T, E> embedded_hal_async::serial::Write for BlockingAsync<T>
+where
+ T: blocking::serial::Write<u8, Error = E> + serial::Read<u8, Error = E>,
+ E: embedded_hal_1::serial::Error + 'static,
+{
+ type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where T: 'a;
+ fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
+ async move { self.wrapped.bwrite_all(buf) }
+ }
+
+ type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where T: 'a;
+ fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> {
+ async move { self.wrapped.bflush() }
+ }
+}
+
+/// NOR flash wrapper
+use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
+use embedded_storage_async::nor_flash::{AsyncNorFlash, AsyncReadNorFlash};
+
+impl<T> ErrorType for BlockingAsync<T>
+where
+ T: ErrorType,
+{
+ type Error = T::Error;
+}
+
+impl<T> AsyncNorFlash for BlockingAsync<T>
+where
+ T: NorFlash,
+{
+ const WRITE_SIZE: usize = <T as NorFlash>::WRITE_SIZE;
+ const ERASE_SIZE: usize = <T as NorFlash>::ERASE_SIZE;
+
+ type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
+ fn write<'a>(&'a mut self, offset: u32, data: &'a [u8]) -> Self::WriteFuture<'a> {
+ async move { self.wrapped.write(offset, data) }
+ }
+
+ type EraseFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
+ fn erase<'a>(&'a mut self, from: u32, to: u32) -> Self::EraseFuture<'a> {
+ async move { self.wrapped.erase(from, to) }
+ }
+}
+
+impl<T> AsyncReadNorFlash for BlockingAsync<T>
+where
+ T: ReadNorFlash,
+{
+ const READ_SIZE: usize = <T as ReadNorFlash>::READ_SIZE;
+ type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
+ fn read<'a>(&'a mut self, address: u32, data: &'a mut [u8]) -> Self::ReadFuture<'a> {
+ async move { self.wrapped.read(address, data) }
+ }
+
+ fn capacity(&self) -> usize {
+ self.wrapped.capacity()
+ }
+}
diff --git a/embassy-embedded-hal/src/lib.rs b/embassy-embedded-hal/src/lib.rs
new file mode 100644
index 00000000..27ffa742
--- /dev/null
+++ b/embassy-embedded-hal/src/lib.rs
@@ -0,0 +1,6 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+#![feature(generic_associated_types)]
+#![feature(type_alias_impl_trait)]
+
+pub mod adapter;
+pub mod shared_bus;
diff --git a/embassy-embedded-hal/src/shared_bus/i2c.rs b/embassy-embedded-hal/src/shared_bus/i2c.rs
new file mode 100644
index 00000000..5a180e89
--- /dev/null
+++ b/embassy-embedded-hal/src/shared_bus/i2c.rs
@@ -0,0 +1,120 @@
+//! Asynchronous shared I2C bus
+//!
+//! # Example (nrf52)
+//!
+//! ```rust
+//! use embassy_embedded_hal::shared_bus::i2c::I2cBusDevice;
+//! use embassy::mutex::Mutex;
+//! use embassy::blocking_mutex::raw::ThreadModeRawMutex;
+//!
+//! static I2C_BUS: Forever<Mutex::<ThreadModeRawMutex, Twim<TWISPI0>>> = Forever::new();
+//! let config = twim::Config::default();
+//! let irq = interrupt::take!(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
+//! let i2c = Twim::new(p.TWISPI0, irq, p.P0_03, p.P0_04, config);
+//! let i2c_bus = Mutex::<ThreadModeRawMutex, _>::new(i2c);
+//! let i2c_bus = I2C_BUS.put(i2c_bus);
+//!
+//! // Device 1, using embedded-hal-async compatible driver for QMC5883L compass
+//! let i2c_dev1 = I2cBusDevice::new(i2c_bus);
+//! let compass = QMC5883L::new(i2c_dev1).await.unwrap();
+//!
+//! // Device 2, using embedded-hal-async compatible driver for Mpu6050 accelerometer
+//! let i2c_dev2 = I2cBusDevice::new(i2c_bus);
+//! let mpu = Mpu6050::new(i2c_dev2);
+//! ```
+use core::{fmt::Debug, future::Future};
+use embassy::blocking_mutex::raw::RawMutex;
+use embassy::mutex::Mutex;
+use embedded_hal_async::i2c;
+
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+pub enum I2cBusDeviceError<BUS> {
+ I2c(BUS),
+}
+
+impl<BUS> i2c::Error for I2cBusDeviceError<BUS>
+where
+ BUS: i2c::Error + Debug,
+{
+ fn kind(&self) -> i2c::ErrorKind {
+ match self {
+ Self::I2c(e) => e.kind(),
+ }
+ }
+}
+
+pub struct I2cBusDevice<'a, M: RawMutex, BUS> {
+ bus: &'a Mutex<M, BUS>,
+}
+
+impl<'a, M: RawMutex, BUS> I2cBusDevice<'a, M, BUS> {
+ pub fn new(bus: &'a Mutex<M, BUS>) -> Self {
+ Self { bus }
+ }
+}
+
+impl<'a, M: RawMutex, BUS> i2c::ErrorType for I2cBusDevice<'a, M, BUS>
+where
+ BUS: i2c::ErrorType,
+{
+ type Error = I2cBusDeviceError<BUS::Error>;
+}
+
+impl<M, BUS> i2c::I2c for I2cBusDevice<'_, M, BUS>
+where
+ M: RawMutex + 'static,
+ BUS: i2c::I2c + 'static,
+{
+ type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
+
+ fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> {
+ async move {
+ let mut bus = self.bus.lock().await;
+ bus.read(address, buffer)
+ .await
+ .map_err(I2cBusDeviceError::I2c)?;
+ Ok(())
+ }
+ }
+
+ type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
+
+ fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> {
+ async move {
+ let mut bus = self.bus.lock().await;
+ bus.write(address, bytes)
+ .await
+ .map_err(I2cBusDeviceError::I2c)?;
+ Ok(())
+ }
+ }
+
+ type WriteReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
+
+ fn write_read<'a>(
+ &'a mut self,
+ address: u8,
+ wr_buffer: &'a [u8],
+ rd_buffer: &'a mut [u8],
+ ) -> Self::WriteReadFuture<'a> {
+ async move {
+ let mut bus = self.bus.lock().await;
+ bus.write_read(address, wr_buffer, rd_buffer)
+ .await
+ .map_err(I2cBusDeviceError::I2c)?;
+ Ok(())
+ }
+ }
+
+ type TransactionFuture<'a, 'b> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a, 'b: 'a;
+
+ fn transaction<'a, 'b>(
+ &'a mut self,
+ address: u8,
+ operations: &'a mut [embedded_hal_async::i2c::Operation<'b>],
+ ) -> Self::TransactionFuture<'a, 'b> {
+ let _ = address;
+ let _ = operations;
+ async move { todo!() }
+ }
+}
diff --git a/embassy-embedded-hal/src/shared_bus/mod.rs b/embassy-embedded-hal/src/shared_bus/mod.rs
new file mode 100644
index 00000000..bd4fd2c3
--- /dev/null
+++ b/embassy-embedded-hal/src/shared_bus/mod.rs
@@ -0,0 +1,4 @@
+//! Shared bus implementations for embedded-hal-async
+
+pub mod i2c;
+pub mod spi;
diff --git a/embassy-embedded-hal/src/shared_bus/spi.rs b/embassy-embedded-hal/src/shared_bus/spi.rs
new file mode 100644
index 00000000..3ec064ba
--- /dev/null
+++ b/embassy-embedded-hal/src/shared_bus/spi.rs
@@ -0,0 +1,110 @@
+//! Asynchronous shared SPI bus
+//!
+//! # Example (nrf52)
+//!
+//! ```rust
+//! use embassy_embedded_hal::shared_bus::spi::SpiBusDevice;
+//! use embassy::mutex::Mutex;
+//! use embassy::blocking_mutex::raw::ThreadModeRawMutex;
+//!
+//! static SPI_BUS: Forever<Mutex<ThreadModeRawMutex, spim::Spim<SPI3>>> = Forever::new();
+//! let mut config = spim::Config::default();
+//! config.frequency = spim::Frequency::M32;
+//! let irq = interrupt::take!(SPIM3);
+//! let spi = spim::Spim::new_txonly(p.SPI3, irq, p.P0_15, p.P0_18, config);
+//! let spi_bus = Mutex::<ThreadModeRawMutex, _>::new(spi);
+//! let spi_bus = SPI_BUS.put(spi_bus);
+//!
+//! // Device 1, using embedded-hal-async compatible driver for ST7735 LCD display
+//! let cs_pin1 = Output::new(p.P0_24, Level::Low, OutputDrive::Standard);
+//! let spi_dev1 = SpiBusDevice::new(spi_bus, cs_pin1);
+//! let display1 = ST7735::new(spi_dev1, dc1, rst1, Default::default(), 160, 128);
+//!
+//! // Device 2
+//! let cs_pin2 = Output::new(p.P0_24, Level::Low, OutputDrive::Standard);
+//! let spi_dev2 = SpiBusDevice::new(spi_bus, cs_pin2);
+//! let display2 = ST7735::new(spi_dev2, dc2, rst2, Default::default(), 160, 128);
+//! ```
+use core::{fmt::Debug, future::Future};
+use embassy::blocking_mutex::raw::RawMutex;
+use embassy::mutex::Mutex;
+
+use embedded_hal_1::digital::blocking::OutputPin;
+use embedded_hal_1::spi::ErrorType;
+use embedded_hal_async::spi;
+
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+pub enum SpiBusDeviceError<BUS, CS> {
+ Spi(BUS),
+ Cs(CS),
+}
+
+impl<BUS, CS> spi::Error for SpiBusDeviceError<BUS, CS>
+where
+ BUS: spi::Error + Debug,
+ CS: Debug,
+{
+ fn kind(&self) -> spi::ErrorKind {
+ match self {
+ Self::Spi(e) => e.kind(),
+ Self::Cs(_) => spi::ErrorKind::Other,
+ }
+ }
+}
+
+pub struct SpiBusDevice<'a, M: RawMutex, BUS, CS> {
+ bus: &'a Mutex<M, BUS>,
+ cs: CS,
+}
+
+impl<'a, M: RawMutex, BUS, CS> SpiBusDevice<'a, M, BUS, CS> {
+ pub fn new(bus: &'a Mutex<M, BUS>, cs: CS) -> Self {
+ Self { bus, cs }
+ }
+}
+
+impl<'a, M: RawMutex, BUS, CS> spi::ErrorType for SpiBusDevice<'a, M, BUS, CS>
+where
+ BUS: spi::ErrorType,
+ CS: OutputPin,
+{
+ type Error = SpiBusDeviceError<BUS::Error, CS::Error>;
+}
+
+impl<M, BUS, CS> spi::SpiDevice for SpiBusDevice<'_, M, BUS, CS>
+where
+ M: RawMutex + 'static,
+ BUS: spi::SpiBusFlush + 'static,
+ CS: OutputPin,
+{
+ type Bus = BUS;
+
+ type TransactionFuture<'a, R, F, Fut> = impl Future<Output = Result<R, Self::Error>> + 'a
+ where
+ Self: 'a, R: 'a, F: FnOnce(*mut Self::Bus) -> Fut + 'a,
+ Fut: Future<Output = Result<R, <Self::Bus as ErrorType>::Error>> + 'a;
+
+ fn transaction<'a, R, F, Fut>(&'a mut self, f: F) -> Self::TransactionFuture<'a, R, F, Fut>
+ where
+ R: 'a,
+ F: FnOnce(*mut Self::Bus) -> Fut + 'a,
+ Fut: Future<Output = Result<R, <Self::Bus as ErrorType>::Error>> + 'a,
+ {
+ async move {
+ let mut bus = self.bus.lock().await;
+ self.cs.set_low().map_err(SpiBusDeviceError::Cs)?;
+
+ let f_res = f(&mut *bus).await;
+
+ // On failure, it's important to still flush and deassert CS.
+ let flush_res = bus.flush().await;
+ let cs_res = self.cs.set_high();
+
+ let f_res = f_res.map_err(SpiBusDeviceError::Spi)?;
+ flush_res.map_err(SpiBusDeviceError::Spi)?;
+ cs_res.map_err(SpiBusDeviceError::Cs)?;
+
+ Ok(f_res)
+ }
+ }
+}