Skip to content

Commit

Permalink
Add fast rectangle fill impl and RTIC DVD example
Browse files Browse the repository at this point in the history
  • Loading branch information
jamwaffles committed Mar 22, 2022
1 parent 18a2bb2 commit 72f84f7
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 2 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ cortex-m = "0.7.3"
cortex-m-rt = "0.6.10"
embedded-graphics = "0.7.1"
panic-semihosting = "0.5.2"
cortex-m-rtic = "0.5.6"
tinybmp = "0.3.1"

[dev-dependencies.stm32f1xx-hal]
version = "0.7.0"
Expand Down
Binary file added examples/dvd.bmp
Binary file not shown.
180 changes: 180 additions & 0 deletions examples/rtic_dvd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
//! 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 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::Alternate<gpio::OpenDrain>>,
gpio::gpiob::PB9<gpio::Alternate<gpio::OpenDrain>>,
),
>,
>,
>;

#[app(device = stm32f1xx_hal::pac, peripherals = true)]
const APP: () = {
struct Resources {
display: Display,
timer: CountDownTimer<pac::TIM1>,
top_left: Point,
velocity: Point,
bmp: Bmp<Rgb565, 'static>,
}

#[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();
}
};
79 changes: 77 additions & 2 deletions src/mode/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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")]
Expand Down

0 comments on commit 72f84f7

Please sign in to comment.