Files
lucky-cat/docs/light-standby-power-optimization.zh-CN.md
T

26 KiB
Raw Blame History

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 始终使用活跃策略。
  • 远程日志持续尝试 HTTP 上传。
  • 电池检测和内存监控仍按活跃周期运行。
  • 缺少统一的待机状态、空闲计时、唤醒入口和失败回滚机制。
  • 如果进入待机时立即关闭功放,待机提示音将无法完整播放。

3. 总体解决方式

系统增加统一的轻待机状态协调器,状态变化如下:

ACTIVE
  | 5 分钟无交互,且当前业务允许待机
  v
ENTERING
  | 播放 standby_5min.opus,并等待真实播放完成
  | 再依次应用硬件和运行时低功耗策略
  v
STANDBY
  | 唤醒词、按键、抽签、模型命令或语音活动
  v
WAKING
  | 恢复背光、功放、Wi-Fi 和周期任务策略
  v
ACTIVE

进入待机前还会检查远端回复、用户讲话、待发送请求、配网等运行状态。播放期间如果出现新活动,或提示音播放失败、停止、卡死、被其他播放抢占,本次进入待机将被取消。应用低功耗策略中途失败时,已修改的策略会尽量恢复,避免设备停留在半待机状态。

4. 默认配置

当前默认配置如下,均可通过 menuconfig 或 sdkconfig.defaults 调整:

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="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_PAUSE_REMOTE_LOG_UPLOAD=y
CONFIG_TQ_LIGHT_STANDBY_REMOTE_LOG_POLL_MS=5000
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%。

具体代码

#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 和唤醒词保持运行。

具体代码

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 或等待超时:取消进入待机。
  • 播放期间出现新用户活动:提示音结束后重新检查活动时间,取消进入待机。

超时时间只作为故障保护上限,不是用来代替播放完成事件的固定等待。

具体代码

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;
    }
}

待机协调器按“先播报、后关功放”的顺序执行:

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 电机驱动

问题原因

步进电机停止移动后,如果线圈仍保持励磁,会持续产生保持电流。轻待机期间没有必要维持该静态扭矩。

解决方式

进入待机时获取运动互斥锁并释放线圈;后续运动命令会按原有流程重新驱动电机。

具体代码

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。

具体代码

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.6 远程日志

问题原因

待机期间继续发起 HTTP 上传会周期性唤醒网络和 CPU。完全停止日志采集又会丢失现场诊断信息。

解决方式

待机期间继续接收日志并落盘,但暂停 coredump 和日志文件的 HTTP 上传;轮询间隔放宽至 5 秒。唤醒后恢复上传。

具体代码

while (!s_stop_requested) {
    TickType_t now = xTaskGetTickCount();
    bool light_standby = remote_log_light_standby();

    if ((now - last_flush) >=
        pdMS_TO_TICKS(CONFIG_TQ_REMOTE_LOG_FLUSH_INTERVAL_MS)) {
        drain_memory_queue_to_flash();
        last_flush = now;
    }

    if (!light_standby &&
        voice_startup_upload_gate_open(startup_tick) &&
        (backoff_ms == 0U || now >= next_upload_tick)) {
        drain_memory_queue_to_flash();
        upload_coredump_report();
        upload_pending_files();
    }

    uint32_t poll_ms = light_standby ?
            CONFIG_TQ_LIGHT_STANDBY_REMOTE_LOG_POLL_MS :
            REMOTE_LOG_POLL_MS;
    ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(poll_ms));
}

功耗与影响

  • 减少待机期间 HTTP、TLS/TCP 和 Wi-Fi 活动。
  • 日志仍会写入 Flash,不会因为轻待机直接丢失。
  • 待机期间服务端无法立即看到新日志;唤醒后再补传。
  • 持续落盘仍有 Flash 写入功耗和寿命影响,但保留了诊断能力。

5.7 电池检测

问题原因

电池 ADC 和状态处理在待机期间没有必要保持活跃状态下的采样频率。

解决方式

普通采样周期在轻待机时切换为 300 秒。充电状态 GPIO 中断仍可立即唤醒电池检测任务并进行防抖后的新采样,满电确认和测量受限时的重试周期也保持原有优先级。

具体代码

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 秒周期报告。内存分配失败仍会绕过普通采样周期并立即处理。

具体代码

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 广播只在小程序配网期间使用,而配网期间轻待机资格检查会直接返回不允许进入待机:

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,必要时恢复此前已修改的项目。

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 运行时策略

static esp_err_t control_plane_light_standby_apply_runtime_policy(bool standby)
{
    control_plane_remote_log_set_light_standby(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_remote_log_set_light_standby(false);
            control_plane_battery_service_set_light_standby(false);
            control_plane_runtime_diagnostics_set_light_standby(false);
            return wifi_err;
        }
    }
    return ESP_OK;
}

6.3 唤醒来源

当前活动类型包括:

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。

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 省电,同时保留连接。
  7. 暂停远程日志 HTTP 上传,日志采集和落盘继续工作。
  8. 电池常规采样周期放宽到 300 秒,充电状态边沿仍及时处理。
  9. 内存监控采样放宽到 10 秒、周期报告放宽到 300 秒,分配失败仍及时处理。
  10. 支持唤醒词、按键、SW2 抽签、模型命令和语音活动快速唤醒。
  11. 唤醒后恢复正常背光、功放、Wi-Fi、日志、电池和内存监控策略。
  12. 增加进入失败回滚和状态竞态保护。
  13. 将轻待机任务栈增至 4096 字节,解决已观察到的任务栈溢出。

8. 验证情况

已完成的软件侧验证:

  • ESP-IDF 5.5.4 完整构建通过。
  • 当前构建生成的应用固件约 2.08 MiB,应用分区剩余约 41%。
  • standby_5min.opus 已纳入 LittleFS 音频资源。
  • 源码差异格式检查通过。
  • 已确认提示音等待的是播放管线终止事件,而不是固定时长硬等待。

当前尚未完成以下真机量化测试:

  • 活跃状态和轻待机状态的整机电流对比。
  • 各单项策略分别节省的电流。
  • 不同 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 刷新和音频下行链路是否值得进入下一轮优化。