1. 从一块跑不出声音的板子说起第一次在RK3568上接I2S音频编解码芯片十有八九会遇到同一个场景驱动加载了aplay -l能看到声卡但喇叭就是不出声或者出来的全是刺耳的噪声。更让人抓狂的是dmesg里干干净净什么报错都没有。这不是你运气差而是I2S这条链路涉及的环节太多——时钟、格式、引脚复用、设备树节点匹配任何一个环节对不上结果都是“静默失败”。RK3568这颗芯片在工业控制和边缘计算场景里用得非常多四核A55的架构配上丰富的外设接口I2S控制器最多支持到8通道采样率覆盖8kHz到192kHz。但瑞芯微的BSP代码结构比较特殊音频子系统的设备树绑定跟主线内核有差异很多从其他平台转过来的工程师第一次看RK3568的音频设备树都会愣一下。这篇内容就是把我自己在RK3568上调试I2S音频驱动的完整过程拆开来讲从硬件信号链路到设备树配置再到ALSA调试手段每一步都配上实际验证过的参数和命令。适合的读者是已经能跑通RK3568基础系统、需要对接音频Codec或者数字功放的嵌入式工程师。如果你手上正好有RK3568的开发板不管是正点原子还是其他家的跟着走一遍应该能少踩不少坑。整个调试过程围绕I2S协议、ALSA框架、设备树配置这三个核心点展开最终目标是让音频数据从CPU的I2S控制器稳定地流到Codec芯片再变成你能听到的声音。2. 先搞清楚I2S在RK3568上的硬件链路2.1 I2S协议到底在传什么I2S全称Inter-IC Sound是飞利浦定的一套音频数据传输协议。它跟I2C、SPI最大的区别在于I2S是专门为音频设计的时钟和数据的时序关系有严格要求。一根完整的I2S链路至少包含三根信号线BCLK位时钟、LRCLK左右声道时钟也叫帧时钟、SDATA串行数据。有些场景下还需要MCLK主时钟给Codec提供参考时钟源。BCLK的频率等于采样率乘以位宽乘以声道数。举个例子48kHz采样率、16位位宽、双声道BCLK就是48000×16×21.536MHz。LRCLK的频率就等于采样率48kHz。这两个时钟的相位关系决定了数据在哪个边沿被采样、左右声道如何切换。I2S标准规定数据在BCLK的下降沿变化、上升沿采样LRCLK低电平为左声道、高电平为右声道。但实际芯片实现中经常有变体比如左对齐、右对齐、DSP模式等这就是为什么设备树里要配format参数。RK3568的I2S控制器支持主模式和从模式。主模式下RK3568产生BCLK和LRCLK从模式下由外部Codec提供时钟。绝大多数应用场景用主模式因为RK3568的时钟树可以精确分频出标准音频时钟。这里有个关键点RK3568的I2S时钟源来自mclk_i2s0_8ch_rx和mclk_i2s0_8ch_tx这两个时钟又由clk_i2s0_8ch分频得到。分频系数没配对采样率就会偏表现出来就是声音变调或者Codec锁不住时钟。2.2 RK3568 I2S控制器的特性RK3568一共有四个I2S控制器其中I2S0是8通道的I2S1到I2S3是2通道的。8通道那个主要给HDMI音频或者多声道场景用普通立体声Codec接2通道的I2S1/I2S2/I2S3就够了。每个控制器有独立的寄存器基地址在设备树里通过reg属性区分。控制器内部有FIFOTX和RX各有一块。FIFO深度决定了音频流的缓冲能力太浅容易欠载导致断音太深会增加延迟。RK3568的I2S FIFO深度是32个样本对于大多数场景够用。DMA接口方面RK3568用的是DW DMA控制器音频数据通过DMA在内存和I2S FIFO之间搬运不占用CPU。设备树里要正确配置dmas和dma-names属性否则DMA通道申请失败音频流根本起不来。引脚复用是另一个容易翻车的地方。RK3568的I2S引脚跟其他功能比如UART、SPI是复用的必须通过pinctrl正确配置。比如I2S1的SCLK、LRCK、SDI、SDO分别对应GPIO3的某些引脚具体哪几个要看你的原理图。pinctrl配置错了信号根本出不来但软件层面不会有任何报错因为引脚被别的功能占用了。2.3 典型硬件连接方案最常见的接法是这样的RK3568的I2S1作为主设备输出MCLK、BCLK、LRCLK给Codec同时通过SDATA线收发音频数据。Codec这边比如ES8388、WM8960、NAU8822这些都是常见的立体声Codec。以ES8388为例它需要MCLK来工作内部PLL锁相到MCLK后产生自己的采样时钟。如果MCLK频率不对ES8388的I2C控制接口能通但音频就是不出声。硬件上还有几个细节要注意。MCLK的走线尽量短如果实在要拉长记得做阻抗匹配。BCLK和LRCLK如果走线太长可能需要在源端串一个22欧姆到33欧姆的电阻来抑制反射。SDATA线因为是双向的如果Codec的驱动能力不够可能需要加上拉电阻。这些硬件层面的东西软件调试时看不出来但会直接影响音频质量。电源和地也要留意。Codec的模拟电源和数字电源最好分开供电模拟地跟数字地在Codec下方单点连接。如果板子上音频输出有底噪十有八九是地处理得不好。我遇到过一块板子喇叭里一直有“滋滋”声最后发现是Codec的AVDD跟DVDD共用了一个LDO分开供电后底噪立刻消失。3. 设备树配置I2S音频驱动的核心战场3.1 设备树里音频节点的整体结构RK3568的音频设备树配置分三层SoC级的I2S控制器节点、板级的Codec节点、以及把两者连起来的sound节点。SoC级的节点在rk3568.dtsi里已经定义好了一般不需要改。板级的Codec节点和sound节点需要你自己在板级dts里添加。先看SoC级的I2S1节点长什么样i2s1_8ch: i2sfe410000 { compatible rockchip,rk3568-i2s-tdm; reg 0x0 0xfe410000 0x0 0x1000; interrupts GIC_SPI 53 IRQ_TYPE_LEVEL_HIGH; clocks cru MCLK_I2S1_8CH_TX, cru MCLK_I2S1_8CH_RX, cru HCLK_I2S1_8CH; clock-names mclk_tx, mclk_rx, hclk; dmas dmac1 2, dmac1 3; dma-names tx, rx; resets cru SRST_M_I2S1_8CH_TX, cru SRST_M_I2S1_8CH_RX; reset-names mclk_tx, mclk_rx; rockchip,grf grf; #sound-dai-cells 0; status disabled; };这个节点里几个关键属性compatible匹配驱动clocks和clock-names定义时钟源dmas和dma-names定义DMA通道rockchip,grf指向GRF寄存器用于配置I2S的时钟分频。status默认是disabled板级dts里要把它改成okay。板级dts里需要覆盖这个节点加上pinctrl配置i2s1_8ch { status okay; pinctrl-names default; pinctrl-0 i2s1m0_sclk i2s1m0_lrck i2s1m0_sdi i2s1m0_sdo i2s1m0_mclk; rockchip,trcm tx-rx; rockchip,bclk-fs 64; };rockchip,trcm tx-rx表示TX和RX共用同一个时钟源这在全双工场景下必须配。rockchip,bclk-fs定义BCLK跟采样率的倍数关系64是常见值对应32位位宽双声道。如果你的Codec要求BCLK采样率×32就改成32。3.2 Codec节点的配置要点以ES8388为例它挂在I2C总线上设备树节点大概是这样es8388: es838810 { compatible everest,es8388; reg 0x10; clocks cru MCLK_I2S1_8CH_TX; clock-names mclk; assigned-clocks cru MCLK_I2S1_8CH_TX; assigned-clock-rates 12288000; pinctrl-names default; pinctrl-0 i2s1m0_mclk; status okay; };assigned-clock-rates设成12288000这是48kHz采样率系列的标准MCLK频率。为什么是12288000而不是12000000因为1228800048000×256能整除出一系列标准采样率48k、96k、192k而12000000只能整除出44.1k系列。如果你的应用以44.1kHz为主就设成11289600。这个细节很多教程不讲但设错了采样率切换时会有杂音。Codec节点里还要注意clocks和clock-names的对应关系。ES8388驱动里会通过clock-names mclk来获取时钟名字对不上就获取失败Codec初始化直接报错。3.3 sound节点的绑定逻辑sound节点是把CPU DAI和Codec DAI连起来的关键rk809_sound: rk809-sound { compatible simple-audio-card; simple-audio-card,format i2s; simple-audio-card,name rockchip,es8388; simple-audio-card,mclk-fs 256; simple-audio-card,cpu { sound-dai i2s1_8ch; }; simple-audio-card,codec { sound-dai es8388; }; };simple-audio-card是ALSA提供的通用音频卡驱动适合大多数简单场景。format i2s指定I2S标准格式如果Codec用的是左对齐就改成left_j右对齐改成right_jDSP模式改成dsp_a或dsp_b。mclk-fs 256表示MCLK是采样率的256倍这个值要跟Codec的要求一致。simple-audio-card,cpu和simple-audio-card,codec分别指向CPU侧的DAI和Codec侧的DAI。这两个节点的#sound-dai-cells属性必须配成0否则引用会失败。RK3568的I2S节点里默认就有#sound-dai-cells 0Codec驱动里也要确保有这个属性。如果用的是rockchip,rk809-codec这类瑞芯微自家的Codecsound节点会复杂一些因为要处理多路音频路由。但基本结构是一样的只是多了rockchip,card-name、rockchip,cpu、rockchip,codec这些瑞芯微特有的属性。4. 从零开始I2S音频驱动的完整调试流程4.1 硬件检查与时钟验证在动软件之前先用示波器或者逻辑分析仪确认硬件信号。把探头接到MCLK、BCLK、LRCLK上上电后应该能看到时钟波形。如果MCLK没有输出先检查Codec的I2C是否通信成功因为很多Codec驱动是在I2C探测成功后才去使能MCLK的。MCLK频率用频率计测一下12288000Hz是目标值。如果偏差超过1%Codec内部PLL可能锁不住。我遇到过一块板子MCLK只有6MHz查了半天发现是assigned-clock-rates被别的驱动改掉了最后在sound节点里加了simple-audio-card,mclk-fs 256才固定住。BCLK和LRCLK在播放时才有输出可以用aplay播放一个测试音频同时用示波器观察。LRCLK的频率应该等于采样率BCLK频率等于采样率×位宽×声道数。如果LRCLK有但BCLK没有检查I2S控制器的rockchip,bclk-fs配置。如果两个都没有检查pinctrl配置和I2S控制器的status。4.2 内核配置与驱动编译RK3568的音频驱动涉及几个内核配置项在make menuconfig里要确保打开Device Drivers - Sound card support - Advanced Linux Sound Architecture - ALSA for SoC audio support - Rockchip I2S Device Driver (选中) CODEC drivers - Everest ES8388 CODEC (选中)如果用的是其他Codec对应选中相应的驱动。编译内核和设备树make ARCHarm64 rk3568-evb1-ddr4-v10.img -j$(nproc)编译完成后把boot.img和resource.img烧录到板子上。启动后先看dmesg | grep -i i2s应该有类似这样的输出rk3568-i2s-tdm fe410000.i2s: rk3568_i2s_tdm_probe再看dmesg | grep -i es8388确认Codec探测成功es8388 1-0010: es8388_probe如果Codec探测失败最常见的原因是I2C地址不对或者I2C总线没使能。用i2cdetect -y 1扫一下总线看0x10地址上有没有设备。4.3 ALSA设备与控件调试驱动加载成功后aplay -l应该能看到声卡card 0: rockchipes8388 [rockchip,es8388], device 0: ff410000.i2s-es8388-hifi es8388-hifi-0 []如果看不到声卡检查sound节点的compatible是否匹配以及simple-audio-card,cpu和simple-audio-card,codec的引用是否正确。用cat /proc/asound/cards也能看到声卡列表。声卡出来了但没声音先用amixer检查控件amixer -c 0 controls amixer -c 0 sset Playback Volume 80% amixer -c 0 sset Master onES8388有几个关键控件Left Mixer、Right Mixer、Output 1、Output 2。要确保对应的输出通道是打开的。我见过有人调了半天没声音最后发现Output 2没开而喇叭接在Output 2上。播放测试音频aplay -D hw:0,0 -f S16_LE -r 48000 -c 2 test.wav如果播放时报underrun说明DMA缓冲不够可以在/etc/asound.conf里加大bufferpcm.!default { type hw card 0 device 0 } pcm.dmixer { type dmix ipc_key 1024 slave { pcm hw:0,0 period_size 1024 buffer_size 8192 rate 48000 } }4.4 用tinyplay和tinycap做底层验证Android或者瑞芯微的SDK里通常带tinyplay和tinycap工具它们直接操作ALSA的PCM接口绕过上层音频框架适合底层调试。tinyplay test.wav -D 0 -d 0 tinycap /sdcard/record.wav -D 0 -d 0 -c 2 -r 48000 -b 16tinyplay播放时如果听到声音但断断续续多半是DMA周期设置不合理。可以在I2S节点里调整rockchip,playback-fifo-threshold默认是FIFO深度的一半改成更小的值能降低延迟但太小会增加中断频率。tinycap录音时如果录到的全是静音先检查Codec的输入通道配置。ES8388的输入有MIC和LINE IN要通过amixer选择正确的输入源amixer -c 0 sset Left Input Boost Mixer LINPUT1 on amixer -c 0 sset Right Input Boost Mixer RINPUT1 on5. 常见问题排查与避坑经验5.1 声卡注册失败问题速查现象可能原因排查方法aplay -l无声卡sound节点未使能检查status okay声卡注册但无PCM设备CPU DAI或Codec DAI引用错误检查sound-dai指向的节点声卡注册报-EPROBE_DEFERCodec驱动未加载检查Codec的compatible和I2C地址声卡注册报-ENODEV时钟获取失败检查clocks和clock-names声卡注册报-EINVALformat不匹配检查simple-audio-card,format-EPROBE_DEFER是最常见的意思是Codec驱动还没准备好sound驱动会延迟重试。如果一直重试失败看dmesg里Codec的报错信息。有时候是I2C总线没使能有时候是Codec的reset引脚没配。5.2 播放无声或杂音的排查思路播放无声先分三步走第一确认amixer里所有相关控件都打开了第二用示波器看I2S的SDATA线上有没有数据波形第三检查Codec的模拟输出是否使能。如果SDATA有波形但没声音问题在Codec的模拟部分。ES8388的DAC Output控件要打开Output 1和Output 2要选对。如果SDATA没波形问题在数字部分检查I2S控制器的status和pinctrl。杂音问题更棘手。如果是持续的“滋滋”声多半是地环路或者电源纹波。如果是随音频变化的“沙沙”声可能是位宽或格式配错了。比如Codec配的是24位但I2S传的是16位数据对齐方式不对就会产生噪声。这时候要检查simple-audio-card,format和Codec的dai_fmt是否一致。还有一种情况是采样率不匹配。播放48kHz的音频但Codec被配成了44.1kHz声音会变调且伴有杂音。用cat /proc/asound/card0/pcm0p/sub0/hw_params可以看到当前的硬件参数确认采样率、位宽、声道数是否跟播放的文件一致。5.3 时钟配置的坑与经验RK3568的I2S时钟树比较复杂mclk_i2s1_8ch_tx的父时钟是clk_i2s1_8ch再往上是gpll或cpll。如果assigned-clock-rates设了一个不能整除的值时钟框架会自动选最近的可用频率导致实际MCLK跟预期不符。我踩过的一个坑把assigned-clock-rates设成12000000想支持44.1kHz系列但gpll的分频表里没有能精确分出11289600的档位最后实际输出是11000000左右Codec锁不住。解决办法是在assigned-clocks里指定父时钟为cpll因为cpll的分频更灵活。另一个经验是如果Codec支持从模式由Codec提供BCLK和LRCLK可以试试把RK3568配成从模式让Codec做主时钟源。这样能避开RK3568时钟分频的限制但要求Codec的时钟精度足够高。ES8388做从模式时MCLK还是由RK3568提供但BCLK和LRCLK由ES8388产生这时候I2S节点的rockchip,trcm要改成rx-tx或者根据实际情况调整。5.4 多声卡场景下的设备选择RK3568上可能同时存在HDMI音频、USB音频和I2S音频aplay -l会列出多个声卡。默认声卡是card 0但I2S音频不一定是card 0。用aplay -D hw:1,0指定具体的声卡和设备。如果要在应用层固定使用I2S声卡可以在/etc/asound.conf里配置pcm.i2s { type hw card 1 device 0 } ctl.i2s { type hw card 1 }然后应用里用pcm.i2s这个名字来打开设备。这样即使声卡编号变了配置也不用改。6. 进阶调试用寄存器级手段定位疑难问题6.1 查看I2S控制器寄存器状态当软件层面查不出问题时直接读寄存器是最有效的手段。RK3568的I2S1寄存器基地址是0xfe410000用devmem工具读取devmem 0xfe410000 32 devmem 0xfe410004 320xfe410000是I2S的TXCR寄存器控制发送使能、格式、位宽等。0xfe410004是RXCR。如果TXCR的bit 0是0说明发送没使能检查驱动里是否调用了SND_SOC_DAIFMT相关的配置。时钟分频寄存器在0xfe410008CKR读出来的值可以反推分频系数。如果分频系数跟预期不符说明时钟框架选的频率不对需要回头检查assigned-clock-rates。6.2 用逻辑分析仪抓I2S时序逻辑分析仪是调试I2S的利器。把BCLK、LRCLK、SDATA三根线接上设置采样率至少是BCLK的4倍。抓到的波形里重点看几个东西LRCLK的占空比是不是50%SDATA在BCLK的哪个边沿变化数据跟LRCLK的对齐关系。如果LRCLK占空比不是50%说明I2S控制器的分频配置有问题。如果SDATA在BCLK的上升沿变化说明格式配成了左对齐而不是标准I2S。这些细节在软件层面很难发现但逻辑分析仪一抓就清楚。6.3 动态调试与ftrace内核的ftrace可以跟踪I2S驱动的函数调用echo function /sys/kernel/debug/tracing/current_tracer echo rk3568_i2s* /sys/kernel/debug/tracing/set_ftrace_filter echo 1 /sys/kernel/debug/tracing/tracing_on aplay -D hw:0,0 test.wav echo 0 /sys/kernel/debug/tracing/tracing_on cat /sys/kernel/debug/tracing/trace这样能看到I2S驱动在播放过程中调用了哪些函数哪个环节耗时最长。如果rk3568_i2s_trigger被调用了但rk3568_i2s_start没被调用说明触发条件没满足检查DMA通道是否申请成功。7. 一些实战中攒下来的经验调试I2S音频驱动这件事最怕的就是“想当然”。我见过太多人拿着别人的设备树直接抄结果引脚复用不一样、Codec型号不一样、时钟要求不一样抄过来根本跑不通。设备树里的每一个属性都有它的作用配之前先搞清楚为什么这么配。关于Codec的选型如果项目对成本不敏感建议选ES8388或者WM8960这类资料多的。NAU8822也不错但驱动里有些寄存器配置需要自己调。瑞芯微自家的RK809/RK817是PMICCodec二合一省事但灵活性差一些适合对音频要求不高的场景。MCLK的走线一定要短如果板子空间允许尽量让Codec靠近RK3568。我见过一块板子MCLK走了10厘米结果48kHz以上采样率就锁不住降到44.1kHz才勉强能用。后来在MCLK源端串了33欧姆电阻问题才解决。最后说一个调试习惯每次改完设备树先make dtbs单独编译设备树烧录后重启看dmesg里有没有新的报错。不要一次改好几个地方否则出了问题不知道是哪个改动引起的。音频调试是个细致活一步一步来比一次改一堆然后抓瞎要快得多。如果aplay播放时提示Device or resource busy先检查是不是有别的进程占用了声卡。用fuser -v /dev/snd/*可以看到哪个进程在占用。有时候是PulseAudio或者PipeWire在后台跑着把声卡抢走了。嵌入式系统里一般没有这些但Ubuntu Rockchip这类桌面系统上要注意。录音回环测试是个很好的验证手段用tinycap录一段再用tinyplay放出来。如果录放都正常说明I2S的收发链路都通了。如果录到的声音有延迟或者断续检查DMA的period_size和buffer_size这两个参数直接影响录音的实时性。