这个教程算是在st7789屏幕没有买之前就已经开始打算了,之前的使用slint驱动oled也算是为这个打下基础吧
创建新项目
可以参考 ESP32C3使用Slint渲染OLED屏幕 引入slint依赖,创建跟配置slint跟这篇文章一样,引入的依赖基本上大差不差,只不过这个项目还需要引入st7789屏幕的相关依赖。
然后在Cargo.toml
中添加依赖
[dependencies]
# 基础依赖
critical-section = "1.2.0"
esp-hal = { version = "1.0.0-beta.0", features = ["esp32c3","unstable"] }
esp-alloc = "0.7.0"
esp-println = { version = "0.13.1",features = ["esp32c3","log"] }
log = "0.4.27"
esp-backtrace = { version = "0.15.1" ,features = ["esp32c3","println","panic-handler"]}
# 图形相关
embedded-hal = "1.0.0"
embedded-graphics-core = "0.4.0"
embedded-graphics = "0.8.1"
embedded-graphics-framebuf = "0.5.0"
display-interface = "0.5.0"
display-interface-spi = "0.5.0"
mipidsi = "0.9.0"
embedded-hal-bus = "0.3.0"
# 异步
esp-hal-embassy = { version = "0.7.0", features = ["esp32c3"] }
embassy-time = { version = "0.4.0", features = ["generic-queue-8"] }
embassy-executor = { version = "0.7.0",features = ["task-arena-size-20480","executor-thread"] }
#embassy-futures = "0.1.1"
static_cell = { version = "2.1.0", features = ["nightly"] }
tinygif = "0.0.4"
[dependencies.slint]
git = "https://github.com/slint-ui/slint"
rev = "29168bc89270798f6075a0a729c14a3f011ceb4f" # 修复提交的哈希
default-features = false
features = [
"compat-1-2",
"unsafe-single-threaded", # 启用单线程模式
"libm",
"renderer-software",
]
[build-dependencies]
slint-build = {git = "https://github.com/slint-ui/slint",rev = "29168bc89270798f6075a0a729c14a3f011ceb4f"}
创建ui文件
创建 ui/main.slint
文件,作为程序的入口文件
import { System,LocalTime,Route , Setting} from "./global.slint";
import { HomePage } from "./page/home_page.slint";
import { SettingPage } from "./page/setting_page.slint";
// 导出给rust代码调用
export { System,LocalTime,Setting }
export component MainView inherits Window {
background: black;
width: System.screen_width;
height: System.screen_height;
default-font-family: "黑体";
// 简单实现路由
if System.route==Route.HomePage: HomePage{}
if System.route==Route.SettingPage: SettingPage{}
// 显示帧率
if Setting.fps == true:Rectangle {
padding: 2px;
x:0px;
y:0px;
width: fps.width;
height: fps.height;
background: black;
fps:=Text {
font-size: 20px;
text: @tr("FPS:{}",System.fps);
color: white;
}
}
// 触摸切换页面,用于测试
TouchArea {
clicked => {
if(System.route == Route.HomePage){
System.route = Route.SettingPage;
}else{
System.route = Route.HomePage;
}
}
}
}
创建 ui/global.slint
存储全局变量,跟rust代码进行交互
export enum Route {
HomePage,
SettingPage
}
export global System {
// 屏幕宽高
out property <length> screen_width:240px;
out property <length> screen_height:240px;
// 路由页面
in-out property <Route> route:Route.HomePage;
// 帧率
in-out property <int> fps:0;
}
export global Setting {
// 开启fps帧率显示
in-out property <bool> fps:true;
}
export global LocalTime {
in-out property <int> hour:12;
in-out property <int> minute:12;
in-out property <int> scond:30;
in-out property <int> year:2025;
in-out property <int> month:12;
in-out property <int> day:12;
in-out property <int> week:1;
private property <[string]> weeks:["周日","周一","周二","周三","周四","周五","周六"];
in-out property <string> week_cn:weeks[week];
}
创建ui/page/home_page.slint
文件展示主页
import { System,LocalTime } from "../global.slint";
export component HomePage inherits Rectangle{
width: System.screen_width;
height: System.screen_height;
// 背景图片
bg:=Image {
source: @image-url("../../assets/bg_ndmz.png");
}
animate x {
easing: ease-in-out;
duration: 300ms;
}
VerticalLayout {
spacing: 4px;
alignment: start;
week:=HorizontalLayout{
padding-top: 20px;
alignment: center;
spacing: 5px;
Text {
text: LocalTime.month;
color: white;
font-size: 20px;
}
Text {
text: LocalTime.week_cn;
color: white;
font-size: 20px;
}
}
time:=HorizontalLayout {
padding: 2px;
spacing: 5px;
alignment: center;
Rectangle {
// background: blue;
Text {
text: LocalTime.hour;
font-size: 60px;
color: white;
font-weight: 500;
}
}
Text {
width: 20px;
text: ":";
font-size: 60px;
color: white;
font-weight: 500;
}
Rectangle {
// background: pink;
Text {
text: LocalTime.minute;
font-size: 60px;
color: white;
font-weight: 500;
}
}
}
}
// 这里的定时器是简单模拟时间,实际项目应该从rust代码传入时间
Timer {
running: true;
interval: 1s;
triggered => {
LocalTime.minute+=1;
}
}
}
创建ui/page/setting_page.slint
展示设置页面,这里简单做了给跑马灯的效果
import { System } from "../global.slint";
export component SettingPage inherits Rectangle{
width: System.screen_width;
height: System.screen_height;
background: white;
property <length> pos_x:0px;
text:=Text {
x:pos_x;
text: "{}这里是设置页面aaaadfsadfsdf";
font-size: 40px;
font-weight: 999;
}
Timer {
running: true;
interval: 50ms;
triggered => {
if(pos_x < System.screen_width - text.width){
pos_x = System.screen_width;
}else{
pos_x -= 1px;
}
}
}
}
编写main.rs
代码
#![no_std]
#![no_main]
extern crate alloc;
use alloc::boxed::Box;
use alloc::format;
use alloc::rc::Rc;
use alloc::string::ToString;
use alloc::vec::Vec;
use core::cell::RefCell;
use core::fmt::Debug;
use core::sync::atomic::{AtomicBool, Ordering};
use critical_section::Mutex;
use embassy_executor::Spawner;
use embassy_time::Timer;
use embedded_graphics::mono_font::ascii::FONT_10X20;
use embedded_graphics::mono_font::iso_8859_7::FONT_5X8;
use embedded_graphics::mono_font::MonoTextStyle;
use embedded_graphics::prelude::Primitive;
use embedded_graphics::primitives::PrimitiveStyle;
use embedded_graphics::text::Text;
use embedded_graphics_core::Drawable;
use embedded_graphics_core::pixelcolor::{Bgr565, BinaryColor, Rgb565};
use embedded_graphics_core::prelude::{DrawTarget, ImageDrawable, Point, RgbColor, Size};
use embedded_graphics_core::primitives::Rectangle;
use embedded_hal::delay::DelayNs;
use embedded_hal_bus::spi::{ExclusiveDevice, NoDelay};
use esp_hal::clock::{CpuClock, RadioClockController};
use esp_hal::delay::Delay;
use esp_hal::{handler, main, ram, spi, Blocking};
use esp_hal::gpio::{Event, Input, InputConfig, Io, Level, Output, OutputConfig, Pull};
use esp_hal::i2c::master::I2c;
use esp_hal::peripherals::Peripherals;
use esp_hal::rtc_cntl::Rtc;
use esp_hal::spi::master::Config;
use esp_hal::spi::master::Spi;
use esp_hal::spi::Mode;
use esp_hal::time::{Duration, Instant, Rate};
use esp_hal::timer::systimer::SystemTimer;
use esp_hal::timer::timg::TimerGroup;
use esp_println::logger::{init_logger, init_logger_from_env};
use esp_println::println;
use log::{error, info, warn, LevelFilter};
use mipidsi::{Builder, Display};
use mipidsi::interface::SpiInterface;
use mipidsi::models::ST7789;
use mipidsi::options::{ColorInversion, Orientation};
use slint::platform::{Platform, WindowAdapter};
use slint::platform::software_renderer::{LineBufferProvider, MinimalSoftwareWindow, Rgb565Pixel};
use slint::{invoke_from_event_loop, ModelRc, PhysicalSize, PlatformError, Rgb8Pixel, Rgba8Pixel, SharedPixelBuffer, TimerMode, Weak};
slint::include_modules!();
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
error!("panic:{}",info);
loop {}
}
#[esp_hal_embassy::main]
async fn main(spawner: Spawner) {
// generator version: 0.3.1
// init_logger_from_env();
init_logger(LevelFilter::Debug);
esp_alloc::heap_allocator!(size: 100 * 1024);
info!("分配内存完成");
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
let peripherals =esp_hal::init(config);
// 配置异步
let timer0 = SystemTimer::new(peripherals.SYSTIMER);
esp_hal_embassy::init(timer0.alarm0);
let mut delay = Delay::new();
let mut rst = Output::new(peripherals.GPIO3,Level::Low,OutputConfig::default());
rst.set_high();
let dc = Output::new(peripherals.GPIO4,Level::Low,OutputConfig::default());
let mut backlight = Output::new(peripherals.GPIO5,Level::Low,OutputConfig::default());
let sclk = peripherals.GPIO6;
let sda = peripherals.GPIO7;
let sdi = peripherals.GPIO8;
let cs = peripherals.GPIO10;
let spi = Spi::new(peripherals.SPI2,
Config::default()
.with_mode(Mode::_3)
.with_frequency(Rate::from_mhz(60))
).unwrap()
.with_sck(sclk)
.with_miso(sdi)
.with_mosi(sda);
let cs_output = Output::new(cs,Level::High,OutputConfig::default());
let spi_device = ExclusiveDevice::new_no_delay(spi,cs_output).unwrap();
let di = SpiInterface::new(spi_device,dc,unsafe{ &mut BUFFER });
let display = Builder::new(ST7789, di)
.display_size(240,240)
.invert_colors(ColorInversion::Inverted)
.color_order(mipidsi::options::ColorOrder::Rgb)
.orientation(Orientation::new())
.reset_pin(rst)
.init(&mut delay).unwrap();
println!("display 初始化完成");
backlight.set_high();
info!("开启异步任务");
spawner.spawn(ui_run(display)).ok();
// for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.0.0-beta.0/examples/src/bin
}
static mut BUFFER: [u8; 512] = [0_u8; 512];
type DISPLAY = Display<SpiInterface<'static, ExclusiveDevice<Spi<'static,Blocking>,Output<'static>,NoDelay>,Output<'static>>,ST7789,Output<'static>>;
#[embassy_executor::task]
async fn ui_run(mut display: DISPLAY){
// 创建平台实例
let window = MinimalSoftwareWindow::new(
slint::platform::software_renderer::RepaintBufferType::ReusedBuffer,
);
window.set_size(PhysicalSize::new(240,240));
let platform = EspPlatform::new(window.clone());
slint::platform::set_platform(Box::new(platform)).unwrap();
// 创建ui
let ui = MainView::new().unwrap();
ui.window().set_size(PhysicalSize::new(240,240));
// 初始化显示
display.clear(Rgb565::BLACK).unwrap();
info!("清理屏幕");
// 创建行缓冲区
let display_width = 240; // 根据实际显示宽度设置
let mut the_frame_buffer = [Rgb565Pixel(0); 240 ];
let mut time = Instant::now();;
let mut last_frame_time = time.duration_since_epoch();
let mut fps = 0;
let mut frame_count = 0;
loop {
frame_count+=1;
// 更新UI状态
slint::platform::update_timers_and_animations();
// 渲染UI
window.draw_if_needed(|renderer| {
renderer.render_by_line(FrameBuffer{ frame_buffer: &mut the_frame_buffer, stride: display_width,display:&mut display });
});
// 计算FPS
let now = time.duration_since_epoch();
if now.as_millis()-last_frame_time.as_millis() >= 1000{
last_frame_time = now;
fps = frame_count;
frame_count = 0;
ui.global::<System>().set_fps(fps);
}
// 根据动画状态决定是否延迟
if !window.has_active_animations() {
if let Some(duration) = slint::platform::duration_until_next_timer_update(){
Timer::after_millis(duration.as_millis() as u64).await;
continue;
}
}
Timer::after_millis(10).await;
}
}
// 平台实现结构体
struct EspPlatform{
window: Rc<MinimalSoftwareWindow>,
}
impl EspPlatform {
fn new(window:Rc<MinimalSoftwareWindow>) -> Self {
Self {
window
}
}
}
/// 实现 Platform trait
impl Platform for EspPlatform {
fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError> {
info!("成功创建了window");
Ok(self.window.clone())
}
fn duration_since_start(&self) -> core::time::Duration {
// 使用定时器获取时间(需要实际硬件计时器实现)
let time = Instant::now().duration_since_epoch().as_millis();
// info!("time :{}",time);
core::time::Duration::from_millis(
time
)
}
}
struct FrameBuffer<'a>{ frame_buffer: &'a mut [Rgb565Pixel], stride: usize,display:&'a mut DISPLAY }
impl<'a> LineBufferProvider for FrameBuffer<'a> {
type TargetPixel = Rgb565Pixel;
fn process_line(
&mut self,
line: usize,
range: core::ops::Range<usize>,
render_fn: impl FnOnce(&mut [Self::TargetPixel]),
) {
let buf = &mut self.frame_buffer[range.clone()];
render_fn(buf);
self.display.set_pixels(
range.start as u16,
line as _,
range.end as u16,
line as u16,
buf.iter().map(|x|{
embedded_graphics_core::pixelcolor::raw::RawU16::new(x.0).into()
})
).unwrap()
}
}
效果展示
浏览首页页面
设置页面
最终的效果展示
评论 (0)