杰理 SDK 文档中心
首页
首页
  • 项目概览

    • AD23N SDK 概述与芯片平台
    • 工程结构与模块划分
  • 快速开始

    • 开发环境搭建与工具链
    • 编译构建指南
    • 烧录与固件升级工具
  • 应用框架与产品工作流

    • 应用入口与模式调度
    • 音乐播放应用
    • MIDI 解码与键盘演奏
    • 录音应用
    • LINEIN 与扩音应用
    • USB 从设备应用
    • 待机、软关机与空闲检测
    • 公共 UI 与 LED 显示
  • 音频子系统

    • 音频解码器框架
    • 音频编码器框架
    • 音效算法库
    • 音频管理与输出通路
  • 存储与文件系统

    • 文件系统层
    • NOR Flash 与虚拟机存储
    • 设备与设备管理
  • 系统服务与运行时

    • 消息机制与事件分发
    • 按键扫描与输入处理
    • 电源管理与低功耗控制
    • 定时器与系统任务
  • 外设驱动与平台

    • CPU 平台与启动流程
    • USB 协议栈与主机/设备驱动
    • SPI 与通用外设接口
  • 固件升级与构建工具

    • 固件升级机制
    • 编译后处理与镜像打包
    • 构建系统与命令行工具

待机、软关机与空闲检测

本文档介绍 AD23N GP-MCU SDK 中与待机(IDLE)、软关机(Soft-off)和空闲检测(Idle Detection)相关的完整实现:工作模式切换机制、关机提示音播放与 sys_softoff() 调用链、基于 500ms 心跳计数的空闲检测,以及待机模式的消息处理循环。

Purpose and Scope

本页面覆盖「应用层模式管理」中与低功耗退出路径相关的三个核心能力:

  • 软关机(Soft-off):收到 MSG_POWER_OFF 后切换到 SOFTOFF_MODE,播放关机提示音 dir_notice/power_off.f1a,随后调用 sys_softoff() 进入系统软关机;
  • 空闲检测(Idle Detection):app_powerdown_deal() 通过 500ms 级计数判断系统是否空闲,连续空闲达到阈值后进入 sys_power_down(-2) 睡眠;
  • 待机模式(Idle Mode):idle_app() 作为空闲工作模式的消息循环,处理热键并响应 MSG_CHANGE_WORK_MODE 退出。

不覆盖的内容:底层电源管理 API 的具体实现(sys_power_down/sys_softoff 属于 asm/power/power_api.h 驱动的硬件层)、按键扫描与热键处理(见 hot_msg.c 的按键分发页面)、以及文件系统/解码器本身的实现。这些属于独立的目录条目。

Overview

在 AD23N 这类低成本 MCU 音频方案中,「待机」与「软关机」是用户可感知的两种低功耗退出形态,而「空闲检测」是它们背后的公共机制:

  1. 软关机(Soft-off):区别于硬断电,软关机先播放一段提示音(power_off.f1a,F1A1 格式),播放结束后才真正调用 sys_softoff() 关闭系统。这是典型的消费电子交互设计——给用户听觉反馈后再断电。播放期间需要持续喂狗(wdt_clear()),并处理解码器结束消息。
  2. 空闲检测(Idle):系统主循环(bsp_loop())周期性调用 app_powerdown_deal(is_busy)。若系统繁忙则清零计数,否则累加;计数达到 IDLE_CNT_MAX(=2,即 2×500ms = 1 秒)后,保存 DAC 采样率、关闭 DAC、关闭数码管显示,然后进入 sys_power_down(-2) 睡眠,唤醒后恢复 DAC。
  3. 待机模式(Idle Mode):idle_app() 是应用层的一个工作模式,负责在空闲状态下接收消息(热键、模式切换),并保持系统运行。它的退出由 MSG_CHANGE_WORK_MODE 触发。

三者共同构成「检测空闲 → 降功耗 → 可唤醒」的闭环:空闲检测决定何时进入 power-down;软关机则是带提示音的一次性退出;待机模式则是系统停留在低负载但未断电的状态。

Architecture

下图展示了工作模式分发、软关机、空闲检测与底层电源 API 之间的关系(节点名称与源码中的函数/文件一一对应):

