1. 项目背景与核心需求拆解全志T527这颗芯片在工业控制和车载中控领域出货量不小八核A55架构加上独立的RISC-V协处理器算力和接口丰富度都够看。但BSP调试里Audio子系统向来是块难啃的骨头——它不像GPIO或者I2C那样寄存器配好就能出波形。音频链路涉及数字接口时序、模拟增益、时钟树、DMA搬运、混音路由任何一环出问题表现都是“没声音”或者“有杂音”排查起来像大海捞针。我这次拿到的板子是客户定制的T527方案音频部分用了两颗外置Codec一颗负责模拟麦克风阵列输入另一颗负责多路扬声器输出中间通过TDM接口和T527的I2S/PCM控制器对接。项目标题叫“BSP调试#15Audio”说明这是系列调试记录里的第十五篇前面大概率已经趟过了DDR、eMMC、Display这些坑轮到音频了。核心需求很明确让T527的音频子系统在Linux BSP环境下完整跑通包括录音、回放、混音、通路切换并且要满足车载场景下的多路并发和低延迟要求。适合谁看做嵌入式Linux BSP的同行、刚接手全志平台音频调试的工程师、以及想了解TDM多Codec架构怎么落地的技术爱好者。下面我把整个调试过程拆开揉碎从设计思路到实操细节再到踩过的坑一并倒出来。2. 音频子系统整体设计与选型考量2.1 为什么选TDM而不是多路I2ST527的I2S/PCM控制器数量有限如果每颗Codec都独占一组I2S引脚复用会非常紧张而且PCB走线也麻烦。TDMTime Division Multiplexing的好处是一组时钟线加一根数据线就能挂多颗Codec通过时隙分配来区分通道。T527的TDM控制器支持最多16个时隙每个时隙32位足够覆盖两颗Codec的通道需求。选TDM的另一个原因是同步性。多路I2S各自独立跑时钟域不同步录音和回放之间容易产生漂移做回声消除或者波束成形时相位对不齐。TDM模式下所有Codec共享同一组BCLK和LRCLK采样时刻严格对齐后期算法处理省心很多。但TDM也有代价配置复杂度高。时隙分配、帧同步极性、数据延迟这些参数只要有一个对不上出来的就是噪声或者静音。后面我会详细说怎么调。2.2 Codec选型与链路拓扑模拟麦克风阵列用的是四颗MEMS麦克风直接进Codec的ADCCodec通过TDM的4个时隙把数据传给T527。扬声器输出这边Codec的DAC接收T527发来的TDM数据占用另外4个时隙输出四路模拟信号分别驱动四个车门喇叭。链路拓扑大致是上行MEMS麦克风 → Codec ADC → TDM TX → T527 TDM RX → ALSA capture下行ALSA playback → T527 TDM TX → Codec DAC → 功放 → 喇叭这里有个细节T527的TDM控制器既可以做TX也可以做RX但同一组引脚在同一时刻只能选一个方向。所以上行和下行用了两组独立的TDM控制器各自跑自己的时钟。如果非要用一组TDM做双向那就得用TDM的“半双工”模式分时切换方向延迟会大很多不适合实时音频。2.3 软件框架选择ASoC还是简单字符设备全志的BSP默认用ASoCALSA System on Chip框架这是Linux音频的标准做法。ASoC把音频系统拆成三部分Machine驱动、Platform驱动、Codec驱动。Machine驱动负责把Platform和Codec绑在一起定义DAI链路和时钟Platform驱动管DMA和TDM控制器Codec驱动管芯片内部的寄存器和通路。用ASoC的好处是生态成熟上层应用直接走ALSA接口不需要自己写字符设备。而且ASoC的DAPM动态音频电源管理能自动管理通路上的电源开关省电又安全。坏处是调试时层次多出问题不好定位。我的经验是先在Machine驱动里把DAI链路配通确保时钟和数据能跑起来再去调Codec内部的增益和路由。3. 核心细节解析与实操要点3.1 设备树配置TDM控制器与Codec节点设备树是BSP调试的起点配错了后面全白搭。T527的TDM控制器节点在sun50iw10.dtsi里已经定义好了我们需要在板级dts里覆盖状态和引脚。tdm0 { status okay; pinctrl-names default; pinctrl-0 tdm0_pins_a; #sound-dai-cells 0; dai-tdm-slot-num 8; dai-tdm-slot-width 32; dai-tdm-slot-tx-mask 0x0f; /* 下行占用时隙0-3 */ dai-tdm-slot-rx-mask 0xf0; /* 上行占用时隙4-7 */ };这里dai-tdm-slot-num设成8意味着每帧有8个时隙每个时隙32位。tx-mask和rx-mask是位掩码0x0f表示低4个时隙用于发送0xf0表示高4个时隙用于接收。注意这两个掩码不能重叠否则同一时隙既发又收会冲突。Codec节点挂在I2C总线上需要配好地址和时钟i2c2 { status okay; codec_a: codec1a { compatible vendor,codec-a; reg 0x1a; clocks ccu CLK_TDM0; clock-names mclk; #sound-dai-cells 0; }; };clocks属性指向TDM0的时钟这是Codec的主时钟源。如果Codec需要独立的MCLK还得在CCU里配好分频系数。我遇到过Codec不出声最后发现是MCLK频率不对Codec内部PLL锁不住。3.2 时钟树与采样率计算音频时钟是调试的重灾区。T527的TDM控制器时钟来自PLL_AUDIO经过分频后产生BCLK和LRCLK。BCLK的频率等于采样率乘以时隙数乘以时隙位宽。假设采样率48kHz8个时隙每个时隙32位那么BCLK 48000 × 8 × 32 12.288 MHzLRCLK就是采样率48kHz占空比50%。PLL_AUDIO的典型频率是24.576MHz正好是12.288MHz的两倍分频系数取2就能得到BCLK。如果采样率换成44.1kHzBCLK变成11.2896MHzPLL_AUDIO就得重新配因为24.576不是11.2896的整数倍。这时候要么换PLL频率要么用分数分频。全志的CCU支持分数分频但配置起来麻烦建议尽量用48kHz系列采样率。注意BCLK频率必须在Codec的规格范围内。有些Codec最高只支持到6.144MHz这时候就得减少时隙数或者降低位宽。3.3 DAI链路绑定与DAPM路由Machine驱动里要定义DAI链路把TDM控制器和Codec绑起来static struct snd_soc_dai_link t527_codec_dai { .name t527-codec, .stream_name T527 Audio, .cpu_dai_name sunxi-tdm0, .codec_dai_name codec-a-hifi, .platform_name sunxi-tdm0-pcm, .codec_name codec-a.2-001a, .dai_fmt SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_NB_NF, .ops t527_codec_ops, };dai_fmt里的DSP_A表示TDM模式帧同步脉冲在第一个数据位之前。CBS_CFS表示Codec是时钟从设备T527提供BCLK和LRCLK。NB_NF表示数据在BCLK的下降沿采样帧同步高电平有效。这些参数必须和Codec的数据手册严格对应差一个极性就出噪声。DAPM路由在Codec驱动里定义比如static const struct snd_soc_dapm_route codec_routes[] { {ADC, NULL, MIC1}, {ADC, NULL, MIC2}, {DAC, NULL, SPK1}, {DAC, NULL, SPK2}, };这些路由决定了信号从哪个输入走到哪个输出。调试时可以用amixer命令查看和修改路由状态。4. 实操过程与核心环节实现4.1 内核配置与驱动编译首先确认内核配置里打开了ASoC和全志TDM驱动make ARCHarm64 menuconfig需要勾选的选项CONFIG_SND_SOCyCONFIG_SND_SUNXI_TDMyCONFIG_SND_SOC_CODEC_Ay假设Codec驱动叫这个CONFIG_SND_SIMPLE_CARDy如果用simple-card框架编译完内核后把dtb和ko文件推到板子上。如果是内置驱动直接重启就行。4.2 上电验证与寄存器读取板子上电后先看I2C能不能读到Codeci2cdetect -y 2如果地址0x1a出现说明I2C通了。然后用regmap调试接口读Codec的ID寄存器cat /sys/kernel/debug/regmap/2-001a/registers确认ID值符合数据手册。如果读不到检查I2C上拉电阻和供电。接着看TDM控制器有没有注册成功cat /proc/asound/cards应该能看到声卡列表。如果声卡没出现多半是DAI链路绑定失败dmesg里会有报错。4.3 播放测试与波形抓取用aplay放一个48kHz的正弦波aplay -D hw:0,0 -r 48000 -c 4 -f S32_LE sine.wav如果喇叭有声音说明下行链路通了。如果没声音先用示波器抓TDM的BCLK、LRCLK和DATA线。正常情况下BCLK应该是12.288MHzLRCLK是48kHzDATA线上有时隙跳变。我遇到过BCLK有波形但DATA没数据的情况最后发现是DMA没启动。检查/proc/asound/card0/pcm0p/sub0/status如果state是PREPARED而不是RUNNING说明DMA没搬数据。这时候要查Platform驱动里的DMA通道配置确认dma-names和dmas属性对不对。4.4 录音测试与增益调整录音用arecordarecord -D hw:0,0 -r 48000 -c 4 -f S32_LE -d 5 test.wav录完后用audacity看波形。如果波形幅度太小调Codec的ADC增益寄存器。如果波形是平的检查麦克风偏置电压和DAPM路由。增益调整有个技巧先用amixer把增益设到中间值然后逐步增加同时观察波形是否削顶。削顶了就降6dB直到波形饱满但不失真。4.5 多路并发与延迟优化车载场景要求四路扬声器同时出声且延迟低于20ms。TDM模式下四路数据在同一帧的不同时隙里传输天然同步。延迟主要来自DMA缓冲区大小。默认的period size是1024帧在48kHz下约21ms加上DMA搬运和Codec解码总延迟可能超过30ms。优化方法是减小period sizeaplay -D hw:0,0 --period-size256 --buffer-size1024 sine.wav256帧在48kHz下约5.3ms总延迟能压到15ms以内。但period size太小会增加中断频率CPU占用率上升。实测256是一个比较好的平衡点。5. 常见问题与排查技巧实录5.1 问题速查表现象可能原因排查方法完全没声音DAI链路未绑定查dmesg看snd_soc_register_card是否成功有噪声无音乐时钟极性错误抓BCLK和LRCLK确认采样沿和帧同步极性录音波形平麦克风偏置未开读Codec寄存器确认MICBIAS使能播放断断续续DMA缓冲区太小增大buffer-size或检查DDR带宽多路不同步TDM时隙掩码重叠确认tx-mask和rx-mask无交集采样率切换失败PLL无法锁定换用48kHz系列采样率或配分数分频5.2 独家避坑技巧第一个坑TDM的帧同步极性。数据手册上写“帧同步高有效”但实际抓波形发现Codec期望的是低有效。这时候改dai_fmt里的NB_NF为NB_IF或者反过来。我一般先用示波器抓T527输出的LRCLK再看Codec手册里的时序图对比上升沿和下降沿的位置。第二个坑Codec的MCLK频率。有些Codec要求MCLK是采样率的256倍或512倍。48kHz采样率下MCLK应该是12.288MHz或24.576MHz。如果CCU配错了Codec内部PLL锁不住表现是无声或噪声。用clk_summary命令查看实际时钟频率cat /sys/kernel/debug/clk/clk_summary | grep tdm第三个坑DAPM路由的电源管理。ASoC的DAPM会自动关闭未使用的通路但有时候路由配了电源却没开。这时候在Codec驱动里加SND_SOC_DAPM_SUPPLY控件强制打开偏置电源。或者用amixer手动开amixer -c 0 sset MICBIAS on第四个坑TDM时隙的数据延迟。DSP_A模式下数据在帧同步脉冲之后立即有效。但有些Codec要求一个BCLK周期的延迟。这时候改dai_fmt为DSP_B数据延迟一个周期。这个参数在数据手册里通常叫“data delay”或“frame sync offset”。5.3 调试工具与命令汇总i2cdetect -y 2扫描I2C设备cat /sys/kernel/debug/regmap/2-001a/registers读Codec寄存器cat /proc/asound/cards查看声卡列表aplay -l列出播放设备arecord -l列出录音设备amixer -c 0 controls列出所有混音控件cat /proc/asound/card0/pcm0p/sub0/status查看播放流状态cat /sys/kernel/debug/clk/clk_summary查看时钟树这些命令我调试时基本不离手尤其是regmap和clk_summary能省很多猜的时间。6. 性能验证与稳定性测试6.1 音频质量客观测试链路通了之后得验证音质。我用sox生成1kHz正弦波播放后录回来用sox的stat功能看THDNsox sine.wav -n statTHDN低于0.1%算合格低于0.05%算优秀。如果THDN偏高检查Codec的模拟增益是否过大导致削顶或者电源纹波是否耦合到了音频通路。6.2 长时间稳定性测试车载场景要求连续工作不出错。我跑了一个24小时的循环播放录音测试while true; do aplay -D hw:0,0 test.wav arecord -D hw:0,0 -d 10 rec.wav done期间监控dmesg有没有报错/proc/asound里的xrun计数有没有增加。xrun是DMA缓冲区溢出或欠载偶尔一次没关系频繁出现就得调period size。6.3 多路并发压力测试四路扬声器同时播放不同内容用aplay的-D参数指定不同的PCM设备。如果TDM时隙分配正确四路应该互不干扰。我遇到过一路声音大一路声音小的情况最后发现是Codec的DAC增益寄存器没配对四路增益不一致。统一设成0dB就好了。7. 后续扩展与个人体会这套TDM多Codec架构跑通之后扩展性其实很好。比如要加一路数字麦克风直接往TDM的空闲时隙里塞就行不用改硬件。或者要支持8通道录音把dai-tdm-slot-rx-mask改成0xffCodec那边同步配好时隙映射。我个人在实际操作中的体会是音频调试最怕“想当然”。数据手册上的时序图一定要用示波器验证不能只看文字描述。时钟频率一定要用clk_summary确认不能只信设备树里的配置。DAPM路由一定要用amixer逐条检查不能只靠驱动里的默认值。最后分享一个小技巧如果手头没有示波器可以用T527的I2S回环模式自测。把TDM的TX和RX短接播放的数据直接录回来对比原始文件和录制文件。如果一致说明TDM控制器和DMA没问题问题在Codec侧。这个方法能快速定位故障域省得两头抓瞎。