如果我们直接使用类似下面的代码(伪代码)来显示内容:
let mut display = st7789::ST7789::new();
let small_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE);
loop {
delay.delay_millis(1000);
display.clear(Rgb565::BLACK).unwrap(); // <====== 清除屏幕内容,否则会导致上一帧内容和新内容重叠。
let next = Text::new("这是显示内容", next, small_style).draw(&mut display)?;
}
只要上面的显示内容有变动,触发重绘,就会出现屏幕闪烁。而我在使用 C 语言实现时,使用 lvgl 框架时,没有这个问题。
这说明,问题出现在 Rust 的实现中。
通过仔细观察,我发现 st7789 屏幕的刷新慢到可以观察到从上往下一个个像素点刷新。
尝试方案1: 提高 SPI 时钟频率
let spi = esp_hal::spi::master::Spi::new(
peripherals.SPI2,
Config::default().with_frequency(Rate::from_mhz(80)),
)
.unwrap()
.with_sck(peripherals.GPIO5)
.with_mosi(peripherals.GPIO6);
初始化 SPI 时,可以配置时钟频率。需要注意的是,esp32s3 的 SPI2 接口的最大时钟频率为 80MHz。超过会报下面的错误:
[ERROR] panicked at src/bin/main.rs:88:6:
called `Result::unwrap()` on an `Err` value: FrequencyOutOfRange
但是哪怕我设置到了 80mhz,屏幕还是有闪烁。只是没有之前那么严重了。
尝试方案二:减少刷新区域
既然 st7789 的屏幕刷新是从上往下的,那么我是不是可以尝试只刷新一部分区域?通过减少刷新的区域来加快刷新速度。
这个也是有 api 实现的。
display.fill_solid(area, color).unwrap();
其中 area 就是我们要计算出来要刷新的区域。
但是这个方案并没有真正地解决问题。
尝试方案三:使用帧缓冲区
帧缓冲区(Frame Buffer)是一种用于存储图像数据的内存区域,通常用于显示设备,如屏幕。在帧缓冲区中,图像数据被存储为像素数据,然后被显示设备显示。
我们需要添加这个crate :
[dependencies]
embedded-graphics-framebuf = "0.5.0"
然后在代码中使用,比如我的项目代码如下:
// 创建 FrameBuf 用于双缓冲,避免闪烁
// 使用 Rgb565 颜色格式,大小为 200x80 足够容纳两行文本
let mut data = [Rgb565::BLACK; 200 * 80];
let mut fbuf: FrameBuf<Rgb565, &mut [Rgb565; 200 * 80]> = FrameBuf::new(&mut data, 200, 80);
loop {
delay.delay_millis(1000);
let temp_str = alloc::format!("温度:{}°C", temp);
let temp_character_style = BdfTextStyle::new(®ULAR_FONT, Rgb565::new(31, 41, 0));
Text::with_alignment(
&temp_str,
fbuf.bounding_box().center() + Point::new(0, 10),
temp_character_style,
Alignment::Center,
)
.draw(&mut *fbuf).unwrap();
// 计算目标位置(屏幕中心偏移)
let display_center = display.bounding_box().center();
let fbuf_size = fbuf.size();
let target_point = Point::new(
display_center.x - (fbuf_size.width as i32 / 2),
display_center.y - (fbuf_size.height as i32 / 2) - 30,
);
// 使用 fill_contiguous 一次性写入显示器,避免闪烁
let area = Rectangle::new(target_point, fbuf_size);
display.fill_contiguous(&area, fbuf.data.iter().copied())?;
}
这也是目前为止最有效的方案。因此可以通过这个方案再配合上方案一,提高 SPI 时钟频率,来进一步减少闪烁。
当然,我们也要注意耗电问题,最好找一个适合的平衡点。