From d1711c9faf4b88f59334f6f1451e68712cbf2613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Tue, 2 May 2023 11:09:30 +0200 Subject: [PATCH] Add async operations --- .circleci/config.yml | 2 +- Cargo.toml | 7 +- src/command.rs | 14 +++ src/lib.rs | 192 +++++++++++++++++++++++++++++++++- src/mode/buffered_graphics.rs | 52 +++++++++ 5 files changed, 261 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 060e5af5..e2c40272 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ target_steps: &target_steps docker: - - image: cimg/rust:1.57.0 + - image: cimg/rust:1.66.0 steps: - checkout - restore_cache: diff --git a/Cargo.toml b/Cargo.toml index cac49d98..049f90bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/jamwaffles/ssd1306" version = "0.7.1" edition = "2018" exclude = [ "build.rs", "build.sh", "memory.x", "doc", "*.jpg", "*.png", "*.bmp" ] -rust-version = "1.57" +rust-version = "1.66" [badges] circle-ci = { repository = "jamwaffles/ssd1306", branch = "master" } @@ -21,9 +21,11 @@ targets = [ "thumbv7m-none-eabi", "thumbv7em-none-eabihf" ] [dependencies] embedded-hal = "1.0.0-alpha.10" +embedded-hal-async = { version = "0.2.0-alpha.1", optional = true } display-interface = { git = "https://github.com/bugadani/display-interface.git", branch = "async" } display-interface-i2c = { git = "https://github.com/bugadani/display-interface.git", branch = "async" } display-interface-spi = { git = "https://github.com/bugadani/display-interface.git", branch = "async" } +display-interface-spi-async = { git = "https://github.com/bugadani/display-interface.git", branch = "async", optional = true } embedded-graphics-core = { version = "0.3.2", optional = true } [dev-dependencies] @@ -40,8 +42,9 @@ rand = { version = "0.8.4", default-features = false, features = [ "small_rng" ] stm32f1xx-hal = { version = "0.7.0", features = [ "rt", "stm32f103" ] } [features] -default = ["graphics"] +default = ["graphics", "async"] graphics = ["embedded-graphics-core"] +async = ["display-interface-spi-async", "display-interface/async", "embedded-hal-async"] [profile.dev] codegen-units = 1 diff --git a/src/command.rs b/src/command.rs index 1a48d8c9..5504ae91 100644 --- a/src/command.rs +++ b/src/command.rs @@ -192,6 +192,20 @@ impl Command { Ok(()) } } + + /// Send command to SSD1306 asynchronously + #[cfg(feature = "async")] + pub async fn send_async(self, iface: &mut DI) -> Result<(), DisplayError> + where + DI: display_interface::AsyncWriteOnlyDataCommand, + { + if let Some((data, len)) = self.bytes() { + // Send command over the interface + iface.send_commands(U8(&data[0..len])).await + } else { + Ok(()) + } + } } /// Horizontal Scroll Direction diff --git a/src/lib.rs b/src/lib.rs index e9b7e1d3..73afb7f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,10 +103,12 @@ #![deny(trivial_casts)] #![deny(trivial_numeric_casts)] #![deny(unsafe_code)] -#![deny(unstable_features)] +#![cfg_attr(not(feature = "async"), deny(unstable_features))] #![deny(unused_import_braces)] #![deny(unused_qualifications)] #![deny(rustdoc::broken_intra_doc_links)] +#![cfg_attr(feature = "async", allow(incomplete_features))] +#![cfg_attr(feature = "async", feature(async_fn_in_trait, impl_trait_projections))] mod brightness; pub mod command; @@ -119,12 +121,11 @@ pub mod size; #[doc(hidden)] pub mod test_helpers; -use core::convert::Infallible; - pub use crate::i2c_interface::I2CDisplayInterface; use crate::mode::BasicMode; use brightness::Brightness; use command::{AddrMode, Command, VcomhLevel}; +use core::convert::Infallible; use display_interface::{DataFormat::U8, DisplayError, WriteOnlyDataCommand}; use display_interface_spi::{SPIInterface, SPIInterfaceNoCS}; use embedded_hal::{delay::DelayUs, digital::OutputPin}; @@ -439,6 +440,140 @@ where } } +#[cfg(feature = "async")] +impl Ssd1306> +where + DI: display_interface::AsyncWriteOnlyDataCommand, + SIZE: DisplaySize, +{ + async fn send_commands_async(&mut self, commands: &[Command]) -> Result<(), DisplayError> { + for command in commands { + command.send_async(&mut self.interface).await?; + } + + Ok(()) + } + + /// Initialise the display in one of the available addressing modes. + pub async fn init_with_addr_mode_async(&mut self, mode: AddrMode) -> Result<(), DisplayError> { + self.send_commands_async(&self.init_commands(mode)).await?; + self.addr_mode = mode; + + Ok(()) + } + + /// Change the addressing mode + pub async fn set_addr_mode_async(&mut self, mode: AddrMode) -> Result<(), DisplayError> { + self.send_commands_async(&[Command::AddressMode(mode)]) + .await?; + self.addr_mode = mode; + + Ok(()) + } + + /// Send the data to the display for drawing at the current position in the framebuffer + /// and advance the position accordingly. Cf. `set_draw_area` to modify the affected area by + /// this method. + /// + /// This method takes advantage of a bounding box for faster writes. + pub async fn bounded_draw_async( + &mut self, + buffer: &[u8], + disp_width: usize, + upper_left: (u8, u8), + lower_right: (u8, u8), + ) -> Result<(), DisplayError> { + Self::flush_buffer_chunks_async( + &mut self.interface, + buffer, + disp_width, + upper_left, + lower_right, + ) + .await + } + + /// Send a raw buffer to the display. + pub async fn draw_async(&mut self, buffer: &[u8]) -> Result<(), DisplayError> { + self.interface.send_data(U8(&buffer)).await + } + + /// Set the display rotation. + pub async fn set_rotation_async( + &mut self, + rotation: DisplayRotation, + ) -> Result<(), DisplayError> { + self.send_commands_async(&self.rotation_commands(rotation)) + .await?; + self.rotation = rotation; + + Ok(()) + } + + /// Set mirror enabled/disabled. + pub async fn set_mirror_async(&mut self, mirror: bool) -> Result<(), DisplayError> { + self.send_commands_async(&self.mirror_commands(mirror)) + .await + } + + /// Change the display brightness. + pub async fn set_brightness_async( + &mut self, + brightness: Brightness, + ) -> Result<(), DisplayError> { + self.send_commands_async(&self.brightness_commands(brightness)) + .await + } + + /// Turn the display on or off. The display can be drawn to and retains all + /// of its memory even while off. + pub async fn set_display_on_async(&mut self, on: bool) -> Result<(), DisplayError> { + self.send_commands_async(&[Command::DisplayOn(on)]).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 async fn set_draw_area_async( + &mut self, + start: (u8, u8), + end: (u8, u8), + ) -> Result<(), DisplayError> { + self.send_commands_async(&self.draw_area_commands(start, end)) + .await + } + + /// Set the column address in the framebuffer of the display where any sent data should be + /// drawn. + pub async fn set_column_async(&mut self, column: u8) -> Result<(), DisplayError> { + self.send_commands_async(&[Command::ColStart(column)]).await + } + + /// Set the page address (row 8px high) in the framebuffer of the display where any sent data + /// should be drawn. + /// + /// 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 async fn set_row_async(&mut self, row: u8) -> Result<(), DisplayError> { + self.send_commands_async(&[Command::PageStart(row.into())]) + .await + } + + async fn flush_buffer_chunks_async( + interface: &mut DI, + buffer: &[u8], + disp_width: usize, + upper_left: (u8, u8), + lower_right: (u8, u8), + ) -> Result<(), DisplayError> { + for chunk in Self::buffer_chunks(buffer, disp_width, upper_left, lower_right) { + interface.send_data(U8(chunk)).await?; + } + + Ok(()) + } +} + // SPI-only reset impl Ssd1306, SIZE, MODE> { /// Reset the display. @@ -482,3 +617,54 @@ where delay.delay_ms(10); rst.set_high() } + +#[cfg(feature = "async")] +// SPI-only reset +impl + Ssd1306, SIZE, MODE> +{ + /// Reset the display. + pub async fn reset( + &mut self, + rst: &mut RST, + delay: &mut DELAY, + ) -> Result<(), Error> + where + RST: OutputPin, + DELAY: embedded_hal_async::delay::DelayUs, + { + inner_reset_async(rst, delay).await.map_err(Error::Pin) + } +} + +#[cfg(feature = "async")] +// SPI-only reset +impl + Ssd1306, SIZE, MODE> +{ + /// Reset the display. + pub async fn reset( + &mut self, + rst: &mut RST, + delay: &mut DELAY, + ) -> Result<(), Error> + where + RST: OutputPin, + DELAY: embedded_hal_async::delay::DelayUs, + { + inner_reset_async(rst, delay).await.map_err(Error::Pin) + } +} + +#[cfg(feature = "async")] +async fn inner_reset_async(rst: &mut RST, delay: &mut DELAY) -> Result<(), RST::Error> +where + RST: OutputPin, + DELAY: embedded_hal_async::delay::DelayUs, +{ + rst.set_high()?; + delay.delay_ms(1).await; + rst.set_low()?; + delay.delay_ms(10).await; + rst.set_high() +} diff --git a/src/mode/buffered_graphics.rs b/src/mode/buffered_graphics.rs index d7e6e1d3..1b7e0f75 100644 --- a/src/mode/buffered_graphics.rs +++ b/src/mode/buffered_graphics.rs @@ -200,6 +200,58 @@ where } } +#[cfg(feature = "async")] +impl Ssd1306> +where + DI: display_interface::AsyncWriteOnlyDataCommand, + SIZE: DisplaySize, +{ + /// Write out data to a display. + /// + /// This only updates the parts of the display that have changed since the last flush. + pub async fn flush_async(&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(()); + } + + let (width, height) = self.dimensions(); + let (disp_min, disp_max) = self.dirty_area(width, height); + let (area_start, area_end) = self.display_area(disp_min, disp_max); + + self.mode.min_x = 255; + self.mode.max_x = 0; + self.mode.min_y = 255; + self.mode.max_y = 0; + + // Tell the display to update only the part that has changed + self.set_draw_area_async(area_start, area_end).await?; + + match self.rotation { + DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => { + Self::flush_buffer_chunks_async( + &mut self.interface, + self.mode.buffer.as_mut(), + width as usize, + (disp_min.0, disp_min.1), + (disp_max.0, disp_max.1), + ) + .await + } + DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => { + Self::flush_buffer_chunks_async( + &mut self.interface, + self.mode.buffer.as_mut(), + height as usize, + (disp_min.1, disp_min.0), + (disp_max.1, disp_max.0), + ) + .await + } + } + } +} + #[cfg(feature = "graphics")] use embedded_graphics_core::{ draw_target::DrawTarget,