LINEIN 与扩音应用
本文档介绍 AD23N 平台的 LINEIN(AUX 线路输入)与扩音(Loudspeaker)两种应用模式的完整实现:从工作模式分发入口 app.c,到 linein_app() / loudspeaker_app() 的应用层消息循环,再到 audio_adc / dac 音频链路与 BSP 层 ADC 输入配置,覆盖启动、运行、退出全生命周期。
Purpose and Scope
本页覆盖以下内容:
sdk/app/src/mbox_flash/linein/linein_mode.c—— LINEIN(AUX)模式应用主逻辑sdk/app/src/mbox_flash/loudspeaker/loudspk_mode.c—— 扩音模式应用主逻辑sdk/app/src/mbox_flash/app.c—— 工作模式分发(AUX_MODE→linein_app())sdk/app/bsp/cpu/sh59/audio_adc_cpu.c—— BSP 层ADC_LINE_IN输入通道配置
本页不重复覆盖以下内容(属于其他目录页):
- 各模式的按键消息过滤实现(
linein_mode_key.c/loudspk_mode_key.c中的linein_key_msg_filter/loudspk_key_msg_filter由本页模式调用,但按键映射细节见对应文档) - MIDI 解码、MIDI 键盘、录音(record)等其他工作模式
- 公共基础设施(消息系统
msg.h、环形缓冲circular_buf.h、VM 存储vm_api.h)
Overview
AD23N 是一颗面向音频应用的 MCU,其 SDK(sdk/app/src/mbox_flash/)采用**工作模式(work mode)**架构:同一份固件内包含 idle、linein、loudspeaker、midi_dec、record 等多个互斥的应用模式,由 app.c 根据当前 work_mode 分发到对应的 *_app() 入口函数。本文档聚焦其中两种密切相关的声音放大场景:
LINEIN(AUX)模式:通过 3.5mm AUX 接口接入外部音源(手机、电脑等),模拟信号经 ADC(
ADC_LINE_IN通道)采样为数字流,写入环形缓冲区(cbuf_digital_linein),再经 DAC 通道(digital_linein_sound)播放输出到功放/喇叭。这是一个典型的"输入直通输出"(passthrough)实时音频链路。扩音(Loudspeaker)模式:直接通过麦克风拾音并实时放大输出,调用
audio_adc_speaker_start()启动专用采样通路,同样经 DAC 输出。常见于演讲、喊话器等"即插即用扩音"场景。
两个模式共享同一套运行骨架:持久化模式 → 挂接按键过滤表 → 初始化音频链路 → 消息循环 → 退出清理。理解其中一个模式的代码,即可触类旁通另一个模式。
Architecture
flowchart TD
subgraph sg_App["应用层 (mbox_flash)"]
App["app.c<br/>模式分发"]
LineinApp["linein_mode.c<br/>linein_app()"]
LoudspkApp["loudspk_mode.c<br/>loudspeaker_app()"]
LineinKey["linein_mode_key.c<br/>linein_key_msg_filter"]
LoudspkKey["loudspk_mode_key.c<br/>loudspk_key_msg_filter"]
end
subgraph sg_Service["音频服务层"]
AdcApi["audio_adc<br/>ADC 采样通道"]
DacApi["dac_api<br/>DAC 播放通道"]
Cbuf["circular_buf<br/>环形缓冲区"]
SoundEffect["sound_effect_api<br/>音效"]
end
subgraph sg_Bsp["BSP 层 (sh59)"]
AdcCpu["audio_adc_cpu.c<br/>ADC_LINE_IN / 差分输入"]
HwAdc["硬件 AUADC"]
HwDac["硬件 DAC + PA 功放"]
end
App -->|"AUX_MODE"| LineinApp
App -->|"LOUDSPEAKER"| LoudspkApp
LineinApp --> LineinKey
LoudspkApp --> LoudspkKey
LineinApp --> AdcApi
LineinApp --> DacApi
LineinApp --> Cbuf
LoudspkApp --> AdcApi
LoudspkApp --> DacApi
AdcApi --> AdcCpu
AdcCpu --> HwAdc
DacApi --> HwDac
架构说明:
app.c是唯一的模式分发入口:#if LINEIN_MODE_EN编译开关下,case AUX_MODE: linein_app();将控制权交给 LINEIN 模式(见 app.c)。- 应用层
linein_app()/loudspeaker_app()不直接操作寄存器,而是通过audio_adc/dac_api服务层注册通道对象(sound_out_obj),由服务层统一管理采样率、通道使能与中断回调。 - BSP 层
audio_adc_cpu.c根据通道类型(ADC_LINE_IN等)配置 AUADC 的差分/单端输入模式,屏蔽芯片寄存器细节。 - 按键过滤表(
key_table_sel)在模式进入时挂接、退出时置 NULL,保证按键消息只在当前模式下生效。
主要实现详解
LINEIN 模式启动流程(linein_app)
linein_app() 是 LINEIN 模式的入口,位于 linein_mode.c。其启动序列按顺序完成四件事:
1. 持久化当前工作模式
vm_write(VM_INDEX_SYSMODE, &work_mode, sizeof(work_mode));
key_table_sel(linein_key_msg_filter);
通过 vm_write 将当前 work_mode 写入 VM(虚拟存储)索引 VM_INDEX_SYSMODE,使掉电重启后能恢复模式;随后 key_table_sel(linein_key_msg_filter) 挂接 LINEIN 模式专属按键过滤表,模式运行期间只有该表内的按键消息会被处理。
2. 初始化数字音频链路
memset(&digital_linein_sound, 0, sizeof(digital_linein_sound));
cbuf_init(&cbuf_digital_linein, &obuf_digital_linein[0], sizeof(obuf_digital_linein));
digital_linein_sound.p_obuf = &cbuf_digital_linein;
digital_linein_sound 是 sound_out_obj 类型的通道对象,p_obuf 指向环形缓冲区 cbuf_digital_linein,其存储空间来自静态数组 obuf_digital_linein[1024 / 2]。设计意图:ADC 中断将采样数据写入环形缓冲,DAC 播放端从同一缓冲取数,形成"生产者-消费者"解耦,避免 ADC 采样率抖动直接冲击 DAC 输出。
3. 配置采样率与输入通道
u32 sr = dac_sr_read();
dac_sr_api(LINEIN_SR);
audio_adc_init_api(LINEIN_SR, ADC_LINE_IN, audio_adc_aux_input_port);
regist_dac_channel(&digital_linein_sound, NULL);
regist_audio_adc_channel(&digital_linein_sound, NULL);
audio_adc_enable(1);
digital_linein_sound.enable |= B_DEC_RUN_EN | B_DEC_FIRST;
先用 dac_sr_read() 保存原 DAC 采样率(退出时恢复),再统一设置 LINEIN_SR(即 SR_DEFAULT)。audio_adc_init_api 以 ADC_LINE_IN 通道、audio_adc_aux_input_port 输入端口初始化 ADC;随后把同一个 digital_linein_sound 对象同时注册为 DAC 播放通道和 ADC 采集通道,并置位 B_DEC_RUN_EN | B_DEC_FIRST 使能解码运行。这是典型的通道复用设计:一个 sound_out_obj 同时绑定 ADC 与 DAC 两端,数据在内部完成直通。
4. 进入消息循环
int msg[2];
u32 err;
u8 mute = 0;
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]) { ... }
}
get_msg(2, &msg[0]) 阻塞等待消息队列,bsp_loop() 维护系统心跳/底层轮询。取消息失败时置 NO_MSG 走默认分支,保证循环不因单次错误退出。
消息循环与静音切换
LINEIN 模式的消息处理(linein_mode.c)与扩音模式完全一致,处理三类消息:
| 消息 | 行为 | 说明 |
|---|---|---|
MSG_PP | 切换 dac_mute(1) / dac_mute(0) | 播放/暂停键:静音状态用局部变量 mute 记录并翻转 |
MSG_CHANGE_WORK_MODE | goto __linein_app_exit | 模式切换请求:跳出循环进入清理路径 |
MSG_500MS / 默认 | ap_handle_hotkey(msg[0]) | 500ms 定时消息与其余消息交给热键处理器 |
设计意图:MSG_PP 的静音仅作用于 DAC(dac_mute),不停止 ADC 采样——这样取消静音时音频链路仍在运行,不会出现恢复瞬间的爆音或延迟;pa_mute(功放静音)被注释保留,便于后续硬件上需要同时关闭 PA 时恢复使用。
扩音模式(loudspeaker_app)
扩音模式位于 loudspk_mode.c,骨架与 LINEIN 模式一致,差异在音频链路初始化:
vm_write(VM_INDEX_SYSMODE, &work_mode, sizeof(work_mode));
key_table_sel(loudspk_key_msg_filter);
audio_adc_speaker_start();
扩音模式不显式调用 audio_adc_init_api / regist_*_channel,而是通过 audio_adc_speaker_start() 一键启动麦克风扩音采样通路(该 API 内部封装了通道注册与使能,采样率相关配置被注释保留,采用默认值)。退出时调用 audio_adc_speaker_reless() 释放通路,同样用 key_table_sel(NULL) 卸载按键过滤表。
退出与资源清理
两个模式共用同一套退出纪律(__linein_app_exit / __loudspk_app_exit):
- 若处于静音状态先
dac_mute(0)恢复(避免切换模式后 DAC 仍静音); - 清除
B_DEC_RUN_EN停止解码运行、audio_adc_disable()/audio_adc_speaker_reless()停止采样; unregist_audio_adc_channel/unregist_dac_channel注销通道,audio_adc_off_api()关闭 ADC 电源;dac_sr_api(sr)恢复进入前保存的 DAC 采样率;key_table_sel(NULL)卸载按键过滤表,控制权交还app.c的模式分发循环。
这种"进入时保存现场、退出时恢复现场"的设计使各模式之间可以安全地反复切换,是工作模式架构的核心保证。
Core Flow
sequenceDiagram
participant App as app.c 分发
participant Linein as linein_app()
participant VM as vm_api
participant Key as key_table_sel
participant Adc as audio_adc
participant Cbuf as circular_buf
participant Dac as dac_api
participant Msg as 消息队列
App->>Linein: AUX_MODE 进入
Linein->>VM: vm_write(VM_INDEX_SYSMODE, work_mode)
Linein->>Key: key_table_sel(linein_key_msg_filter)
Linein->>Adc: dac_sr_api(LINEIN_SR)
Linein->>Adc: audio_adc_init_api(SR, ADC_LINE_IN, aux_port)
Adc->>Cbuf: ADC 中断写入采样数据
Linein->>Dac: regist_dac_channel(digital_linein_sound)
Dac->>Cbuf: DAC 从缓冲读取数据播放
Note over Linein,Msg: 进入消息循环
Msg-->>Linein: MSG_PP
Linein->>Dac: dac_mute 切换静音
Msg-->>Linein: MSG_CHANGE_WORK_MODE
Linein->>Adc: audio_adc_disable / unregist 通道
Linein->>Dac: 恢复原 dac_sr
Linein->>Key: key_table_sel(NULL)
Linein-->>App: 返回,切换下一模式
数据流上,ADC 中断 → 环形缓冲 → DAC 播放构成实时直通链路;控制流上,消息循环驱动静音与模式切换。两者并行:音频数据不经消息队列,避免阻塞造成声音断续。
使用示例
以下代码片段均从仓库实际源码提取。
示例 1:LINEIN 模式完整入口函数
linein_app() 展示了"持久化模式 → 挂按键表 → 建缓冲 → 配采样率 → 注册通道 → 消息循环 → 退出清理"的完整生命周期:
void linein_app(void)
{
vm_write(VM_INDEX_SYSMODE, &work_mode, sizeof(work_mode));
key_table_sel(linein_key_msg_filter);
log_info("digital linein init!\n");
memset(&digital_linein_sound, 0, sizeof(digital_linein_sound));
cbuf_init(&cbuf_digital_linein, &obuf_digital_linein[0], sizeof(obuf_digital_linein));
digital_linein_sound.p_obuf = &cbuf_digital_linein;
u32 sr = dac_sr_read();
dac_sr_api(LINEIN_SR);
audio_adc_init_api(LINEIN_SR, ADC_LINE_IN, audio_adc_aux_input_port);
regist_dac_channel(&digital_linein_sound, NULL);
regist_audio_adc_channel(&digital_linein_sound, NULL);
audio_adc_enable(1);
digital_linein_sound.enable |= B_DEC_RUN_EN | B_DEC_FIRST;
...
}
Source: linein_mode.c
示例 2:LINEIN 模式消息循环与静音切换
MSG_PP 通过局部变量 mute 记录静音状态并翻转 dac_mute;MSG_CHANGE_WORK_MODE 直接跳出循环:
switch (msg[0]) {
case MSG_PP:
if (mute == 0) {
log_info("MUTE\n");
mute = 1;
dac_mute(1);
/* pa_mute(1); */
} else {
log_info("UNMUTE\n");
mute = 0;
dac_mute(0);
/* pa_mute(0); */
}
break;
case MSG_CHANGE_WORK_MODE:
goto __linein_app_exit;
case MSG_500MS:
default:
ap_handle_hotkey(msg[0]);
break;
}
Source: linein_mode.c
示例 3:扩音模式入口函数
扩音模式通过 audio_adc_speaker_start() 一键启动采样通路,退出时用 audio_adc_speaker_reless() 释放:
void loudspeaker_app(void)
{
vm_write(VM_INDEX_SYSMODE, &work_mode, sizeof(work_mode));
key_table_sel(loudspk_key_msg_filter);
/* u32 dac_sr = dac_sr_read(); */
/* dac_sr_api(LOUDSPK_SR); */
audio_adc_speaker_start();
int msg[2];
u32 err;
u8 mute = 0;
while (1) {
err = get_msg(2, &msg[0]);
...
switch (msg[0]) {
case MSG_PP:
/* 与 LINEIN 相同的静音切换逻辑 */
break;
case MSG_CHANGE_WORK_MODE:
goto __loudspk_app_exit;
default:
ap_handle_hotkey(msg[0]);
break;
}
}
__loudspk_app_exit:
if (0 != mute) {
dac_mute(0);
}
audio_adc_speaker_reless();
key_table_sel(NULL);
}
Source: loudspk_mode.c
示例 4:工作模式分发入口(app.c)
LINEIN 模式由 app.c 中 AUX_MODE 分支触发,编译期由 LINEIN_MODE_EN 宏控制:
#if LINEIN_MODE_EN
case AUX_MODE:
linein_app();
break;
#endif
Source: app.c
示例 5:BSP 层 ADC_LINE_IN 输入配置
BSP 层在 audio_adc_cpu.c 中按通道类型选择 ADC 模式,ADC_LINE_IN 与 MIC 共用差分输入配置路径,auadc_diff_in 决定是否启用差分采样:
goto __ADC_MIC_AND_LINEIN;
case ADC_LINE_IN:
...
auadc_param.auadc_diff_in = audio_adc_diff_aux_mode;
__ADC_MIC_AND_LINEIN:
if (auadc_param.auadc_diff_in) {
Source: audio_adc_cpu.c
配置选项
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
LINEIN_MODE_EN | 编译宏 | 由工程配置 | 编译开关,控制 app.c 中 AUX_MODE 分支与 linein_mode.c 主体代码是否编译 |
LOUDSPEAKER_EN | 编译宏 | 由工程配置 | 编译开关,控制 loudspk_mode.c 主体代码是否编译 |
LINEIN_SR | 采样率 | SR_DEFAULT | LINEIN 模式 DAC/ADC 统一采样率 |
obuf_digital_linein[1024 / 2] | u16 数组 | 1024 字节 | 环形缓冲区存储空间,决定 ADC→DAC 直通链路的延迟与抗抖动能力 |
audio_adc_aux_input_port | 输入端口 | 硬件定义 | AUX 输入的引脚/通道选择,传给 audio_adc_init_api |
audio_adc_diff_aux_mode | bool | 硬件定义 | AUADC 差分输入使能配置,决定 ADC_LINE_IN 采样方式 |
VM_INDEX_SYSMODE | VM 索引 | 系统定义 | 工作模式持久化的 VM 键 |
API 参考
应用入口
void linein_app(void)
LINEIN(AUX)模式入口,由 app.c 在 AUX_MODE 下调用。负责初始化 ADC→缓冲→DAC 直通链路并进入消息循环,直到收到 MSG_CHANGE_WORK_MODE 后清理资源返回。
- 调用方:
app.c模式分发(#if LINEIN_MODE_EN编译开关内) - 阻塞行为:函数内部
while(1)循环,仅在模式切换消息到来时返回 - 关键副作用:写入
VM_INDEX_SYSMODE持久化模式、挂接linein_key_msg_filter按键表、修改 DAC 采样率(退出时恢复)
void loudspeaker_app(void)
扩音模式入口,负责启动麦克风采样通路并进入消息循环,退出时释放通路。
- 调用方:
app.c模式分发 - 关键副作用:写入
VM_INDEX_SYSMODE、挂接loudspk_key_msg_filter按键表、启动/释放扩音采样通路
模式内使用的服务 API(签名见对应头文件)
| API | 作用 | 使用模式 |
|---|---|---|
vm_write(VM_INDEX_SYSMODE, &work_mode, sizeof(work_mode)) | 持久化当前工作模式到 VM | 两者 |
key_table_sel(filter) | 挂接/卸载按键过滤表(传 NULL 卸载) | 两者 |
cbuf_init(cbuf, buf, size) | 初始化环形缓冲区 | LINEIN |
dac_sr_read() / dac_sr_api(sr) | 读取/设置 DAC 采样率 | LINEIN |
audio_adc_init_api(sr, channel, port) | 以指定采样率/通道/端口初始化 ADC | LINEIN(ADC_LINE_IN + audio_adc_aux_input_port) |
regist_dac_channel(obj, NULL) / regist_audio_adc_channel(obj, NULL) | 将 sound_out_obj 注册为 DAC 播放 / ADC 采集通道 | LINEIN |
audio_adc_enable(1) / audio_adc_disable() | 使能/禁用 ADC 采样 | LINEIN |
audio_adc_off_api() | 关闭 ADC 电源 | LINEIN |
audio_adc_speaker_start() / audio_adc_speaker_reless() | 启动/释放扩音采样通路 | 扩音 |
dac_mute(1/0) | DAC 静音/取消静音 | 两者 |
unregist_audio_adc_channel(obj) / unregist_dac_channel(obj) | 注销通道 | LINEIN |
get_msg(2, &msg[0]) | 从消息队列取消息,返回 MSG_NO_ERROR 或错误码 | 两者 |
ap_handle_hotkey(msg[0]) | 热键消息统一处理(默认分支兜底) | 两者 |
消息类型
| 消息 | 值来源 | 处理 |
|---|---|---|
MSG_PP | 播放/暂停按键(经 *_key_msg_filter 映射) | 翻转 DAC 静音 |
MSG_CHANGE_WORK_MODE | 模式切换请求 | 跳出消息循环,进入退出清理 |
MSG_500MS | 系统 500ms 定时器 | 默认分支,交 ap_handle_hotkey |
NO_MSG | get_msg 返回错误时兜底 | 默认分支,循环继续 |
故障模式、边界情况与并发
消息获取错误
get_msg(2, &msg[0]) 返回非 MSG_NO_ERROR 时,两个模式都将 msg[0] 置为 NO_MSG 并打印错误日志,随后继续循环而不是退出。设计意图:消息系统的瞬时错误(如队列竞争)不应导致整个音频模式崩溃,静默降级为"本轮无消息"。
静音状态下的模式切换
mute 是循环内的局部变量。若用户先按 MSG_PP 静音、再触发 MSG_CHANGE_WORK_MODE,退出清理路径会检查 if (0 != mute) dac_mute(0),确保把 DAC 恢复到非静音状态后才交还控制权——否则下一个模式会带着静音启动,造成"无声"故障。
采样率现场恢复
LINEIN 模式进入时用 dac_sr_read() 保存原采样率并切换到 LINEIN_SR;退出时 dac_sr_api(sr) 恢复。若省略该步骤,模式切换后 DAC 会以错误的采样率运行,导致其他模式播放变调或无声。
通道注销顺序
LINEIN 退出时按"停止使能 → 禁用 ADC → 注销 ADC 通道 → 注销 DAC 通道 → 关闭 ADC 电源"的顺序清理。若先关电源再注销通道,ADC 中断可能仍在访问已释放的通道对象,产生空指针/野指针风险。
并发与实时性
- 音频数据路径(ADC 中断 → 环形缓冲 → DAC 播放)不经过消息队列,与消息循环并行运行,这是保证实时音频不卡顿的关键设计。
- 静音操作直接调用
dac_mute,只控制 DAC 输出,不打断 ADC 采样链路,避免恢复时的爆音。 - 消息循环是单线程模型,
MSG_PP的mute状态读写不存在跨线程竞争;共享状态(如环形缓冲读写指针)由circular_buf内部机制保证。
性能与运维注意事项
内存段放置
两个模式文件开头都使用段重定向 pragma,将代码与数据放入专属段:
#pragma bss_seg(".linein_mode.data.bss")
#pragma data_seg(".linein_mode.data")
#pragma const_seg(".linein_mode.text.const")
#pragma code_seg(".linein_mode.text")
#pragma str_literal_override(".linein_mode.text.const")
Source: linein_mode.c(loudspk_mode.c 同理,段名为 .loudspk_mode.*)
设计意图:AD23N 为 MCU 平台,RAM/Flash 资源有限。把互斥模式的代码/数据放入独立段后,链接脚本可以按需覆盖或分页加载,未激活模式的代码不常驻 RAM。这也是"工作模式"架构在资源受限平台上的核心收益之一。
缓冲区与延迟
obuf_digital_linein 仅 1024 字节(512 个 u16 采样),在 SR_DEFAULT 采样率下对应毫秒级的缓冲深度。这保证了 ADC→DAC 直通的低延迟,但缓冲过小也意味着对系统中断延迟更敏感——若 ADC 中断被长时间抢占,缓冲可能下溢/上溢,因此 LINEIN 模式运行期间应避免耗时操作(如 Flash 擦写)占用中断。
调试手段
- 模式日志 TAG:LINEIN 为
[aux](LOG_TAG),扩音为[loud_spk],日志级别NORM。排查模式切换问题时,可按 TAG 过滤日志。 - 启动日志
log_info("digital linein init!\n")标识 LINEIN 链路初始化完成。 - 扩音模式中
get_energe_db_api()(能量/分贝读取)被注释保留,可作为后续扩展音量电平显示/自动增益的挂载点。
扩展点
新增按键行为
在 MSG_500MS 之外的 case 中追加消息分支,或在默认分支 ap_handle_hotkey(msg[0]) 的热键表中注册新键值。注意消息枚举值需与 *_key_msg_filter 按键映射表保持一致。
PA 功放联动
pa_mute() 调用在两模式中均被注释。若硬件功放使能脚需跟随静音状态,取消注释并确保 pa_mute.h 已包含(LINEIN 已 include;扩音模式注释掉了 include,需自行补上)。
扩音电平检测
loudspk_mode.c 中 get_energe_db_api(void) 前置声明与 MSG_500MS 分支内的调用点均已预留,可用于实现实时电平指示(如 LED 电平表)或自动增益控制。
新增工作模式
仿照 linein/loudspeaker 目录结构:新建 xxx_mode.c/.h/_key.c,在 app.c 的模式分发 switch 中增加 case(受编译宏控制),进入时挂接 xxx_key_msg_filter、退出时 key_table_sel(NULL),即可无缝接入现有架构。
相关链接
- app.c 模式分发 ——
AUX_MODE→linein_app()的调用点 - linein_mode.c —— LINEIN 模式完整实现
- loudspk_mode.c —— 扩音模式完整实现
- audio_adc_cpu.c —— BSP 层
ADC_LINE_IN输入配置 - 同目录下的其他工作模式(idle、midi_dec、midi_keyboard、record)各有独立页面,按键映射细节见各模式
*_mode_key.c对应文档