EmbeddedEra EmbeddedEra

← Notes from the bench.

Why an ESP32-S3 RGB display shifts or flickers with LVGL

Nov 2, 2026firmwarepcb-designDrafted with AI assistance, reviewed and edited by our engineer.

The demo runs clean. Then you add Wi-Fi, an OTA check, or a log write to flash, and the image jumps sideways, rolls, or flashes for a moment. Swap in a bigger LVGL buffer and nothing changes. This is one of the most repeated display questions on the ESP32 and LVGL forums, and the usual answers mix up three different problems.

They are different problems with different fixes: the panel is starved of data (a bandwidth problem), the panel is shown a half-drawn frame (a tearing problem), or the library is spending too long drawing (a rendering problem). Treat them separately and the fix gets short.

What an RGB panel demands

A parallel RGB panel has no memory of its own. The ESP32-S3 has to push every pixel of every frame, continuously, at the pixel clock. If a single line of pixels arrives late, the panel does not wait. The picture shifts, or flashes, until the next frame resynchronizes it.

Espressif’s LCD FAQ names the main cause of a drifting image: the pixel clock is set higher than the PSRAM or GDMA bandwidth can feed. It also lists a second cause that surprises people. PSRAM is unavailable while the chip writes to flash, which happens during Wi-Fi activity, OTA, and Bluetooth LE operations. The frame buffer sits in PSRAM, so a flash write can starve the display at the worst possible moment.

That explains the classic symptom: the screen is fine until something unrelated starts. The display is not broken. Something else is competing for the same memory bus.

Bandwidth: the first thing to check

Start with the memory, not with LVGL.

  1. PSRAM type and speed. Espressif’s FAQ says quad PSRAM has lower bandwidth and may not reach the pixel clock a given panel needs, and recommends octal PSRAM where the hardware allows it. This is a board decision, made when you pick the module. It is cheap to get right at schematic time and much harder to fix in firmware afterward.
  2. Pixel clock. The ESP-IDF RGB panel documentation sets the starting rate with timings.pclk_hz and notes a high pixel clock can starve the display of PSRAM bandwidth and corrupt it. Read your panel’s datasheet for its minimum and typical clock. Many panels accept a range, so running at the low end of that range is a legitimate trade for stability. A lower clock also lowers refresh rate, so confirm the result still looks right.
  3. Code and read-only data in PSRAM. The Espressif FAQ points to several configuration options, including CONFIG_SPIRAM_FETCH_INSTRUCTIONS and CONFIG_SPIRAM_RODATA, that change how much traffic goes over the flash and PSRAM path. Read the descriptions in your ESP-IDF version before enabling them, because they change what lives where.
  4. Filesystems on the same flash. The IDF documentation notes that PSRAM shares a bus with main flash, so using flash for a filesystem can corrupt the display too. If a screen glitches every time the device logs, this is the first place to look.

A useful experiment takes ten minutes. Disable Wi-Fi and every flash write, run the UI alone, and see whether the glitch disappears. If it does, you have a contention problem and LVGL is innocent.

Bounce buffers: what they fix and what they cost

With a single frame buffer in PSRAM, the DMA engine and the CPU share the PSRAM bus. The IDF documentation describes the alternative: bounce buffer mode. The driver allocates two small buffers in internal memory. The CPU copies lines from PSRAM into them, and DMA sends them to the panel from internal RAM. According to the documentation, the advantages are a higher achievable pixel clock and more tolerance for short bandwidth spikes.

Be clear about the price, because the documentation is. Bounce buffers cost CPU time and internal SRAM: Espressif documents a requirement of roughly 10 × screen width × 4 bytes of SRAM, and a 64-byte cache line. The same documentation warns that the display cannot work while the external memory cache is disabled, which is exactly what happens during an OTA or NVS write to main flash. So bounce buffers alone may not remove the problem. Espressif documents running code from PSRAM with XIP (CONFIG_SPIRAM_XIP_FROM_PSRAM) combined with bounce buffers as the recommended setup when flash writes happen alongside Wi-Fi or BLE.

