Skip to content

Commit 72f84f7

Browse files
committed
Add fast rectangle fill impl and RTIC DVD example
1 parent 18a2bb2 commit 72f84f7

File tree

4 files changed

+259
-2
lines changed

4 files changed

+259
-2
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ cortex-m = "0.7.3"
2727
cortex-m-rt = "0.6.10"
2828
embedded-graphics = "0.7.1"
2929
panic-semihosting = "0.5.2"
30+
cortex-m-rtic = "0.5.6"
31+
tinybmp = "0.3.1"
3032

3133
[dev-dependencies.stm32f1xx-hal]
3234
version = "0.7.0"

examples/dvd.bmp

2.76 KB
Binary file not shown.

examples/rtic_dvd.rs

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
//! Bounce a DVD player logo around the screen
2+
//!
3+
//! Like this, but with no color changing: https://bouncingdvdlogo.com/
4+
//!
5+
//! This example is for the STM32F103 "Blue Pill" board using I2C1.
6+
//!
7+
//! Wiring connections are as follows for a CRIUS-branded display:
8+
//!
9+
//! ```
10+
//! Display -> Blue Pill
11+
//! (black) GND -> GND
12+
//! (red) +5V -> VCC
13+
//! (yellow) SDA -> PB9
14+
//! (green) SCL -> PB8
15+
//! ```
16+
//!
17+
//! Run on a Blue Pill with `cargo run --example rtic_dvd`.
18+
19+
#![no_std]
20+
#![no_main]
21+
22+
use embedded_graphics::{
23+
geometry::Point,
24+
image::Image,
25+
pixelcolor::{BinaryColor, Rgb565},
26+
prelude::*,
27+
primitives::{PrimitiveStyle, Rectangle},
28+
};
29+
use panic_semihosting as _;
30+
use rtic::app;
31+
use sh1106::{prelude::*, Builder};
32+
use stm32f1xx_hal::{
33+
gpio,
34+
i2c::{BlockingI2c, DutyCycle, Mode},
35+
pac::{self, I2C1},
36+
prelude::*,
37+
timer::{CountDownTimer, Event, Timer},
38+
};
39+
use tinybmp::Bmp;
40+
41+
type Display = GraphicsMode<
42+
I2cInterface<
43+
BlockingI2c<
44+
I2C1,
45+
(
46+
gpio::gpiob::PB8<gpio::Alternate<gpio::OpenDrain>>,
47+
gpio::gpiob::PB9<gpio::Alternate<gpio::OpenDrain>>,
48+
),
49+
>,
50+
>,
51+
>;
52+
53+
#[app(device = stm32f1xx_hal::pac, peripherals = true)]
54+
const APP: () = {
55+
struct Resources {
56+
display: Display,
57+
timer: CountDownTimer<pac::TIM1>,
58+
top_left: Point,
59+
velocity: Point,
60+
bmp: Bmp<Rgb565, 'static>,
61+
}
62+
63+
#[init]
64+
fn init(cx: init::Context) -> init::LateResources {
65+
let dp = cx.device;
66+
67+
let mut flash = dp.FLASH.constrain();
68+
let mut rcc = dp.RCC.constrain();
69+
70+
let clocks = rcc
71+
.cfgr
72+
.use_hse(8.mhz())
73+
.sysclk(72.mhz())
74+
.pclk1(36.mhz())
75+
.freeze(&mut flash.acr);
76+
77+
let mut afio = dp.AFIO.constrain(&mut rcc.apb2);
78+
79+
let mut gpiob = dp.GPIOB.split(&mut rcc.apb2);
80+
81+
let scl = gpiob.pb8.into_alternate_open_drain(&mut gpiob.crh);
82+
let sda = gpiob.pb9.into_alternate_open_drain(&mut gpiob.crh);
83+
84+
let i2c = BlockingI2c::i2c1(
85+
dp.I2C1,
86+
(scl, sda),
87+
&mut afio.mapr,
88+
Mode::Fast {
89+
frequency: 400_000.hz(),
90+
duty_cycle: DutyCycle::Ratio2to1,
91+
},
92+
clocks,
93+
&mut rcc.apb1,
94+
1000,
95+
10,
96+
1000,
97+
1000,
98+
);
99+
100+
let mut display: GraphicsMode<_> = Builder::new().connect_i2c(i2c).into();
101+
102+
display.init().unwrap();
103+
display.flush().unwrap();
104+
105+
// Update framerate
106+
let fps = 20;
107+
108+
let mut timer = Timer::tim1(dp.TIM1, &clocks, &mut rcc.apb2).start_count_down(fps.hz());
109+
110+
timer.listen(Event::Update);
111+
112+
let bmp = Bmp::from_slice(include_bytes!("dvd.bmp")).unwrap();
113+
114+
// Init the static resources to use them later through RTIC
115+
init::LateResources {
116+
timer,
117+
display,
118+
top_left: Point::new(5, 3),
119+
velocity: Point::new(1, 1),
120+
bmp,
121+
}
122+
}
123+
124+
#[idle()]
125+
fn idle(_: idle::Context) -> ! {
126+
loop {
127+
// Fix default wfi() behaviour breaking debug probe
128+
core::hint::spin_loop();
129+
}
130+
}
131+
132+
#[task(binds = TIM1_UP, resources = [display, top_left, velocity, timer, bmp])]
133+
fn update(cx: update::Context) {
134+
let update::Resources {
135+
display,
136+
top_left,
137+
velocity,
138+
timer,
139+
bmp,
140+
..
141+
} = cx.resources;
142+
143+
let bottom_right = *top_left + bmp.bounding_box().size;
144+
145+
// Erase previous image position with a filled black rectangle
146+
Rectangle::with_corners(*top_left, bottom_right)
147+
.into_styled(PrimitiveStyle::with_fill(BinaryColor::Off))
148+
.draw(display)
149+
.unwrap();
150+
151+
// Check if the image collided with a screen edge
152+
{
153+
if bottom_right.x > display.size().width as i32 || top_left.x < 0 {
154+
velocity.x = -velocity.x;
155+
}
156+
157+
if bottom_right.y > display.size().height as i32 || top_left.y < 0 {
158+
velocity.y = -velocity.y;
159+
}
160+
}
161+
162+
// Move the image
163+
*top_left += *velocity;
164+
165+
// Draw image at new position
166+
Image::new(bmp, *top_left)
167+
.draw(&mut display.color_converted())
168+
.unwrap();
169+
170+
// Write changes to the display
171+
display.flush().unwrap();
172+
173+
// Clears the update flag
174+
timer.clear_update_interrupt_flag();
175+
}
176+
177+
extern "C" {
178+
fn EXTI0();
179+
}
180+
};

