From 62202e5df4cc5606d597b205609bfd746a5cc07c Mon Sep 17 00:00:00 2001 From: James Waples Date: Mon, 21 Mar 2022 18:26:08 +0000 Subject: [PATCH 01/13] Add fast rectangle fill impl and RTIC DVD example --- examples/bench-fill.rs | 207 +++++++++++++++++++++++++++++++++++++++++ examples/dvd.bmp | Bin 0 -> 2826 bytes examples/rtic_dvd.rs | 180 +++++++++++++++++++++++++++++++++++ src/mode/graphics.rs | 79 +++++++++++++++- 4 files changed, 464 insertions(+), 2 deletions(-) create mode 100644 examples/bench-fill.rs create mode 100644 examples/dvd.bmp create mode 100644 examples/rtic_dvd.rs diff --git a/examples/bench-fill.rs b/examples/bench-fill.rs new file mode 100644 index 0000000..10dc643 --- /dev/null +++ b/examples/bench-fill.rs @@ -0,0 +1,207 @@ +//! Bounce a DVD player logo around the screen +//! +//! Like this, but with no color changing: https://bouncingdvdlogo.com/ +//! +//! 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 -> PB9 +//! (green) SCL -> PB8 +//! ``` +//! +//! Run on a Blue Pill with `cargo run --example rtic_dvd`. + +#![no_std] +#![no_main] + +use core::fmt::Write; +use embedded_graphics::{ + geometry::Point, + mono_font::{ascii::FONT_6X10, MonoTextStyleBuilder}, + pixelcolor::BinaryColor, + prelude::*, + primitives::{PrimitiveStyle, Rectangle}, + text::{Baseline, Text}, +}; +use heapless::String; +use panic_semihosting as _; +use rtic::app; +use sh1106::{prelude::*, Builder}; +use stm32f1xx_hal::{ + gpio, + i2c::{BlockingI2c, DutyCycle, Mode}, + pac::{self, I2C1}, + prelude::*, + timer::{CountDownTimer, Event, Timer}, +}; + +type Display = GraphicsMode< + I2cInterface< + BlockingI2c< + I2C1, + ( + gpio::gpiob::PB8>, + gpio::gpiob::PB9>, + ), + >, + >, +>; + +#[inline(always)] +fn stopwatch(f: F) -> u32 +where + F: FnOnce() -> (), +{ + let start: u32 = pac::DWT::cycle_count(); + f(); + let end: u32 = pac::DWT::cycle_count(); + end.wrapping_sub(start) +} + +#[app(device = stm32f1xx_hal::pac, peripherals = true)] +const APP: () = { + struct Resources { + display: Display, + timer: CountDownTimer, + #[init(0)] + frame: u32, + } + + #[init] + fn init(cx: init::Context) -> init::LateResources { + let dp = cx.device; + let mut cp = cx.core; + + cp.DCB.enable_trace(); + cp.DWT.enable_cycle_counter(); + + let mut flash = dp.FLASH.constrain(); + let mut rcc = dp.RCC.constrain(); + + let clocks = rcc + .cfgr + .use_hse(8.mhz()) + .sysclk(72.mhz()) + .pclk1(36.mhz()) + .freeze(&mut flash.acr); + + let mut afio = dp.AFIO.constrain(&mut rcc.apb2); + + let mut gpiob = dp.GPIOB.split(&mut rcc.apb2); + + let scl = gpiob.pb8.into_alternate_open_drain(&mut gpiob.crh); + let sda = gpiob.pb9.into_alternate_open_drain(&mut gpiob.crh); + + let i2c = BlockingI2c::i2c1( + dp.I2C1, + (scl, sda), + &mut afio.mapr, + Mode::Fast { + frequency: 400_000.hz(), + duty_cycle: DutyCycle::Ratio2to1, + }, + clocks, + &mut rcc.apb1, + 1000, + 10, + 1000, + 1000, + ); + + let mut display: GraphicsMode<_> = Builder::new().connect_i2c(i2c).into(); + + display.init().unwrap(); + display.flush().unwrap(); + + // Update framerate + let fps = 1; + + let mut timer = Timer::tim1(dp.TIM1, &clocks, &mut rcc.apb2).start_count_down(fps.hz()); + + timer.listen(Event::Update); + + // Init the static resources to use them later through RTIC + init::LateResources { timer, display } + } + + #[idle()] + fn idle(_: idle::Context) -> ! { + loop { + // Fix default wfi() behaviour breaking debug probe + core::hint::spin_loop(); + } + } + + #[task(binds = TIM1_UP, resources = [display, timer, frame])] + fn update(cx: update::Context) { + let update::Resources { + display, + timer, + frame, + .. + } = cx.resources; + + display.clear(); + display.flush().unwrap(); + + let center = display.bounding_box().center(); + + // Only bench time taken to draw rectangles + let time = stopwatch(|| { + for x in 0i32..64 { + // Square squares in center + Rectangle::with_center(center, Size::new(x as u32, x as u32)) + .into_styled(PrimitiveStyle::with_fill(BinaryColor::On)) + .draw(display) + .unwrap(); + } + + for x in 0i32..64 { + // Tall rectangles + Rectangle::with_center(Point::new(x * 5, 20), Size::new(4, x as u32)) + .into_styled(PrimitiveStyle::with_fill(BinaryColor::On)) + .draw(display) + .unwrap(); + + // Wide rectangles + Rectangle::with_center(Point::new(0, x * 2), Size::new(x as u32, 4)) + .into_styled(PrimitiveStyle::with_fill(BinaryColor::On)) + .draw(display) + .unwrap(); + } + }); + + // Convert time to ms by dividing by sysclk * 1000 + let time = time / 72_000; + + let mut s: String<32> = String::new(); + + write!(s, "{}ms", time).ok(); + + let text_style = MonoTextStyleBuilder::new() + .font(&FONT_6X10) + .text_color(BinaryColor::On) + .background_color(BinaryColor::Off) + .build(); + + Text::with_baseline(&s, Point::zero(), text_style, Baseline::Top) + .draw(display) + .unwrap(); + + display.flush().unwrap(); + + *frame += 1; + + // Clears the update flag + timer.clear_update_interrupt_flag(); + } + + extern "C" { + fn EXTI0(); + } +}; diff --git a/examples/dvd.bmp b/examples/dvd.bmp new file mode 100644 index 0000000000000000000000000000000000000000..9c13c84b2a33e9357cf9d1aacb6a6069d652f826 GIT binary patch literal 2826 zcmdT?yG}zf40Y*HsVt1$I8++Y5P+WxEe+k;f<7t+n+j^05-xp4;8a+q+Yv_uZ&azAj$F(I?@> z@dJkH1#>pl8*jIs5mDAq$NM~<+Ot6Vbj$S9Z*y1&cdRfo;@oM1ryx5bst3nzt})Fk zW`DEx{Q3KqiSx Blue Pill +//! (black) GND -> GND +//! (red) +5V -> VCC +//! (yellow) SDA -> PB9 +//! (green) SCL -> PB8 +//! ``` +//! +//! Run on a Blue Pill with `cargo run --example rtic_dvd`. + +#![no_std] +#![no_main] + +use embedded_graphics::{ + geometry::Point, + image::Image, + pixelcolor::{BinaryColor, Rgb565}, + prelude::*, + primitives::{PrimitiveStyle, Rectangle}, +}; +use panic_semihosting as _; +use rtic::app; +use sh1106::{prelude::*, Builder}; +use stm32f1xx_hal::{ + gpio, + i2c::{BlockingI2c, DutyCycle, Mode}, + pac::{self, I2C1}, + prelude::*, + timer::{CountDownTimer, Event, Timer}, +}; +use tinybmp::Bmp; + +type Display = GraphicsMode< + I2cInterface< + BlockingI2c< + I2C1, + ( + gpio::gpiob::PB8>, + gpio::gpiob::PB9>, + ), + >, + >, +>; + +#[app(device = stm32f1xx_hal::pac, peripherals = true)] +const APP: () = { + struct Resources { + display: Display, + timer: CountDownTimer, + top_left: Point, + velocity: Point, + bmp: Bmp, + } + + #[init] + fn init(cx: init::Context) -> init::LateResources { + let dp = cx.device; + + let mut flash = dp.FLASH.constrain(); + let mut rcc = dp.RCC.constrain(); + + let clocks = rcc + .cfgr + .use_hse(8.mhz()) + .sysclk(72.mhz()) + .pclk1(36.mhz()) + .freeze(&mut flash.acr); + + let mut afio = dp.AFIO.constrain(&mut rcc.apb2); + + let mut gpiob = dp.GPIOB.split(&mut rcc.apb2); + + let scl = gpiob.pb8.into_alternate_open_drain(&mut gpiob.crh); + let sda = gpiob.pb9.into_alternate_open_drain(&mut gpiob.crh); + + let i2c = BlockingI2c::i2c1( + dp.I2C1, + (scl, sda), + &mut afio.mapr, + Mode::Fast { + frequency: 400_000.hz(), + duty_cycle: DutyCycle::Ratio2to1, + }, + clocks, + &mut rcc.apb1, + 1000, + 10, + 1000, + 1000, + ); + + let mut display: GraphicsMode<_> = Builder::new().connect_i2c(i2c).into(); + + display.init().unwrap(); + display.flush().unwrap(); + + // Update framerate + let fps = 20; + + let mut timer = Timer::tim1(dp.TIM1, &clocks, &mut rcc.apb2).start_count_down(fps.hz()); + + timer.listen(Event::Update); + + let bmp = Bmp::from_slice(include_bytes!("dvd.bmp")).unwrap(); + + // Init the static resources to use them later through RTIC + init::LateResources { + timer, + display, + top_left: Point::new(5, 3), + velocity: Point::new(1, 1), + bmp, + } + } + + #[idle()] + fn idle(_: idle::Context) -> ! { + loop { + // Fix default wfi() behaviour breaking debug probe + core::hint::spin_loop(); + } + } + + #[task(binds = TIM1_UP, resources = [display, top_left, velocity, timer, bmp])] + fn update(cx: update::Context) { + let update::Resources { + display, + top_left, + velocity, + timer, + bmp, + .. + } = cx.resources; + + let bottom_right = *top_left + bmp.bounding_box().size; + + // Erase previous image position with a filled black rectangle + Rectangle::with_corners(*top_left, bottom_right) + .into_styled(PrimitiveStyle::with_fill(BinaryColor::Off)) + .draw(display) + .unwrap(); + + // Check if the image collided with a screen edge + { + if bottom_right.x > display.size().width as i32 || top_left.x < 0 { + velocity.x = -velocity.x; + } + + if bottom_right.y > display.size().height as i32 || top_left.y < 0 { + velocity.y = -velocity.y; + } + } + + // Move the image + *top_left += *velocity; + + // Draw image at new position + Image::new(bmp, *top_left) + .draw(&mut display.color_converted()) + .unwrap(); + + // Write changes to the display + display.flush().unwrap(); + + // Clears the update flag + timer.clear_update_interrupt_flag(); + } + + extern "C" { + fn EXTI0(); + } +}; diff --git a/src/mode/graphics.rs b/src/mode/graphics.rs index 15642fb..ed61d3f 100644 --- a/src/mode/graphics.rs +++ b/src/mode/graphics.rs @@ -42,12 +42,12 @@ //! display.flush().unwrap(); //! ``` -use hal::{blocking::delay::DelayMs, digital::v2::OutputPin}; - use crate::{ displayrotation::DisplayRotation, interface::DisplayInterface, mode::displaymode::DisplayModeTrait, properties::DisplayProperties, Error, }; +use embedded_graphics_core::{prelude::Point, primitives::Rectangle}; +use hal::{blocking::delay::DelayMs, digital::v2::OutputPin}; const BUFFER_SIZE: usize = 132 * 64 / 8; @@ -226,6 +226,81 @@ where Ok(()) } + + fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> { + let Rectangle { + top_left: Point { x, y }, + size: Size { width, height }, + } = area.intersection(&self.bounding_box()); + // swap coordinates if rotated + let (x, y, width, height) = match self.properties.get_rotation() { + DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => (x, y, width, height), + DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => (y, x, height, width), + }; + + let color = if color.is_on() { 0xff } else { 0x00 }; + + let display_width = self.properties.get_size().dimensions().0 as u32; + + let mut remaining = height; + + let mut block = y as u32 / 8; + + // Top partial row + if y % 8 > 0 { + // Row (aka bit) in this block + let row = y as u32 % 8; + + let mask = (2u8.pow(height) - 1) << row; + + let start = (x as u32 + (block * display_width)) as usize; + + let slice = &mut self.buffer[start..(start + width as usize)]; + + // Write fill color without clobbering existing data in the other bits of the block + slice.iter_mut().for_each(|column| { + *column = (*column & !mask) | (color & mask); + }); + + remaining = remaining.saturating_sub((8 - row).min(height)); + block += 1; + } + + // Full height rows to fill + while remaining > 8 { + let start = (x as u32 + (block * display_width)) as usize; + + let slice = &mut self.buffer[start..(start + width as usize)]; + + slice.fill(color); + + block += 1; + remaining = remaining.saturating_sub(8); + } + + // Bottom partial row + if remaining > 0 { + // Row (aka bit) in this block + let row = 8 - (remaining % 8); + + let mask = (2u8.pow(height) - 1) >> row; + + let start = (x as u32 + (block * display_width)) as usize; + + let slice = &mut self.buffer[start..(start + width as usize)]; + + // Write fill color without clobbering existing data in the other bits of the block + slice.iter_mut().for_each(|column| { + *column = (*column & !mask) | (color & mask); + }); + + remaining = remaining.saturating_sub(8); + } + + debug_assert_eq!(remaining, 0); + + Ok(()) + } } #[cfg(feature = "graphics")] From 49535e15b1ece642e32cf1590af73586b5e32426 Mon Sep 17 00:00:00 2001 From: James Waples Date: Tue, 22 Mar 2022 18:30:47 +0000 Subject: [PATCH 02/13] Use cargo-deadlinks instead of Python linkchecker --- .circleci/config.yml | 6 +++++- CHANGELOG.md | 45 ++++++++++++++++++++++++++++++-------------- build.sh | 5 ++--- src/lib.rs | 2 +- 4 files changed, 39 insertions(+), 19 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7519bef..2d9caba 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,8 +5,12 @@ target_steps: &target_steps - checkout - restore_cache: key: v1-sh1106-{{ .Environment.CIRCLE_JOB }}-{{ checksum "Cargo.toml" }} - - run: sudo apt install -qq linkchecker + - run: rustup install 1.57.0 --profile minimal + - run: cargo +1.57.0 install cargo-deadlinks --target x86_64-unknown-linux-gnu - run: rustup default ${RUST_VERSION:-stable} + # For docs gen + - run: rustup target add thumbv7m-none-eabi + - run: rustup target add thumbv7em-none-eabihf - run: rustup component add rustfmt - run: | SYSROOT=$(rustc --print sysroot) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1613299..645d802 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,36 +1,45 @@ # Changelog -[`sh1106`](https://crates.io/crates/sh1106) is a Rust driver for the SH1106 OLED display. It supports -[embedded-graphics](https://crates.io/crates/embedded-graphics) or raw pixel drawing modes and works -with the [embedded-hal](crates.io/crates/embedded-hal) traits for maximum portability. +[`sh1106`](https://crates.io/crates/sh1106) is a Rust driver for the SH1106 OLED display. It +supports [embedded-graphics](https://crates.io/crates/embedded-graphics) or raw pixel drawing modes +and works with the [embedded-hal](crates.io/crates/embedded-hal) traits for maximum portability. ## [Unreleased] - ReleaseDate +### Changed + +- **(breaking)** [#28](https://github.com/jamwaffles/sh1106/pull/28) Upgrade MSRV to 1.50.0, add a + faster implementation of `DrawTarget::fill_solid`. + ## [0.4.0] - 2021-07-11 ### Changed -- **(breaking)** [#25](https://github.com/jamwaffles/sh1106/pull/25) Upgrade to `embedded-graphics` 0.7. +- **(breaking)** [#25](https://github.com/jamwaffles/sh1106/pull/25) Upgrade to `embedded-graphics` + 0.7. ## [0.3.4] - 2020-12-28 ### Fixed -- [#23](https://github.com/jamwaffles/sh1106/pull/23) Fixed command bytes for `PreChargePeriod` and `VcomhDeselect`. +- [#23](https://github.com/jamwaffles/sh1106/pull/23) Fixed command bytes for `PreChargePeriod` and + `VcomhDeselect`. ## [0.3.3] - 2020-06-09 ### Added -- [#22](https://github.com/jamwaffles/sh1106/pull/22) Add `DisplaySize::Display128x64NoOffset` variant for 128x64 displays that don't use a 132x64 buffer internally. +- [#22](https://github.com/jamwaffles/sh1106/pull/22) Add `DisplaySize::Display128x64NoOffset` + variant for 128x64 displays that don't use a 132x64 buffer internally. ## [0.3.2] - 2020-04-30 ### Added -- [#20](https://github.com/jamwaffles/sh1106/pull/20) Add `set_contrast` method to set the display contrast/brightness. +- [#20](https://github.com/jamwaffles/sh1106/pull/20) Add `set_contrast` method to set the display + contrast/brightness. ## [0.3.1] - 2020-03-21 @@ -46,7 +55,8 @@ with the [embedded-hal](crates.io/crates/embedded-hal) traits for maximum portab ### Changed -- **(breaking)** [#18](https://github.com/jamwaffles/sh1106/pull/18) Upgrade to embedded-graphics 0.6.0 +- **(breaking)** [#18](https://github.com/jamwaffles/sh1106/pull/18) Upgrade to embedded-graphics + 0.6.0 ## [0.3.0-alpha.4] @@ -58,7 +68,8 @@ with the [embedded-hal](crates.io/crates/embedded-hal) traits for maximum portab ### Added -- Added the `NoOutputPin` dummy pin type for SPI cases when no Chip Select pin is required. Use it like this: +- Added the `NoOutputPin` dummy pin type for SPI cases when no Chip Select pin is required. Use it + like this: ```rust let spi = Spi::spi1( @@ -72,23 +83,29 @@ let mut disp: GraphicsMode<_> = sh1106::Builder::new() ## 0.3.0-alpha.2 -Upgrade to new embedded-graphics `0.6.0-alpha.2` release. Please see the [embedded-graphics changelog](https://github.com/jamwaffles/embedded-graphics/blob/c0ed1700635f307a4c5114fec1769147878fd584/CHANGELOG.md) for more information. +Upgrade to new embedded-graphics `0.6.0-alpha.2` release. Please see the +[embedded-graphics changelog](https://github.com/jamwaffles/embedded-graphics/blob/c0ed1700635f307a4c5114fec1769147878fd584/CHANGELOG.md) +for more information. ### Changed -- **(breaking)** #11 Upgraded to [embedded-graphics](https://crates.io/crates/embedded-graphics) 0.6.0-alpha.2 +- **(breaking)** #11 Upgraded to [embedded-graphics](https://crates.io/crates/embedded-graphics) + 0.6.0-alpha.2 ## 0.3.0-alpha.1 -Upgrade to new embedded-graphics `0.6.0-alpha.1` release. Please see the [embedded-graphics changelog](https://github.com/jamwaffles/embedded-graphics/blob/embedded-graphics-v0.6.0-alpha.1/CHANGELOG.md) for more information. +Upgrade to new embedded-graphics `0.6.0-alpha.1` release. Please see the +[embedded-graphics changelog](https://github.com/jamwaffles/embedded-graphics/blob/embedded-graphics-v0.6.0-alpha.1/CHANGELOG.md) +for more information. ### Changed -- **(breaking)** #9 Upgraded to [embedded-graphics](https://crates.io/crates/embedded-graphics) 0.6.0-alpha.1 +- **(breaking)** #9 Upgraded to [embedded-graphics](https://crates.io/crates/embedded-graphics) + 0.6.0-alpha.1 -[unreleased]: https://github.com/jamwaffles/sh1106/compare/v0.4.0...HEAD +[unreleased]: https://github.com/jamwaffles/sh1106/compare/v0.4.0...HEAD [0.4.0]: https://github.com/jamwaffles/sh1106/compare/v0.3.4...v0.4.0 [0.3.4]: https://github.com/jamwaffles/sh1106/compare/v0.3.3...v0.3.4 [0.3.3]: https://github.com/jamwaffles/sh1106/compare/v0.3.2...v0.3.3 diff --git a/build.sh b/build.sh index 5b03c4f..a816898 100755 --- a/build.sh +++ b/build.sh @@ -1,6 +1,6 @@ #!/bin/sh -set -e +set -ex cargo build --target $TARGET --all-features --release @@ -8,5 +8,4 @@ if [ -z $DISABLE_EXAMPLES ]; then cargo build --target $TARGET --all-features --examples fi -cargo test --lib --target x86_64-unknown-linux-gnu -cargo test --doc --target x86_64-unknown-linux-gnu +cargo deadlinks --ignore-fragments diff --git a/src/lib.rs b/src/lib.rs index 843b243..6245586 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,7 +37,7 @@ //! //! ## Draw some text to the display //! -//! Uses [mode::GraphicsMode] and [embedded_graphics](../embedded_graphics/index.html). +//! Uses [mode::GraphicsMode] and [embedded_graphics](https://docs.rs/embedded_graphics). //! //! ```rust,no_run //! use embedded_graphics::{ From 2b1b272555e718f0b9642f99ab77fba9dc8fde15 Mon Sep 17 00:00:00 2001 From: James Waples Date: Tue, 22 Mar 2022 18:38:49 +0000 Subject: [PATCH 03/13] Use 1.50.0 in CI Required so we can use slice::fill --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2d9caba..2b9ebb9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ target_steps: &target_steps docker: - - image: circleci/rust:1.42.0 + - image: cimg/rust:1.50.0 steps: - checkout - restore_cache: From a2a42e5a567d9b85224067ca7120618f2649f6cf Mon Sep 17 00:00:00 2001 From: James Waples Date: Tue, 29 Mar 2022 20:49:51 +0100 Subject: [PATCH 04/13] Refactor fill solid to use u128 bit shifts --- src/mode/graphics.rs | 63 ++++++++++++-------------------------------- 1 file changed, 17 insertions(+), 46 deletions(-) diff --git a/src/mode/graphics.rs b/src/mode/graphics.rs index ed61d3f..f7f1b6a 100644 --- a/src/mode/graphics.rs +++ b/src/mode/graphics.rs @@ -242,63 +242,34 @@ where let display_width = self.properties.get_size().dimensions().0 as u32; - let mut remaining = height; + // Display is at most 128 pixels tall when rotated by 90º or 270º so we'll use a u128 here + let fill = 2u128.pow(height) - 1; + let moved = fill << y; - let mut block = y as u32 / 8; + let start_block = (y / 8) as usize; - // Top partial row - if y % 8 > 0 { - // Row (aka bit) in this block - let row = y as u32 % 8; + // Represents a bit mask of a single column of the entire display height + let whole_column = moved.to_le_bytes(); - let mask = (2u8.pow(height) - 1) << row; + let end_block = start_block + (height as usize / 8 + 1); - let start = (x as u32 + (block * display_width)) as usize; + // Ensure we don't wrap off the bottom of the screen + let end_block = end_block.min(7); - let slice = &mut self.buffer[start..(start + width as usize)]; + for current_x in x..(x + width as i32) { + // let new_blocks = &whole_column[start_block..=end_block.min(8)]; - // Write fill color without clobbering existing data in the other bits of the block - slice.iter_mut().for_each(|column| { - *column = (*column & !mask) | (color & mask); - }); - - remaining = remaining.saturating_sub((8 - row).min(height)); - block += 1; - } - - // Full height rows to fill - while remaining > 8 { - let start = (x as u32 + (block * display_width)) as usize; - - let slice = &mut self.buffer[start..(start + width as usize)]; - - slice.fill(color); - - block += 1; - remaining = remaining.saturating_sub(8); - } - - // Bottom partial row - if remaining > 0 { - // Row (aka bit) in this block - let row = 8 - (remaining % 8); + for block in start_block..=end_block { + let buffer_offset = current_x as usize + display_width as usize * block; - let mask = (2u8.pow(height) - 1) >> row; + let current = self.buffer[buffer_offset]; - let start = (x as u32 + (block * display_width)) as usize; + let mask = whole_column[block]; - let slice = &mut self.buffer[start..(start + width as usize)]; - - // Write fill color without clobbering existing data in the other bits of the block - slice.iter_mut().for_each(|column| { - *column = (*column & !mask) | (color & mask); - }); - - remaining = remaining.saturating_sub(8); + self.buffer[buffer_offset] = (current & !mask) | (color & mask); + } } - debug_assert_eq!(remaining, 0); - Ok(()) } } From c44f688efb21fd2606deee76469d82d49dc49455 Mon Sep 17 00:00:00 2001 From: James Waples Date: Wed, 30 Mar 2022 21:30:15 +0100 Subject: [PATCH 05/13] Remove animation example --- examples/dvd.bmp | Bin 2826 -> 0 bytes examples/rtic_dvd.rs | 180 ------------------------------------------- 2 files changed, 180 deletions(-) delete mode 100644 examples/dvd.bmp delete mode 100644 examples/rtic_dvd.rs diff --git a/examples/dvd.bmp b/examples/dvd.bmp deleted file mode 100644 index 9c13c84b2a33e9357cf9d1aacb6a6069d652f826..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2826 zcmdT?yG}zf40Y*HsVt1$I8++Y5P+WxEe+k;f<7t+n+j^05-xp4;8a+q+Yv_uZ&azAj$F(I?@> z@dJkH1#>pl8*jIs5mDAq$NM~<+Ot6Vbj$S9Z*y1&cdRfo;@oM1ryx5bst3nzt})Fk zW`DEx{Q3KqiSx Blue Pill -//! (black) GND -> GND -//! (red) +5V -> VCC -//! (yellow) SDA -> PB9 -//! (green) SCL -> PB8 -//! ``` -//! -//! Run on a Blue Pill with `cargo run --example rtic_dvd`. - -#![no_std] -#![no_main] - -use embedded_graphics::{ - geometry::Point, - image::Image, - pixelcolor::{BinaryColor, Rgb565}, - prelude::*, - primitives::{PrimitiveStyle, Rectangle}, -}; -use panic_semihosting as _; -use rtic::app; -use sh1106::{prelude::*, Builder}; -use stm32f1xx_hal::{ - gpio, - i2c::{BlockingI2c, DutyCycle, Mode}, - pac::{self, I2C1}, - prelude::*, - timer::{CountDownTimer, Event, Timer}, -}; -use tinybmp::Bmp; - -type Display = GraphicsMode< - I2cInterface< - BlockingI2c< - I2C1, - ( - gpio::gpiob::PB8>, - gpio::gpiob::PB9>, - ), - >, - >, ->; - -#[app(device = stm32f1xx_hal::pac, peripherals = true)] -const APP: () = { - struct Resources { - display: Display, - timer: CountDownTimer, - top_left: Point, - velocity: Point, - bmp: Bmp, - } - - #[init] - fn init(cx: init::Context) -> init::LateResources { - let dp = cx.device; - - let mut flash = dp.FLASH.constrain(); - let mut rcc = dp.RCC.constrain(); - - let clocks = rcc - .cfgr - .use_hse(8.mhz()) - .sysclk(72.mhz()) - .pclk1(36.mhz()) - .freeze(&mut flash.acr); - - let mut afio = dp.AFIO.constrain(&mut rcc.apb2); - - let mut gpiob = dp.GPIOB.split(&mut rcc.apb2); - - let scl = gpiob.pb8.into_alternate_open_drain(&mut gpiob.crh); - let sda = gpiob.pb9.into_alternate_open_drain(&mut gpiob.crh); - - let i2c = BlockingI2c::i2c1( - dp.I2C1, - (scl, sda), - &mut afio.mapr, - Mode::Fast { - frequency: 400_000.hz(), - duty_cycle: DutyCycle::Ratio2to1, - }, - clocks, - &mut rcc.apb1, - 1000, - 10, - 1000, - 1000, - ); - - let mut display: GraphicsMode<_> = Builder::new().connect_i2c(i2c).into(); - - display.init().unwrap(); - display.flush().unwrap(); - - // Update framerate - let fps = 20; - - let mut timer = Timer::tim1(dp.TIM1, &clocks, &mut rcc.apb2).start_count_down(fps.hz()); - - timer.listen(Event::Update); - - let bmp = Bmp::from_slice(include_bytes!("dvd.bmp")).unwrap(); - - // Init the static resources to use them later through RTIC - init::LateResources { - timer, - display, - top_left: Point::new(5, 3), - velocity: Point::new(1, 1), - bmp, - } - } - - #[idle()] - fn idle(_: idle::Context) -> ! { - loop { - // Fix default wfi() behaviour breaking debug probe - core::hint::spin_loop(); - } - } - - #[task(binds = TIM1_UP, resources = [display, top_left, velocity, timer, bmp])] - fn update(cx: update::Context) { - let update::Resources { - display, - top_left, - velocity, - timer, - bmp, - .. - } = cx.resources; - - let bottom_right = *top_left + bmp.bounding_box().size; - - // Erase previous image position with a filled black rectangle - Rectangle::with_corners(*top_left, bottom_right) - .into_styled(PrimitiveStyle::with_fill(BinaryColor::Off)) - .draw(display) - .unwrap(); - - // Check if the image collided with a screen edge - { - if bottom_right.x > display.size().width as i32 || top_left.x < 0 { - velocity.x = -velocity.x; - } - - if bottom_right.y > display.size().height as i32 || top_left.y < 0 { - velocity.y = -velocity.y; - } - } - - // Move the image - *top_left += *velocity; - - // Draw image at new position - Image::new(bmp, *top_left) - .draw(&mut display.color_converted()) - .unwrap(); - - // Write changes to the display - display.flush().unwrap(); - - // Clears the update flag - timer.clear_update_interrupt_flag(); - } - - extern "C" { - fn EXTI0(); - } -}; From 49caef1fcd28ebc771332a74b28fbb7f5e645a7c Mon Sep 17 00:00:00 2001 From: James Waples Date: Fri, 1 Apr 2022 21:20:57 +0100 Subject: [PATCH 06/13] Cleanup --- src/mode/graphics.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/mode/graphics.rs b/src/mode/graphics.rs index f7f1b6a..a1bf0aa 100644 --- a/src/mode/graphics.rs +++ b/src/mode/graphics.rs @@ -257,8 +257,6 @@ where let end_block = end_block.min(7); for current_x in x..(x + width as i32) { - // let new_blocks = &whole_column[start_block..=end_block.min(8)]; - for block in start_block..=end_block { let buffer_offset = current_x as usize + display_width as usize * block; From 1b1d8cd0e0e6bd55e500ac25383467832178b07c Mon Sep 17 00:00:00 2001 From: James Waples Date: Fri, 1 Apr 2022 21:24:19 +0100 Subject: [PATCH 07/13] More cleanup --- examples/bench-fill.rs | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/examples/bench-fill.rs b/examples/bench-fill.rs index 10dc643..78209f7 100644 --- a/examples/bench-fill.rs +++ b/examples/bench-fill.rs @@ -1,20 +1,5 @@ -//! Bounce a DVD player logo around the screen -//! -//! Like this, but with no color changing: https://bouncingdvdlogo.com/ -//! -//! 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 -> PB9 -//! (green) SCL -> PB8 -//! ``` -//! -//! Run on a Blue Pill with `cargo run --example rtic_dvd`. +//! Meant for development use only. Prints the time taken to draw a bunch of rectangles to the +//! display. Originally created to benchmark the `fill_solid` method. #![no_std] #![no_main] From e640e1b1bccf2900e447f8990dbf4a4cc0cfe797 Mon Sep 17 00:00:00 2001 From: James Waples Date: Sat, 25 Jun 2022 18:58:45 +0100 Subject: [PATCH 08/13] --wip-- [skip ci] --- Cargo.toml | 4 +- examples/graphics.rs | 6 +- examples/rotation.rs | 4 +- examples/text.rs | 6 +- src/interface/i2c.rs | 48 +++++++------ src/mode/graphics.rs | 158 +++++++++++++++++-------------------------- src/properties.rs | 22 ++++-- 7 files changed, 118 insertions(+), 130 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b337d27..9ad60d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,8 @@ circle-ci = { repository = "jamwaffles/sh1106", branch = "master" } [dependencies] embedded-hal = "0.2.3" embedded-graphics-core = { version = "0.3.2", optional = true } +# packed-display-buffer = { git = "https://github.com/embedded-graphics/packed-display-buffer.git", rev = "3741342d43c78fc8984db6ea316e7b13270ec4b7" } +packed-display-buffer = { path = "../embedded-graphics/packed-display-buffer" } [dev-dependencies] cortex-m = "0.7.3" @@ -44,5 +46,5 @@ incremental = false [profile.release] codegen-units = 1 -debug = true +debug = 2 lto = true diff --git a/examples/graphics.rs b/examples/graphics.rs index 01e6b00..4ddd28f 100644 --- a/examples/graphics.rs +++ b/examples/graphics.rs @@ -24,7 +24,7 @@ use embedded_graphics::{ primitives::{Circle, Line, PrimitiveStyle, Rectangle}, }; use panic_semihosting as _; -use sh1106::{prelude::*, Builder}; +use sh1106::{mode::graphics::Clear, prelude::*, Builder}; use stm32f1xx_hal::{ i2c::{BlockingI2c, DutyCycle, Mode}, prelude::*, @@ -66,7 +66,9 @@ fn main() -> ! { let mut display: GraphicsMode<_> = Builder::new().connect_i2c(i2c).into(); display.init().unwrap(); - display.flush().unwrap(); + display.clear(Clear::BufferAndDisplay).unwrap(); + + display.set_rotation(DisplayRotation::Rotate90).unwrap(); Line::new(Point::new(8, 16 + 16), Point::new(8 + 16, 16 + 16)) .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1)) diff --git a/examples/rotation.rs b/examples/rotation.rs index 78f3eed..5361f8c 100644 --- a/examples/rotation.rs +++ b/examples/rotation.rs @@ -30,7 +30,7 @@ use embedded_graphics::{ prelude::*, }; use panic_semihosting as _; -use sh1106::{prelude::*, Builder}; +use sh1106::{mode::graphics::Clear, prelude::*, Builder}; use stm32f1xx_hal::{ i2c::{BlockingI2c, DutyCycle, Mode}, prelude::*, @@ -76,7 +76,7 @@ fn main() -> ! { .into(); display.init().unwrap(); - display.flush().unwrap(); + display.clear(Clear::BufferAndDisplay).unwrap(); // Contrived example to test builder and instance methods. Sets rotation to 270 degress // or 90 degress counterclockwise diff --git a/examples/text.rs b/examples/text.rs index a2bce28..1f2fb05 100644 --- a/examples/text.rs +++ b/examples/text.rs @@ -26,7 +26,7 @@ use embedded_graphics::{ text::{Baseline, Text}, }; use panic_semihosting as _; -use sh1106::{prelude::*, Builder}; +use sh1106::{mode::graphics::Clear, prelude::*, Builder}; use stm32f1xx_hal::{ i2c::{BlockingI2c, DutyCycle, Mode}, prelude::*, @@ -68,7 +68,7 @@ fn main() -> ! { let mut display: GraphicsMode<_> = Builder::new().connect_i2c(i2c).into(); display.init().unwrap(); - display.flush().unwrap(); + display.clear(Clear::BufferAndDisplay).unwrap(); let text_style = MonoTextStyleBuilder::new() .font(&FONT_6X10) @@ -79,7 +79,7 @@ fn main() -> ! { .draw(&mut display) .unwrap(); - Text::with_baseline("Hello Rust!", Point::new(0, 16), text_style, Baseline::Top) + Text::with_baseline("Hello Rust!", Point::new(0, 10), text_style, Baseline::Top) .draw(&mut display) .unwrap(); diff --git a/src/interface/i2c.rs b/src/interface/i2c.rs index 543cb3b..f3cede9 100644 --- a/src/interface/i2c.rs +++ b/src/interface/i2c.rs @@ -3,7 +3,7 @@ use hal; use super::DisplayInterface; -use crate::{command::Page, Error}; +use crate::Error; /// SH1106 I2C communication interface pub struct I2cInterface { @@ -52,33 +52,37 @@ where return Ok(()); } - let mut page = Page::Page0 as u8; + // let mut page = Page::Page0 as u8; // Display width plus 4 start bytes let mut writebuf: [u8; BUFLEN] = [0; BUFLEN]; writebuf[0] = 0x40; // Following bytes are data bytes - for chunk in buf.chunks(CHUNKLEN) { - // Copy over all data from buffer, leaving the data command byte intact - writebuf[1..BUFLEN].copy_from_slice(&chunk); - - self.i2c - .write( - self.addr, - &[ - 0x00, // Command - page, // Page address - 0x02, // Lower column address - 0x10, // Upper column address (always zero, base is 10h) - ], - ) - .map_err(Error::Comm)?; - - self.i2c.write(self.addr, &writebuf).map_err(Error::Comm)?; - - page += 1; - } + // for chunk in buf.chunks(CHUNKLEN) { + let sub_buf = &mut writebuf[1..(buf.len() + 1)]; + + // Copy over all data from buffer, leaving the data command byte intact + sub_buf.copy_from_slice(&buf); + + let send_buf = &writebuf[0..(buf.len() + 1)]; + + // self.i2c + // .write( + // self.addr, + // &[ + // 0x00, // Command + // page, // Page address + // 0x02, // Lower column address + // 0x10, // Upper column address (always zero, base is 10h) + // ], + // ) + // .map_err(Error::Comm)?; + + self.i2c.write(self.addr, &send_buf).map_err(Error::Comm)?; + + // page += 1; + // } Ok(()) } diff --git a/src/mode/graphics.rs b/src/mode/graphics.rs index a1bf0aa..0dc3cd7 100644 --- a/src/mode/graphics.rs +++ b/src/mode/graphics.rs @@ -43,13 +43,25 @@ //! ``` use crate::{ - displayrotation::DisplayRotation, interface::DisplayInterface, + command::Page, displayrotation::DisplayRotation, interface::DisplayInterface, mode::displaymode::DisplayModeTrait, properties::DisplayProperties, Error, }; use embedded_graphics_core::{prelude::Point, primitives::Rectangle}; use hal::{blocking::delay::DelayMs, digital::v2::OutputPin}; -const BUFFER_SIZE: usize = 132 * 64 / 8; +/// What to clear. +#[derive(Debug, Copy, Clone)] +pub enum Clear { + /// Clear the display buffer only, leaving the display contents alone. + Buffer, + + /// Clear both the buffer and display. + BufferAndDisplay, +} + +// const BUFFER_SIZE: usize = 132 * 64 / 8; +const W: u32 = 132; +const H: u32 = 64; /// Graphics mode handler pub struct GraphicsMode @@ -57,7 +69,7 @@ where DI: DisplayInterface, { properties: DisplayProperties, - buffer: [u8; BUFFER_SIZE], + buffer: PackedBuffer, } impl DisplayModeTrait for GraphicsMode @@ -68,7 +80,7 @@ where fn new(properties: DisplayProperties) -> Self { GraphicsMode { properties, - buffer: [0; BUFFER_SIZE], + buffer: PackedBuffer::new(), } } @@ -82,9 +94,21 @@ impl GraphicsMode where DI: DisplayInterface, { - /// Clear the display buffer. You need to call `display.flush()` for any effect on the screen - pub fn clear(&mut self) { - self.buffer = [0; BUFFER_SIZE]; + /// Clear the display buffer. + pub fn clear(&mut self, clear: Clear) -> Result<(), DI::Error> { + self.buffer = PackedBuffer::new(); + + if matches!(clear, Clear::BufferAndDisplay) { + let display_size = self.properties.get_size(); + let column_offset = display_size.column_offset(); + + for i in 0..8 { + self.properties + .draw_page(Page::from(i * 8), column_offset, &[0x00; 128])?; + } + } + + Ok(()) } /// Reset display @@ -104,72 +128,46 @@ where rst.set_high().map_err(Error::Pin) } - /// Write out data to display + /// Write out data to display. pub fn flush(&mut self) -> Result<(), DI::Error> { let display_size = self.properties.get_size(); - // Ensure the display buffer is at the origin of the display before we send the full frame - // to prevent accidental offsets - let (display_width, display_height) = display_size.dimensions(); + let active = self.buffer.active_area().intersection(&self.bounding_box()); + let start_page = (active.top_left.y / 8) as u8; + let start_column = active.top_left.x as u8; + let column_offset = display_size.column_offset(); - self.properties.set_draw_area( - (column_offset, 0), - (display_width + column_offset, display_height), - )?; - let length = (display_width as usize) * (display_height as usize) / 8; + for (i, block) in self.buffer.active_blocks().enumerate() { + let page = Page::from((start_page + i as u8) * 8); + + self.properties + .draw_page(page, column_offset + start_column, block)?; + } - self.properties.draw(&self.buffer[..length]) + Ok(()) } /// Turn a pixel on or off. A non-zero `value` is treated as on, `0` as off. If the X and Y /// coordinates are out of the bounds of the display, this method call is a noop. pub fn set_pixel(&mut self, x: u32, y: u32, value: u8) { - let (display_width, _) = self.properties.get_size().dimensions(); let display_rotation = self.properties.get_rotation(); - let idx = match display_rotation { - DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => { - if x >= display_width as u32 { - return; - } - ((y as usize) / 8 * display_width as usize) + (x as usize) - } - + let point = match display_rotation { + DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => Point::new(x as i32, y as i32), DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => { - if y >= display_width as u32 { - return; - } - ((x as usize) / 8 * display_width as usize) + (y as usize) + Point::new(y as i32, x as i32) } }; - if idx >= self.buffer.len() { - return; - } - - let (byte, bit) = match display_rotation { - DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => { - let byte = - &mut self.buffer[((y as usize) / 8 * display_width as usize) + (x as usize)]; - let bit = 1 << (y % 8); - - (byte, bit) - } - DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => { - let byte = - &mut self.buffer[((x as usize) / 8 * display_width as usize) + (y as usize)]; - let bit = 1 << (x % 8); - - (byte, bit) - } - }; - - if value == 0 { - *byte &= !bit; - } else { - *byte |= bit; - } + self.buffer.set_pixel( + point, + if value == 0 { + BinaryColor::Off + } else { + BinaryColor::On + }, + ) } /// Display is set up in column mode, i.e. a byte walks down a column of 8 pixels from @@ -202,6 +200,7 @@ use embedded_graphics_core::{ pixelcolor::BinaryColor, Pixel, }; +use packed_display_buffer::PackedBuffer; #[cfg(feature = "graphics")] impl DrawTarget for GraphicsMode @@ -228,47 +227,14 @@ where } fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> { - let Rectangle { - top_left: Point { x, y }, - size: Size { width, height }, - } = area.intersection(&self.bounding_box()); - // swap coordinates if rotated - let (x, y, width, height) = match self.properties.get_rotation() { - DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => (x, y, width, height), - DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => (y, x, height, width), - }; - - let color = if color.is_on() { 0xff } else { 0x00 }; - - let display_width = self.properties.get_size().dimensions().0 as u32; - - // Display is at most 128 pixels tall when rotated by 90º or 270º so we'll use a u128 here - let fill = 2u128.pow(height) - 1; - let moved = fill << y; - - let start_block = (y / 8) as usize; - - // Represents a bit mask of a single column of the entire display height - let whole_column = moved.to_le_bytes(); - - let end_block = start_block + (height as usize / 8 + 1); - - // Ensure we don't wrap off the bottom of the screen - let end_block = end_block.min(7); - - for current_x in x..(x + width as i32) { - for block in start_block..=end_block { - let buffer_offset = current_x as usize + display_width as usize * block; - - let current = self.buffer[buffer_offset]; - - let mask = whole_column[block]; - - self.buffer[buffer_offset] = (current & !mask) | (color & mask); - } - } + self.buffer.fill_solid(area, color) + } - Ok(()) + fn fill_contiguous(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error> + where + I: IntoIterator, + { + self.buffer.fill_contiguous(area, colors) } } diff --git a/src/properties.rs b/src/properties.rs index c37b654..db1e67a 100644 --- a/src/properties.rs +++ b/src/properties.rs @@ -1,7 +1,7 @@ //! Container to store and set display properties use crate::{ - command::{Command, VcomhLevel}, + command::{Command, Page, VcomhLevel}, displayrotation::DisplayRotation, displaysize::DisplaySize, interface::DisplayInterface, @@ -113,10 +113,24 @@ where Ok(()) } + pub(crate) fn draw_page( + &mut self, + page: Page, + start_column: u8, + buffer: &[u8], + ) -> Result<(), DI::Error> { + self.set_page_start(page, start_column)?; + self.iface.send_data(buffer) + } + fn send_draw_address(&mut self) -> Result<(), DI::Error> { - Command::PageAddress(self.draw_row.into()).send(&mut self.iface)?; - Command::ColumnAddressLow(0xF & self.draw_column).send(&mut self.iface)?; - Command::ColumnAddressHigh(0xF & (self.draw_column >> 4)).send(&mut self.iface) + self.set_page_start(self.draw_row.into(), self.draw_column) + } + + fn set_page_start(&mut self, page: Page, column: u8) -> Result<(), DI::Error> { + Command::PageAddress(page.into()).send(&mut self.iface)?; + Command::ColumnAddressLow(0xF & column).send(&mut self.iface)?; + Command::ColumnAddressHigh(0xF & (column >> 4)).send(&mut self.iface) } /// Get the configured display size From e38efaa1c3d963ae6b2fe0eec9bc1b1245d458e4 Mon Sep 17 00:00:00 2001 From: techmccat Date: Tue, 16 Jan 2024 12:22:23 +0100 Subject: [PATCH 09/13] Revert "--wip-- [skip ci]" This reverts commit e640e1b1bccf2900e447f8990dbf4a4cc0cfe797. --- Cargo.toml | 4 +- examples/graphics.rs | 6 +- examples/rotation.rs | 4 +- examples/text.rs | 6 +- src/interface/i2c.rs | 48 ++++++------- src/mode/graphics.rs | 158 ++++++++++++++++++++++++++----------------- src/properties.rs | 22 ++---- 7 files changed, 130 insertions(+), 118 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9ad60d6..b337d27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,8 +21,6 @@ circle-ci = { repository = "jamwaffles/sh1106", branch = "master" } [dependencies] embedded-hal = "0.2.3" embedded-graphics-core = { version = "0.3.2", optional = true } -# packed-display-buffer = { git = "https://github.com/embedded-graphics/packed-display-buffer.git", rev = "3741342d43c78fc8984db6ea316e7b13270ec4b7" } -packed-display-buffer = { path = "../embedded-graphics/packed-display-buffer" } [dev-dependencies] cortex-m = "0.7.3" @@ -46,5 +44,5 @@ incremental = false [profile.release] codegen-units = 1 -debug = 2 +debug = true lto = true diff --git a/examples/graphics.rs b/examples/graphics.rs index 4ddd28f..01e6b00 100644 --- a/examples/graphics.rs +++ b/examples/graphics.rs @@ -24,7 +24,7 @@ use embedded_graphics::{ primitives::{Circle, Line, PrimitiveStyle, Rectangle}, }; use panic_semihosting as _; -use sh1106::{mode::graphics::Clear, prelude::*, Builder}; +use sh1106::{prelude::*, Builder}; use stm32f1xx_hal::{ i2c::{BlockingI2c, DutyCycle, Mode}, prelude::*, @@ -66,9 +66,7 @@ fn main() -> ! { let mut display: GraphicsMode<_> = Builder::new().connect_i2c(i2c).into(); display.init().unwrap(); - display.clear(Clear::BufferAndDisplay).unwrap(); - - display.set_rotation(DisplayRotation::Rotate90).unwrap(); + display.flush().unwrap(); Line::new(Point::new(8, 16 + 16), Point::new(8 + 16, 16 + 16)) .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1)) diff --git a/examples/rotation.rs b/examples/rotation.rs index 5361f8c..78f3eed 100644 --- a/examples/rotation.rs +++ b/examples/rotation.rs @@ -30,7 +30,7 @@ use embedded_graphics::{ prelude::*, }; use panic_semihosting as _; -use sh1106::{mode::graphics::Clear, prelude::*, Builder}; +use sh1106::{prelude::*, Builder}; use stm32f1xx_hal::{ i2c::{BlockingI2c, DutyCycle, Mode}, prelude::*, @@ -76,7 +76,7 @@ fn main() -> ! { .into(); display.init().unwrap(); - display.clear(Clear::BufferAndDisplay).unwrap(); + display.flush().unwrap(); // Contrived example to test builder and instance methods. Sets rotation to 270 degress // or 90 degress counterclockwise diff --git a/examples/text.rs b/examples/text.rs index 1f2fb05..a2bce28 100644 --- a/examples/text.rs +++ b/examples/text.rs @@ -26,7 +26,7 @@ use embedded_graphics::{ text::{Baseline, Text}, }; use panic_semihosting as _; -use sh1106::{mode::graphics::Clear, prelude::*, Builder}; +use sh1106::{prelude::*, Builder}; use stm32f1xx_hal::{ i2c::{BlockingI2c, DutyCycle, Mode}, prelude::*, @@ -68,7 +68,7 @@ fn main() -> ! { let mut display: GraphicsMode<_> = Builder::new().connect_i2c(i2c).into(); display.init().unwrap(); - display.clear(Clear::BufferAndDisplay).unwrap(); + display.flush().unwrap(); let text_style = MonoTextStyleBuilder::new() .font(&FONT_6X10) @@ -79,7 +79,7 @@ fn main() -> ! { .draw(&mut display) .unwrap(); - Text::with_baseline("Hello Rust!", Point::new(0, 10), text_style, Baseline::Top) + Text::with_baseline("Hello Rust!", Point::new(0, 16), text_style, Baseline::Top) .draw(&mut display) .unwrap(); diff --git a/src/interface/i2c.rs b/src/interface/i2c.rs index f3cede9..543cb3b 100644 --- a/src/interface/i2c.rs +++ b/src/interface/i2c.rs @@ -3,7 +3,7 @@ use hal; use super::DisplayInterface; -use crate::Error; +use crate::{command::Page, Error}; /// SH1106 I2C communication interface pub struct I2cInterface { @@ -52,37 +52,33 @@ where return Ok(()); } - // let mut page = Page::Page0 as u8; + let mut page = Page::Page0 as u8; // Display width plus 4 start bytes let mut writebuf: [u8; BUFLEN] = [0; BUFLEN]; writebuf[0] = 0x40; // Following bytes are data bytes - // for chunk in buf.chunks(CHUNKLEN) { - let sub_buf = &mut writebuf[1..(buf.len() + 1)]; - - // Copy over all data from buffer, leaving the data command byte intact - sub_buf.copy_from_slice(&buf); - - let send_buf = &writebuf[0..(buf.len() + 1)]; - - // self.i2c - // .write( - // self.addr, - // &[ - // 0x00, // Command - // page, // Page address - // 0x02, // Lower column address - // 0x10, // Upper column address (always zero, base is 10h) - // ], - // ) - // .map_err(Error::Comm)?; - - self.i2c.write(self.addr, &send_buf).map_err(Error::Comm)?; - - // page += 1; - // } + for chunk in buf.chunks(CHUNKLEN) { + // Copy over all data from buffer, leaving the data command byte intact + writebuf[1..BUFLEN].copy_from_slice(&chunk); + + self.i2c + .write( + self.addr, + &[ + 0x00, // Command + page, // Page address + 0x02, // Lower column address + 0x10, // Upper column address (always zero, base is 10h) + ], + ) + .map_err(Error::Comm)?; + + self.i2c.write(self.addr, &writebuf).map_err(Error::Comm)?; + + page += 1; + } Ok(()) } diff --git a/src/mode/graphics.rs b/src/mode/graphics.rs index 0dc3cd7..a1bf0aa 100644 --- a/src/mode/graphics.rs +++ b/src/mode/graphics.rs @@ -43,25 +43,13 @@ //! ``` use crate::{ - command::Page, displayrotation::DisplayRotation, interface::DisplayInterface, + displayrotation::DisplayRotation, interface::DisplayInterface, mode::displaymode::DisplayModeTrait, properties::DisplayProperties, Error, }; use embedded_graphics_core::{prelude::Point, primitives::Rectangle}; use hal::{blocking::delay::DelayMs, digital::v2::OutputPin}; -/// What to clear. -#[derive(Debug, Copy, Clone)] -pub enum Clear { - /// Clear the display buffer only, leaving the display contents alone. - Buffer, - - /// Clear both the buffer and display. - BufferAndDisplay, -} - -// const BUFFER_SIZE: usize = 132 * 64 / 8; -const W: u32 = 132; -const H: u32 = 64; +const BUFFER_SIZE: usize = 132 * 64 / 8; /// Graphics mode handler pub struct GraphicsMode @@ -69,7 +57,7 @@ where DI: DisplayInterface, { properties: DisplayProperties, - buffer: PackedBuffer, + buffer: [u8; BUFFER_SIZE], } impl DisplayModeTrait for GraphicsMode @@ -80,7 +68,7 @@ where fn new(properties: DisplayProperties) -> Self { GraphicsMode { properties, - buffer: PackedBuffer::new(), + buffer: [0; BUFFER_SIZE], } } @@ -94,21 +82,9 @@ impl GraphicsMode where DI: DisplayInterface, { - /// Clear the display buffer. - pub fn clear(&mut self, clear: Clear) -> Result<(), DI::Error> { - self.buffer = PackedBuffer::new(); - - if matches!(clear, Clear::BufferAndDisplay) { - let display_size = self.properties.get_size(); - let column_offset = display_size.column_offset(); - - for i in 0..8 { - self.properties - .draw_page(Page::from(i * 8), column_offset, &[0x00; 128])?; - } - } - - Ok(()) + /// Clear the display buffer. You need to call `display.flush()` for any effect on the screen + pub fn clear(&mut self) { + self.buffer = [0; BUFFER_SIZE]; } /// Reset display @@ -128,46 +104,72 @@ where rst.set_high().map_err(Error::Pin) } - /// Write out data to display. + /// Write out data to display pub fn flush(&mut self) -> Result<(), DI::Error> { let display_size = self.properties.get_size(); - let active = self.buffer.active_area().intersection(&self.bounding_box()); - let start_page = (active.top_left.y / 8) as u8; - let start_column = active.top_left.x as u8; - + // Ensure the display buffer is at the origin of the display before we send the full frame + // to prevent accidental offsets + let (display_width, display_height) = display_size.dimensions(); let column_offset = display_size.column_offset(); + self.properties.set_draw_area( + (column_offset, 0), + (display_width + column_offset, display_height), + )?; - for (i, block) in self.buffer.active_blocks().enumerate() { - let page = Page::from((start_page + i as u8) * 8); - - self.properties - .draw_page(page, column_offset + start_column, block)?; - } + let length = (display_width as usize) * (display_height as usize) / 8; - Ok(()) + self.properties.draw(&self.buffer[..length]) } /// Turn a pixel on or off. A non-zero `value` is treated as on, `0` as off. If the X and Y /// coordinates are out of the bounds of the display, this method call is a noop. pub fn set_pixel(&mut self, x: u32, y: u32, value: u8) { + let (display_width, _) = self.properties.get_size().dimensions(); let display_rotation = self.properties.get_rotation(); - let point = match display_rotation { - DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => Point::new(x as i32, y as i32), + let idx = match display_rotation { + DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => { + if x >= display_width as u32 { + return; + } + ((y as usize) / 8 * display_width as usize) + (x as usize) + } + DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => { - Point::new(y as i32, x as i32) + if y >= display_width as u32 { + return; + } + ((x as usize) / 8 * display_width as usize) + (y as usize) } }; - self.buffer.set_pixel( - point, - if value == 0 { - BinaryColor::Off - } else { - BinaryColor::On - }, - ) + if idx >= self.buffer.len() { + return; + } + + let (byte, bit) = match display_rotation { + DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => { + let byte = + &mut self.buffer[((y as usize) / 8 * display_width as usize) + (x as usize)]; + let bit = 1 << (y % 8); + + (byte, bit) + } + DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => { + let byte = + &mut self.buffer[((x as usize) / 8 * display_width as usize) + (y as usize)]; + let bit = 1 << (x % 8); + + (byte, bit) + } + }; + + if value == 0 { + *byte &= !bit; + } else { + *byte |= bit; + } } /// Display is set up in column mode, i.e. a byte walks down a column of 8 pixels from @@ -200,7 +202,6 @@ use embedded_graphics_core::{ pixelcolor::BinaryColor, Pixel, }; -use packed_display_buffer::PackedBuffer; #[cfg(feature = "graphics")] impl DrawTarget for GraphicsMode @@ -227,14 +228,47 @@ where } fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> { - self.buffer.fill_solid(area, color) - } + let Rectangle { + top_left: Point { x, y }, + size: Size { width, height }, + } = area.intersection(&self.bounding_box()); + // swap coordinates if rotated + let (x, y, width, height) = match self.properties.get_rotation() { + DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => (x, y, width, height), + DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => (y, x, height, width), + }; - fn fill_contiguous(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error> - where - I: IntoIterator, - { - self.buffer.fill_contiguous(area, colors) + let color = if color.is_on() { 0xff } else { 0x00 }; + + let display_width = self.properties.get_size().dimensions().0 as u32; + + // Display is at most 128 pixels tall when rotated by 90º or 270º so we'll use a u128 here + let fill = 2u128.pow(height) - 1; + let moved = fill << y; + + let start_block = (y / 8) as usize; + + // Represents a bit mask of a single column of the entire display height + let whole_column = moved.to_le_bytes(); + + let end_block = start_block + (height as usize / 8 + 1); + + // Ensure we don't wrap off the bottom of the screen + let end_block = end_block.min(7); + + for current_x in x..(x + width as i32) { + for block in start_block..=end_block { + let buffer_offset = current_x as usize + display_width as usize * block; + + let current = self.buffer[buffer_offset]; + + let mask = whole_column[block]; + + self.buffer[buffer_offset] = (current & !mask) | (color & mask); + } + } + + Ok(()) } } diff --git a/src/properties.rs b/src/properties.rs index db1e67a..c37b654 100644 --- a/src/properties.rs +++ b/src/properties.rs @@ -1,7 +1,7 @@ //! Container to store and set display properties use crate::{ - command::{Command, Page, VcomhLevel}, + command::{Command, VcomhLevel}, displayrotation::DisplayRotation, displaysize::DisplaySize, interface::DisplayInterface, @@ -113,24 +113,10 @@ where Ok(()) } - pub(crate) fn draw_page( - &mut self, - page: Page, - start_column: u8, - buffer: &[u8], - ) -> Result<(), DI::Error> { - self.set_page_start(page, start_column)?; - self.iface.send_data(buffer) - } - fn send_draw_address(&mut self) -> Result<(), DI::Error> { - self.set_page_start(self.draw_row.into(), self.draw_column) - } - - fn set_page_start(&mut self, page: Page, column: u8) -> Result<(), DI::Error> { - Command::PageAddress(page.into()).send(&mut self.iface)?; - Command::ColumnAddressLow(0xF & column).send(&mut self.iface)?; - Command::ColumnAddressHigh(0xF & (column >> 4)).send(&mut self.iface) + Command::PageAddress(self.draw_row.into()).send(&mut self.iface)?; + Command::ColumnAddressLow(0xF & self.draw_column).send(&mut self.iface)?; + Command::ColumnAddressHigh(0xF & (self.draw_column >> 4)).send(&mut self.iface) } /// Get the configured display size From f38c6132d378f83f853a79764544aa65582b9b8b Mon Sep 17 00:00:00 2001 From: techmccat Date: Fri, 19 Jan 2024 11:51:13 +0100 Subject: [PATCH 10/13] Switch to embedded-hal 1.0 Won't pass CI because stm32f1xx-hal doesn't have the new traits yet --- Cargo.toml | 2 +- src/builder.rs | 21 +++++++++++------ src/interface/i2c.rs | 4 ++-- src/interface/spi.rs | 9 +++++--- src/mode/graphics.rs | 4 ++-- src/test_helpers.rs | 54 ++++++++++++++++++++++++-------------------- 6 files changed, 54 insertions(+), 40 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d3baf5a..f09617b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ targets = [ "thumbv7m-none-eabi", "thumbv7em-none-eabihf" ] circle-ci = { repository = "jamwaffles/sh1106", branch = "master" } [dependencies] -embedded-hal = "0.2.3" +embedded-hal = "1" embedded-graphics-core = { version = "0.4.0", optional = true } [dev-dependencies] diff --git a/src/builder.rs b/src/builder.rs index bdcbf28..22094b4 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -56,7 +56,7 @@ //! ``` use core::marker::PhantomData; -use hal::{self, digital::v2::OutputPin}; +use hal::{self, digital::OutputPin}; use crate::{ displayrotation::DisplayRotation, @@ -116,7 +116,7 @@ impl Builder { /// Finish the builder and use I2C to communicate with the display pub fn connect_i2c(self, i2c: I2C) -> DisplayMode>> where - I2C: hal::blocking::i2c::Write, + I2C: hal::i2c::I2c, { let properties = DisplayProperties::new( I2cInterface::new(i2c, self.i2c_addr), @@ -138,8 +138,7 @@ impl Builder { cs: CS, ) -> DisplayMode>> where - SPI: hal::blocking::spi::Transfer - + hal::blocking::spi::Write, + SPI: hal::spi::SpiBus, DC: OutputPin, CS: OutputPin, { @@ -158,15 +157,17 @@ pub struct NoOutputPin { _m: PhantomData, } -impl NoOutputPin { +impl NoOutputPin { /// Create a new instance of `NoOutputPin` pub fn new() -> Self { Self { _m: PhantomData } } } -impl OutputPin for NoOutputPin { +impl hal::digital::ErrorType for NoOutputPin { type Error = PinE; +} +impl OutputPin for NoOutputPin { fn set_low(&mut self) -> Result<(), PinE> { Ok(()) } @@ -178,9 +179,15 @@ impl OutputPin for NoOutputPin { #[cfg(test)] mod tests { use super::NoOutputPin; - use embedded_hal::digital::v2::OutputPin; + use embedded_hal::digital::OutputPin; + #[derive(Debug)] enum SomeError {} + impl hal::digital::Error for SomeError { + fn kind(&self) -> hal::digital::ErrorKind { + hal::digital::ErrorKind::Other + } + } struct SomeDriver> { #[allow(dead_code)] diff --git a/src/interface/i2c.rs b/src/interface/i2c.rs index 543cb3b..ff7a857 100644 --- a/src/interface/i2c.rs +++ b/src/interface/i2c.rs @@ -13,7 +13,7 @@ pub struct I2cInterface { impl I2cInterface where - I2C: hal::blocking::i2c::Write, + I2C: hal::i2c::I2c, { /// Create new sh1106 I2C interface pub fn new(i2c: I2C, addr: u8) -> Self { @@ -23,7 +23,7 @@ where impl DisplayInterface for I2cInterface where - I2C: hal::blocking::i2c::Write, + I2C: hal::i2c::I2c, { type Error = Error; diff --git a/src/interface/spi.rs b/src/interface/spi.rs index 5a44918..a00a1d1 100644 --- a/src/interface/spi.rs +++ b/src/interface/spi.rs @@ -1,6 +1,6 @@ //! sh1106 SPI interface -use hal::{self, digital::v2::OutputPin}; +use hal::{self, digital::OutputPin}; use super::DisplayInterface; use crate::Error; @@ -16,7 +16,8 @@ pub struct SpiInterface { impl SpiInterface where - SPI: hal::blocking::spi::Write, + // we shouldn't need the whole bus but we need to flush before setting dc + SPI: hal::spi::SpiBus, DC: OutputPin, CS: OutputPin, { @@ -28,7 +29,7 @@ where impl DisplayInterface for SpiInterface where - SPI: hal::blocking::spi::Write, + SPI: hal::spi::SpiBus, DC: OutputPin, CS: OutputPin, { @@ -43,6 +44,7 @@ where self.dc.set_low().map_err(Error::Pin)?; self.spi.write(&cmds).map_err(Error::Comm)?; + self.spi.flush().map_err(Error::Comm)?; self.dc.set_high().map_err(Error::Pin)?; self.cs.set_high().map_err(Error::Pin) @@ -55,6 +57,7 @@ where self.dc.set_high().map_err(Error::Pin)?; self.spi.write(&buf).map_err(Error::Comm)?; + self.spi.flush().map_err(Error::Comm)?; self.cs.set_high().map_err(Error::Pin) } diff --git a/src/mode/graphics.rs b/src/mode/graphics.rs index a1bf0aa..5ee4b28 100644 --- a/src/mode/graphics.rs +++ b/src/mode/graphics.rs @@ -47,7 +47,7 @@ use crate::{ mode::displaymode::DisplayModeTrait, properties::DisplayProperties, Error, }; use embedded_graphics_core::{prelude::Point, primitives::Rectangle}; -use hal::{blocking::delay::DelayMs, digital::v2::OutputPin}; +use hal::{delay::DelayNs, digital::OutputPin}; const BUFFER_SIZE: usize = 132 * 64 / 8; @@ -95,7 +95,7 @@ where ) -> Result<(), Error<(), PinE>> where RST: OutputPin, - DELAY: DelayMs, + DELAY: DelayNs, { rst.set_high().map_err(Error::Pin)?; delay.delay_ms(1); diff --git a/src/test_helpers.rs b/src/test_helpers.rs index 2573559..2cdee8a 100644 --- a/src/test_helpers.rs +++ b/src/test_helpers.rs @@ -1,45 +1,48 @@ //! Helpers for use in examples and tests use embedded_hal::{ - blocking::{ - i2c, - spi::{self, Transfer}, - }, - digital::v2::OutputPin, + i2c::{self, Operation}, + spi, + digital::OutputPin, }; #[allow(dead_code)] #[derive(Debug, Clone, Copy)] pub struct SpiStub; -impl spi::Write for SpiStub { - type Error = (); - - fn write(&mut self, _buf: &[u8]) -> Result<(), ()> { +impl hal::spi::ErrorType for SpiStub { + type Error = core::convert::Infallible; +} +impl spi::SpiBus for SpiStub { + fn write(&mut self, _buf: &[u8]) -> Result<(), Self::Error> { Ok(()) } -} - -impl Transfer for SpiStub { - type Error = (); - - fn transfer<'a>(&mut self, buf: &'a mut [u8]) -> Result<&'a [u8], ()> { - Ok(buf) + fn read(&mut self, _buf: &mut [u8]) -> Result<(), Self::Error> { + Ok(()) + } + fn transfer(&mut self, _buf: &mut [u8], _buf2: &[u8]) -> Result<(), Self::Error> { + Ok(()) + } + fn transfer_in_place(&mut self, _buf: &mut [u8]) -> Result<(), Self::Error> { + Ok(()) + } + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) } } #[allow(dead_code)] #[derive(Debug, Clone, Copy)] pub struct PinStub; - +impl hal::digital::ErrorType for PinStub { + type Error = core::convert::Infallible; +} impl OutputPin for PinStub { - type Error = (); - - fn set_high(&mut self) -> Result<(), ()> { + fn set_high(&mut self) -> Result<(), Self::Error> { Ok(()) } - fn set_low(&mut self) -> Result<(), ()> { + fn set_low(&mut self) -> Result<(), Self::Error> { Ok(()) } } @@ -48,10 +51,11 @@ impl OutputPin for PinStub { #[derive(Debug, Clone, Copy)] pub struct I2cStub; -impl i2c::Write for I2cStub { - type Error = (); - - fn write(&mut self, _addr: u8, _buf: &[u8]) -> Result<(), ()> { +impl hal::i2c::ErrorType for I2cStub { + type Error = core::convert::Infallible; +} +impl i2c::I2c for I2cStub { + fn transaction(&mut self, _addr: u8, _buf: &mut [Operation<'_>]) -> Result<(), Self::Error> { Ok(()) } } From 6ea79f62bde758acc0f269e01dbc95e40f06e5b3 Mon Sep 17 00:00:00 2001 From: techmccat Date: Fri, 19 Jan 2024 12:36:57 +0100 Subject: [PATCH 11/13] spi: switch to SpiDevice Don't require bus ownership and managing the CS pin --- src/interface/spi.rs | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/src/interface/spi.rs b/src/interface/spi.rs index a00a1d1..a33e650 100644 --- a/src/interface/spi.rs +++ b/src/interface/spi.rs @@ -11,27 +11,24 @@ use crate::Error; pub struct SpiInterface { spi: SPI, dc: DC, - cs: CS, } impl SpiInterface where // we shouldn't need the whole bus but we need to flush before setting dc - SPI: hal::spi::SpiBus, + SPI: hal::spi::SpiDevice, DC: OutputPin, - CS: OutputPin, { /// Create new SPI interface for communciation with sh1106 pub fn new(spi: SPI, dc: DC, cs: CS) -> Self { - Self { spi, dc, cs } + Self { spi, dc } } } impl DisplayInterface for SpiInterface where - SPI: hal::spi::SpiBus, + SPI: hal::spi::SpiDevice, DC: OutputPin, - CS: OutputPin, { type Error = Error; @@ -40,25 +37,14 @@ where } fn send_commands(&mut self, cmds: &[u8]) -> Result<(), Self::Error> { - self.cs.set_low().map_err(Error::Pin)?; self.dc.set_low().map_err(Error::Pin)?; - self.spi.write(&cmds).map_err(Error::Comm)?; - self.spi.flush().map_err(Error::Comm)?; - - self.dc.set_high().map_err(Error::Pin)?; - self.cs.set_high().map_err(Error::Pin) + self.dc.set_high().map_err(Error::Pin) } fn send_data(&mut self, buf: &[u8]) -> Result<(), Self::Error> { - self.cs.set_low().map_err(Error::Pin)?; - // 1 = data, 0 = command self.dc.set_high().map_err(Error::Pin)?; - - self.spi.write(&buf).map_err(Error::Comm)?; - self.spi.flush().map_err(Error::Comm)?; - - self.cs.set_high().map_err(Error::Pin) + self.spi.write(&buf).map_err(Error::Comm) } } From 6726e5b272d771e4612d8ef13aad0ed053ebe847 Mon Sep 17 00:00:00 2001 From: techmccat Date: Fri, 19 Jan 2024 13:13:24 +0100 Subject: [PATCH 12/13] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2fcedc..3fffe20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and works with the [embedded-hal](crates.io/crates/embedded-hal) traits for maxi - **(breaking)** [#28](https://github.com/jamwaffles/sh1106/pull/28) Upgrade MSRV to 1.50.0, add a faster implementation of `DrawTarget::fill_solid`. +- **(breaking)** Switch to embedded-hal 1 ## [0.5.0] - 2023-08-30 From 637f5ed8bf0d9b66d481f49088b478aeeb50bc2c Mon Sep 17 00:00:00 2001 From: techmccat Date: Sat, 20 Jan 2024 12:12:58 +0100 Subject: [PATCH 13/13] spi: remove unused cs pins --- src/builder.rs | 12 +++++------- src/interface/spi.rs | 12 +++++------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 22094b4..835e21a 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -131,23 +131,21 @@ impl Builder { /// If the Chip Select (CS) pin is not required, [`NoOutputPin`] can be used as a dummy argument /// /// [`NoOutputPin`]: ./struct.NoOutputPin.html - pub fn connect_spi( + pub fn connect_spi( self, spi: SPI, dc: DC, - cs: CS, - ) -> DisplayMode>> + ) -> DisplayMode>> where - SPI: hal::spi::SpiBus, + SPI: hal::spi::SpiDevice, DC: OutputPin, - CS: OutputPin, { let properties = DisplayProperties::new( - SpiInterface::new(spi, dc, cs), + SpiInterface::new(spi, dc), self.display_size, self.rotation, ); - DisplayMode::>>::new(properties) + DisplayMode::>>::new(properties) } } diff --git a/src/interface/spi.rs b/src/interface/spi.rs index a33e650..fd49c32 100644 --- a/src/interface/spi.rs +++ b/src/interface/spi.rs @@ -8,33 +8,31 @@ use crate::Error; /// SPI display interface. /// /// This combines the SPI peripheral and a data/command pin -pub struct SpiInterface { +pub struct SpiInterface { spi: SPI, dc: DC, } -impl SpiInterface +impl SpiInterface where // we shouldn't need the whole bus but we need to flush before setting dc SPI: hal::spi::SpiDevice, DC: OutputPin, { /// Create new SPI interface for communciation with sh1106 - pub fn new(spi: SPI, dc: DC, cs: CS) -> Self { + pub fn new(spi: SPI, dc: DC) -> Self { Self { spi, dc } } } -impl DisplayInterface for SpiInterface +impl DisplayInterface for SpiInterface where SPI: hal::spi::SpiDevice, DC: OutputPin, { type Error = Error; - fn init(&mut self) -> Result<(), Self::Error> { - self.cs.set_high().map_err(Error::Pin) - } + fn init(&mut self) -> Result<(), Self::Error> { Ok(()) } fn send_commands(&mut self, cmds: &[u8]) -> Result<(), Self::Error> { self.dc.set_low().map_err(Error::Pin)?;