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