stm32f7xx_hal/
ltdc.rs

1//! Interface to the LCD-TFT display controller
2
3#[cfg_attr(test, allow(unused_imports))]
4use micromath::F32Ext;
5
6use crate::{
7    pac::{DMA2D, LTDC, RCC},
8    rcc::{Enable, HSEClock, Reset},
9};
10
11/// Display configuration constants
12pub struct DisplayConfig {
13    pub active_width: u16,
14    pub active_height: u16,
15    pub h_back_porch: u16,
16    pub h_front_porch: u16,
17    pub v_back_porch: u16,
18    pub v_front_porch: u16,
19    pub h_sync: u16,
20    pub v_sync: u16,
21    pub frame_rate: u16,
22    /// `false`: active low, `true`: active high
23    pub h_sync_pol: bool,
24    /// `false`: active low, `true`: active high
25    pub v_sync_pol: bool,
26    /// `false`: active low, `true`: active high
27    pub no_data_enable_pol: bool,
28    /// `false`: active low, `true`: active high
29    pub pixel_clock_pol: bool,
30}
31
32/// Accessible layers
33/// * `L1`: layer 1
34/// * `L2`: layer 2
35pub enum Layer {
36    L1,
37    L2,
38}
39
40pub struct DisplayController<T: 'static + SupportedWord> {
41    /// ltdc instance
42    _ltdc: LTDC,
43    /// dma2d instance
44    _dma2d: DMA2D,
45    /// Configuration structure
46    config: DisplayConfig,
47    /// layer 1 buffer
48    buffer1: Option<&'static mut [T]>,
49    /// layer 2 buffer
50    buffer2: Option<&'static mut [T]>,
51    /// Pixels format in the layers
52    pixel_format: PixelFormat,
53}
54
55impl<T: 'static + SupportedWord> DisplayController<T> {
56    /// Create and configure the DisplayController
57    pub fn new(
58        ltdc: LTDC,
59        dma2d: DMA2D,
60        pixel_format: PixelFormat,
61        config: DisplayConfig,
62        hse: Option<&HSEClock>,
63    ) -> DisplayController<T> {
64        // Screen constants
65        let total_width: u16 =
66            config.h_sync + config.h_back_porch + config.active_width + config.h_front_porch - 1;
67        let total_height: u16 =
68            config.v_sync + config.v_back_porch + config.active_height + config.v_front_porch - 1;
69        let lcd_clk: u32 =
70            (total_width as u32) * (total_height as u32) * (config.frame_rate as u32);
71
72        // TODO : change it to something safe ...
73        unsafe {
74            // Enable LTDC
75            LTDC::enable_unchecked();
76            // Reset LTDC peripheral
77            LTDC::reset_unchecked();
78
79            // Enable DMA2D
80            DMA2D::enable_unchecked();
81            // Reset DMA2D
82            DMA2D::reset_unchecked();
83        }
84
85        // Get base clock and PLLM divisor
86        let base_clk: u32;
87        match &hse {
88            Some(hse) => base_clk = hse.freq.raw(),
89            // If no HSE is provided, we use the HSI clock at 16 MHz
90            None => base_clk = 16_000_000,
91        }
92        let rcc = unsafe { &(*RCC::ptr()) };
93        let pllm: u8 = rcc.pllcfgr.read().pllm().bits();
94
95        // There are 24 combinations possible for a divisor with PLLR and DIVR
96        // We find the one that is the closest possible to the target value
97        // while respecting all the conditions
98        let vco_in_mhz: f32 = (base_clk as f32 / pllm as f32) / 1_000_000.0;
99        let lcd_clk_mhz = (lcd_clk as f32) / 1_000_000.0;
100        let allowed_pllr = [2.0, 3.0, 4.0, 5.0, 6.0, 7.0];
101        let allowed_divr = [2.0, 4.0, 8.0, 16.0];
102        let mut best_pllr: f32 = allowed_pllr[0];
103        let mut best_divr: f32 = allowed_divr[0];
104        let mut best_plln: f32 = 100.0;
105        let mut best_error: f32 = (vco_in_mhz * best_plln) / (best_pllr * best_divr);
106        let mut error: f32;
107        let mut plln: f32;
108
109        for pllr in &allowed_pllr {
110            for divr in &allowed_divr {
111                plln = ((lcd_clk_mhz * divr * pllr) / vco_in_mhz).floor();
112                error = lcd_clk_mhz - (vco_in_mhz * plln) / (pllr * divr);
113
114                // We have to make sure that the VCO_OUT is in range [100, 432]
115                // MHz Because VCO_IN is in range [1, 2] Mhz, the condition
116                // PLLN in range [50, 432] is automatically satisfied
117                if 100.0 <= vco_in_mhz * plln
118                    && vco_in_mhz * plln <= 432.0
119                    && error >= 0.0
120                    && error < best_error
121                {
122                    best_pllr = *pllr;
123                    best_divr = *divr;
124                    best_plln = plln;
125                    best_error = error;
126                }
127            }
128        }
129
130        let pllsaidivr: u8 = match best_divr as u16 {
131            2 => 0b00,
132            4 => 0b01,
133            8 => 0b10,
134            16 => 0b11,
135            _ => unreachable!(),
136        };
137
138        // // Write PPLSAI configuration
139        rcc.pllsaicfgr.write(|w| unsafe {
140            w.pllsain()
141                .bits(best_plln as u16)
142                .pllsair()
143                .bits(best_pllr as u8)
144        });
145        rcc.dckcfgr1.modify(|_, w| w.pllsaidivr().bits(pllsaidivr));
146
147        // Enable PLLSAI and wait for it
148        rcc.cr.modify(|_, w| w.pllsaion().on());
149        while rcc.cr.read().pllsairdy().is_not_ready() {}
150
151        // Configure LTDC Timing registers
152        ltdc.sscr.write(|w| {
153            w.hsw()
154                .bits((config.h_sync - 1) as u16)
155                .vsh()
156                .bits((config.v_sync - 1) as u16)
157        });
158        ltdc.bpcr.write(|w| {
159            w.ahbp()
160                .bits((config.h_sync + config.h_back_porch - 1) as u16)
161                .avbp()
162                .bits((config.v_sync + config.v_back_porch - 1) as u16)
163        });
164        ltdc.awcr.write(|w| {
165            w.aaw()
166                .bits((config.h_sync + config.h_back_porch + config.active_width - 1) as u16)
167                .aah()
168                .bits((config.v_sync + config.v_back_porch + config.active_height - 1) as u16)
169        });
170        ltdc.twcr.write(|w| {
171            w.totalw()
172                .bits(total_width as u16)
173                .totalh()
174                .bits(total_height as u16)
175        });
176
177        // Configure LTDC signals polarity
178        ltdc.gcr.write(|w| {
179            w.hspol()
180                .bit(config.h_sync_pol)
181                .vspol()
182                .bit(config.v_sync_pol)
183                .depol()
184                .bit(config.no_data_enable_pol)
185                .pcpol()
186                .bit(config.pixel_clock_pol)
187        });
188
189        // Set blue background color
190        ltdc.bccr.write(|w| unsafe { w.bits(0xAAAAAAAA) });
191
192        // TODO: configure interupts
193
194        // Reload ltdc config immediatly
195        ltdc.srcr.modify(|_, w| w.imr().set_bit());
196        // Turn display ON
197        ltdc.gcr.modify(|_, w| w.ltdcen().set_bit().den().set_bit());
198
199        // Reload ltdc config immediatly
200        ltdc.srcr.modify(|_, w| w.imr().set_bit());
201
202        DisplayController {
203            _ltdc: ltdc,
204            _dma2d: dma2d,
205            config,
206            buffer1: None,
207            buffer2: None,
208            pixel_format,
209        }
210    }
211
212    /// Configure the layer
213    ///
214    /// Note : the choice is made (for the sake of simplicity) to make the layer
215    /// as big as the screen
216    ///
217    /// Color Keying and CLUT are not yet supported
218    pub fn config_layer(
219        &mut self,
220        layer: Layer,
221        buffer: &'static mut [T],
222        pixel_format: PixelFormat,
223    ) {
224        let _layer = match &layer {
225            Layer::L1 => &self._ltdc.layer1,
226            Layer::L2 => &self._ltdc.layer2,
227        };
228
229        let height = self.config.active_height;
230        let width = self.config.active_width;
231        assert!(buffer.len() == height as usize * width as usize);
232
233        // Horizontal and vertical window (coordinates include porches): where
234        // in the time frame the layer values should be sent
235        let h_win_start = self.config.h_sync + self.config.h_back_porch - 1;
236        let v_win_start = self.config.v_sync + self.config.v_back_porch - 1;
237
238        _layer.whpcr.write(|w| {
239            w.whstpos()
240                .bits(h_win_start + 1)
241                .whsppos()
242                .bits(h_win_start + width)
243        });
244        _layer.wvpcr.write(|w| {
245            w.wvstpos()
246                .bits(v_win_start + 1)
247                .wvsppos()
248                .bits(v_win_start + height)
249        });
250
251        // Set pixel format
252        _layer.pfcr.write(|w| {
253            w.pf().bits(match &pixel_format {
254                PixelFormat::ARGB8888 => 0b000,
255                // PixelFormat::RGB888 => 0b001,
256                PixelFormat::RGB565 => 0b010,
257                PixelFormat::ARGB1555 => 0b011,
258                PixelFormat::ARGB4444 => 0b100,
259                PixelFormat::L8 => 0b101,
260                PixelFormat::AL44 => 0b110,
261                PixelFormat::AL88 => 0b111,
262                // _ => unimplemented!(),
263            })
264        });
265
266        // Set global alpha value to 1 (255/255). Used for layer blending.
267        _layer.cacr.write(|w| w.consta().bits(0xFF));
268
269        // Set default color to plain (not transparent) red (for debug
270        // purposes). The default color is used outside the defined layer window
271        // or when a layer is disabled.
272        _layer.dccr.write(|w| unsafe { w.bits(0xFFFF0000) });
273
274        // Blending factor: how the layer is combined with the layer below it
275        // (layer 2 with layer 1 or layer 1 with background). Here it is set so
276        // that the blending factor does not take the pixel alpha value, just
277        // the global value of the layer
278        _layer
279            .bfcr
280            .write(|w| unsafe { w.bf1().bits(0b100).bf2().bits(0b101) });
281
282        // Color frame buffer start address
283        _layer
284            .cfbar
285            .write(|w| w.cfbadd().bits(buffer.as_ptr() as u32));
286
287        // Color frame buffer line length (active*byte per pixel + 3), and pitch
288        let byte_per_pixel: u16 = match &pixel_format {
289            PixelFormat::ARGB8888 => 4,
290            // PixelFormat::RGB888 => 24, unsupported for now because u24 does not exist
291            PixelFormat::RGB565 => 2,
292            PixelFormat::ARGB1555 => 2,
293            PixelFormat::ARGB4444 => 16,
294            PixelFormat::L8 => 1,
295            PixelFormat::AL44 => 1,
296            PixelFormat::AL88 => 2,
297            // _ => unimplemented!(),
298        };
299        _layer.cfblr.write(|w| {
300            w.cfbp()
301                .bits(width * byte_per_pixel)
302                .cfbll()
303                .bits(width * byte_per_pixel + 3)
304        });
305
306        // Frame buffer number of lines
307        _layer.cfblnr.write(|w| w.cfblnbr().bits(height));
308
309        // No Color Lookup table (CLUT)
310        _layer.cr.modify(|_, w| w.cluten().clear_bit());
311
312        // Config DMA2D hardware acceleration : pixel format, no CLUT
313        self._dma2d.fgpfccr.write(|w| unsafe {
314            w.bits(match &pixel_format {
315                PixelFormat::ARGB8888 => 0b000,
316                // PixelFormat::RGB888 => 0b0001, unsupported for now because u24 does not exist
317                PixelFormat::RGB565 => 0b0010,
318                PixelFormat::ARGB1555 => 0b0011,
319                PixelFormat::ARGB4444 => 0b0100,
320                PixelFormat::L8 => 0b0101,
321                PixelFormat::AL44 => 0b0110,
322                PixelFormat::AL88 => 0b0111,
323                // PixelFormat::L4 => 0b1000, unsupported for now
324                // PixelFormat::A8 => 0b1001,
325                // PixelFormat::A4 => 0b1010
326                // _ => unimplemented!(),
327            })
328        });
329
330        match &layer {
331            Layer::L1 => self.buffer1 = Some(buffer),
332            Layer::L2 => self.buffer2 = Some(buffer),
333        }
334    }
335
336    /// Enable the layer
337    pub fn enable_layer(&self, layer: Layer) {
338        match layer {
339            Layer::L1 => self._ltdc.layer1.cr.modify(|_, w| w.len().set_bit()),
340            Layer::L2 => self._ltdc.layer2.cr.modify(|_, w| w.len().set_bit()),
341        }
342    }
343
344    /// Draw a pixel at position (x,y) on the given layer
345    pub fn draw_pixel(&mut self, layer: Layer, x: usize, y: usize, color: T) {
346        if x >= self.config.active_width as usize || y >= self.config.active_height as usize {
347            panic!("Invalid (x,y) pixel position");
348        }
349
350        match layer {
351            Layer::L1 => {
352                self.buffer1.as_mut().unwrap()[x + self.config.active_width as usize * y] = color
353            }
354            Layer::L2 => {
355                self.buffer2.as_mut().unwrap()[x + self.config.active_width as usize * y] = color
356            }
357        }
358    }
359
360    /// Draw hardware accelerated rectangle
361    ///
362    /// # Safety
363    ///
364    /// TODO: use safer DMA transfers
365    pub unsafe fn draw_rectangle(
366        &mut self,
367        layer: Layer,
368        top_left: (usize, usize),
369        bottom_right: (usize, usize),
370        color: u32,
371    ) {
372        // Output color format
373        self._dma2d.opfccr.write(|w| {
374            w.cm().bits(match &self.pixel_format {
375                PixelFormat::ARGB8888 => 0b000,
376                // PixelFormat::RGB888 => 0b001, unsupported for now
377                PixelFormat::RGB565 => 0b010,
378                PixelFormat::ARGB1555 => 0b011,
379                PixelFormat::ARGB4444 => 0b100,
380                _ => unreachable!(),
381            })
382        });
383
384        // Output color
385        self._dma2d.ocolr.write_with_zero(|w| w.bits(color));
386
387        // Destination memory address
388        let offset: isize = (top_left.0 + self.config.active_width as usize * top_left.1) as isize;
389        self._dma2d.omar.write_with_zero(|w| {
390            w.bits(match &layer {
391                Layer::L1 => self.buffer1.as_ref().unwrap().as_ptr().offset(offset) as u32,
392                Layer::L2 => self.buffer2.as_ref().unwrap().as_ptr().offset(offset) as u32,
393            })
394        });
395
396        // Pixels per line and number of lines
397        self._dma2d.nlr.write(|w| {
398            w.pl()
399                .bits((bottom_right.0 - top_left.0) as u16)
400                .nl()
401                .bits((bottom_right.1 - top_left.1) as u16)
402        });
403
404        // Line offset
405        self._dma2d.oor.write(|w| {
406            w.lo()
407                .bits(top_left.0 as u16 + self.config.active_width - bottom_right.0 as u16)
408        });
409
410        // Start transfert: register to memory mode
411        self._dma2d
412            .cr
413            .modify(|_, w| w.mode().bits(0b11).start().set_bit());
414    }
415
416    /// Reload display controller immediatly
417    pub fn reload(&self) {
418        // Reload ltdc config immediatly
419        self._ltdc.srcr.modify(|_, w| w.imr().set_bit());
420    }
421}
422
423/// Available PixelFormats to work with
424///
425/// Notes :
426/// * `L8`: 8-bit luminance or CLUT
427/// * `AL44`: 4-bit alpha + 4-bit luminance
428/// * `AL88`: 8-bit alpha + 8-bit luminance
429pub enum PixelFormat {
430    ARGB8888,
431    // RGB888(u24) unsupported for now because u24 does not exist
432    RGB565,
433    ARGB1555,
434    ARGB4444,
435    L8,
436    AL44,
437    AL88,
438}
439
440pub trait SupportedWord {}
441impl SupportedWord for u8 {}
442impl SupportedWord for u16 {}
443impl SupportedWord for u32 {}