Skip to content

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

Closed
philmoz wants to merge 1 commit into
mainfrom
philmoz/reduce-busy-wait
Closed

chore(color): reduce busy wait time on LCD refresh#7448
philmoz wants to merge 1 commit into
mainfrom
philmoz/reduce-busy-wait

Conversation

@philmoz

@philmoz philmoz commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Currently when the LCD display needs updating, the code initiates a hardware display update then enters a busy wait loop until the update is done.
This blocks LVGL until the busy wait loop ends.

Testing on current radios shows this busy wait averages ~9ms whenever the display is refreshed.

Since the display is double buffered the busy wait can be moved to before the display update is triggered - i.e. only wait if the previous frame has not finished.

This reduces the average busy wait to < 1us.

Applies to 2.12 and 3.0. Could be added to 2.11 if desired.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved LCD frame-buffer reload synchronization across several targets, including clearer wait behavior for bootloader builds to ensure updates complete at the right time.
  • Refactor

    • Standardized LCD timing/geometry calculations to use fixed physical dimension constants.
    • Updated headers to use #pragma once.
    • Removed unused global variables and legacy function-pointer based initialization for the affected LCD drivers.

@philmoz philmoz added this to the 2.12.2 milestone Jun 10, 2026
@philmoz philmoz requested review from 3djc and gagarinlg June 10, 2026 00:48
@philmoz philmoz added color Related generally to color LCD radios house keeping 🧹 Cleanup of code and house keeping labels Jun 10, 2026
@coderabbitai

coderabbitai Bot commented Jun 10, 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: 00ca2cb2-f292-49bb-81be-1a65cc46b534

📥 Commits

Reviewing files that changed from the base of the PR and between ce3d8f6 and 6cfc9c6.

📒 Files selected for processing (11)
  • 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/targets/horus/lcd_driver.cpp
  • radio/src/targets/horus/lcd_st7796s_driver.cpp
  • radio/src/targets/pl18/lcd_driver.cpp
  • radio/src/targets/st16/lcd_driver.cpp
  • radio/src/targets/stm32h7s78-dk/lcd_driver.cpp
💤 Files with no reviewable changes (1)
  • radio/src/boards/rm-h750/lcd_driver_800.h
✅ Files skipped from review due to trivial changes (1)
  • radio/src/boards/jumper-h750/lcd_driver.h
🚧 Files skipped from review as they are similar to previous changes (9)
  • radio/src/targets/horus/lcd_driver.cpp
  • radio/src/targets/stm32h7s78-dk/lcd_driver.cpp
  • radio/src/targets/horus/lcd_st7796s_driver.cpp
  • radio/src/targets/pl18/lcd_driver.cpp
  • radio/src/boards/jumper-h750/lcd_driver.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_480.cpp
  • radio/src/targets/st16/lcd_driver.cpp

📝 Walkthrough

Walkthrough

This PR refactors LCD drivers across multiple board and target configurations to use conditional LTDC reload synchronization based on build mode, migrate RM-H750 to fixed display constants, eliminate function pointer indirection, and modernize header include guards. Changes are applied consistently across jumper-h750, rm-h750, horus, pl18, st16, and stm32h7s78-dk.

Changes

LCD Driver Synchronization and Infrastructure Refactoring

