From 808e7b1a038c3dc60a212383b9e37761e08021c2 Mon Sep 17 00:00:00 2001 From: Sjoerd Simons Date: Thu, 27 Jun 2024 21:50:16 +0200 Subject: [PATCH] Add async support use maybe_async_cfg to add an async interface as well, enabled by the "async" feature. When enabled an Ssd1306Async struct is added with the same api as Ssd1306, only async. --- .github/workflows/ci.yml | 4 + CHANGELOG.md | 1 + Cargo.toml | 17 ++- examples/async_i2c_spi.rs | 127 +++++++++++++++++ examples/async_terminal_i2c.rs | 64 +++++++++ src/command.rs | 126 ++++++++++------- src/lib.rs | 248 +++++++++++++++++++++++---------- src/mode/buffered_graphics.rs | 77 +++++++++- src/mode/mod.rs | 5 +- src/mode/terminal.rs | 158 +++++++++++++++++---- src/prelude.rs | 3 + src/size.rs | 104 ++++++++++---- 12 files changed, 752 insertions(+), 182 deletions(-) create mode 100644 examples/async_i2c_spi.rs create mode 100644 examples/async_terminal_i2c.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 93311cf7..118e3b82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,9 @@ jobs: with: toolchain: 1.75 - run: cargo test --lib --target x86_64-unknown-linux-gnu + - run: cargo test --lib --target x86_64-unknown-linux-gnu --features async - run: cargo test --doc --target x86_64-unknown-linux-gnu + - run: cargo test --doc --target x86_64-unknown-linux-gnu --features async build: strategy: @@ -68,6 +70,8 @@ jobs: components: rustfmt - run: rustup target add ${{matrix.target}} - run: cargo build --target ${{matrix.target}} --all-features --release + - if: ${{ matrix.examples }} + run: cargo build --target ${{matrix.target}} --examples --release - if: ${{ matrix.examples }} run: cargo build --target ${{matrix.target}} --all-features --examples --release - run: cargo doc --all-features --target ${{matrix.target }} diff --git a/CHANGELOG.md b/CHANGELOG.md index d6a004b8..bf96ad09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ SSD1306 monochrome OLED display. - Updated dependencies for `embedded-hal` 1.0.0. - Switch examples to embassy STM32 PAC which implements `embedded-hal` 1.0.0 traits. +- Add an async interface, enabled via the async feature. - **(breaking)** Increased MSRV to 1.75.0 ### Added diff --git a/Cargo.toml b/Cargo.toml index 1ed08e6a..54dd88ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ rust-version = "1.75.0" [package.metadata.docs.rs] targets = [ "thumbv7m-none-eabi", "thumbv7em-none-eabihf" ] +all-features = true [dependencies] embedded-hal = "1.0.0" @@ -22,6 +23,8 @@ display-interface = "0.5.0" display-interface-i2c = "0.5.0" display-interface-spi = "0.5.0" embedded-graphics-core = { version = "0.4.0", optional = true } +embedded-hal-async = { version = "1.0.0", optional = true } +maybe-async-cfg = "0.2.4" [dev-dependencies] embedded-graphics = "0.8.0" @@ -37,13 +40,25 @@ panic-probe = { version = "0.3.1", features = ["print-defmt"] } tinybmp = "0.5.0" # Used by the noise_i2c examples rand = { version = "0.8.4", default-features = false, features = [ "small_rng" ] } +embassy-embedded-hal = { version = "0.1.0", git = "https://github.com/embassy-rs/embassy" } +embassy-executor = { version = "0.5.0", git = "https://github.com/embassy-rs/embassy", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-stm32 = { version = "0.1.0", git = "https://github.com/embassy-rs/embassy", features = [ "stm32f103c8", "memory-x", "defmt", "exti", "time-driver-tim3" , "unstable-pac"] } embassy-time = { version = "0.3.1", git = "https://github.com/embassy-rs/embassy" } -embedded-hal-bus = "0.2.0" +embassy-futures = "0.1.1" +embedded-hal-bus = { version = "0.2.0", features = ["async"]} [features] default = ["graphics"] graphics = ["embedded-graphics-core"] +async = [ "dep:embedded-hal-async" ] + +[[example]] +name = "async_i2c_spi" +required-features = [ "async" ] + +[[example]] +name = "async_terminal_i2c" +required-features = [ "async" ] [profile.dev] opt-level="s" diff --git a/examples/async_i2c_spi.rs b/examples/async_i2c_spi.rs new file mode 100644 index 00000000..28bbe12d --- /dev/null +++ b/examples/async_i2c_spi.rs @@ -0,0 +1,127 @@ +//! Draw a 1 bit per pixel black and white rust logo to 128x64 SSD1306 displays over both I2C +//! and SPI. This uses async an approach to transfer to i2c and spi simultaniously using DMA +//! +//! This example is for the STM32F103 "Blue Pill" board using I2C1 and SPI1. +//! +//! Wiring connections are as follows for a CRIUS-branded display: +//! +//! ``` +//! I2c Display -> Blue Pill +//! GND -> GND +//! +5V -> VCC +//! SDA -> PB7 +//! SCL -> PB6 +//! +//! SPI display -> Blue Pill +//! GND -> GND +//! 3V3 -> VCC +//! PA5 -> SCL (D0) +//! PA7 -> SDA (D1) +//! PB0 -> RST +//! PB1 -> D/C +//! PB10 -> CS +//! ``` +//! +//! Run on a Blue Pill with `cargo run --example async_i2c_spi`. + +#![no_std] +#![no_main] + +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::{bind_interrupts, gpio, i2c, peripherals, spi::Spi, time::Hertz, Config}; +use embedded_graphics::{ + image::{Image, ImageRaw}, + pixelcolor::BinaryColor, + prelude::*, +}; +use panic_probe as _; +use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306Async}; + +bind_interrupts!(struct Irqs { + I2C1_EV => i2c::EventInterruptHandler; + I2C1_ER => i2c::ErrorInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config: Config = Default::default(); + config.rcc.hse = Some(embassy_stm32::rcc::Hse { + freq: Hertz::mhz(8), + mode: embassy_stm32::rcc::HseMode::Oscillator, + }); + config.rcc.sys = embassy_stm32::rcc::Sysclk::PLL1_P; + config.rcc.pll = Some(embassy_stm32::rcc::Pll { + src: embassy_stm32::rcc::PllSource::HSE, + prediv: embassy_stm32::rcc::PllPreDiv::DIV1, + mul: embassy_stm32::rcc::PllMul::MUL9, // 8 * 9 = 72Mhz + }); + + // Scale down to 36Mhz (maximum allowed) + config.rcc.apb1_pre = embassy_stm32::rcc::APBPrescaler::DIV2; + let p = embassy_stm32::init(config); + + // I2C + let i2c = embassy_stm32::i2c::I2c::new( + p.I2C1, + p.PB6, + p.PB7, + Irqs, + p.DMA1_CH6, + p.DMA1_CH7, + // According to the datasheet the stm32f1xx only supports up to 400khz, but 1mhz seems to + // work just fine. WHen having issues try changing this to Hertz::khz(400). + Hertz::mhz(1), + Default::default(), + ); + + let interface = I2CDisplayInterface::new(i2c); + let mut display_i2c = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) + .into_buffered_graphics_mode(); + + // SPI + let spi = Spi::new_txonly(p.SPI1, p.PA5, p.PA7, p.DMA1_CH3, Default::default()); + + let mut rst = gpio::Output::new(p.PB0, gpio::Level::Low, gpio::Speed::Low); + let dc = gpio::Output::new(p.PB1, gpio::Level::Low, gpio::Speed::Low); + let cs = gpio::Output::new(p.PB10, gpio::Level::Low, gpio::Speed::Low); + let spi = embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi, cs).unwrap(); + + let interface = SPIInterface::new(spi, dc); + let mut display_spi = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) + .into_buffered_graphics_mode(); + + // Init and reset both displays as needed + join( + async { + display_i2c.init().await.unwrap(); + }, + async { + display_spi + .reset(&mut rst, &mut embassy_time::Delay {}) + .await + .unwrap(); + display_spi.init().await.unwrap(); + }, + ) + .await; + + let raw: ImageRaw = ImageRaw::new(include_bytes!("./rust.raw"), 64); + + for i in (0..=64).chain((0..64).rev()).cycle() { + let top_left = Point::new(i, 0); + let im = Image::new(&raw, top_left); + + im.draw(&mut display_i2c).unwrap(); + im.draw(&mut display_spi).unwrap(); + + join(async { display_i2c.flush().await.unwrap() }, async { + display_spi.flush().await.unwrap() + }) + .await; + + display_i2c.clear(BinaryColor::Off).unwrap(); + display_spi.clear(BinaryColor::Off).unwrap(); + } +} diff --git a/examples/async_terminal_i2c.rs b/examples/async_terminal_i2c.rs new file mode 100644 index 00000000..b21e5458 --- /dev/null +++ b/examples/async_terminal_i2c.rs @@ -0,0 +1,64 @@ +//! Endlessly fill the screen with characters from the alphabet +//! +//! This example is for the STM32F103 "Blue Pill" board using I2C1. +//! +//! Wiring connections are as follows for a CRIUS-branded display: +//! +//! ``` +//! Display -> Blue Pill +//! (black) GND -> GND +//! (red) +5V -> VCC +//! (yellow) SDA -> PB7 +//! (green) SCL -> PB6 +//! ``` +//! +//! Run on a Blue Pill with `cargo run --example async_terminal_i2c`. + +#![no_std] +#![no_main] + +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::{bind_interrupts, i2c, peripherals, time::Hertz}; +use panic_probe as _; +use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306Async}; + +bind_interrupts!(struct Irqs { + I2C1_EV => i2c::EventInterruptHandler; + I2C1_ER => i2c::ErrorInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let i2c = embassy_stm32::i2c::I2c::new( + p.I2C1, + p.PB6, + p.PB7, + Irqs, + p.DMA1_CH6, + p.DMA1_CH7, + Hertz::khz(400), + Default::default(), + ); + + let interface = I2CDisplayInterface::new(i2c); + let mut display = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) + .into_terminal_mode(); + display.init().await.unwrap(); + let _ = display.clear().await; + + /* Endless loop */ + loop { + for c in 97..123 { + let _ = display + .write_str(unsafe { core::str::from_utf8_unchecked(&[c]) }) + .await; + } + for c in 65..91 { + let _ = display + .write_str(unsafe { core::str::from_utf8_unchecked(&[c]) }) + .await; + } + } +} diff --git a/src/command.rs b/src/command.rs index c203ccde..b68ec261 100644 --- a/src/command.rs +++ b/src/command.rs @@ -2,9 +2,12 @@ // Shamefully taken from https://github.com/EdgewaterDevelopment/rust-ssd1306 +#[cfg(feature = "async")] +use display_interface::AsyncWriteOnlyDataCommand; use display_interface::{DataFormat::U8, DisplayError, WriteOnlyDataCommand}; /// SSD1306 Commands +#[maybe_async_cfg::maybe(sync(keep_self), async(feature = "async"))] #[derive(Debug, Copy, Clone)] pub enum Command { /// Set contrast. Higher number is higher contrast. Default = 0x7F @@ -87,85 +90,110 @@ pub enum Command { InternalIref(bool, bool), } +#[maybe_async_cfg::maybe( + sync(keep_self), + async( + feature = "async", + idents(WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand")) + ) +)] impl Command { /// Send command to SSD1306 - pub fn send(self, iface: &mut DI) -> Result<(), DisplayError> + pub async fn send(self, iface: &mut DI) -> Result<(), DisplayError> where DI: WriteOnlyDataCommand, { match self { - Command::Contrast(val) => Self::send_commands(iface, &[0x81, val]), - Command::AllOn(on) => Self::send_commands(iface, &[0xA4 | (on as u8)]), - Command::Invert(inv) => Self::send_commands(iface, &[0xA6 | (inv as u8)]), - Command::DisplayOn(on) => Self::send_commands(iface, &[0xAE | (on as u8)]), - Command::HScrollSetup(dir, start, end, rate) => Self::send_commands( - iface, - &[ - 0x26 | (dir as u8), - 0, - start as u8, - rate as u8, - end as u8, - 0, - 0xFF, - ], - ), - Command::VHScrollSetup(dir, start, end, rate, offset) => Self::send_commands( - iface, - &[ - 0x28 | (dir as u8), - 0, - start as u8, - rate as u8, - end as u8, - offset, - ], - ), - Command::EnableScroll(en) => Self::send_commands(iface, &[0x2E | (en as u8)]), - Command::VScrollArea(above, lines) => Self::send_commands(iface, &[0xA3, above, lines]), - Command::LowerColStart(addr) => Self::send_commands(iface, &[0xF & addr]), - Command::UpperColStart(addr) => Self::send_commands(iface, &[0x10 | (0xF & addr)]), + Command::Contrast(val) => Self::send_commands(iface, &[0x81, val]).await, + Command::AllOn(on) => Self::send_commands(iface, &[0xA4 | (on as u8)]).await, + Command::Invert(inv) => Self::send_commands(iface, &[0xA6 | (inv as u8)]).await, + Command::DisplayOn(on) => Self::send_commands(iface, &[0xAE | (on as u8)]).await, + Command::HScrollSetup(dir, start, end, rate) => { + Self::send_commands( + iface, + &[ + 0x26 | (dir as u8), + 0, + start as u8, + rate as u8, + end as u8, + 0, + 0xFF, + ], + ) + .await + } + Command::VHScrollSetup(dir, start, end, rate, offset) => { + Self::send_commands( + iface, + &[ + 0x28 | (dir as u8), + 0, + start as u8, + rate as u8, + end as u8, + offset, + ], + ) + .await + } + Command::EnableScroll(en) => Self::send_commands(iface, &[0x2E | (en as u8)]).await, + Command::VScrollArea(above, lines) => { + Self::send_commands(iface, &[0xA3, above, lines]).await + } + Command::LowerColStart(addr) => Self::send_commands(iface, &[0xF & addr]).await, + Command::UpperColStart(addr) => { + Self::send_commands(iface, &[0x10 | (0xF & addr)]).await + } Command::ColStart(addr) => { - Self::send_commands(iface, &[0xF & addr, 0x10 | (0xF & (addr >> 4))]) + Self::send_commands(iface, &[0xF & addr, 0x10 | (0xF & (addr >> 4))]).await + } + Command::AddressMode(mode) => Self::send_commands(iface, &[0x20, mode as u8]).await, + Command::ColumnAddress(start, end) => { + Self::send_commands(iface, &[0x21, start, end]).await } - Command::AddressMode(mode) => Self::send_commands(iface, &[0x20, mode as u8]), - Command::ColumnAddress(start, end) => Self::send_commands(iface, &[0x21, start, end]), Command::PageAddress(start, end) => { - Self::send_commands(iface, &[0x22, start as u8, end as u8]) + Self::send_commands(iface, &[0x22, start as u8, end as u8]).await + } + Command::PageStart(page) => Self::send_commands(iface, &[0xB0 | (page as u8)]).await, + Command::StartLine(line) => Self::send_commands(iface, &[0x40 | (0x3F & line)]).await, + Command::SegmentRemap(remap) => { + Self::send_commands(iface, &[0xA0 | (remap as u8)]).await + } + Command::Multiplex(ratio) => Self::send_commands(iface, &[0xA8, ratio]).await, + Command::ReverseComDir(rev) => { + Self::send_commands(iface, &[0xC0 | ((rev as u8) << 3)]).await } - Command::PageStart(page) => Self::send_commands(iface, &[0xB0 | (page as u8)]), - Command::StartLine(line) => Self::send_commands(iface, &[0x40 | (0x3F & line)]), - Command::SegmentRemap(remap) => Self::send_commands(iface, &[0xA0 | (remap as u8)]), - Command::Multiplex(ratio) => Self::send_commands(iface, &[0xA8, ratio]), - Command::ReverseComDir(rev) => Self::send_commands(iface, &[0xC0 | ((rev as u8) << 3)]), - Command::DisplayOffset(offset) => Self::send_commands(iface, &[0xD3, offset]), + Command::DisplayOffset(offset) => Self::send_commands(iface, &[0xD3, offset]).await, Command::ComPinConfig(alt, lr) => { Self::send_commands(iface, &[0xDA, 0x2 | ((alt as u8) << 4) | ((lr as u8) << 5)]) + .await } Command::DisplayClockDiv(fosc, div) => { - Self::send_commands(iface, &[0xD5, ((0xF & fosc) << 4) | (0xF & div)]) + Self::send_commands(iface, &[0xD5, ((0xF & fosc) << 4) | (0xF & div)]).await } Command::PreChargePeriod(phase1, phase2) => { - Self::send_commands(iface, &[0xD9, ((0xF & phase2) << 4) | (0xF & phase1)]) + Self::send_commands(iface, &[0xD9, ((0xF & phase2) << 4) | (0xF & phase1)]).await } Command::VcomhDeselect(level) => { - Self::send_commands(iface, &[0xDB, (level as u8) << 4]) + Self::send_commands(iface, &[0xDB, (level as u8) << 4]).await } - Command::Noop => Self::send_commands(iface, &[0xE3]), + Command::Noop => Self::send_commands(iface, &[0xE3]).await, Command::ChargePump(en) => { - Self::send_commands(iface, &[0x8D, 0x10 | ((en as u8) << 2)]) + Self::send_commands(iface, &[0x8D, 0x10 | ((en as u8) << 2)]).await } Command::InternalIref(en, current) => { Self::send_commands(iface, &[0xAD, ((current as u8) << 5) | ((en as u8) << 4)]) + .await } } } - fn send_commands(iface: &mut DI, data: &[u8]) -> Result<(), DisplayError> + async fn send_commands(iface: &mut DI, data: &[u8]) -> Result<(), DisplayError> where DI: WriteOnlyDataCommand, { - iface.send_commands(U8(data)) + iface.send_commands(U8(data)).await } } diff --git a/src/lib.rs b/src/lib.rs index 7cedc54a..cc8c33b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,6 +107,7 @@ #![deny(unused_import_braces)] #![deny(unused_qualifications)] #![deny(rustdoc::broken_intra_doc_links)] +#![allow(async_fn_in_trait)] mod brightness; pub mod command; @@ -124,17 +125,28 @@ use core::convert::Infallible; pub use crate::i2c_interface::I2CDisplayInterface; use crate::mode::BasicMode; use brightness::Brightness; +#[cfg(feature = "async")] +use command::CommandAsync; use command::{AddrMode, Command, VcomhLevel}; +#[cfg(feature = "async")] +use display_interface::AsyncWriteOnlyDataCommand; use display_interface::{DataFormat::U8, DisplayError, WriteOnlyDataCommand}; use embedded_hal::{delay::DelayNs, digital::OutputPin}; +#[cfg(feature = "async")] +use embedded_hal_async::delay::DelayNs as DelayNsAsync; use error::Error; use mode::{BufferedGraphicsMode, TerminalMode}; +#[cfg(feature = "async")] +use mode::{BufferedGraphicsModeAsync, TerminalModeAsync}; use rotation::DisplayRotation; use size::DisplaySize; +#[cfg(feature = "async")] +use size::DisplaySizeAsync; /// SSD1306 driver. /// /// Note that some methods are only available when the display is configured in a certain [`mode`]. +#[maybe_async_cfg::maybe(sync(keep_self), async(feature = "async"))] #[derive(Copy, Clone, Debug)] pub struct Ssd1306 { interface: DI, @@ -144,9 +156,12 @@ pub struct Ssd1306 { rotation: DisplayRotation, } +#[maybe_async_cfg::maybe( + sync(keep_self,), + async(feature = "async", idents(DisplaySize(async = "DisplaySizeAsync"))) +)] impl Ssd1306 where - DI: WriteOnlyDataCommand, SIZE: DisplaySize, { /// Create a basic SSD1306 interface. @@ -163,9 +178,19 @@ where } } +#[maybe_async_cfg::maybe( + sync(keep_self,), + async( + feature = "async", + idents( + DisplaySize(async = "DisplaySizeAsync"), + BufferedGraphicsMode(async = "BufferedGraphicsModeAsync"), + TerminalMode(async = "TerminalModeAsync"), + ) + ) +)] impl Ssd1306 where - DI: WriteOnlyDataCommand, SIZE: DisplaySize, { /// Convert the display into another interface mode. @@ -193,29 +218,54 @@ where pub fn into_terminal_mode(self) -> Ssd1306 { self.into_mode(TerminalMode::new()) } +} +#[maybe_async_cfg::maybe( + sync(keep_self), + async( + feature = "async", + idents( + Command(async = "CommandAsync"), + DisplaySize(async = "DisplaySizeAsync"), + WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand"), + ) + ) +)] +impl Ssd1306 +where + DI: WriteOnlyDataCommand, + SIZE: DisplaySize, +{ /// Initialise the display in one of the available addressing modes. - pub fn init_with_addr_mode(&mut self, mode: AddrMode) -> Result<(), DisplayError> { + pub async fn init_with_addr_mode(&mut self, mode: AddrMode) -> Result<(), DisplayError> { let rotation = self.rotation; - Command::DisplayOn(false).send(&mut self.interface)?; - Command::DisplayClockDiv(0x8, 0x0).send(&mut self.interface)?; - Command::Multiplex(SIZE::HEIGHT - 1).send(&mut self.interface)?; - Command::DisplayOffset(0).send(&mut self.interface)?; - Command::StartLine(0).send(&mut self.interface)?; + Command::DisplayOn(false).send(&mut self.interface).await?; + Command::DisplayClockDiv(0x8, 0x0) + .send(&mut self.interface) + .await?; + Command::Multiplex(SIZE::HEIGHT - 1) + .send(&mut self.interface) + .await?; + Command::DisplayOffset(0).send(&mut self.interface).await?; + Command::StartLine(0).send(&mut self.interface).await?; // TODO: Ability to turn charge pump on/off - Command::ChargePump(true).send(&mut self.interface)?; - Command::AddressMode(mode).send(&mut self.interface)?; - - self.size.configure(&mut self.interface)?; - self.set_rotation(rotation)?; - - self.set_brightness(Brightness::default())?; - Command::VcomhDeselect(VcomhLevel::Auto).send(&mut self.interface)?; - Command::AllOn(false).send(&mut self.interface)?; - Command::Invert(false).send(&mut self.interface)?; - Command::EnableScroll(false).send(&mut self.interface)?; - Command::DisplayOn(true).send(&mut self.interface)?; + Command::ChargePump(true).send(&mut self.interface).await?; + Command::AddressMode(mode).send(&mut self.interface).await?; + + self.size.configure(&mut self.interface).await?; + self.set_rotation(rotation).await?; + + self.set_brightness(Brightness::default()).await?; + Command::VcomhDeselect(VcomhLevel::Auto) + .send(&mut self.interface) + .await?; + Command::AllOn(false).send(&mut self.interface).await?; + Command::Invert(false).send(&mut self.interface).await?; + Command::EnableScroll(false) + .send(&mut self.interface) + .await?; + Command::DisplayOn(true).send(&mut self.interface).await?; self.addr_mode = mode; @@ -223,8 +273,8 @@ where } /// Change the addressing mode - pub fn set_addr_mode(&mut self, mode: AddrMode) -> Result<(), DisplayError> { - Command::AddressMode(mode).send(&mut self.interface)?; + pub async fn set_addr_mode(&mut self, mode: AddrMode) -> Result<(), DisplayError> { + Command::AddressMode(mode).send(&mut self.interface).await?; self.addr_mode = mode; Ok(()) } @@ -234,7 +284,7 @@ where /// this method. /// /// This method takes advantage of a bounding box for faster writes. - pub fn bounded_draw( + pub async fn bounded_draw( &mut self, buffer: &[u8], disp_width: usize, @@ -248,11 +298,12 @@ where upper_left, lower_right, ) + .await } /// Send a raw buffer to the display. - pub fn draw(&mut self, buffer: &[u8]) -> Result<(), DisplayError> { - self.interface.send_data(U8(buffer)) + pub async fn draw(&mut self, buffer: &[u8]) -> Result<(), DisplayError> { + self.interface.send_data(U8(buffer)).await } /// Get display dimensions, taking into account the current rotation of the display @@ -290,25 +341,41 @@ where } /// Set the display rotation. - pub fn set_rotation(&mut self, rotation: DisplayRotation) -> Result<(), DisplayError> { + pub async fn set_rotation(&mut self, rotation: DisplayRotation) -> Result<(), DisplayError> { self.rotation = rotation; match rotation { DisplayRotation::Rotate0 => { - Command::SegmentRemap(true).send(&mut self.interface)?; - Command::ReverseComDir(true).send(&mut self.interface)?; + Command::SegmentRemap(true) + .send(&mut self.interface) + .await?; + Command::ReverseComDir(true) + .send(&mut self.interface) + .await?; } DisplayRotation::Rotate90 => { - Command::SegmentRemap(false).send(&mut self.interface)?; - Command::ReverseComDir(true).send(&mut self.interface)?; + Command::SegmentRemap(false) + .send(&mut self.interface) + .await?; + Command::ReverseComDir(true) + .send(&mut self.interface) + .await?; } DisplayRotation::Rotate180 => { - Command::SegmentRemap(false).send(&mut self.interface)?; - Command::ReverseComDir(false).send(&mut self.interface)?; + Command::SegmentRemap(false) + .send(&mut self.interface) + .await?; + Command::ReverseComDir(false) + .send(&mut self.interface) + .await?; } DisplayRotation::Rotate270 => { - Command::SegmentRemap(true).send(&mut self.interface)?; - Command::ReverseComDir(false).send(&mut self.interface)?; + Command::SegmentRemap(true) + .send(&mut self.interface) + .await?; + Command::ReverseComDir(false) + .send(&mut self.interface) + .await?; } }; @@ -316,53 +383,80 @@ where } /// Set mirror enabled/disabled. - pub fn set_mirror(&mut self, mirror: bool) -> Result<(), DisplayError> { + pub async fn set_mirror(&mut self, mirror: bool) -> Result<(), DisplayError> { if mirror { match self.rotation { DisplayRotation::Rotate0 => { - Command::SegmentRemap(false).send(&mut self.interface)?; - Command::ReverseComDir(true).send(&mut self.interface)?; + Command::SegmentRemap(false) + .send(&mut self.interface) + .await?; + Command::ReverseComDir(true) + .send(&mut self.interface) + .await?; } DisplayRotation::Rotate90 => { - Command::SegmentRemap(false).send(&mut self.interface)?; - Command::ReverseComDir(false).send(&mut self.interface)?; + Command::SegmentRemap(false) + .send(&mut self.interface) + .await?; + Command::ReverseComDir(false) + .send(&mut self.interface) + .await?; } DisplayRotation::Rotate180 => { - Command::SegmentRemap(true).send(&mut self.interface)?; - Command::ReverseComDir(false).send(&mut self.interface)?; + Command::SegmentRemap(true) + .send(&mut self.interface) + .await?; + Command::ReverseComDir(false) + .send(&mut self.interface) + .await?; } DisplayRotation::Rotate270 => { - Command::SegmentRemap(true).send(&mut self.interface)?; - Command::ReverseComDir(true).send(&mut self.interface)?; + Command::SegmentRemap(true) + .send(&mut self.interface) + .await?; + Command::ReverseComDir(true) + .send(&mut self.interface) + .await?; } }; } else { - self.set_rotation(self.rotation)?; + self.set_rotation(self.rotation).await?; } Ok(()) } /// Change the display brightness. - pub fn set_brightness(&mut self, brightness: Brightness) -> Result<(), DisplayError> { - Command::PreChargePeriod(1, brightness.precharge).send(&mut self.interface)?; - Command::Contrast(brightness.contrast).send(&mut self.interface) + pub async fn set_brightness(&mut self, brightness: Brightness) -> Result<(), DisplayError> { + Command::PreChargePeriod(1, brightness.precharge) + .send(&mut self.interface) + .await?; + Command::Contrast(brightness.contrast) + .send(&mut self.interface) + .await } /// Turn the display on or off. The display can be drawn to and retains all /// of its memory even while off. - pub fn set_display_on(&mut self, on: bool) -> Result<(), DisplayError> { - Command::DisplayOn(on).send(&mut self.interface) + pub async fn set_display_on(&mut self, on: bool) -> Result<(), DisplayError> { + Command::DisplayOn(on).send(&mut self.interface).await } /// Set the position in the framebuffer of the display limiting where any sent data should be /// drawn. This method can be used for changing the affected area on the screen as well /// as (re-)setting the start point of the next `draw` call. - pub fn set_draw_area(&mut self, start: (u8, u8), end: (u8, u8)) -> Result<(), DisplayError> { - Command::ColumnAddress(start.0, end.0.saturating_sub(1)).send(&mut self.interface)?; + pub async fn set_draw_area( + &mut self, + start: (u8, u8), + end: (u8, u8), + ) -> Result<(), DisplayError> { + Command::ColumnAddress(start.0, end.0.saturating_sub(1)) + .send(&mut self.interface) + .await?; if self.addr_mode != AddrMode::Page { Command::PageAddress(start.1.into(), (end.1.saturating_sub(1)).into()) - .send(&mut self.interface)?; + .send(&mut self.interface) + .await?; } Ok(()) @@ -370,8 +464,8 @@ where /// Set the column address in the framebuffer of the display where any sent data should be /// drawn. - pub fn set_column(&mut self, column: u8) -> Result<(), DisplayError> { - Command::ColStart(column).send(&mut self.interface) + pub async fn set_column(&mut self, column: u8) -> Result<(), DisplayError> { + Command::ColStart(column).send(&mut self.interface).await } /// Set the page address (row 8px high) in the framebuffer of the display where any sent data @@ -379,16 +473,18 @@ where /// /// Note that the parameter is in pixels, but the page will be set to the start of the 8px /// row which contains the passed-in row. - pub fn set_row(&mut self, row: u8) -> Result<(), DisplayError> { - Command::PageStart(row.into()).send(&mut self.interface) + pub async fn set_row(&mut self, row: u8) -> Result<(), DisplayError> { + Command::PageStart(row.into()) + .send(&mut self.interface) + .await } /// Set the screen pixel on/off inversion - pub fn set_invert(&mut self, invert: bool) -> Result<(), DisplayError> { - Command::Invert(invert).send(&mut self.interface) + pub async fn set_invert(&mut self, invert: bool) -> Result<(), DisplayError> { + Command::Invert(invert).send(&mut self.interface).await } - fn flush_buffer_chunks( + async fn flush_buffer_chunks( interface: &mut DI, buffer: &[u8], disp_width: usize, @@ -406,24 +502,34 @@ where let page_lower = upper_left.0 as usize; let page_upper = lower_right.0 as usize; - buffer + for c in buffer .chunks(disp_width) .skip(starting_page) .take(num_pages) .map(|s| &s[page_lower..page_upper]) - .try_for_each(|c| interface.send_data(U8(c))) - } - - /// Release the contained interface. - pub fn release(self) -> DI { - self.interface + { + interface.send_data(U8(c)).await? + } + Ok(()) } } // SPI-only reset +#[maybe_async_cfg::maybe( + sync(keep_self), + async( + feature = "async", + idents( + Command(async = "CommandAsync"), + DisplaySize(async = "DisplaySizeAsync"), + WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand"), + DelayNs(async = "DelayNsAsync") + ) + ) +)] impl Ssd1306 { /// Reset the display. - pub fn reset( + pub async fn reset( &mut self, rst: &mut RST, delay: &mut DELAY, @@ -432,18 +538,18 @@ impl Ssd1306 { RST: OutputPin, DELAY: DelayNs, { - fn inner_reset(rst: &mut RST, delay: &mut DELAY) -> Result<(), RST::Error> + async fn inner_reset(rst: &mut RST, delay: &mut DELAY) -> Result<(), RST::Error> where RST: OutputPin, DELAY: DelayNs, { rst.set_high()?; - delay.delay_ms(1); + delay.delay_ms(1).await; rst.set_low()?; - delay.delay_ms(10); + delay.delay_ms(10).await; rst.set_high() } - inner_reset(rst, delay).map_err(Error::Pin) + inner_reset(rst, delay).await.map_err(Error::Pin) } } diff --git a/src/mode/buffered_graphics.rs b/src/mode/buffered_graphics.rs index 82570b0d..3e6695c5 100644 --- a/src/mode/buffered_graphics.rs +++ b/src/mode/buffered_graphics.rs @@ -6,6 +6,10 @@ use crate::{ size::{DisplaySize, NewZeroed}, Ssd1306, }; +#[cfg(feature = "async")] +use crate::{size::DisplaySizeAsync, Ssd1306Async}; +#[cfg(feature = "async")] +use display_interface::AsyncWriteOnlyDataCommand; use display_interface::{DisplayError, WriteOnlyDataCommand}; /// Buffered graphics mode. @@ -14,6 +18,10 @@ use display_interface::{DisplayError, WriteOnlyDataCommand}; /// buffer is drawn to by [`set_pixel`](Ssd1306::set_pixel) commands or /// [`embedded-graphics`](https://docs.rs/embedded-graphics) commands. The display can then be /// updated using the [`flush`](Ssd1306::flush) method. +#[maybe_async_cfg::maybe( + sync(keep_self), + async(feature = "async", idents(DisplaySize(async = "DisplaySizeAsync"))) +)] #[derive(Clone, Debug)] pub struct BufferedGraphicsMode where @@ -26,6 +34,10 @@ where max_y: u8, } +#[maybe_async_cfg::maybe( + sync(keep_self), + async(feature = "async", idents(DisplaySize(async = "DisplaySizeAsync"))) +)] impl BufferedGraphicsMode where SIZE: DisplaySize, @@ -42,6 +54,18 @@ where } } +#[maybe_async_cfg::maybe( + sync(keep_self), + async( + feature = "async", + idents( + DisplaySize(async = "DisplaySizeAsync"), + DisplayConfig(async = "DisplayConfigAsync"), + WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand"), + BufferedGraphicsMode(async = "BufferedGraphicsModeAsync"), + ) + ) +)] impl DisplayConfig for Ssd1306> where DI: WriteOnlyDataCommand, @@ -52,17 +76,28 @@ where /// Set the display rotation /// /// This method resets the cursor but does not clear the screen. - fn set_rotation(&mut self, rot: DisplayRotation) -> Result<(), DisplayError> { - self.set_rotation(rot) + async fn set_rotation(&mut self, rot: DisplayRotation) -> Result<(), DisplayError> { + self.set_rotation(rot).await } /// Initialise and clear the display in graphics mode. - fn init(&mut self) -> Result<(), DisplayError> { + async fn init(&mut self) -> Result<(), DisplayError> { self.clear_impl(false); - self.init_with_addr_mode(AddrMode::Horizontal) + self.init_with_addr_mode(AddrMode::Horizontal).await } } +#[maybe_async_cfg::maybe( + sync(keep_self), + async( + feature = "async", + idents( + DisplaySize(async = "DisplaySizeAsync"), + WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand"), + BufferedGraphicsMode(async = "BufferedGraphicsModeAsync") + ) + ) +)] impl Ssd1306> where DI: WriteOnlyDataCommand, @@ -86,7 +121,7 @@ where /// Write out data to a display. /// /// This only updates the parts of the display that have changed since the last flush. - pub fn flush(&mut self) -> Result<(), DisplayError> { + pub async fn flush(&mut self) -> Result<(), DisplayError> { // Nothing to do if no pixels have changed since the last update if self.mode.max_x < self.mode.min_x || self.mode.max_y < self.mode.min_y { return Ok(()); @@ -129,7 +164,8 @@ where self.set_draw_area( (disp_min_x + offset_x, disp_min_y + SIZE::OFFSETY), (disp_max_x + offset_x, disp_max_y + SIZE::OFFSETY), - )?; + ) + .await?; Self::flush_buffer_chunks( &mut self.interface, @@ -138,12 +174,14 @@ where (disp_min_x, disp_min_y), (disp_max_x, disp_max_y), ) + .await } DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => { self.set_draw_area( (disp_min_y + offset_x, disp_min_x + SIZE::OFFSETY), (disp_max_y + offset_x, disp_max_x + SIZE::OFFSETY), - )?; + ) + .await?; Self::flush_buffer_chunks( &mut self.interface, @@ -152,6 +190,7 @@ where (disp_min_y, disp_min_x), (disp_max_y, disp_max_x), ) + .await } } } @@ -202,8 +241,21 @@ use embedded_graphics_core::{ }; use super::DisplayConfig; +#[cfg(feature = "async")] +use super::DisplayConfigAsync; #[cfg(feature = "graphics")] +#[maybe_async_cfg::maybe( + sync(keep_self), + async( + feature = "async", + idents( + DisplaySize(async = "DisplaySizeAsync"), + BufferedGraphicsMode(async = "BufferedGraphicsModeAsync"), + WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand") + ) + ) +)] impl DrawTarget for Ssd1306> where DI: WriteOnlyDataCommand, @@ -235,6 +287,17 @@ where } #[cfg(feature = "graphics")] +#[maybe_async_cfg::maybe( + sync(keep_self,), + async( + feature = "async", + idents( + DisplaySize(async = "DisplaySizeAsync"), + WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand"), + BufferedGraphicsMode(async = "BufferedGraphicsModeAsync") + ) + ) +)] impl OriginDimensions for Ssd1306> where DI: WriteOnlyDataCommand, diff --git a/src/mode/mod.rs b/src/mode/mod.rs index 68244376..ffa9d655 100644 --- a/src/mode/mod.rs +++ b/src/mode/mod.rs @@ -9,15 +9,16 @@ use display_interface::{DisplayError, WriteOnlyDataCommand}; pub use terminal::*; /// Common functions to all display modes. +#[maybe_async_cfg::maybe(sync(keep_self,), async(feature = "async"))] pub trait DisplayConfig { /// Error. type Error; /// Set display rotation. - fn set_rotation(&mut self, rotation: DisplayRotation) -> Result<(), Self::Error>; + async fn set_rotation(&mut self, rotation: DisplayRotation) -> Result<(), Self::Error>; /// Initialise and configure the display for the given mode. - fn init(&mut self) -> Result<(), Self::Error>; + async fn init(&mut self) -> Result<(), Self::Error>; } /// A mode with no additional functionality beyond that provided by the base [`Ssd1306`] struct. diff --git a/src/mode/terminal.rs b/src/mode/terminal.rs index 33e623b9..3f8c7ab2 100644 --- a/src/mode/terminal.rs +++ b/src/mode/terminal.rs @@ -1,7 +1,23 @@ +#[cfg(feature = "async")] +use crate::mode::DisplayConfigAsync; use crate::{command::AddrMode, mode::DisplayConfig, rotation::DisplayRotation, size::*, Ssd1306}; +#[cfg(feature = "async")] +use crate::{size::DisplaySizeAsync, Ssd1306Async}; use core::{cmp::min, fmt}; +#[cfg(feature = "async")] +use display_interface::AsyncWriteOnlyDataCommand; use display_interface::{DisplayError, WriteOnlyDataCommand}; +#[maybe_async_cfg::maybe( + sync(keep_self), + async( + feature = "async", + idents( + TerminalDisplaySize(async = "TerminalDisplaySizeAsync"), + DisplaySize(async = "DisplaySizeAsync"), + ) + ) +)] /// Extends the [`DisplaySize`](crate::size::DisplaySize) trait /// to include number of characters that can fit on the display. pub trait TerminalDisplaySize: DisplaySize { @@ -9,22 +25,62 @@ pub trait TerminalDisplaySize: DisplaySize { const CHAR_NUM: u8; } +#[maybe_async_cfg::maybe( + sync(keep_self), + async( + feature = "async", + keep_self, + idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),) + ) +)] impl TerminalDisplaySize for DisplaySize128x64 { const CHAR_NUM: u8 = 128; } +#[maybe_async_cfg::maybe( + sync(keep_self), + async( + feature = "async", + keep_self, + idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),) + ) +)] impl TerminalDisplaySize for DisplaySize128x32 { const CHAR_NUM: u8 = 64; } +#[maybe_async_cfg::maybe( + sync(keep_self), + async( + feature = "async", + keep_self, + idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),) + ) +)] impl TerminalDisplaySize for DisplaySize96x16 { const CHAR_NUM: u8 = 24; } +#[maybe_async_cfg::maybe( + sync(keep_self), + async( + keep_self, + feature = "async", + idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),) + ) +)] impl TerminalDisplaySize for DisplaySize72x40 { const CHAR_NUM: u8 = 45; } +#[maybe_async_cfg::maybe( + sync(keep_self), + async( + feature = "async", + keep_self, + idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),) + ) +)] impl TerminalDisplaySize for DisplaySize64x48 { const CHAR_NUM: u8 = 48; } @@ -119,11 +175,19 @@ impl From for TerminalModeError { } /// Terminal mode. +#[maybe_async_cfg::maybe( + sync(keep_self), + async(feature = "async", idents(TerminalMode(async = "TerminalModeAsync"))) +)] #[derive(Debug, Copy, Clone, Default)] pub struct TerminalMode { cursor: Option, } +#[maybe_async_cfg::maybe( + sync(keep_self), + async(feature = "async", idents(TerminalMode(async = "TerminalModeAsync"))) +)] impl TerminalMode { /// Create a new terminal mode config instance. pub fn new() -> Self { @@ -131,6 +195,19 @@ impl TerminalMode { } } +#[maybe_async_cfg::maybe( + sync(keep_self), + async( + feature = "async", + idents( + DisplaySize(async = "DisplaySizeAsync"), + DisplayConfig(async = "DisplayConfigAsync"), + WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand"), + TerminalMode(async = "TerminalModeAsync"), + TerminalDisplaySize(async = "TerminalDisplaySizeAsync"), + ) + ) +)] impl DisplayConfig for Ssd1306 where DI: WriteOnlyDataCommand, @@ -141,31 +218,44 @@ where /// Set the display rotation /// /// This method resets the cursor but does not clear the screen. - fn set_rotation(&mut self, rot: DisplayRotation) -> Result<(), TerminalModeError> { - self.set_rotation(rot)?; + async fn set_rotation(&mut self, rot: DisplayRotation) -> Result<(), TerminalModeError> { + self.set_rotation(rot).await?; // Need to reset cursor position, otherwise coordinates can become invalid - self.reset_pos() + self.reset_pos().await } /// Initialise the display in page mode (i.e. a byte walks down a column of 8 pixels) with /// column 0 on the left and column _(SIZE::Width::U8 - 1)_ on the right, but no automatic line /// wrapping. - fn init(&mut self) -> Result<(), TerminalModeError> { - self.init_with_addr_mode(AddrMode::Page)?; - self.reset_pos()?; + async fn init(&mut self) -> Result<(), TerminalModeError> { + self.init_with_addr_mode(AddrMode::Page).await?; + self.reset_pos().await?; Ok(()) } } +#[maybe_async_cfg::maybe( + sync(keep_self), + async( + feature = "async", + idents( + DisplaySize(async = "DisplaySizeAsync"), + DisplayConfig(async = "DisplayConfigAsync"), + WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand"), + TerminalMode(async = "TerminalModeAsync"), + TerminalDisplaySize(async = "TerminalDisplaySizeAsync"), + ) + ) +)] impl Ssd1306 where DI: WriteOnlyDataCommand, SIZE: TerminalDisplaySize, { /// Clear the display and reset the cursor to the top left corner - pub fn clear(&mut self) -> Result<(), TerminalModeError> { + pub async fn clear(&mut self) -> Result<(), TerminalModeError> { // Let the chip handle line wrapping so we can fill the screen with blanks faster - self.set_addr_mode(AddrMode::Horizontal)?; + self.set_addr_mode(AddrMode::Horizontal).await?; let offset_x = match self.rotation() { DisplayRotation::Rotate0 | DisplayRotation::Rotate270 => SIZE::OFFSETX, @@ -178,29 +268,30 @@ where self.set_draw_area( (offset_x, SIZE::OFFSETY), (SIZE::WIDTH + offset_x, SIZE::HEIGHT + SIZE::OFFSETY), - )?; + ) + .await?; // Clear the display for _ in 0..SIZE::CHAR_NUM { - self.draw(&[0; 8])?; + self.draw(&[0; 8]).await?; } // But for normal operation we manage the line wrapping - self.set_addr_mode(AddrMode::Page)?; - self.reset_pos()?; + self.set_addr_mode(AddrMode::Page).await?; + self.reset_pos().await?; Ok(()) } /// Print a character to the display - pub fn print_char(&mut self, c: char) -> Result<(), TerminalModeError> { + pub async fn print_char(&mut self, c: char) -> Result<(), TerminalModeError> { match c { '\n' => { let CursorWrapEvent(new_line) = self.ensure_cursor()?.advance_line(); - self.set_position(0, new_line)?; + self.set_position(0, new_line).await?; } '\r' => { - self.set_column(0)?; + self.set_column(0).await?; let (_, cur_line) = self.ensure_cursor()?.get_position(); self.ensure_cursor()?.set_position(0, cur_line); } @@ -215,10 +306,10 @@ where } }; - self.draw(&bitmap)?; + self.draw(&bitmap).await?; // Increment character counter and potentially wrap line - self.advance_cursor()?; + self.advance_cursor().await?; } } @@ -238,7 +329,7 @@ where /// Set the cursor position, in character coordinates. /// This is the (column, row) that the next character will be written to. /// If the position is out of bounds, an Err will be returned. - pub fn set_position(&mut self, column: u8, row: u8) -> Result<(), TerminalModeError> { + pub async fn set_position(&mut self, column: u8, row: u8) -> Result<(), TerminalModeError> { let (width, height) = self.ensure_cursor()?.get_dimensions(); if column >= width || row >= height { Err(TerminalModeError::OutOfBounds) @@ -253,12 +344,12 @@ where }; match self.rotation() { DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => { - self.set_column(offset_x + column * 8)?; - self.set_row(SIZE::OFFSETY + row * 8)?; + self.set_column(offset_x + column * 8).await?; + self.set_row(SIZE::OFFSETY + row * 8).await?; } DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => { - self.set_column(offset_x + row * 8)?; - self.set_row(SIZE::OFFSETY + column * 8)?; + self.set_column(offset_x + row * 8).await?; + self.set_row(SIZE::OFFSETY + column * 8).await?; } } self.ensure_cursor()?.set_position(column, row); @@ -267,7 +358,7 @@ where } /// Reset the draw area and move pointer to the top left corner - fn reset_pos(&mut self) -> Result<(), TerminalModeError> { + async fn reset_pos(&mut self) -> Result<(), TerminalModeError> { // Initialise the counter when we know it's valid let (w, h) = match self.rotation() { DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => (SIZE::WIDTH, SIZE::HEIGHT), @@ -276,19 +367,19 @@ where self.mode.cursor = Some(Cursor::new(w, h)); // Reset cursor position - self.set_position(0, 0)?; + self.set_position(0, 0).await?; Ok(()) } /// Advance the cursor, automatically wrapping lines and/or screens if necessary /// Takes in an already-unwrapped cursor to avoid re-unwrapping - fn advance_cursor(&mut self) -> Result<(), TerminalModeError> { + async fn advance_cursor(&mut self) -> Result<(), TerminalModeError> { let cursor = self.ensure_cursor()?; cursor.advance(); let (c, r) = cursor.get_position(); - self.set_position(c, r)?; + self.set_position(c, r).await?; Ok(()) } @@ -519,6 +610,21 @@ where } } +#[cfg(feature = "async")] +impl Ssd1306Async +where + DI: AsyncWriteOnlyDataCommand, + SIZE: TerminalDisplaySizeAsync, +{ + /// Write a string slice to the display + pub async fn write_str(&mut self, s: &str) -> Result<(), TerminalModeError> { + for c in s.chars() { + self.print_char(c).await?; + } + Ok(()) + } +} + impl fmt::Write for Ssd1306 where DI: WriteOnlyDataCommand, diff --git a/src/prelude.rs b/src/prelude.rs index 20616030..e1098132 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -13,3 +13,6 @@ pub use super::{ DisplaySize96x16, }, }; + +#[cfg(feature = "async")] +pub use super::mode::DisplayConfigAsync; diff --git a/src/size.rs b/src/size.rs index 32835d90..62bca180 100644 --- a/src/size.rs +++ b/src/size.rs @@ -1,6 +1,10 @@ //! Display size. use super::command::Command; +#[cfg(feature = "async")] +use super::command::CommandAsync; +#[cfg(feature = "async")] +use display_interface::AsyncWriteOnlyDataCommand; use display_interface::{DisplayError, WriteOnlyDataCommand}; /// Workaround trait, since `Default` is only implemented to arrays up to 32 of size @@ -19,6 +23,13 @@ impl NewZeroed for [u8; N] { /// /// This trait describes information related to a particular display. /// This includes resolution, offset and framebuffer size. +#[maybe_async_cfg::maybe( + sync(keep_self), + async( + feature = "async", + idents(WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand")) + ) +)] pub trait DisplaySize { /// Width in pixels const WIDTH: u8; @@ -47,90 +58,131 @@ pub trait DisplaySize { /// See [`Command::ComPinConfig`] /// and [`Command::InternalIref`] /// for more information - fn configure(&self, iface: &mut impl WriteOnlyDataCommand) -> Result<(), DisplayError>; + async fn configure(&self, iface: &mut impl WriteOnlyDataCommand) -> Result<(), DisplayError>; } +maybe_async_cfg::content! { +#![maybe_async_cfg::default( + idents( + WriteOnlyDataCommand(sync, async = "AsyncWriteOnlyDataCommand"), + Command(sync, async = "CommandAsync"), + DisplaySize(sync, async="DisplaySizeAsync") + ) +)] + /// Size information for the common 128x64 variants #[derive(Debug, Copy, Clone)] pub struct DisplaySize128x64; +#[maybe_async_cfg::maybe(sync(keep_self), async(feature = "async", keep_self))] impl DisplaySize for DisplaySize128x64 { const WIDTH: u8 = 128; const HEIGHT: u8 = 64; - type Buffer = [u8; Self::WIDTH as usize * Self::HEIGHT as usize / 8]; - - fn configure(&self, iface: &mut impl WriteOnlyDataCommand) -> Result<(), DisplayError> { - Command::ComPinConfig(true, false).send(iface) + type Buffer = [u8; ::WIDTH as usize * + ::HEIGHT as usize / 8]; + + async fn configure( + &self, + iface: &mut impl WriteOnlyDataCommand, + ) -> Result<(), DisplayError> { + Command::ComPinConfig(true, false).send(iface).await } } /// Size information for the common 128x32 variants #[derive(Debug, Copy, Clone)] pub struct DisplaySize128x32; +#[maybe_async_cfg::maybe(sync(keep_self), async(feature = "async", keep_self))] impl DisplaySize for DisplaySize128x32 { const WIDTH: u8 = 128; const HEIGHT: u8 = 32; - type Buffer = [u8; Self::WIDTH as usize * Self::HEIGHT as usize / 8]; - - fn configure(&self, iface: &mut impl WriteOnlyDataCommand) -> Result<(), DisplayError> { - Command::ComPinConfig(false, false).send(iface) + type Buffer = [u8; ::WIDTH as usize * + ::HEIGHT as usize / 8]; + + async fn configure( + &self, + iface: &mut impl WriteOnlyDataCommand, + ) -> Result<(), DisplayError> { + Command::ComPinConfig(false, false).send(iface).await } } /// Size information for the common 96x16 variants #[derive(Debug, Copy, Clone)] pub struct DisplaySize96x16; +#[maybe_async_cfg::maybe(sync(keep_self), async(feature = "async", keep_self))] impl DisplaySize for DisplaySize96x16 { const WIDTH: u8 = 96; const HEIGHT: u8 = 16; - type Buffer = [u8; Self::WIDTH as usize * Self::HEIGHT as usize / 8]; - - fn configure(&self, iface: &mut impl WriteOnlyDataCommand) -> Result<(), DisplayError> { - Command::ComPinConfig(false, false).send(iface) + type Buffer = [u8; ::WIDTH as usize * + ::HEIGHT as usize / 8]; + + async fn configure( + &self, + iface: &mut impl WriteOnlyDataCommand, + ) -> Result<(), DisplayError> { + Command::ComPinConfig(false, false).send(iface).await } } /// Size information for the common 72x40 variants #[derive(Debug, Copy, Clone)] pub struct DisplaySize72x40; +#[maybe_async_cfg::maybe(sync(keep_self), async(feature = "async", keep_self))] impl DisplaySize for DisplaySize72x40 { const WIDTH: u8 = 72; const HEIGHT: u8 = 40; const OFFSETX: u8 = 28; const OFFSETY: u8 = 0; - type Buffer = [u8; Self::WIDTH as usize * Self::HEIGHT as usize / 8]; - - fn configure(&self, iface: &mut impl WriteOnlyDataCommand) -> Result<(), DisplayError> { - Command::ComPinConfig(true, false).send(iface)?; - Command::InternalIref(true, true).send(iface) + type Buffer = [u8; ::WIDTH as usize * + ::HEIGHT as usize / 8]; + + async fn configure( + &self, + iface: &mut impl WriteOnlyDataCommand, + ) -> Result<(), DisplayError> { + Command::ComPinConfig(true, false).send(iface).await?; + Command::InternalIref(true, true).send(iface).await } } /// Size information for the common 64x48 variants #[derive(Debug, Copy, Clone)] pub struct DisplaySize64x48; +#[maybe_async_cfg::maybe(sync(keep_self), async(feature = "async", keep_self))] impl DisplaySize for DisplaySize64x48 { const WIDTH: u8 = 64; const HEIGHT: u8 = 48; const OFFSETX: u8 = 32; const OFFSETY: u8 = 0; - type Buffer = [u8; Self::WIDTH as usize * Self::HEIGHT as usize / 8]; - - fn configure(&self, iface: &mut impl WriteOnlyDataCommand) -> Result<(), DisplayError> { - Command::ComPinConfig(true, false).send(iface) + type Buffer = [u8; ::WIDTH as usize * + ::HEIGHT as usize / 8]; + + async fn configure( + &self, + iface: &mut impl WriteOnlyDataCommand, + ) -> Result<(), DisplayError> { + Command::ComPinConfig(true, false).send(iface).await } } /// Size information for the common 64x32 variants #[derive(Debug, Copy, Clone)] pub struct DisplaySize64x32; +#[maybe_async_cfg::maybe(sync(keep_self), async(feature = "async", keep_self))] impl DisplaySize for DisplaySize64x32 { const WIDTH: u8 = 64; const HEIGHT: u8 = 32; const OFFSETX: u8 = 32; const OFFSETY: u8 = 0; - type Buffer = [u8; Self::WIDTH as usize * Self::HEIGHT as usize / 8]; - - fn configure(&self, iface: &mut impl WriteOnlyDataCommand) -> Result<(), DisplayError> { - Command::ComPinConfig(true, false).send(iface) + type Buffer = [u8; ::WIDTH as usize * + ::HEIGHT as usize / 8]; + + async fn configure( + &self, + iface: &mut impl WriteOnlyDataCommand, + ) -> Result<(), DisplayError> { + Command::ComPinConfig(true, false).send(iface).await } } + +} // content