【rust/esp32】初识slint ui框架并在st7789 lcd上显示

文章目录

  • 说在前面
  • 关于slint
  • 关于no-std
  • 关于dma
  • 准备工作
  • 相关依赖
  • 代码
  • 结果
  • 参考

说在前面

  • esp32版本:s3
  • 运行环境:no-std
  • 开发环境:wsl2
  • LCD模块:ST7789V2 240*280 LCD
  • Slint版本:master分支
  • github地址:这里

关于slint

  • 官网
  • 为啥不用lvgl
    只能说rust的生态还是不太行,lvgl的rust binding似乎还在开发中,已经有仓库了,但是还在开发中。
    slint目前比较完善,但是相关资料也少。
    反正已经在折腾rust了,也不在乎再多折腾个小众点的。

关于no-std

  • 上一篇还是std环境,怎么就变成no-std了?
    std环境下也折腾了slint,但是fps就是不怎么高 (可能哪里写的不对) ,然后就试了下no-std,稍微丝滑点,并且编译也快。

关于dma

  • rust生态下dma的资料更是少的可怜,找了好久也没啥进展,所以本文不涉及dma

准备工作

  • 引脚连接见上篇
  • 开发环境部分,由于不需要esp idf,简单很多,cargo直接搞定

相关依赖

  • Cargo.toml
    [dependencies]
    hal = { package = "esp32s3-hal", version = "0.13.0"}
    esp-backtrace = { version = "0.9.0", features = ["esp32s3", "panic-handler", "exception-handler", "print-uart"] }
    esp-println = { version = "0.7.0", features = ["esp32s3","log"] }
    log = { version = "0.4.18" }
    esp-alloc = { version = "0.3.0" }
    embedded-hal = "0.2.7"
    embedded-graphics-core = "0.4.0"
    embedded-graphics = "0.8.1" 
    embedded-graphics-framebuf = "0.5.0"
    display-interface = "0.4"
    display-interface-spi = "0.4"
    mipidsi = "0.7.1"
    slint = { git = "https://githubfast.com/slint-ui/slint", default-features = false, features = ["compat-1-2","unsafe-single-threaded","libm", "renderer-software"] }
    
    [build-dependencies]
    slint-build = { git = "https://githubfast.com/slint-ui/slint" }
    

代码

#![no_std]
#![no_main]

extern crate alloc;
use alloc::boxed::Box;
use alloc::rc::Rc;
use rs_esp32s3_no_std_st7789_demo::dma::DmaBackend;
use core::cell::RefCell;
use core::mem::MaybeUninit;
use display_interface_spi::SPIInterfaceNoCS;
use embedded_graphics_core::prelude::{DrawTarget, Point, RgbColor, Size};
use embedded_graphics_core::{pixelcolor::raw::RawU16, primitives::Rectangle};
use esp_backtrace as _;
use esp_println::println;
use hal::spi::master::{Spi, dma};
use hal::{
    clock::{ClockControl, CpuClock},
    peripherals::Peripherals,
    prelude::*,
    spi::SpiMode,
    systimer::SystemTimer,
    timer::TimerGroup,
    Delay, Rtc, IO,
};
use mipidsi::Display;

#[global_allocator]
static ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty();

// 分配内存
fn init_heap() {
    const HEAP_SIZE: usize = 250 * 1024;
    static mut HEAP: MaybeUninit<[u8; HEAP_SIZE]> = MaybeUninit::uninit();

    unsafe {
        ALLOCATOR.init(HEAP.as_mut_ptr() as *mut u8, HEAP_SIZE);
    }
}

// slint自动编译ui代码
slint::include_modules!();
#[entry]
fn main() -> ! {
    init_heap();

	// slint 设置默认backend
    slint::platform::set_platform(Box::new(EspBackend::default()))
        .expect("backend already initialized");

    let main_window = Recipe::new().unwrap();

    let strong = main_window.clone_strong();
    let timer = slint::Timer::default();
    // 由于我的lcd不支持触屏 这里模拟了下按钮点击
    timer.start(
        slint::TimerMode::Repeated,
        core::time::Duration::from_millis(1000),
        move || {
            if strong.get_counter() <= 0 {
                strong.set_counter(25);
            } else {
                strong.set_counter(0);
            }
        },
    );

    main_window.run().unwrap();

    panic!("The MCU demo should not quit");
}

#[derive(Default)]
pub struct EspBackend {
    window: RefCell<Option<Rc<slint::platform::software_renderer::MinimalSoftwareWindow>>>,
}

