diff options
| -rw-r--r-- | embassy-stm32/src/ltdc.rs | 224 | ||||
| -rw-r--r-- | examples/stm32h7/src/bin/ltdc.rs | 4 |
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 | ||
| 177 | impl<'d, T: Instance> Ltdc<'d, T> { | 177 | impl<'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 | ||
| 480 | impl<'d, T: Instance> Drop for Ltdc<'d, T> { | 514 | impl<'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 |
