Skip to content

Commit

Permalink
Add async operations
Browse files Browse the repository at this point in the history
  • Loading branch information
bugadani committed May 2, 2023
1 parent 9dfc7cd commit d1711c9
Show file tree
Hide file tree
Showing 5 changed files with 261 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand All @@ -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]
Expand All @@ -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
Expand Down
14 changes: 14 additions & 0 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,20 @@ impl Command {
Ok(())
}
}

/// Send command to SSD1306 asynchronously
#[cfg(feature = "async")]
pub async fn send_async<DI>(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
Expand Down
192 changes: 189 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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};
Expand Down Expand Up @@ -439,6 +440,140 @@ where
}
}

#[cfg(feature = "async")]
impl<DI, SIZE> Ssd1306<DI, SIZE, BufferedGraphicsMode<SIZE>>
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<SPI, DC, SIZE, MODE> Ssd1306<SPIInterfaceNoCS<SPI, DC>, SIZE, MODE> {
/// Reset the display.
Expand Down Expand Up @@ -482,3 +617,54 @@ where
delay.delay_ms(10);
rst.set_high()
}

#[cfg(feature = "async")]
// SPI-only reset
impl<SPI, DC, SIZE, MODE>
Ssd1306<display_interface_spi_async::SPIInterfaceNoCS<SPI, DC>, SIZE, MODE>
{
/// Reset the display.
pub async fn reset<RST, DELAY>(
&mut self,
rst: &mut RST,
delay: &mut DELAY,
) -> Result<(), Error<Infallible, RST::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<SPI, DC, CS, SIZE, MODE>
Ssd1306<display_interface_spi_async::SPIInterface<SPI, DC, CS>, SIZE, MODE>
{
/// Reset the display.
pub async fn reset<RST, DELAY>(
&mut self,
rst: &mut RST,
delay: &mut DELAY,
) -> Result<(), Error<Infallible, RST::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, DELAY>(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()
}
52 changes: 52 additions & 0 deletions src/mode/buffered_graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,58 @@ where
}
}

#[cfg(feature = "async")]
impl<DI, SIZE> Ssd1306<DI, SIZE, BufferedGraphicsMode<SIZE>>
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,
Expand Down

0 comments on commit d1711c9

Please # to comment.