That matters for any product with field updates. If your device can show a progress screen during an update, test that screen on the real hardware. Espressif also documents lowering the pixel clock temporarily, with esp_lcd_rgb_panel_set_pclk(), while the flash operation runs. The FAQ says this may cause a short blank flash. Decide in advance whether a brief blank is acceptable, and what the user sees. Our note on firmware updates that cannot brick the device covers the update side of the same design.

The IDF also provides CONFIG_LCD_RGB_RESTART_IN_VSYNC, which restarts the transfer at the next vertical sync after a shift. The IDF page describes it as a way to recover from desync, and the FAQ adds that it does not prevent the glitch and may cost frame rate. It is a safety net, not a design.

Tearing is a separate problem

Tearing is a different symptom. The image does not shift. A horizontal seam appears during motion because the panel scanned out a frame while the CPU was still drawing it. Drawing to the live frame buffer causes this, whatever the bandwidth.

The cure is buffer management. The IDF documentation describes driver-allocated double frame buffers (num_fbs), where the CPU writes to one while DMA reads the other, and the switch happens only after a frame completes. The cost is two screen-sized buffers in PSRAM, plus keeping them in sync. It also adds to the bandwidth load, so a design on the edge of bandwidth can trade tearing for drift.

LVGL’s display setup documentation gives the matching software side through lv_display_set_buffers() and three render modes:

  • Partial uses buffers smaller than the screen, and your flush callback copies each rendered area into the panel. LVGL recommends at least one-tenth of the display size.
  • Direct uses display-sized buffers, and LVGL renders at the correct location. Flushing often only means updating the frame buffer address.
  • Full also uses display-sized buffers but redraws the entire screen every time. With two buffers it behaves like classic double buffering, and the flush callback only swaps the address.

Match the LVGL mode to the driver configuration. Partial mode copying into a single live RGB frame buffer can tear. Direct or full mode with two driver-managed buffers is the combination built to avoid it. Whichever you pick, the flush callback must call lv_display_flush_ready() when the copy is done, and the right moment depends on whether it is a copy or just an address swap. Calling it too early lets LVGL draw into a buffer the panel is still reading. A buffer-complete or vsync event from the driver is the right trigger when you are swapping.

A short diagnosis order

  1. Run the UI with radio and flash writes off. Glitch gone means contention.
  2. Lower the pixel clock to the bottom of the panel’s allowed range and check again.
  3. Confirm PSRAM type and speed, and which build options move code or data into it.
  4. Enable bounce buffers and measure CPU load, because LVGL now competes with the copy.
  5. Only then look at tearing: check the driver’s frame buffer count against LVGL’s render mode and the flush logic.
  6. Test the update path on a real unit, not on a development board with different PSRAM.

One more point for buyers. Many cheap ESP32-S3 display boards use the memory configuration the vendor happened to ship. If your product copies that module, it inherits the limit. Choosing the PSRAM and the panel together, before layout, is far cheaper than debugging bandwidth later. The same applies to a different interface: for some products, a panel with its own controller memory over SPI avoids the problem entirely, which our note on choosing a display interface walks through.

How we approach it

We start by reproducing the glitch with a measured trigger, then change one thing at a time on your hardware: clock, memory configuration, buffer scheme, in that order. If you send the panel part number, the module or schematic, your ESP-IDF and LVGL versions, and a description of when it glitches, we can usually say whether it is a firmware fix or a board change.

Your RGB panel looks fine on the bench and shifts or flickers once Wi-Fi, OTA or logging runs; we would measure the memory contention and rework the display path.

ENGINEERING NOTES

One note a week on PCB design, firmware and getting hardware built. No sales email.

Double opt-in. Unsubscribe in one click.