Skip to content

Commit 0ca17a2

Browse files
grodinomvertescher
authored andcommitted
Added basic support for ltdc peripheral
1 parent 45817ee commit 0ca17a2

File tree

3 files changed

+362
-7
lines changed

3 files changed

+362
-7
lines changed

Cargo.toml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,21 @@ panic-semihosting = "0.5.2"
4646
[features]
4747
device-selected = []
4848
dma-support = []
49+
ltdc = []
4950
rt = ["stm32f7/rt"]
5051
stm32f722 = ["stm32f7/stm32f7x2", "device-selected"]
5152
stm32f723 = ["stm32f7/stm32f7x3", "device-selected"]
5253
stm32f732 = ["stm32f7/stm32f7x2", "device-selected"]
5354
stm32f733 = ["stm32f7/stm32f7x3", "device-selected"]
5455
stm32f745 = ["stm32f7/stm32f745", "device-selected","dma-support"]
55-
stm32f746 = ["stm32f7/stm32f7x6", "device-selected","dma-support"]
56-
stm32f756 = ["stm32f7/stm32f7x6", "device-selected","dma-support"]
56+
stm32f746 = ["stm32f7/stm32f7x6", "device-selected","dma-support","ltdc"]
57+
stm32f756 = ["stm32f7/stm32f7x6", "device-selected","dma-support","ltdc"]
5758
stm32f765 = ["stm32f7/stm32f765", "device-selected","dma-support"]
58-
stm32f767 = ["stm32f7/stm32f7x7", "device-selected","dma-support"]
59-
stm32f769 = ["stm32f7/stm32f7x9", "device-selected","dma-support"]
60-
stm32f777 = ["stm32f7/stm32f7x7", "device-selected","dma-support"]
61-
stm32f778 = ["stm32f7/stm32f7x9", "device-selected","dma-support"]
62-
stm32f779 = ["stm32f7/stm32f7x9", "device-selected","dma-support"]
59+
stm32f767 = ["stm32f7/stm32f7x7", "device-selected","dma-support","ltdc"]
60+
stm32f769 = ["stm32f7/stm32f7x9", "device-selected","dma-support","ltdc"]
61+
stm32f777 = ["stm32f7/stm32f7x7", "device-selected","dma-support","ltdc"]
62+
stm32f778 = ["stm32f7/stm32f7x9", "device-selected","dma-support","ltdc"]
63+
stm32f779 = ["stm32f7/stm32f7x9", "device-selected","dma-support","ltdc"]
6364

6465
[profile.dev]
6566
incremental = false

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ pub mod timer;
9797
#[cfg(feature = "device-selected")]
9898
pub mod signature;
9999

100+
#[cfg(feature = "ltdc")]
101+
pub mod ltdc;
102+
100103
pub mod state {
101104
/// Indicates that a peripheral is enabled
102105
pub struct Enabled;

src/ltdc.rs

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

0 commit comments

Comments
 (0)