diff --git a/.circleci/config.yml b/.circleci/config.yml index 7519bef..2b9ebb9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,12 +1,16 @@ target_steps: &target_steps docker: - - image: circleci/rust:1.42.0 + - image: cimg/rust:1.50.0 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/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/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/examples/bench-fill.rs b/examples/bench-fill.rs new file mode 100644 index 0000000..78209f7 --- /dev/null +++ b/examples/bench-fill.rs @@ -0,0 +1,192 @@ +//! 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] + +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/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/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::{ diff --git a/src/mode/graphics.rs b/src/mode/graphics.rs index 15642fb..0dc3cd7 100644 --- a/src/mode/graphics.rs +++ b/src/mode/graphics.rs @@ -42,14 +42,26 @@ //! display.flush().unwrap(); //! ``` -use hal::{blocking::delay::DelayMs, digital::v2::OutputPin}; - 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(&self.buffer[..length]) + self.properties + .draw_page(page, column_offset + start_column, block)?; + } + + 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 @@ -226,6 +225,17 @@ where Ok(()) } + + fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> { + self.buffer.fill_solid(area, color) + } + + fn fill_contiguous(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error> + where + I: IntoIterator, + { + self.buffer.fill_contiguous(area, colors) + } } #[cfg(feature = "graphics")] 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