From 7e8664a5a51e31d82f80c9caec23e7b826296100 Mon Sep 17 00:00:00 2001 From: Roman Isaikin Date: Fri, 1 Dec 2023 10:42:22 +0100 Subject: [PATCH 1/8] Implement DSI HAL. --- Cargo.toml | 18 +- ...y-dsi-command-teartest-stm32h747i-disco.rs | 372 +++++++++ .../display-dsi-video-stm32h747i-disco.rs | 352 +++++++++ ...lay-dsi-video-teartest-stm32h747i-disco.rs | 371 +++++++++ examples/embedded-graphics.rs | 129 +--- examples/utilities/display_primitives.rs | 166 ++++ examples/utilities/display_target.rs | 139 ++++ examples/utilities/logger.rs | 4 +- examples/utilities/mod.rs | 10 + examples/utilities/mpu_config.rs | 77 ++ examples/utilities/write.rs | 54 ++ src/delay.rs | 2 +- src/dsi.rs | 707 ++++++++++++++++++ src/lib.rs | 2 + 14 files changed, 2272 insertions(+), 131 deletions(-) create mode 100644 examples/display-dsi-command-teartest-stm32h747i-disco.rs create mode 100644 examples/display-dsi-video-stm32h747i-disco.rs create mode 100644 examples/display-dsi-video-teartest-stm32h747i-disco.rs create mode 100644 examples/utilities/display_primitives.rs create mode 100644 examples/utilities/display_target.rs create mode 100644 examples/utilities/mpu_config.rs create mode 100644 examples/utilities/write.rs create mode 100644 src/dsi.rs diff --git a/Cargo.toml b/Cargo.toml index 9e2370b4..a9436156 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ embedded-display-controller = { version = "^0.1.0", optional = true } log = { version = "0.4.14", optional = true} # see also the dev-dependencies section fdcan = { version = "0.2", optional = true } embedded-storage = "0.3" +embedded-dsi = { path = "../embedded-hal/embedded-dsi", optional = true } [dependencies.smoltcp] version = "0.10.0" @@ -81,6 +82,9 @@ usbd-serial = "0.1.0" numtoa = "0.2.3" tinybmp = "0.5" embedded-graphics = "0.8" +otm8009a = { path = "../otm8009a" } +eg-seven-segment = "0.2.0" +ft6236 = { git = "https://github.com/romixlab/ft6236.git" } [dev-dependencies.smoltcp] version = "0.10.0" @@ -100,7 +104,7 @@ gpio-h72 = [] gpio-h747 = [] gpio-h7a2 = [] -dsi = [] +dsi = ["embedded-dsi", "embedded-display-controller"] cm4 = [] cm7 = [] smps = [] @@ -262,3 +266,15 @@ required-features = ["rt", "usb_hs", "rm0433"] [[example]] name = "vos0" required-features = ["revision_v"] + +[[example]] +name = "display-dsi-video-stm32h747i-disco" +required-features = ["dsi", "ltdc", "fmc"] + +[[example]] +name = "display-dsi-video-teartest-stm32h747i-disco" +required-features = ["dsi", "ltdc", "fmc"] + +[[example]] +name = "display-dsi-command-teartest-stm32h747i-disco" +required-features = ["dsi", "ltdc", "fmc"] diff --git a/examples/display-dsi-command-teartest-stm32h747i-disco.rs b/examples/display-dsi-command-teartest-stm32h747i-disco.rs new file mode 100644 index 00000000..6b642729 --- /dev/null +++ b/examples/display-dsi-command-teartest-stm32h747i-disco.rs @@ -0,0 +1,372 @@ +//! This example uses the embedded-graphics library to draw fast moving objects on +//! an external display. The external display is connected through the DSI link. +//! DSI Adapted command mode is used to refresh the display only when needed. +//! +//! When using the display in Portrait mode, no diagonal tearing line should be visible. +//! +//! While in Landscape mode diagonal line is visible, because display is still refreshed +//! in 480px lines from display's own graphics RAM, while writes are now 800px high. +//! +//! Run command: cargo embed --release --features="stm32h747cm7,dsi,log,ltdc,fmc,rm0399,smps,example-smps,log-rtt,rt,rtc" --example display-dsi-command-teartest-stm32h747i-disco +//! +//! Tested on a STM32H747I-DISCO development board with a ST MB1166 Display +//! (supplied together with the development kit). +//! Display Controller: OTM8009A, LCD: KJD KM-040TMP-02, Frida FRD397B2509 + +// #![deny(warnings)] +#![no_main] +#![no_std] + +use core::{mem, slice}; + +#[macro_use] +mod utilities; +use log::info; +use otm8009a::Otm8009AConfig; +use stm32h7xx_hal::dsi::{ColorCoding, DsiChannel, DsiConfig, DsiPllConfig}; + +extern crate cortex_m; +extern crate cortex_m_rt as rt; +use cortex_m_rt::{entry, exception}; + +use crate::utilities::display_target::BufferedDisplay; +use stm32h7xx_hal::gpio::Speed; +use stm32h7xx_hal::ltdc; +use stm32h7xx_hal::stm32::rcc::d1ccipr::FMCSEL_A; +use stm32h7xx_hal::{prelude::*, stm32}; + +use embedded_display_controller::DisplayController; + +use embedded_graphics::pixelcolor::Rgb888; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::{Circle, PrimitiveStyleBuilder, Rectangle}; + +use embedded_display_controller::DisplayConfiguration; +use otm8009a::Otm8009A; +use stm32h7xx_hal::dsi::{ + DsiCmdModeTransmissionKind, DsiHost, DsiInterrupts, DsiMode, DsiPhyTimers, + DsiVideoMode, LaneCount, +}; +use ft6236::{FT6236}; +use crate::utilities::mpu_config::init_mpu; +use crate::utilities::write::write_to::WriteTo; +use core::fmt::Write; +use stm32h7xx_hal::rcc::PllConfigStrategy; +use crate::utilities::display_primitives::{colored_label, display_test}; + +// Remember to use correct display controller orientation, Portrait in this case +pub const WIDTH: usize = 480; +pub const HEIGHT: usize = 800; + +pub const DISPLAY_CONFIGURATION: DisplayConfiguration = DisplayConfiguration { + 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, + h_sync_pol: false, + v_sync_pol: false, + not_data_enable_pol: false, + pixel_clock_pol: false, +}; + +/// Configure a pin for the FMC controller +macro_rules! fmc_pins { + ($($pin:expr),*) => { + ( + $( + $pin.into_push_pull_output() + .speed(Speed::VeryHigh) + .into_alternate::<12>() + .internal_pull_up(true) + ),* + ) + }; +} + +#[entry] +fn main() -> ! { + utilities::logger::init(); + + let dp = stm32::Peripherals::take().unwrap(); + let mut cp = stm32::CorePeripherals::take().unwrap(); + + // Constrain and Freeze power + info!("Setup PWR..."); + let pwr = dp.PWR.constrain(); + let pwrcfg = example_power!(pwr).vos0(&dp.SYSCFG).freeze(); + + // Constrain and Freeze clock + info!("Setup RCC..."); + let rcc = dp.RCC.constrain(); + + // Important for DSI PLL to configure this correctly. + // Disco board uses an oscillator while Eval - a crystal. + let hse_freq = 25.MHz(); + let rcc = rcc.use_hse(hse_freq).bypass_hse(); + // Any clock should work in adapted command mode, the faster the better. + let ltdc_freq = 42_000.kHz(); + + let ccdr = rcc + .sys_ck(400.MHz()) + // FMC will run at 100MHz, as this clock is further divided by 2 + .pll2_p_ck(200.MHz()) + // .pll2_q_ck(200.MHz()) + .pll2_r_ck(200.MHz()) + .pll2_strategy(PllConfigStrategy::Iterative) + // LTDC + .pll3_p_ck(400.MHz()) + .pll3_q_ck(400.MHz()) + .pll3_r_ck(ltdc_freq) + .pll3_strategy(PllConfigStrategy::Iterative) + .freeze(pwrcfg, &dp.SYSCFG); + + // Get frequency of LTDC pixel clock + info!("pll3_r_ck: {:?}", ccdr.clocks.pll3_r_ck()); + let _pll3_r = ccdr.clocks.pll3_r_ck().expect("pll3 must run!"); + + // Get the delay provider. + let mut delay = cp.SYST.delay(ccdr.clocks); + + // Initialise system... + cp.SCB.invalidate_icache(); + cp.SCB.enable_icache(); + //cp.SCB.enable_dcache(&mut cp.CPUID); // TODO invalidate dcache when writing to the display + cp.DWT.enable_cycle_counter(); + + // Initialise IO... + // let gpioa = dp.GPIOA.split(ccdr.peripheral.GPIOA); + // let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB); + // let gpioc = dp.GPIOC.split(ccdr.peripheral.GPIOC); + let gpiod = dp.GPIOD.split(ccdr.peripheral.GPIOD); + let gpioe = dp.GPIOE.split(ccdr.peripheral.GPIOE); + let gpiof = dp.GPIOF.split(ccdr.peripheral.GPIOF); + let gpiog = dp.GPIOG.split(ccdr.peripheral.GPIOG); + let gpioh = dp.GPIOH.split(ccdr.peripheral.GPIOH); + let gpioi = dp.GPIOI.split(ccdr.peripheral.GPIOI); + let gpioj = dp.GPIOJ.split(ccdr.peripheral.GPIOJ); + + let _syscfg = dp.SYSCFG; + let _exti = dp.EXTI; + + // MPU config for SDRAM write-through + let sdram_size = 32 * 1024 * 1024; + init_mpu(cp.MPU, &mut cp.SCB, sdram_size); + + // pin setup for SDRAM + let sdram_pins = fmc_pins! { + // A0-A11 + gpiof.pf0, gpiof.pf1, gpiof.pf2, gpiof.pf3, + gpiof.pf4, gpiof.pf5, gpiof.pf12, gpiof.pf13, + gpiof.pf14, gpiof.pf15, gpiog.pg0, gpiog.pg1, + // BA0-BA1 + gpiog.pg4, gpiog.pg5, + // D0-D31 + gpiod.pd14, gpiod.pd15, gpiod.pd0, gpiod.pd1, + gpioe.pe7, gpioe.pe8, gpioe.pe9, gpioe.pe10, + gpioe.pe11, gpioe.pe12, gpioe.pe13, gpioe.pe14, + gpioe.pe15, gpiod.pd8, gpiod.pd9, gpiod.pd10, + gpioh.ph8, gpioh.ph9, gpioh.ph10, gpioh.ph11, + gpioh.ph12, gpioh.ph13, gpioh.ph14, gpioh.ph15, + gpioi.pi0, gpioi.pi1, gpioi.pi2, gpioi.pi3, + gpioi.pi6, gpioi.pi7, gpioi.pi9, gpioi.pi10, + // NBL0 - NBL3 + gpioe.pe0, gpioe.pe1, gpioi.pi4, gpioi.pi5, + gpioh.ph7, // SDCKE1 + gpiog.pg8, // SDCLK + gpiog.pg15, // SDNCAS + gpioh.ph6, // SDNE1 (!CS) + gpiof.pf11, // SDRAS + gpioh.ph5 // SDNWE + }; + + let fmc_ccdr = ccdr.peripheral.FMC.kernel_clk_mux(FMCSEL_A::Pll2R); + // TODO: incorrect for disco! + let sdram_chip = stm32_fmc::devices::is42s32800g_6::Is42s32800g {}; + let mut sdram = dp.FMC.sdram( + sdram_pins, + sdram_chip, + // ccdr.peripheral.FMC, + fmc_ccdr, + &ccdr.clocks, + ); + + let (fb1, fb2) = unsafe { + // Initialise controller and SDRAM + let ram_ptr: *mut u32 = sdram.init(&mut delay); + slice::from_raw_parts_mut(ram_ptr, sdram_size / mem::size_of::()) + .fill(0); + + let fb_size = WIDTH * HEIGHT; + let bank_size_words = 8 * 1024 * 1024 / 4; + let fb1 = slice::from_raw_parts_mut(ram_ptr, fb_size); + // Offset the second buffer into another SDRAM bank - this saves a bit of time if DMA2D is used + // to clear the fb while update is ongoing. + let fb2 = + slice::from_raw_parts_mut(ram_ptr.offset(bank_size_words), fb_size); + (fb1, fb2) + }; + + info!("Initialised SDRAM..."); + + // Initialise LCD... + // Display controller reset through dedicated IO + let mut display_reset = gpiog.pg3.into_push_pull_output(); + display_reset.set_low(); + delay.delay_ms(20u32); + display_reset.set_high(); + delay.delay_ms(10u32); + + // Display backlight enable + let mut display_backlight_en = gpioj.pj12.into_push_pull_output(); + display_backlight_en.set_high(); + + // Display controller TE (hw tear effect sync) pin as input + // let _display_te = gpioj.pj2.into_alternate::<13>(); + let _display_te = gpioj.pj2.into_input(); + // display_te.make_interrupt_source(&mut syscfg); + // display_te.trigger_on_edge(&mut exti, Edge::Rising); + // display_te.enable_interrupt(&mut exti); + + let mut ltdc = ltdc::Ltdc::new(dp.LTDC, ccdr.peripheral.LTDC, &ccdr.clocks); + ltdc.init(DISPLAY_CONFIGURATION); + + let layer = ltdc.split(); + let mut disp = BufferedDisplay::new(layer, fb1, fb2, WIDTH, HEIGHT); + + // Fin = 25MHz ->/idf = 5MHz ->*2 = 10MHz ->*ndiv = 1GHz ->/2 = 500MHz ->/odf = 500MHz (500Mbps per lane); pix clk (/8) = 62.5MHz + let dsi_pll_config = unsafe { DsiPllConfig::manual(100, 5, 0, 4) }; + + let dsi_config = DsiConfig { + mode: DsiMode::AdaptedCommand { + tear_effect: None, + }, + 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, // for OTM8009A + vlp_size: 4, + }; + let mut dsi_host = DsiHost::init( + dsi_pll_config, + DISPLAY_CONFIGURATION, + dsi_config, + dp.DSIHOST, + ccdr.peripheral.DSI, + &ccdr.clocks, + ) + .expect("DSI host failed to init"); + dsi_host.set_command_mode_transmission_kind( + DsiCmdModeTransmissionKind::AllInLowPower, + ); + + // Enable DSI host + dsi_host.start(); + dsi_host.enable_bus_turn_around(); // Must be before read attempts + + 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, + }); + + let otm8009a_config = Otm8009AConfig { + frame_rate: otm8009a::FrameRate::_70Hz, + // NOTE: In Landscape mode diagonal tearing line will be visible when fast changing content is present + mode: otm8009a::Mode::Portrait, + color_map: otm8009a::ColorMap::Rgb, + cols: WIDTH as u16, + rows: HEIGHT as u16, + }; + let mut glass_ctrl = Otm8009A::new(); + glass_ctrl + .init(&mut dsi_host, otm8009a_config, &mut delay) + .unwrap(); + glass_ctrl.enable_te_output(533, &mut dsi_host).unwrap(); + + // Not sure if this is needed + dsi_host.set_command_mode_transmission_kind( + DsiCmdModeTransmissionKind::AllInHighSpeed, + ); + dsi_host.force_rx_low_power(true); + + let mut dsi_refresh_handle = dsi_host.refresh_handle(); + info!("Initialised Display..."); + + let scl = gpiod.pd12.into_alternate_open_drain(); + let sda = gpiod.pd13.into_alternate_open_drain(); + let i2c4 = + dp.I2C4 + .i2c((scl, sda), 100.kHz(), ccdr.peripheral.I2C4, &ccdr.clocks); + let mut touch_ctrl = FT6236::new(i2c4); + // let touch_int = gpiok.pk7 + + let mut x = 0; + let mut y = 0; + let mut frame = 0; + let style_green = PrimitiveStyleBuilder::new() + .fill_color(Rgb888::GREEN) + .build(); + let mut buf = [0u8; 64]; + + loop { + // Draw on a double buffered display + disp.layer(|draw| { + draw.clear(); + + Rectangle::new(Point::new(x, 0), Size::new(100, HEIGHT as u32)) + .into_styled(style_green) + .draw(draw) + .unwrap(); + + Rectangle::new(Point::new(0, y), Size::new(WIDTH as u32, 100)) + .into_styled(style_green) + .draw(draw) + .unwrap(); + + x += 5; + y += 5; + if x >= WIDTH as i32 { + x = 0; + } + if y >= HEIGHT as i32 { + y = 0; + } + + let mut buf = WriteTo::new(&mut buf); + write!(&mut buf, "f: {frame}").unwrap(); + frame += 1; + colored_label(buf.as_str().unwrap(), 50, 20, Rgb888::RED, draw).unwrap(); + + if let Ok(Some(pt)) = touch_ctrl.get_point0() { + info!("Touch: {} {}", pt.y, 480 - pt.x); + Circle::new(Point::new(480 - pt.x as i32 - 25, pt.y as i32 - 25), 50).into_styled(style_green).draw(draw).unwrap(); + } + + display_test(draw).unwrap(); + }); + disp.swap_layer_wait(); + dsi_refresh_handle.refresh_now(); + delay.delay_ms(500u32); + } +} + +#[exception] +unsafe fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! { + panic!("HardFault at {:#?}", ef); +} + +#[exception] +unsafe fn DefaultHandler(irqn: i16) { + panic!("Unhandled exception (IRQn = {})", irqn); +} \ No newline at end of file diff --git a/examples/display-dsi-video-stm32h747i-disco.rs b/examples/display-dsi-video-stm32h747i-disco.rs new file mode 100644 index 00000000..65ee0471 --- /dev/null +++ b/examples/display-dsi-video-stm32h747i-disco.rs @@ -0,0 +1,352 @@ +//! This example uses the embedded-graphics library to draw text and an image on +//! an external display. The external display is connected through the DSI link. +//! DSI Video mode is used, so the display is constantly refreshed by hardware. +//! +//! Run command: cargo embed --release --features="stm32h747cm7,dsi,log,ltdc,fmc,rm0399,smps,example-smps,log-rtt,rt,rtc" --example display-dsi-video-stm32h747i-disco +//! +//! Tested on a STM32H747I-DISCO development board with a ST MB1166 Display +//! (supplied together with the development kit). +//! Display Controller: OTM8009A, LCD: KJD KM-040TMP-02, Frida FRD397B2509 + +#![deny(warnings)] +#![no_main] +#![no_std] + +use core::{mem, slice}; + +#[macro_use] +mod utilities; +use log::info; +use otm8009a::Otm8009AConfig; +use stm32h7xx_hal::dsi::{ColorCoding, DsiChannel, DsiConfig, DsiPllConfig}; + +extern crate cortex_m; +extern crate cortex_m_rt as rt; +use cortex_m_rt::{entry, exception}; + +use crate::utilities::display_target::BufferedDisplay; +use stm32h7xx_hal::gpio::Speed; +use stm32h7xx_hal::ltdc; +use stm32h7xx_hal::stm32::rcc::d1ccipr::FMCSEL_A; +use stm32h7xx_hal::{prelude::*, rtc, stm32}; + +use embedded_display_controller::DisplayController; + +use chrono::{NaiveDateTime, NaiveTime}; +use embedded_graphics::pixelcolor::Rgb888; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::{Circle, PrimitiveStyleBuilder}; + +use embedded_display_controller::DisplayConfiguration; +use otm8009a::Otm8009A; +use stm32h7xx_hal::dsi::{ + DsiCmdModeTransmissionKind, DsiHost, DsiInterrupts, DsiMode, DsiPhyTimers, + DsiVideoMode, LaneCount, +}; +use ft6236::{FT6236}; +use crate::utilities::display_primitives::time_circuit; +use crate::utilities::mpu_config::init_mpu; + +pub const WIDTH: usize = 800; +pub const HEIGHT: usize = 480; + +pub const DISPLAY_CONFIGURATION: DisplayConfiguration = DisplayConfiguration { + 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, + h_sync_pol: false, + v_sync_pol: false, + not_data_enable_pol: false, + pixel_clock_pol: false, +}; + +/// Configure a pin for the FMC controller +macro_rules! fmc_pins { + ($($pin:expr),*) => { + ( + $( + $pin.into_push_pull_output() + .speed(Speed::VeryHigh) + .into_alternate::<12>() + .internal_pull_up(true) + ),* + ) + }; +} + +#[entry] +fn main() -> ! { + utilities::logger::init(); + + let dp = stm32::Peripherals::take().unwrap(); + let mut cp = stm32::CorePeripherals::take().unwrap(); + + // Constrain and Freeze power + info!("Setup PWR..."); + let pwr = dp.PWR.constrain(); + let mut pwrcfg = example_power!(pwr).vos0(&dp.SYSCFG).freeze(); + let backup = pwrcfg.backup().unwrap(); + + // Constrain and Freeze clock + info!("Setup RCC..."); + let rcc = dp.RCC.constrain(); + + // Important for DSI PLL to configure this correctly. + // Disco board uses an oscillator while Eval - a crystal. + let hse_freq = 25.MHz(); + let rcc = rcc.use_hse(hse_freq).bypass_hse(); + // Precisely crafted clock in video mode + let ltdc_freq = 27_429.kHz(); + + let ccdr = rcc + .sys_ck(400.MHz()) + // Octo SPI + .pll2_p_ck(400.MHz() / 5) + .pll2_q_ck(400.MHz() / 2) + .pll2_r_ck(400.MHz() / 2) + // LTDC + .pll3_p_ck(330.MHz()) + .pll3_q_ck(330.MHz()) + .pll3_r_ck(ltdc_freq) + .freeze(pwrcfg, &dp.SYSCFG); + + // Get frequency of LTDC pixel clock + info!("pll3_r_ck: {:?}", ccdr.clocks.pll3_r_ck()); + let _pll3_r = ccdr.clocks.pll3_r_ck().expect("pll3 must run!"); + + let mut rtc = rtc::Rtc::open_or_init( + dp.RTC, + backup.RTC, + rtc::RtcClock::Lsi, + &ccdr.clocks, + ); + + use chrono::NaiveDate; + let now = NaiveDate::from_ymd_opt(2023, 11, 24) + .unwrap() + .and_hms_opt(12, 54, 0) + .unwrap(); + rtc.set_date_time(now); + + // Get the delay provider. + let mut delay = cp.SYST.delay(ccdr.clocks); + + // Initialise system... + cp.SCB.invalidate_icache(); + cp.SCB.enable_icache(); + //cp.SCB.enable_dcache(&mut cp.CPUID); // TODO invalidate dcache when writing to the display + cp.DWT.enable_cycle_counter(); + + // Initialise IO... + // let gpioa = dp.GPIOA.split(ccdr.peripheral.GPIOA); + // let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB); + // let gpioc = dp.GPIOC.split(ccdr.peripheral.GPIOC); + let gpiod = dp.GPIOD.split(ccdr.peripheral.GPIOD); + let gpioe = dp.GPIOE.split(ccdr.peripheral.GPIOE); + let gpiof = dp.GPIOF.split(ccdr.peripheral.GPIOF); + let gpiog = dp.GPIOG.split(ccdr.peripheral.GPIOG); + let gpioh = dp.GPIOH.split(ccdr.peripheral.GPIOH); + let gpioi = dp.GPIOI.split(ccdr.peripheral.GPIOI); + let gpioj = dp.GPIOJ.split(ccdr.peripheral.GPIOJ); + + let _syscfg = dp.SYSCFG; + let _exti = dp.EXTI; + + // MPU config for SDRAM write-through + let sdram_size = 32 * 1024 * 1024; + init_mpu(cp.MPU, &mut cp.SCB, sdram_size); + + // pin setup for SDRAM + let sdram_pins = fmc_pins! { + // A0-A11 + gpiof.pf0, gpiof.pf1, gpiof.pf2, gpiof.pf3, + gpiof.pf4, gpiof.pf5, gpiof.pf12, gpiof.pf13, + gpiof.pf14, gpiof.pf15, gpiog.pg0, gpiog.pg1, + // BA0-BA1 + gpiog.pg4, gpiog.pg5, + // D0-D31 + gpiod.pd14, gpiod.pd15, gpiod.pd0, gpiod.pd1, + gpioe.pe7, gpioe.pe8, gpioe.pe9, gpioe.pe10, + gpioe.pe11, gpioe.pe12, gpioe.pe13, gpioe.pe14, + gpioe.pe15, gpiod.pd8, gpiod.pd9, gpiod.pd10, + gpioh.ph8, gpioh.ph9, gpioh.ph10, gpioh.ph11, + gpioh.ph12, gpioh.ph13, gpioh.ph14, gpioh.ph15, + gpioi.pi0, gpioi.pi1, gpioi.pi2, gpioi.pi3, + gpioi.pi6, gpioi.pi7, gpioi.pi9, gpioi.pi10, + // NBL0 - NBL3 + gpioe.pe0, gpioe.pe1, gpioi.pi4, gpioi.pi5, + gpioh.ph7, // SDCKE1 + gpiog.pg8, // SDCLK + gpiog.pg15, // SDNCAS + gpioh.ph6, // SDNE1 (!CS) + gpiof.pf11, // SDRAS + gpioh.ph5 // SDNWE + }; + + let fmc_ccdr = ccdr.peripheral.FMC.kernel_clk_mux(FMCSEL_A::Pll2R); + // TODO: incorrect for disco! + let sdram_chip = stm32_fmc::devices::is42s32800g_6::Is42s32800g {}; + let mut sdram = dp.FMC.sdram( + sdram_pins, + sdram_chip, + // ccdr.peripheral.FMC, + fmc_ccdr, + &ccdr.clocks, + ); + + let (fb1, fb2) = unsafe { + // Initialise controller and SDRAM + let ram_ptr: *mut u32 = sdram.init(&mut delay); + slice::from_raw_parts_mut(ram_ptr, sdram_size / mem::size_of::()) + .fill(0); + + let fb_size = WIDTH * HEIGHT; + let bank_size_words = 8 * 1024 * 1024 / 4; + let fb1 = slice::from_raw_parts_mut(ram_ptr, fb_size); + // Offset the second buffer into another SDRAM bank - this saves a bit of time if DMA2D is used + // to clear the fb while update is ongoing. + let fb2 = + slice::from_raw_parts_mut(ram_ptr.offset(bank_size_words), fb_size); + (fb1, fb2) + }; + + info!("Initialised SDRAM..."); + + // Initialise LCD... + // Display controller reset through dedicated IO + let mut display_reset = gpiog.pg3.into_push_pull_output(); + display_reset.set_low(); + delay.delay_ms(20u32); + display_reset.set_high(); + delay.delay_ms(10u32); + + // Display backlight enable + let mut display_backlight_en = gpioj.pj12.into_push_pull_output(); + display_backlight_en.set_high(); + + // Display controller TE (hw tear effect sync) pin as input + // let _display_te = gpioj.pj2.into_alternate::<13>(); + let _display_te = gpioj.pj2.into_input(); + // display_te.make_interrupt_source(&mut syscfg); + // display_te.trigger_on_edge(&mut exti, Edge::Rising); + // display_te.enable_interrupt(&mut exti); + + let mut ltdc = ltdc::Ltdc::new(dp.LTDC, ccdr.peripheral.LTDC, &ccdr.clocks); + ltdc.init(DISPLAY_CONFIGURATION); + + let layer = ltdc.split(); + let mut disp = BufferedDisplay::new(layer, fb1, fb2, WIDTH, HEIGHT); + + // Fin = 25MHz ->/idf = 5MHz ->*2 = 10MHz ->*ndiv = 1GHz ->/2 = 500MHz ->/odf = 500MHz (500Mbps per lane); pix clk (/8) = 62.5MHz + let dsi_pll_config = unsafe { DsiPllConfig::manual(100, 5, 0, 4) }; + + let dsi_config = DsiConfig { + mode: DsiMode::Video { + // mode: DsiVideoMode::NonBurstWithSyncEvents, + 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, // for OTM8009A + vlp_size: 4, + }; + let mut dsi_host = DsiHost::init( + dsi_pll_config, + DISPLAY_CONFIGURATION, + dsi_config, + dp.DSIHOST, + ccdr.peripheral.DSI, + &ccdr.clocks, + ) + .expect("DSI host failed to init"); + dsi_host.set_command_mode_transmission_kind( + DsiCmdModeTransmissionKind::AllInLowPower, + ); + + // Enable DSI host + dsi_host.start(); + dsi_host.enable_bus_turn_around(); // Must be before read attempts + + 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, + }); + + let otm8009a_config = Otm8009AConfig { + frame_rate: otm8009a::FrameRate::_70Hz, + // NOTE: In Landscape mode diagonal tearing line will be visible when fast changing content is present + mode: otm8009a::Mode::Landscape, + color_map: otm8009a::ColorMap::Rgb, + cols: WIDTH as u16, + rows: HEIGHT as u16, + }; + let mut glass_ctrl = Otm8009A::new(); + glass_ctrl + .init(&mut dsi_host, otm8009a_config, &mut delay) + .unwrap(); + glass_ctrl.enable_te_output(533, &mut dsi_host).unwrap(); + + // Not sure if this is needed + dsi_host.set_command_mode_transmission_kind( + DsiCmdModeTransmissionKind::AllInHighSpeed, + ); + dsi_host.force_rx_low_power(true); + + //let mut dsi_refresh_handle = dsi_host.refresh_handle(); + info!("Initialised Display..."); + + let scl = gpiod.pd12.into_alternate_open_drain(); + let sda = gpiod.pd13.into_alternate_open_drain(); + let i2c4 = + dp.I2C4 + .i2c((scl, sda), 100.kHz(), ccdr.peripheral.I2C4, &ccdr.clocks); + let mut touch_ctrl = FT6236::new(i2c4); + // let touch_int = gpiok.pk7 + + let style_green = PrimitiveStyleBuilder::new() + .fill_color(Rgb888::GREEN) + .build(); + + loop { + // Draw on a double buffered display + disp.layer(|draw| { + draw.clear(); + + let tc_x = 54; + time_circuit(NaiveDateTime::new(NaiveDate::from_ymd_opt(1985, 10, 26).unwrap(), NaiveTime::from_hms_opt(01, 21, 0).unwrap()), tc_x, 100, "DESTINATION TIME", Rgb888::CSS_ORANGE_RED, Rgb888::new(102, 27, 0), draw).unwrap(); + let now = rtc.date_time().unwrap(); + time_circuit(now, tc_x, 250, "PRESENT TIME", Rgb888::CSS_LIME_GREEN, Rgb888::new(15, 64, 15), draw).unwrap(); + time_circuit(NaiveDateTime::new(NaiveDate::from_ymd_opt(1985, 10, 26).unwrap(), NaiveTime::from_hms_opt(01, 20, 0).unwrap()), tc_x, 400, "LAST TIME DEPARTED", Rgb888::CSS_ORANGE, Rgb888::new(77, 42, 0), draw).unwrap(); + + if let Ok(Some(pt)) = touch_ctrl.get_point0() { + info!("Touch: {} {}", pt.y, 480 - pt.x); + Circle::new(Point::new(pt.y as i32 - 25, 480 - pt.x as i32 - 25), 50).into_styled(style_green).draw(draw).unwrap(); + } + }); + disp.swap_layer_wait(); + } +} + +#[exception] +unsafe fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! { + panic!("HardFault at {:#?}", ef); +} + +#[exception] +unsafe fn DefaultHandler(irqn: i16) { + panic!("Unhandled exception (IRQn = {})", irqn); +} \ No newline at end of file diff --git a/examples/display-dsi-video-teartest-stm32h747i-disco.rs b/examples/display-dsi-video-teartest-stm32h747i-disco.rs new file mode 100644 index 00000000..0d249daa --- /dev/null +++ b/examples/display-dsi-video-teartest-stm32h747i-disco.rs @@ -0,0 +1,371 @@ +//! This example uses the embedded-graphics library to draw fast moving objects on +//! an external display. The external display is connected through the DSI link. +//! DSI Video mode is used, so the display is constantly refreshed by hardware. +//! +//! When using the display in Portrait mode, no diagonal tearing line should be visible. +//! +//! While in Landscape mode diagonal line is visible, because display is still refreshed +//! in 480px lines from display's own graphics RAM, while writes are now 800px high. +//! +//! Run command: cargo embed --release --features="stm32h747cm7,dsi,log,ltdc,fmc,rm0399,smps,example-smps,log-rtt,rt,rtc" --example display-dsi-video-teartest-stm32h747i-disco +//! +//! Tested on a STM32H747I-DISCO development board with a ST MB1166 Display +//! (supplied together with the development kit). +//! Display Controller: OTM8009A, LCD: KJD KM-040TMP-02, Frida FRD397B2509 + +#![deny(warnings)] +#![no_main] +#![no_std] + +use core::{mem, slice}; + +#[macro_use] +mod utilities; +use log::info; +use otm8009a::Otm8009AConfig; +use stm32h7xx_hal::dsi::{ColorCoding, DsiChannel, DsiConfig, DsiPllConfig}; + +extern crate cortex_m; +extern crate cortex_m_rt as rt; +use cortex_m_rt::{entry, exception}; + +use crate::utilities::display_target::BufferedDisplay; +use stm32h7xx_hal::gpio::Speed; +use stm32h7xx_hal::ltdc; +use stm32h7xx_hal::stm32::rcc::d1ccipr::FMCSEL_A; +use stm32h7xx_hal::{prelude::*, stm32}; + +use embedded_display_controller::DisplayController; + +use embedded_graphics::pixelcolor::Rgb888; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::{Circle, PrimitiveStyleBuilder, Rectangle}; + +use embedded_display_controller::DisplayConfiguration; +use otm8009a::Otm8009A; +use stm32h7xx_hal::dsi::{ + DsiCmdModeTransmissionKind, DsiHost, DsiInterrupts, DsiMode, DsiPhyTimers, + DsiVideoMode, LaneCount, +}; +use ft6236::{FT6236}; +use crate::utilities::mpu_config::init_mpu; +use crate::utilities::write::write_to::WriteTo; +use core::fmt::Write; +use stm32h7xx_hal::rcc::PllConfigStrategy; +use crate::utilities::display_primitives::{colored_label, display_test}; + +// Remember to use correct display controller orientation, Portrait in this case +pub const WIDTH: usize = 480; +pub const HEIGHT: usize = 800; + +pub const DISPLAY_CONFIGURATION: DisplayConfiguration = DisplayConfiguration { + 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, + h_sync_pol: false, + v_sync_pol: false, + not_data_enable_pol: false, + pixel_clock_pol: false, +}; + +/// Configure a pin for the FMC controller +macro_rules! fmc_pins { + ($($pin:expr),*) => { + ( + $( + $pin.into_push_pull_output() + .speed(Speed::VeryHigh) + .into_alternate::<12>() + .internal_pull_up(true) + ),* + ) + }; +} + +#[entry] +fn main() -> ! { + utilities::logger::init(); + + let dp = stm32::Peripherals::take().unwrap(); + let mut cp = stm32::CorePeripherals::take().unwrap(); + + // Constrain and Freeze power + info!("Setup PWR..."); + let pwr = dp.PWR.constrain(); + let pwrcfg = example_power!(pwr).vos0(&dp.SYSCFG).freeze(); + + // Constrain and Freeze clock + info!("Setup RCC..."); + let rcc = dp.RCC.constrain(); + + // Important for DSI PLL to configure this correctly. + // Disco board uses an oscillator while Eval - a crystal. + let hse_freq = 25.MHz(); + let rcc = rcc.use_hse(hse_freq).bypass_hse(); + // Precisely crafted clock in video mode + let ltdc_freq = 27_429.kHz(); + + let ccdr = rcc + .sys_ck(400.MHz()) + // FMC will run at 100MHz, as this clock is further divided by 2 + .pll2_p_ck(200.MHz()) + // .pll2_q_ck(200.MHz() / 2) + .pll2_r_ck(200.MHz()) + .pll2_strategy(PllConfigStrategy::Iterative) + // LTDC + .pll3_p_ck(330.MHz()) + .pll3_q_ck(330.MHz()) + .pll3_r_ck(ltdc_freq) + .pll3_strategy(PllConfigStrategy::Iterative) + .freeze(pwrcfg, &dp.SYSCFG); + + // Get frequency of LTDC pixel clock + info!("pll3_r_ck: {:?}", ccdr.clocks.pll3_r_ck()); + let _pll3_r = ccdr.clocks.pll3_r_ck().expect("pll3 must run!"); + + // Get the delay provider. + let mut delay = cp.SYST.delay(ccdr.clocks); + + // Initialise system... + cp.SCB.invalidate_icache(); + cp.SCB.enable_icache(); + //cp.SCB.enable_dcache(&mut cp.CPUID); // TODO invalidate dcache when writing to the display + cp.DWT.enable_cycle_counter(); + + // Initialise IO... + // let gpioa = dp.GPIOA.split(ccdr.peripheral.GPIOA); + // let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB); + // let gpioc = dp.GPIOC.split(ccdr.peripheral.GPIOC); + let gpiod = dp.GPIOD.split(ccdr.peripheral.GPIOD); + let gpioe = dp.GPIOE.split(ccdr.peripheral.GPIOE); + let gpiof = dp.GPIOF.split(ccdr.peripheral.GPIOF); + let gpiog = dp.GPIOG.split(ccdr.peripheral.GPIOG); + let gpioh = dp.GPIOH.split(ccdr.peripheral.GPIOH); + let gpioi = dp.GPIOI.split(ccdr.peripheral.GPIOI); + let gpioj = dp.GPIOJ.split(ccdr.peripheral.GPIOJ); + + let _syscfg = dp.SYSCFG; + let _exti = dp.EXTI; + + // MPU config for SDRAM write-through + let sdram_size = 32 * 1024 * 1024; + init_mpu(cp.MPU, &mut cp.SCB, sdram_size); + + // pin setup for SDRAM + let sdram_pins = fmc_pins! { + // A0-A11 + gpiof.pf0, gpiof.pf1, gpiof.pf2, gpiof.pf3, + gpiof.pf4, gpiof.pf5, gpiof.pf12, gpiof.pf13, + gpiof.pf14, gpiof.pf15, gpiog.pg0, gpiog.pg1, + // BA0-BA1 + gpiog.pg4, gpiog.pg5, + // D0-D31 + gpiod.pd14, gpiod.pd15, gpiod.pd0, gpiod.pd1, + gpioe.pe7, gpioe.pe8, gpioe.pe9, gpioe.pe10, + gpioe.pe11, gpioe.pe12, gpioe.pe13, gpioe.pe14, + gpioe.pe15, gpiod.pd8, gpiod.pd9, gpiod.pd10, + gpioh.ph8, gpioh.ph9, gpioh.ph10, gpioh.ph11, + gpioh.ph12, gpioh.ph13, gpioh.ph14, gpioh.ph15, + gpioi.pi0, gpioi.pi1, gpioi.pi2, gpioi.pi3, + gpioi.pi6, gpioi.pi7, gpioi.pi9, gpioi.pi10, + // NBL0 - NBL3 + gpioe.pe0, gpioe.pe1, gpioi.pi4, gpioi.pi5, + gpioh.ph7, // SDCKE1 + gpiog.pg8, // SDCLK + gpiog.pg15, // SDNCAS + gpioh.ph6, // SDNE1 (!CS) + gpiof.pf11, // SDRAS + gpioh.ph5 // SDNWE + }; + + let fmc_ccdr = ccdr.peripheral.FMC.kernel_clk_mux(FMCSEL_A::Pll2R); + // TODO: incorrect for disco! + let sdram_chip = stm32_fmc::devices::is42s32800g_6::Is42s32800g {}; + let mut sdram = dp.FMC.sdram( + sdram_pins, + sdram_chip, + // ccdr.peripheral.FMC, + fmc_ccdr, + &ccdr.clocks, + ); + + let (fb1, fb2) = unsafe { + // Initialise controller and SDRAM + let ram_ptr: *mut u32 = sdram.init(&mut delay); + slice::from_raw_parts_mut(ram_ptr, sdram_size / mem::size_of::()) + .fill(0); + + let fb_size = WIDTH * HEIGHT; + let bank_size_words = 8 * 1024 * 1024 / 4; + let fb1 = slice::from_raw_parts_mut(ram_ptr, fb_size); + // Offset the second buffer into another SDRAM bank - this saves a bit of time if DMA2D is used + // to clear the fb while update is ongoing. + let fb2 = + slice::from_raw_parts_mut(ram_ptr.offset(bank_size_words), fb_size); + (fb1, fb2) + }; + + info!("Initialised SDRAM..."); + + // Initialise LCD... + // Display controller reset through dedicated IO + let mut display_reset = gpiog.pg3.into_push_pull_output(); + display_reset.set_low(); + delay.delay_ms(20u32); + display_reset.set_high(); + delay.delay_ms(10u32); + + // Display backlight enable + let mut display_backlight_en = gpioj.pj12.into_push_pull_output(); + display_backlight_en.set_high(); + + // Display controller TE (hw tear effect sync) pin as input + // let _display_te = gpioj.pj2.into_alternate::<13>(); + let _display_te = gpioj.pj2.into_input(); + // display_te.make_interrupt_source(&mut syscfg); + // display_te.trigger_on_edge(&mut exti, Edge::Rising); + // display_te.enable_interrupt(&mut exti); + + let mut ltdc = ltdc::Ltdc::new(dp.LTDC, ccdr.peripheral.LTDC, &ccdr.clocks); + ltdc.init(DISPLAY_CONFIGURATION); + + let layer = ltdc.split(); + let mut disp = BufferedDisplay::new(layer, fb1, fb2, WIDTH, HEIGHT); + + // Fin = 25MHz ->/idf = 5MHz ->*2 = 10MHz ->*ndiv = 1GHz ->/2 = 500MHz ->/odf = 500MHz (500Mbps per lane); pix clk (/8) = 62.5MHz + let dsi_pll_config = unsafe { DsiPllConfig::manual(100, 5, 0, 4) }; + + let dsi_config = DsiConfig { + mode: DsiMode::Video { + // mode: DsiVideoMode::NonBurstWithSyncEvents, + 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, // for OTM8009A + vlp_size: 4, + }; + let mut dsi_host = DsiHost::init( + dsi_pll_config, + DISPLAY_CONFIGURATION, + dsi_config, + dp.DSIHOST, + ccdr.peripheral.DSI, + &ccdr.clocks, + ) + .expect("DSI host failed to init"); + dsi_host.set_command_mode_transmission_kind( + DsiCmdModeTransmissionKind::AllInLowPower, + ); + + // Enable DSI host + dsi_host.start(); + dsi_host.enable_bus_turn_around(); // Must be before read attempts + + 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, + }); + + let otm8009a_config = Otm8009AConfig { + frame_rate: otm8009a::FrameRate::_70Hz, + // NOTE: In Landscape mode diagonal tearing line will be visible when fast changing content is present + mode: otm8009a::Mode::Portrait, + color_map: otm8009a::ColorMap::Rgb, + cols: WIDTH as u16, + rows: HEIGHT as u16, + }; + let mut glass_ctrl = Otm8009A::new(); + glass_ctrl + .init(&mut dsi_host, otm8009a_config, &mut delay) + .unwrap(); + glass_ctrl.enable_te_output(533, &mut dsi_host).unwrap(); + + // Not sure if this is needed + dsi_host.set_command_mode_transmission_kind( + DsiCmdModeTransmissionKind::AllInHighSpeed, + ); + dsi_host.force_rx_low_power(true); + + //let mut dsi_refresh_handle = dsi_host.refresh_handle(); + info!("Initialised Display..."); + + let scl = gpiod.pd12.into_alternate_open_drain(); + let sda = gpiod.pd13.into_alternate_open_drain(); + let i2c4 = + dp.I2C4 + .i2c((scl, sda), 100.kHz(), ccdr.peripheral.I2C4, &ccdr.clocks); + let mut touch_ctrl = FT6236::new(i2c4); + // let touch_int = gpiok.pk7 + + let mut x = 0; + let mut y = 0; + let mut frame = 0; + let style_green = PrimitiveStyleBuilder::new() + .fill_color(Rgb888::GREEN) + .build(); + let mut buf = [0u8; 64]; + + loop { + // Draw on a double buffered display + disp.layer(|draw| { + draw.clear(); + + Rectangle::new(Point::new(x, 0), Size::new(100, HEIGHT as u32)) + .into_styled(style_green) + .draw(draw) + .unwrap(); + + Rectangle::new(Point::new(0, y), Size::new(WIDTH as u32, 100)) + .into_styled(style_green) + .draw(draw) + .unwrap(); + + x += 5; + y += 5; + if x >= WIDTH as i32 { + x = 0; + } + if y >= HEIGHT as i32 { + y = 0; + } + + let mut buf = WriteTo::new(&mut buf); + write!(&mut buf, "f: {frame}").unwrap(); + frame += 1; + colored_label(buf.as_str().unwrap(), 50, 20, Rgb888::RED, draw).unwrap(); + + if let Ok(Some(pt)) = touch_ctrl.get_point0() { + info!("Touch: {} {}", pt.y, 480 - pt.x); + Circle::new(Point::new(480 - pt.x as i32 - 25, pt.y as i32 - 25), 50).into_styled(style_green).draw(draw).unwrap(); + } + + display_test(draw).unwrap(); + }); + disp.swap_layer_wait(); + } +} + +#[exception] +unsafe fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! { + panic!("HardFault at {:#?}", ef); +} + +#[exception] +unsafe fn DefaultHandler(irqn: i16) { + panic!("Unhandled exception (IRQn = {})", irqn); +} \ No newline at end of file diff --git a/examples/embedded-graphics.rs b/examples/embedded-graphics.rs index 0ec5be04..7c37cf01 100644 --- a/examples/embedded-graphics.rs +++ b/examples/embedded-graphics.rs @@ -34,6 +34,7 @@ use embedded_graphics::text::Text; use numtoa::NumToA; use tinybmp::Bmp; +use crate::utilities::display_target::BufferedDisplay; const WIDTH: usize = 480; const HEIGHT: usize = 272; @@ -230,7 +231,7 @@ fn main() -> ! { }); let layer = ltdc.split(); - let mut disp = BufferedDisplay::new(layer, fb1, fb2); + let mut disp = BufferedDisplay::new(layer, fb1, fb2, WIDTH, HEIGHT); lcd_disp_en.set_low(); lcd_disp_ctrl.set_high(); @@ -281,132 +282,6 @@ fn main() -> ! { } } -/// A display with swappable framebuffers -pub struct BufferedDisplay<'a, LY> { - layer: LY, - front_buffer: &'a mut [u32], - back_buffer: &'a mut [u32], -} - -/// An individual display layer, borrowing from `BufferedDisplay` -pub struct DisplayBuffer<'a, 'p>( - /// Underlying buffer - pub &'p mut &'a mut [u32], -); - -impl<'a, LY> BufferedDisplay<'a, LY> -where - LY: embedded_display_controller::DisplayControllerLayer, -{ - pub fn new( - mut layer: LY, - front_buffer: &'a mut [u32], - back_buffer: &'a mut [u32], - ) -> Self { - // Safety: the frame buffer has the right size - unsafe { - layer.enable( - front_buffer.as_ptr() as *const u8, - embedded_display_controller::PixelFormat::ARGB8888, - ); - } - - BufferedDisplay { - layer, - front_buffer, - back_buffer, - } - } - - /// Swaps frame buffers - /// - /// # Safety - /// - /// Does not wait for the swap to actually occur, and hence layer accesses - /// after this may write to the wrong layer. For a safe version, use - /// swap_layer_wait - pub unsafe fn swap_layer(&mut self) { - // Have been filling back buffer - self.layer.swap_framebuffer(self.back_buffer.as_ptr()); - // Swap the back buffer to the front and visa versa - mem::swap(&mut self.back_buffer, &mut self.front_buffer); - } - - /// Swaps frame buffers then waits for the swap to occour on the next - /// vertical blanking period - pub fn swap_layer_wait(&mut self) { - // unsafe: we wait for the swap to occour, so current - // displayed buffer is protected - unsafe { self.swap_layer() }; - while self.layer.is_swap_pending() {} - } - - /// Access to layer via closure - pub fn layer(&mut self, func: F) -> T - where - F: FnOnce(&mut DisplayBuffer) -> T, - { - // Create a layer that lives until the end of this call - let mut layer = DisplayBuffer(&mut self.back_buffer); - func(&mut layer) - } -} - -use embedded_graphics::{ - geometry, - pixelcolor::raw::{RawData, RawU24}, - pixelcolor::Rgb888, - Pixel, -}; - -// Implement DrawTarget for -impl embedded_graphics::draw_target::DrawTarget for DisplayBuffer<'_, '_> { - type Color = Rgb888; - type Error = (); - - /// Draw a pixel - fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> - where - I: IntoIterator>, - { - for pixel in pixels { - let Pixel(point, color) = pixel; - let raw: RawU24 = color.into(); - let rgb: u32 = 0xFF00_0000u32 | raw.into_inner(); - - if point.x >= 0 - && point.y >= 0 - && point.x < (WIDTH as i32) - && point.y < (HEIGHT as i32) - { - let index = (point.y * (WIDTH as i32)) + point.x; - self.0[index as usize] = rgb; - } else { - // Ignore invalid points - } - } - - Ok(()) - } -} -impl geometry::OriginDimensions for DisplayBuffer<'_, '_> { - /// Return the size of the display - fn size(&self) -> geometry::Size { - geometry::Size::new(HEIGHT as u32, WIDTH as u32) - } -} - -impl DisplayBuffer<'_, '_> { - /// Clears the buffer - pub fn clear(&mut self) { - let pixels = WIDTH * HEIGHT; - - for a in self.0[..pixels as usize].iter_mut() { - *a = 0xFF00_0000u32; // Solid black - } - } -} - #[exception] fn SysTick() { TIME.fetch_add(1, Ordering::Relaxed); diff --git a/examples/utilities/display_primitives.rs b/examples/utilities/display_primitives.rs new file mode 100644 index 00000000..6004ac8c --- /dev/null +++ b/examples/utilities/display_primitives.rs @@ -0,0 +1,166 @@ +#[cfg(feature = "chrono")] +use chrono::{Datelike, NaiveDateTime, Timelike}; +use embedded_graphics::image::Image; +use embedded_graphics::mono_font::{ascii, MonoTextStyle}; +use embedded_graphics::pixelcolor::Rgb888; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle}; +use embedded_graphics::text::Text; + +#[allow(dead_code)] +pub fn colored_label( + text: &str, + x: i32, + y: i32, + color: Rgb888, + target: &mut D, +) -> Result<(), D::Error> + where + D: DrawTarget, +{ + let style_red = PrimitiveStyleBuilder::new().fill_color(color).build(); + Rectangle::new( + Point::new(x, y), + Size::new((text.len() as u32) * 9 + 8, 22), + ) + .into_styled(style_red) + .draw(target)?; + + let text_style = + MonoTextStyle::new(&ascii::FONT_9X18_BOLD, RgbColor::WHITE); + Text::new(text, Point::new(x + 4, y + 18 - 2), text_style).draw(target)?; + Ok(()) +} + +#[allow(dead_code)] +pub fn display_test( + target: &mut D, +) -> Result<(), D::Error> + where + D: DrawTarget, +{ + let style_red = + PrimitiveStyleBuilder::new().fill_color(Rgb888::RED).build(); + let style_green = PrimitiveStyleBuilder::new() + .fill_color(Rgb888::GREEN) + .build(); + let style_blue = PrimitiveStyleBuilder::new() + .fill_color(Rgb888::BLUE) + .build(); + let style_cyan = PrimitiveStyleBuilder::new() + .fill_color(Rgb888::CYAN) + .build(); + + let size = target.bounding_box().size; + Rectangle::new(Point::new(0, 0), Size::new(10, 10)) + .into_styled(style_red) + .draw(target)?; + Rectangle::new(Point::new(size.width as i32 - 10, 0), Size::new(10, 10)) + .into_styled(style_green) + .draw(target)?; + Rectangle::new( + Point::new(0, size.height as i32 - 10), + Size::new(10, 10), + ) + .into_styled(style_blue) + .draw(target)?; + Rectangle::new( + Point::new(size.width as i32 - 10, size.height as i32 - 10), + Size::new(10, 10), + ) + .into_styled(style_cyan) + .draw(target)?; + + let ferris = tinybmp::Bmp::from_slice(include_bytes!("../ferris.bmp")).unwrap(); + let ferris = Image::new(&ferris, Point::new((size.width / 2 - ferris.size().width / 2) as i32, (size.height / 2 - ferris.size().height / 2) as i32)); + ferris.draw(target)?; + Ok(()) +} + +#[cfg(feature = "chrono")] +#[allow(dead_code)] +pub fn seven_segment_style(color: Rgb888) -> eg_seven_segment::SevenSegmentStyle { + eg_seven_segment::SevenSegmentStyleBuilder::new() + .digit_size(Size::new(36, 48)) // digits are 10x20 pixels + .digit_spacing(5) // 5px spacing between digits + .segment_width(5) + .segment_color(color) // active segments are green + .build() +} + +#[cfg(feature = "chrono")] +#[allow(dead_code)] +pub fn date_labels(y: i32, target: &mut D) -> Result<(), D::Error> + where + D: DrawTarget, +{ + let bg_color = Rgb888::RED; + colored_label("MONTH", 86, y, bg_color, target)?; + colored_label("DAY", 240, y, bg_color, target)?; + colored_label("YEAR", 398, y, bg_color, target)?; + colored_label("HOUR", 563, y, bg_color, target)?; + colored_label("MIN", 692, y, bg_color, target)?; + Ok(()) +} + +#[cfg(feature = "chrono")] +#[allow(dead_code)] +pub fn time_circuit(dt: NaiveDateTime, x: i32, y: i32, label: &str, fg: Rgb888, bg: Rgb888, target: &mut D) -> Result<(), D::Error> + where + D: DrawTarget, +{ + let fg_text_style = seven_segment_style(fg); + let bg_text_style = seven_segment_style(bg); + let all_segments = "888 88 8888 88 88"; + Text::new(all_segments, Point::new(x, y), bg_text_style) + .draw(target)?; + let month = match dt.date().month() { + 1 => "JAN", + 2 => "FEB", + 3 => "MAR", + 4 => "APR", + 5 => "MAY", + 6 => "JUN", + 7 => "JUL", + 8 => "AUG", + 9 => "SEP", + 10 => "OCT", + 11 => "NOU", + 12 => "DEC", + _ => unreachable!() + }; + let mut buf = [0u8; 17]; + use crate::utilities::write::write_to::WriteTo; + use core::fmt::Write; + let mut buf = WriteTo::new(&mut buf); + let (is_pm, hour) = dt.time().hour12(); + write!(&mut buf, "{} {:02} {:04} {:02} {:02}", month, dt.date().day(), dt.date().year(), hour, dt.time().minute()).unwrap(); + Text::new(buf.as_str().unwrap(), Point::new(x, y), fg_text_style) + .draw(target)?; + date_labels(y - 75, target)?; + colored_label( + label, + x + 363 - ((label.len() as i32) / 2) * 9, + y + 10, + Rgb888::CSS_DIM_GRAY, + target, + )?; + + let fg_style = PrimitiveStyleBuilder::new() + .fill_color(fg) + .build(); + let bg_style = PrimitiveStyleBuilder::new() + .fill_color(bg) + .build(); + colored_label("AM", 633, y - 65, Rgb888::RED, target)?; + let (am_style, pm_style) = if is_pm { + (bg_style, fg_style) + } else { + (fg_style, bg_style) + }; + use embedded_graphics::primitives::Circle; + Circle::new(Point::new(641, y - 40), 10).into_styled(am_style).draw(target)?; + colored_label("PM", 633, y - 26, Rgb888::RED, target)?; + Circle::new(Point::new(641, y), 10).into_styled(pm_style).draw(target)?; + Ok(()) +} diff --git a/examples/utilities/display_target.rs b/examples/utilities/display_target.rs new file mode 100644 index 00000000..3047bb20 --- /dev/null +++ b/examples/utilities/display_target.rs @@ -0,0 +1,139 @@ +use core::mem; + +/// A display with swappable framebuffers +pub struct BufferedDisplay<'a, LY> { + layer: LY, + front_buffer: &'a mut [u32], + back_buffer: &'a mut [u32], + width: i32, + height: i32, +} + +/// An individual display layer, borrowing from `BufferedDisplay` +pub struct DisplayBuffer<'a, 'p> { + /// Underlying buffer + pub buf: &'p mut &'a mut [u32], + pub width: i32, + pub height: i32, +} + +impl<'a, LY> BufferedDisplay<'a, LY> +where + LY: embedded_display_controller::DisplayControllerLayer, +{ + pub fn new( + mut layer: LY, + front_buffer: &'a mut [u32], + back_buffer: &'a mut [u32], + width: usize, + height: usize, + ) -> Self { + // Safety: the frame buffer has the right size + unsafe { + layer.enable( + front_buffer.as_ptr() as *const u8, + embedded_display_controller::PixelFormat::ARGB8888, + ); + } + + BufferedDisplay { + layer, + front_buffer, + back_buffer, + width: width as i32, + height: height as i32, + } + } + + /// Swaps frame buffers + /// + /// # Safety + /// + /// Does not wait for the swap to actually occur, and hence layer accesses + /// after this may write to the wrong layer. For a safe version, use + /// swap_layer_wait + pub unsafe fn swap_layer(&mut self) { + // Have been filling back buffer + self.layer.swap_framebuffer(self.back_buffer.as_ptr()); + // Swap the back buffer to the front and visa versa + mem::swap(&mut self.back_buffer, &mut self.front_buffer); + } + + /// Swaps frame buffers then waits for the swap to occour on the next + /// vertical blanking period + pub fn swap_layer_wait(&mut self) { + // unsafe: we wait for the swap to occour, so current + // displayed buffer is protected + unsafe { self.swap_layer() }; + while self.layer.is_swap_pending() {} + } + + /// Access to layer via closure + pub fn layer(&mut self, func: F) -> T + where + F: FnOnce(&mut DisplayBuffer) -> T, + { + // Create a layer that lives until the end of this call + let mut layer = DisplayBuffer { + buf: &mut self.back_buffer, + width: self.width, + height: self.height, + }; + func(&mut layer) + } +} + +use embedded_graphics::{ + geometry, + pixelcolor::raw::{RawData, RawU24}, + pixelcolor::Rgb888, + Pixel, +}; + +// Implement DrawTarget for +impl embedded_graphics::draw_target::DrawTarget for DisplayBuffer<'_, '_> { + type Color = Rgb888; + type Error = (); + + /// Draw a pixel + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + for pixel in pixels { + let Pixel(point, color) = pixel; + let raw: RawU24 = color.into(); + let rgb: u32 = 0xFF00_0000u32 | raw.into_inner(); + + if point.x >= 0 + && point.y >= 0 + && point.x < self.width + && point.y < self.height + { + let index = point.y * self.width + point.x; + self.buf[index as usize] = rgb; + } else { + // Ignore invalid points + } + } + + Ok(()) + } +} +impl geometry::OriginDimensions for DisplayBuffer<'_, '_> { + /// Return the size of the display + fn size(&self) -> geometry::Size { + geometry::Size::new(self.width as u32, self.height as u32) + } +} + +impl DisplayBuffer<'_, '_> { + /// Clears the buffer + pub fn clear(&mut self) { + let pixels = self.width * self.height; + + for a in self.buf[..pixels as usize].iter_mut() { + *a = 0xFF00_0000u32; // Solid black + } + } +} diff --git a/examples/utilities/logger.rs b/examples/utilities/logger.rs index 400a9b3f..650dce7c 100644 --- a/examples/utilities/logger.rs +++ b/examples/utilities/logger.rs @@ -43,12 +43,12 @@ cfg_if::cfg_if! { } static LOGGER: Logger = Logger { - level: Level::Info, + level: Level::Debug, }; pub fn init() { rtt_init_print!(); - log::set_logger(&LOGGER).map(|()| log::set_max_level(LevelFilter::Info)).unwrap(); + log::set_logger(&LOGGER).map(|()| log::set_max_level(LevelFilter::Debug)).unwrap(); } impl log::Log for Logger { diff --git a/examples/utilities/mod.rs b/examples/utilities/mod.rs index 39e92a33..87f09dbf 100644 --- a/examples/utilities/mod.rs +++ b/examples/utilities/mod.rs @@ -3,3 +3,13 @@ pub mod logger; #[macro_use] mod power; + +#[cfg(feature = "fmc")] +pub mod mpu_config; + +#[cfg(feature = "ltdc")] +pub mod display_target; +#[cfg(feature = "ltdc")] +pub mod write; +#[cfg(feature = "ltdc")] +pub mod display_primitives; \ No newline at end of file diff --git a/examples/utilities/mpu_config.rs b/examples/utilities/mpu_config.rs new file mode 100644 index 00000000..b91afa60 --- /dev/null +++ b/examples/utilities/mpu_config.rs @@ -0,0 +1,77 @@ +use cortex_m::peripheral::{MPU, SCB}; + +pub fn init_mpu(mpu: MPU, scb: &mut SCB, sdram_size: usize) { + // Refer to ARM®v7-M Architecture Reference Manual ARM DDI 0403 + // Version E.b Section B3.5 + const MEMFAULTENA: u32 = 1 << 16; + + unsafe { + /* Make sure outstanding transfers are done */ + cortex_m::asm::dmb(); + + scb.shcsr.modify(|r| r & !MEMFAULTENA); + + /* Disable the MPU and clear the control register*/ + mpu.ctrl.write(0); + } + + const REGION_NUMBER0: u32 = 0x00; + const REGION_BASE_ADDRESS: u32 = 0xD000_0000; + + const REGION_FULL_ACCESS: u32 = 0x03; + const REGION_CACHEABLE: u32 = 0x01; + const REGION_WRITE_BACK: u32 = 0x01; + const REGION_ENABLE: u32 = 0x01; + + assert_eq!( + sdram_size & (sdram_size - 1), + 0, + "SDRAM memory region size must be a power of 2" + ); + assert_eq!( + sdram_size & 0x1F, + 0, + "SDRAM memory region size must be 32 bytes or more" + ); + fn log2minus1(sz: u32) -> u32 { + for i in 5..=31 { + if sz == (1 << i) { + return i - 1; + } + } + panic!("Unknown SDRAM memory region size!"); + } + + //info!("SDRAM Memory Size 0x{:x}", log2minus1(size as u32)); + + // Configure region 0 + // + // Cacheable, outer and inner write-back, no write allocate. So + // reads are cached, but writes always write all the way to SDRAM + unsafe { + mpu.rnr.write(REGION_NUMBER0); + mpu.rbar.write(REGION_BASE_ADDRESS); + mpu.rasr.write( + (REGION_FULL_ACCESS << 24) + | (REGION_CACHEABLE << 17) + | (REGION_WRITE_BACK << 16) + | (log2minus1(sdram_size as u32) << 1) + | REGION_ENABLE, + ); + } + + const MPU_ENABLE: u32 = 0x01; + const MPU_DEFAULT_MMAP_FOR_PRIVILEGED: u32 = 0x04; + + // Enable + unsafe { + mpu.ctrl + .modify(|r| r | MPU_DEFAULT_MMAP_FOR_PRIVILEGED | MPU_ENABLE); + + scb.shcsr.modify(|r| r | MEMFAULTENA); + + // Ensure MPU settings take effect + cortex_m::asm::dsb(); + cortex_m::asm::isb(); + } +} \ No newline at end of file diff --git a/examples/utilities/write.rs b/examples/utilities/write.rs new file mode 100644 index 00000000..7eabc933 --- /dev/null +++ b/examples/utilities/write.rs @@ -0,0 +1,54 @@ +pub mod write_to { + use core::cmp::min; + use core::fmt; + + pub struct WriteTo<'a> { + buffer: &'a mut [u8], + // on write error (i.e. not enough space in buffer) this grows beyond + // `buffer.len()`. + used: usize, + } + + impl<'a> WriteTo<'a> { + #[allow(dead_code)] + pub fn new(buffer: &'a mut [u8]) -> Self { + WriteTo { buffer, used: 0 } + } + + #[allow(dead_code)] + pub fn as_str(self) -> Option<&'a str> { + if self.used <= self.buffer.len() { + // only successful concats of str - must be a valid str. + use core::str::from_utf8_unchecked; + Some(unsafe { from_utf8_unchecked(&self.buffer[..self.used]) }) + } else { + None + } + } + } + + impl<'a> fmt::Write for WriteTo<'a> { + fn write_str(&mut self, s: &str) -> fmt::Result { + if self.used > self.buffer.len() { + return Err(fmt::Error); + } + let remaining_buf = &mut self.buffer[self.used..]; + let raw_s = s.as_bytes(); + let write_num = min(raw_s.len(), remaining_buf.len()); + remaining_buf[..write_num].copy_from_slice(&raw_s[..write_num]); + self.used += raw_s.len(); + if write_num < raw_s.len() { + Err(fmt::Error) + } else { + Ok(()) + } + } + } + + #[allow(dead_code)] + pub fn show<'a>(buffer: &'a mut [u8], args: fmt::Arguments) -> Result<&'a str, fmt::Error> { + let mut w = WriteTo::new(buffer); + fmt::write(&mut w, args)?; + w.as_str().ok_or(fmt::Error) + } +} \ No newline at end of file diff --git a/src/delay.rs b/src/delay.rs index 42f5b13a..d3bd2087 100644 --- a/src/delay.rs +++ b/src/delay.rs @@ -49,7 +49,7 @@ use void::Void; use crate::nb::block; use crate::rcc::CoreClocks; -use crate::time::Hertz; +use crate::time::{Hertz, MicroSeconds}; use fugit::RateExtU32; pub trait DelayExt { diff --git a/src/dsi.rs b/src/dsi.rs new file mode 100644 index 00000000..1a60fc76 --- /dev/null +++ b/src/dsi.rs @@ -0,0 +1,707 @@ +use crate::rcc::CoreClocks; +use crate::{ + device::DSIHOST, + rcc::{rec, ResetEnable}, + time::Hertz, +}; +use core::cmp::{max, min}; +use embedded_display_controller::DisplayConfiguration; +use embedded_dsi::{DsiHostCtrlIo, DsiReadCommand, DsiWriteCommand}; +#[cfg(feature = "log")] +use log::debug; + +const DSI_TIMEOUT_MS: usize = 100; + +pub struct DsiHost { + dsi: DSIHOST, + channel: DsiChannel, + cycles_1ms: u32, +} + +#[derive(Copy, Clone)] +pub enum DsiChannel { + Ch0 = 0b00, + Ch1 = 0b01, + Ch2 = 0b10, + Ch3 = 0b11, +} + +#[derive(Debug)] +pub enum Error { + RegTimeout, + PllTimeout, + BufferIsToBig, + WriteTimeout, + ReadTimeout, + ReadError, + FifoTimeout, + WrongId, +} + +pub enum DsiMode { + Video { mode: DsiVideoMode }, + AdaptedCommand { tear_effect: Option }, +} + +pub enum DsiVideoMode { + NonBurstWithSyncPulses = 0b00, + NonBurstWithSyncEvents = 0b01, + Burst = 0b10, +} + +pub struct TearEffectMode { + pub source: TearEffectSource, + pub auto_refresh: bool, +} + +#[derive(PartialEq, Eq)] +pub enum TearEffectSource { + DsiLink, + ExternalPin, +} + +pub enum DsiInterrupts { + None, + All, +} + +pub enum DsiCmdModeTransmissionKind { + AllInHighSpeed, + AllInLowPower, +} + +pub struct DsiPhyTimers { + pub dataline_hs2lp: u8, + pub dataline_lp2hs: u8, + pub clock_hs2lp: u16, + pub clock_lp2hs: u16, + pub dataline_max_read_time: u16, + pub stop_wait_time: u8, +} + +pub struct DsiRefreshHandle { + dsi: DSIHOST, + // refresh_request: *mut bool, +} +// unsafe impl Send for DsiRefreshHandle {} + +pub enum LaneCount { + SingleLane = 0b00, + DoubleLane = 0b01, +} + +pub struct DsiPllConfig { + ndiv: u8, + idf: u8, + odf: u8, + eckdiv: u8, +} + +impl DsiPllConfig { + pub unsafe fn manual(ndiv: u8, idf: u8, odf: u8, eckdiv: u8) -> Self { + DsiPllConfig { + ndiv, + idf, + odf, + eckdiv, + } + } +} + +#[repr(u8)] +pub enum ColorCoding { + SixteenBitsConfig1 = 0b000, + SixteenBitsConfig2 = 0b001, + SixteenBitsConfig3 = 0b010, + EighteenBitsConfig1 = 0b011, + EighteenBitsConfig2 = 0b100, + TwentyFourBits = 0b101 +} + +pub struct DsiConfig { + pub mode: DsiMode, + pub lane_count: LaneCount, + pub channel: DsiChannel, + pub hse_freq: Hertz, + pub ltdc_freq: Hertz, + pub interrupts: DsiInterrupts, + pub color_coding_host: ColorCoding, + pub color_coding_wrapper: ColorCoding, + pub lp_size: u8, + pub vlp_size: u8, +} + +impl DsiHost { + pub fn init( + pll_config: DsiPllConfig, + display_config: DisplayConfiguration, + dsi_config: DsiConfig, + dsi: DSIHOST, + dsi_rec: rec::Dsi, + clocks: &CoreClocks, + ) -> Result { + // Enable and reset DSI peripheral + dsi_rec.enable().reset(); + //RCC_D1CCIPR: DSI clock from PHY is selected as DSI byte lane clock (default after reset) + let cycles_1ms = clocks.sysclk().raw() / 1_000; + + // Enable regulator + dsi.wrpcr.modify(|_, w| w.regen().set_bit()); + // Wait for it to be ready + block_with_timeout( + || dsi.wisr.read().rrs() == false, + DSI_TIMEOUT_MS, + cycles_1ms, + Error::RegTimeout, + )?; + + // Set PLL division factors + // Fin = 25MHz ->/idf = 5MHz ->*2 = 10MHz ->*ndiv = 1GHz ->/2 = 500MHz ->/odf = 500MHz ->/8 = 62.5MHz + // let ndiv = 125; + // let ndiv = 102; + // let idf = 5; + // let odf = 0b00; + dsi.wrpcr.modify(|_, w| unsafe { + w.ndiv() + .bits(pll_config.ndiv) // allowed: 10 ..= 125 + .idf() + .bits(pll_config.idf) // div1: 0b000, 0b001, div2: 0b010, div3: 0b011 ..= div7 + .odf() + .bits(pll_config.odf) // div1: 0b00, div2: 0b01, div4: 0b10, div8: 0b11 + }); + // Enable PLL + dsi.wrpcr.modify(|_, w| w.pllen().set_bit()); + // Required to wait 400us before checking PLLLS flag + cortex_m::asm::delay(cycles_1ms / 2); + // Wait for the lock + block_with_timeout( + || dsi.wisr.read().pllls() == false, + DSI_TIMEOUT_MS, + cycles_1ms, + Error::PllTimeout, + )?; + + // Clock and digital section enable + dsi.pctlr.modify(|_, w| w.cke().set_bit().den().set_bit()); + + // Clock lane config + dsi.clcr.modify( + |_, w| { + w.dpcc() + .set_bit() // 1: lanes are running in high speed mode + .acr() + .clear_bit() + }, // Automatically stop lanes clock when "time allows" + ); + + // Configure the number of active data lanes + dsi.pconfr + .modify(|_, w| unsafe { w.nl().bits(dsi_config.lane_count as u8) }); // 0b00 - 1 lanes, 0b01 - 2 lanes + + // Set TX escape clock division factor + dsi.ccr + .modify(|_, w| unsafe { w.txeckdiv().bits(pll_config.eckdiv) }); + + // Set the bit period in high speed mode + // Calculate the bit period in high-speed mode in unit of 0.25 ns (UIX4) + // The equation is : UIX4 = IntegerPart( (1000/F_PHY_Mhz) * 4 ) + // Where : F_PHY_Mhz = (NDIV * HSE_Mhz) / (IDF * ODF) + let odf = match pll_config.odf { + 0b00 => 1, + 0b01 => 2, + 0b10 => 4, + 0b11 => 8, + _ => unreachable!(), + }; + let f_phy_hz = ((pll_config.ndiv as u32) * dsi_config.hse_freq.raw()) + / u32::from(pll_config.idf) + / odf; + let f_pix_khz = f_phy_hz / 1_000 / 8; + let uix4 = 4_000_000_000 / f_phy_hz; + #[cfg(feature = "log")] + debug!("f_phy={}kHz, f_pix={}kHz, uix4={}", f_phy_hz / 1_000, f_pix_khz, uix4); + dsi.wpcr0 + .modify(|_, w| unsafe { w.uix4().bits(uix4 as u8) }); + // debug!("f_phy={}, uix4=override=8", f_phy); + // dsi.wpcr0.modify(|_, w| unsafe { w.uix4().bits(8) }); + + match dsi_config.interrupts { + DsiInterrupts::None => { + // Disable all error interrupts for now and reset the error mask + dsi.ier0.write(|w| unsafe { w.bits(0) }); + dsi.ier1.write(|w| unsafe { w.bits(0) }); + } + DsiInterrupts::All => { + // Enable all error interrupts + dsi.ier0.write(|w| unsafe { + w.bits(0b00000000_00011111_11111111_11111111) + }); + dsi.ier1.write(|w| unsafe { w.bits(0b00011111_11111111) }); + + // Enable wrapper interrupts + dsi.wier.write(|w| w.teie().set_bit().erie().set_bit()); + } + } + + match dsi_config.mode { + DsiMode::Video { mode } => { + // Select video mode + dsi.mcr.modify(|_, w| w.cmdm().clear_bit()); // 0 - video mode, 1 - command mode + dsi.wcfgr.modify(|_, w| { + w + // 0 - video mode, 1 - adapted command mode + .dsim() + .clear_bit() + // 0 - DSI Link, 1 - External pin + .tesrc() + .clear_bit() + // 0 - Rising edge, 1 - Falling edge + .tepol() + .clear_bit() + // Refresh mode in DBI mode, 0 - disabled, 1 - automatic refresh enabled + .ar() + .clear_bit() + }); + + // Video mode transmission type, p. 1346 + dsi.vmcr.modify(|_, w| unsafe { + w.vmt() + .bits(mode as u8) // 0b00 - non-burst with sync pulses, 0b01 - non-burst with sync event, 0b1x - burst mode + .lpvsae() + .set_bit() // Enable LP transition in vertical sync period + .lpvbpe() + .set_bit() // Enable LP transition in VBP period + .lpvfpe() + .set_bit() // Enable LP transition in VFP period + .lpvae() + .set_bit() // Enable LP transition in VACT period + .lphbpe() + .set_bit() // Enable LP transition in HBP period + .lphfpe() + .set_bit() // Enable LP transition in HFP period + .lpce() + .set_bit() // 1 = Command transmission in low power mode enabled + .fbtaae() + .clear_bit() // Disable the request for an acknowledge response at the end of a frame + }); + + // Packet size, 14 bits max + // TODO: Might be incorrect for 16 or 18bit + dsi.vpcr.modify(|_, w| unsafe { + w.vpsize().bits(display_config.active_width) + }); + + // TODO: Unhardcode? + // This register configures the number of chunks to be transmitted during a line period (a chunk + // consists of a video packet and a null packet). + // If set to 0 or 1, the video line is transmitted in a single packet. + // If set to 1, the packet is part of a chunk, so a null packet follows it if NPSIZE > 0. Otherwise, + // multiple chunks are used to transmit each video line. + dsi.vccr.modify(|_, w| unsafe { w.numc().bits(1) }); + + // Size of the null packet + dsi.vnpcr.modify(|_, w| unsafe { w.npsize().bits(0) }); + + // Horizontal sync active (HSA) in lane byte clock cycles + let f_ltdc_khz = dsi_config.ltdc_freq.to_kHz(); + let hsa = ((display_config.h_sync as u32) * f_pix_khz / f_ltdc_khz) as u16; + #[cfg(feature = "log")] + debug!("hsa={}", hsa); + dsi.vhsacr.modify(|_, w| unsafe { w.hsa().bits(hsa) }); + + // Horizontal back porch (HBP) in lane byte clock cycles + let hbp = ((display_config.h_back_porch as u32) * f_pix_khz / f_ltdc_khz) as u16; + #[cfg(feature = "log")] + debug!("hbp={}", hbp); + dsi.vhbpcr.modify(|_, w| unsafe { w.hbp().bits(hbp) }); + + // Total line time, HLINE = HSA + HBP + HACT + HFP + let hline = display_config.h_sync + + display_config.h_back_porch + + display_config.active_width + + display_config.h_front_porch; + let hline = ((hline as u32) * f_pix_khz / f_ltdc_khz) as u16; + // let hsync = f_phy * 3 * hline as u32 / 8; + #[cfg(feature = "log")] + debug!("hline={}", hline); + dsi.vlcr.modify(|_, w| unsafe { w.hline().bits(hline) }); + + // Vertical sync active (VSA) + dsi.vvsacr.modify(|_, w| unsafe { + w.vsa().bits(display_config.v_sync) + }); + + // Vertical back porch (VBP) + dsi.vvbpcr.modify(|_, w| unsafe { + w.vbp().bits(display_config.v_back_porch) + }); + + // Vertical front porch (VFP) + dsi.vvfpcr.modify(|_, w| unsafe { + w.vfp().bits(display_config.v_front_porch) + }); + + // Vertical active period + dsi.vvacr.modify(|_, w| unsafe { + w.va().bits(display_config.active_height) + }); + } + DsiMode::AdaptedCommand { tear_effect } => { + // Select command mode + dsi.mcr.modify(|_, w| w.cmdm().set_bit()); // 0 - video mode, 1 - command mode + let (is_external_pin, auto_refresh) = match tear_effect { + Some(te) => ( + te.source == TearEffectSource::ExternalPin, + te.auto_refresh, + ), + None => (false, false), + }; + dsi.wcfgr.modify(|_, w| { + w + // 0 - video mode, 1 - adapted command mode + .dsim() + .set_bit() + // 0 - DSI Link, 1 - External pin + .tesrc() + .bit(is_external_pin) + // 0 - Rising edge, 1 - Falling edge + .tepol() + .clear_bit() + // Refresh mode in DBI mode, 0 - disabled, 1 - automatic refresh enabled + .ar() + .bit(auto_refresh) + // VSync polarity, 0 - LTDC halted on falling edge, 1 - LTDC halted on rising edge + .vspol() + .clear_bit() + }); + + // Maximum allowed size for memory write command + dsi.lccr.modify(|_, w| unsafe { + w.cmdsize().bits(display_config.active_width) + }); + + // Tearing effect acknowledge request + dsi.cmcr.modify(|_, w| w.teare().set_bit()) + } + } + + // Select virtual channel for the LTDC interface traffic + dsi.lvcidr + .modify(|_, w| unsafe { w.vcid().bits(dsi_config.channel as u8) }); + + // Polarity + dsi.lpcr.modify(|_, w| { + w.dep().clear_bit().vsp().clear_bit().hsp().clear_bit() + }); + + // Color coding for the host + let lpe = match dsi_config.color_coding_host { + ColorCoding::EighteenBitsConfig1 => true, + ColorCoding::EighteenBitsConfig2 => true, + _ => false + }; + dsi.lcolcr.modify(|_, w| unsafe { + w.lpe() + .bit(lpe) // loosely packed: 18bits + .colc() + .bits(dsi_config.color_coding_host as u8) // 0: 16bit_1, 1: 16bit_2, 2: 16bit_3, 3: 18bit_1, 4: 18bit_2, 5: 24bit + }); + + // Color coding for the wrapper + dsi.wcfgr.modify(|_, w| unsafe { w.colmux().bits(dsi_config.color_coding_wrapper as u8) }); + + dsi.lpmcr.modify(|_, w| unsafe { + w.lpsize() + .bits(dsi_config.lp_size) // Low power largest packet size + .vlpsize() + .bits(dsi_config.vlp_size) // Low power VACT largest packet size + }); + + Ok(DsiHost { + dsi, + channel: dsi_config.channel, + cycles_1ms, + }) + } + + pub fn set_command_mode_transmission_kind( + &mut self, + kind: DsiCmdModeTransmissionKind, + ) { + let is_low_power = match kind { + DsiCmdModeTransmissionKind::AllInHighSpeed => false, + DsiCmdModeTransmissionKind::AllInLowPower => true, + }; + self.dsi.cmcr.modify(|_, w| { + w.gsw0tx() + .bit(is_low_power) + .gsw1tx() + .bit(is_low_power) + .gsw2tx() + .bit(is_low_power) + .gsr0tx() + .bit(is_low_power) + .gsr1tx() + .bit(is_low_power) + .gsr2tx() + .bit(is_low_power) + .glwtx() + .bit(is_low_power) + .dsw0tx() + .bit(is_low_power) + .dsw1tx() + .bit(is_low_power) + .dsr0tx() + .bit(is_low_power) + .dlwtx() + .bit(is_low_power) + .mrdps() + .bit(is_low_power) + }); + self.dsi.cmcr.modify(|_, w| w.are().clear_bit()); // FIXME: might be incorrect + } + + pub fn configure_phy_timers(&mut self, phy_timers: DsiPhyTimers) { + let max_time = max(phy_timers.clock_lp2hs, phy_timers.clock_hs2lp); + self.dsi.cltcr.modify(|_, w| unsafe { + w.hs2lp_time().bits(max_time).lp2hs_time().bits(max_time) + }); + self.dsi.dltcr.modify(|_, w| unsafe { + w.mrd_time() + .bits(phy_timers.dataline_max_read_time) + .hs2lp_time() + .bits(phy_timers.dataline_hs2lp) + .lp2hs_time() + .bits(phy_timers.dataline_lp2hs) + }); + self.dsi.pconfr.modify(|_, w| unsafe { + w.sw_time().bits(phy_timers.stop_wait_time) + }); + } + + pub fn force_rx_low_power(&mut self, force: bool) { + self.dsi.wpcr1.modify(|_, w| w.flprxlpm().bit(force)); + } + + fn long_write( + &mut self, + cmd: u8, + buf: &[u8], + ghcr_dt: u8, + ) -> Result<(), Error> { + // debug!("{}, long {dcs_cmd:02x}, {buf:02x?}", self.write_idx); + // self.write_idx += 1; + + if buf.len() >= 65_535 { + // TODO: is it correct length? + return Err(Error::BufferIsToBig); + } + + // Put dcs_command and up to 3 bytes of data to GPDR + let mut fifoword = u32::from(cmd); + for (i, byte) in buf.iter().take(3).enumerate() { + fifoword |= (*byte as u32) << (8 + 8 * i); + } + self.dsi.gpdr.write(|w| unsafe { w.bits(fifoword) }); + //debug!("gpdr = {fifoword:08x}"); + + // Put the rest of the data, assuming that GPDR is accumulated in the hardware in some buffer. + if buf.len() > 3 { + let mut iter = buf[3..].chunks_exact(4); + for chunk in &mut iter { + let fifoword: [u8; 4] = chunk.try_into().unwrap(); + let fifoword = u32::from_ne_bytes(fifoword); //.swap_bytes(); + self.dsi.gpdr.write(|w| unsafe { w.bits(fifoword) }); + //debug!("gpdr = {fifoword:08x}"); + } + if !iter.remainder().is_empty() { + let mut fifoword = 0u32; + for (i, byte) in iter.remainder().iter().enumerate() { + fifoword |= (*byte as u32) << (8 * i); + } + self.dsi.gpdr.write(|w| unsafe { w.bits(fifoword) }); + //debug!("gpdr = {fifoword:08x}"); + } + } + + let len = buf.len() + 1; // dcs_cmd + actual data + self.ghcr_write(((len >> 8) & 0xff) as u8, (len & 0xff) as u8, ghcr_dt); + + Ok(()) + } + + fn ghcr_write(&mut self, msb: u8, lsb: u8, dt: u8) { + self.dsi.ghcr.write(|w| unsafe { + w // GHCR p. 1354 + .wcmsb() + .bits(msb) + .wclsb() + .bits(lsb) + .vcid() + .bits(self.channel as u8) + .dt() + .bits(dt) + }); + } + + pub fn start(&mut self) { + self.dsi.cr.modify(|_, w| w.en().set_bit()); + self.dsi.wcr.modify(|_, w| w.dsien().set_bit()); + } + + pub fn refresh(&mut self) { + self.dsi.wcr.modify(|_, w| w.ltdcen().set_bit()); + } + + pub fn refresh_handle(&self) -> DsiRefreshHandle { + let dsi = unsafe { crate::pac::Peripherals::steal().DSIHOST }; + DsiRefreshHandle { dsi } + } + + pub fn enable_bus_turn_around(&mut self) { + self.dsi.pcr.modify(|_, w| w.btae().set_bit()); // Enable bus turn around + } +} + +impl DsiRefreshHandle { + pub fn refresh_now(&mut self) { + self.dsi.wcr.modify(|_, w| w.ltdcen().set_bit()); + } + + // pub fn refresh_when_te_happens(&mut self) { + // cortex_m::interrupt::free(|_| unsafe { + // *self.refresh_request = true; + // }) + // } +} + +impl DsiHostCtrlIo for DsiHost { + type Error = Error; + + fn write(&mut self, kind: DsiWriteCommand) -> Result<(), Error> { + // debug!("DSI write: {:x?}", kind); + // wait for command fifo to be empty + block_with_timeout( + || self.dsi.gpsr.read().cmdfe() == false, + DSI_TIMEOUT_MS, + self.cycles_1ms, + Error::FifoTimeout, + )?; + match kind { + DsiWriteCommand::DcsShortP0 { .. } => todo!(), + DsiWriteCommand::DcsShortP1 { reg, data } => { + // debug!("{}, short_p1: reg: {reg:02x}, data: {data:02x}", self.write_idx); + // self.write_idx += 1; + self.ghcr_write(data, reg, kind.discriminant()); + } + DsiWriteCommand::DcsLongWrite { dcs_cmd, buf } => { + self.long_write(dcs_cmd, buf, kind.discriminant())? + } + DsiWriteCommand::GenericShortP0 => todo!(), + DsiWriteCommand::GenericShortP1 => todo!(), + DsiWriteCommand::GenericShortP2 => todo!(), + DsiWriteCommand::GenericLongWrite { cmd, buf } => { + self.long_write(cmd, buf, kind.discriminant())? + } + DsiWriteCommand::SetMaximumReturnPacketSize(len) => { + self.ghcr_write( + ((len >> 8) & 0xff) as u8, + (len & 0xff) as u8, + kind.discriminant(), + ); + } + } + Ok(()) + } + + fn read( + &mut self, + kind: DsiReadCommand, + buf: &mut [u8], + ) -> Result<(), Error> { + // println!("DSI read: {:x?}", kind); + if buf.len() > 2 && buf.len() <= 65_535 { + self.write(DsiWriteCommand::SetMaximumReturnPacketSize( + buf.len() as u16 + ))?; + } else if buf.len() > 65_535 { + return Err(Error::BufferIsToBig); + } + + match kind { + DsiReadCommand::DcsShort { dcs_cmd } => { + self.ghcr_write(0, dcs_cmd, kind.discriminant()); + } + DsiReadCommand::GenericShortP0 => { + self.ghcr_write(0, 0, kind.discriminant()); + } + DsiReadCommand::GenericShortP1 { arg0 } => { + self.ghcr_write(0, arg0, kind.discriminant()); + } + DsiReadCommand::GenericShortP2 { arg0, arg1 } => { + self.ghcr_write(arg1, arg0, kind.discriminant()); + } + } + + let mut idx = 0; + let mut bytes_left = buf.len(); + block_with_timeout( + || { + if bytes_left > 0 { + if self.dsi.gpsr.read().prdfe().bit_is_clear() { + // GPSR: p. 1355 + let fifoword = self.dsi.gpdr.read().bits(); + //debug!("fifoword read: {fifoword:08x}"); + for b in fifoword + // .swap_bytes() + .to_ne_bytes() + .iter() + .take(min(bytes_left, 4)) + { + buf[idx] = *b; + bytes_left -= 1; + idx += 1; + } + } + // Software workaround to avoid HAL_TIMEOUT when a DSI read command is + // issued to the panel and the read data is not captured by the DSI Host + // which returns Packet Size Error. + // Need to ensure that the Read command has finished before checking PSE + if self.dsi.gpsr.read().rcb().bit_is_clear() + && self.dsi.isr1.read().pse().bit_is_set() + { + return false; + } + true + } else { + false + } + }, + DSI_TIMEOUT_MS, + self.cycles_1ms, + Error::ReadTimeout, + ) + .map_err(|_| Error::ReadTimeout)?; + if bytes_left > 0 { + return Err(Error::ReadError); + } + Ok(()) + } +} + +fn block_with_timeout bool>( + mut f: F, + retries: usize, + delay_cycles: u32, + err: Error, +) -> Result<(), Error> { + for _ in 0..retries { + if f() { + cortex_m::asm::delay(delay_cycles); + } else { + return Ok(()); + } + } + //debug!("{name} {}", self.tim.counter()); + Err(err) +} diff --git a/src/lib.rs b/src/lib.rs index 054a7f65..c45297d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -217,6 +217,8 @@ pub mod usb_hs; #[cfg(all(feature = "device-selected", feature = "xspi"))] #[cfg_attr(docsrs, doc(cfg(feature = "xspi")))] pub mod xspi; +#[cfg(feature="dsi")] +pub mod dsi; #[cfg(feature = "device-selected")] mod sealed { From 2dfc9192c0c05d589f7b374511fea31a74035295 Mon Sep 17 00:00:00 2001 From: Roman Isaikin Date: Fri, 1 Dec 2023 12:02:15 +0100 Subject: [PATCH 2/8] Update dependencies to git links. --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index af5c61a1..bb2b6d14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ embedded-display-controller = { version = "^0.1.0", optional = true } log = { version = "0.4.14", optional = true} # see also the dev-dependencies section fdcan = { version = "0.2", optional = true } embedded-storage = "0.3" -embedded-dsi = { path = "../embedded-hal/embedded-dsi", optional = true } +embedded-dsi = { git = "https://github.com/romixlab/embedded-hal.git", branch = "dsi", optional = true } [dependencies.smoltcp] version = "0.10.0" @@ -82,7 +82,7 @@ usbd-serial = "0.2.0" numtoa = "0.2.3" tinybmp = "0.5" embedded-graphics = "0.8" -otm8009a = { path = "../otm8009a" } +otm8009a = { git = "https://github.com/romixlab/otm8009a.git" } eg-seven-segment = "0.2.0" ft6236 = { git = "https://github.com/romixlab/ft6236.git" } From cf8701e037d8475f0647c79b94e81bb429486ad0 Mon Sep 17 00:00:00 2001 From: Roman Isaikin Date: Wed, 24 Jan 2024 15:12:30 +0100 Subject: [PATCH 3/8] Update embedded-display-controller to 0.2. Use otm8009a from crates. Update DSI examples. --- Cargo.toml | 8 ++--- ...y-dsi-command-teartest-stm32h747i-disco.rs | 16 +--------- .../display-dsi-video-stm32h747i-disco.rs | 29 +++++++++---------- ...lay-dsi-video-teartest-stm32h747i-disco.rs | 16 +--------- src/dsi.rs | 18 ++++++------ 5 files changed, 28 insertions(+), 59 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bb2b6d14..791a2c60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,11 +42,10 @@ sdio-host = { version = "0.9", optional = true } embedded-sdmmc = { version = "0.5", optional = true } stm32-fmc = { version = "0.3", optional = true } synopsys-usb-otg = { version = "0.4", features = ["cortex-m"], optional = true } -embedded-display-controller = { version = "^0.1.0", optional = true } +embedded-display-controller = { version = "^0.2.0", optional = true } log = { version = "0.4.14", optional = true} # see also the dev-dependencies section fdcan = { version = "0.2", optional = true } embedded-storage = "0.3" -embedded-dsi = { git = "https://github.com/romixlab/embedded-hal.git", branch = "dsi", optional = true } [dependencies.smoltcp] version = "0.10.0" @@ -82,9 +81,8 @@ usbd-serial = "0.2.0" numtoa = "0.2.3" tinybmp = "0.5" embedded-graphics = "0.8" -otm8009a = { git = "https://github.com/romixlab/otm8009a.git" } +otm8009a = "0.1" eg-seven-segment = "0.2.0" -ft6236 = { git = "https://github.com/romixlab/ft6236.git" } [dev-dependencies.smoltcp] version = "0.10.0" @@ -104,7 +102,7 @@ gpio-h72 = [] gpio-h747 = [] gpio-h7a2 = [] -dsi = ["embedded-dsi", "embedded-display-controller"] +dsi = ["embedded-display-controller"] cm4 = [] cm7 = [] smps = [] diff --git a/examples/display-dsi-command-teartest-stm32h747i-disco.rs b/examples/display-dsi-command-teartest-stm32h747i-disco.rs index 6b642729..b673f40f 100644 --- a/examples/display-dsi-command-teartest-stm32h747i-disco.rs +++ b/examples/display-dsi-command-teartest-stm32h747i-disco.rs @@ -45,9 +45,8 @@ use embedded_display_controller::DisplayConfiguration; use otm8009a::Otm8009A; use stm32h7xx_hal::dsi::{ DsiCmdModeTransmissionKind, DsiHost, DsiInterrupts, DsiMode, DsiPhyTimers, - DsiVideoMode, LaneCount, + LaneCount, }; -use ft6236::{FT6236}; use crate::utilities::mpu_config::init_mpu; use crate::utilities::write::write_to::WriteTo; use core::fmt::Write; @@ -303,14 +302,6 @@ fn main() -> ! { let mut dsi_refresh_handle = dsi_host.refresh_handle(); info!("Initialised Display..."); - let scl = gpiod.pd12.into_alternate_open_drain(); - let sda = gpiod.pd13.into_alternate_open_drain(); - let i2c4 = - dp.I2C4 - .i2c((scl, sda), 100.kHz(), ccdr.peripheral.I2C4, &ccdr.clocks); - let mut touch_ctrl = FT6236::new(i2c4); - // let touch_int = gpiok.pk7 - let mut x = 0; let mut y = 0; let mut frame = 0; @@ -348,11 +339,6 @@ fn main() -> ! { frame += 1; colored_label(buf.as_str().unwrap(), 50, 20, Rgb888::RED, draw).unwrap(); - if let Ok(Some(pt)) = touch_ctrl.get_point0() { - info!("Touch: {} {}", pt.y, 480 - pt.x); - Circle::new(Point::new(480 - pt.x as i32 - 25, pt.y as i32 - 25), 50).into_styled(style_green).draw(draw).unwrap(); - } - display_test(draw).unwrap(); }); disp.swap_layer_wait(); diff --git a/examples/display-dsi-video-stm32h747i-disco.rs b/examples/display-dsi-video-stm32h747i-disco.rs index 65ee0471..6ed865cb 100644 --- a/examples/display-dsi-video-stm32h747i-disco.rs +++ b/examples/display-dsi-video-stm32h747i-disco.rs @@ -35,7 +35,6 @@ use embedded_display_controller::DisplayController; use chrono::{NaiveDateTime, NaiveTime}; use embedded_graphics::pixelcolor::Rgb888; use embedded_graphics::prelude::*; -use embedded_graphics::primitives::{Circle, PrimitiveStyleBuilder}; use embedded_display_controller::DisplayConfiguration; use otm8009a::Otm8009A; @@ -43,7 +42,6 @@ use stm32h7xx_hal::dsi::{ DsiCmdModeTransmissionKind, DsiHost, DsiInterrupts, DsiMode, DsiPhyTimers, DsiVideoMode, LaneCount, }; -use ft6236::{FT6236}; use crate::utilities::display_primitives::time_circuit; use crate::utilities::mpu_config::init_mpu; @@ -309,17 +307,18 @@ fn main() -> ! { //let mut dsi_refresh_handle = dsi_host.refresh_handle(); info!("Initialised Display..."); - let scl = gpiod.pd12.into_alternate_open_drain(); - let sda = gpiod.pd13.into_alternate_open_drain(); - let i2c4 = - dp.I2C4 - .i2c((scl, sda), 100.kHz(), ccdr.peripheral.I2C4, &ccdr.clocks); - let mut touch_ctrl = FT6236::new(i2c4); + // Works + // let scl = gpiod.pd12.into_alternate_open_drain(); + // let sda = gpiod.pd13.into_alternate_open_drain(); + // let i2c4 = + // dp.I2C4 + // .i2c((scl, sda), 100.kHz(), ccdr.peripheral.I2C4, &ccdr.clocks); + // let mut touch_ctrl = FT6236::new(i2c4); // let touch_int = gpiok.pk7 - let style_green = PrimitiveStyleBuilder::new() - .fill_color(Rgb888::GREEN) - .build(); + // let style_green = PrimitiveStyleBuilder::new() + // .fill_color(Rgb888::GREEN) + // .build(); loop { // Draw on a double buffered display @@ -332,10 +331,10 @@ fn main() -> ! { time_circuit(now, tc_x, 250, "PRESENT TIME", Rgb888::CSS_LIME_GREEN, Rgb888::new(15, 64, 15), draw).unwrap(); time_circuit(NaiveDateTime::new(NaiveDate::from_ymd_opt(1985, 10, 26).unwrap(), NaiveTime::from_hms_opt(01, 20, 0).unwrap()), tc_x, 400, "LAST TIME DEPARTED", Rgb888::CSS_ORANGE, Rgb888::new(77, 42, 0), draw).unwrap(); - if let Ok(Some(pt)) = touch_ctrl.get_point0() { - info!("Touch: {} {}", pt.y, 480 - pt.x); - Circle::new(Point::new(pt.y as i32 - 25, 480 - pt.x as i32 - 25), 50).into_styled(style_green).draw(draw).unwrap(); - } + // if let Ok(Some(pt)) = touch_ctrl.get_point0() { + // info!("Touch: {} {}", pt.y, 480 - pt.x); + // Circle::new(Point::new(pt.y as i32 - 25, 480 - pt.x as i32 - 25), 50).into_styled(style_green).draw(draw).unwrap(); + // } }); disp.swap_layer_wait(); } diff --git a/examples/display-dsi-video-teartest-stm32h747i-disco.rs b/examples/display-dsi-video-teartest-stm32h747i-disco.rs index 0d249daa..db751a76 100644 --- a/examples/display-dsi-video-teartest-stm32h747i-disco.rs +++ b/examples/display-dsi-video-teartest-stm32h747i-disco.rs @@ -39,7 +39,7 @@ use embedded_display_controller::DisplayController; use embedded_graphics::pixelcolor::Rgb888; use embedded_graphics::prelude::*; -use embedded_graphics::primitives::{Circle, PrimitiveStyleBuilder, Rectangle}; +use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle}; use embedded_display_controller::DisplayConfiguration; use otm8009a::Otm8009A; @@ -47,7 +47,6 @@ use stm32h7xx_hal::dsi::{ DsiCmdModeTransmissionKind, DsiHost, DsiInterrupts, DsiMode, DsiPhyTimers, DsiVideoMode, LaneCount, }; -use ft6236::{FT6236}; use crate::utilities::mpu_config::init_mpu; use crate::utilities::write::write_to::WriteTo; use core::fmt::Write; @@ -304,14 +303,6 @@ fn main() -> ! { //let mut dsi_refresh_handle = dsi_host.refresh_handle(); info!("Initialised Display..."); - let scl = gpiod.pd12.into_alternate_open_drain(); - let sda = gpiod.pd13.into_alternate_open_drain(); - let i2c4 = - dp.I2C4 - .i2c((scl, sda), 100.kHz(), ccdr.peripheral.I2C4, &ccdr.clocks); - let mut touch_ctrl = FT6236::new(i2c4); - // let touch_int = gpiok.pk7 - let mut x = 0; let mut y = 0; let mut frame = 0; @@ -349,11 +340,6 @@ fn main() -> ! { frame += 1; colored_label(buf.as_str().unwrap(), 50, 20, Rgb888::RED, draw).unwrap(); - if let Ok(Some(pt)) = touch_ctrl.get_point0() { - info!("Touch: {} {}", pt.y, 480 - pt.x); - Circle::new(Point::new(480 - pt.x as i32 - 25, pt.y as i32 - 25), 50).into_styled(style_green).draw(draw).unwrap(); - } - display_test(draw).unwrap(); }); disp.swap_layer_wait(); diff --git a/src/dsi.rs b/src/dsi.rs index 1a60fc76..d08e0395 100644 --- a/src/dsi.rs +++ b/src/dsi.rs @@ -6,7 +6,7 @@ use crate::{ }; use core::cmp::{max, min}; use embedded_display_controller::DisplayConfiguration; -use embedded_dsi::{DsiHostCtrlIo, DsiReadCommand, DsiWriteCommand}; +use embedded_display_controller::dsi::{DsiHostCtrlIo, DsiReadCommand, DsiWriteCommand}; #[cfg(feature = "log")] use log::debug; @@ -589,19 +589,19 @@ impl DsiHostCtrlIo for DsiHost { )?; match kind { DsiWriteCommand::DcsShortP0 { .. } => todo!(), - DsiWriteCommand::DcsShortP1 { reg, data } => { + DsiWriteCommand::DcsShortP1 { arg, data } => { // debug!("{}, short_p1: reg: {reg:02x}, data: {data:02x}", self.write_idx); // self.write_idx += 1; - self.ghcr_write(data, reg, kind.discriminant()); + self.ghcr_write(data, arg, kind.discriminant()); } - DsiWriteCommand::DcsLongWrite { dcs_cmd, buf } => { - self.long_write(dcs_cmd, buf, kind.discriminant())? + DsiWriteCommand::DcsLongWrite { arg, data } => { + self.long_write(arg, data, kind.discriminant())? } DsiWriteCommand::GenericShortP0 => todo!(), DsiWriteCommand::GenericShortP1 => todo!(), DsiWriteCommand::GenericShortP2 => todo!(), - DsiWriteCommand::GenericLongWrite { cmd, buf } => { - self.long_write(cmd, buf, kind.discriminant())? + DsiWriteCommand::GenericLongWrite { arg, data } => { + self.long_write(arg, data, kind.discriminant())? } DsiWriteCommand::SetMaximumReturnPacketSize(len) => { self.ghcr_write( @@ -629,8 +629,8 @@ impl DsiHostCtrlIo for DsiHost { } match kind { - DsiReadCommand::DcsShort { dcs_cmd } => { - self.ghcr_write(0, dcs_cmd, kind.discriminant()); + DsiReadCommand::DcsShort { arg } => { + self.ghcr_write(0, arg, kind.discriminant()); } DsiReadCommand::GenericShortP0 => { self.ghcr_write(0, 0, kind.discriminant()); From ae2c504e2d76b671468e3c23795d58a4baa0f0ee Mon Sep 17 00:00:00 2001 From: Roman Isaikin Date: Sat, 27 Jan 2024 12:42:48 +0100 Subject: [PATCH 4/8] Move display related utility functions to utilities_display mod. Clarify examples run commands. Reformat. --- Cargo.toml | 2 +- ...y-dsi-command-teartest-stm32h747i-disco.rs | 27 +++--- .../display-dsi-video-stm32h747i-disco.rs | 51 ++++++++-- ...lay-dsi-video-teartest-stm32h747i-disco.rs | 20 ++-- examples/embedded-graphics.rs | 3 +- examples/utilities/mod.rs | 7 -- .../display_primitives.rs | 93 +++++++++++-------- .../display_target.rs | 0 examples/utilities_display/mod.rs | 3 + .../{utilities => utilities_display}/write.rs | 7 +- src/dsi.rs | 25 +++-- src/lib.rs | 4 +- 12 files changed, 157 insertions(+), 85 deletions(-) rename examples/{utilities => utilities_display}/display_primitives.rs (71%) rename examples/{utilities => utilities_display}/display_target.rs (100%) create mode 100644 examples/utilities_display/mod.rs rename examples/{utilities => utilities_display}/write.rs (92%) diff --git a/Cargo.toml b/Cargo.toml index 791a2c60..bfe37aef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -267,7 +267,7 @@ required-features = ["revision_v"] [[example]] name = "display-dsi-video-stm32h747i-disco" -required-features = ["dsi", "ltdc", "fmc"] +required-features = ["dsi", "ltdc", "fmc", "rtc"] [[example]] name = "display-dsi-video-teartest-stm32h747i-disco" diff --git a/examples/display-dsi-command-teartest-stm32h747i-disco.rs b/examples/display-dsi-command-teartest-stm32h747i-disco.rs index b673f40f..e14907da 100644 --- a/examples/display-dsi-command-teartest-stm32h747i-disco.rs +++ b/examples/display-dsi-command-teartest-stm32h747i-disco.rs @@ -7,7 +7,7 @@ //! While in Landscape mode diagonal line is visible, because display is still refreshed //! in 480px lines from display's own graphics RAM, while writes are now 800px high. //! -//! Run command: cargo embed --release --features="stm32h747cm7,dsi,log,ltdc,fmc,rm0399,smps,example-smps,log-rtt,rt,rtc" --example display-dsi-command-teartest-stm32h747i-disco +//! Run command: cargo embed --release --features="stm32h747cm7,dsi,log,ltdc,fmc,example-smps,log-rtt,rt" --example display-dsi-command-teartest-stm32h747i-disco //! //! Tested on a STM32H747I-DISCO development board with a ST MB1166 Display //! (supplied together with the development kit). @@ -21,6 +21,8 @@ use core::{mem, slice}; #[macro_use] mod utilities; +mod utilities_display; + use log::info; use otm8009a::Otm8009AConfig; use stm32h7xx_hal::dsi::{ColorCoding, DsiChannel, DsiConfig, DsiPllConfig}; @@ -29,7 +31,7 @@ extern crate cortex_m; extern crate cortex_m_rt as rt; use cortex_m_rt::{entry, exception}; -use crate::utilities::display_target::BufferedDisplay; +use crate::utilities_display::display_target::BufferedDisplay; use stm32h7xx_hal::gpio::Speed; use stm32h7xx_hal::ltdc; use stm32h7xx_hal::stm32::rcc::d1ccipr::FMCSEL_A; @@ -39,19 +41,21 @@ use embedded_display_controller::DisplayController; use embedded_graphics::pixelcolor::Rgb888; use embedded_graphics::prelude::*; -use embedded_graphics::primitives::{Circle, PrimitiveStyleBuilder, Rectangle}; +use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle}; +use crate::utilities::mpu_config::init_mpu; +use crate::utilities_display::display_primitives::{ + colored_label, display_test, +}; +use crate::utilities_display::write::write_to::WriteTo; +use core::fmt::Write; use embedded_display_controller::DisplayConfiguration; use otm8009a::Otm8009A; use stm32h7xx_hal::dsi::{ DsiCmdModeTransmissionKind, DsiHost, DsiInterrupts, DsiMode, DsiPhyTimers, LaneCount, }; -use crate::utilities::mpu_config::init_mpu; -use crate::utilities::write::write_to::WriteTo; -use core::fmt::Write; use stm32h7xx_hal::rcc::PllConfigStrategy; -use crate::utilities::display_primitives::{colored_label, display_test}; // Remember to use correct display controller orientation, Portrait in this case pub const WIDTH: usize = 480; @@ -240,9 +244,7 @@ fn main() -> ! { let dsi_pll_config = unsafe { DsiPllConfig::manual(100, 5, 0, 4) }; let dsi_config = DsiConfig { - mode: DsiMode::AdaptedCommand { - tear_effect: None, - }, + mode: DsiMode::AdaptedCommand { tear_effect: None }, lane_count: LaneCount::DoubleLane, channel: DsiChannel::Ch0, hse_freq, @@ -337,7 +339,8 @@ fn main() -> ! { let mut buf = WriteTo::new(&mut buf); write!(&mut buf, "f: {frame}").unwrap(); frame += 1; - colored_label(buf.as_str().unwrap(), 50, 20, Rgb888::RED, draw).unwrap(); + colored_label(buf.as_str().unwrap(), 50, 20, Rgb888::RED, draw) + .unwrap(); display_test(draw).unwrap(); }); @@ -355,4 +358,4 @@ unsafe fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! { #[exception] unsafe fn DefaultHandler(irqn: i16) { panic!("Unhandled exception (IRQn = {})", irqn); -} \ No newline at end of file +} diff --git a/examples/display-dsi-video-stm32h747i-disco.rs b/examples/display-dsi-video-stm32h747i-disco.rs index 6ed865cb..e27fc9b4 100644 --- a/examples/display-dsi-video-stm32h747i-disco.rs +++ b/examples/display-dsi-video-stm32h747i-disco.rs @@ -2,7 +2,7 @@ //! an external display. The external display is connected through the DSI link. //! DSI Video mode is used, so the display is constantly refreshed by hardware. //! -//! Run command: cargo embed --release --features="stm32h747cm7,dsi,log,ltdc,fmc,rm0399,smps,example-smps,log-rtt,rt,rtc" --example display-dsi-video-stm32h747i-disco +//! Run command: cargo embed --release --features="stm32h747cm7,dsi,log,ltdc,fmc,example-smps,log-rtt,rt,rtc" --example display-dsi-video-stm32h747i-disco //! //! Tested on a STM32H747I-DISCO development board with a ST MB1166 Display //! (supplied together with the development kit). @@ -16,6 +16,8 @@ use core::{mem, slice}; #[macro_use] mod utilities; +mod utilities_display; + use log::info; use otm8009a::Otm8009AConfig; use stm32h7xx_hal::dsi::{ColorCoding, DsiChannel, DsiConfig, DsiPllConfig}; @@ -24,7 +26,7 @@ extern crate cortex_m; extern crate cortex_m_rt as rt; use cortex_m_rt::{entry, exception}; -use crate::utilities::display_target::BufferedDisplay; +use crate::utilities_display::display_target::BufferedDisplay; use stm32h7xx_hal::gpio::Speed; use stm32h7xx_hal::ltdc; use stm32h7xx_hal::stm32::rcc::d1ccipr::FMCSEL_A; @@ -36,14 +38,14 @@ use chrono::{NaiveDateTime, NaiveTime}; use embedded_graphics::pixelcolor::Rgb888; use embedded_graphics::prelude::*; +use crate::utilities::mpu_config::init_mpu; +use crate::utilities_display::display_primitives::time_circuit; use embedded_display_controller::DisplayConfiguration; use otm8009a::Otm8009A; use stm32h7xx_hal::dsi::{ DsiCmdModeTransmissionKind, DsiHost, DsiInterrupts, DsiMode, DsiPhyTimers, DsiVideoMode, LaneCount, }; -use crate::utilities::display_primitives::time_circuit; -use crate::utilities::mpu_config::init_mpu; pub const WIDTH: usize = 800; pub const HEIGHT: usize = 480; @@ -326,10 +328,43 @@ fn main() -> ! { draw.clear(); let tc_x = 54; - time_circuit(NaiveDateTime::new(NaiveDate::from_ymd_opt(1985, 10, 26).unwrap(), NaiveTime::from_hms_opt(01, 21, 0).unwrap()), tc_x, 100, "DESTINATION TIME", Rgb888::CSS_ORANGE_RED, Rgb888::new(102, 27, 0), draw).unwrap(); + time_circuit( + NaiveDateTime::new( + NaiveDate::from_ymd_opt(1985, 10, 26).unwrap(), + NaiveTime::from_hms_opt(01, 21, 0).unwrap(), + ), + tc_x, + 100, + "DESTINATION TIME", + Rgb888::CSS_ORANGE_RED, + Rgb888::new(102, 27, 0), + draw, + ) + .unwrap(); let now = rtc.date_time().unwrap(); - time_circuit(now, tc_x, 250, "PRESENT TIME", Rgb888::CSS_LIME_GREEN, Rgb888::new(15, 64, 15), draw).unwrap(); - time_circuit(NaiveDateTime::new(NaiveDate::from_ymd_opt(1985, 10, 26).unwrap(), NaiveTime::from_hms_opt(01, 20, 0).unwrap()), tc_x, 400, "LAST TIME DEPARTED", Rgb888::CSS_ORANGE, Rgb888::new(77, 42, 0), draw).unwrap(); + time_circuit( + now, + tc_x, + 250, + "PRESENT TIME", + Rgb888::CSS_LIME_GREEN, + Rgb888::new(15, 64, 15), + draw, + ) + .unwrap(); + time_circuit( + NaiveDateTime::new( + NaiveDate::from_ymd_opt(1985, 10, 26).unwrap(), + NaiveTime::from_hms_opt(01, 20, 0).unwrap(), + ), + tc_x, + 400, + "LAST TIME DEPARTED", + Rgb888::CSS_ORANGE, + Rgb888::new(77, 42, 0), + draw, + ) + .unwrap(); // if let Ok(Some(pt)) = touch_ctrl.get_point0() { // info!("Touch: {} {}", pt.y, 480 - pt.x); @@ -348,4 +383,4 @@ unsafe fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! { #[exception] unsafe fn DefaultHandler(irqn: i16) { panic!("Unhandled exception (IRQn = {})", irqn); -} \ No newline at end of file +} diff --git a/examples/display-dsi-video-teartest-stm32h747i-disco.rs b/examples/display-dsi-video-teartest-stm32h747i-disco.rs index db751a76..4b95ac8c 100644 --- a/examples/display-dsi-video-teartest-stm32h747i-disco.rs +++ b/examples/display-dsi-video-teartest-stm32h747i-disco.rs @@ -7,7 +7,7 @@ //! While in Landscape mode diagonal line is visible, because display is still refreshed //! in 480px lines from display's own graphics RAM, while writes are now 800px high. //! -//! Run command: cargo embed --release --features="stm32h747cm7,dsi,log,ltdc,fmc,rm0399,smps,example-smps,log-rtt,rt,rtc" --example display-dsi-video-teartest-stm32h747i-disco +//! Run command: cargo embed --release --features="stm32h747cm7,dsi,log,ltdc,fmc,example-smps,log-rtt,rt" --example display-dsi-video-teartest-stm32h747i-disco //! //! Tested on a STM32H747I-DISCO development board with a ST MB1166 Display //! (supplied together with the development kit). @@ -21,6 +21,7 @@ use core::{mem, slice}; #[macro_use] mod utilities; +mod utilities_display; use log::info; use otm8009a::Otm8009AConfig; use stm32h7xx_hal::dsi::{ColorCoding, DsiChannel, DsiConfig, DsiPllConfig}; @@ -29,7 +30,7 @@ extern crate cortex_m; extern crate cortex_m_rt as rt; use cortex_m_rt::{entry, exception}; -use crate::utilities::display_target::BufferedDisplay; +use crate::utilities_display::display_target::BufferedDisplay; use stm32h7xx_hal::gpio::Speed; use stm32h7xx_hal::ltdc; use stm32h7xx_hal::stm32::rcc::d1ccipr::FMCSEL_A; @@ -41,17 +42,19 @@ use embedded_graphics::pixelcolor::Rgb888; use embedded_graphics::prelude::*; use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle}; +use crate::utilities::mpu_config::init_mpu; +use crate::utilities_display::display_primitives::{ + colored_label, display_test, +}; +use crate::utilities_display::write::write_to::WriteTo; +use core::fmt::Write; use embedded_display_controller::DisplayConfiguration; use otm8009a::Otm8009A; use stm32h7xx_hal::dsi::{ DsiCmdModeTransmissionKind, DsiHost, DsiInterrupts, DsiMode, DsiPhyTimers, DsiVideoMode, LaneCount, }; -use crate::utilities::mpu_config::init_mpu; -use crate::utilities::write::write_to::WriteTo; -use core::fmt::Write; use stm32h7xx_hal::rcc::PllConfigStrategy; -use crate::utilities::display_primitives::{colored_label, display_test}; // Remember to use correct display controller orientation, Portrait in this case pub const WIDTH: usize = 480; @@ -338,7 +341,8 @@ fn main() -> ! { let mut buf = WriteTo::new(&mut buf); write!(&mut buf, "f: {frame}").unwrap(); frame += 1; - colored_label(buf.as_str().unwrap(), 50, 20, Rgb888::RED, draw).unwrap(); + colored_label(buf.as_str().unwrap(), 50, 20, Rgb888::RED, draw) + .unwrap(); display_test(draw).unwrap(); }); @@ -354,4 +358,4 @@ unsafe fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! { #[exception] unsafe fn DefaultHandler(irqn: i16) { panic!("Unhandled exception (IRQn = {})", irqn); -} \ No newline at end of file +} diff --git a/examples/embedded-graphics.rs b/examples/embedded-graphics.rs index 7c37cf01..cbdc2386 100644 --- a/examples/embedded-graphics.rs +++ b/examples/embedded-graphics.rs @@ -13,6 +13,7 @@ use core::{mem, slice}; #[macro_use] mod utilities; +mod utilities_display; use log::info; extern crate cortex_m; @@ -32,9 +33,9 @@ use embedded_graphics::mono_font::{ascii, MonoTextStyle}; use embedded_graphics::prelude::*; use embedded_graphics::text::Text; +use crate::utilities_display::display_target::BufferedDisplay; use numtoa::NumToA; use tinybmp::Bmp; -use crate::utilities::display_target::BufferedDisplay; const WIDTH: usize = 480; const HEIGHT: usize = 272; diff --git a/examples/utilities/mod.rs b/examples/utilities/mod.rs index 87f09dbf..63bfbc0c 100644 --- a/examples/utilities/mod.rs +++ b/examples/utilities/mod.rs @@ -6,10 +6,3 @@ mod power; #[cfg(feature = "fmc")] pub mod mpu_config; - -#[cfg(feature = "ltdc")] -pub mod display_target; -#[cfg(feature = "ltdc")] -pub mod write; -#[cfg(feature = "ltdc")] -pub mod display_primitives; \ No newline at end of file diff --git a/examples/utilities/display_primitives.rs b/examples/utilities_display/display_primitives.rs similarity index 71% rename from examples/utilities/display_primitives.rs rename to examples/utilities_display/display_primitives.rs index 6004ac8c..2cc75e88 100644 --- a/examples/utilities/display_primitives.rs +++ b/examples/utilities_display/display_primitives.rs @@ -7,7 +7,6 @@ use embedded_graphics::prelude::*; use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle}; use embedded_graphics::text::Text; -#[allow(dead_code)] pub fn colored_label( text: &str, x: i32, @@ -15,16 +14,16 @@ pub fn colored_label( color: Rgb888, target: &mut D, ) -> Result<(), D::Error> - where - D: DrawTarget, +where + D: DrawTarget, { let style_red = PrimitiveStyleBuilder::new().fill_color(color).build(); Rectangle::new( Point::new(x, y), Size::new((text.len() as u32) * 9 + 8, 22), ) - .into_styled(style_red) - .draw(target)?; + .into_styled(style_red) + .draw(target)?; let text_style = MonoTextStyle::new(&ascii::FONT_9X18_BOLD, RgbColor::WHITE); @@ -33,11 +32,9 @@ pub fn colored_label( } #[allow(dead_code)] -pub fn display_test( - target: &mut D, -) -> Result<(), D::Error> - where - D: DrawTarget, +pub fn display_test(target: &mut D) -> Result<(), D::Error> +where + D: DrawTarget, { let style_red = PrimitiveStyleBuilder::new().fill_color(Rgb888::RED).build(); @@ -58,28 +55,34 @@ pub fn display_test( Rectangle::new(Point::new(size.width as i32 - 10, 0), Size::new(10, 10)) .into_styled(style_green) .draw(target)?; - Rectangle::new( - Point::new(0, size.height as i32 - 10), - Size::new(10, 10), - ) + Rectangle::new(Point::new(0, size.height as i32 - 10), Size::new(10, 10)) .into_styled(style_blue) .draw(target)?; Rectangle::new( Point::new(size.width as i32 - 10, size.height as i32 - 10), Size::new(10, 10), ) - .into_styled(style_cyan) - .draw(target)?; + .into_styled(style_cyan) + .draw(target)?; - let ferris = tinybmp::Bmp::from_slice(include_bytes!("../ferris.bmp")).unwrap(); - let ferris = Image::new(&ferris, Point::new((size.width / 2 - ferris.size().width / 2) as i32, (size.height / 2 - ferris.size().height / 2) as i32)); + let ferris = + tinybmp::Bmp::from_slice(include_bytes!("../ferris.bmp")).unwrap(); + let ferris = Image::new( + &ferris, + Point::new( + (size.width / 2 - ferris.size().width / 2) as i32, + (size.height / 2 - ferris.size().height / 2) as i32, + ), + ); ferris.draw(target)?; Ok(()) } #[cfg(feature = "chrono")] #[allow(dead_code)] -pub fn seven_segment_style(color: Rgb888) -> eg_seven_segment::SevenSegmentStyle { +pub fn seven_segment_style( + color: Rgb888, +) -> eg_seven_segment::SevenSegmentStyle { eg_seven_segment::SevenSegmentStyleBuilder::new() .digit_size(Size::new(36, 48)) // digits are 10x20 pixels .digit_spacing(5) // 5px spacing between digits @@ -91,8 +94,8 @@ pub fn seven_segment_style(color: Rgb888) -> eg_seven_segment::SevenSegmentStyle #[cfg(feature = "chrono")] #[allow(dead_code)] pub fn date_labels(y: i32, target: &mut D) -> Result<(), D::Error> - where - D: DrawTarget, +where + D: DrawTarget, { let bg_color = Rgb888::RED; colored_label("MONTH", 86, y, bg_color, target)?; @@ -105,15 +108,22 @@ pub fn date_labels(y: i32, target: &mut D) -> Result<(), D::Error> #[cfg(feature = "chrono")] #[allow(dead_code)] -pub fn time_circuit(dt: NaiveDateTime, x: i32, y: i32, label: &str, fg: Rgb888, bg: Rgb888, target: &mut D) -> Result<(), D::Error> - where - D: DrawTarget, +pub fn time_circuit( + dt: NaiveDateTime, + x: i32, + y: i32, + label: &str, + fg: Rgb888, + bg: Rgb888, + target: &mut D, +) -> Result<(), D::Error> +where + D: DrawTarget, { let fg_text_style = seven_segment_style(fg); let bg_text_style = seven_segment_style(bg); let all_segments = "888 88 8888 88 88"; - Text::new(all_segments, Point::new(x, y), bg_text_style) - .draw(target)?; + Text::new(all_segments, Point::new(x, y), bg_text_style).draw(target)?; let month = match dt.date().month() { 1 => "JAN", 2 => "FEB", @@ -127,14 +137,23 @@ pub fn time_circuit(dt: NaiveDateTime, x: i32, y: i32, label: &str, fg: Rgb88 10 => "OCT", 11 => "NOU", 12 => "DEC", - _ => unreachable!() + _ => unreachable!(), }; let mut buf = [0u8; 17]; - use crate::utilities::write::write_to::WriteTo; + use crate::utilities_display::write::write_to::WriteTo; use core::fmt::Write; let mut buf = WriteTo::new(&mut buf); let (is_pm, hour) = dt.time().hour12(); - write!(&mut buf, "{} {:02} {:04} {:02} {:02}", month, dt.date().day(), dt.date().year(), hour, dt.time().minute()).unwrap(); + write!( + &mut buf, + "{} {:02} {:04} {:02} {:02}", + month, + dt.date().day(), + dt.date().year(), + hour, + dt.time().minute() + ) + .unwrap(); Text::new(buf.as_str().unwrap(), Point::new(x, y), fg_text_style) .draw(target)?; date_labels(y - 75, target)?; @@ -146,12 +165,8 @@ pub fn time_circuit(dt: NaiveDateTime, x: i32, y: i32, label: &str, fg: Rgb88 target, )?; - let fg_style = PrimitiveStyleBuilder::new() - .fill_color(fg) - .build(); - let bg_style = PrimitiveStyleBuilder::new() - .fill_color(bg) - .build(); + let fg_style = PrimitiveStyleBuilder::new().fill_color(fg).build(); + let bg_style = PrimitiveStyleBuilder::new().fill_color(bg).build(); colored_label("AM", 633, y - 65, Rgb888::RED, target)?; let (am_style, pm_style) = if is_pm { (bg_style, fg_style) @@ -159,8 +174,12 @@ pub fn time_circuit(dt: NaiveDateTime, x: i32, y: i32, label: &str, fg: Rgb88 (fg_style, bg_style) }; use embedded_graphics::primitives::Circle; - Circle::new(Point::new(641, y - 40), 10).into_styled(am_style).draw(target)?; + Circle::new(Point::new(641, y - 40), 10) + .into_styled(am_style) + .draw(target)?; colored_label("PM", 633, y - 26, Rgb888::RED, target)?; - Circle::new(Point::new(641, y), 10).into_styled(pm_style).draw(target)?; + Circle::new(Point::new(641, y), 10) + .into_styled(pm_style) + .draw(target)?; Ok(()) } diff --git a/examples/utilities/display_target.rs b/examples/utilities_display/display_target.rs similarity index 100% rename from examples/utilities/display_target.rs rename to examples/utilities_display/display_target.rs diff --git a/examples/utilities_display/mod.rs b/examples/utilities_display/mod.rs new file mode 100644 index 00000000..315a58cf --- /dev/null +++ b/examples/utilities_display/mod.rs @@ -0,0 +1,3 @@ +pub mod display_primitives; +pub mod display_target; +pub mod write; diff --git a/examples/utilities/write.rs b/examples/utilities_display/write.rs similarity index 92% rename from examples/utilities/write.rs rename to examples/utilities_display/write.rs index 7eabc933..c6b7bd9d 100644 --- a/examples/utilities/write.rs +++ b/examples/utilities_display/write.rs @@ -46,9 +46,12 @@ pub mod write_to { } #[allow(dead_code)] - pub fn show<'a>(buffer: &'a mut [u8], args: fmt::Arguments) -> Result<&'a str, fmt::Error> { + pub fn show<'a>( + buffer: &'a mut [u8], + args: fmt::Arguments, + ) -> Result<&'a str, fmt::Error> { let mut w = WriteTo::new(buffer); fmt::write(&mut w, args)?; w.as_str().ok_or(fmt::Error) } -} \ No newline at end of file +} diff --git a/src/dsi.rs b/src/dsi.rs index d08e0395..de41b890 100644 --- a/src/dsi.rs +++ b/src/dsi.rs @@ -5,8 +5,10 @@ use crate::{ time::Hertz, }; use core::cmp::{max, min}; +use embedded_display_controller::dsi::{ + DsiHostCtrlIo, DsiReadCommand, DsiWriteCommand, +}; use embedded_display_controller::DisplayConfiguration; -use embedded_display_controller::dsi::{DsiHostCtrlIo, DsiReadCommand, DsiWriteCommand}; #[cfg(feature = "log")] use log::debug; @@ -115,7 +117,7 @@ pub enum ColorCoding { SixteenBitsConfig3 = 0b010, EighteenBitsConfig1 = 0b011, EighteenBitsConfig2 = 0b100, - TwentyFourBits = 0b101 + TwentyFourBits = 0b101, } pub struct DsiConfig { @@ -219,7 +221,12 @@ impl DsiHost { let f_pix_khz = f_phy_hz / 1_000 / 8; let uix4 = 4_000_000_000 / f_phy_hz; #[cfg(feature = "log")] - debug!("f_phy={}kHz, f_pix={}kHz, uix4={}", f_phy_hz / 1_000, f_pix_khz, uix4); + debug!( + "f_phy={}kHz, f_pix={}kHz, uix4={}", + f_phy_hz / 1_000, + f_pix_khz, + uix4 + ); dsi.wpcr0 .modify(|_, w| unsafe { w.uix4().bits(uix4 as u8) }); // debug!("f_phy={}, uix4=override=8", f_phy); @@ -304,13 +311,15 @@ impl DsiHost { // Horizontal sync active (HSA) in lane byte clock cycles let f_ltdc_khz = dsi_config.ltdc_freq.to_kHz(); - let hsa = ((display_config.h_sync as u32) * f_pix_khz / f_ltdc_khz) as u16; + let hsa = ((display_config.h_sync as u32) * f_pix_khz + / f_ltdc_khz) as u16; #[cfg(feature = "log")] debug!("hsa={}", hsa); dsi.vhsacr.modify(|_, w| unsafe { w.hsa().bits(hsa) }); // Horizontal back porch (HBP) in lane byte clock cycles - let hbp = ((display_config.h_back_porch as u32) * f_pix_khz / f_ltdc_khz) as u16; + let hbp = ((display_config.h_back_porch as u32) * f_pix_khz + / f_ltdc_khz) as u16; #[cfg(feature = "log")] debug!("hbp={}", hbp); dsi.vhbpcr.modify(|_, w| unsafe { w.hbp().bits(hbp) }); @@ -398,7 +407,7 @@ impl DsiHost { let lpe = match dsi_config.color_coding_host { ColorCoding::EighteenBitsConfig1 => true, ColorCoding::EighteenBitsConfig2 => true, - _ => false + _ => false, }; dsi.lcolcr.modify(|_, w| unsafe { w.lpe() @@ -408,7 +417,9 @@ impl DsiHost { }); // Color coding for the wrapper - dsi.wcfgr.modify(|_, w| unsafe { w.colmux().bits(dsi_config.color_coding_wrapper as u8) }); + dsi.wcfgr.modify(|_, w| unsafe { + w.colmux().bits(dsi_config.color_coding_wrapper as u8) + }); dsi.lpmcr.modify(|_, w| unsafe { w.lpsize() diff --git a/src/lib.rs b/src/lib.rs index 295556fe..7b8ff309 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -156,6 +156,8 @@ pub mod dac; pub mod delay; #[cfg(feature = "device-selected")] pub mod dma; +#[cfg(feature = "dsi")] +pub mod dsi; #[cfg(all( feature = "device-selected", feature = "ethernet", @@ -217,8 +219,6 @@ pub mod usb_hs; #[cfg(all(feature = "device-selected", feature = "xspi"))] #[cfg_attr(docsrs, doc(cfg(feature = "xspi")))] pub mod xspi; -#[cfg(feature="dsi")] -pub mod dsi; #[cfg(feature = "device-selected")] mod sealed { From 7f67fe5d8e64160c2a1f116887939e9aad3f1b08 Mon Sep 17 00:00:00 2001 From: Roman Isaikin Date: Sat, 27 Jan 2024 14:56:05 +0100 Subject: [PATCH 5/8] Add attributes to utilities::init_mpu fn. --- examples/utilities/mpu_config.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/utilities/mpu_config.rs b/examples/utilities/mpu_config.rs index b91afa60..18a4f7e1 100644 --- a/examples/utilities/mpu_config.rs +++ b/examples/utilities/mpu_config.rs @@ -1,5 +1,6 @@ use cortex_m::peripheral::{MPU, SCB}; +#[allow(unused, unsafe_code)] pub fn init_mpu(mpu: MPU, scb: &mut SCB, sdram_size: usize) { // Refer to ARM®v7-M Architecture Reference Manual ARM DDI 0403 // Version E.b Section B3.5 @@ -74,4 +75,4 @@ pub fn init_mpu(mpu: MPU, scb: &mut SCB, sdram_size: usize) { cortex_m::asm::dsb(); cortex_m::asm::isb(); } -} \ No newline at end of file +} From c90484c1271794cbf8140f439bc14642e1f38f97 Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Sat, 27 Jan 2024 18:00:38 +0100 Subject: [PATCH 6/8] dsi: revert unneeded change to delay.rs --- src/delay.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/delay.rs b/src/delay.rs index d3bd2087..42f5b13a 100644 --- a/src/delay.rs +++ b/src/delay.rs @@ -49,7 +49,7 @@ use void::Void; use crate::nb::block; use crate::rcc::CoreClocks; -use crate::time::{Hertz, MicroSeconds}; +use crate::time::Hertz; use fugit::RateExtU32; pub trait DelayExt { From 34841e4a30eca7558bf589f805618e329d91a5b7 Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Sat, 27 Jan 2024 18:02:22 +0100 Subject: [PATCH 7/8] dsi: add to ci; dsi is only available on rm0399 parts --- .github/workflows/ci.yml | 2 +- .github/workflows/nightly.yml | 2 +- Cargo.toml | 24 ++++++++++++------------ src/dsi.rs | 4 ++++ src/lib.rs | 2 +- src/rcc/rec.rs | 2 +- 6 files changed, 20 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e9346b9..8c1743be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: - stm32h7b0 - stm32h735 env: # Peripheral Feature flags - FLAGS: rt,xspi,sdmmc,sdmmc-fatfs,fmc,usb_hs,rtc,ethernet,ltdc,crc,rand,can,defmt + FLAGS: rt,xspi,sdmmc,sdmmc-fatfs,fmc,usb_hs,rtc,ethernet,ltdc,crc,rand,can,dsi,defmt steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 4d991ec7..9e12fee8 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -19,7 +19,7 @@ jobs: - log-semihost - log-rtt env: # Peripheral Feature flags - FLAGS: rt,xspi,sdmmc,sdmmc-fatfs,fmc,usb_hs,rtc,ethernet,ltdc,crc,rand,can + FLAGS: rt,xspi,sdmmc,sdmmc-fatfs,fmc,usb_hs,rtc,ethernet,ltdc,crc,rand,can,dsi steps: - uses: actions/checkout@v4 diff --git a/Cargo.toml b/Cargo.toml index bfe37aef..6e568d36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -165,6 +165,18 @@ required-features = ["can"] name = "crc" required-features = ["crc"] +[[example]] +name = "display-dsi-video-stm32h747i-disco" +required-features = ["dsi", "ltdc", "fmc", "rtc", "rm0399"] + +[[example]] +name = "display-dsi-video-teartest-stm32h747i-disco" +required-features = ["dsi", "ltdc", "fmc", "rm0399"] + +[[example]] +name = "display-dsi-command-teartest-stm32h747i-disco" +required-features = ["dsi", "ltdc", "fmc", "rm0399"] + [[example]] name = "embedded-graphics" required-features = ["ltdc", "xspi", "rm0468"] @@ -264,15 +276,3 @@ required-features = ["rt", "usb_hs", "rm0433"] [[example]] name = "vos0" required-features = ["revision_v"] - -[[example]] -name = "display-dsi-video-stm32h747i-disco" -required-features = ["dsi", "ltdc", "fmc", "rtc"] - -[[example]] -name = "display-dsi-video-teartest-stm32h747i-disco" -required-features = ["dsi", "ltdc", "fmc"] - -[[example]] -name = "display-dsi-command-teartest-stm32h747i-disco" -required-features = ["dsi", "ltdc", "fmc"] diff --git a/src/dsi.rs b/src/dsi.rs index de41b890..4aff69f6 100644 --- a/src/dsi.rs +++ b/src/dsi.rs @@ -1,3 +1,7 @@ +//! Display Serial Interface +//! +//! Interface with MIPI D-PHY + use crate::rcc::CoreClocks; use crate::{ device::DSIHOST, diff --git a/src/lib.rs b/src/lib.rs index 7b8ff309..fd63fb5e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -156,7 +156,7 @@ pub mod dac; pub mod delay; #[cfg(feature = "device-selected")] pub mod dma; -#[cfg(feature = "dsi")] +#[cfg(all(feature = "device-selected", feature = "dsi", feature = "rm0399"))] pub mod dsi; #[cfg(all( feature = "device-selected", diff --git a/src/rcc/rec.rs b/src/rcc/rec.rs index 6b2390da..e2cc75d6 100644 --- a/src/rcc/rec.rs +++ b/src/rcc/rec.rs @@ -698,7 +698,7 @@ peripheral_reset_and_enable_control! { #[cfg(all())] APB3, "Advanced Peripheral Bus 3 (APB3) peripherals" => [ Ltdc [fixed clk: "pll3_r_ck"], - #[cfg(any(feature = "dsi"))] Dsi + #[cfg(any(feature = "rm0399"))] Dsi ]; From 891d9f53f004632296321904739e690ec45168ff Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Sat, 27 Jan 2024 18:26:58 +0100 Subject: [PATCH 8/8] dsi: increase MSRV to 1.66.1 --- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 2 ++ Cargo.toml | 2 +- README.md | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c1743be..66dbe040 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: strategy: matrix: # All permutations of {rust, mcu} rust: - - 1.65.0 # MSRV + - 1.66.1 # MSRV - stable mcu: - stm32h743 diff --git a/CHANGELOG.md b/CHANGELOG.md index b5bd57bc..30893f4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## [Unreleased] +* MSRV increased to Rust 1.66.1 [#473] + ## [v0.15.1] 2023-11-03 * Bugfix, usb: On RM0455 and RM0468 parts, PA11/PA12 do not have an alternate function diff --git a/Cargo.toml b/Cargo.toml index 6e568d36..8b04bee5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ authors = ["Andrew Straw ", "Florian Jung ", "Matt Ickstadt "] edition = "2021" -rust-version = "1.65" +rust-version = "1.66.1" categories = ["embedded", "hardware-support", "no-std"] description = "Hardware Abstraction Layer implementation for STM32H7 series microcontrollers" keywords = ["arm", "cortex-m", "stm32h7xx", "hal", "embedded-hal"] diff --git a/README.md b/README.md index 9b42e16a..a7474095 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ stm32h7xx-hal [![docs.rs](https://docs.rs/stm32h7xx-hal/badge.svg)](https://docs.rs/stm32h7xx-hal) [![CI](https://github.com/stm32-rs/stm32h7xx-hal/workflows/Continuous%20integration/badge.svg)](https://github.com/stm32-rs/stm32h7xx-hal/actions) [![Crates.io](https://img.shields.io/crates/v/stm32h7xx-hal.svg)](https://crates.io/crates/stm32h7xx-hal) -![Minimum rustc version](https://img.shields.io/badge/rustc-1.65.0+-yellow.svg) +![Minimum rustc version](https://img.shields.io/badge/rustc-1.66.1+-yellow.svg) [_stm32h7xx-hal_](https://github.com/stm32-rs/stm32h7xx-hal) contains a hardware abstraction layer on top of the peripheral access API for @@ -110,7 +110,7 @@ programming interfaces are only available on the high density connectors. Minimum supported Rust version ------------------------------ -The Minimum Supported Rust Version (MSRV) at the moment is **1.65.0**. Older +The Minimum Supported Rust Version (MSRV) at the moment is **1.66.1**. Older versions **may** compile, especially when some features are not used in your application.