@@ -32,8 +32,19 @@ static size_t g_screenLogCount = 0;
3232static char g_logText[LOG_TEXT_CAP ];
3333static size_t g_logTextLen = 0 ;
3434static uint32_t g_lastLvglTickMs = 0 ;
35+ static uint32_t g_lastLvglHandlerMs = 0 ;
36+ static uint32_t g_logDirtySinceMs = 0 ;
37+ static bool g_logDirty = false ;
38+ static bool g_forceFollowPending = false ;
3539static bool g_logoShown = false ;
3640
41+ // Keep display work out of the servo command path. Log messages are appended
42+ // to the in-memory ring immediately, then coalesced into one LVGL update after
43+ // a short quiet window. LVGL itself only needs a modest service rate because
44+ // this UI has no latency-sensitive animations.
45+ static const uint32_t LOG_FLUSH_DEBOUNCE_MS = 50 ;
46+ static const uint32_t LVGL_HANDLER_INTERVAL_MS = 20 ;
47+
3748void lvglDisplayFlush (lv_disp_drv_t * disp, const lv_area_t * area,
3849 lv_color_t * colorP) {
3950 uint32_t w = static_cast <uint32_t >(area->x2 - area->x1 + 1 );
@@ -109,16 +120,29 @@ void scrollLogToBottom() {
109120 lv_obj_scroll_to_y (g_logPanel, LV_COORD_MAX , LV_ANIM_OFF );
110121}
111122
112- void appendLogToUi (const char * line, bool forceFollow) {
113- if (!g_logLabel) return ;
114- if (!recoverbot::shouldShowLineOnScreen (line)) return ;
123+ void flushPendingLogUi () {
124+ if (!g_logDirty || !g_logLabel) return ;
115125
116126 bool wasAtBottom = true ;
117127 if (g_logPanel) {
118128 lv_obj_update_layout (g_logPanel);
119129 wasAtBottom = lv_obj_get_scroll_bottom (g_logPanel) <= 6 ;
120130 }
121131
132+ rebuildLogText ();
133+ lv_label_set_text_static (g_logLabel, g_logText);
134+ updateStatusLabel ();
135+
136+ if (g_forceFollowPending || wasAtBottom) scrollLogToBottom ();
137+
138+ g_logDirty = false ;
139+ g_forceFollowPending = false ;
140+ }
141+
142+ void appendLogToUi (const char * line, bool forceFollow) {
143+ if (!g_logLabel) return ;
144+ if (!recoverbot::shouldShowLineOnScreen (line)) return ;
145+
122146 size_t idx;
123147 if (g_screenLogCount < SCREEN_LOG_LINES ) {
124148 idx = screenLogPhysicalIndex (g_screenLogCount);
@@ -129,12 +153,9 @@ void appendLogToUi(const char* line, bool forceFollow) {
129153 }
130154
131155 snprintf (g_screenLog[idx], SCREEN_LOG_LINE_CHARS , " %s" , line);
132- rebuildLogText ();
133-
134- lv_label_set_text_static (g_logLabel, g_logText);
135- updateStatusLabel ();
136-
137- if (forceFollow || wasAtBottom) scrollLogToBottom ();
156+ if (!g_logDirty) g_logDirtySinceMs = millis ();
157+ g_logDirty = true ;
158+ g_forceFollowPending = g_forceFollowPending || forceFollow;
138159}
139160
140161bool isLogoShown () { return g_logoShown; }
@@ -149,8 +170,9 @@ bool prepareLogUiForMessage() {
149170
150171void finishLogUiMessage (bool wasLogoShown) {
151172 if (!wasLogoShown) return ;
152- lv_obj_invalidate (lv_scr_act ());
153- lv_timer_handler ();
173+ // The pending log flush will restore the LVGL screen. Avoid a synchronous
174+ // render here because logMsg() is also called by press/release operations.
175+ g_forceFollowPending = true ;
154176}
155177
156178void initLogUi () {
@@ -216,6 +238,7 @@ void initLogUi() {
216238 lv_label_set_text_static (g_logLabel, g_logText);
217239
218240 g_lastLvglTickMs = millis ();
241+ g_lastLvglHandlerMs = g_lastLvglTickMs;
219242 lv_timer_handler ();
220243}
221244
@@ -224,7 +247,18 @@ void serviceLogUi() {
224247 lv_tick_inc (now - g_lastLvglTickMs);
225248 g_lastLvglTickMs = now;
226249
227- if (!g_logoShown) lv_timer_handler ();
250+ if (g_logoShown) return ;
251+
252+ bool flushed = false ;
253+ if (g_logDirty && (now - g_logDirtySinceMs) >= LOG_FLUSH_DEBOUNCE_MS ) {
254+ flushPendingLogUi ();
255+ flushed = true ;
256+ }
257+
258+ if (flushed || (now - g_lastLvglHandlerMs) >= LVGL_HANDLER_INTERVAL_MS ) {
259+ lv_timer_handler ();
260+ g_lastLvglHandlerMs = now;
261+ }
228262}
229263
230264void showLogoScreen () {
0 commit comments