Skip to content

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

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

chore(color): reduce busy wait time on LCD refresh#7483
philmoz wants to merge 4 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
    • Updated LCD refresh/interrupt flow so boot-time and normal runtime use different completion signaling paths to reduce display stalls.
    • Centralized LVGL flush completion via a dedicated flush-ready notifier and simplified non-boot completion behavior.
    • Aligned LCD frame buffers to improve rendering consistency.
    • Refined simulator-only LCD callback wiring so firmware builds don’t define simulation wait logic.
  • Bug Fixes
    • Fixed cases where flush-ready timing could be early or inconsistent outside boot scenarios.
  • Chores
    • Modernized header interfaces (including switching to #pragma once) and cleaned up outdated declarations.

@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

📝 Walkthrough

Walkthrough

The LCD refresh path now uses BOOT-only reload waiting and runtime lvglFlushed() signaling across shared GUI code, simulator hooks, boot menu rendering, and multiple target LCD drivers. LCD headers and callbacks were updated to match the new build-specific interfaces.

Changes

LCD Flush Synchronization Refactor

Layer / File(s) Summary
API contracts and build guards
radio/src/boards/jumper-h750/lcd_driver.h, radio/src/boards/rm-h750/lcd_driver_480.h, 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, radio/src/targets/simu/simulib.cpp, radio/src/gui/colorlcd/boot_menu.cpp
Updates LCD callback declarations and build guards, adds the shared lvglFlushed() declaration, conditionally exposes simulator-only lcdFlushed(), and replaces include guards with #pragma once in the LCD headers.
Shared GUI flush and framebuffer wiring
radio/src/gui/colorlcd/lcd.cpp, radio/src/gui/colorlcd/boot_lcd.cpp
Aligns the shared LCD implementation with aligned frame buffers, simulator-only wait callbacks, lvglFlushed() completion, and the updated boot display driver initialization.
Boot menu drawing uses lcd object
radio/src/gui/colorlcd/boot_menu.cpp
Switches boot menu rendering from lcd pointers to a direct lcd object and updates the drawing calls across the bootloader screens.
Target LCD drivers and LTDC sync
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
Moves LTDC reload completion to BOOT-only waits and runtime lvglFlushed() signaling across board drivers, removes init-time line interrupt enables where changed, updates LCD_PHYS_W/H usage, and adjusts buffer sizing and direct-mode handling.

Sequence Diagram(s)

sequenceDiagram
  participant startLcdRefresh
  participant LTDC_IRQHandler
  participant lvglFlushed
  participant lv_disp_flush_ready

  startLcdRefresh->>LTDC_IRQHandler: trigger LTDC reload
  LTDC_IRQHandler->>LTDC_IRQHandler: BOOT or runtime branch
  LTDC_IRQHandler->>lvglFlushed: runtime flush completion
  lvglFlushed->>lv_disp_flush_ready: mark flush ready
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

firmware, UX-UI

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.82% 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: reducing LCD refresh busy-waiting.
Description check ✅ Passed The description includes the key change summary, testing note, and target-specific exception, which is sufficient for the template.
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).

@philmoz philmoz force-pushed the philmoz/reduce-busy-wait branch from f80393d to c83bfce Compare June 25, 2026 11:19

@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.

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@radio/src/targets/horus/lcd_driver.cpp`:
- Around line 129-133: The LTDC line interrupt can remain latched after the
handler disables it, so re-enabling LTDC_IT_LI in lcd_driver.cpp may trigger an
immediate interrupt instead of waiting for vertical blank. In the code path that
updates LTDC_Layer1->CFBAR and requests the shadow reload via LTDC->SRCR, clear
the latched line interrupt flag with the existing LTDC handle before calling
__HAL_LTDC_ENABLE_IT, using the same interrupt setup around
__HAL_LTDC_CLEAR_FLAG / __HAL_LTDC_ENABLE_IT to ensure the next interrupt fires
only after the intended reload.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 26a6a050-1d6f-4187-814b-323ba66ad2fb

📥 Commits

Reviewing files that changed from the base of the PR and between f80393d and c83bfce.

📒 Files selected for processing (20)
  • 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/boot_lcd.cpp
  • radio/src/gui/colorlcd/boot_menu.cpp
  • 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/targets/taranis/board.h
  • radio/src/boards/rm-h750/lcd_driver_800.h
✅ Files skipped from review due to trivial changes (2)
  • radio/src/gui/common/stdlcd/lcd_common.h
  • radio/src/gui/common/stdlcd/lcd_common.cpp
🚧 Files skipped from review as they are similar to previous changes (11)
  • radio/src/targets/stm32h7s78-dk/lcd_driver.cpp
  • radio/src/targets/simu/simulib.cpp
  • radio/src/targets/horus/lcd_st7796s_driver.cpp
  • radio/src/boards/rm-h750/lcd_driver_480.h
  • radio/src/boards/rm-h750/lcd_driver_480.cpp
  • radio/src/targets/pa01/lcd_driver.cpp
  • radio/src/gui/colorlcd/lcd.h
  • radio/src/boards/rm-h750/lcd_driver_800.cpp
  • radio/src/targets/st16/lcd_driver.cpp
  • radio/src/boards/jumper-h750/lcd_driver.cpp
  • radio/src/targets/pl18/lcd_driver.cpp

Comment thread radio/src/targets/horus/lcd_driver.cpp
@philmoz

philmoz commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator Author

Busy wait loop removed from F4 radios with inverted LCD display.
This also saves 255K of SDRAM since only one LVGL frame buffer is needed.

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