Skip to content

Rust Embedded Development: How to Fix ST7789 Screen Flickering During Redraw

kingzcheung
Published date:
Edit this post

If we use code similar to the following (pseudo-code) to display content:

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(); // <====== Clear screen content, otherwise the previous frame content will overlap with new content.
    let next = Text::new("这是显示内容", next, small_style).draw(&mut display)?;
    }

As long as the displayed content changes and triggers a redraw, screen flickering will occur. However, when I implemented it in C using the LVGL framework, I didn’t have this problem.

This indicates that the problem lies in the Rust implementation.

Through careful observation, I found that the ST7789 screen refreshes slowly enough that you can observe pixel-by-pixel refresh from top to bottom.

Attempt 1: Increase SPI Clock Frequency

    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);

When initializing SPI, you can configure the clock frequency. Note that the maximum clock frequency for ESP32S3’s SPI2 interface is 80MHz. Exceeding this will result in the following error:

[ERROR] panicked at src/bin/main.rs:88:6:
called `Result::unwrap()` on an `Err` value: FrequencyOutOfRange

But even when I set it to 80MHz, the screen still flickered. It just wasn’t as severe as before.

Attempt 2: Reduce Refresh Area

Since the ST7789 screen refreshes from top to bottom, can I try to refresh only a portion of the area? By reducing the refresh area, we can speed up the refresh.

There’s an API for this implementation.

display.fill_solid(area, color).unwrap();

Where area is the region we need to calculate for refreshing.

However, this solution didn’t really solve the problem.

Attempt 3: Use Frame Buffer

A Frame Buffer is a memory area used to store image data, typically used for display devices such as screens. In a frame buffer, image data is stored as pixel data, which is then displayed by the display device.

We need to add this crate:

[dependencies]
embedded-graphics-framebuf = "0.5.0"

Then use it in the code, for example, my project code is as follows:

    // Create FrameBuf for double buffering to avoid flickering
    // Use Rgb565 color format, size 200x80 is enough to accommodate two lines of text
    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();

         // Calculate target position (screen center offset)
        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,
        );
        
        // Use fill_contiguous to write to display at once, avoiding flickering
        let area = Rectangle::new(target_point, fbuf_size);
        display.fill_contiguous(&area, fbuf.data.iter().copied())?;
    }

This is the most effective solution so far. Therefore, you can combine this solution with solution 1, increasing the SPI clock frequency to further reduce flickering.

Of course, we also need to pay attention to power consumption and find a suitable balance.

Previous
使用 Rust 和 Ratatui 在 ESP32-S3 上实现 ST7789 屏幕的 TUI 界面
Next
Rust Embedded Development: Displaying Images on ST7789 Screen