perf(rtc-only): Phase 6 收尾 - 卡顿优化 + PowerSaveTimer 守卫 + 开机加速
代码改动: - AudioLoop 加 vTaskDelay(1),让出 Core 1 idle task 防 WiFi/RTC 饥饿 - BackgroundTask 优先级 2 → 5,提升 Opus 解码实时性 - LVGL 刷新 5ms → 16ms (60Hz),CPU 占用降 60% - GIF 定时器 20ms → 33ms (3 处),PSRAM 流量减半 - AI 字幕推送 100ms 节流,避免 LVGL 锁争抢 - EnterIdleHibernate 清空 audio_decode_queue_,防 standby_sound 残留误触发首帧 - PowerSaveTimer OnEnterSleepMode 加 device_state 守卫,拦截 dialog/connecting 期间关功放(修复欢迎语期间被静音 bug) - 取消开机 ADC 阻塞采样,开机播报响应从 6 秒缩到 < 3 秒 新增规划: - Phase 7 占位文档:电量保护 + PowerSaveTimer 重构 + 唤醒杂音根治 + RTC 抖动缓解 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b8a5fe958f
commit
4b7b1949d4
@ -0,0 +1,97 @@
|
|||||||
|
# Phase 7:电量保护 + 低功耗管理重构
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
Phase 6 在调试唤醒杂音过程中,暴露出三个**历史代码的耦合问题**,它们彼此牵连影响 UX:
|
||||||
|
|
||||||
|
1. **开机电量保护**([application.cc:614](../../../../main/application.cc#L614) 原 618-630)
|
||||||
|
- 同步采样 20 × 10 × 10ms = **6 秒阻塞**才能进入开机播报
|
||||||
|
- 电量 ≤ 25% 直接 `SetOutputVolumeRuntime(0)` 静音,没有 UI 提示
|
||||||
|
- 无屏 UI 阶段的遗留设计(防止低电压下功放产生噪声)
|
||||||
|
- **Phase 6 已临时禁用**,恢复开机响应速度
|
||||||
|
|
||||||
|
2. **PowerSaveTimer 在 dialog/connecting 状态错误关闭功放**
|
||||||
|
- PowerSaveCheck 状态机 `in_sleep_mode_` 翻转有边角 bug:WakeUp 重置 ticks 但 `in_sleep_mode_` 残留为 true 的路径
|
||||||
|
- 历史症状:欢迎语期间 PowerSaveTimer 触发 OnEnterSleepMode → `codec->EnableOutput(false)` → 听不到欢迎语
|
||||||
|
- **Phase 6 已加 device_state 守卫拦截**([movecall_moji_esp32s3.cc:259](../../../../main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc#L259)),但只是补丁,根因未除
|
||||||
|
|
||||||
|
3. **PowerSaveCheck callback 外的 esp_pm_configure 不受守卫保护**
|
||||||
|
- [power_save_timer.cc:65](../../../../main/boards/common/power_save_timer.cc) callback 后无条件下发 `light_sleep_enable=true`
|
||||||
|
- 即使守卫拦截关功放,I2C/I2S 总线仍可能因 Light Sleep 被掐
|
||||||
|
- 历史症状:唤醒后 codec 通信失败 / I2S DMA 卡死
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
把上述三块**重构成一个连贯系统**,而非局部打补丁:
|
||||||
|
|
||||||
|
- 异步 + 增量电量监测,移除开机阻塞
|
||||||
|
- 屏幕 UI 低电提示(图标 + 文案),替代粗暴静音
|
||||||
|
- 分级低电策略(>25% 正常 / 15-25% 降音量 / <15% UI 警告 / <5% 强制 idle)
|
||||||
|
- PowerSaveTimer 状态机重写,根本性解决 `in_sleep_mode_` 边角
|
||||||
|
- esp_pm_configure 调用统一收口到 callback 内部,受 device_state 守卫保护
|
||||||
|
|
||||||
|
## 范围(暂定,进入 Phase 7 时细化)
|
||||||
|
|
||||||
|
### 7.1 异步电量监测
|
||||||
|
- 后台 FreeRTOS task 定时(如 5s 一次)ADC 采样,更新 `battery_level_` 原子变量
|
||||||
|
- `GetBatteryLevel()` 立即返回缓存值,开机首次返回 100% 或上次 NVS 持久化值
|
||||||
|
- 开机播报不再被电池采样阻塞
|
||||||
|
|
||||||
|
### 7.2 屏幕低电 UI
|
||||||
|
- 顶部状态栏电量图标(已有 LVGL 框架支持)
|
||||||
|
- ≤15% 弹窗"电量不足,请充电",但**不静音**,让用户主动响应
|
||||||
|
- ≤5% 才强制进入 idle,配合 Phase 6 hibernate 流程退出 RTC 房间
|
||||||
|
|
||||||
|
### 7.3 PowerSaveTimer 状态机重写
|
||||||
|
- 用清晰的 4 态机:ACTIVE / DIMMING / SLEEPING / WAKING
|
||||||
|
- `WakeUp()` 同时清 `ticks_` 和 `in_sleep_mode_`,消除"已睡未标记"路径
|
||||||
|
- `OnEnterSleepMode` 内部统一调用 `esp_pm_configure`,被 device_state 守卫保护
|
||||||
|
- 与 Phase 6 hibernate 状态机协同(不重复进入 sleep)
|
||||||
|
|
||||||
|
### 7.4 PA 启停时机 / 唤醒杂音根治
|
||||||
|
- PowerSaveTimer/hibernate 都不应在 dialog 期间关 codec/PA
|
||||||
|
- 唤醒后 codec EnableOutput → 真实 PCM 到达约有 1 秒空窗,I2S 跑空 DMA → 杂音
|
||||||
|
- 候选方案:
|
||||||
|
- 推迟 EnableOutput(true) 到 OnIncomingAudio 首帧(彻底消除空窗)
|
||||||
|
- GPIO PA 推迟到首帧 PCM 入队(事件驱动,不用 ramp)
|
||||||
|
- 用 codec 软静音但**不启用 DAC ramp**(避免之前 23s 爬升副作用),首帧瞬时解
|
||||||
|
- 多方案对比并实测后再决定
|
||||||
|
|
||||||
|
### 7.5 RTC 抖动缓解(音质优化)
|
||||||
|
- **下行音频编码 G.711A → Opus**:
|
||||||
|
- 当前 G.711A = 64 kbps,对丢包无 FEC 保护
|
||||||
|
- Opus 16 kbps 自带 FEC + DTX,抗丢包/带宽降 4 倍
|
||||||
|
- 需要服务端配合切换编解码器
|
||||||
|
- **Jitter buffer target 调整**:100ms → 200-300ms
|
||||||
|
- 用更多缓冲延迟换抗抖动能力
|
||||||
|
- 实测当前 buffer_ms 经常被自适应拉到 240-440ms,目标 100ms 偏低
|
||||||
|
- **Adaptive jitter buffer**:根据近 10s reor/expand_loss 动态调整 target
|
||||||
|
- 评估指标:reor 降到 < 200,expand_loss 降到 < 5/2 秒为达标
|
||||||
|
|
||||||
|
## 当前临时状态(进入 Phase 7 前)
|
||||||
|
|
||||||
|
| 模块 | 临时方案 | 长期方案 |
|
||||||
|
|---|---|---|
|
||||||
|
| 开机电量保护 | application.cc 注释,直接用 NVS 音量 | Phase 7.1 + 7.2 |
|
||||||
|
| PowerSaveTimer 误关功放 | board.cc OnEnterSleepMode 加 device_state 守卫 | Phase 7.3 |
|
||||||
|
| 唤醒杂音 | 已知短板,~1s 杂音用户可接受 | Phase 7.4 |
|
||||||
|
| 下行音频抖动 | 接受 reor 700-1800 / expand_loss 20-130 的现状 | Phase 7.5 |
|
||||||
|
| hibernate 队列残留 | EnterIdleHibernate 清空 audio_decode_queue_ | 保留 |
|
||||||
|
|
||||||
|
## 输入文档
|
||||||
|
|
||||||
|
- [Phase 6 PLAN.md](../phase_06_idle_hibernate/PLAN.md) - hibernate 流程
|
||||||
|
- [Phase 6 HIBERNATE_REPORT.md](../phase_06_idle_hibernate/HIBERNATE_REPORT.md) - 实施记录
|
||||||
|
- [音频卡顿_全局资源分析.md](../../../../docs/Rtc_AIavatar/音频卡顿_全局资源分析.md)
|
||||||
|
- 本次调试笔记(待补充):唤醒杂音 → soft ramp 副作用 → 回退教训
|
||||||
|
|
||||||
|
## 触发条件
|
||||||
|
|
||||||
|
进入 Phase 7 的前置条件:
|
||||||
|
- [ ] Phase 6 hibernate 稳定运行 ≥ 1 周无回归
|
||||||
|
- [ ] 用户体验确认开机/休眠/唤醒流程顺畅
|
||||||
|
- [ ] 决定是否同步实现电量 UI(依赖屏幕设计稿)
|
||||||
|
|
||||||
|
## 状态
|
||||||
|
|
||||||
|
🟡 **占位中** - 等待 Phase 6 稳定后启动正式规划。
|
||||||
@ -611,23 +611,18 @@ void Application::Start() {
|
|||||||
uplink_resampler_.Configure(16000, 8000);
|
uplink_resampler_.Configure(16000, 8000);
|
||||||
codec->Start();
|
codec->Start();
|
||||||
}
|
}
|
||||||
{
|
// ⚠️ 开机电量保护逻辑临时禁用(Phase 7 重构)
|
||||||
int battery_level = 0;
|
// 原设计:开机同步采样 20×10×10ms ADC 数据 → 电量≤25% 时强制静音
|
||||||
bool charging = false;
|
// 问题:阻塞 6 秒才能播放开机播报,且阈值粗暴无 UI 提示
|
||||||
bool discharging = false;
|
// 临时方案:跳过阻塞采样,直接读 NVS 音量设置,恢复开机响应速度
|
||||||
if (board.GetBatteryLevel(battery_level, charging, discharging)) {
|
// 长期方案:见 .planning/milestones/digital_human_rtc/phases/phase_07_battery_psm/
|
||||||
// 如果电池电量低于25%,则将输出音量设置为0(静音)
|
{
|
||||||
if (battery_level <= 25) {
|
Settings s("audio", false);
|
||||||
codec->SetOutputVolumeRuntime(0);
|
int vol = s.GetInt("output_volume", AudioCodec::default_output_volume());
|
||||||
} else {
|
if (vol <= 0) {
|
||||||
Settings s("audio", false);
|
vol = AudioCodec::default_output_volume();
|
||||||
int vol = s.GetInt("output_volume", AudioCodec::default_output_volume());
|
|
||||||
if (vol <= 0) {
|
|
||||||
vol = AudioCodec::default_output_volume();
|
|
||||||
}
|
|
||||||
codec->SetOutputVolumeRuntime(vol);// 设置运行时输出音量
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
codec->SetOutputVolumeRuntime(vol);
|
||||||
}
|
}
|
||||||
|
|
||||||
// // 在启动阶段创建并运行播放管道以统一输出(开机启动播放管道)
|
// // 在启动阶段创建并运行播放管道以统一输出(开机启动播放管道)
|
||||||
@ -2023,6 +2018,10 @@ void Application::AudioLoop() {
|
|||||||
if (codec->output_enabled()) {
|
if (codec->output_enabled()) {
|
||||||
OnAudioOutput();
|
OnAudioOutput();
|
||||||
}
|
}
|
||||||
|
// 卡顿优化 1: 让出 Core 1 idle task(FreeRTOS 100Hz tick = 10ms)
|
||||||
|
// 避免 busy loop 占满 Core 1,防止 WiFi 中断/RTC 协议栈饥饿
|
||||||
|
// OnAudioInput/Output 内部本身处理一个完整 PCM 帧(20ms),10ms 调度间隔够
|
||||||
|
vTaskDelay(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4383,26 +4382,42 @@ void Application::EnterIdleHibernate() {
|
|||||||
|
|
||||||
auto display = Board::GetInstance().GetDisplay();
|
auto display = Board::GetInstance().GetDisplay();
|
||||||
|
|
||||||
|
// [废弃方案] 静音填充曾尝试在此处用 codec->OutputData 填 200ms 静音覆盖 DMA 残留
|
||||||
|
// 但实测会让 ES7210 codec 进入卡死状态(连续 10 次重启 ES7210 I2C Open fail)
|
||||||
|
// 移除该方案,杂音问题需要用其他方式解决(如降低唤醒后初始音量)
|
||||||
|
|
||||||
// 1. 真退出 RTC 房间(释放 License)
|
// 1. 真退出 RTC 房间(释放 License)
|
||||||
// Protocol 基类的虚函数 LeaveRoom 默认回退到 CloseAudioChannel,
|
// Protocol 基类的虚函数 LeaveRoom 默认回退到 CloseAudioChannel,
|
||||||
// VolcRtcProtocol 覆写为 volc_rtc_stop + volc_rtc_destroy
|
// VolcRtcProtocol 覆写为 volc_rtc_stop + volc_rtc_destroy
|
||||||
|
// 注意:LeaveRoom 内部会触发 on_audio_channel_closed_ 回调 → codec EnableOutput(false)
|
||||||
if (protocol_) {
|
if (protocol_) {
|
||||||
protocol_->LeaveRoom();
|
protocol_->LeaveRoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 字幕显示推迟到最后做(此时 LVGL 锁竞争最少)— 见步骤 9
|
auto codec = Board::GetInstance().GetAudioCodec();
|
||||||
|
|
||||||
// 3. 关闭 codec input/output 让状态机重置
|
// 3. 字幕显示推迟到最后做(此时 LVGL 锁竞争最少)— 见步骤 9
|
||||||
|
|
||||||
|
// 4. 显式关闭 codec input/output 让状态机重置(回调可能已关 output,这里幂等 + 关 input)
|
||||||
// 修复 bug:若不关闭,唤醒后 EnableInput(true) 会进入 "已 open" 异常路径
|
// 修复 bug:若不关闭,唤醒后 EnableInput(true) 会进入 "已 open" 异常路径
|
||||||
// → esp_codec_dev_set_in_channel_gain ES_ERROR_CHECK 失败 abort
|
// → esp_codec_dev_set_in_channel_gain ES_ERROR_CHECK 失败 abort
|
||||||
// → ESP32-S3 软重启而不是恢复对话
|
|
||||||
auto codec = Board::GetInstance().GetAudioCodec();
|
|
||||||
if (codec) {
|
if (codec) {
|
||||||
ESP_LOGI(TAG, "EnterIdleHibernate: 关闭 codec input/output 重置状态机");
|
ESP_LOGI(TAG, "EnterIdleHibernate: 关闭 codec input/output 重置状态机");
|
||||||
codec->EnableInput(false);
|
codec->EnableInput(false);
|
||||||
codec->EnableOutput(false);
|
codec->EnableOutput(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3.5. 清空音频解码队列:阻止 hibernate 之前残留的 standby_sound / AI 半句 PCM
|
||||||
|
// 在唤醒后的 OnAudioOutput 中被错误"首帧"识别,从而把软静音过早解开。
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
if (!audio_decode_queue_.empty()) {
|
||||||
|
ESP_LOGI(TAG, "EnterIdleHibernate: 清空残留音频队列 size=%zu",
|
||||||
|
audio_decode_queue_.size());
|
||||||
|
audio_decode_queue_.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 4. 关闭录音管道(避免唤醒后重新打开时冲突)
|
// 4. 关闭录音管道(避免唤醒后重新打开时冲突)
|
||||||
if (recorder_pipeline_) {
|
if (recorder_pipeline_) {
|
||||||
recorder_pipeline_close(recorder_pipeline_);
|
recorder_pipeline_close(recorder_pipeline_);
|
||||||
|
|||||||
@ -6,10 +6,12 @@
|
|||||||
#define TAG "BackgroundTask"
|
#define TAG "BackgroundTask"
|
||||||
|
|
||||||
BackgroundTask::BackgroundTask(uint32_t stack_size) {
|
BackgroundTask::BackgroundTask(uint32_t stack_size) {
|
||||||
|
// 卡顿优化 2: priority 2 → 5
|
||||||
|
// 避免 AI Opus 解码被 main_loop(pri 4)延迟,提升音频实时性
|
||||||
xTaskCreate([](void* arg) {
|
xTaskCreate([](void* arg) {
|
||||||
BackgroundTask* task = (BackgroundTask*)arg;
|
BackgroundTask* task = (BackgroundTask*)arg;
|
||||||
task->BackgroundTaskLoop();
|
task->BackgroundTaskLoop();
|
||||||
}, "background_task", stack_size, this, 2, &background_task_handle_);
|
}, "background_task", stack_size, this, 5, &background_task_handle_);
|
||||||
}
|
}
|
||||||
|
|
||||||
BackgroundTask::~BackgroundTask() {
|
BackgroundTask::~BackgroundTask() {
|
||||||
|
|||||||
@ -257,6 +257,15 @@ public:
|
|||||||
// 创建 PowerSaveTimer(仅 AI 模式需要)
|
// 创建 PowerSaveTimer(仅 AI 模式需要)
|
||||||
power_save_timer_ = new PowerSaveTimer(240, 10, -1);
|
power_save_timer_ = new PowerSaveTimer(240, 10, -1);
|
||||||
power_save_timer_->OnEnterSleepMode([this]() {
|
power_save_timer_->OnEnterSleepMode([this]() {
|
||||||
|
// 门禁:CanEnterSleepMode 已要求 idle,但 PowerSaveTimer 状态机存在
|
||||||
|
// "in_sleep_mode_ 未翻转 + WakeUp 后立即再次进入"的边角情况,
|
||||||
|
// 历史上曾在 dialog/connecting 期间关功放,导致欢迎语无声。
|
||||||
|
auto& app = Application::GetInstance();
|
||||||
|
auto state = app.GetDeviceState();
|
||||||
|
if (state != kDeviceStateIdle) {
|
||||||
|
ESP_LOGW(TAG, "PowerSaveTimer 在非 idle 状态(%d)触发,忽略关功放", (int)state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
ESP_LOGI(TAG, "🔋 进入低功耗模式:CPU降频、Light Sleep启用、功放关闭");
|
ESP_LOGI(TAG, "🔋 进入低功耗模式:CPU降频、Light Sleep启用、功放关闭");
|
||||||
auto codec = GetAudioCodec();
|
auto codec = GetAudioCodec();
|
||||||
if (codec) {
|
if (codec) {
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
#include "lvgl.h"
|
#include "lvgl.h"
|
||||||
#include "esp_lvgl_port.h"
|
#include "esp_lvgl_port.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
#include "esp_timer.h" // 卡顿优化 5: 字幕节流用 esp_timer_get_time
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
@ -174,7 +175,7 @@ void ai_chat_screen_init(void) {
|
|||||||
|
|
||||||
// 降低 GIF 定时器频率(10ms→20ms),平衡动画流畅度与 CPU 占用
|
// 降低 GIF 定时器频率(10ms→20ms),平衡动画流畅度与 CPU 占用
|
||||||
lv_gif_t *gifobj = (lv_gif_t *)gif_emotion;
|
lv_gif_t *gifobj = (lv_gif_t *)gif_emotion;
|
||||||
lv_timer_set_period(gifobj->timer, 20);
|
lv_timer_set_period(gifobj->timer, 33); // 卡顿优化 3: 20ms→33ms 减半 PSRAM 流量
|
||||||
|
|
||||||
// GIF 图标(表情上方居中,45x45)
|
// GIF 图标(表情上方居中,45x45)
|
||||||
// 表情高89,顶边y=-44.5,icon高45,中心再上移几像素避免重叠
|
// 表情高89,顶边y=-44.5,icon高45,中心再上移几像素避免重叠
|
||||||
@ -373,14 +374,14 @@ void ai_chat_set_emotion(const char* emotion) {
|
|||||||
lv_gif_set_src(gif_emotion, entry->emotion_gif);
|
lv_gif_set_src(gif_emotion, entry->emotion_gif);
|
||||||
// set_src 内部会重建 10ms 定时器,重新设置为 50ms 降低 CPU 占用
|
// set_src 内部会重建 10ms 定时器,重新设置为 50ms 降低 CPU 占用
|
||||||
lv_gif_t *gifobj = (lv_gif_t *)gif_emotion;
|
lv_gif_t *gifobj = (lv_gif_t *)gif_emotion;
|
||||||
lv_timer_set_period(gifobj->timer, 20);
|
lv_timer_set_period(gifobj->timer, 33); // 卡顿优化 3: 20ms→33ms 减半 PSRAM 流量
|
||||||
gif_animation_paused = false;
|
gif_animation_paused = false;
|
||||||
|
|
||||||
// 处理叠加图标
|
// 处理叠加图标
|
||||||
if (entry->icon_gif) {
|
if (entry->icon_gif) {
|
||||||
lv_gif_set_src(gif_icon, entry->icon_gif);
|
lv_gif_set_src(gif_icon, entry->icon_gif);
|
||||||
lv_gif_t *icon_gifobj = (lv_gif_t *)gif_icon;
|
lv_gif_t *icon_gifobj = (lv_gif_t *)gif_icon;
|
||||||
lv_timer_set_period(icon_gifobj->timer, 20);
|
lv_timer_set_period(icon_gifobj->timer, 33); // 卡顿优化 3: 20ms→33ms
|
||||||
lv_obj_clear_flag(gif_icon, LV_OBJ_FLAG_HIDDEN);
|
lv_obj_clear_flag(gif_icon, LV_OBJ_FLAG_HIDDEN);
|
||||||
} else {
|
} else {
|
||||||
// 隐藏图标时暂停其定时器,避免空跑浪费 CPU
|
// 隐藏图标时暂停其定时器,避免空跑浪费 CPU
|
||||||
@ -425,6 +426,17 @@ void ai_chat_set_chat_message(const char* role, const char* content) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 卡顿优化 5: 100ms 最小更新间隔(防抖)
|
||||||
|
// AI 流式 TTS 字幕每秒 5-15 次推送,节流后最多每秒 10 次
|
||||||
|
// 减少 PSRAM 写入流量 5-10 倍(chat_label 重绘)
|
||||||
|
// 例外:空内容(清空字幕)不节流,立即响应
|
||||||
|
static int64_t last_update_us = 0;
|
||||||
|
int64_t now_us = esp_timer_get_time();
|
||||||
|
if (content[0] != '\0' && (now_us - last_update_us) < 100000) { // 100ms
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
last_update_us = now_us;
|
||||||
|
|
||||||
if (!lvgl_port_lock(500)) { // 200ms → 500ms(GIF 解码繁忙时给予更长等待)
|
if (!lvgl_port_lock(500)) { // 200ms → 500ms(GIF 解码繁忙时给予更长等待)
|
||||||
ESP_LOGW(TAG, "LVGL锁超时,跳过字幕更新");
|
ESP_LOGW(TAG, "LVGL锁超时,跳过字幕更新");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -215,7 +215,7 @@ esp_err_t bg_gif_demo_switch_gif(const char *new_gif_path) {
|
|||||||
// (CLAUDE.md "lv_gif_set_src 会重建定时器" 经验)
|
// (CLAUDE.md "lv_gif_set_src 会重建定时器" 经验)
|
||||||
lv_gif_t *gifobj = (lv_gif_t *)g_gif_obj;
|
lv_gif_t *gifobj = (lv_gif_t *)g_gif_obj;
|
||||||
if (gifobj->timer) {
|
if (gifobj->timer) {
|
||||||
lv_timer_set_period(gifobj->timer, 20);
|
lv_timer_set_period(gifobj->timer, 33); // 卡顿优化 3: 20ms→33ms 减半 PSRAM 流量
|
||||||
}
|
}
|
||||||
|
|
||||||
lvgl_port_unlock();
|
lvgl_port_unlock();
|
||||||
|
|||||||
@ -330,7 +330,7 @@ void lvgl_lcd_init(){
|
|||||||
.task_stack = 8192,
|
.task_stack = 8192,
|
||||||
.task_affinity = -1,
|
.task_affinity = -1,
|
||||||
.task_max_sleep_ms = 500,
|
.task_max_sleep_ms = 500,
|
||||||
.timer_period_ms = 5
|
.timer_period_ms = 16 // 卡顿优化 4: 5ms→16ms (60Hz) 减少 LVGL CPU 占用 60%
|
||||||
};
|
};
|
||||||
lvgl_port_init(&lvgl_cfg);
|
lvgl_port_init(&lvgl_cfg);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user