flowchart TD
    subgraph sg_App["应用层 app.c 主循环"]
        MainLoop["while(1) 消息分发"]
        ModeSoftoff["SOFTOFF_MODE → softoff_app()"]
        ModeIdle["IDLE_MODE → idle_app()"]
        ModeLoudspk["LOUDSPEAKER_MODE → loudspk_app()"]
    end

    subgraph sg_HotMsg["热键消息 hot_msg.c"]
        HotMsg["MSG_POWER_OFF 处理"]
        SetMode["work_mode = SOFTOFF_MODE<br/>post_msg(MSG_CHANGE_WORK_MODE)"]
    end

    subgraph sg_Softoff["软关机 softoff_mode.c"]
        SoftoffApp["softoff_app()"]
        Decoder["decoder_io(pfile, BIT_F1A1)"]
        PlayEnd{"MSG_*_FILE_END?"}
        SysSoftoff["sys_softoff()"]
        Wdt["MSG_500MS → wdt_clear()"]
    end

    subgraph sg_IdleCheck["空闲检测 idle_check.c"]
        PowerdownDeal["app_powerdown_deal(is_busy)"]
        Cnt{"app_idle_cnt == IDLE_CNT_MAX?"}
        DacOff["dac_power_off() + UI_init()"]
        SysPowerDown["sys_power_down(-2)"]
        DacOn["dac_power_on(sr, 0)"]
    end

    subgraph sg_IdleMode["待机模式 idle_mode.c"]
        IdleApp["idle_app()"]
        KeyTable["key_table_sel(idle_msg_filter)"]
        HandleHotkey["ap_handle_hotkey(msg[0])"]
        Exit["MSG_CHANGE_WORK_MODE → 退出"]
    end

    subgraph sg_Power["底层电源 API power_api.h"]
        PowerApi["sys_softoff / sys_power_down"]
    end

    MainLoop -->|"case IDLE_MODE"| ModeIdle
    MainLoop -->|"case SOFTOFF_MODE"| ModeSoftoff
    HotMsg -->|"MSG_POWER_OFF"| SetMode
    SetMode -->|"MSG_CHANGE_WORK_MODE"| MainLoop

    SoftoffApp --> Decoder
    SoftoffApp --> PlayEnd
    PlayEnd -->|"是"| SysSoftoff
    SoftoffApp --> Wdt
    SysSoftoff --> PowerApi

    PowerdownDeal --> Cnt
    Cnt -->|"是(连续空闲 1s)"| DacOff
    DacOff --> SysPowerDown
    SysPowerDown --> DacOn
    SysPowerDown --> PowerApi

    IdleApp --> KeyTable
    IdleApp --> HandleHotkey
    IdleApp --> Exit

架构要点说明:

  • app.c 的主循环是整个应用的中枢:它持有全局 work_mode 变量,根据消息 MSG_CHANGE_WORK_MODE 切换 IDLE_MODE / SOFTOFF_MODE / LOUDSPEAKER_MODE 等模式,并调用对应的 *_app() 函数(见 app.c)。模式函数各自拥有独立的 while(1) 消息循环,只有收到模式切换消息才返回,形成「一个模式一个循环」的结构。
  • 软关机路径是「先播放后断电」:softoff_app() 先初始化解码器、挂载文件系统、打开提示音文件,播放完成后才调用 sys_softoff()。任何一步失败(挂载失败、打开失败、解码器创建失败)都会跳过播放直接执行 __softoff_app_deal 标签处的 sys_softoff()。
  • 空闲检测位于 common/idle_check.c,是一个无状态依赖的纯计数函数,由系统主循环周期调用;它不关心上层是哪种工作模式,只根据 is_busy 标志决定计数清零或累加。
  • 待机模式 idle_app() 本质是一个「最小消息循环」:不播放音频、不操作外设,只处理热键(通过 ap_handle_hotkey)和模式切换。

软关机实现详解(softoff_app)

段链接指令与低功耗布局

softoff_mode.c 文件开头使用了一组 #pragma 段重定向指令,这是该文件最显著的工程特征:

#pragma bss_seg(".softoff_mode.data.bss")
#pragma data_seg(".softoff_mode.data")
#pragma const_seg(".softoff_mode.text.const")
#pragma code_seg(".softoff_mode.text")
#pragma str_literal_override(".softoff_mode.text.const")

