Feb 10, 2026
1 min read
ESP32,
Rust,
C,
Embedded Systems,

Rust嵌入式开发:如何解决st7789屏幕在重绘时闪烁的问题

如果我们直接使用类似下面的代码(伪代码)来显示内容:

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(&REGULAR_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 时钟频率,来进一步减少闪烁。

当然,我们也要注意耗电问题,最好找一个适合的平衡点。