impl slint::platform::Platform for EspBackend {
    fn create_window_adapter(
        &self,
    ) -> Result<Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> {
        let window = slint::platform::software_renderer::MinimalSoftwareWindow::new(
            slint::platform::software_renderer::RepaintBufferType::ReusedBuffer,
        );
        self.window.replace(Some(window.clone()));
        Ok(window)
    }

    fn duration_since_start(&self) -> core::time::Duration {
        core::time::Duration::from_millis(
            SystemTimer::now() / (SystemTimer::TICKS_PER_SECOND / 1000),
        )
    }

    fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
        let peripherals = Peripherals::take();
        let mut system = peripherals.SYSTEM.split();
        let clocks = ClockControl::configure(system.clock_control, CpuClock::Clock240MHz).freeze();

        let mut rtc = Rtc::new(peripherals.RTC_CNTL);
        let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
        let mut wdt0 = timer_group0.wdt;
        let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks);
        let mut wdt1 = timer_group1.wdt;

        rtc.rwdt.disable();
        wdt0.disable();
        wdt1.disable();

        let mut delay = Delay::new(&clocks);
        let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);

        let clk = io.pins.gpio18;
        let sdo = io.pins.gpio17;
        let cs = io.pins.gpio14;

		// 初始化spi
        let spi = Spi::new_no_miso(
            peripherals.SPI2,
            clk,
            sdo,
            cs,
            60u32.MHz(),
            SpiMode::Mode0,
            &clocks,
        );
        println!("spi init.");

        let dc = io.pins.gpio15.into_push_pull_output();
        let rst = io.pins.gpio16.into_push_pull_output();

		// spi interface
        let di = SPIInterfaceNoCS::new(spi, dc);
        // st7789 驱动
        let mut display = mipidsi::Builder::st7789(di)
            .with_display_size(240, 280)
            .with_window_offset_handler(|_| (0, 20)) // 这里稍微设置了下偏移
            .with_framebuffer_size(240, 280)
            .with_invert_colors( mipidsi::ColorInversion::Inverted)
            .init(&mut delay, Some(rst))
            .unwrap();

        println!("display init.");
        let mut bl = io.pins.gpio13.into_push_pull_output();
        bl.set_high().unwrap();

        let size = slint::PhysicalSize::new(240, 280);

        self.window.borrow().as_ref().unwrap().set_size(size);

        let mut buffer_provider = DrawBuffer {
            display,
            buffer: &mut [slint::platform::software_renderer::Rgb565Pixel::default(); 240],
        };

        loop {
            slint::platform::update_timers_and_animations();

			// 这里的大致流程是:
			// slint会计算出当前帧需要变化的像素
			// 结果会暂时存放在buffer_provider
			// 然后将buffer_provider中的数据传给spi
            if let Some(window) = self.window.borrow().clone() {
                window.draw_if_needed(|renderer| {
                    renderer.render_by_line(&mut buffer_provider);
                });
                if window.has_active_animations() {
                    continue;
                }
            }
        }
    }

    fn debug_log(&self, arguments: core::fmt::Arguments) {
        println!("{}", arguments);
    }
}

struct DrawBuffer<'a, Display> {
    display: Display,
    buffer: &'a mut [slint::platform::software_renderer::Rgb565Pixel],
}

impl<DI: display_interface::WriteOnlyDataCommand, RST: embedded_hal::digital::v2::OutputPin>
    slint::platform::software_renderer::LineBufferProvider
    for &mut DrawBuffer<'_, Display<DI, mipidsi::models::ST7789, RST>>
{
    type TargetPixel = slint::platform::software_renderer::Rgb565Pixel;

    fn process_line(
        &mut self,
        line: usize,
        range: core::ops::Range<usize>,
        render_fn: impl FnOnce(&mut [slint::platform::software_renderer::Rgb565Pixel]),
    ) {
        let buffer = &mut self.buffer[range.clone()];

        render_fn(buffer);

        // We send empty data just to get the device in the right window
        self.display
            .set_pixels(
                range.start as u16,
                line as _,
                range.end as u16,
                line as u16,
                buffer
                    .iter()
                    .map(|x| embedded_graphics_core::pixelcolor::raw::RawU16::new(x.0).into()),
            )
            .unwrap();
    }
}

结果

  • 还是有卡顿的感觉

参考

  • slint mcu

你可能感兴趣的:(Rust,单片机,rust,ui,开发语言,esp32,st7789)