@@ -12,6 +12,14 @@ use crate::{
1212} ;
1313use fugit:: HertzU32 as Hertz ;
1414
15+ #[ cfg( feature = "framebuffer" ) ]
16+ use embedded_graphics_core:: {
17+ draw_target:: DrawTarget ,
18+ geometry:: { OriginDimensions , Size } ,
19+ pixelcolor:: { IntoStorage , Rgb565 } ,
20+ Pixel ,
21+ } ;
22+
1523/// Display configuration constants
1624pub struct DisplayConfig {
1725 pub active_width : u16 ,
@@ -352,6 +360,72 @@ impl<T: 'static + SupportedWord> DisplayController<T> {
352360 }
353361 }
354362
363+ /// Create a DisplayController for DSI-driven displays.
364+ ///
365+ /// Unlike [`new()`](Self::new), this constructor does not configure LTDC pins
366+ /// or PLLSAI. On DSI boards the DSI host drives the pixel clock and data
367+ /// lines, so LTDC only needs its timing registers set up.
368+ pub fn new_dsi (
369+ ltdc : LTDC ,
370+ dma2d : DMA2D ,
371+ pixel_format : PixelFormat ,
372+ config : DisplayConfig ,
373+ ) -> DisplayController < T > {
374+ unsafe {
375+ LTDC :: enable_unchecked ( ) ;
376+ LTDC :: reset_unchecked ( ) ;
377+ DMA2D :: enable_unchecked ( ) ;
378+ DMA2D :: reset_unchecked ( ) ;
379+ }
380+
381+ let total_width: u16 =
382+ config. h_sync + config. h_back_porch + config. active_width + config. h_front_porch - 1 ;
383+ let total_height: u16 =
384+ config. v_sync + config. v_back_porch + config. active_height + config. v_front_porch - 1 ;
385+
386+ ltdc. sscr ( ) . write ( |w| {
387+ w. hsw ( ) . set ( config. h_sync - 1 ) ;
388+ w. vsh ( ) . set ( config. v_sync - 1 )
389+ } ) ;
390+ ltdc. bpcr ( ) . write ( |w| {
391+ w. ahbp ( ) . set ( config. h_sync + config. h_back_porch - 1 ) ;
392+ w. avbp ( ) . set ( config. v_sync + config. v_back_porch - 1 )
393+ } ) ;
394+ ltdc. awcr ( ) . write ( |w| {
395+ w. aaw ( )
396+ . set ( config. h_sync + config. h_back_porch + config. active_width - 1 ) ;
397+ w. aah ( )
398+ . set ( config. v_sync + config. v_back_porch + config. active_height - 1 )
399+ } ) ;
400+ ltdc. twcr ( ) . write ( |w| {
401+ w. totalw ( ) . set ( total_width) ;
402+ w. totalh ( ) . set ( total_height)
403+ } ) ;
404+
405+ ltdc. gcr ( ) . write ( |w| {
406+ w. hspol ( ) . bit ( config. h_sync_pol ) ;
407+ w. vspol ( ) . bit ( config. v_sync_pol ) ;
408+ w. depol ( ) . bit ( config. no_data_enable_pol ) ;
409+ w. pcpol ( ) . bit ( config. pixel_clock_pol )
410+ } ) ;
411+
412+ ltdc. bccr ( ) . write ( |w| unsafe { w. bits ( 0xAAAAAAAA ) } ) ;
413+
414+ ltdc. srcr ( ) . modify ( |_, w| w. imr ( ) . set_bit ( ) ) ;
415+ ltdc. gcr ( )
416+ . modify ( |_, w| w. ltdcen ( ) . set_bit ( ) . den ( ) . set_bit ( ) ) ;
417+ ltdc. srcr ( ) . modify ( |_, w| w. imr ( ) . set_bit ( ) ) ;
418+
419+ DisplayController {
420+ _ltdc : ltdc,
421+ _dma2d : dma2d,
422+ config,
423+ buffer1 : None ,
424+ buffer2 : None ,
425+ pixel_format,
426+ }
427+ }
428+
355429 /// Configure the layer
356430 ///
357431 /// Note : the choice is made (for the sake of simplicity) to make the layer
@@ -424,7 +498,7 @@ impl<T: 'static + SupportedWord> DisplayController<T> {
424498 // PixelFormat::RGB888 => 24, unsupported for now because u24 does not exist
425499 PixelFormat :: RGB565 => 2 ,
426500 PixelFormat :: ARGB1555 => 2 ,
427- PixelFormat :: ARGB4444 => 16 ,
501+ PixelFormat :: ARGB4444 => 2 ,
428502 PixelFormat :: L8 => 1 ,
429503 PixelFormat :: AL44 => 1 ,
430504 PixelFormat :: AL88 => 2 ,
@@ -487,11 +561,64 @@ impl<T: 'static + SupportedWord> DisplayController<T> {
487561 } ) [ x + self . config . active_width as usize * y] = color;
488562 }
489563
564+ /// Get a mutable reference to the layer's framebuffer.
565+ ///
566+ /// Returns `None` if the layer has not been configured with [`config_layer()`](Self::config_layer).
567+ pub fn layer_buffer_mut ( & mut self , layer : Layer ) -> Option < & mut [ T ] > {
568+ match layer {
569+ Layer :: L1 => self . buffer1 . as_deref_mut ( ) ,
570+ Layer :: L2 => self . buffer2 . as_deref_mut ( ) ,
571+ }
572+ }
573+
574+ /// Set the global alpha (transparency) for a layer.
575+ ///
576+ /// `alpha`: 0 = fully transparent, 255 = fully opaque.
577+ /// Takes effect after [`reload()`](Self::reload).
578+ pub fn set_layer_transparency ( & self , layer : Layer , alpha : u8 ) {
579+ self . _ltdc
580+ . layer ( layer as usize )
581+ . cacr ( )
582+ . write ( |w| w. consta ( ) . set ( alpha) ) ;
583+ self . reload_on_vblank ( ) ;
584+ }
585+
586+ /// Change the framebuffer address for a layer.
587+ ///
588+ /// This can be used for double-buffering by swapping between two
589+ /// pre-allocated framebuffers. Takes effect after [`reload()`](Self::reload).
590+ pub fn set_layer_buffer_address ( & self , layer : Layer , address : u32 ) {
591+ self . _ltdc
592+ . layer ( layer as usize )
593+ . cfbar ( )
594+ . write ( |w| w. cfbadd ( ) . set ( address) ) ;
595+ self . reload_on_vblank ( ) ;
596+ }
597+
598+ /// Enable color keying on a layer.
599+ ///
600+ /// Pixels matching `color_key` (RGB888 format, 24-bit) become fully
601+ /// transparent, allowing the layer below to show through.
602+ /// Takes effect after [`reload()`](Self::reload).
603+ pub fn set_color_keying ( & mut self , layer : Layer , color_key : u32 ) {
604+ let l = self . _ltdc . layer ( layer as usize ) ;
605+ l. ckcr ( )
606+ . write ( |w| unsafe { w. bits ( color_key & 0x00FF_FFFF ) } ) ;
607+ l. cr ( ) . modify ( |_, w| w. colken ( ) . set_bit ( ) ) ;
608+ self . reload_on_vblank ( ) ;
609+ }
610+
490611 /// Draw hardware accelerated rectangle
491612 ///
492613 /// # Safety
493614 ///
494- /// TODO: use safer DMA transfers
615+ /// The caller must ensure:
616+ /// - The framebuffer for `layer` has been configured via [`config_layer()`](Self::config_layer)
617+ /// - `top_left` and `bottom_right` coordinates are within the display bounds
618+ /// - No other DMA2D operation is in progress
619+ ///
620+ /// This method uses DMA2D register-to-memory mode to fill the rectangle.
621+ /// A future version may provide a safe wrapper with proper DMA completion tracking.
495622 pub unsafe fn draw_rectangle (
496623 & mut self ,
497624 layer : Layer ,
@@ -544,13 +671,88 @@ impl<T: 'static + SupportedWord> DisplayController<T> {
544671 self . _dma2d
545672 . cr ( )
546673 . modify ( |_, w| w. mode ( ) . bits ( 0b11 ) . start ( ) . set_bit ( ) ) ;
674+ // Wait for DMA2D transfer to complete
675+ while self . _dma2d . cr ( ) . read ( ) . start ( ) . bit_is_set ( ) { }
547676 }
548677
549678 /// Reload display controller immediatly
550679 pub fn reload ( & self ) {
551680 // Reload ltdc config immediatly
552681 self . _ltdc . srcr ( ) . modify ( |_, w| w. imr ( ) . set_bit ( ) ) ;
553682 }
683+
684+ /// Reload display controller on next vertical blanking
685+ pub fn reload_on_vblank ( & self ) {
686+ self . _ltdc . srcr ( ) . modify ( |_, w| w. vbr ( ) . set_bit ( ) ) ;
687+ }
688+ }
689+
690+ /// A framebuffer wrapper that implements [`DrawTarget`] for use with
691+ /// `embedded-graphics`.
692+ ///
693+ /// `LtdcFramebuffer` owns a `&'static mut [T]` SDRAM buffer and provides
694+ /// pixel-level drawing via the `embedded-graphics` `DrawTarget` trait.
695+ ///
696+ /// # Usage
697+ ///
698+ /// ```ignore
699+ /// let mut fb = LtdcFramebuffer::new(buffer, 480, 800);
700+ /// fb.clear(Rgb565::BLACK).ok();
701+ /// // ... draw with embedded-graphics ...
702+ /// let buffer = fb.into_inner();
703+ /// display_ctrl.config_layer(Layer::L1, buffer, PixelFormat::RGB565);
704+ /// ```
705+ #[ cfg( feature = "framebuffer" ) ]
706+ pub struct LtdcFramebuffer < T : ' static + SupportedWord > {
707+ buf : & ' static mut [ T ] ,
708+ width : u16 ,
709+ height : u16 ,
710+ }
711+
712+ #[ cfg( feature = "framebuffer" ) ]
713+ impl < T : ' static + SupportedWord > LtdcFramebuffer < T > {
714+ /// Create a new framebuffer wrapper.
715+ ///
716+ /// # Panics
717+ ///
718+ /// Panics if `buf.len() != width * height`.
719+ pub fn new ( buf : & ' static mut [ T ] , width : u16 , height : u16 ) -> Self {
720+ assert ! ( buf. len( ) == ( width as usize ) * ( height as usize ) ) ;
721+ Self { buf, width, height }
722+ }
723+
724+ /// Consume the framebuffer and return the underlying buffer.
725+ pub fn into_inner ( self ) -> & ' static mut [ T ] {
726+ self . buf
727+ }
728+ }
729+
730+ #[ cfg( feature = "framebuffer" ) ]
731+ impl DrawTarget for LtdcFramebuffer < u16 > {
732+ type Color = Rgb565 ;
733+ type Error = core:: convert:: Infallible ;
734+
735+ fn draw_iter < I > ( & mut self , pixels : I ) -> Result < ( ) , Self :: Error >
736+ where
737+ I : IntoIterator < Item = Pixel < Self :: Color > > ,
738+ {
739+ let w = self . width as i32 ;
740+ let h = self . height as i32 ;
741+ for Pixel ( coord, color) in pixels {
742+ let ( x, y) : ( i32 , i32 ) = coord. into ( ) ;
743+ if x >= 0 && x < w && y >= 0 && y < h {
744+ self . buf [ y as usize * self . width as usize + x as usize ] = color. into_storage ( ) ;
745+ }
746+ }
747+ Ok ( ( ) )
748+ }
749+ }
750+
751+ #[ cfg( feature = "framebuffer" ) ]
752+ impl < T : ' static + SupportedWord > OriginDimensions for LtdcFramebuffer < T > {
753+ fn size ( & self ) -> Size {
754+ Size :: new ( self . width as u32 , self . height as u32 )
755+ }
554756}
555757
556758/// Available PixelFormats to work with
0 commit comments