From 41a35a66bd10509391b913fb005c0a74a76a3647 Mon Sep 17 00:00:00 2001 From: Mark Szente Date: Tue, 23 Jan 2024 15:26:00 +0100 Subject: [PATCH 1/3] Remove quasi-duplicate sync code using maybe-async-cfg --- Cargo.toml | 1 + i2c/Cargo.toml | 7 ++- i2c/src/asynch.rs | 85 ---------------------------- i2c/src/lib.rs | 58 ++++++++++++------- spi/Cargo.toml | 7 ++- spi/src/asynch.rs | 139 ---------------------------------------------- spi/src/lib.rs | 72 ++++++++++++++++++------ src/lib.rs | 12 +--- 8 files changed, 106 insertions(+), 275 deletions(-) delete mode 100644 i2c/src/asynch.rs delete mode 100644 spi/src/asynch.rs diff --git a/Cargo.toml b/Cargo.toml index 38e1378..8ff5a07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ all-features = true [dependencies] defmt = { version = "0.3", optional = true } +maybe-async-cfg = "0.2.3" [workspace] members = [ diff --git a/i2c/Cargo.toml b/i2c/Cargo.toml index 6ff12cc..9aaaac6 100644 --- a/i2c/Cargo.toml +++ b/i2c/Cargo.toml @@ -18,5 +18,10 @@ all-features = true [dependencies] embedded-hal = "1.0.0" -embedded-hal-async = "1.0.0" +embedded-hal-async = { version = "1.0.0", optional = true } display-interface = { version = "0.5.0", path = ".." } +maybe-async-cfg = "0.2.3" + +[features] +default = [] +async = ["embedded-hal-async"] \ No newline at end of file diff --git a/i2c/src/asynch.rs b/i2c/src/asynch.rs deleted file mode 100644 index 5160ea3..0000000 --- a/i2c/src/asynch.rs +++ /dev/null @@ -1,85 +0,0 @@ -use display_interface::{AsyncWriteOnlyDataCommand, DataFormat, DisplayError}; - -use crate::I2cInterface; - -impl AsyncWriteOnlyDataCommand for I2cInterface -where - I2C: embedded_hal_async::i2c::I2c, -{ - async fn send_commands(&mut self, cmds: DataFormat<'_>) -> Result<(), DisplayError> { - // Copy over given commands to new aray to prefix with command identifier - match cmds { - DataFormat::U8(slice) => { - let mut writebuf: [u8; 8] = [0; 8]; - writebuf[1..=slice.len()].copy_from_slice(&slice[0..slice.len()]); - - self.i2c - .write(self.addr, &writebuf[..=slice.len()]) - .await - .map_err(|_| DisplayError::BusWriteError) - } - _ => Err(DisplayError::DataFormatNotImplemented), - } - } - - async fn send_data(&mut self, buf: DataFormat<'_>) -> Result<(), DisplayError> { - match buf { - DataFormat::U8(slice) => { - // No-op if the data buffer is empty - if slice.is_empty() { - return Ok(()); - } - - let mut writebuf = [0; 17]; - - // Data mode - writebuf[0] = self.data_byte; - - for chunk in slice.chunks(16) { - let chunk_len = chunk.len(); - - // Copy over all data from buffer, leaving the data command byte intact - writebuf[1..=chunk_len].copy_from_slice(chunk); - - self.i2c - .write(self.addr, &writebuf[0..=chunk_len]) - .await - .map_err(|_| DisplayError::BusWriteError)?; - } - - Ok(()) - } - DataFormat::U8Iter(iter) => { - let mut writebuf = [0; 17]; - let mut i = 1; - let len = writebuf.len(); - - // Data mode - writebuf[0] = self.data_byte; - - for byte in iter.into_iter() { - writebuf[i] = byte; - i += 1; - - if i == len { - self.i2c - .write(self.addr, &writebuf[0..=len]) - .await - .map_err(|_| DisplayError::BusWriteError)?; - i = 1; - } - } - - if i > 1 { - self.i2c - .write(self.addr, &writebuf[0..=i]) - .await - .map_err(|_| DisplayError::BusWriteError)?; - } - - Ok(()) - } - _ => Err(DisplayError::DataFormatNotImplemented), - } - } -} diff --git a/i2c/src/lib.rs b/i2c/src/lib.rs index 7def8d1..0a222b4 100644 --- a/i2c/src/lib.rs +++ b/i2c/src/lib.rs @@ -1,10 +1,12 @@ -//! Generic I2C interface for display drivers - -#![no_std] - -mod asynch; - -use display_interface::{DataFormat, DisplayError, WriteOnlyDataCommand}; +#[cfg(feature = "async")] +use display_interface::AsyncWriteOnlyDataCommand; +#[cfg(not(feature = "async"))] +use display_interface::WriteOnlyDataCommand; +use display_interface::{DataFormat, DisplayError}; +#[cfg(not(feature = "async"))] +use embedded_hal::i2c::I2c; +#[cfg(feature = "async")] +use embedded_hal_async::i2c::I2c as AsyncI2c; /// I2C communication interface pub struct I2cInterface { @@ -30,11 +32,22 @@ impl I2cInterface { } } -impl WriteOnlyDataCommand for I2cInterface +#[maybe_async_cfg::maybe( + sync( + cfg(not(feature = "async")), + keep_self, + idents( + AsyncWriteOnlyDataCommand(sync = "WriteOnlyDataCommand"), + AsyncI2c(sync = "I2c"), + ) + ), + async(feature = "async", keep_self) +)] +impl AsyncWriteOnlyDataCommand for I2cInterface where - I2C: embedded_hal::i2c::I2c, + I2C: AsyncI2c, { - fn send_commands(&mut self, cmds: DataFormat<'_>) -> Result<(), DisplayError> { + async fn send_commands(&mut self, cmds: DataFormat<'_>) -> Result<(), DisplayError> { // Copy over given commands to new aray to prefix with command identifier match cmds { DataFormat::U8(slice) => { @@ -43,13 +56,14 @@ where self.i2c .write(self.addr, &writebuf[..=slice.len()]) + .await .map_err(|_| DisplayError::BusWriteError) } _ => Err(DisplayError::DataFormatNotImplemented), } } - fn send_data(&mut self, buf: DataFormat<'_>) -> Result<(), DisplayError> { + async fn send_data(&mut self, buf: DataFormat<'_>) -> Result<(), DisplayError> { match buf { DataFormat::U8(slice) => { // No-op if the data buffer is empty @@ -62,17 +76,19 @@ where // Data mode writebuf[0] = self.data_byte; - slice - .chunks(16) - .try_for_each(|c| { - let chunk_len = c.len(); + for chunk in slice.chunks(16) { + let chunk_len = chunk.len(); - // Copy over all data from buffer, leaving the data command byte intact - writebuf[1..=chunk_len].copy_from_slice(c); + // Copy over all data from buffer, leaving the data command byte intact + writebuf[1..=chunk_len].copy_from_slice(chunk); - self.i2c.write(self.addr, &writebuf[0..=chunk_len]) - }) - .map_err(|_| DisplayError::BusWriteError) + self.i2c + .write(self.addr, &writebuf[0..=chunk_len]) + .await + .map_err(|_| DisplayError::BusWriteError)?; + } + + Ok(()) } DataFormat::U8Iter(iter) => { let mut writebuf = [0; 17]; @@ -89,6 +105,7 @@ where if i == len { self.i2c .write(self.addr, &writebuf[0..=len]) + .await .map_err(|_| DisplayError::BusWriteError)?; i = 1; } @@ -97,6 +114,7 @@ where if i > 1 { self.i2c .write(self.addr, &writebuf[0..=i]) + .await .map_err(|_| DisplayError::BusWriteError)?; } diff --git a/spi/Cargo.toml b/spi/Cargo.toml index 73285ad..99fc732 100644 --- a/spi/Cargo.toml +++ b/spi/Cargo.toml @@ -18,6 +18,11 @@ all-features = true [dependencies] embedded-hal = "1.0.0" -embedded-hal-async = "1.0.0" +embedded-hal-async = { version = "1.0.0", optional = true } display-interface = { version = "0.5.0", path = ".." } byte-slice-cast = { version = "1.2.2", default-features = false } +maybe-async-cfg = "0.2.3" + +[features] +default = [] +async = ["embedded-hal-async"] \ No newline at end of file diff --git a/spi/src/asynch.rs b/spi/src/asynch.rs deleted file mode 100644 index ef62f39..0000000 --- a/spi/src/asynch.rs +++ /dev/null @@ -1,139 +0,0 @@ -//! Generic asynchronous SPI interface for display drivers - -use byte_slice_cast::*; -use embedded_hal::digital::OutputPin; -use embedded_hal_async::spi::SpiDevice; - -use display_interface::{AsyncWriteOnlyDataCommand, DataFormat, DisplayError}; - -use crate::{SpiInterface, BUFFER_SIZE}; - -type Result = core::result::Result<(), DisplayError>; - -async fn send_u8(spi: &mut SPI, words: DataFormat<'_>) -> Result -where - SPI: SpiDevice, -{ - match words { - DataFormat::U8(slice) => spi - .write(slice) - .await - .map_err(|_| DisplayError::BusWriteError), - DataFormat::U16(slice) => spi - .write(slice.as_byte_slice()) - .await - .map_err(|_| DisplayError::BusWriteError), - DataFormat::U16LE(slice) => { - for v in slice.as_mut() { - *v = v.to_le(); - } - spi.write(slice.as_byte_slice()) - .await - .map_err(|_| DisplayError::BusWriteError) - } - DataFormat::U16BE(slice) => { - for v in slice.as_mut() { - *v = v.to_be(); - } - spi.write(slice.as_byte_slice()) - .await - .map_err(|_| DisplayError::BusWriteError) - } - DataFormat::U8Iter(iter) => { - let mut buf = [0; BUFFER_SIZE]; - let mut i = 0; - - for v in iter.into_iter() { - buf[i] = v; - i += 1; - - if i == buf.len() { - spi.write(&buf) - .await - .map_err(|_| DisplayError::BusWriteError)?; - i = 0; - } - } - - if i > 0 { - spi.write(&buf[..i]) - .await - .map_err(|_| DisplayError::BusWriteError)?; - } - - Ok(()) - } - DataFormat::U16LEIter(iter) => { - let mut buf = [0; BUFFER_SIZE]; - let mut i = 0; - - for v in iter.map(u16::to_le) { - buf[i] = v; - i += 1; - - if i == buf.len() { - spi.write(buf.as_byte_slice()) - .await - .map_err(|_| DisplayError::BusWriteError)?; - i = 0; - } - } - - if i > 0 { - spi.write(buf[..i].as_byte_slice()) - .await - .map_err(|_| DisplayError::BusWriteError)?; - } - - Ok(()) - } - DataFormat::U16BEIter(iter) => { - let mut buf = [0; BUFFER_SIZE]; - let mut i = 0; - let len = buf.len(); - - for v in iter.map(u16::to_be) { - buf[i] = v; - i += 1; - - if i == len { - spi.write(buf.as_byte_slice()) - .await - .map_err(|_| DisplayError::BusWriteError)?; - i = 0; - } - } - - if i > 0 { - spi.write(buf[..i].as_byte_slice()) - .await - .map_err(|_| DisplayError::BusWriteError)?; - } - - Ok(()) - } - _ => Err(DisplayError::DataFormatNotImplemented), - } -} - -impl AsyncWriteOnlyDataCommand for SpiInterface -where - SPI: SpiDevice, - DC: OutputPin, -{ - async fn send_commands(&mut self, cmds: DataFormat<'_>) -> Result { - // 1 = data, 0 = command - self.dc.set_low().map_err(|_| DisplayError::DCError)?; - - // Send words over SPI - send_u8(&mut self.spi, cmds).await - } - - async fn send_data(&mut self, buf: DataFormat<'_>) -> Result { - // 1 = data, 0 = command - self.dc.set_high().map_err(|_| DisplayError::DCError)?; - - // Send words over SPI - send_u8(&mut self.spi, buf).await - } -} diff --git a/spi/src/lib.rs b/spi/src/lib.rs index 83cc799..58b5af9 100644 --- a/spi/src/lib.rs +++ b/spi/src/lib.rs @@ -1,31 +1,48 @@ -//! Generic SPI interface for display drivers - -#![no_std] - -mod asynch; +//! Generic asynchronous SPI interface for display drivers use byte_slice_cast::*; -use display_interface::{DataFormat, DisplayError, WriteOnlyDataCommand}; -use embedded_hal::{digital::OutputPin, spi::SpiDevice}; +#[cfg(feature = "async")] +use display_interface::AsyncWriteOnlyDataCommand; +#[cfg(not(feature = "async"))] +use display_interface::WriteOnlyDataCommand; + +use display_interface::{DataFormat, DisplayError}; +use embedded_hal::digital::OutputPin; +#[cfg(not(feature = "async"))] +use embedded_hal::spi::SpiDevice; +#[cfg(feature = "async")] +use embedded_hal_async::spi::SpiDevice as AsyncSpiDevice; type Result = core::result::Result<(), DisplayError>; - pub(crate) const BUFFER_SIZE: usize = 64; -fn send_u8(spi: &mut SPI, words: DataFormat<'_>) -> Result +#[maybe_async_cfg::maybe( + sync( + cfg(not(feature = "async")), + keep_self, + idents(AsyncSpiDevice(sync = "SpiDevice"),) + ), + async(feature = "async", keep_self) +)] +async fn send_u8(spi: &mut SPI, words: DataFormat<'_>) -> Result where - SPI: SpiDevice, + SPI: AsyncSpiDevice, { match words { - DataFormat::U8(slice) => spi.write(slice).map_err(|_| DisplayError::BusWriteError), + DataFormat::U8(slice) => spi + .write(slice) + .await + .map_err(|_| DisplayError::BusWriteError), DataFormat::U16(slice) => spi .write(slice.as_byte_slice()) + .await .map_err(|_| DisplayError::BusWriteError), DataFormat::U16LE(slice) => { for v in slice.as_mut() { *v = v.to_le(); } spi.write(slice.as_byte_slice()) + .await .map_err(|_| DisplayError::BusWriteError) } DataFormat::U16BE(slice) => { @@ -33,6 +50,7 @@ where *v = v.to_be(); } spi.write(slice.as_byte_slice()) + .await .map_err(|_| DisplayError::BusWriteError) } DataFormat::U8Iter(iter) => { @@ -44,13 +62,16 @@ where i += 1; if i == buf.len() { - spi.write(&buf).map_err(|_| DisplayError::BusWriteError)?; + spi.write(&buf) + .await + .map_err(|_| DisplayError::BusWriteError)?; i = 0; } } if i > 0 { spi.write(&buf[..i]) + .await .map_err(|_| DisplayError::BusWriteError)?; } @@ -66,6 +87,7 @@ where if i == buf.len() { spi.write(buf.as_byte_slice()) + .await .map_err(|_| DisplayError::BusWriteError)?; i = 0; } @@ -73,6 +95,7 @@ where if i > 0 { spi.write(buf[..i].as_byte_slice()) + .await .map_err(|_| DisplayError::BusWriteError)?; } @@ -89,6 +112,7 @@ where if i == len { spi.write(buf.as_byte_slice()) + .await .map_err(|_| DisplayError::BusWriteError)?; i = 0; } @@ -96,6 +120,7 @@ where if i > 0 { spi.write(buf[..i].as_byte_slice()) + .await .map_err(|_| DisplayError::BusWriteError)?; } @@ -126,24 +151,35 @@ impl SpiInterface { } } -impl WriteOnlyDataCommand for SpiInterface +#[maybe_async_cfg::maybe( + sync( + cfg(not(feature = "async")), + keep_self, + idents( + AsyncWriteOnlyDataCommand(sync = "WriteOnlyDataCommand"), + AsyncSpiDevice(sync = "SpiDevice"), + ) + ), + async(feature = "async", keep_self) +)] +impl AsyncWriteOnlyDataCommand for SpiInterface where - SPI: SpiDevice, + SPI: AsyncSpiDevice, DC: OutputPin, { - fn send_commands(&mut self, cmds: DataFormat<'_>) -> Result { + async fn send_commands(&mut self, cmds: DataFormat<'_>) -> Result { // 1 = data, 0 = command self.dc.set_low().map_err(|_| DisplayError::DCError)?; // Send words over SPI - send_u8(&mut self.spi, cmds) + send_u8(&mut self.spi, cmds).await } - fn send_data(&mut self, buf: DataFormat<'_>) -> Result { + async fn send_data(&mut self, buf: DataFormat<'_>) -> Result { // 1 = data, 0 = command self.dc.set_high().map_err(|_| DisplayError::DCError)?; // Send words over SPI - send_u8(&mut self.spi, buf) + send_u8(&mut self.spi, buf).await } } diff --git a/src/lib.rs b/src/lib.rs index 5973f68..b53d4c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,17 +53,7 @@ pub enum DataFormat<'a> { U16LEIter(&'a mut dyn Iterator), } -/// This trait implements a write-only interface for a display which has separate data and command -/// modes. It is the responsibility of implementations to activate the correct mode in their -/// implementation when corresponding method is called. -pub trait WriteOnlyDataCommand { - /// Send a batch of commands to display - fn send_commands(&mut self, cmd: DataFormat<'_>) -> Result<(), DisplayError>; - - /// Send pixel data to display - fn send_data(&mut self, buf: DataFormat<'_>) -> Result<(), DisplayError>; -} - +#[maybe_async_cfg::maybe(sync(self = "WriteOnlyDataCommand"), async(keep_self))] /// This trait implements a write-only interface for a display which has separate data and command /// modes. It is the responsibility of implementations to activate the correct mode in their /// implementation when corresponding method is called. From e82e9e6b2aec8508f1dd99b3c1abe7ca89aa7130 Mon Sep 17 00:00:00 2001 From: Mark Szente Date: Tue, 23 Jan 2024 15:30:55 +0100 Subject: [PATCH 2/3] Add documentation --- i2c/README.md | 6 ++++++ spi/README.md | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/i2c/README.md b/i2c/README.md index dfeaf38..37ad588 100644 --- a/i2c/README.md +++ b/i2c/README.md @@ -4,6 +4,12 @@ This Rust crate contains a generic I2C implementation of a data/command interface for displays over any I2C driver implementing the `embedded-hal`/`embedded-hal-async` `i2c::I2c` trait(s). +## Crate features + +Additional features can be enabled by adding the following features to your Cargo.toml. + +- `async`: adds `embedded-hal-async` `i2c::I2c` implementation. + ## License Licensed under either of diff --git a/spi/README.md b/spi/README.md index 0206f6c..c42b1cf 100644 --- a/spi/README.md +++ b/spi/README.md @@ -4,6 +4,12 @@ This Rust crate contains a generic SPI implementation of a data/command interface for displays over any SPI driver implementing the `embedded-hal`/`embedded-hal-async` `SpiDevice` trait(s). +## Crate features + +Additional features can be enabled by adding the following features to your Cargo.toml. + +- `async`: adds `embedded-hal-async` `SpiDevice` implementation. + ## License Licensed under either of From 4f7bddad5d3effb1ac267b5600ce17d2afd103ad Mon Sep 17 00:00:00 2001 From: Mark Szente Date: Tue, 23 Jan 2024 15:36:33 +0100 Subject: [PATCH 3/3] Add changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 107a7b9..946acda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## Changed - **Breaking** lib: `{SPI, I2C}Interface, PGPIO{8, 16}BitInterface` is renamed to `{Spi, I2c}Interface, PGpio{8, 16}BitInterface` +- **Breaking** i2c, spi: asynchronous implementations require the `async` feature +- simplified sync/async code by adding `maybe-async-cfg` crate to remove quasi-duplicate sync code ## [v0.5.0] - 2023-01-12