Skip to content

chore(color): reduce busy wait time on LCD refresh#7483

Open
philmoz wants to merge 2 commits into
mainfrom
philmoz/reduce-busy-wait
Open

chore(color): reduce busy wait time on LCD refresh#7483
philmoz wants to merge 2 commits into
mainfrom
philmoz/reduce-busy-wait

Conversation

@philmoz

@philmoz philmoz commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Version 2 - replacement for #7448.

Moved the call to tell LVGL that it can swap buffers and continue into the LDTC vertical refresh interrupt.
Seems to solves all the issues.
Tested widgets using lcd and lvgl API's with no issues.

This removes the busy wait loop completely in the firmware (still used in bootloader).

Note: PA01 does not have a vertical refresh interrupt so still uses a busy wait loop.

Summary by CodeRabbit

  • Refactor
    • Improved LCD refresh synchronization so boot-time behavior differs from normal runtime, reducing display stalls and improving reliability.
    • Streamlined graphics flush completion handling so the UI stack is notified consistently after each flush.
    • Updated interrupt-driven refresh flow to better coordinate frame updates with rendering completion.
  • Bug Fixes
    • Fixed mismatches where flush readiness/signaling could occur too early or not at the expected time, especially outside boot scenarios.

@philmoz philmoz added this to the 2.12.3 milestone Jun 23, 2026
@philmoz philmoz added color Related generally to color LCD radios house keeping 🧹 Cleanup of code and house keeping backport/2.12 To be backported to a 2.12 release also. labels Jun 23, 2026
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 29ab9b52-6906-4a85-b906-41ad5dfda8f8

📥 Commits

Reviewing files that changed from the base of the PR and between d8f3b90 and 5e8308a.

📒 Files selected for processing (4)
  • radio/src/gui/colorlcd/boot_lcd.cpp
  • radio/src/gui/colorlcd/lcd.cpp
  • radio/src/gui/colorlcd/lcd.h
  • radio/src/targets/horus/lcd_driver.cpp
💤 Files with no reviewable changes (1)
  • radio/src/gui/colorlcd/lcd.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • radio/src/gui/colorlcd/lcd.cpp

📝 Walkthrough

Walkthrough

LTDC LCD refresh synchronization is split by build mode across all hardware targets: the _frame_addr_reloaded busy-wait mechanism in startLcdRefresh and LTDC_IRQHandler is compiled only for BOOT builds, while runtime builds route completion through a new lvglFlushed() function that calls lv_disp_flush_ready. The core flushLcd() callback flow is refactored to delegate completion to either a registered callback or lvglFlushed(). Simulator-only state is isolated behind SIMU guards. Dead globals, function-pointer typedefs, and legacy include guards are removed.

Changes

LCD Flush Synchronization Refactor

Layer / File(s) Summary
lvglFlushed API contracts and SIMU/BOOT guards
radio/src/gui/colorlcd/lcd.h, radio/src/gui/common/stdlcd/lcd_common.h, radio/src/gui/common/stdlcd/lcd_common.cpp, radio/src/targets/taranis/board.h
Declares lvglFlushed() unconditionally as extern "C"; gates lcdFlushed() and lcdSetWaitCb() behind #if defined(SIMU), and lcdInitDirectDrawing() behind #if defined(BOOT); wraps lcdFlushed() definition in lcd_common.cpp for SIMU only; removes lcdFlushed() from taranis board header and adds lcdSetContrast().
Core flushLcd() and simulator plumbing
radio/src/gui/colorlcd/lcd.cpp, radio/src/gui/colorlcd/boot_lcd.cpp, radio/src/targets/simu/simulib.cpp
Implements lvglFlushed() in both lcd.cpp and boot_lcd.cpp as entry point for LTDC interrupt completion; restructures flushLcd() to call lv_disp_flush_ready() only in direct-mode non-last case, otherwise routes to lcd_flush_cb or lvglFlushed(); confines simulator state (wait_cb, refr_disp, lcdFlushed(), lcdRefresh()) within #if defined(SIMU) guards; removes global-scope ::lcdFlushed() qualifier in simulib.cpp.
Per-target LTDC BOOT/non-BOOT sync in all LCD drivers
radio/src/boards/jumper-h750/lcd_driver.cpp, radio/src/boards/rm-h750/lcd_driver_480.cpp, radio/src/boards/rm-h750/lcd_driver_800.cpp, radio/src/targets/horus/lcd_driver.cpp, radio/src/targets/horus/lcd_st7796s_driver.cpp, radio/src/targets/pa01/lcd_driver.cpp, radio/src/targets/pl18/lcd_driver.cpp, radio/src/targets/st16/lcd_driver.cpp, radio/src/targets/stm32h7s78-dk/lcd_driver.cpp
Wraps _frame_addr_reloaded declaration and busy-wait in startLcdRefresh() under #if defined(BOOT); removes LTDC_IT_LI enable from LCD_Init_LTDC(), shifting responsibility to startLcdRefresh(); makes LTDC_IRQHandler() set _frame_addr_reloaded = 1 in BOOT or call lvglFlushed() in non-BOOT builds. Replaces lcd_phys_w/h globals with LCD_PHYS_W/H constants in rm-h750 drivers; removes TouchControllerType globals and function-pointer indirection in lcdInit().
Header include guard and typedef cleanup
radio/src/boards/jumper-h750/lcd_driver.h, radio/src/boards/rm-h750/lcd_driver_480.h, radio/src/boards/rm-h750/lcd_driver_800.h
Replaces #ifndef include guards with #pragma once; removes lcdSpiInitFucPtr and LcdReadIDFucPtr typedefs and extern function-pointer declarations; refactors front/back porch timing macros to use named HORIZONTAL_BACK_PORCH and VERTICAL_BACK_PORCH constants.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

