656 lines
24 KiB
Markdown
656 lines
24 KiB
Markdown
# TQ CatN 轻待机低功耗优化说明
|
||
|
||
## 1. 文档范围
|
||
|
||
本文整理以下五个低功耗相关提交的设计和最终实现:
|
||
|
||
| 提交 | 内容 |
|
||
| --- | --- |
|
||
| `988dc92` | 增加 LCD 背光 PWM 亮度控制 |
|
||
| `6a11aca` | 增加轻待机状态协调器和活动事件接入 |
|
||
| `baad9dc` | 增加背光、扬声器功放和电机的待机策略 |
|
||
| `e773ebc` | 增加 Wi-Fi、电池检测和内存监控的待机策略(远程日志部分现已移除) |
|
||
| `cb203a7` | 进入轻待机前播放提示音,并等待真实播放完成 |
|
||
|
||
`bba99a0 fix(control-plane): trigger PTT on short press` 是独立的按键行为修复,不属于低功耗改动,因此不在本文范围内。
|
||
|
||
## 2. 需求与问题原因
|
||
|
||
设备需要在 5 分钟无交互后降低功耗,同时满足以下约束:
|
||
|
||
1. 基础表情必须持续显示。
|
||
2. 唤醒词检测必须持续工作。
|
||
3. 按键和 SW2 抽签操作必须能够快速唤醒。
|
||
4. 进入待机前必须完整播放 `standby_5min.opus`。
|
||
|
||
这些约束决定了设备不能直接进入 ESP Light Sleep 或 Deep Sleep:
|
||
|
||
- 基础表情当前由 LCD 渲染任务循环播放,需要 CPU、LCD 控制器和 SPI 链路继续工作。
|
||
- 唤醒词依赖麦克风采集、I2S RX、AFE 和本地识别任务持续运行。
|
||
- 设备还需要及时响应按键、抽签和业务事件。
|
||
|
||
因此,本次实现的是“轻待机”,即系统仍保持运行,只关闭或降低非必要硬件和周期任务的功耗。优化前存在以下主要问题:
|
||
|
||
- LCD 背光只有开关控制,无法在保留画面的同时降低背光功耗。
|
||
- 扬声器功放在无音频播放时仍可能保持供电。
|
||
- 步进电机完成动作后可能继续保持线圈电流。
|
||
- Wi-Fi 始终使用活跃策略。
|
||
- 电池检测和内存监控仍按活跃周期运行。
|
||
- 缺少统一的待机状态、空闲计时、唤醒入口和失败回滚机制。
|
||
- 如果进入待机时立即关闭功放,待机提示音将无法完整播放。
|
||
|
||
## 3. 总体解决方式
|
||
|
||
系统增加统一的轻待机状态协调器,状态变化如下:
|
||
|
||
```text
|
||
ACTIVE
|
||
| 5 分钟无交互,且当前业务允许待机
|
||
v
|
||
ENTERING
|
||
| 播放 standby_5min.opus,并等待真实播放完成
|
||
| 再依次应用硬件和运行时低功耗策略
|
||
v
|
||
STANDBY
|
||
| 唤醒词、按键、抽签、模型命令或语音活动
|
||
v
|
||
WAKING
|
||
| 恢复背光、功放、Wi-Fi 和周期任务策略
|
||
v
|
||
ACTIVE
|
||
```
|
||
|
||
进入待机前还会检查远端回复、用户讲话、待发送请求、配网等运行状态。播放期间如果出现新活动,或提示音播放失败、停止、卡死、被其他播放抢占,本次进入待机将被取消。应用低功耗策略中途失败时,已修改的策略会尽量恢复,避免设备停留在半待机状态。
|
||
|
||
## 4. 默认配置
|
||
|
||
当前默认配置如下,均可通过 `menuconfig` 或 `sdkconfig.defaults` 调整:
|
||
|
||
```ini
|
||
CONFIG_TQ_SCREEN_BACKLIGHT_BRIGHTNESS_PERCENT=100
|
||
|
||
CONFIG_TQ_LIGHT_STANDBY_ENABLE=y
|
||
CONFIG_TQ_LIGHT_STANDBY_IDLE_TIMEOUT_MS=300000
|
||
CONFIG_TQ_LIGHT_STANDBY_DIM_BACKLIGHT=y
|
||
CONFIG_TQ_LIGHT_STANDBY_BACKLIGHT_PERCENT=10
|
||
CONFIG_TQ_LIGHT_STANDBY_PROMPT_ENABLE=y
|
||
CONFIG_TQ_LIGHT_STANDBY_PROMPT_FILE="audio_prompts/standby_5min.opus"
|
||
CONFIG_TQ_LIGHT_STANDBY_DISABLE_SPEAKER_OUTPUT=y
|
||
CONFIG_TQ_LIGHT_STANDBY_RELEASE_MOTOR_HOLD=y
|
||
CONFIG_TQ_LIGHT_STANDBY_WIFI_POWER_SAVE=y
|
||
CONFIG_TQ_LIGHT_STANDBY_BATTERY_INTERVAL_SEC=300
|
||
CONFIG_TQ_LIGHT_STANDBY_MEMORY_SAMPLE_INTERVAL_MS=10000
|
||
CONFIG_TQ_LIGHT_STANDBY_MEMORY_REPORT_INTERVAL_MS=300000
|
||
CONFIG_TQ_LIGHT_STANDBY_TASK_STACK_BYTES=4096
|
||
CONFIG_TQ_LIGHT_STANDBY_TASK_PRIORITY=3
|
||
```
|
||
|
||
其中:
|
||
|
||
- 活跃状态背光默认 100%。
|
||
- 轻待机背光默认 10%,仍保持基础表情可见。
|
||
- 空闲超时默认 300000 ms,即 5 分钟。
|
||
- 协调器任务栈为 4096 字节。原 3072 字节配置在日志压力场景下出现过 `cp_light_standby` 栈溢出,增大后解决该问题。
|
||
|
||
## 5. 各项优化及具体代码
|
||
|
||
### 5.1 LCD 背光 PWM
|
||
|
||
#### 问题原因
|
||
|
||
背光 GPIO 原先只能输出固定电平。基础表情必须保留,因此不能直接关闭 LCD;若背光一直保持全亮,LCD 背光会成为待机阶段的主要持续耗电项之一。
|
||
|
||
#### 解决方式
|
||
|
||
使用 ESP-IDF LEDC 输出 20 kHz、10 位 PWM,并兼容背光高电平有效和低电平有效。提供运行时亮度接口,活跃状态使用正常亮度,待机状态切换为配置的 10%。
|
||
|
||
#### 具体代码
|
||
|
||
```c
|
||
#define LCD_DIRECT_BACKLIGHT_PWM_FREQUENCY_HZ 20000U
|
||
#define LCD_DIRECT_BACKLIGHT_PWM_DUTY_RESOLUTION LEDC_TIMER_10_BIT
|
||
#define LCD_DIRECT_BACKLIGHT_PWM_DUTY_MAX ((1U << 10U) - 1U)
|
||
#define LCD_DIRECT_BACKLIGHT_PWM_TIMER LEDC_TIMER_0
|
||
#define LCD_DIRECT_BACKLIGHT_PWM_CHANNEL LEDC_CHANNEL_0
|
||
|
||
static uint32_t lcd_direct_backlight_duty(uint8_t brightness_percent)
|
||
{
|
||
uint32_t duty = ((uint32_t)brightness_percent *
|
||
LCD_DIRECT_BACKLIGHT_PWM_DUTY_MAX + 50U) / 100U;
|
||
#if !CONFIG_TQ_SCREEN_BACKLIGHT_ACTIVE_HIGH
|
||
duty = LCD_DIRECT_BACKLIGHT_PWM_DUTY_MAX - duty;
|
||
#endif
|
||
return duty;
|
||
}
|
||
|
||
static esp_err_t lcd_direct_backlight_init(void)
|
||
{
|
||
const ledc_timer_config_t timer_config = {
|
||
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||
.duty_resolution = LCD_DIRECT_BACKLIGHT_PWM_DUTY_RESOLUTION,
|
||
.timer_num = LCD_DIRECT_BACKLIGHT_PWM_TIMER,
|
||
.freq_hz = LCD_DIRECT_BACKLIGHT_PWM_FREQUENCY_HZ,
|
||
.clk_cfg = LEDC_AUTO_CLK,
|
||
};
|
||
ESP_RETURN_ON_ERROR(ledc_timer_config(&timer_config), TAG,
|
||
"backlight PWM timer config failed");
|
||
|
||
const ledc_channel_config_t channel_config = {
|
||
.gpio_num = CONFIG_TQ_SCREEN_BACKLIGHT_GPIO,
|
||
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||
.channel = LCD_DIRECT_BACKLIGHT_PWM_CHANNEL,
|
||
.intr_type = LEDC_INTR_DISABLE,
|
||
.timer_sel = LCD_DIRECT_BACKLIGHT_PWM_TIMER,
|
||
.duty = lcd_direct_backlight_duty(
|
||
CONFIG_TQ_SCREEN_BACKLIGHT_BRIGHTNESS_PERCENT),
|
||
.hpoint = 0,
|
||
};
|
||
return ledc_channel_config(&channel_config);
|
||
}
|
||
|
||
esp_err_t lcd_direct_set_backlight_brightness(uint8_t brightness_percent)
|
||
{
|
||
ESP_RETURN_ON_FALSE(brightness_percent <= 100U,
|
||
ESP_ERR_INVALID_ARG, TAG,
|
||
"backlight brightness must be between 0 and 100 percent");
|
||
|
||
esp_err_t err = ledc_set_duty(
|
||
LEDC_LOW_SPEED_MODE,
|
||
LCD_DIRECT_BACKLIGHT_PWM_CHANNEL,
|
||
lcd_direct_backlight_duty(brightness_percent));
|
||
if (err == ESP_OK) {
|
||
err = ledc_update_duty(
|
||
LEDC_LOW_SPEED_MODE,
|
||
LCD_DIRECT_BACKLIGHT_PWM_CHANNEL);
|
||
}
|
||
return err;
|
||
}
|
||
```
|
||
|
||
#### 功耗与影响
|
||
|
||
- 降低 LED 背光平均电流,是本方案中最直接的节电项。
|
||
- LCD 控制器、SPI 传输和基础表情渲染仍在运行,因此并未消除显示链路功耗。
|
||
- 亮度百分比与实际电流通常不是严格线性关系,需要真机测量后确定最佳值。
|
||
|
||
### 5.2 扬声器功放与音频输出链路
|
||
|
||
#### 问题原因
|
||
|
||
待机期间不需要声音输出,但仍要保留麦克风和唤醒词。直接关闭整套音频设备会破坏快速语音唤醒;只停止播放又不能保证外部功放已断电。
|
||
|
||
#### 解决方式
|
||
|
||
待机时先将 Codec 下行输出静音,再关闭 PA GPIO。唤醒时重新打开 PA。麦克风采集、I2S RX、AFE 和唤醒词保持运行。
|
||
|
||
#### 具体代码
|
||
|
||
```c
|
||
esp_err_t multimodal_audio_set_speaker_output_enabled(bool enabled)
|
||
{
|
||
multimodal_audio_ctx_t *ctx = &s_audio;
|
||
if (!ctx->initialized || ctx->codec_dev == NULL ||
|
||
ctx->gpio_if == NULL || ctx->gpio_if->set == NULL) {
|
||
return ESP_ERR_INVALID_STATE;
|
||
}
|
||
|
||
if (!enabled) {
|
||
esp_err_t mute_err = set_downstream_output_mute_if_needed(
|
||
ctx, true, "Light standby");
|
||
if (mute_err != ESP_OK) {
|
||
return mute_err;
|
||
}
|
||
}
|
||
|
||
int ret = ctx->gpio_if->set(CONFIG_MULTIMODAL_AUDIO_PA_GPIO, enabled);
|
||
if (ret != ESP_CODEC_DEV_OK) {
|
||
return (esp_err_t)ret;
|
||
}
|
||
return ESP_OK;
|
||
}
|
||
```
|
||
|
||
#### 功耗与影响
|
||
|
||
- 降低外部扬声器功放的静态功耗。
|
||
- 不关闭音频输入侧,因此语音唤醒能力不受该策略影响。
|
||
- I2S TX、解码和下行缓冲相关任务未完全停用,仍有进一步优化空间。
|
||
- 唤醒后 PA 恢复存在很短的硬件稳定时间,首段声音需要在真机上确认是否完整。
|
||
|
||
### 5.3 待机提示音完整播放
|
||
|
||
#### 问题原因
|
||
|
||
如果空闲时间到达后立即关闭功放,`standby_5min.opus` 无法播放。仅按音频时长固定延时也不可靠,因为缓冲、解码、播放被打断或管线卡死都会使预计时长与真实播放状态不一致。
|
||
|
||
#### 解决方式
|
||
|
||
提示音发送完成后,以本次播放的 generation 为标识等待真实终止事件:
|
||
|
||
- `PLAYBACK_COMPLETED`:允许继续进入待机。
|
||
- `PLAYBACK_STOPPED`:取消进入待机。
|
||
- `PLAYBACK_STALLED` 或等待超时:取消进入待机。
|
||
- 播放期间出现新用户活动:提示音结束后重新检查活动时间,取消进入待机。
|
||
|
||
超时时间只作为故障保护上限,不是用来代替播放完成事件的固定等待。
|
||
|
||
#### 具体代码
|
||
|
||
```c
|
||
esp_err_t voice_prompt_play_file(const char *file_name)
|
||
{
|
||
/* 省略文件读取和 Opus 包发送过程。 */
|
||
esp_err_t finish_err = multimodal_audio_finish_downstream_response();
|
||
if (err == ESP_OK) {
|
||
err = finish_err;
|
||
}
|
||
|
||
if (err == ESP_OK) {
|
||
uint64_t timeout_ms =
|
||
(uint64_t)packet_count * VOICE_PROMPT_OPUS_FRAME_MS +
|
||
VOICE_PROMPT_COMPLETION_TIMEOUT_MARGIN_MS;
|
||
if (timeout_ms > INT32_MAX) {
|
||
timeout_ms = INT32_MAX;
|
||
}
|
||
err = multimodal_audio_wait_downstream_playback_complete(
|
||
playback_generation,
|
||
(int)timeout_ms);
|
||
}
|
||
return err;
|
||
}
|
||
|
||
static esp_err_t downstream_terminal_type_to_error(
|
||
multimodal_audio_downstream_playback_event_type_t type)
|
||
{
|
||
switch (type) {
|
||
case MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_COMPLETED:
|
||
return ESP_OK;
|
||
case MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_STALLED:
|
||
return ESP_ERR_TIMEOUT;
|
||
case MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_STOPPED:
|
||
default:
|
||
return ESP_ERR_INVALID_STATE;
|
||
}
|
||
}
|
||
```
|
||
|
||
待机协调器按“先播报、后关功放”的顺序执行:
|
||
|
||
```c
|
||
esp_err_t policy_err = control_plane_light_standby_play_entry_prompt();
|
||
|
||
bool entry_current = policy_err == ESP_OK &&
|
||
control_plane_light_standby_entry_is_current(entry_activity_ms);
|
||
bool runtime_eligible = entry_current &&
|
||
control_plane_light_standby_runtime_is_eligible();
|
||
|
||
if (runtime_eligible) {
|
||
policy_applied = true;
|
||
policy_err = control_plane_light_standby_apply_entry_policy();
|
||
}
|
||
```
|
||
|
||
#### 功耗与影响
|
||
|
||
- 提示音播放期间仍保持正常音频输出功耗。
|
||
- 实际进入轻待机的时间为“5 分钟空闲时间 + 提示音实际播放时间”。
|
||
- 播放异常时宁可取消本次待机,也不会在提示音未完成时关闭功放。
|
||
|
||
### 5.4 电机驱动
|
||
|
||
#### 问题原因
|
||
|
||
步进电机停止移动后,如果线圈仍保持励磁,会持续产生保持电流。轻待机期间没有必要维持该静态扭矩。
|
||
|
||
#### 解决方式
|
||
|
||
进入待机时获取运动互斥锁并释放线圈;后续运动命令会按原有流程重新驱动电机。
|
||
|
||
#### 具体代码
|
||
|
||
```c
|
||
esp_err_t platform_stepper_driver_release_hold(int timeout_ms)
|
||
{
|
||
if (timeout_ms < 0) {
|
||
return ESP_ERR_INVALID_ARG;
|
||
}
|
||
if (!ensure_stepper_motion_mutex()) {
|
||
return ESP_ERR_INVALID_STATE;
|
||
}
|
||
|
||
TickType_t lock_ticks = pdMS_TO_TICKS(timeout_ms);
|
||
if (timeout_ms > 0 && lock_ticks == 0) {
|
||
lock_ticks = 1;
|
||
}
|
||
if (xSemaphoreTake(s_stepper_motion_mutex, lock_ticks) != pdTRUE) {
|
||
return ESP_ERR_TIMEOUT;
|
||
}
|
||
|
||
portENTER_CRITICAL(&s_stepper_mux);
|
||
stepper_release_locked();
|
||
portEXIT_CRITICAL(&s_stepper_mux);
|
||
|
||
xSemaphoreGive(s_stepper_motion_mutex);
|
||
return ESP_OK;
|
||
}
|
||
```
|
||
|
||
#### 功耗与影响
|
||
|
||
- 消除电机线圈的待机保持电流。
|
||
- 电机不再提供静态保持扭矩,外力可能改变机构位置。
|
||
- 唤醒本身不重新励磁;下一次实际运动时才恢复驱动,避免无意义耗电。
|
||
|
||
### 5.5 Wi-Fi 省电
|
||
|
||
#### 问题原因
|
||
|
||
设备需要保留网络连接,但活跃 Wi-Fi 策略会使射频和基带更频繁工作。
|
||
|
||
#### 解决方式
|
||
|
||
进入待机后切换为 `WIFI_PS_MIN_MODEM`,保留 STA 和 WebSocket 连接。唤醒后恢复项目配置的正常策略;当前默认正常策略为 `WIFI_PS_NONE`。
|
||
|
||
#### 具体代码
|
||
|
||
```c
|
||
esp_err_t platform_wifi_portal_set_light_standby(bool standby)
|
||
{
|
||
wifi_ps_type_t ps_type = standby ? WIFI_PS_MIN_MODEM :
|
||
(CONFIG_TQ_WIFI_STA_POWER_SAVE ?
|
||
WIFI_PS_MIN_MODEM : WIFI_PS_NONE);
|
||
return esp_wifi_set_ps(ps_type);
|
||
}
|
||
```
|
||
|
||
#### 功耗与影响
|
||
|
||
- 保持联网能力的同时降低 Wi-Fi 平均功耗。
|
||
- 可能增加网络首包延迟,实际幅度受 AP 的 DTIM、信号质量和网络负载影响。
|
||
- 不等同于关闭 Wi-Fi,网络任务和 WebSocket 仍会消耗资源。
|
||
|
||
### 5.7 电池检测
|
||
|
||
#### 问题原因
|
||
|
||
电池 ADC 和状态处理在待机期间没有必要保持活跃状态下的采样频率。
|
||
|
||
#### 解决方式
|
||
|
||
普通采样周期在轻待机时切换为 300 秒。充电状态 GPIO 中断仍可立即唤醒电池检测任务并进行防抖后的新采样,满电确认和测量受限时的重试周期也保持原有优先级。
|
||
|
||
#### 具体代码
|
||
|
||
```c
|
||
uint32_t delay_ms;
|
||
if (status_err == ESP_OK && status.measurement_suppressed) {
|
||
delay_ms = BATTERY_SUPPRESSED_RETRY_MS;
|
||
} else if (full_hint_candidate) {
|
||
delay_ms = BATTERY_FULL_CONFIRM_RETRY_MS;
|
||
} else if (battery_light_standby()) {
|
||
delay_ms =
|
||
(uint32_t)CONFIG_TQ_LIGHT_STANDBY_BATTERY_INTERVAL_SEC * 1000U;
|
||
} else {
|
||
delay_ms =
|
||
(uint32_t)CONFIG_TQ_BATTERY_SAMPLE_INTERVAL_SEC * 1000U;
|
||
}
|
||
|
||
uint32_t notifications = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(delay_ms));
|
||
if (notifications > 0U && !battery_stop_requested()) {
|
||
vTaskDelay(pdMS_TO_TICKS(BATTERY_CHARGE_DEBOUNCE_MS));
|
||
ulTaskNotifyTake(pdTRUE, 0U);
|
||
}
|
||
```
|
||
|
||
#### 功耗与影响
|
||
|
||
- 减少 ADC 采样、状态计算和相关日志频率。
|
||
- 普通电量百分比更新变慢,最长可能接近配置的 300 秒。
|
||
- 插拔充电等 GPIO 边沿仍会触发及时检测,不受普通采样周期影响。
|
||
|
||
### 5.8 内存监控
|
||
|
||
#### 问题原因
|
||
|
||
内存监控在活跃状态下每 1 秒采样、每 30 秒报告。待机期间这种诊断频率会造成不必要的 CPU 唤醒和日志输出。
|
||
|
||
#### 解决方式
|
||
|
||
待机时改为每 10 秒采样、每 300 秒周期报告。内存分配失败仍会绕过普通采样周期并立即处理。
|
||
|
||
#### 具体代码
|
||
|
||
```c
|
||
uint32_t sample_interval_ms = light_standby ?
|
||
CONFIG_TQ_LIGHT_STANDBY_MEMORY_SAMPLE_INTERVAL_MS :
|
||
CONFIG_TQ_MEMORY_MONITOR_SAMPLE_INTERVAL_MS;
|
||
uint32_t report_interval_ms = light_standby ?
|
||
CONFIG_TQ_LIGHT_STANDBY_MEMORY_REPORT_INTERVAL_MS :
|
||
CONFIG_TQ_MEMORY_MONITOR_REPORT_INTERVAL_MS;
|
||
|
||
uint32_t alloc_fail_count =
|
||
__atomic_load_n(&s_alloc_fail_count, __ATOMIC_ACQUIRE);
|
||
bool unreported_alloc_failure =
|
||
alloc_fail_count != s_memory_monitor.reported_alloc_fail_count;
|
||
|
||
if (!force && !unreported_alloc_failure && s_memory_monitor.initialized &&
|
||
(now_tick - s_memory_monitor.last_sample_tick) < sample_ticks) {
|
||
return;
|
||
}
|
||
```
|
||
|
||
#### 功耗与影响
|
||
|
||
- 减少堆信息采集和周期日志带来的 CPU 开销。
|
||
- 常规内存水位变化的可见时间变长。
|
||
- 分配失败仍立即上报,关键故障检测没有被普通周期限制。
|
||
|
||
### 5.9 BLE 广播
|
||
|
||
#### 当前处理
|
||
|
||
本次没有增加轻待机下的 BLE 广播切换逻辑。项目的 BLE 广播只在小程序配网期间使用,而配网期间轻待机资格检查会直接返回不允许进入待机:
|
||
|
||
```c
|
||
static bool control_plane_light_standby_runtime_is_eligible(void)
|
||
{
|
||
if (control_plane_is_reset_in_progress() ||
|
||
control_plane_is_mini_program_provisioning_running()) {
|
||
return false;
|
||
}
|
||
/* 继续检查语音会话和远端回复状态。 */
|
||
return true;
|
||
}
|
||
```
|
||
|
||
#### 功耗与影响
|
||
|
||
- 正常业务状态没有持续 BLE 广播,因此没有可应用的待机节电策略。
|
||
- 配网状态优先保证 BLE 配网可用性,不进入轻待机。
|
||
- 如果后续增加常驻 BLE 广播,需要重新设计待机广播间隔和连接参数。
|
||
|
||
## 6. 统一进入、回滚和唤醒策略
|
||
|
||
### 6.1 进入策略
|
||
|
||
进入顺序为:释放电机保持电流、关闭扬声器输出、降低背光、应用运行时策略。任一步骤失败都会阻止状态进入 `STANDBY`,必要时恢复此前已修改的项目。
|
||
|
||
```c
|
||
static esp_err_t control_plane_light_standby_apply_entry_policy(void)
|
||
{
|
||
#if CONFIG_TQ_LIGHT_STANDBY_RELEASE_MOTOR_HOLD
|
||
ESP_RETURN_ON_ERROR(
|
||
domain_direction_release_motor_hold(MOTOR_RELEASE_WAIT_MS),
|
||
TAG, "release motor hold failed");
|
||
#endif
|
||
|
||
#if CONFIG_TQ_LIGHT_STANDBY_DISABLE_SPEAKER_OUTPUT
|
||
ESP_RETURN_ON_ERROR(
|
||
domain_runtime_multimodal_audio_set_speaker_output_enabled(false),
|
||
TAG, "disable speaker output failed");
|
||
#endif
|
||
|
||
#if CONFIG_TQ_LIGHT_STANDBY_DIM_BACKLIGHT
|
||
esp_err_t backlight_err = domain_runtime_set_lcd_backlight_brightness(
|
||
CONFIG_TQ_LIGHT_STANDBY_BACKLIGHT_PERCENT);
|
||
if (backlight_err != ESP_OK) {
|
||
domain_runtime_multimodal_audio_set_speaker_output_enabled(true);
|
||
return backlight_err;
|
||
}
|
||
#endif
|
||
|
||
esp_err_t runtime_err =
|
||
control_plane_light_standby_apply_runtime_policy(true);
|
||
if (runtime_err != ESP_OK) {
|
||
domain_runtime_set_lcd_backlight_brightness(
|
||
CONFIG_TQ_SCREEN_BACKLIGHT_BRIGHTNESS_PERCENT);
|
||
domain_runtime_multimodal_audio_set_speaker_output_enabled(true);
|
||
}
|
||
return runtime_err;
|
||
}
|
||
```
|
||
|
||
以上代码为便于阅读的等价摘录,实际代码还包含每个配置项的条件编译和详细错误日志。
|
||
|
||
### 6.2 运行时策略
|
||
|
||
```c
|
||
static esp_err_t control_plane_light_standby_apply_runtime_policy(bool standby)
|
||
{
|
||
control_plane_battery_service_set_light_standby(standby);
|
||
control_plane_runtime_diagnostics_set_light_standby(standby);
|
||
|
||
if (control_plane_is_sta_connected()) {
|
||
esp_err_t wifi_err =
|
||
domain_runtime_wifi_portal_set_light_standby(standby);
|
||
if (wifi_err != ESP_OK && standby) {
|
||
control_plane_battery_service_set_light_standby(false);
|
||
control_plane_runtime_diagnostics_set_light_standby(false);
|
||
return wifi_err;
|
||
}
|
||
}
|
||
return ESP_OK;
|
||
}
|
||
```
|
||
|
||
### 6.3 唤醒来源
|
||
|
||
当前活动类型包括:
|
||
|
||
```c
|
||
typedef enum {
|
||
CONTROL_PLANE_LIGHT_STANDBY_ACTIVITY_UNSPECIFIED = 0,
|
||
CONTROL_PLANE_LIGHT_STANDBY_ACTIVITY_KEY,
|
||
CONTROL_PLANE_LIGHT_STANDBY_ACTIVITY_LOCAL_WAKE,
|
||
CONTROL_PLANE_LIGHT_STANDBY_ACTIVITY_VOICE_INPUT,
|
||
CONTROL_PLANE_LIGHT_STANDBY_ACTIVITY_VOICE_OUTPUT,
|
||
CONTROL_PLANE_LIGHT_STANDBY_ACTIVITY_MODEL_COMMAND,
|
||
CONTROL_PLANE_LIGHT_STANDBY_ACTIVITY_EXTERNAL,
|
||
} control_plane_light_standby_activity_t;
|
||
```
|
||
|
||
- 唤醒词对应 `LOCAL_WAKE`。
|
||
- 普通按键和 SW2 抽签按键对应 `KEY`。
|
||
- 麦克风语音活动对应 `VOICE_INPUT`。
|
||
- 远端回复对应 `VOICE_OUTPUT`。
|
||
- 本地识别的抽签、方向等命令对应 `MODEL_COMMAND`。
|
||
|
||
记录活动时会立即刷新空闲计时。如果已经处于 `STANDBY`,状态切换为 `WAKING`,恢复运行时策略、正常背光和扬声器功放后返回 `ACTIVE`。
|
||
|
||
```c
|
||
esp_err_t control_plane_light_standby_record_activity(
|
||
control_plane_light_standby_activity_t activity)
|
||
{
|
||
uint64_t now_ms = control_plane_light_standby_now_ms();
|
||
|
||
portENTER_CRITICAL(&s_standby_lock);
|
||
s_standby.last_activity = activity;
|
||
s_standby.last_activity_ms = now_ms;
|
||
bool wake_required =
|
||
s_standby.state == CONTROL_PLANE_LIGHT_STANDBY_STATE_STANDBY;
|
||
portEXIT_CRITICAL(&s_standby_lock);
|
||
|
||
if (!wake_required) {
|
||
return ESP_OK;
|
||
}
|
||
|
||
s_standby.state = CONTROL_PLANE_LIGHT_STANDBY_STATE_WAKING;
|
||
esp_err_t wake_err = control_plane_light_standby_apply_wake_policy();
|
||
s_standby.state = CONTROL_PLANE_LIGHT_STANDBY_STATE_ACTIVE;
|
||
return wake_err;
|
||
}
|
||
```
|
||
|
||
以上为突出主流程的等价摘录;实际实现使用临界区和转换互斥锁保护状态,避免进入待机和唤醒同时修改硬件策略。
|
||
|
||
## 7. 解决成果
|
||
|
||
本轮改动已经实现:
|
||
|
||
1. 5 分钟无交互后自动进入轻待机。
|
||
2. 基础表情保持显示,待机背光默认降至 10%。
|
||
3. 进入待机前完整播放 `standby_5min.opus`,等待真实播放完成事件后才关闭功放。
|
||
4. 待机时 Codec 输出静音并关闭扬声器 PA,同时保留麦克风、AFE 和唤醒词。
|
||
5. 释放步进电机线圈保持电流。
|
||
6. Wi-Fi 切换为最小 Modem 省电,同时保留连接。
|
||
8. 电池常规采样周期放宽到 300 秒,充电状态边沿仍及时处理。
|
||
9. 内存监控采样放宽到 10 秒、周期报告放宽到 300 秒,分配失败仍及时处理。
|
||
10. 支持唤醒词、按键、SW2 抽签、模型命令和语音活动快速唤醒。
|
||
11. 唤醒后恢复正常背光、功放、Wi-Fi、电池和内存监控策略。
|
||
12. 增加进入失败回滚和状态竞态保护。
|
||
13. 将轻待机任务栈增至 4096 字节,解决已观察到的任务栈溢出。
|
||
|
||
## 8. 验证情况
|
||
|
||
已完成的软件侧验证:
|
||
|
||
- ESP-IDF 5.5.4 完整构建通过。
|
||
- 当前构建生成的应用固件约 2.08 MiB,应用分区剩余约 41%。
|
||
- `audio_prompts/standby_5min.opus` 已纳入应用镜像中的音频素材包。
|
||
- 源码差异格式检查通过。
|
||
- 已确认提示音等待的是播放管线终止事件,而不是固定时长硬等待。
|
||
|
||
当前尚未完成以下真机量化测试:
|
||
|
||
- 活跃状态和轻待机状态的整机电流对比。
|
||
- 各单项策略分别节省的电流。
|
||
- 不同 AP/DTIM 条件下的网络首包延迟。
|
||
- 唤醒词、按键和抽签从触发到完全恢复的耗时。
|
||
- PA 恢复后第一段声音是否存在削头或延迟。
|
||
|
||
因此,目前只能确认节电机制已生效,不能在没有仪器数据的情况下声称降低了具体多少 mA 或百分比。
|
||
|
||
## 9. 后续影响与剩余优化空间
|
||
|
||
### 9.1 明确的行为影响
|
||
|
||
| 项目 | 后续影响 |
|
||
| --- | --- |
|
||
| LCD | 画面继续刷新,背光变暗,但 LCD 控制器、SPI 和渲染功耗仍存在 |
|
||
| 扬声器 | 待机无声音输出;唤醒后 PA 需要短暂恢复时间 |
|
||
| 麦克风 | 保持运行,以满足唤醒词快速唤醒要求 |
|
||
| 电机 | 待机不保持静态扭矩,机构可能被外力移动 |
|
||
| Wi-Fi | 保持连接,但首包延迟可能增加 |
|
||
| 电池检测 | 普通电量更新变慢,充电状态边沿仍及时检测 |
|
||
| 内存监控 | 普通水位报告变慢,分配失败仍及时处理 |
|
||
| 提示音 | 真正进入待机的时间会比 5 分钟多出提示音播放时间 |
|
||
| BLE | 配网期间禁止进入轻待机,因此本次不修改 BLE 广播 |
|
||
|
||
### 9.2 仍可继续优化的项目
|
||
|
||
在“不关闭基础表情”和“不关闭唤醒词”的约束下,仍有以下优化空间:
|
||
|
||
1. 避免 LCD 重复传输完全相同的画面。
|
||
2. 轻待机时降低基础表情动画帧率,或停在静态基础帧。
|
||
3. 在确保提示音和唤醒后首包完整的前提下,进一步暂停 I2S TX、解码器和下行音频任务。
|
||
4. 继续检查任务监控、网络保活、传感器和其他周期诊断任务的唤醒频率。
|
||
5. 通过功耗仪逐项关闭策略,建立各模块的实际 mA 收益表,再决定优化优先级。
|
||
|
||
受持续显示和持续唤醒词约束,ESP Light Sleep/Deep Sleep 目前不适合直接使用。若未来允许基础表情静止、LCD 使用自刷新能力,或增加独立低功耗唤醒芯片,才有机会进入更深的睡眠状态并获得数量级更高的节电收益。
|
||
|
||
## 10. 结论
|
||
|
||
本次改动已经完成当前产品约束下的主要轻待机优化:保留基础表情、唤醒词和按键响应,同时降低背光、功放、电机、Wi-Fi、电池检测和内存监控的持续功耗。实现具备统一状态管理、真实音频完成等待、进入失败回滚和快速唤醒恢复。
|
||
|
||
下一步重点不是继续盲目关闭模块,而是进行真机电流和唤醒延迟测试。测试数据可以确认主要耗电来源,并判断 LCD 刷新和音频下行链路是否值得进入下一轮优化。
|