aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--embassy-stm32/src/ltdc.rs224
-rw-r--r--examples/stm32h7/src/bin/ltdc.rs4
2 files changed, 131 insertions, 97 deletions
diff --git a/embassy-stm32/src/ltdc.rs b/embassy-stm32/src/ltdc.rs
index adc9b8862..64558ee52 100644
--- a/embassy-stm32/src/ltdc.rs
+++ b/embassy-stm32/src/ltdc.rs
@@ -175,8 +175,31 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl
175} 175}
176 176
177impl<'d, T: Instance> Ltdc<'d, T> { 177impl<'d, T: Instance> Ltdc<'d, T> {
178 // 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
179 /// Note: Full-Duplex modes are not supported at this time
180 pub fn new(peri: impl Peripheral<P = T> + 'd) -> Self {
181 critical_section::with(|_cs| {
182 // 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.
183 // According to the debugger, this bit gets set, anyway.
184 #[cfg(stm32f7)]
185 stm32_metapac::RCC
186 .dckcfgr1()
187 .modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2));
188
189 // It is set to RCC_PLLSAIDIVR_2 in ST's BSP example for the STM32469I-DISCO.
190 #[cfg(not(any(stm32f7, stm32u5)))]
191 stm32_metapac::RCC
192 .dckcfgr()
193 .modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2));
194 });
195
196 rcc::enable_and_reset::<T>();
197 into_ref!(peri);
198 Self { _peri: peri }
199 }
200
178 /// Create a new LTDC driver. 8 pins per color channel for blue, green and red 201 /// Create a new LTDC driver. 8 pins per color channel for blue, green and red
179 pub fn new( 202 pub fn new_with_pins(
180 peri: impl Peripheral<P = T> + 'd, 203 peri: impl Peripheral<P = T> + 'd,
181 _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd, 204 _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
182 clk: impl Peripheral<P = impl ClkPin<T>> + 'd, 205 clk: impl Peripheral<P = impl ClkPin<T>> + 'd,
@@ -241,93 +264,7 @@ impl<'d, T: Instance> Ltdc<'d, T> {
241 Self { _peri: peri } 264 Self { _peri: peri }
242 } 265 }
243 266
244 fn clear_interrupt_flags() { 267 /// Initialise and enable the display
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);
250 });
251 }
252
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 }
269
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();
274
275 // if all clear
276 if !bits.fuif() && !bits.lif() && !bits.rrif() && !bits.terrif() {
277 // wait for interrupt
278 poll_fn(|cx| {
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);
296 });
297
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
328 }
329
330 /// Initialize the display
331 pub fn init(&mut self, config: &LtdcConfiguration) { 268 pub fn init(&mut self, config: &LtdcConfiguration) {
332 use stm32_metapac::ltdc::vals::{Depol, Hspol, Pcpol, Vspol}; 269 use stm32_metapac::ltdc::vals::{Depol, Hspol, Pcpol, Vspol};
333 let ltdc = T::regs(); 270 let ltdc = T::regs();
@@ -393,16 +330,27 @@ impl<'d, T: Instance> Ltdc<'d, T> {
393 w.set_bcblue(0); 330 w.set_bcblue(0);
394 }); 331 });
395 332
396 // enable LTDC by setting LTDCEN bit 333 self.enable();
397 ltdc.gcr().modify(|w| { 334 }
398 w.set_ltdcen(true); 335
399 }); 336 /// Set the enable bit in the control register and assert that it has been enabled
337 ///
338 /// This does need to be called if init has already been called
339 pub fn enable(&mut self) {
340 T::regs().gcr().modify(|w| w.set_ltdcen(true));
341 assert!(T::regs().gcr().read().ltdcen())
342 }
343
344 /// Unset the enable bit in the control register and assert that it has been disabled
345 pub fn disable(&mut self) {
346 T::regs().gcr().modify(|w| w.set_ltdcen(false));
347 assert!(!T::regs().gcr().read().ltdcen())
400 } 348 }
401 349
402 /// Enable the layer 350 /// Initialise and enable the layer
403 /// 351 ///
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 352 /// 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
405 pub fn enable_layer(&mut self, layer_config: &LtdcLayerConfig, clut: Option<&[RgbColor]>) { 353 pub fn init_layer(&mut self, layer_config: &LtdcLayerConfig, clut: Option<&[RgbColor]>) {
406 let ltdc = T::regs(); 354 let ltdc = T::regs();
407 let layer = ltdc.layer(layer_config.layer as usize); 355 let layer = ltdc.layer(layer_config.layer as usize);
408 356
@@ -475,6 +423,92 @@ impl<'d, T: Instance> Ltdc<'d, T> {
475 w.set_len(true); 423 w.set_len(true);
476 }); 424 });
477 } 425 }
426
427 /// Set the current buffer. The async function will return when buffer has been completely copied to the LCD screen
428 /// frame_buffer_addr is a pointer to memory that should not move (best to make it static)
429 pub async fn set_buffer(&mut self, layer: LtdcLayer, frame_buffer_addr: *const ()) -> Result<(), Error> {
430 let mut bits = T::regs().isr().read();
431
432 // if all clear
433 if !bits.fuif() && !bits.lif() && !bits.rrif() && !bits.terrif() {
434 // wait for interrupt
435 poll_fn(|cx| {
436 // quick check to avoid registration if already done.
437 let bits = T::regs().isr().read();
438 if bits.fuif() || bits.lif() || bits.rrif() || bits.terrif() {
439 return Poll::Ready(());
440 }
441
442 LTDC_WAKER.register(cx.waker());
443 Self::clear_interrupt_flags(); // don't poison the request with old flags
444 Self::enable_interrupts(true);
445
446 // set the new frame buffer address
447 let layer = T::regs().layer(layer as usize);
448 layer.cfbar().modify(|w| w.set_cfbadd(frame_buffer_addr as u32));
449
450 // configure a shadow reload for the next blanking period
451 T::regs().srcr().write(|w| {
452 w.set_vbr(Vbr::RELOAD);
453 });
454
455 // need to check condition after register to avoid a race
456 // condition that would result in lost notifications.
457 let bits = T::regs().isr().read();
458 if bits.fuif() || bits.lif() || bits.rrif() || bits.terrif() {
459 Poll::Ready(())
460 } else {
461 Poll::Pending
462 }
463 })
464 .await;
465
466 // re-read the status register after wait.
467 bits = T::regs().isr().read();
468 }
469
470 let result = if bits.fuif() {
471 Err(Error::FifoUnderrun)
472 } else if bits.terrif() {
473 Err(Error::TransferError)
474 } else if bits.lif() {
475 panic!("line interrupt event is disabled")
476 } else if bits.rrif() {
477 // register reload flag is expected
478 Ok(())
479 } else {
480 unreachable!("all interrupt status values checked")
481 };
482
483 Self::clear_interrupt_flags();
484 result
485 }
486
487 fn clear_interrupt_flags() {
488 T::regs().icr().write(|w| {
489 w.set_cfuif(Cfuif::CLEAR);
490 w.set_clif(Clif::CLEAR);
491 w.set_crrif(Crrif::CLEAR);
492 w.set_cterrif(Cterrif::CLEAR);
493 });
494 }
495
496 fn enable_interrupts(enable: bool) {
497 T::regs().ier().write(|w| {
498 w.set_fuie(enable);
499 w.set_lie(false); // we are not interested in the line interrupt enable event
500 w.set_rrie(enable);
501 w.set_terrie(enable)
502 });
503
504 // enable interrupts for LTDC peripheral
505 T::Interrupt::unpend();
506 if enable {
507 unsafe { T::Interrupt::enable() };
508 } else {
509 T::Interrupt::disable()
510 }
511 }
478} 512}
479 513
480impl<'d, T: Instance> Drop for Ltdc<'d, T> { 514impl<'d, T: Instance> Drop for Ltdc<'d, T> {
diff --git a/examples/stm32h7/src/bin/ltdc.rs b/examples/stm32h7/src/bin/ltdc.rs
index 3bd307012..3b56bbb6c 100644
--- a/examples/stm32h7/src/bin/ltdc.rs
+++ b/examples/stm32h7/src/bin/ltdc.rs
@@ -87,7 +87,7 @@ async fn main(spawner: Spawner) {
87 }; 87 };
88 88
89 info!("init ltdc"); 89 info!("init ltdc");
90 let mut ltdc = Ltdc::new( 90 let mut ltdc = Ltdc::new_with_pins(
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, 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, 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 ); 93 );
@@ -109,7 +109,7 @@ async fn main(spawner: Spawner) {
109 let clut = build_clut(&color_map); 109 let clut = build_clut(&color_map);
110 110
111 // enable the bottom layer with a 256 color lookup table 111 // enable the bottom layer with a 256 color lookup table
112 ltdc.enable_layer(&layer_config, Some(&clut)); 112 ltdc.init_layer(&layer_config, Some(&clut));
113 113
114 // Safety: the DoubleBuffer controls access to the statically allocated frame buffers 114 // Safety: the DoubleBuffer controls access to the statically allocated frame buffers
115 // and it is the only thing that mutates their content 115 // and it is the only thing that mutates their content