From e815b4d314d7d8876e285be7a33672067c750660 Mon Sep 17 00:00:00 2001 From: Michael Fairman Date: Sat, 3 Aug 2024 08:20:07 -0700 Subject: [PATCH] dsi: add color pattern test for STM32F469I-DISC board Initializes the LTDC, DSIHOST and OTM8009A to output alternating color / BER patterns. This does not use a framebuffer so can run without SDRAM. --- CHANGELOG.md | 1 + Cargo.toml | 5 ++ examples/f469disco-lcd-test.rs | 156 +++++++++++++++++++++++++++++++++ src/dsi.rs | 12 +++ 4 files changed, 174 insertions(+) create mode 100644 examples/f469disco-lcd-test.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f29d3711..24bdedbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] + - Add `f469disc-lcd-test` with color/BER test pattern LCD output [#789] ### Added diff --git a/Cargo.toml b/Cargo.toml index addf4e78..2af92c36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ synopsys-usb-otg = { version = "0.4.0", features = [ sdio-host = { version = "0.9.0", optional = true } embedded-dma = "0.2.0" embedded-display-controller = { version = "^0.2.0", optional = true } +otm8009a = "0.1" bare-metal = { version = "1" } void = { default-features = false, version = "1.0.2" } display-interface = { version = "0.5.0", optional = true } @@ -605,6 +606,10 @@ required-features = [ "fsmc_lcd", ] # stm32f413 +[[example]] +name = "f469disco-lcd-test" +required-features = ["stm32f469", "defmt"] + [[example]] name = "hd44780" required-features = [] diff --git a/examples/f469disco-lcd-test.rs b/examples/f469disco-lcd-test.rs new file mode 100644 index 00000000..53f3d9f5 --- /dev/null +++ b/examples/f469disco-lcd-test.rs @@ -0,0 +1,156 @@ +//! This example initializes the STM32F469I-DISCO LCD and displays a test pattern +//! Run as: +//! cargo run --release --example f469disco-lcd-test --features="stm32f469,defmt" + +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate cortex_m; +extern crate cortex_m_rt as rt; + +use cortex_m_rt::entry; + +use defmt_rtt as _; +use panic_probe as _; + +use stm32f4xx_hal as hal; + +use crate::hal::{ + dsi::{ + ColorCoding, DsiChannel, DsiCmdModeTransmissionKind, DsiConfig, DsiHost, DsiInterrupts, + DsiMode, DsiPhyTimers, DsiPllConfig, DsiVideoMode, LaneCount, + }, + ltdc::{DisplayConfig, DisplayController, PixelFormat}, + pac::{CorePeripherals, Peripherals}, + prelude::*, +}; + +use otm8009a::{Otm8009A, Otm8009AConfig}; + +pub const WIDTH: usize = 480; +pub const HEIGHT: usize = 800; + +pub const DISPLAY_CONFIGURATION: DisplayConfig = DisplayConfig { + active_width: WIDTH as _, + active_height: HEIGHT as _, + h_back_porch: 34, + h_front_porch: 34, + v_back_porch: 15, + v_front_porch: 16, + h_sync: 2, + v_sync: 1, + frame_rate: 60, + h_sync_pol: true, + v_sync_pol: true, + no_data_enable_pol: false, + pixel_clock_pol: true, +}; + +#[entry] +fn main() -> ! { + let dp = Peripherals::take().unwrap(); + let cp = CorePeripherals::take().unwrap(); + + let rcc = dp.RCC.constrain(); + + let hse_freq = 8.MHz(); + let clocks = rcc + .cfgr + .use_hse(hse_freq) + .pclk2(32.MHz()) + .sysclk(180.MHz()) + .freeze(); + let mut delay = cp.SYST.delay(&clocks); + + let gpioh = dp.GPIOH.split(); + + // Reset display + let mut lcd_reset = gpioh.ph7.into_push_pull_output(); + lcd_reset.set_low(); + delay.delay_ms(20u32); + lcd_reset.set_high(); + delay.delay_ms(10u32); + + // Initialize LTDC, needed to provide pixel clock to DSI + defmt::info!("Initializing LTDC"); + let ltdc_freq = 27_429.kHz(); + let _display = DisplayController::::new( + dp.LTDC, + dp.DMA2D, + None, + PixelFormat::ARGB8888, + DISPLAY_CONFIGURATION, + Some(hse_freq), + ); + + // Initialize DSI Host + // VCO = (8MHz HSE / 2 IDF) * 2 * 125 = 1000MHz + // 1000MHz VCO / (2 * 1 ODF * 8) = 62.5MHz + let dsi_pll_config = unsafe { + DsiPllConfig::manual(125, 2, 0 /*div1*/, 4) + }; + + let dsi_config = DsiConfig { + mode: DsiMode::Video { + mode: DsiVideoMode::Burst, + }, + lane_count: LaneCount::DoubleLane, + channel: DsiChannel::Ch0, + hse_freq, + ltdc_freq, + interrupts: DsiInterrupts::None, + color_coding_host: ColorCoding::TwentyFourBits, + color_coding_wrapper: ColorCoding::TwentyFourBits, + lp_size: 4, + vlp_size: 4, + }; + + defmt::info!("Initializing DSI {:?} {:?}", dsi_config, dsi_pll_config); + let mut dsi_host = DsiHost::init( + dsi_pll_config, + DISPLAY_CONFIGURATION, + dsi_config, + dp.DSI, + &clocks, + ) + .unwrap(); + + dsi_host.configure_phy_timers(DsiPhyTimers { + dataline_hs2lp: 35, + dataline_lp2hs: 35, + clock_hs2lp: 35, + clock_lp2hs: 35, + dataline_max_read_time: 0, + stop_wait_time: 10, + }); + + dsi_host.set_command_mode_transmission_kind(DsiCmdModeTransmissionKind::AllInLowPower); + dsi_host.start(); + dsi_host.enable_bus_turn_around(); // Must be before read attempts + dsi_host.set_command_mode_transmission_kind(DsiCmdModeTransmissionKind::AllInHighSpeed); + dsi_host.force_rx_low_power(true); + dsi_host.enable_color_test(); // Must enable before display initialized + + defmt::info!("Initializing OTM8009A"); + let otm8009a_config = Otm8009AConfig { + frame_rate: otm8009a::FrameRate::_60Hz, + mode: otm8009a::Mode::Portrait, + color_map: otm8009a::ColorMap::Rgb, + cols: WIDTH as u16, + rows: HEIGHT as u16, + }; + let mut otm8009a = Otm8009A::new(); + otm8009a + .init(&mut dsi_host, otm8009a_config, &mut delay) + .unwrap(); + + defmt::info!("Outputting Color/BER test patterns..."); + let delay_ms = 5000u32; + loop { + dsi_host.enable_color_test(); + delay.delay_ms(delay_ms); + dsi_host.enable_ber_test(); + delay.delay_ms(delay_ms); + } +} diff --git a/src/dsi.rs b/src/dsi.rs index 77d36f40..87dbcddb 100644 --- a/src/dsi.rs +++ b/src/dsi.rs @@ -555,6 +555,18 @@ impl DsiHost { pub fn enable_bus_turn_around(&mut self) { self.dsi.pcr().modify(|_, w| w.btae().set_bit()); // Enable bus turn around } + + pub fn enable_color_test(&mut self) { + self.dsi + .vmcr() + .modify(|_, w| w.pge().set_bit().pgm().clear_bit()); + } + + pub fn enable_ber_test(&mut self) { + self.dsi + .vmcr() + .modify(|_, w| w.pge().set_bit().pgm().set_bit()); + } } impl DsiRefreshHandle {