|
| 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