embassy_stm32/
ltdc.rs

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;
6use core::marker::PhantomData;
7use core::task::Poll;
8
9use embassy_hal_internal::PeripheralType;
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, Peri};
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,
48
49    /// Horizontal synchronization width (in units of pixel clock period)
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}
83
84/// LTDC driver.
85pub struct Ltdc<'d, T: Instance> {
86    _peri: Peri<'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    }
176}
177
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
180    /// Note: Full-Duplex modes are not supported at this time
181    pub fn new(peri: Peri<'d, T>) -> Self {
182        Self::setup_clocks();
183        Self { _peri: peri }
184    }
185
186    /// Create a new LTDC driver. 8 pins per color channel for blue, green and red
187    #[allow(clippy::too_many_arguments)]
188    pub fn new_with_pins(
189        peri: Peri<'d, T>,
190        _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
191        clk: Peri<'d, impl ClkPin<T>>,
192        hsync: Peri<'d, impl HsyncPin<T>>,
193        vsync: Peri<'d, impl VsyncPin<T>>,
194        b0: Peri<'d, impl B0Pin<T>>,
195        b1: Peri<'d, impl B1Pin<T>>,
196        b2: Peri<'d, impl B2Pin<T>>,
197        b3: Peri<'d, impl B3Pin<T>>,
198        b4: Peri<'d, impl B4Pin<T>>,
199        b5: Peri<'d, impl B5Pin<T>>,
200        b6: Peri<'d, impl B6Pin<T>>,
201        b7: Peri<'d, impl B7Pin<T>>,
202        g0: Peri<'d, impl G0Pin<T>>,
203        g1: Peri<'d, impl G1Pin<T>>,
204        g2: Peri<'d, impl G2Pin<T>>,
205        g3: Peri<'d, impl G3Pin<T>>,
206        g4: Peri<'d, impl G4Pin<T>>,
207        g5: Peri<'d, impl G5Pin<T>>,
208        g6: Peri<'d, impl G6Pin<T>>,
209        g7: Peri<'d, impl G7Pin<T>>,
210        r0: Peri<'d, impl R0Pin<T>>,
211        r1: Peri<'d, impl R1Pin<T>>,
212        r2: Peri<'d, impl R2Pin<T>>,
213        r3: Peri<'d, impl R3Pin<T>>,
214        r4: Peri<'d, impl R4Pin<T>>,
215        r5: Peri<'d, impl R5Pin<T>>,
216        r6: Peri<'d, impl R6Pin<T>>,
217        r7: Peri<'d, impl R7Pin<T>>,
218    ) -> Self {
219        Self::setup_clocks();
220        new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh));
221        new_pin!(hsync, AfType::output(OutputType::PushPull, Speed::VeryHigh));
222        new_pin!(vsync, AfType::output(OutputType::PushPull, Speed::VeryHigh));
223        new_pin!(b0, AfType::output(OutputType::PushPull, Speed::VeryHigh));
224        new_pin!(b1, AfType::output(OutputType::PushPull, Speed::VeryHigh));
225        new_pin!(b2, AfType::output(OutputType::PushPull, Speed::VeryHigh));
226        new_pin!(b3, AfType::output(OutputType::PushPull, Speed::VeryHigh));
227        new_pin!(b4, AfType::output(OutputType::PushPull, Speed::VeryHigh));
228        new_pin!(b5, AfType::output(OutputType::PushPull, Speed::VeryHigh));
229        new_pin!(b6, AfType::output(OutputType::PushPull, Speed::VeryHigh));
230        new_pin!(b7, AfType::output(OutputType::PushPull, Speed::VeryHigh));
231        new_pin!(g0, AfType::output(OutputType::PushPull, Speed::VeryHigh));
232        new_pin!(g1, AfType::output(OutputType::PushPull, Speed::VeryHigh));
233        new_pin!(g2, AfType::output(OutputType::PushPull, Speed::VeryHigh));
234        new_pin!(g3, AfType::output(OutputType::PushPull, Speed::VeryHigh));
235        new_pin!(g4, AfType::output(OutputType::PushPull, Speed::VeryHigh));
236        new_pin!(g5, AfType::output(OutputType::PushPull, Speed::VeryHigh));
237        new_pin!(g6, AfType::output(OutputType::PushPull, Speed::VeryHigh));
238        new_pin!(g7, AfType::output(OutputType::PushPull, Speed::VeryHigh));
239        new_pin!(r0, AfType::output(OutputType::PushPull, Speed::VeryHigh));
240        new_pin!(r1, AfType::output(OutputType::PushPull, Speed::VeryHigh));
241        new_pin!(r2, AfType::output(OutputType::PushPull, Speed::VeryHigh));
242        new_pin!(r3, AfType::output(OutputType::PushPull, Speed::VeryHigh));
243        new_pin!(r4, AfType::output(OutputType::PushPull, Speed::VeryHigh));
244        new_pin!(r5, AfType::output(OutputType::PushPull, Speed::VeryHigh));
245        new_pin!(r6, AfType::output(OutputType::PushPull, Speed::VeryHigh));
246        new_pin!(r7, AfType::output(OutputType::PushPull, Speed::VeryHigh));
247
248        Self { _peri: peri }
249    }
250
251    /// Initialise and enable the display
252    pub fn init(&mut self, config: &LtdcConfiguration) {
253        use stm32_metapac::ltdc::vals::{Depol, Hspol, Pcpol, Vspol};
254        let ltdc = T::regs();
255
256        // check bus access
257        assert!(ltdc.gcr().read().0 == 0x2220); // reset value
258
259        // configure the HS, VS, DE and PC polarity
260        ltdc.gcr().modify(|w| {
261            w.set_hspol(match config.h_sync_polarity {
262                PolarityActive::ActiveHigh => Hspol::ACTIVE_HIGH,
263                PolarityActive::ActiveLow => Hspol::ACTIVE_LOW,
264            });
265
266            w.set_vspol(match config.v_sync_polarity {
267                PolarityActive::ActiveHigh => Vspol::ACTIVE_HIGH,
268                PolarityActive::ActiveLow => Vspol::ACTIVE_LOW,
269            });
270
271            w.set_depol(match config.data_enable_polarity {
272                PolarityActive::ActiveHigh => Depol::ACTIVE_HIGH,
273                PolarityActive::ActiveLow => Depol::ACTIVE_LOW,
274            });
275
276            w.set_pcpol(match config.pixel_clock_polarity {
277                PolarityEdge::RisingEdge => Pcpol::RISING_EDGE,
278                PolarityEdge::FallingEdge => Pcpol::FALLING_EDGE,
279            });
280        });
281
282        // set synchronization pulse width
283        ltdc.sscr().modify(|w| {
284            w.set_vsh(config.v_sync - 1);
285            w.set_hsw(config.h_sync - 1);
286        });
287
288        // set accumulated back porch
289        ltdc.bpcr().modify(|w| {
290            w.set_avbp(config.v_sync + config.v_back_porch - 1);
291            w.set_ahbp(config.h_sync + config.h_back_porch - 1);
292        });
293
294        // set accumulated active width
295        let aa_height = config.v_sync + config.v_back_porch + config.active_height - 1;
296        let aa_width = config.h_sync + config.h_back_porch + config.active_width - 1;
297        ltdc.awcr().modify(|w| {
298            w.set_aah(aa_height);
299            w.set_aaw(aa_width);
300        });
301
302        // set total width and height
303        let total_height: u16 = config.v_sync + config.v_back_porch + config.active_height + config.v_front_porch - 1;
304        let total_width: u16 = config.h_sync + config.h_back_porch + config.active_width + config.h_front_porch - 1;
305        ltdc.twcr().modify(|w| {
306            w.set_totalh(total_height);
307            w.set_totalw(total_width)
308        });
309
310        // set the background color value to black
311        ltdc.bccr().modify(|w| {
312            w.set_bcred(0);
313            w.set_bcgreen(0);
314            w.set_bcblue(0);
315        });
316
317        self.enable();
318    }
319
320    /// Set the enable bit in the control register and assert that it has been enabled
321    ///
322    /// This does need to be called if init has already been called
323    pub fn enable(&mut self) {
324        T::regs().gcr().modify(|w| w.set_ltdcen(true));
325        assert!(T::regs().gcr().read().ltdcen())
326    }
327
328    /// Unset the enable bit in the control register and assert that it has been disabled
329    pub fn disable(&mut self) {
330        T::regs().gcr().modify(|w| w.set_ltdcen(false));
331        assert!(!T::regs().gcr().read().ltdcen())
332    }
333
334    /// Initialise and enable the layer
335    ///
336    /// 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
337    pub fn init_layer(&mut self, layer_config: &LtdcLayerConfig, clut: Option<&[RgbColor]>) {
338        let ltdc = T::regs();
339        let layer = ltdc.layer(layer_config.layer as usize);
340
341        // 256 color look-up table for L8, AL88 and AL88 pixel formats
342        if let Some(clut) = clut {
343            assert_eq!(clut.len(), 256, "Color lookup table must be exactly 256 in length");
344            for (index, color) in clut.iter().enumerate() {
345                layer.clutwr().write(|w| {
346                    w.set_clutadd(index as u8);
347                    w.set_red(color.red);
348                    w.set_green(color.green);
349                    w.set_blue(color.blue);
350                });
351            }
352        }
353
354        // configure the horizontal start and stop position
355        let h_win_start = layer_config.window_x0 + ltdc.bpcr().read().ahbp() + 1;
356        let h_win_stop = layer_config.window_x1 + ltdc.bpcr().read().ahbp();
357        layer.whpcr().write(|w| {
358            w.set_whstpos(h_win_start);
359            w.set_whsppos(h_win_stop);
360        });
361
362        // configure the vertical start and stop position
363        let v_win_start = layer_config.window_y0 + ltdc.bpcr().read().avbp() + 1;
364        let v_win_stop = layer_config.window_y1 + ltdc.bpcr().read().avbp();
365        layer.wvpcr().write(|w| {
366            w.set_wvstpos(v_win_start);
367            w.set_wvsppos(v_win_stop)
368        });
369
370        // set the pixel format
371        layer
372            .pfcr()
373            .write(|w| w.set_pf(Pf::from_bits(layer_config.pixel_format as u8)));
374
375        // set the default color value to transparent black
376        layer.dccr().write_value(Dccr::default());
377
378        // set the global constant alpha value
379        let alpha = 0xFF;
380        layer.cacr().write(|w| w.set_consta(alpha));
381
382        // set the blending factors.
383        layer.bfcr().modify(|w| {
384            w.set_bf1(Bf1::PIXEL);
385            w.set_bf2(Bf2::PIXEL);
386        });
387
388        // calculate framebuffer pixel size in bytes
389        let bytes_per_pixel = layer_config.pixel_format.bytes_per_pixel() as u16;
390        let width = layer_config.window_x1 - layer_config.window_x0;
391        let height = layer_config.window_y1 - layer_config.window_y0;
392
393        // framebuffer pitch and line length
394        layer.cfblr().modify(|w| {
395            w.set_cfbp(width * bytes_per_pixel);
396            #[cfg(not(stm32u5))]
397            w.set_cfbll(width * bytes_per_pixel + 7);
398            #[cfg(stm32u5)]
399            w.set_cfbll(width * bytes_per_pixel + 3);
400        });
401
402        // framebuffer line number
403        layer.cfblnr().modify(|w| w.set_cfblnbr(height));
404
405        // enable LTDC_Layer by setting LEN bit
406        layer.cr().modify(|w| {
407            if clut.is_some() {
408                w.set_cluten(true);
409            }
410            w.set_len(true);
411        });
412    }
413
414    /// Set the current buffer. The async function will return when buffer has been completely copied to the LCD screen
415    /// frame_buffer_addr is a pointer to memory that should not move (best to make it static)
416    pub async fn set_buffer(&mut self, layer: LtdcLayer, frame_buffer_addr: *const ()) -> Result<(), Error> {
417        let mut bits = T::regs().isr().read();
418
419        // if all clear
420        if !bits.fuif() && !bits.lif() && !bits.rrif() && !bits.terrif() {
421            // wait for interrupt
422            poll_fn(|cx| {
423                // quick check to avoid registration if already done.
424                let bits = T::regs().isr().read();
425                if bits.fuif() || bits.lif() || bits.rrif() || bits.terrif() {
426                    return Poll::Ready(());
427                }
428
429                LTDC_WAKER.register(cx.waker());
430                Self::clear_interrupt_flags(); // don't poison the request with old flags
431                Self::enable_interrupts(true);
432
433                // set the new frame buffer address
434                let layer = T::regs().layer(layer as usize);
435                layer.cfbar().modify(|w| w.set_cfbadd(frame_buffer_addr as u32));
436
437                // configure a shadow reload for the next blanking period
438                T::regs().srcr().write(|w| {
439                    w.set_vbr(Vbr::RELOAD);
440                });
441
442                // need to check condition after register to avoid a race
443                // condition that would result in lost notifications.
444                let bits = T::regs().isr().read();
445                if bits.fuif() || bits.lif() || bits.rrif() || bits.terrif() {
446                    Poll::Ready(())
447                } else {
448                    Poll::Pending
449                }
450            })
451            .await;
452
453            // re-read the status register after wait.
454            bits = T::regs().isr().read();
455        }
456
457        let result = if bits.fuif() {
458            Err(Error::FifoUnderrun)
459        } else if bits.terrif() {
460            Err(Error::TransferError)
461        } else if bits.lif() {
462            panic!("line interrupt event is disabled")
463        } else if bits.rrif() {
464            // register reload flag is expected
465            Ok(())
466        } else {
467            unreachable!("all interrupt status values checked")
468        };
469
470        Self::clear_interrupt_flags();
471        result
472    }
473
474    fn setup_clocks() {
475        critical_section::with(|_cs| {
476            // 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.
477            // According to the debugger, this bit gets set, anyway.
478            #[cfg(stm32f7)]
479            crate::pac::RCC
480                .dckcfgr1()
481                .modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2));
482
483            // It is set to RCC_PLLSAIDIVR_2 in ST's BSP example for the STM32469I-DISCO.
484            #[cfg(stm32f4)]
485            crate::pac::RCC
486                .dckcfgr()
487                .modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2));
488        });
489
490        rcc::enable_and_reset::<T>();
491    }
492
493    fn clear_interrupt_flags() {
494        T::regs().icr().write(|w| {
495            w.set_cfuif(Cfuif::CLEAR);
496            w.set_clif(Clif::CLEAR);
497            w.set_crrif(Crrif::CLEAR);
498            w.set_cterrif(Cterrif::CLEAR);
499        });
500    }
501
502    fn enable_interrupts(enable: bool) {
503        T::regs().ier().write(|w| {
504            w.set_fuie(enable);
505            w.set_lie(false); // we are not interested in the line interrupt enable event
506            w.set_rrie(enable);
507            w.set_terrie(enable)
508        });
509
510        // enable interrupts for LTDC peripheral
511        T::Interrupt::unpend();
512        if enable {
513            unsafe { T::Interrupt::enable() };
514        } else {
515            T::Interrupt::disable()
516        }
517    }
518}
519
520impl<'d, T: Instance> Drop for Ltdc<'d, T> {
521    fn drop(&mut self) {}
522}
523
524trait SealedInstance: crate::rcc::SealedRccPeripheral {
525    fn regs() -> crate::pac::ltdc::Ltdc;
526}
527
528/// LTDC instance trait.
529#[allow(private_bounds)]
530pub trait Instance: SealedInstance + PeripheralType + crate::rcc::RccPeripheral + 'static + Send {
531    /// Interrupt for this LTDC instance.
532    type Interrupt: interrupt::typelevel::Interrupt;
533}
534
535pin_trait!(ClkPin, Instance);
536pin_trait!(HsyncPin, Instance);
537pin_trait!(VsyncPin, Instance);
538pin_trait!(DePin, Instance);
539pin_trait!(R0Pin, Instance);
540pin_trait!(R1Pin, Instance);
541pin_trait!(R2Pin, Instance);
542pin_trait!(R3Pin, Instance);
543pin_trait!(R4Pin, Instance);
544pin_trait!(R5Pin, Instance);
545pin_trait!(R6Pin, Instance);
546pin_trait!(R7Pin, Instance);
547pin_trait!(G0Pin, Instance);
548pin_trait!(G1Pin, Instance);
549pin_trait!(G2Pin, Instance);
550pin_trait!(G3Pin, Instance);
551pin_trait!(G4Pin, Instance);
552pin_trait!(G5Pin, Instance);
553pin_trait!(G6Pin, Instance);
554pin_trait!(G7Pin, Instance);
555pin_trait!(B0Pin, Instance);
556pin_trait!(B1Pin, Instance);
557pin_trait!(B2Pin, Instance);
558pin_trait!(B3Pin, Instance);
559pin_trait!(B4Pin, Instance);
560pin_trait!(B5Pin, Instance);
561pin_trait!(B6Pin, Instance);
562pin_trait!(B7Pin, Instance);
563
564foreach_interrupt!(
565    ($inst:ident, ltdc, LTDC, GLOBAL, $irq:ident) => {
566        impl Instance for peripherals::$inst {
567            type Interrupt = crate::interrupt::typelevel::$irq;
568        }
569
570        impl SealedInstance for peripherals::$inst {
571            fn regs() -> crate::pac::ltdc::Ltdc {
572                crate::pac::$inst
573            }
574        }
575    };
576);