firmware

Suggested reviewers

  • pfeerick
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: removing busy wait loops from LCD refresh operations in firmware.
Description check ✅ Passed The description provides context about the change (replacement for PR #7448), implementation approach (moving buffer swap notification to LTDC interrupt), testing performed, and platform-specific notes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch philmoz/reduce-busy-wait

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@3djc

3djc commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

tested on h7 hardware without issue I could spot

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
radio/src/targets/horus/lcd_driver.cpp (1)

169-193: 🩺 Stability & Availability | 🔴 Critical

DMA operations are asynchronous; data race when lvglFlushed() is signaled mid-copy.

DMACopyBitmap() calls LL_DMA2D_Start() and returns immediately without waiting for the transfer to complete. In non-BOOT builds, _update_frame_buffer_addr() enables LTDC_IT_LI but skips the busy-wait, so LTDC_IRQHandler fires at the next vertical blank—potentially while DMA2D is still writing to _back_buffer. When the IRQ calls lvglFlushed(), it signals to LVGL that the refresh is complete before the DMA copy operations have actually finished, creating a data race on the buffer contents.

Add DMAWait() after the invalidation area loop (after line 192) to ensure all pending DMA transfers complete before the IRQ can fire.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@radio/src/targets/horus/lcd_driver.cpp` around lines 169 - 193, The issue is
that `DMACopyBitmap()` initiates asynchronous DMA transfers but returns
immediately, while the loop iterates through multiple invalidation areas and
queues multiple DMA operations. The LTDC IRQ can fire before all DMA operations
complete, causing `lvglFlushed()` to signal refresh completion while DMA is
still writing to `_back_buffer`, creating a data race. After the for loop that
processes `disp->inv_p` invalidation areas and calls `DMACopyBitmap()`, add a
call to `DMAWait()` to ensure all pending DMA transfers finish before the IRQ
handler fires and signals the buffer is ready.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@radio/src/targets/horus/lcd_driver.cpp`:
- Around line 169-193: The issue is that `DMACopyBitmap()` initiates
asynchronous DMA transfers but returns immediately, while the loop iterates
through multiple invalidation areas and queues multiple DMA operations. The LTDC
IRQ can fire before all DMA operations complete, causing `lvglFlushed()` to
signal refresh completion while DMA is still writing to `_back_buffer`, creating
a data race. After the for loop that processes `disp->inv_p` invalidation areas
and calls `DMACopyBitmap()`, add a call to `DMAWait()` to ensure all pending DMA
transfers finish before the IRQ handler fires and signals the buffer is ready.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 505ba5a8-533c-4f46-9efa-aa96027da32b

📥 Commits

Reviewing files that changed from the base of the PR and between 66463ec and d8f3b90.

📒 Files selected for processing (18)
  • radio/src/boards/jumper-h750/lcd_driver.cpp
  • radio/src/boards/jumper-h750/lcd_driver.h
  • radio/src/boards/rm-h750/lcd_driver_480.cpp
  • radio/src/boards/rm-h750/lcd_driver_480.h
  • radio/src/boards/rm-h750/lcd_driver_800.cpp
  • radio/src/boards/rm-h750/lcd_driver_800.h
  • radio/src/gui/colorlcd/lcd.cpp
  • radio/src/gui/colorlcd/lcd.h
  • radio/src/gui/common/stdlcd/lcd_common.cpp
  • radio/src/gui/common/stdlcd/lcd_common.h
  • radio/src/targets/horus/lcd_driver.cpp
  • radio/src/targets/horus/lcd_st7796s_driver.cpp
  • radio/src/targets/pa01/lcd_driver.cpp
  • radio/src/targets/pl18/lcd_driver.cpp
  • radio/src/targets/simu/simulib.cpp
  • radio/src/targets/st16/lcd_driver.cpp
  • radio/src/targets/stm32h7s78-dk/lcd_driver.cpp
  • radio/src/targets/taranis/board.h
💤 Files with no reviewable changes (2)
  • radio/src/boards/rm-h750/lcd_driver_800.h
  • radio/src/targets/taranis/board.h

@philmoz

philmoz commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator Author

Reverted to using busy wait loop for radios with inverted LCD.

While the interrupt driven version works - it can result in some screen tearing while the inverted front buffer contents are being DMA copied to the back buffer.

Interrupt version works on F4 radios with non-inverted LCD and H7 radios (except PA01).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport/2.12 To be backported to a 2.12 release also. color Related generally to color LCD radios house keeping 🧹 Cleanup of code and house keeping

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants