aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2024-07-01 23:18:15 +0000
committerGitHub <[email protected]>2024-07-01 23:18:15 +0000
commit00babd2ec4c618f910706e50cb2b0742bb9de6dd (patch)
treedfbe22c0eb8ce042b9ed0d1945b5262ab66abd37
parent92eb6011d60b95a7c249212fef874ff952f98f78 (diff)
parent6edf7b4688361e165c2ea9af03df9725a89a853e (diff)
Merge pull request #3126 from ninjasource/stm32-ltdc
Add support for the stm32 LTDC display peripheral
-rwxr-xr-xci.sh1
-rw-r--r--embassy-stm32/Cargo.toml4
-rw-r--r--embassy-stm32/src/ltdc.rs510
-rw-r--r--examples/stm32h735/.cargo/config.toml8
-rw-r--r--examples/stm32h735/Cargo.toml61
-rw-r--r--examples/stm32h735/build.rs35
-rw-r--r--examples/stm32h735/memory.x5
-rw-r--r--examples/stm32h735/src/bin/ferris.bmpbin0 -> 6794 bytes
-rw-r--r--examples/stm32h735/src/bin/ltdc.rs467
9 files changed, 1051 insertions, 40 deletions
diff --git a/ci.sh b/ci.sh
index 04877dcd3..8ac9e1ccd 100755
--- a/ci.sh
+++ b/ci.sh
@@ -201,6 +201,7 @@ cargo batch \
201 --- build --release --manifest-path examples/stm32g4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32g4 \ 201 --- build --release --manifest-path examples/stm32g4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32g4 \
202 --- build --release --manifest-path examples/stm32h5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32h5 \ 202 --- build --release --manifest-path examples/stm32h5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32h5 \
203 --- build --release --manifest-path examples/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7 \ 203 --- build --release --manifest-path examples/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7 \
204 --- build --release --manifest-path examples/stm32h735/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h735 \
204 --- build --release --manifest-path examples/stm32h7rs/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7rs \ 205 --- build --release --manifest-path examples/stm32h7rs/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7rs \
205 --- build --release --manifest-path examples/stm32l0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32l0 \ 206 --- build --release --manifest-path examples/stm32l0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32l0 \
206 --- build --release --manifest-path examples/stm32l1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32l1 \ 207 --- build --release --manifest-path examples/stm32l1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32l1 \
diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml
index 77b517dba..d0e7ffd6d 100644
--- a/embassy-stm32/Cargo.toml
+++ b/embassy-stm32/Cargo.toml
@@ -72,7 +72,7 @@ rand_core = "0.6.3"
72sdio-host = "0.5.0" 72sdio-host = "0.5.0"
73critical-section = "1.1" 73critical-section = "1.1"
74#stm32-metapac = { version = "15" } 74#stm32-metapac = { version = "15" }
75stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-a8ab0a3421ed0ca4b282f54028a0a2decacd8631" } 75stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-cb8af7b3c2cfb88283e0ce979a318657853434c0" }
76 76
77vcell = "0.1.3" 77vcell = "0.1.3"
78nb = "1.0.0" 78nb = "1.0.0"
@@ -97,7 +97,7 @@ proc-macro2 = "1.0.36"
97quote = "1.0.15" 97quote = "1.0.15"
98 98
99#stm32-metapac = { version = "15", default-features = false, features = ["metadata"]} 99#stm32-metapac = { version = "15", default-features = false, features = ["metadata"]}
100stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-a8ab0a3421ed0ca4b282f54028a0a2decacd8631", default-features = false, features = ["metadata"] } 100stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-cb8af7b3c2cfb88283e0ce979a318657853434c0", default-features = false, features = ["metadata"] }
101 101
102[features] 102[features]
103default = ["rt"] 103default = ["rt"]
diff --git a/embassy-stm32/src/ltdc.rs b/embassy-stm32/src/ltdc.rs
index 481d77843..4c5239971 100644
--- a/embassy-stm32/src/ltdc.rs
+++ b/embassy-stm32/src/ltdc.rs
@@ -1,19 +1,194 @@
1//! LTDC 1//! LTDC - LCD-TFT Display Controller
2//! See ST application note AN4861: Introduction to LCD-TFT display controller (LTDC) on STM32 MCUs for high level details
3//! This module was tested against the stm32h735g-dk using the RM0468 ST reference manual for detailed register information
4
5use core::future::poll_fn;
2use core::marker::PhantomData; 6use core::marker::PhantomData;
7use core::task::Poll;
8
9use embassy_hal_internal::{into_ref, PeripheralRef};
10use embassy_sync::waitqueue::AtomicWaker;
11use stm32_metapac::ltdc::regs::Dccr;
12use stm32_metapac::ltdc::vals::{Bf1, Bf2, Cfuif, Clif, Crrif, Cterrif, Pf, Vbr};
13
14use crate::gpio::{AfType, OutputType, Speed};
15use crate::interrupt::typelevel::Interrupt;
16use crate::interrupt::{self};
17use crate::{peripherals, rcc, Peripheral};
18
19static LTDC_WAKER: AtomicWaker = AtomicWaker::new();
20
21/// LTDC error
22#[derive(Debug, PartialEq, Eq, Clone, Copy)]
23#[cfg_attr(feature = "defmt", derive(defmt::Format))]
24pub enum Error {
25 /// FIFO underrun. Generated when a pixel is requested while the FIFO is empty
26 FifoUnderrun,
27 /// Transfer error. Generated when a bus error occurs
28 TransferError,
29}
30
31/// Display configuration parameters
32#[derive(Clone, Copy, Debug, PartialEq)]
33#[cfg_attr(feature = "defmt", derive(defmt::Format))]
34pub struct LtdcConfiguration {
35 /// Active width in pixels
36 pub active_width: u16,
37 /// Active height in pixels
38 pub active_height: u16,
39
40 /// Horizontal back porch (in units of pixel clock period)
41 pub h_back_porch: u16,
42 /// Horizontal front porch (in units of pixel clock period)
43 pub h_front_porch: u16,
44 /// Vertical back porch (in units of horizontal scan line)
45 pub v_back_porch: u16,
46 /// Vertical front porch (in units of horizontal scan line)
47 pub v_front_porch: u16,
3 48
4use crate::rcc::{self, RccPeripheral}; 49 /// Horizontal synchronization width (in units of pixel clock period)
5use crate::{peripherals, Peripheral}; 50 pub h_sync: u16,
51 /// Vertical synchronization height (in units of horizontal scan line)
52 pub v_sync: u16,
53
54 /// Horizontal synchronization polarity: `false`: active low, `true`: active high
55 pub h_sync_polarity: PolarityActive,
56 /// Vertical synchronization polarity: `false`: active low, `true`: active high
57 pub v_sync_polarity: PolarityActive,
58 /// Data enable polarity: `false`: active low, `true`: active high
59 pub data_enable_polarity: PolarityActive,
60 /// Pixel clock polarity: `false`: falling edge, `true`: rising edge
61 pub pixel_clock_polarity: PolarityEdge,
62}
63
64/// Edge polarity
65#[derive(Clone, Copy, Debug, PartialEq)]
66#[cfg_attr(feature = "defmt", derive(defmt::Format))]
67pub enum PolarityEdge {
68 /// Falling edge
69 FallingEdge,
70 /// Rising edge
71 RisingEdge,
72}
73
74/// Active polarity
75#[derive(Clone, Copy, Debug, PartialEq)]
76#[cfg_attr(feature = "defmt", derive(defmt::Format))]
77pub enum PolarityActive {
78 /// Active low
79 ActiveLow,
80 /// Active high
81 ActiveHigh,
82}
6 83
7/// LTDC driver. 84/// LTDC driver.
8pub struct Ltdc<'d, T: Instance> { 85pub struct Ltdc<'d, T: Instance> {
9 _peri: PhantomData<&'d mut T>, 86 _peri: PeripheralRef<'d, T>,
87}
88
89/// LTDC interrupt handler.
90pub struct InterruptHandler<T: Instance> {
91 _phantom: PhantomData<T>,
92}
93
94/// 24 bit color
95#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
96#[cfg_attr(feature = "defmt", derive(defmt::Format))]
97pub struct RgbColor {
98 /// Red
99 pub red: u8,
100 /// Green
101 pub green: u8,
102 /// Blue
103 pub blue: u8,
104}
105
106/// Layer
107#[derive(Debug, PartialEq, Eq, Clone, Copy)]
108#[cfg_attr(feature = "defmt", derive(defmt::Format))]
109pub struct LtdcLayerConfig {
110 /// Layer number
111 pub layer: LtdcLayer,
112 /// Pixel format
113 pub pixel_format: PixelFormat,
114 /// Window left x in pixels
115 pub window_x0: u16,
116 /// Window right x in pixels
117 pub window_x1: u16,
118 /// Window top y in pixels
119 pub window_y0: u16,
120 /// Window bottom y in pixels
121 pub window_y1: u16,
122}
123
124/// Pixel format
125#[repr(u8)]
126#[derive(Debug, PartialEq, Eq, Clone, Copy)]
127#[cfg_attr(feature = "defmt", derive(defmt::Format))]
128pub enum PixelFormat {
129 /// ARGB8888
130 ARGB8888 = Pf::ARGB8888 as u8,
131 /// RGB888
132 RGB888 = Pf::RGB888 as u8,
133 /// RGB565
134 RGB565 = Pf::RGB565 as u8,
135 /// ARGB1555
136 ARGB1555 = Pf::ARGB1555 as u8,
137 /// ARGB4444
138 ARGB4444 = Pf::ARGB4444 as u8,
139 /// L8 (8-bit luminance)
140 L8 = Pf::L8 as u8,
141 /// AL44 (4-bit alpha, 4-bit luminance
142 AL44 = Pf::AL44 as u8,
143 /// AL88 (8-bit alpha, 8-bit luminance)
144 AL88 = Pf::AL88 as u8,
145}
146
147impl PixelFormat {
148 /// Number of bytes per pixel
149 pub fn bytes_per_pixel(&self) -> usize {
150 match self {
151 PixelFormat::ARGB8888 => 4,
152 PixelFormat::RGB888 => 3,
153 PixelFormat::RGB565 | PixelFormat::ARGB4444 | PixelFormat::ARGB1555 | PixelFormat::AL88 => 2,
154 PixelFormat::AL44 | PixelFormat::L8 => 1,
155 }
156 }
157}
158
159/// Ltdc Blending Layer
160#[repr(usize)]
161#[derive(Debug, PartialEq, Eq, Clone, Copy)]
162#[cfg_attr(feature = "defmt", derive(defmt::Format))]
163pub enum LtdcLayer {
164 /// Bottom layer
165 Layer1 = 0,
166 /// Top layer
167 Layer2 = 1,
168}
169
170impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
171 unsafe fn on_interrupt() {
172 cortex_m::asm::dsb();
173 Ltdc::<T>::enable_interrupts(false);
174 LTDC_WAKER.wake();
175 }
10} 176}
11 177
12impl<'d, T: Instance> Ltdc<'d, T> { 178impl<'d, T: Instance> Ltdc<'d, T> {
179 // Create a new LTDC driver without specifying color and control pins. This is typically used if you want to drive a display though a DsiHost
13 /// Note: Full-Duplex modes are not supported at this time 180 /// Note: Full-Duplex modes are not supported at this time
14 pub fn new( 181 pub fn new(peri: impl Peripheral<P = T> + 'd) -> Self {
15 _peri: impl Peripheral<P = T> + 'd, 182 Self::setup_clocks();
16 /* 183 into_ref!(peri);
184 Self { _peri: peri }
185 }
186
187 /// Create a new LTDC driver. 8 pins per color channel for blue, green and red
188 #[allow(clippy::too_many_arguments)]
189 pub fn new_with_pins(
190 peri: impl Peripheral<P = T> + 'd,
191 _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
17 clk: impl Peripheral<P = impl ClkPin<T>> + 'd, 192 clk: impl Peripheral<P = impl ClkPin<T>> + 'd,
18 hsync: impl Peripheral<P = impl HsyncPin<T>> + 'd, 193 hsync: impl Peripheral<P = impl HsyncPin<T>> + 'd,
19 vsync: impl Peripheral<P = impl VsyncPin<T>> + 'd, 194 vsync: impl Peripheral<P = impl VsyncPin<T>> + 'd,
@@ -41,40 +216,112 @@ impl<'d, T: Instance> Ltdc<'d, T> {
41 r5: impl Peripheral<P = impl R5Pin<T>> + 'd, 216 r5: impl Peripheral<P = impl R5Pin<T>> + 'd,
42 r6: impl Peripheral<P = impl R6Pin<T>> + 'd, 217 r6: impl Peripheral<P = impl R6Pin<T>> + 'd,
43 r7: impl Peripheral<P = impl R7Pin<T>> + 'd, 218 r7: impl Peripheral<P = impl R7Pin<T>> + 'd,
44 */
45 ) -> Self { 219 ) -> Self {
46 //into_ref!(clk); 220 Self::setup_clocks();
221 into_ref!(peri);
222 new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh));
223 new_pin!(hsync, AfType::output(OutputType::PushPull, Speed::VeryHigh));
224 new_pin!(vsync, AfType::output(OutputType::PushPull, Speed::VeryHigh));
225 new_pin!(b0, AfType::output(OutputType::PushPull, Speed::VeryHigh));
226 new_pin!(b1, AfType::output(OutputType::PushPull, Speed::VeryHigh));
227 new_pin!(b2, AfType::output(OutputType::PushPull, Speed::VeryHigh));
228 new_pin!(b3, AfType::output(OutputType::PushPull, Speed::VeryHigh));
229 new_pin!(b4, AfType::output(OutputType::PushPull, Speed::VeryHigh));
230 new_pin!(b5, AfType::output(OutputType::PushPull, Speed::VeryHigh));
231 new_pin!(b6, AfType::output(OutputType::PushPull, Speed::VeryHigh));
232 new_pin!(b7, AfType::output(OutputType::PushPull, Speed::VeryHigh));
233 new_pin!(g0, AfType::output(OutputType::PushPull, Speed::VeryHigh));
234 new_pin!(g1, AfType::output(OutputType::PushPull, Speed::VeryHigh));
235 new_pin!(g2, AfType::output(OutputType::PushPull, Speed::VeryHigh));
236 new_pin!(g3, AfType::output(OutputType::PushPull, Speed::VeryHigh));
237 new_pin!(g4, AfType::output(OutputType::PushPull, Speed::VeryHigh));
238 new_pin!(g5, AfType::output(OutputType::PushPull, Speed::VeryHigh));
239 new_pin!(g6, AfType::output(OutputType::PushPull, Speed::VeryHigh));
240 new_pin!(g7, AfType::output(OutputType::PushPull, Speed::VeryHigh));
241 new_pin!(r0, AfType::output(OutputType::PushPull, Speed::VeryHigh));
242 new_pin!(r1, AfType::output(OutputType::PushPull, Speed::VeryHigh));
243 new_pin!(r2, AfType::output(OutputType::PushPull, Speed::VeryHigh));
244 new_pin!(r3, AfType::output(OutputType::PushPull, Speed::VeryHigh));
245 new_pin!(r4, AfType::output(OutputType::PushPull, Speed::VeryHigh));
246 new_pin!(r5, AfType::output(OutputType::PushPull, Speed::VeryHigh));
247 new_pin!(r6, AfType::output(OutputType::PushPull, Speed::VeryHigh));
248 new_pin!(r7, AfType::output(OutputType::PushPull, Speed::VeryHigh));
47 249
48 critical_section::with(|_cs| { 250 Self { _peri: peri }
49 // RM says the pllsaidivr should only be changed when pllsai is off. But this could have other unintended side effects. So let's just give it a try like this. 251 }
50 // According to the debugger, this bit gets set, anyway.
51 #[cfg(stm32f7)]
52 stm32_metapac::RCC
53 .dckcfgr1()
54 .modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2));
55 252
56 // It is set to RCC_PLLSAIDIVR_2 in ST's BSP example for the STM32469I-DISCO. 253 /// Initialise and enable the display
57 #[cfg(not(any(stm32f7, stm32u5)))] 254 pub fn init(&mut self, config: &LtdcConfiguration) {
58 stm32_metapac::RCC 255 use stm32_metapac::ltdc::vals::{Depol, Hspol, Pcpol, Vspol};
59 .dckcfgr() 256 let ltdc = T::regs();
60 .modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2)); 257
258 // check bus access
259 assert!(ltdc.gcr().read().0 == 0x2220); // reset value
260
261 // configure the HS, VS, DE and PC polarity
262 ltdc.gcr().modify(|w| {
263 w.set_hspol(match config.h_sync_polarity {
264 PolarityActive::ActiveHigh => Hspol::ACTIVEHIGH,
265 PolarityActive::ActiveLow => Hspol::ACTIVELOW,
266 });
267
268 w.set_vspol(match config.v_sync_polarity {
269 PolarityActive::ActiveHigh => Vspol::ACTIVEHIGH,
270 PolarityActive::ActiveLow => Vspol::ACTIVELOW,
271 });
272
273 w.set_depol(match config.data_enable_polarity {
274 PolarityActive::ActiveHigh => Depol::ACTIVEHIGH,
275 PolarityActive::ActiveLow => Depol::ACTIVELOW,
276 });
277
278 w.set_pcpol(match config.pixel_clock_polarity {
279 PolarityEdge::RisingEdge => Pcpol::RISINGEDGE,
280 PolarityEdge::FallingEdge => Pcpol::FALLINGEDGE,
281 });
61 }); 282 });
62 283
63 rcc::enable_and_reset::<T>(); 284 // set synchronization pulse width
285 ltdc.sscr().modify(|w| {
286 w.set_vsh(config.v_sync - 1);
287 w.set_hsw(config.h_sync - 1);
288 });
64 289
65 //new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh)); 290 // set accumulated back porch
291 ltdc.bpcr().modify(|w| {
292 w.set_avbp(config.v_sync + config.v_back_porch - 1);
293 w.set_ahbp(config.h_sync + config.h_back_porch - 1);
294 });
66 295
67 // Set Tearing Enable pin according to CubeMx example 296 // set accumulated active width
68 //te.set_as_af_pull(te.af_num(), AfType::output(OutputType::PushPull, Speed::Low)); 297 let aa_height = config.v_sync + config.v_back_porch + config.active_height - 1;
69 /* 298 let aa_width = config.h_sync + config.h_back_porch + config.active_width - 1;
70 T::regs().wcr().modify(|w| { 299 ltdc.awcr().modify(|w| {
71 w.set_dsien(true); 300 w.set_aah(aa_height);
72 }); 301 w.set_aaw(aa_width);
73 */ 302 });
74 Self { _peri: PhantomData } 303
304 // set total width and height
305 let total_height: u16 = config.v_sync + config.v_back_porch + config.active_height + config.v_front_porch - 1;
306 let total_width: u16 = config.h_sync + config.h_back_porch + config.active_width + config.h_front_porch - 1;
307 ltdc.twcr().modify(|w| {
308 w.set_totalh(total_height);
309 w.set_totalw(total_width)
310 });
311
312 // set the background color value to black
313 ltdc.bccr().modify(|w| {
314 w.set_bcred(0);
315 w.set_bcgreen(0);
316 w.set_bcblue(0);
317 });
318
319 self.enable();
75 } 320 }
76 321
77 /// Set the enable bit in the control register and assert that it has been enabled 322 /// Set the enable bit in the control register and assert that it has been enabled
323 ///
324 /// This does need to be called if init has already been called
78 pub fn enable(&mut self) { 325 pub fn enable(&mut self) {
79 T::regs().gcr().modify(|w| w.set_ltdcen(true)); 326 T::regs().gcr().modify(|w| w.set_ltdcen(true));
80 assert!(T::regs().gcr().read().ltdcen()) 327 assert!(T::regs().gcr().read().ltdcen())
@@ -85,6 +332,188 @@ impl<'d, T: Instance> Ltdc<'d, T> {
85 T::regs().gcr().modify(|w| w.set_ltdcen(false)); 332 T::regs().gcr().modify(|w| w.set_ltdcen(false));
86 assert!(!T::regs().gcr().read().ltdcen()) 333 assert!(!T::regs().gcr().read().ltdcen())
87 } 334 }
335
336 /// Initialise and enable the layer
337 ///
338 /// clut - a 256 length color look-up table applies to L8, AL44 and AL88 pixel format and will default to greyscale if `None` supplied and these pixel formats are used
339 pub fn init_layer(&mut self, layer_config: &LtdcLayerConfig, clut: Option<&[RgbColor]>) {
340 let ltdc = T::regs();
341 let layer = ltdc.layer(layer_config.layer as usize);
342
343 // 256 color look-up table for L8, AL88 and AL88 pixel formats
344 if let Some(clut) = clut {
345 assert_eq!(clut.len(), 256, "Color lookup table must be exactly 256 in length");
346 for (index, color) in clut.iter().enumerate() {
347 layer.clutwr().write(|w| {
348 w.set_clutadd(index as u8);
349 w.set_red(color.red);
350 w.set_green(color.green);
351 w.set_blue(color.blue);
352 });
353 }
354 }
355
356 // configure the horizontal start and stop position
357 let h_win_start = layer_config.window_x0 + ltdc.bpcr().read().ahbp() + 1;
358 let h_win_stop = layer_config.window_x1 + ltdc.bpcr().read().ahbp();
359 layer.whpcr().write(|w| {
360 w.set_whstpos(h_win_start);
361 w.set_whsppos(h_win_stop);
362 });
363
364 // configure the vertical start and stop position
365 let v_win_start = layer_config.window_y0 + ltdc.bpcr().read().avbp() + 1;
366 let v_win_stop = layer_config.window_y1 + ltdc.bpcr().read().avbp();
367 layer.wvpcr().write(|w| {
368 w.set_wvstpos(v_win_start);
369 w.set_wvsppos(v_win_stop)
370 });
371
372 // set the pixel format
373 layer
374 .pfcr()
375 .write(|w| w.set_pf(Pf::from_bits(layer_config.pixel_format as u8)));
376
377 // set the default color value to transparent black
378 layer.dccr().write_value(Dccr::default());
379
380 // set the global constant alpha value
381 let alpha = 0xFF;
382 layer.cacr().write(|w| w.set_consta(alpha));
383
384 // set the blending factors.
385 layer.bfcr().modify(|w| {
386 w.set_bf1(Bf1::PIXEL);
387 w.set_bf2(Bf2::PIXEL);
388 });
389
390 // calculate framebuffer pixel size in bytes
391 let bytes_per_pixel = layer_config.pixel_format.bytes_per_pixel() as u16;
392 let width = layer_config.window_x1 - layer_config.window_x0;
393 let height = layer_config.window_y1 - layer_config.window_y0;
394
395 // framebuffer pitch and line length
396 layer.cfblr().modify(|w| {
397 w.set_cfbp(width * bytes_per_pixel);
398 w.set_cfbll(width * bytes_per_pixel + 7);
399 });
400
401 // framebuffer line number
402 layer.cfblnr().modify(|w| w.set_cfblnbr(height));
403
404 // enable LTDC_Layer by setting LEN bit
405 layer.cr().modify(|w| {
406 if clut.is_some() {
407 w.set_cluten(true);
408 }
409 w.set_len(true);
410 });
411 }
412
413 /// Set the current buffer. The async function will return when buffer has been completely copied to the LCD screen
414 /// frame_buffer_addr is a pointer to memory that should not move (best to make it static)
415 pub async fn set_buffer(&mut self, layer: LtdcLayer, frame_buffer_addr: *const ()) -> Result<(), Error> {
416 let mut bits = T::regs().isr().read();
417
418 // if all clear
419 if !bits.fuif() && !bits.lif() && !bits.rrif() && !bits.terrif() {
420 // wait for interrupt
421 poll_fn(|cx| {
422 // quick check to avoid registration if already done.
423 let bits = T::regs().isr().read();
424 if bits.fuif() || bits.lif() || bits.rrif() || bits.terrif() {
425 return Poll::Ready(());
426 }
427
428 LTDC_WAKER.register(cx.waker());
429 Self::clear_interrupt_flags(); // don't poison the request with old flags
430 Self::enable_interrupts(true);
431
432 // set the new frame buffer address
433 let layer = T::regs().layer(layer as usize);
434 layer.cfbar().modify(|w| w.set_cfbadd(frame_buffer_addr as u32));
435
436 // configure a shadow reload for the next blanking period
437 T::regs().srcr().write(|w| {
438 w.set_vbr(Vbr::RELOAD);
439 });
440
441 // need to check condition after register to avoid a race
442 // condition that would result in lost notifications.
443 let bits = T::regs().isr().read();
444 if bits.fuif() || bits.lif() || bits.rrif() || bits.terrif() {
445 Poll::Ready(())
446 } else {
447 Poll::Pending
448 }
449 })
450 .await;
451
452 // re-read the status register after wait.
453 bits = T::regs().isr().read();
454 }
455
456 let result = if bits.fuif() {
457 Err(Error::FifoUnderrun)
458 } else if bits.terrif() {
459 Err(Error::TransferError)
460 } else if bits.lif() {
461 panic!("line interrupt event is disabled")
462 } else if bits.rrif() {
463 // register reload flag is expected
464 Ok(())
465 } else {
466 unreachable!("all interrupt status values checked")
467 };
468
469 Self::clear_interrupt_flags();
470 result
471 }
472
473 fn setup_clocks() {
474 critical_section::with(|_cs| {
475 // RM says the pllsaidivr should only be changed when pllsai is off. But this could have other unintended side effects. So let's just give it a try like this.
476 // According to the debugger, this bit gets set, anyway.
477 #[cfg(stm32f7)]
478 crate::pac::RCC
479 .dckcfgr1()
480 .modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2));
481
482 // It is set to RCC_PLLSAIDIVR_2 in ST's BSP example for the STM32469I-DISCO.
483 #[cfg(stm32f4)]
484 crate::pac::RCC
485 .dckcfgr()
486 .modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2));
487 });
488
489 rcc::enable_and_reset::<T>();
490 }
491
492 fn clear_interrupt_flags() {
493 T::regs().icr().write(|w| {
494 w.set_cfuif(Cfuif::CLEAR);
495 w.set_clif(Clif::CLEAR);
496 w.set_crrif(Crrif::CLEAR);
497 w.set_cterrif(Cterrif::CLEAR);
498 });
499 }
500
501 fn enable_interrupts(enable: bool) {
502 T::regs().ier().write(|w| {
503 w.set_fuie(enable);
504 w.set_lie(false); // we are not interested in the line interrupt enable event
505 w.set_rrie(enable);
506 w.set_terrie(enable)
507 });
508
509 // enable interrupts for LTDC peripheral
510 T::Interrupt::unpend();
511 if enable {
512 unsafe { T::Interrupt::enable() };
513 } else {
514 T::Interrupt::disable()
515 }
516 }
88} 517}
89 518
90impl<'d, T: Instance> Drop for Ltdc<'d, T> { 519impl<'d, T: Instance> Drop for Ltdc<'d, T> {
@@ -95,9 +524,12 @@ trait SealedInstance: crate::rcc::SealedRccPeripheral {
95 fn regs() -> crate::pac::ltdc::Ltdc; 524 fn regs() -> crate::pac::ltdc::Ltdc;
96} 525}
97 526
98/// DSI instance trait. 527/// LTDC instance trait.
99#[allow(private_bounds)] 528#[allow(private_bounds)]
100pub trait Instance: SealedInstance + RccPeripheral + 'static {} 529pub trait Instance: SealedInstance + Peripheral<P = Self> + crate::rcc::RccPeripheral + 'static + Send {
530 /// Interrupt for this LTDC instance.
531 type Interrupt: interrupt::typelevel::Interrupt;
532}
101 533
102pin_trait!(ClkPin, Instance); 534pin_trait!(ClkPin, Instance);
103pin_trait!(HsyncPin, Instance); 535pin_trait!(HsyncPin, Instance);
@@ -128,14 +560,16 @@ pin_trait!(B5Pin, Instance);
128pin_trait!(B6Pin, Instance); 560pin_trait!(B6Pin, Instance);
129pin_trait!(B7Pin, Instance); 561pin_trait!(B7Pin, Instance);
130 562
131foreach_peripheral!( 563foreach_interrupt!(
132 (ltdc, $inst:ident) => { 564 ($inst:ident, ltdc, LTDC, GLOBAL, $irq:ident) => {
133 impl crate::ltdc::SealedInstance for peripherals::$inst { 565 impl Instance for peripherals::$inst {
566 type Interrupt = crate::interrupt::typelevel::$irq;
567 }
568
569 impl SealedInstance for peripherals::$inst {
134 fn regs() -> crate::pac::ltdc::Ltdc { 570 fn regs() -> crate::pac::ltdc::Ltdc {
135 crate::pac::$inst 571 crate::pac::$inst
136 } 572 }
137 } 573 }
138
139 impl crate::ltdc::Instance for peripherals::$inst {}
140 }; 574 };
141); 575);
diff --git a/examples/stm32h735/.cargo/config.toml b/examples/stm32h735/.cargo/config.toml
new file mode 100644
index 000000000..95536c6a8
--- /dev/null
+++ b/examples/stm32h735/.cargo/config.toml
@@ -0,0 +1,8 @@
1[target.thumbv7em-none-eabihf]
2runner = 'probe-rs run --chip STM32H735IGKx'
3
4[build]
5target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
6
7[env]
8DEFMT_LOG = "trace"
diff --git a/examples/stm32h735/Cargo.toml b/examples/stm32h735/Cargo.toml
new file mode 100644
index 000000000..fc21cc894
--- /dev/null
+++ b/examples/stm32h735/Cargo.toml
@@ -0,0 +1,61 @@
1[package]
2edition = "2021"
3name = "embassy-stm32h735-examples"
4version = "0.1.0"
5license = "MIT OR Apache-2.0"
6
7[dependencies]
8embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h735ig", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] }
9embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] }
10embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" }
11embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] }
12embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
13embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
14
15defmt = "0.3"
16defmt-rtt = "0.4"
17
18cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
19cortex-m-rt = "0.7.0"
20panic-probe = { version = "0.3", features = ["print-defmt"] }
21heapless = { version = "0.8", default-features = false }
22embedded-graphics = { version = "0.8.1" }
23tinybmp = { version = "0.5" }
24
25# cargo build/run
26[profile.dev]
27codegen-units = 1
28debug = 2
29debug-assertions = true # <-
30incremental = false
31opt-level = 3 # <-
32overflow-checks = true # <-
33
34# cargo test
35[profile.test]
36codegen-units = 1
37debug = 2
38debug-assertions = true # <-
39incremental = false
40opt-level = 3 # <-
41overflow-checks = true # <-
42
43# cargo build/run --release
44[profile.release]
45codegen-units = 1
46debug = 2
47debug-assertions = false # <-
48incremental = false
49lto = 'fat'
50opt-level = 3 # <-
51overflow-checks = false # <-
52
53# cargo test --release
54[profile.bench]
55codegen-units = 1
56debug = 2
57debug-assertions = false # <-
58incremental = false
59lto = 'fat'
60opt-level = 3 # <-
61overflow-checks = false # <-
diff --git a/examples/stm32h735/build.rs b/examples/stm32h735/build.rs
new file mode 100644
index 000000000..30691aa97
--- /dev/null
+++ b/examples/stm32h735/build.rs
@@ -0,0 +1,35 @@
1//! This build script copies the `memory.x` file from the crate root into
2//! a directory where the linker can always find it at build time.
3//! For many projects this is optional, as the linker always searches the
4//! project root directory -- wherever `Cargo.toml` is. However, if you
5//! are using a workspace or have a more complicated build setup, this
6//! build script becomes required. Additionally, by requesting that
7//! Cargo re-run the build script whenever `memory.x` is changed,
8//! updating `memory.x` ensures a rebuild of the application with the
9//! new memory settings.
10
11use std::env;
12use std::fs::File;
13use std::io::Write;
14use std::path::PathBuf;
15
16fn main() {
17 // Put `memory.x` in our output directory and ensure it's
18 // on the linker search path.
19 let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
20 File::create(out.join("memory.x"))
21 .unwrap()
22 .write_all(include_bytes!("memory.x"))
23 .unwrap();
24 println!("cargo:rustc-link-search={}", out.display());
25
26 // By default, Cargo will re-run a build script whenever
27 // any file in the project changes. By specifying `memory.x`
28 // here, we ensure the build script is only re-run when
29 // `memory.x` is changed.
30 println!("cargo:rerun-if-changed=memory.x");
31
32 println!("cargo:rustc-link-arg-bins=--nmagic");
33 println!("cargo:rustc-link-arg-bins=-Tlink.x");
34 println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
35}
diff --git a/examples/stm32h735/memory.x b/examples/stm32h735/memory.x
new file mode 100644
index 000000000..3a70d24d2
--- /dev/null
+++ b/examples/stm32h735/memory.x
@@ -0,0 +1,5 @@
1MEMORY
2{
3 FLASH : ORIGIN = 0x08000000, LENGTH = 1024K
4 RAM : ORIGIN = 0x24000000, LENGTH = 320K
5} \ No newline at end of file
diff --git a/examples/stm32h735/src/bin/ferris.bmp b/examples/stm32h735/src/bin/ferris.bmp
new file mode 100644
index 000000000..7a222ab84
--- /dev/null
+++ b/examples/stm32h735/src/bin/ferris.bmp
Binary files differ
diff --git a/examples/stm32h735/src/bin/ltdc.rs b/examples/stm32h735/src/bin/ltdc.rs
new file mode 100644
index 000000000..a36fdef2c
--- /dev/null
+++ b/examples/stm32h735/src/bin/ltdc.rs
@@ -0,0 +1,467 @@
1#![no_std]
2#![no_main]
3#![macro_use]
4#![allow(static_mut_refs)]
5
6/// This example demonstrates the LTDC lcd display peripheral and was tested to run on an stm32h735g-dk (embassy-stm32 feature "stm32h735ig" and probe-rs chip "STM32H735IGKx")
7/// Even though the dev kit has 16MB of attached PSRAM this example uses the 320KB of internal AXIS RAM found on the mcu itself to make the example more standalone and portable.
8/// For this reason a 256 color lookup table had to be used to keep the memory requirement down to an acceptable level.
9/// The example bounces a ferris crab bitmap around the screen while blinking an led on another task
10///
11use bouncy_box::BouncyBox;
12use defmt::{info, unwrap};
13use embassy_executor::Spawner;
14use embassy_stm32::gpio::{Level, Output, Speed};
15use embassy_stm32::ltdc::{self, Ltdc, LtdcConfiguration, LtdcLayer, LtdcLayerConfig, PolarityActive, PolarityEdge};
16use embassy_stm32::{bind_interrupts, peripherals};
17use embassy_time::{Duration, Timer};
18use embedded_graphics::draw_target::DrawTarget;
19use embedded_graphics::geometry::{OriginDimensions, Point, Size};
20use embedded_graphics::image::Image;
21use embedded_graphics::pixelcolor::raw::RawU24;
22use embedded_graphics::pixelcolor::Rgb888;
23use embedded_graphics::prelude::*;
24use embedded_graphics::primitives::Rectangle;
25use embedded_graphics::Pixel;
26use heapless::{Entry, FnvIndexMap};
27use tinybmp::Bmp;
28use {defmt_rtt as _, panic_probe as _};
29
30const DISPLAY_WIDTH: usize = 480;
31const DISPLAY_HEIGHT: usize = 272;
32const MY_TASK_POOL_SIZE: usize = 2;
33
34// the following two display buffers consume 261120 bytes that just about fits into axis ram found on the mcu
35pub static mut FB1: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT];
36pub static mut FB2: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT];
37
38bind_interrupts!(struct Irqs {
39 LTDC => ltdc::InterruptHandler<peripherals::LTDC>;
40});
41
42const NUM_COLORS: usize = 256;
43
44#[embassy_executor::main]
45async fn main(spawner: Spawner) {
46 let p = rcc_setup::stm32h735g_init();
47
48 // blink the led on another task
49 let led = Output::new(p.PC3, Level::High, Speed::Low);
50 unwrap!(spawner.spawn(led_task(led)));
51
52 // numbers from STMicroelectronics/STM32CubeH7 STM32H735G-DK C-based example
53 const RK043FN48H_HSYNC: u16 = 41; // Horizontal synchronization
54 const RK043FN48H_HBP: u16 = 13; // Horizontal back porch
55 const RK043FN48H_HFP: u16 = 32; // Horizontal front porch
56 const RK043FN48H_VSYNC: u16 = 10; // Vertical synchronization
57 const RK043FN48H_VBP: u16 = 2; // Vertical back porch
58 const RK043FN48H_VFP: u16 = 2; // Vertical front porch
59
60 let ltdc_config = LtdcConfiguration {
61 active_width: DISPLAY_WIDTH as _,
62 active_height: DISPLAY_HEIGHT as _,
63 h_back_porch: RK043FN48H_HBP - 11, // -11 from MX_LTDC_Init
64 h_front_porch: RK043FN48H_HFP,
65 v_back_porch: RK043FN48H_VBP,
66 v_front_porch: RK043FN48H_VFP,
67 h_sync: RK043FN48H_HSYNC,
68 v_sync: RK043FN48H_VSYNC,
69 h_sync_polarity: PolarityActive::ActiveLow,
70 v_sync_polarity: PolarityActive::ActiveLow,
71 data_enable_polarity: PolarityActive::ActiveHigh,
72 pixel_clock_polarity: PolarityEdge::FallingEdge,
73 };
74
75 info!("init ltdc");
76 let mut ltdc = Ltdc::new_with_pins(
77 p.LTDC, Irqs, p.PG7, p.PC6, p.PA4, p.PG14, p.PD0, p.PD6, p.PA8, p.PE12, p.PA3, p.PB8, p.PB9, p.PB1, p.PB0,
78 p.PA6, p.PE11, p.PH15, p.PH4, p.PC7, p.PD3, p.PE0, p.PH3, p.PH8, p.PH9, p.PH10, p.PH11, p.PE1, p.PE15,
79 );
80 ltdc.init(&ltdc_config);
81
82 // we only need to draw on one layer for this example (not to be confused with the double buffer)
83 info!("enable bottom layer");
84 let layer_config = LtdcLayerConfig {
85 pixel_format: ltdc::PixelFormat::L8, // 1 byte per pixel
86 layer: LtdcLayer::Layer1,
87 window_x0: 0,
88 window_x1: DISPLAY_WIDTH as _,
89 window_y0: 0,
90 window_y1: DISPLAY_HEIGHT as _,
91 };
92
93 let ferris_bmp: Bmp<Rgb888> = Bmp::from_slice(include_bytes!("./ferris.bmp")).unwrap();
94 let color_map = build_color_lookup_map(&ferris_bmp);
95 let clut = build_clut(&color_map);
96
97 // enable the bottom layer with a 256 color lookup table
98 ltdc.init_layer(&layer_config, Some(&clut));
99
100 // Safety: the DoubleBuffer controls access to the statically allocated frame buffers
101 // and it is the only thing that mutates their content
102 let mut double_buffer = DoubleBuffer::new(
103 unsafe { FB1.as_mut() },
104 unsafe { FB2.as_mut() },
105 layer_config,
106 color_map,
107 );
108
109 // this allows us to perform some simple animation for every frame
110 let mut bouncy_box = BouncyBox::new(
111 ferris_bmp.bounding_box(),
112 Rectangle::new(Point::zero(), Size::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32)),
113 2,
114 );
115
116 loop {
117 // cpu intensive drawing to the buffer that is NOT currently being copied to the LCD screen
118 double_buffer.clear();
119 let position = bouncy_box.next_point();
120 let ferris = Image::new(&ferris_bmp, position);
121 unwrap!(ferris.draw(&mut double_buffer));
122
123 // perform async dma data transfer to the lcd screen
124 unwrap!(double_buffer.swap(&mut ltdc).await);
125 }
126}
127
128/// builds the color look-up table from all unique colors found in the bitmap. This should be a 256 color indexed bitmap to work.
129fn build_color_lookup_map(bmp: &Bmp<Rgb888>) -> FnvIndexMap<u32, u8, NUM_COLORS> {
130 let mut color_map: FnvIndexMap<u32, u8, NUM_COLORS> = heapless::FnvIndexMap::new();
131 let mut counter: u8 = 0;
132
133 // add black to position 0
134 color_map.insert(Rgb888::new(0, 0, 0).into_storage(), counter).unwrap();
135 counter += 1;
136
137 for Pixel(_point, color) in bmp.pixels() {
138 let raw = color.into_storage();
139 if let Entry::Vacant(v) = color_map.entry(raw) {
140 v.insert(counter).expect("more than 256 colors detected");
141 counter += 1;
142 }
143 }
144 color_map
145}
146
147/// builds the color look-up table from the color map provided
148fn build_clut(color_map: &FnvIndexMap<u32, u8, NUM_COLORS>) -> [ltdc::RgbColor; NUM_COLORS] {
149 let mut clut = [ltdc::RgbColor::default(); NUM_COLORS];
150 for (color, index) in color_map.iter() {
151 let color = Rgb888::from(RawU24::new(*color));
152 clut[*index as usize] = ltdc::RgbColor {
153 red: color.r(),
154 green: color.g(),
155 blue: color.b(),
156 };
157 }
158
159 clut
160}
161
162#[embassy_executor::task(pool_size = MY_TASK_POOL_SIZE)]
163async fn led_task(mut led: Output<'static>) {
164 let mut counter = 0;
165 loop {
166 info!("blink: {}", counter);
167 counter += 1;
168
169 // on
170 led.set_low();
171 Timer::after(Duration::from_millis(50)).await;
172
173 // off
174 led.set_high();
175 Timer::after(Duration::from_millis(450)).await;
176 }
177}
178
179pub type TargetPixelType = u8;
180
181// A simple double buffer
182pub struct DoubleBuffer {
183 buf0: &'static mut [TargetPixelType],
184 buf1: &'static mut [TargetPixelType],
185 is_buf0: bool,
186 layer_config: LtdcLayerConfig,
187 color_map: FnvIndexMap<u32, u8, NUM_COLORS>,
188}
189
190impl DoubleBuffer {
191 pub fn new(
192 buf0: &'static mut [TargetPixelType],
193 buf1: &'static mut [TargetPixelType],
194 layer_config: LtdcLayerConfig,
195 color_map: FnvIndexMap<u32, u8, NUM_COLORS>,
196 ) -> Self {
197 Self {
198 buf0,
199 buf1,
200 is_buf0: true,
201 layer_config,
202 color_map,
203 }
204 }
205
206 pub fn current(&mut self) -> (&FnvIndexMap<u32, u8, NUM_COLORS>, &mut [TargetPixelType]) {
207 if self.is_buf0 {
208 (&self.color_map, self.buf0)
209 } else {
210 (&self.color_map, self.buf1)
211 }
212 }
213
214 pub async fn swap<T: ltdc::Instance>(&mut self, ltdc: &mut Ltdc<'_, T>) -> Result<(), ltdc::Error> {
215 let (_, buf) = self.current();
216 let frame_buffer = buf.as_ptr();
217 self.is_buf0 = !self.is_buf0;
218 ltdc.set_buffer(self.layer_config.layer, frame_buffer as *const _).await
219 }
220
221 /// Clears the buffer
222 pub fn clear(&mut self) {
223 let (color_map, buf) = self.current();
224 let black = Rgb888::new(0, 0, 0).into_storage();
225 let color_index = color_map.get(&black).expect("no black found in the color map");
226
227 for a in buf.iter_mut() {
228 *a = *color_index; // solid black
229 }
230 }
231}
232
233// Implement DrawTarget for
234impl DrawTarget for DoubleBuffer {
235 type Color = Rgb888;
236 type Error = ();
237
238 /// Draw a pixel
239 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
240 where
241 I: IntoIterator<Item = Pixel<Self::Color>>,
242 {
243 let size = self.size();
244 let width = size.width as i32;
245 let height = size.height as i32;
246 let (color_map, buf) = self.current();
247
248 for pixel in pixels {
249 let Pixel(point, color) = pixel;
250
251 if point.x >= 0 && point.y >= 0 && point.x < width && point.y < height {
252 let index = point.y * width + point.x;
253 let raw_color = color.into_storage();
254
255 match color_map.get(&raw_color) {
256 Some(x) => {
257 buf[index as usize] = *x;
258 }
259 None => panic!("color not found in color map: {}", raw_color),
260 };
261 } else {
262 // Ignore invalid points
263 }
264 }
265
266 Ok(())
267 }
268}
269
270impl OriginDimensions for DoubleBuffer {
271 /// Return the size of the display
272 fn size(&self) -> Size {
273 Size::new(
274 (self.layer_config.window_x1 - self.layer_config.window_x0) as _,
275 (self.layer_config.window_y1 - self.layer_config.window_y0) as _,
276 )
277 }
278}
279
280mod rcc_setup {
281
282 use embassy_stm32::rcc::{Hse, HseMode, *};
283 use embassy_stm32::time::Hertz;
284 use embassy_stm32::{Config, Peripherals};
285
286 /// Sets up clocks for the stm32h735g mcu
287 /// change this if you plan to use a different microcontroller
288 pub fn stm32h735g_init() -> Peripherals {
289 /*
290 https://github.com/STMicroelectronics/STM32CubeH7/blob/master/Projects/STM32H735G-DK/Examples/GPIO/GPIO_EXTI/Src/main.c
291 @brief System Clock Configuration
292 The system Clock is configured as follow :
293 System Clock source = PLL (HSE)
294 SYSCLK(Hz) = 520000000 (CPU Clock)
295 HCLK(Hz) = 260000000 (AXI and AHBs Clock)
296 AHB Prescaler = 2
297 D1 APB3 Prescaler = 2 (APB3 Clock 130MHz)
298 D2 APB1 Prescaler = 2 (APB1 Clock 130MHz)
299 D2 APB2 Prescaler = 2 (APB2 Clock 130MHz)
300 D3 APB4 Prescaler = 2 (APB4 Clock 130MHz)
301 HSE Frequency(Hz) = 25000000
302 PLL_M = 5
303 PLL_N = 104
304 PLL_P = 1
305 PLL_Q = 4
306 PLL_R = 2
307 VDD(V) = 3.3
308 Flash Latency(WS) = 3
309 */
310
311 // setup power and clocks for an stm32h735g-dk run from an external 25 Mhz external oscillator
312 let mut config = Config::default();
313 config.rcc.hse = Some(Hse {
314 freq: Hertz::mhz(25),
315 mode: HseMode::Oscillator,
316 });
317 config.rcc.hsi = None;
318 config.rcc.csi = false;
319 config.rcc.pll1 = Some(Pll {
320 source: PllSource::HSE,
321 prediv: PllPreDiv::DIV5, // PLL_M
322 mul: PllMul::MUL104, // PLL_N
323 divp: Some(PllDiv::DIV1),
324 divq: Some(PllDiv::DIV4),
325 divr: Some(PllDiv::DIV2),
326 });
327 // numbers adapted from Drivers/BSP/STM32H735G-DK/stm32h735g_discovery_ospi.c
328 // MX_OSPI_ClockConfig
329 config.rcc.pll2 = Some(Pll {
330 source: PllSource::HSE,
331 prediv: PllPreDiv::DIV5, // PLL_M
332 mul: PllMul::MUL80, // PLL_N
333 divp: Some(PllDiv::DIV5),
334 divq: Some(PllDiv::DIV2),
335 divr: Some(PllDiv::DIV2),
336 });
337 // numbers adapted from Drivers/BSP/STM32H735G-DK/stm32h735g_discovery_lcd.c
338 // MX_LTDC_ClockConfig
339 config.rcc.pll3 = Some(Pll {
340 source: PllSource::HSE,
341 prediv: PllPreDiv::DIV5, // PLL_M
342 mul: PllMul::MUL160, // PLL_N
343 divp: Some(PllDiv::DIV2),
344 divq: Some(PllDiv::DIV2),
345 divr: Some(PllDiv::DIV83),
346 });
347 config.rcc.voltage_scale = VoltageScale::Scale0;
348 config.rcc.supply_config = SupplyConfig::DirectSMPS;
349 config.rcc.sys = Sysclk::PLL1_P;
350 config.rcc.ahb_pre = AHBPrescaler::DIV2;
351 config.rcc.apb1_pre = APBPrescaler::DIV2;
352 config.rcc.apb2_pre = APBPrescaler::DIV2;
353 config.rcc.apb3_pre = APBPrescaler::DIV2;
354 config.rcc.apb4_pre = APBPrescaler::DIV2;
355 embassy_stm32::init(config)
356 }
357}
358
359mod bouncy_box {
360 use embedded_graphics::geometry::Point;
361 use embedded_graphics::primitives::Rectangle;
362
363 enum Direction {
364 DownLeft,
365 DownRight,
366 UpLeft,
367 UpRight,
368 }
369
370 pub struct BouncyBox {
371 direction: Direction,
372 child_rect: Rectangle,
373 parent_rect: Rectangle,
374 current_point: Point,
375 move_by: usize,
376 }
377
378 // This calculates the coordinates of a chile rectangle bounced around inside a parent bounded box
379 impl BouncyBox {
380 pub fn new(child_rect: Rectangle, parent_rect: Rectangle, move_by: usize) -> Self {
381 let center_box = parent_rect.center();
382 let center_img = child_rect.center();
383 let current_point = Point::new(center_box.x - center_img.x / 2, center_box.y - center_img.y / 2);
384 Self {
385 direction: Direction::DownRight,
386 child_rect,
387 parent_rect,
388 current_point,
389 move_by,
390 }
391 }
392
393 pub fn next_point(&mut self) -> Point {
394 let direction = &self.direction;
395 let img_height = self.child_rect.size.height as i32;
396 let box_height = self.parent_rect.size.height as i32;
397 let img_width = self.child_rect.size.width as i32;
398 let box_width = self.parent_rect.size.width as i32;
399 let move_by = self.move_by as i32;
400
401 match direction {
402 Direction::DownLeft => {
403 self.current_point.x -= move_by;
404 self.current_point.y += move_by;
405
406 let x_out_of_bounds = self.current_point.x < 0;
407 let y_out_of_bounds = (self.current_point.y + img_height) > box_height;
408
409 if x_out_of_bounds && y_out_of_bounds {
410 self.direction = Direction::UpRight
411 } else if x_out_of_bounds && !y_out_of_bounds {
412 self.direction = Direction::DownRight
413 } else if !x_out_of_bounds && y_out_of_bounds {
414 self.direction = Direction::UpLeft
415 }
416 }
417 Direction::DownRight => {
418 self.current_point.x += move_by;
419 self.current_point.y += move_by;
420
421 let x_out_of_bounds = (self.current_point.x + img_width) > box_width;
422 let y_out_of_bounds = (self.current_point.y + img_height) > box_height;
423
424 if x_out_of_bounds && y_out_of_bounds {
425 self.direction = Direction::UpLeft
426 } else if x_out_of_bounds && !y_out_of_bounds {
427 self.direction = Direction::DownLeft
428 } else if !x_out_of_bounds && y_out_of_bounds {
429 self.direction = Direction::UpRight
430 }
431 }
432 Direction::UpLeft => {
433 self.current_point.x -= move_by;
434 self.current_point.y -= move_by;
435
436 let x_out_of_bounds = self.current_point.x < 0;
437 let y_out_of_bounds = self.current_point.y < 0;
438
439 if x_out_of_bounds && y_out_of_bounds {
440 self.direction = Direction::DownRight
441 } else if x_out_of_bounds && !y_out_of_bounds {
442 self.direction = Direction::UpRight
443 } else if !x_out_of_bounds && y_out_of_bounds {
444 self.direction = Direction::DownLeft
445 }
446 }
447 Direction::UpRight => {
448 self.current_point.x += move_by;
449 self.current_point.y -= move_by;
450
451 let x_out_of_bounds = (self.current_point.x + img_width) > box_width;
452 let y_out_of_bounds = self.current_point.y < 0;
453
454 if x_out_of_bounds && y_out_of_bounds {
455 self.direction = Direction::DownLeft
456 } else if x_out_of_bounds && !y_out_of_bounds {
457 self.direction = Direction::UpLeft
458 } else if !x_out_of_bounds && y_out_of_bounds {
459 self.direction = Direction::DownRight
460 }
461 }
462 }
463
464 self.current_point
465 }
466 }
467}