Source: softoff_mode.c

设计意图:把软关机代码、只读数据、字符串常量全部放入独立的段(.softoff_mode.*)。这样链接脚本可以把这些段放置到特定的内存区域(通常是低功耗场景下仍保持供电的 RAM 区),保证系统进入深睡/软关机流程时,执行软关机所需的代码不会因为掉电区域失效而无法运行。这是 MCU 低功耗设计的常见做法——把「最后一段路」的代码隔离到保电内存。

主流程:播放关机提示音

void softoff_app(void)
{
    int msg[2], err;
    void *pfs = 0;
    void *pfile = 0;
    dec_obj *obj = 0;
    decoder_init();

    err = vfs_mount(&pfs, (void *)NULL, (void *)NULL);
    if (err) {
        log_info("fs_mount err : 0x%x\n", err);
        goto __softoff_app_deal;
    }
    err = vfs_openbypath(pfs, &pfile, "dir_notice/power_off.f1a");
    if (err) {
        log_info("fs_openbypath err : 0x%x\n", err);
        vfs_fs_close(&pfs);
        goto __softoff_app_deal;
    }
    obj = decoder_io(pfile, BIT_F1A1, NULL, 0);
    if (NULL == obj) {
        log_info("fs_openbypath err : 0x%x\n", err);
        vfs_file_close(&pfile);
        vfs_fs_close(&pfs);
        goto __softoff_app_deal;
    }

Source: softoff_mode.c

流程分四步:

  1. decoder_init():初始化音频解码器子系统;
  2. vfs_mount(&pfs, NULL, NULL):挂载默认文件系统(NOR Flash 上的 VFS)。失败则打印日志并直接跳转到关机电平;
  3. vfs_openbypath(pfs, &pfile, "dir_notice/power_off.f1a"):按路径打开关机提示音文件。注意路径是相对挂载点的 dir_notice/ 目录,这提示方案中提示音文件统一存放在 dir_notice 目录下;打开失败需要先 vfs_fs_close 释放挂载句柄再跳转;
  4. decoder_io(pfile, BIT_F1A1, NULL, 0):用文件句柄创建解码器对象,格式标志为 BIT_F1A1(F1A1 是杰理私有音频压缩格式,占用空间小、适合提示音)。创建失败时依次关闭文件与文件系统,再跳转。

消息循环:播放中喂狗与结束检测

    while (1) {
        err = get_msg(2, &msg[0]);
        bsp_loop();
        if (MSG_NO_ERROR != err) {
            msg[0] = NO_MSG;
            log_info("get msg err 0x%x\n", err);
        }

        switch (msg[0]) {
        case MSG_F1A1_FILE_END:
        case MSG_MP3_FILE_END:
        case MSG_WAV_FILE_END:
            decoder_stop(obj, NEED_WAIT);
__softoff_app_deal:
            sys_softoff();
            break;
        case MSG_500MS:
            wdt_clear();
            break;
        }
    }

Source: softoff_mode.c

循环体遵循 SDK 统一的「取消息 → 跑 bsp_loop() → 分发」模式:

  • get_msg(2, &msg[0]) 阻塞等待最多 2 个消息字;出错时置为 NO_MSG 并记录日志;
  • bsp_loop() 每轮执行 BSP 级轮询(按键扫描、LED 刷新等底层服务);
  • MSG_F1A1_FILE_END / MSG_MP3_FILE_END / MSG_WAV_FILE_END:解码器上报文件播放结束。之所以兼容三种结束消息,是因为 F1A1 解码器可能以不同消息名上报结束;统一处理确保提示音无论以何种格式解码都能触发关机。先 decoder_stop(obj, NEED_WAIT) 同步停止解码器,再落入 __softoff_app_deal 标签调用 sys_softoff();
  • MSG_500MS:每 500ms 的系统心跳消息。这里只做 wdt_clear() 喂狗——因为提示音播放可能持续数百毫秒到数秒,期间系统不进入 power-down,必须持续喂狗防止复位;同时喂狗也保证关机提示音能完整播完。

值得注意的是 __softoff_app_deal 标签被 goto 与 case 共用:既作为错误处理的跳转目标(挂载/打开/解码失败时),也作为正常播放结束后的收敛点。这种「单一出口」写法保证任何路径最终都只执行一次 sys_softoff(),不会出现多次关机或泄漏句柄的路径。

空闲检测实现详解(app_powerdown_deal)

500ms 计数式空闲判定

#define IDLE_CNT_MAX        2//CNT值不低于2,idle时间=IDLE_CNT_MAX*500ms
static u8 app_idle_cnt;

void app_powerdown_deal(u8 is_busy)
{
    if (is_busy) {
        app_idle_cnt = 0;
        return;
    }

    app_idle_cnt++;

    if (IDLE_CNT_MAX == app_idle_cnt) {
        app_idle_cnt = 0;
        u32 sr = dac_sr_read();
        dac_power_off();
        UI_init();//关闭数码管
        sys_power_down(-2);//进入powerdown

        dac_power_on(sr, 0);
    }
}

Source: idle_check.c

算法与设计意图

  • 忙则清零:is_busy == 1 表示系统正忙(播放、录音、IO 操作中),立刻把计数清零并返回。这是典型的「连续空闲」判定:只要中间出现一次繁忙,就重新计时,避免在播放间隙误入睡眠。
  • 空闲累加:is_busy == 0 时 app_idle_cnt++;计数达到 IDLE_CNT_MAX(=2)时触发休眠。注释明确说明「CNT值不低于2」,原因是单次 500ms 空闲不足以判断系统稳定空闲,至少要连续 1 秒(2×500ms)才可信。
  • 进入 power-down 的时序:
    1. dac_sr_read() 保存当前 DAC 采样率;
    2. dac_power_off() 关闭 DAC——音频通路是主要功耗源;
    3. UI_init():注释为「关闭数码管」,即关闭显示外设(数码管/LED 驱动);
    4. sys_power_down(-2):进入内核级 power-down。参数 -2 表示定时唤醒间隔(-2 秒量级),配合注释「power_down定时唤醒时,睡眠时间不可超过看门狗唤醒时间的一半」——即系统会周期唤醒喂狗,防止看门狗复位;
    5. 唤醒后 dac_power_on(sr, 0) 以保存的采样率恢复 DAC,整个睡眠过程对上层音频状态透明。

该函数的调用点位于系统主循环(bsp_loop() 相关的周期任务),以 500ms 为节拍被周期调用,因此 IDLE_CNT_MAX 直接换算成秒。计数器是 static u8,非重入、无锁,只允许在主循环单线程上下文中调用——这也是它被放在 common/ 目录、以普通函数而非任务形式提供的原因。

待机模式实现详解(idle_app)

void idle_app(void)
{
    u32 err;
    int msg[2];

    log_info("toy idle mode!\n");
    key_table_sel((void *)idle_msg_filter);

    while (1) {
        err = get_msg(2, &msg[0]);
        if (MSG_NO_ERROR != err) {
            msg[0] = NO_MSG;
            log_info("get msg err 0x%x\n", err);
        }
        bsp_loop();

        if (NO_MSG == msg[0]) {
            continue;
        }

        switch (msg[0]) {
        case MSG_CHANGE_WORK_MODE:
            goto __idle_exit;
        default:
            ap_handle_hotkey(msg[0]);
            break;
        }
    }
__idle_exit:
    key_table_sel(NULL);
}

Source: idle_mode.c

待机模式的设计要点:

  • 按键表切换:进入时 key_table_sel((void *)idle_msg_filter) 把按键解析表切换为待机专用的过滤表,退出时 key_table_sel(NULL) 还原。这样同一组物理按键在待机模式下被映射为不同的语义(例如长按开机/唤醒),而不是普通模式下的功能键——这是 SDK 按键框架的典型用法。
  • 空消息直接跳过:NO_MSG 时不走分发直接 continue,保证待机循环最小化 CPU 占用。
  • 热键兜底:除 MSG_CHANGE_WORK_MODE 外的所有消息统一交给 ap_handle_hotkey(msg[0]) 处理——热键机制是全局快捷键,适合在待机态响应开机/唤醒键。
  • 唯一退出条件:MSG_CHANGE_WORK_MODE 触发 goto __idle_exit。该消息由模式管理器在 work_mode 被改写后发出(见 hot_msg.c),例如从待机切回主模式或进入软关机。

模式切换入口(app.c 与 hot_msg.c)

app.c 的主循环按 work_mode 分发到各模式函数;hot_msg.c 的电源键处理把 work_mode 置为 SOFTOFF_MODE 并广播 MSG_CHANGE_WORK_MODE:

case SOFTOFF_MODE:
    softoff_app();
    ...
case IDLE_MODE:
    idle_app();

Source: app.c

log_info("MSG_POWER_OFF\n");
work_mode = SOFTOFF_MODE;
post_msg(1, MSG_CHANGE_WORK_MODE);

Source: hot_msg.c

设计意图:work_mode 是全局变量,而各模式函数都阻塞在自己的消息循环里,因此模式切换必须通过「改标志 + 发消息」两步完成——post_msg(MSG_CHANGE_WORK_MODE) 唤醒当前模式循环,让它检查到 MSG_CHANGE_WORK_MODE 后返回,主循环再按新的 work_mode 进入下一模式。app.c 中 work_mode = IDLE_MODE 的初始化行被注释掉,说明待机并非默认启动模式,需要由上层逻辑显式切入。

Core Flow

软关机时序

sequenceDiagram
    participant Key as 电源键/热键
    participant Hot as hot_msg.c
    participant App as app.c 主循环
    participant Soft as softoff_app()
    participant Dec as 解码器/文件系统
    participant Pwr as power_api

    Key->>Hot: MSG_POWER_OFF
    Hot->>Hot: work_mode = SOFTOFF_MODE
    Hot->>App: post_msg(MSG_CHANGE_WORK_MODE)
    App->>App: 重新分发 → case SOFTOFF_MODE
    App->>Soft: softoff_app()
    Soft->>Dec: decoder_init / vfs_mount
    Dec-->>Soft: 挂载成功
    Soft->>Dec: vfs_openbypath("dir_notice/power_off.f1a")
    Soft->>Dec: decoder_io(pfile, BIT_F1A1)
    loop 播放提示音
        Soft->>Soft: get_msg + bsp_loop
        Dec-->>Soft: MSG_500MS → wdt_clear()
    end
    Dec-->>Soft: MSG_F1A1_FILE_END
    Soft->>Dec: decoder_stop(obj, NEED_WAIT)
    Soft->>Pwr: sys_softoff()
    Note over Pwr: 进入软关机(掉电/深睡)

空闲检测 → power-down 时序

sequenceDiagram
    participant Loop as 主循环/bsp_loop
    participant Idle as app_powerdown_deal()
    participant Dac as DAC
    participant UI as 数码管
    participant Pwr as sys_power_down()

    Loop->>Idle: 周期调用(is_busy=0)
    Idle->>Idle: app_idle_cnt++
    Note over Idle: 连续 2 次空闲(1s)
    Idle->>Dac: dac_sr_read() 保存采样率
    Idle->>Dac: dac_power_off()
    Idle->>UI: UI_init() 关闭数码管
    Idle->>Pwr: sys_power_down(-2)
    Pwr-->>Idle: 定时唤醒(≤看门狗一半)
    Idle->>Dac: dac_power_on(sr, 0) 恢复

两条时序揭示了本页面的核心设计哲学:所有低功耗动作都从消息循环/主循环的单线程上下文发起,不引入独立低功耗任务,从而避免竞态;每个模式循环各自负责「喂狗保活」或「触发断电」的职责划分。

Configuration Options

配置项类型默认值说明
IDLE_CNT_MAX宏(u8 计数上限)2空闲判定阈值。实际空闲时间 = IDLE_CNT_MAX × 500ms。源码注释要求该值不低于 2,避免单次 500ms 空闲即误判
app_idle_cntstatic u80空闲连续计数,仅在 app_powerdown_deal() 内部使用,不可外部修改
sys_power_down(-2) 参数整型(秒)-2power-down 定时唤醒周期。负号表示周期唤醒,睡眠时间不可超过看门狗唤醒时间的一半(见 idle_check.c 注释)
提示音路径字符串常量"dir_notice/power_off.f1a"软关机提示音在 VFS 中的相对路径,需预先烧录到文件系统 dir_notice/ 目录
提示音格式标志宏BIT_F1A1关机提示音使用杰理私有 F1A1 格式解码;结束消息兼容 MSG_F1A1_FILE_END / MSG_MP3_FILE_END / MSG_WAV_FILE_END
段重定向pragma.softoff_mode.*将软关机代码/数据/常量放入独立段,便于链接器放置到保电内存区(见 softoff_mode.c)
work_mode 初始值全局变量非 IDLE(注释掉的默认)主循环 app.c 中 work_mode = IDLE_MODE 被注释,待机模式需显式切入

注:上述宏与常量均为编译期配置,运行时无动态配置入口;如需调整空闲时间或提示音文件,需修改源码后重新编译烧录。

API Reference

void softoff_app(void)

软关机模式入口,由 app.c 在 work_mode == SOFTOFF_MODE 时调用。

  • 流程:初始化解码器 → 挂载 VFS → 打开并解码 dir_notice/power_off.f1a → 消息循环等待播放结束 → sys_softoff()。
  • 参数:无。
  • 返回:无(正常路径永不返回,直接软关机;异常路径也收敛到 sys_softoff())。
  • 消息:消费 MSG_F1A1_FILE_END、MSG_MP3_FILE_END、MSG_WAV_FILE_END、MSG_500MS;错误时置 NO_MSG 并记录日志。
  • 定义见 softoff_mode.c。

void app_powerdown_deal(u8 is_busy)

空闲检测与 power-down 触发函数,由系统主循环以 500ms 节拍周期调用。

  • 参数:is_busy(u8)— 1 表示系统繁忙(清零计数并返回),0 表示空闲(计数累加)。
  • 返回:无。
  • 副作用:连续空闲达 IDLE_CNT_MAX 次后依次执行 dac_sr_read() → dac_power_off() → UI_init() → sys_power_down(-2) → dac_power_on(sr, 0)。
  • 约束:非重入,必须在主循环单线程上下文调用;power-down 唤醒周期不得超过看门狗唤醒时间的一半。
  • 定义见 idle_check.c。

void idle_app(void)

待机模式入口,由 app.c 在 work_mode == IDLE_MODE 时调用。

  • 流程:key_table_sel(idle_msg_filter) 切换按键表 → 消息循环 → 退出时 key_table_sel(NULL)。
  • 参数:无。
  • 返回:仅在收到 MSG_CHANGE_WORK_MODE 时返回(经 goto __idle_exit)。
  • 消息:MSG_CHANGE_WORK_MODE(退出)、其余消息全部转发 ap_handle_hotkey()。
  • 定义见 idle_mode.c。

相关底层 API(由本页面调用,实现在电源驱动层)

API作用调用方
sys_softoff()系统软关机(断电/深睡)softoff_app()
sys_power_down(interval)进入 power-down,支持定时唤醒app_powerdown_deal()
dac_sr_read() / dac_power_off() / dac_power_on(sr, 0)DAC 采样率保存、关闭、恢复app_powerdown_deal()
decoder_init() / decoder_io() / decoder_stop()解码器初始化、创建、停止softoff_app()
vfs_mount() / vfs_openbypath() / vfs_fs_close() / vfs_file_close()文件系统挂载与文件操作softoff_app()

Failure Modes、Edge Cases 与并发

软关机路径的失败降级

softoff_app() 对三个失败点做了显式降级处理,全部收敛到 __softoff_app_deal 标签:

  1. vfs_mount 失败(返回非 0):直接 goto,跳过提示音播放立即软关机。此时文件系统不可用,提示音必然无法播放,继续等待只会卡死;
  2. vfs_openbypath 失败(提示音文件缺失/损坏):先 vfs_fs_close(&pfs) 释放挂载,再跳转。若不释放句柄会造成资源泄漏;
  3. decoder_io 返回 NULL(解码器创建失败):依次 vfs_file_close 与 vfs_fs_close,再跳转。

每步失败都打印 log_info([low_pwr] 标签),便于现场定位是文件缺失还是解码器问题。注意第 3 步的错误日志误用了 err(文件打开的错误码)而非 decoder_io 的返回值,这是源码中的一处瑕疵——实际错误码不可信,应只把日志当作「已失败」标志。

消息循环的异常输入

get_msg 返回非 MSG_NO_ERROR 时,msg[0] 被强制置为 NO_MSG 并记录日志,防止未初始化栈数据进入 switch 造成未定义行为。switch 对未知消息默认不处理(无 default 分支),静默丢弃——软关机循环只关心播放结束与心跳两类消息。

看门狗与定时唤醒的矛盾约束

软关机播放期间,系统停留在正常模式而非 power-down,因此必须靠 MSG_500MS → wdt_clear() 持续喂狗;而空闲检测进入的 sys_power_down(-2) 则是周期唤醒喂狗。两类路径的公共约束是:任何睡眠周期都不得超过看门狗唤醒时间的一半(源码注释明确),否则系统会被看门狗复位而非正常待机。这是本页面最关键的运行时边界条件。

并发与重入

三个函数均为单线程消息循环上下文设计,无锁、无异步回调:

  • app_idle_cnt 是 static 变量,仅 app_powerdown_deal() 访问,主循环周期调用天然串行化;
  • softoff_app() / idle_app() 内部 while(1) 独占执行,work_mode 切换通过 post_msg(MSG_CHANGE_WORK_MODE) 跨循环通信,不存在数据竞争;
  • 唯一需要注意的共享资源是按键表 key_table_sel()——待机模式进入/退出时成对切换,若中途异常退出会破坏按键表状态,因此 idle_app() 只在 MSG_CHANGE_WORK_MODE 一个出口还原。

性能与运维注意事项

  • 空闲检测开销:app_powerdown_deal 每次调用仅做一次比较与自增(繁忙路径直接返回),CPU 开销可忽略;它依赖主循环的 500ms 节拍,节拍精度即空闲判定精度。
  • 软关机延迟:提示音完整播完才断电,播放时长由 power_off.f1a 内容决定;若产品要求快速断电,可缩短提示音或直接去掉 decoder_io 步骤(但会丢失用户反馈)。
  • 功耗路径:空闲 → 关闭 DAC → 关闭数码管 → power-down,是典型的多级降功耗顺序;DAC 与显示是 MCU 音频方案的两大功耗源,先关它们再睡 CPU。
  • 可配置性:空闲时长(IDLE_CNT_MAX)、提示音文件(路径字符串)、唤醒周期(sys_power_down 参数)均为编译期常量,现场不可热调;量产前应通过测试确定 IDLE_CNT_MAX 与看门狗周期的匹配。
  • 日志标签:软关机模块 [low_pwr]、空闲检测 [sys_idle]、待机模式 [normal],抓取低功耗问题时可按标签过滤日志。

Extension Points

  • 更换/新增提示音:修改 vfs_openbypath 的路径字符串,或扩展 switch 中的结束消息枚举;解码格式由 BIT_F1A1 标志控制,可改为 BIT_MP3/BIT_WAV 等。
  • 调整空闲判定粒度:IDLE_CNT_MAX 与调用节拍(500ms)共同决定判定时长;若要支持「忙碌判定只看音频」,可在调用点传入更精确的 is_busy 标志。
  • 待机按键语义扩展:idle_msg_filter 是待机模式的按键过滤表,可在按键表中新增映射,使待机态响应更多按键(例如短按显示时间、长按开机)。
  • 新增低功耗档位:app_powerdown_deal 的睡眠分支目前固定 sys_power_down(-2);如需多级睡眠(如先浅睡后深睡),可在此函数内按计数分档。

Related Links

  • 应用主循环与工作模式(app.c):work_mode 分发与各模式入口。
  • 热键与电源键处理(hot_msg.c):MSG_POWER_OFF → SOFTOFF_MODE 的切换源头。
  • 软关机模块源码(softoff_mode.c):段布局、提示音播放与 sys_softoff()。
  • 空闲检测源码(idle_check.c):500ms 计数与 power-down 触发。
  • 待机模式源码(idle_mode.c):待机消息循环与按键表切换。
  • 底层电源 API 实现位于 sdk/include_lib/cpu/asm/power/power_api.h(sys_softoff / sys_power_down 的硬件实现,超出本页面范围,另见电源管理相关目录)。
Prev
USB 从设备应用
Next
公共 UI 与 LED 显示