diff --git a/.circleci/config.yml b/.circleci/config.yml index f2dd352..47b24fb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,6 +6,9 @@ target_steps: &target_steps - restore_cache: key: v2-sh1106-{{ .Environment.CIRCLE_JOB }}-{{ checksum "Cargo.toml" }} - 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 7a5f99f..3fffe20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ and works with the [embedded-hal](crates.io/crates/embedded-hal) traits for maxi ## [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`. +- **(breaking)** Switch to embedded-hal 1 + ## [0.5.0] - 2023-08-30 ### Changed @@ -15,6 +21,7 @@ and works with the [embedded-hal](crates.io/crates/embedded-hal) traits for maxi - **(breaking)** [#34](https://github.com/jamwaffles/sh1106/pull/34) Upgrade to `embedded-graphics` 0.8 and `embedded-graphics-core` to 0.4. + ## [0.4.0] - 2021-07-11 ### Changed 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/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/src/builder.rs b/src/builder.rs index bdcbf28..835e21a 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), @@ -131,24 +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::blocking::spi::Transfer - + hal::blocking::spi::Write, + 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) } } @@ -158,15 +155,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 +177,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..fd49c32 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; @@ -8,54 +8,41 @@ 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, - cs: CS, } -impl 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::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 } + pub fn new(spi: SPI, dc: DC) -> Self { + Self { spi, dc } } } -impl DisplayInterface for SpiInterface +impl DisplayInterface for SpiInterface where - SPI: hal::blocking::spi::Write, + SPI: hal::spi::SpiDevice, DC: OutputPin, - CS: 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.cs.set_low().map_err(Error::Pin)?; self.dc.set_low().map_err(Error::Pin)?; - self.spi.write(&cmds).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.cs.set_high().map_err(Error::Pin) + self.spi.write(&buf).map_err(Error::Comm) } } 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..5ee4b28 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::{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); @@ -226,6 +226,50 @@ 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; + + // 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(()) + } } #[cfg(feature = "graphics")] 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(()) } }