mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-02 14:51:15 +00:00

... while still being kebab-case in .slint Some enums might become public API and we want to have them as PascalCase to respect the rust conventions
396 lines
16 KiB
Rust
396 lines
16 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
|
|
|
extern crate alloc;
|
|
|
|
use super::TargetPixel;
|
|
pub use cortex_m_rt::entry;
|
|
use embedded_display_controller::{DisplayController, DisplayControllerLayer};
|
|
use embedded_graphics::prelude::RgbColor;
|
|
use hal::delay::Delay;
|
|
use hal::gpio::Speed::High;
|
|
use hal::ltdc::LtdcLayer1;
|
|
use hal::pac;
|
|
use hal::prelude::*;
|
|
use hal::rcc::rec::OctospiClkSelGetter;
|
|
use stm32h7xx_hal as hal;
|
|
|
|
use defmt_rtt as _; // global logger
|
|
|
|
#[cfg(feature = "panic-probe")]
|
|
use panic_probe as _;
|
|
|
|
#[alloc_error_handler]
|
|
fn oom(layout: core::alloc::Layout) -> ! {
|
|
panic!("Out of memory {:?}", layout);
|
|
}
|
|
use alloc_cortex_m::CortexMHeap;
|
|
|
|
use crate::{Devices, PhysicalLength, PhysicalRect, PhysicalSize};
|
|
|
|
const HEAP_SIZE: usize = 200 * 1024;
|
|
static mut HEAP: [u8; HEAP_SIZE] = [0; HEAP_SIZE];
|
|
|
|
const DISPLAY_WIDTH: usize = 480;
|
|
const DISPLAY_HEIGHT: usize = 272;
|
|
|
|
#[global_allocator]
|
|
static ALLOCATOR: CortexMHeap = CortexMHeap::empty();
|
|
|
|
pub fn init() {
|
|
let mut cp = cortex_m::Peripherals::take().unwrap();
|
|
let dp = pac::Peripherals::take().unwrap();
|
|
|
|
unsafe { ALLOCATOR.init(&mut HEAP as *const u8 as usize, core::mem::size_of_val(&HEAP)) }
|
|
|
|
let pwr = dp.PWR.constrain();
|
|
let pwrcfg = pwr.smps().freeze();
|
|
let rcc = dp.RCC.constrain();
|
|
let ccdr = rcc
|
|
.sys_ck(400.MHz())
|
|
// numbers adapted from Drivers/BSP/STM32H735G-DK/stm32h735g_discovery_ospi.c
|
|
// MX_OSPI_ClockConfig
|
|
.pll2_p_ck(400.MHz() / 5)
|
|
.pll2_q_ck(400.MHz() / 2)
|
|
.pll2_r_ck(400.MHz() / 2)
|
|
// numbers adapted from Drivers/BSP/STM32H735G-DK/stm32h735g_discovery_lcd.c
|
|
// MX_LTDC_ClockConfig
|
|
.pll3_p_ck(800.MHz() / 2)
|
|
.pll3_q_ck(800.MHz() / 2)
|
|
.pll3_r_ck(800.MHz() / 83)
|
|
.freeze(pwrcfg, &dp.SYSCFG);
|
|
|
|
assert_eq!(ccdr.clocks.hclk(), 200.MHz::<1, 1>());
|
|
// Octospi from HCLK at 200MHz
|
|
assert_eq!(
|
|
ccdr.peripheral.OCTOSPI2.get_kernel_clk_mux(),
|
|
hal::rcc::rec::OctospiClkSel::RCC_HCLK3
|
|
);
|
|
assert_eq!(
|
|
ccdr.peripheral.OCTOSPI1.get_kernel_clk_mux(),
|
|
hal::rcc::rec::OctospiClkSel::RCC_HCLK3
|
|
);
|
|
|
|
let mut delay = Delay::new(cp.SYST, ccdr.clocks);
|
|
|
|
cp.SCB.invalidate_icache();
|
|
cp.SCB.enable_icache();
|
|
cp.SCB.enable_dcache(&mut cp.CPUID);
|
|
cp.DWT.enable_cycle_counter();
|
|
|
|
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);
|
|
|
|
// setup OCTOSPI HyperRAM
|
|
let _tracweswo = gpiob.pb3.into_alternate::<0>();
|
|
let _ncs = gpiog.pg12.into_alternate::<3>().speed(High).internal_pull_up(true);
|
|
let _dqs = gpiof.pf12.into_alternate::<9>().speed(High).internal_pull_up(true);
|
|
let _clk = gpiof.pf4.into_alternate::<9>().speed(High).internal_pull_up(true);
|
|
let _io0 = gpiof.pf0.into_alternate::<9>().speed(High).internal_pull_up(true);
|
|
let _io1 = gpiof.pf1.into_alternate::<9>().speed(High).internal_pull_up(true);
|
|
let _io2 = gpiof.pf2.into_alternate::<9>().speed(High).internal_pull_up(true);
|
|
let _io3 = gpiof.pf3.into_alternate::<9>().speed(High).internal_pull_up(true);
|
|
let _io4 = gpiog.pg0.into_alternate::<9>().speed(High).internal_pull_up(true);
|
|
let _io5 = gpiog.pg1.into_alternate::<9>().speed(High).internal_pull_up(true);
|
|
let _io6 = gpiog.pg10.into_alternate::<3>().speed(High).internal_pull_up(true);
|
|
let _io7 = gpiog.pg11.into_alternate::<9>().speed(High).internal_pull_up(true);
|
|
|
|
let hyperram_size = 16 * 1024 * 1024; // 16 MByte
|
|
let config = hal::xspi::HyperbusConfig::new(80.MHz())
|
|
.device_size_bytes(24) // 16 Mbyte
|
|
.refresh_interval(4.micros())
|
|
.read_write_recovery(4) // 50ns
|
|
.access_initial_latency(6);
|
|
|
|
let hyperram =
|
|
dp.OCTOSPI2.octospi_hyperbus_unchecked(config, &ccdr.clocks, ccdr.peripheral.OCTOSPI2);
|
|
let hyperram_ptr: *mut u32 = hyperram.init();
|
|
|
|
let _ncs = gpiog.pg6.into_alternate::<10>().speed(High).internal_pull_up(true);
|
|
let _clk = gpiof.pf10.into_alternate::<9>().speed(High).internal_pull_up(true);
|
|
let _dqs = gpiob.pb2.into_alternate::<10>().speed(High).internal_pull_up(true);
|
|
let _io0 = gpiod.pd11.into_alternate::<9>().speed(High).internal_pull_up(true);
|
|
let _io1 = gpiod.pd12.into_alternate::<9>().speed(High).internal_pull_up(true);
|
|
let _io2 = gpioe.pe2.into_alternate::<9>().speed(High).internal_pull_up(true);
|
|
let _io3 = gpiod.pd13.into_alternate::<9>().speed(High).internal_pull_up(true);
|
|
let _io4 = gpiod.pd4.into_alternate::<10>().speed(High).internal_pull_up(true);
|
|
let _io5 = gpiod.pd5.into_alternate::<10>().speed(High).internal_pull_up(true);
|
|
let _io6 = gpiog.pg9.into_alternate::<9>().speed(High).internal_pull_up(true);
|
|
let _io7 = gpiod.pd7.into_alternate::<10>().speed(High).internal_pull_up(true);
|
|
|
|
use stm32h7xx_hal::xspi::*;
|
|
use OctospiWord as XW;
|
|
|
|
let mut octospi =
|
|
dp.OCTOSPI1.octospi_unchecked(12.MHz(), &ccdr.clocks, ccdr.peripheral.OCTOSPI1);
|
|
|
|
// Switch Macronix MX25LM51245GXDI00 to SDR OPI
|
|
// Set WREN bit
|
|
octospi.write_extended(XW::U8(0x06), XW::None, XW::None, &[]).unwrap();
|
|
// Write Configuration Register 2
|
|
octospi.write_extended(XW::U8(0x72), XW::U32(0), XW::None, &[1]).unwrap();
|
|
// Change bus mode
|
|
octospi.configure_mode(OctospiMode::EightBit).unwrap();
|
|
|
|
const MX25LM51245G_OCTA_READ_CFG_REG2_CMD: u16 = 0x718E;
|
|
const MX25LM51245G_CR2_REG1_ADDR: u32 = 0x00000000;
|
|
const MX25LM51245G_OCTA_READ_CMD: u16 = 0xEC13;
|
|
|
|
// check the config register
|
|
let mut read: [u8; 1] = [0];
|
|
octospi
|
|
.read_extended(
|
|
XW::U16(MX25LM51245G_OCTA_READ_CFG_REG2_CMD),
|
|
XW::U32(MX25LM51245G_CR2_REG1_ADDR),
|
|
XW::None,
|
|
5,
|
|
&mut read,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(read[0], 1);
|
|
|
|
extern "C" {
|
|
static mut __s_slint_assets: u8;
|
|
static __e_slint_assets: u8;
|
|
static __si_slint_assets: u8;
|
|
}
|
|
|
|
unsafe {
|
|
let asset_mem_slice = core::slice::from_raw_parts_mut(
|
|
&mut __s_slint_assets as *mut u8,
|
|
&__e_slint_assets as *const u8 as usize - &__s_slint_assets as *const u8 as usize,
|
|
);
|
|
let mut asset_flash_addr = &__si_slint_assets as *const u8 as usize - 0x9000_0000;
|
|
for chunk in asset_mem_slice.chunks_mut(32) {
|
|
octospi
|
|
.read_extended(
|
|
XW::U16(MX25LM51245G_OCTA_READ_CMD),
|
|
XW::U32(asset_flash_addr as u32),
|
|
XW::None,
|
|
20,
|
|
chunk,
|
|
)
|
|
.unwrap();
|
|
asset_flash_addr += chunk.len();
|
|
}
|
|
}
|
|
|
|
/*
|
|
let mut led_red = gpioc.pc2.into_push_pull_output();
|
|
led_red.set_low(); // low mean "on"
|
|
let mut led_green = gpioc.pc3.into_push_pull_output();
|
|
led_green.set_low();
|
|
*/
|
|
|
|
#[link_section = ".frame_buffer"]
|
|
static mut FB1: [TargetPixel; DISPLAY_WIDTH * DISPLAY_HEIGHT] =
|
|
[TargetPixel::BLACK; DISPLAY_WIDTH * DISPLAY_HEIGHT];
|
|
#[link_section = ".frame_buffer"]
|
|
static mut FB2: [TargetPixel; DISPLAY_WIDTH * DISPLAY_HEIGHT] =
|
|
[TargetPixel::BLACK; DISPLAY_WIDTH * DISPLAY_HEIGHT];
|
|
// SAFETY the init function is only called once (as enforced by Peripherals::take)
|
|
let (fb1, fb2) = unsafe { (&mut FB1, &mut FB2) };
|
|
|
|
assert!((hyperram_ptr as usize..hyperram_ptr as usize + hyperram_size)
|
|
.contains(&(fb1.as_ptr() as usize)));
|
|
assert!((hyperram_ptr as usize..hyperram_ptr as usize + hyperram_size)
|
|
.contains(&(fb2.as_ptr() as usize)));
|
|
|
|
// setup LTDC (LTDC_MspInit)
|
|
let _p = gpioa.pa3.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpioa.pa4.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpioa.pa6.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpiob.pb0.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpiob.pb1.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpiob.pb8.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpiob.pb9.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpioc.pc6.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpioc.pc7.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpiod.pd0.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpiod.pd3.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpiod.pd6.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpioe.pe0.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpioe.pe1.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpioe.pe11.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpioe.pe12.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpioe.pe15.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpiog.pg7.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpiog.pg14.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpioh.ph3.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpioh.ph8.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpioh.ph9.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpioh.ph10.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpioh.ph11.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpioh.ph15.into_alternate::<14>().speed(High).internal_pull_up(true);
|
|
let _p = gpioa.pa8.into_alternate::<13>().speed(High).internal_pull_up(true);
|
|
let _p = gpioh.ph4.into_alternate::<9>().speed(High).internal_pull_up(true);
|
|
|
|
let mut lcd_disp_en = gpioe.pe13.into_push_pull_output();
|
|
let mut lcd_disp_ctrl = gpiod.pd10.into_push_pull_output();
|
|
let mut lcd_bl_ctrl = gpiog.pg15.into_push_pull_output();
|
|
|
|
delay.delay_ms(40u8);
|
|
// End LTDC_MspInit
|
|
|
|
let mut ltdc = hal::ltdc::Ltdc::new(dp.LTDC, ccdr.peripheral.LTDC, &ccdr.clocks);
|
|
|
|
const RK043FN48H_HSYNC: u16 = 41; /* Horizontal synchronization */
|
|
const RK043FN48H_HBP: u16 = 13; /* Horizontal back porch */
|
|
const RK043FN48H_HFP: u16 = 32; /* Horizontal front porch */
|
|
const RK043FN48H_VSYNC: u16 = 10; /* Vertical synchronization */
|
|
const RK043FN48H_VBP: u16 = 2; /* Vertical back porch */
|
|
const RK043FN48H_VFP: u16 = 2; /* Vertical front porch */
|
|
|
|
ltdc.init(embedded_display_controller::DisplayConfiguration {
|
|
active_width: DISPLAY_WIDTH as _,
|
|
active_height: DISPLAY_HEIGHT as _,
|
|
h_back_porch: RK043FN48H_HBP - 11, // -11 from MX_LTDC_Init
|
|
h_front_porch: RK043FN48H_HFP,
|
|
v_back_porch: RK043FN48H_VBP,
|
|
v_front_porch: RK043FN48H_VFP,
|
|
h_sync: RK043FN48H_HSYNC,
|
|
v_sync: RK043FN48H_VSYNC,
|
|
h_sync_pol: false,
|
|
v_sync_pol: false,
|
|
not_data_enable_pol: false,
|
|
pixel_clock_pol: false,
|
|
});
|
|
let mut layer = ltdc.split();
|
|
|
|
// Safety: the frame buffer has the right size
|
|
unsafe {
|
|
layer.enable(fb1.as_ptr() as *const u8, embedded_display_controller::PixelFormat::RGB565);
|
|
}
|
|
|
|
lcd_disp_en.set_low();
|
|
lcd_disp_ctrl.set_high();
|
|
lcd_bl_ctrl.set_high();
|
|
|
|
// Init Timer
|
|
let mut timer = dp.TIM2.tick_timer(10000.Hz(), ccdr.peripheral.TIM2, &ccdr.clocks);
|
|
timer.listen(hal::timer::Event::TimeOut);
|
|
|
|
// Init Touch screen
|
|
let scl = gpiof.pf14.into_alternate::<4>().set_open_drain().speed(High).internal_pull_up(true);
|
|
let sda = gpiof.pf15.into_alternate::<4>().set_open_drain().speed(High).internal_pull_up(true);
|
|
let mut touch_i2c = dp.I2C4.i2c((scl, sda), 100u32.kHz(), ccdr.peripheral.I2C4, &ccdr.clocks);
|
|
|
|
{
|
|
let mut ft5336 = touch_device(&mut delay, &mut touch_i2c);
|
|
ft5336.init(&mut touch_i2c);
|
|
}
|
|
|
|
crate::init_with_display(StmDevices {
|
|
work_fb: fb2,
|
|
displayed_fb: fb1,
|
|
line_buffer: alloc::vec![TargetPixel::default(); DISPLAY_WIDTH],
|
|
layer,
|
|
timer,
|
|
delay,
|
|
touch_i2c,
|
|
system_control_block: cp.SCB,
|
|
last_touch: Default::default(),
|
|
prev_dirty: Default::default(),
|
|
});
|
|
}
|
|
|
|
struct StmDevices {
|
|
/// The frame buffer which is not currently shown and on which we can operate
|
|
work_fb: &'static mut [TargetPixel],
|
|
/// The frame buffer which is currently displayed
|
|
displayed_fb: &'static mut [TargetPixel],
|
|
/// A buffer that holds the line in the "fast" ram
|
|
line_buffer: alloc::vec::Vec<TargetPixel>,
|
|
layer: LtdcLayer1,
|
|
timer: hal::timer::Timer<pac::TIM2>,
|
|
delay: Delay,
|
|
touch_i2c: TouchI2C,
|
|
last_touch: Option<i_slint_core::graphics::Point>,
|
|
system_control_block: hal::device::SCB,
|
|
|
|
/// When using double frame buffer, this is the part still dirty in the other buffer
|
|
prev_dirty: PhysicalRect,
|
|
}
|
|
|
|
impl Devices for StmDevices {
|
|
fn screen_size(&self) -> PhysicalSize {
|
|
PhysicalSize::new(DISPLAY_WIDTH as _, DISPLAY_HEIGHT as _)
|
|
}
|
|
|
|
fn get_buffer(&mut self) -> Option<(&mut [TargetPixel], PhysicalRect)> {
|
|
while self.layer.is_swap_pending() {}
|
|
Some((self.work_fb, self.prev_dirty))
|
|
}
|
|
|
|
fn prepare_frame(&mut self, dirty_region: PhysicalRect) -> PhysicalRect {
|
|
dirty_region.union(&core::mem::replace(&mut self.prev_dirty, dirty_region))
|
|
}
|
|
|
|
fn render_line(
|
|
&mut self,
|
|
line: PhysicalLength,
|
|
dirty_region: crate::renderer::DirtyRegion,
|
|
fill_buffer: &mut dyn FnMut(&mut [TargetPixel]),
|
|
) {
|
|
let line = line.get() as usize;
|
|
fill_buffer(&mut self.line_buffer);
|
|
while self.layer.is_swap_pending() {}
|
|
let region = dirty_region.cast::<usize>();
|
|
self.work_fb[line * DISPLAY_WIDTH + region.min_x()..line * DISPLAY_WIDTH + region.max_x()]
|
|
.copy_from_slice(&self.line_buffer[region.min_x()..region.max_x()]);
|
|
|
|
// We don't render directly to the frame buffer because the frame buffer is in a slower RAM
|
|
//fill_buffer(&mut self.work_fb[line * DISPLAY_WIDTH..(line + 1) * DISPLAY_WIDTH])
|
|
}
|
|
|
|
fn flush_frame(&mut self) {
|
|
self.system_control_block.clean_dcache_by_slice(self.work_fb);
|
|
// Safety: the frame buffer has the right size
|
|
unsafe { self.layer.swap_framebuffer(self.work_fb.as_ptr() as *const u8) };
|
|
// Swap the buffer pointer so we will work now on the second buffer
|
|
core::mem::swap::<&mut [_]>(&mut self.work_fb, &mut self.displayed_fb);
|
|
}
|
|
|
|
fn debug(&mut self, text: &str) {
|
|
i_slint_core::debug_log!("Debug: {}", text);
|
|
}
|
|
|
|
fn read_touch_event(&mut self) -> Option<i_slint_core::input::MouseEvent> {
|
|
let mut ft5336 = touch_device(&mut self.delay, &mut self.touch_i2c);
|
|
let touch = ft5336.detect_touch(&mut self.touch_i2c).unwrap();
|
|
let button = i_slint_core::items::PointerEventButton::Left;
|
|
|
|
if touch > 0 {
|
|
let state = ft5336.get_touch(&mut self.touch_i2c, 1).unwrap();
|
|
let pos = i_slint_core::graphics::Point::new(state.y as _, state.x as _);
|
|
Some(match self.last_touch.replace(pos) {
|
|
Some(_) => i_slint_core::input::MouseEvent::MouseMoved { pos },
|
|
None => i_slint_core::input::MouseEvent::MousePressed { pos, button },
|
|
})
|
|
} else {
|
|
self.last_touch
|
|
.take()
|
|
.map(|pos| i_slint_core::input::MouseEvent::MouseReleased { pos, button })
|
|
}
|
|
}
|
|
|
|
fn time(&self) -> core::time::Duration {
|
|
// FIXME! the timer can overflow
|
|
let val = self.timer.counter() / 10;
|
|
core::time::Duration::from_millis(val.into())
|
|
}
|
|
}
|
|
|
|
type TouchI2C = hal::i2c::I2c<pac::I2C4>;
|
|
fn touch_device<'a>(
|
|
delay: &'a mut Delay,
|
|
touch_i2c: &mut TouchI2C,
|
|
) -> ft5336::Ft5336<'a, TouchI2C> {
|
|
ft5336::Ft5336::new(touch_i2c, 0x70 >> 1, delay).unwrap()
|
|
}
|