src/mode/graphics.rs

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@
4242
//! display.flush().unwrap();
4343
//! ```
4444
45-
use hal::{blocking::delay::DelayMs, digital::v2::OutputPin};
46-
4745
use crate::{
4846
displayrotation::DisplayRotation, interface::DisplayInterface,
4947
mode::displaymode::DisplayModeTrait, properties::DisplayProperties, Error,
5048
};
49+
use embedded_graphics_core::{prelude::Point, primitives::Rectangle};
50+
use hal::{blocking::delay::DelayMs, digital::v2::OutputPin};
5151

5252
const BUFFER_SIZE: usize = 132 * 64 / 8;
5353

@@ -226,6 +226,81 @@ where
226226

227227
Ok(())
228228
}
229+
230+
fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
231+
let Rectangle {
232+
top_left: Point { x, y },
233+
size: Size { width, height },
234+
} = area.intersection(&self.bounding_box());
235+
// swap coordinates if rotated
236+
let (x, y, width, height) = match self.properties.get_rotation() {
237+
DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => (x, y, width, height),
238+
DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => (y, x, height, width),
239+
};
240+
241+
let color = if color.is_on() { 0xff } else { 0x00 };
242+
243+
let display_width = self.properties.get_size().dimensions().0 as u32;
244+
245+
let mut remaining = height;
246+
247+
let mut block = y as u32 / 8;
248+
249+
// Top partial row
250+
if y % 8 > 0 {
251+
// Row (aka bit) in this block
252+
let row = y as u32 % 8;
253+
254+
let mask = (2u8.pow(height) - 1) << row;
255+
256+
let start = (x as u32 + (block * display_width)) as usize;
257+
258+
let slice = &mut self.buffer[start..(start + width as usize)];
259+
260+
// Write fill color without clobbering existing data in the other bits of the block
261+
slice.iter_mut().for_each(|column| {
262+
*column = (*column & !mask) | (color & mask);
263+
});
264+
265+
remaining = remaining.saturating_sub((8 - row).min(height));
266+
block += 1;
267+
}
268+
269+
// Full height rows to fill
270+
while remaining > 8 {
271+
let start = (x as u32 + (block * display_width)) as usize;
272+
273+
let slice = &mut self.buffer[start..(start + width as usize)];
274+
275+
slice.fill(color);
276+
277+
block += 1;
278+
remaining = remaining.saturating_sub(8);
279+
}
280+
281+
// Bottom partial row
282+
if remaining > 0 {
283+
// Row (aka bit) in this block
284+
let row = 8 - (remaining % 8);
285+
286+
let mask = (2u8.pow(height) - 1) >> row;
287+
288+
let start = (x as u32 + (block * display_width)) as usize;
289+
290+
let slice = &mut self.buffer[start..(start + width as usize)];
291+
292+
// Write fill color without clobbering existing data in the other bits of the block
293+
slice.iter_mut().for_each(|column| {
294+
*column = (*column & !mask) | (color & mask);
295+
});
296+
297+
remaining = remaining.saturating_sub(8);
298+
}
299+
300+
debug_assert_eq!(remaining, 0);
301+
302+
Ok(())
303+
}
229304
}
230305

231306
#[cfg(feature = "graphics")]

0 commit comments

Comments
 (0)