Layer / File(s) Summary
LTDC frame reload synchronization pattern
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/pl18/lcd_driver.cpp, radio/src/targets/st16/lcd_driver.cpp, radio/src/targets/stm32h7s78-dk/lcd_driver.cpp
_frame_addr_reloaded is initialized to 1 across all drivers. Busy-wait behavior is now conditional on BOOT: non-BOOT builds wait before updating frame buffer; BOOT builds wait after triggering the LTDC reload via vertical blank.
RM-H750 display geometry constants migration
radio/src/boards/rm-h750/lcd_driver_480.cpp, radio/src/boards/rm-h750/lcd_driver_800.cpp
LTDC timing calculations, line-event trigger height, and layer window/image dimension configuration replace runtime lcd_phys_w/lcd_phys_h variables with compile-time constants LCD_PHYS_W/LCD_PHYS_H throughout both driver variants.
RM-H750 LCD initialization and global cleanup
radio/src/boards/rm-h750/lcd_driver_480.cpp, radio/src/boards/rm-h750/lcd_driver_800.cpp
Function pointer indirection (lcdInitFunction, lcdOffFunction, lcdOnFunction) is removed; initialization now directly enables the LTDC clock and calls LCD_ST7365_Init(). Global dimension variables and unused declarations are eliminated.
Header guards and include modernization
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
Headers switch from traditional #ifndef/#define guards to #pragma once. Function-pointer typedefs and extern declarations are removed. Include ordering is adjusted and macro formatting is aligned.

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

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.
Description check ❓ Inconclusive The description clearly explains the problem (9ms blocking busy wait), the solution (moving wait before update using double buffering), and the result (<1us overhead). However, it does not follow the repository's template structure with 'Fixes #' and 'Summary of changes:' sections. Consider formatting the description to match the repository template by adding a 'Fixes #' line if applicable and organizing content under 'Summary of changes:' heading.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title describes reducing busy wait time on LCD refresh, which accurately reflects the main objective of moving synchronization to occur before display updates rather than after.
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 and usage tips.

@pfeerick pfeerick added the backport/2.12 To be backported to a 2.12 release also. label Jun 12, 2026
Comment on lines 50 to +69
@@ -61,18 +63,17 @@ static void startLcdRefresh(lv_disp_drv_t *disp_drv, uint16_t *buffer,
LTDC->SRCR = LTDC_SRCR_VBR;
__HAL_LTDC_ENABLE_IT(&hltdc, LTDC_IT_LI);

// wait for reload
// TODO: replace through some smarter mechanism without busy wait
#if defined(BOOT)
// wait for reload to finish - required for bootloader
while(_frame_addr_reloaded == 0);
#endif

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

gui/colorlcd/lcd.cpp:flushLCD expects the flush to be completed, as it is calling lv_disp_flush_ready
As long as this assumption still holds, I am OK with this PR

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Tested bootloader and firmware on TX16S Mk2 & Mk3, TX15, T15, T15Pro, PA01 and ST16.
All work as expected so I think it's safe.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Given there is a very real possibility that LVGL may swap buffers and start drawing into a buffer that is still being scanned out until next VBlank since LVGL is told to flush before the new framebuffer is actually latched, deferring this for 2.12.3 and more testing.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

AFAIK this can't happen because we use two screen size buffers for LVGL.
LVGL is always rendering to the buffer that is not being displayed.

Blue = LVGL render. Red = LCD update. Green = busy wait.
Screenshot 2026-06-16 at 12 48 45 pm

@3djc 3djc left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Works nicely, reduces overall MCU load on busy screens, which is always very nice

@pfeerick pfeerick modified the milestones: 2.12.2, 2.12.3 Jun 15, 2026
@philmoz philmoz force-pushed the philmoz/reduce-busy-wait branch from ce3d8f6 to 6cfc9c6 Compare June 20, 2026 01:21
@philmoz philmoz marked this pull request as draft June 21, 2026 06:32
@philmoz

philmoz commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator Author

Noticed this can cause some screen flicker when scrolling screen using touch panel.

@philmoz philmoz closed this Jun 22, 2026
@philmoz

philmoz commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator Author

Not compatible with code that still refreshes LVGL outside main loop (e.g. flash bootloader).
Has issues with widgets using 'lcd' API when there are other things overlayed (e.g. menus).

@3djc

3djc commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Damm, too bad, the performance gain was noticeable

@philmoz

philmoz commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator Author

Version 2.

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.

Only concern is possible risk of calling LVGL 'lv_disp_flush_ready' function in interrupt handler - it is a very simple function though. @3djc, @gagarinlg, @raphaelcoeffic - any thoughts?

@gagarinlg

Copy link
Copy Markdown
Member

I am pretty sure that tis funczion is meant to be called from an interrupt context, when necessary.

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.

4 participants