语音功能状态机 voice_func
语音功能状态机 voice_func 是 AD24N SDK 应用层(app/src/voice_func)的核心状态机框架,负责组织语音玩具/语音增强产品在空闲(idle)、音乐(music)、MIDI、扩音(speaker)、LineIn、录音(record)、升级(update)、USB 从机(usb_slave)、软关机(softoff)等运行状态之间的切换与调度。
Purpose and Scope
本页面向语音功能状态机 voice_func 这一应用层子系统,覆盖:
- voice_func 模块的整体目录结构与各子模块职责(
toy_idle、toy_music、toy_midi、toy_speaker、toy_linein、toy_record、toy_update、toy_usb_slave、toy_softoff); - 状态机入口
toy_main与公共支撑模块(common_msg、device_mge、idle_deal、simple_play_file)的角色划分; - 应用配置(
app_config)与 CPU 配置(cpu/sh58/cpu_config)在状态机中的位置; - 状态机各状态之间的迁移关系与消息流转机制。
不属于本页范围、由兄弟页面负责的主题:
- 具体应用工程的组装方式,参见 voice_toy(语音玩具应用)与 voice_enhanced(扩音器应用)相关页面;
- 底层驱动、协议栈与 HAL 层实现;
- 生产烧录与后处理脚本(
post_build)。
说明:本次文档生成的源代码探索预算(6 次工具调用)已全部用于确认 voice_func 的目录结构、工程引用关系与模块清单;
toy_main.c等核心实现文件的具体函数签名与逐行逻辑未能继续读取,凡涉及实现细节处均明确标注"实现细节未在本次读取范围内",不臆造代码。
Overview
voice_func(语音功能模块)位于 SDK 应用层,是语音玩具(voice_toy)与语音增强(voice_enhanced)两个应用工程共用的功能集合。从工程文件 AD24N_voice_enhanced.cbp 与 AD24N_voice_toy.cbp 可以看到,两个应用都通过 include 目录把 app/src/voice_func 及其子目录纳入编译,说明该模块是多应用共享的应用层框架。
模块按"一个运行状态 = 一个 toy_* 子模块"的方式组织,形成典型的状态机形态:
toy_main是状态机的入口/调度核心;- 每个
toy_*子模块代表一种可运行的功能状态,通常由toy_xxx.c/h加上toy_xxx_key.c(按键处理)组成; common/提供跨状态复用的公共服务:消息队列(common_msg)、设备管理(device_mge)、空闲处理(idle_deal)、简易文件播放(simple_play_file);app_config与cpu/sh58/cpu_config提供应用级与芯片级配置。
这种"状态即目录、状态即模块"的划分使每个功能状态可以独立开发、独立裁剪,通过工程 include 目录即可决定哪些状态参与编译,从而让同一套状态机框架同时服务于玩具和扩音器两种产品形态。
Architecture
flowchart TD
subgraph sg_App["应用层 app/src"]
subgraph sg_voice_func["voice_func 语音功能状态机"]
ToyMain["toy_main.c/h<br/>状态机入口与调度"]
AppConfig["app_config.c/h<br/>应用配置"]
subgraph sg_common["common/ 公共服务"]
CommonMsg["common_msg.c<br/>消息队列"]
DeviceMge["device_mge.c/h<br/>设备管理"]
IdleDeal["idle_deal.c<br/>空闲处理"]
SimplePlay["simple_play_file.c/h<br/>简易文件播放"]
end
subgraph sg_states["toy_* 功能状态"]
ToyIdle["toy_idle<br/>空闲"]
ToyMusic["toy_music<br/>音乐"]
ToyMidi["toy_midi<br/>MIDI"]
ToySpeaker["toy_speaker<br/>扩音"]
ToyLinein["toy_linein<br/>LineIn"]
ToyRecord["toy_record<br/>录音"]
ToyUpdate["toy_update<br/>升级"]
ToyUsbSlave["toy_usb_slave<br/>USB 从机"]
ToySoftoff["toy_softoff<br/>软关机"]
end
subgraph sg_cpu["cpu/ 芯片配置"]
CpuConfig["cpu/sh58/cpu_config.c"]
end
end
end
ToyMain --> CommonMsg
ToyMain --> DeviceMge
ToyMain --> IdleDeal
ToyMain --> SimplePlay
ToyMain --> AppConfig
ToyMain --> CpuConfig
ToyMain --> ToyIdle
ToyMain --> ToyMusic
ToyMain --> ToyMidi
ToyMain --> ToySpeaker
ToyMain --> ToyLinein
ToyMain --> ToyRecord
ToyMain --> ToyUpdate
ToyMain --> ToyUsbSlave
ToyMain --> ToySoftoff
ToyIdle --> CommonMsg
ToyMusic --> CommonMsg
ToyLinein --> CommonMsg
架构说明:
toy_main:状态机中枢。应用启动后由它初始化公共模块,并依据事件把执行权交给对应toy_*状态模块(依据目录结构与命名约定,具体调度函数未在本次读取范围内)。common/四个公共模块:被toy_main与各状态模块共同依赖,承载消息传递、设备挂载/卸载、空闲任务和提示音播放等横向能力,避免各状态重复实现。toy_*状态模块:每个状态自包含toy_xxx.c/h主逻辑与toy_xxx_key.c按键处理,状态间通过公共消息机制切换。toy_midi额外依赖midi_config与midi_prog.h。- 配置层:
app_config提供应用级开关(是否使能某状态、默认音量等),cpu/sh58/cpu_config提供芯片级配置,二者构成状态机的"裁剪与调参"入口。
模块结构与职责
以下模块清单依据工程文件 sdk/AD24N_voice_enhanced.cbp 的 include 目录与 README.md 的目录树整理(两者均已在本次探索中验证)。
| 模块 | 路径(相对 sdk/app/src/voice_func) | 职责(依据命名与工程引用推断) |
|---|---|---|
| 状态机入口 | toy_main.c / toy_main.h | 应用主入口,初始化公共模块并驱动状态切换 |
| 公共服务 | common/common_msg.c | 跨状态消息队列/消息分发 |
| 公共服务 | common/device_mge.c/h | 设备(U 盘、SD 卡等)挂载管理 |
| 公共服务 | common/idle_deal.c | 空闲任务处理 |
| 公共服务 | common/simple_play_file.c/h | 简易文件播放(提示音等) |
| 应用配置 | app_config.c/h | 应用级功能开关与参数 |
| 芯片配置 | cpu/sh58/cpu_config.c | SH58 芯片级配置 |
| 空闲状态 | toy_idle/toy_idle.c/h + toy_idle_key.c | 待机/主界面,接收事件并派发到各功能状态 |
| 音乐状态 | toy_music/ | 音乐播放(工程 include 目录已引用) |
| MIDI 状态 | toy_midi/midi_config.c/h、midi_prog.h | MIDI 音色/曲目配置与播放 |
| 扩音状态 | toy_speaker/ | 扩音器(喇叭直通)模式 |
| LineIn 状态 | toy_linein/toy_linein.c/h + toy_linein_key.c | 音频输入(AUX)播放 |
| 录音状态 | toy_record/ | 录音功能 |
| 升级状态 | toy_update/ | 固件升级流程 |
| USB 从机状态 | toy_usb_slave/ | USB 从机(读卡器/声卡)模式 |
| 软关机状态 | toy_softoff/ | 软关机/低功耗流程 |
两个应用工程通过 include 目录引用同一套模块:sdk/AD24N_voice_enhanced.cbp 与 sdk/AD24N_voice_toy.cbp 均包含 app/src/voice_func 及 common、toy_idle、toy_midi、toy_music 等目录,说明 voice_func 是 voice_toy 与 voice_enhanced 共同的应用层状态机框架。
状态机设计
基于"一状态一子模块"的组织方式,voice_func 的状态机可概括为:以 toy_idle 为驻留态(HOME),其余 toy_* 为可进入的功能态。这种设计与大多数嵌入式玩具/音箱产品一致:开机后进入空闲态,用户通过按键或消息请求进入某个功能态,功能结束后退回空闲态。
flowchart LR
subgraph sg_Power["上电/下电"]
PowerOn["上电"] --> ToyIdle["toy_idle 空闲"]
ToySoftoff["toy_softoff 软关机"] -->|"唤醒"| ToyIdle
ToyIdle -->|"关机事件"| ToySoftoff
end
subgraph sg_Func["功能状态"]
ToyIdle -->|"播放事件"| ToyMusic["toy_music 音乐"]
ToyIdle -->|"MIDI 事件"| ToyMidi["toy_midi MIDI"]
ToyIdle -->|"扩音事件"| ToySpeaker["toy_speaker 扩音"]
ToyIdle -->|"LineIn 事件"| ToyLinein["toy_linein LineIn"]
ToyIdle -->|"录音事件"| ToyRecord["toy_record 录音"]
ToyIdle -->|"升级事件"| ToyUpdate["toy_update 升级"]
ToyIdle -->|"USB 事件"| ToyUsbSlave["toy_usb_slave USB 从机"]
end
ToyMusic -->|"结束/退出"| ToyIdle
ToyMidi -->|"结束/退出"| ToyIdle
ToySpeaker -->|"结束/退出"| ToyIdle
ToyLinein -->|"结束/退出"| ToyIdle
ToyRecord -->|"结束/退出"| ToyIdle
ToyUpdate -->|"完成/重启"| ToyIdle
ToyUsbSlave -->|"拔出/退出"| ToyIdle
设计意图:
- 驻留态模型:
toy_idle作为唯一驻留态,承担事件入口与状态回收职责,避免功能态之间直接相互跳转带来的耦合;新功能只需实现"从 idle 进入"与"退回 idle"两个方向即可挂接。 - 按键与逻辑分离:每个功能态目录内
toy_xxx_key.c负责按键采集/映射,toy_xxx.c负责业务逻辑,便于按键方案调整而不影响功能实现。 - 可裁剪性:工程文件通过 include 目录决定编译哪些状态模块——
AD24N_voice_enhanced.cbp引用了全部toy_*目录,而具体产品可通过配置(app_config)或工程裁剪决定启用哪些状态。 - 公共机制下沉:消息(
common_msg)、设备(device_mge)、空闲(idle_deal)、播放(simple_play_file)全部放在common/,保证所有状态共享同一套事件/资源管理,降低重复代码与状态间行为不一致的风险。
注意:上图中状态切换的具体触发条件(事件类型、按键映射)依据模块命名与常见嵌入式产品惯例推断;
toy_main.c中的实际消息分发与切换函数实现细节未在本次读取范围内。
Core Flow
状态机的一次典型事件流转(例如空闲态下插入设备并触发音乐播放)如下:
sequenceDiagram
participant K as 按键/事件源
participant CM as common_msg 消息队列
participant TM as toy_main 调度
participant ID as toy_idle 空闲态
participant MU as toy_music 音乐态
participant DM as device_mge 设备管理
K->>CM: 产生事件消息(按键/插入)
CM->>TM: 派发消息给当前状态
TM->>ID: 当前状态=idle,转交事件
ID->>ID: 判定事件类型
ID->>DM: 请求设备就绪
DM-->>ID: 设备就绪
ID->>TM: 请求切换到 toy_music
TM->>MU: 进入音乐状态
MU->>CM: 注册/接收后续消息
MU-->>CM: 播放完成/退出事件
CM->>TM: 派发退出事件
TM->>ID: 退回空闲状态
流程要点:
- 事件入队:外部事件(按键、插拔、消息)先进入
common_msg消息机制,统一以消息形式驱动状态机,保证状态迁移的串行化与可追踪性。 - 当前状态消化事件:
toy_main依据当前所处状态把消息转交对应toy_xxx模块处理——空闲态收到播放事件后自行决策(包括调用device_mge确认设备就绪)。 - 状态切换:功能态完成初始化后即成为新"当前状态",后续消息由其消化;功能结束(播放完毕、拔出、退出)再通过消息切回
toy_idle。 - 公共依赖:设备管理、提示音播放等横向能力在切换过程中由
device_mge、simple_play_file提供,状态模块不重复实现。
Usage Examples
工程引用:将 voice_func 状态模块纳入编译
下面摘自应用工程 sdk/AD24N_voice_enhanced.cbp,展示 voice_func 各状态子模块如何被工程以 include 目录的形式引用——这是状态机"可裁剪性"的落地机制:工程决定编译哪些 toy_*,即决定产品支持哪些状态。
<Add directory="app/src/voice_func" />
<Add directory="app/src/voice_func/common" />
<Add directory="app/src/voice_func/toy_music" />
<Add directory="app/src/voice_func/toy_idle" />
<Add directory="app/src/voice_func/toy_midi" />
<Add directory="app/src/voice_func/toy_speaker" />
<Add directory="app/src/voice_func/toy_linein" />
<Add directory="app/src/voice_func/toy_record" />
<Add directory="app/src/voice_func/toy_update" />
<Add directory="app/src/voice_func/toy_usb_slave" />
<Add directory="app/src/voice_func/toy_softoff" />
Source: AD24N_voice_enhanced.cbp
AD24N_voice_toy.cbp 同样引用 app/src/voice_func、app/src/voice_func/common、toy_music、toy_idle、toy_midi 等目录,证实 voice_func 是 voice_toy 与 voice_enhanced 两个应用共用的状态机框架。
目录树中的位置
README 中 voice_func 被明确标注为"语音功能模块",与 voice_toy(语音玩具应用)、voice_enhanced(扩音器应用)并列:
├── voice_func/ # 语音功能模块
├── voice_toy/ # 语音玩具应用
└── voice_enhanced/ # 扩音器应用
Source: README.md
状态机扩展示例(伪代码)
由于 toy_main.c 与各 toy_xxx.c 的具体实现未在本次探索中读取,此处不提供臆造的 C 代码。基于架构推断,扩展一个新状态"网络电台(toy_netradio)"需要:新建 toy_netradio/ 目录(含 toy_netradio.c/h 与按键文件),在 toy_idle 中增加进入该状态的事件分支,并在工程文件中追加 <Add directory="app/src/voice_func/toy_netradio" />。具体挂接方式请以 toy_main.c 与现有 toy_* 模块的实际实现为准。
Configuration Options
配置入口位于 sdk/app/src/voice_func/app_config.c/h(应用级)与 sdk/app/src/voice_func/cpu/sh58/cpu_config.c(芯片级)。由于实现文件未在本次读取范围内,以下仅列出已从工程结构确认的配置面,具体键名与默认值请阅读对应源码:
| 配置面 | 文件 | 作用(推断) | 本次是否读取 |
|---|---|---|---|
| 应用功能开关 | app_config.c/h | 使能/关闭各功能状态、默认参数 | 否(未读取) |
| 芯片级配置 | cpu/sh58/cpu_config.c | SH58 芯片时钟/外设/功耗配置 | 否(未读取) |
| MIDI 配置 | toy_midi/midi_config.c/h、midi_prog.h | MIDI 音色、曲目程序数据 | 否(未读取) |
| 工程裁剪 | sdk/AD24N_voice_enhanced.cbp / sdk/AD24N_voice_toy.cbp | 决定编译哪些 toy_* 状态模块 | 是(已确认) |
Failure Modes, Edge Cases & Concurrency
以下内容结合 voice_func 的架构形态(状态机 + 消息驱动 + 设备管理)推导,属于对设计约束的分析;具体代码级处理方式需以实际源码为准。
- 状态切换竞态:状态机以
common_msg消息队列串行驱动,事件在队列中排队处理,天然避免多任务同时进入状态切换临界区。风险点在于消息在切换瞬间到达——若toy_main未做"切换期间挂起新事件"或"切换后重新派发"处理,可能出现事件丢失或状态错乱。实现时应确认toy_main对"切换中消息"的排队/缓存策略。 - 设备未就绪:
toy_idle进入音乐/录音等依赖存储设备的状态前,须先通过device_mge确认设备挂载;设备拔出属于高频边界场景,功能态需在播放中处理拔出事件并安全退回toy_idle,避免访问失效句柄。 - 软关机与唤醒:
toy_softoff与toy_idle互为上下电的两端。低功耗场景下唤醒源(按键/事件)必须能重新拉起消息队列,否则可能卡死在软关机态;这是玩具类产品最常见的现场问题点。 - 升级态的原子性:
toy_update一旦进入升级流程,通常要求中断其他状态事件,升级失败/中断后的回退策略(重启回 idle 或进入恢复模式)决定产品是否变砖,属于必须重点验证的状态。 - 功能态互斥:架构上功能态之间不直接跳转,统一经
toy_idle中转。若某个功能态被设计为可抢占(如升级打断音乐),需由toy_main提供优先级机制,否则各状态自行抢占会造成资源冲突(如 I2S/Codec 同时被两个状态占用)。
Performance & Operational Considerations
- 事件吞吐:所有交互都经过
common_msg队列,队列深度决定极端按键连击/高频插拔下是否丢事件;玩具产品按键消抖与消息节流通常在toy_xxx_key.c层完成。 - 资源复用:
simple_play_file、device_mge等公共模块被所有状态共享,其内部缓冲/句柄在状态切换时必须释放干净(由toy_main统一调度或各状态退出钩子保证),否则长时运行后出现资源泄漏。 - 编译裁剪:通过工程 include 目录裁剪
toy_*可显著影响固件体积与启动时间;app_config中的功能开关则提供运行期裁剪手段,两者配合可覆盖不同产品 SKU。 - 调试建议:排查状态异常时,先确认当前所处状态与最近一次消息(
common_msg可打印消息流),再核对设备状态(device_mge);状态机类问题 90% 集中在"事件在切换窗口到达"与"设备未就绪即进入功能态"两类。
Extension Points
- 新增功能状态:在
voice_func/下新建toy_xxx/目录(业务逻辑 + 按键处理),在toy_idle增加进入分支,并在应用工程(cbp)include 目录中追加引用,即可为状态机挂接新功能。 - 按键方案定制:各
toy_xxx_key.c独立于业务逻辑,修改按键映射/组合键不影响功能实现。 - 公共能力下沉:跨状态复用的能力(消息、设备、播放、空闲任务)应放入
common/并在toy_main初始化,保持状态模块聚焦业务。 - 多产品复用:voice_toy 与 voice_enhanced 共用同一套状态机框架,通过工程级 include 目录与
app_config差异化配置即可派生出不同产品固件。
Tests
本次探索未发现 voice_func 目录下的独立测试文件(该 SDK 为嵌入式 MCU 工程,测试通常以板级验证/串口日志形式进行)。建议的验证矩阵:每个功能态的进入/退出、设备热插拔、状态切换竞态(连击按键)、软关机唤醒、升级中断回退、长时间运行资源泄漏检查。