ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

ES8311音频Codec时钟树配置与分频计算完全指南

ES8311音频Codec时钟树配置与分频计算完全指南 搞音频Codec驱动程序这几年我最怕看到的就是“为什么我I2C寄存器都配好了就是没声音”这类问题。十有八九问题都出在ES8311的时钟树配置上——这门功课不做扎实后续调音量、调EQ全是白费功夫。这篇文章就专门讲ES8311的时钟树配置和分频计算从采样率、MCLK、BCLK、LRCLK的关系入手把内部PLL、ADC/DAC分频这些节点挨个捋清楚再带你走一遍从需求到寄存器配置的完整计算流程。适合正在做嵌入式音频方案的MCU工程师、Linux ALSA驱动工程师以及所有被Codec时钟搞得睡不着觉的软硬件同学。1. ES8311时钟树概览与整体结构1.1 芯片定位与典型应用场景ES8311是一颗超低功耗音频编解码器Audio Codec在智能音箱、语音遥控器、电池供电的IoT音频设备、车载语音模块里非常常见。它的角色很纯粹把模拟麦克风信号转成数字I2S数据送给主控同时把主控的数字音频转成模拟信号驱动喇叭或耳机。我最早接触它是在一个语音识别方案里主控是国产Cortex-M4跑RTOS用I2C控制ES8311I2S传音频数据。这种组合在业内太常见了以至于几乎所有做语音产品的硬件工程师都绕过这颗芯片。这颗芯片之所以流行除了功耗低、封装小还有一个重要原因是它的时钟系统相对灵活支持不同的MCLK输入频率和内部PLL配置。但灵活的另一面就是复杂如果只看数据手册抄一段初始化寄存器大概率能出声但如果你换了一个采样率、换了一颗主控、改了MCLK频率原来的配置就可能完全失效。这也是为什么很多工程师在“能不能响”这件事上反复折腾根源就是对时钟树缺乏系统理解。1.2 时钟树的模块组成与时钟路径打个比方ES8311的时钟系统就像一个自来水分配中心。外部进来的MCLK是总进水口进去之后分成好几路一路直接给数字核心供电一路喂给内部PLL变成“高压水源”再经过多组分压阀分别供给ADC和DAC。所谓时钟树就是这些进水口、阀门、管路的完整拓扑。ES8311内部时钟路径大致有这几个节点输入级选择MCLK引脚输入部分场景也可以从BCLK取参考时钟。预分频级对输入参考时钟做分频常见分频系数有1、2、4、8等。PLL级使能内部PLL后把参考时钟倍频到内部系统时钟SYSCLK。系统时钟分配SYSCLK分别供给ADC时钟链和DAC时钟链。ADC/DAC分频级各自把SYSCLK分频成ADC/DAC的过采样时钟。I2S接口时钟BCLK、LRCLK与采样率严格对应在slave模式下由主控提供。关键点在于ADC和DAC的采样率最终由“过采样时钟”分频得到而过采样时钟又由SYSCLK分频得到。所以只要SYSCLK不对后面每级分频出来的频率都是错的。这也是为什么时钟树必须从源头算起而不是只核对最后一两个分频寄存器。1.3 为什么时钟树必须先想清楚再动手因为Codec的MCLK和采样率之间要求严格的比例关系。一旦比例不对即使寄存器看起来写对了声音也不对。我见过一个很典型的例子主控输出24.576MHz给ES8311目标采样率是48kHz这时候MCLK_Ratio512刚刚好。结果同事图省事把采样率改成44.1kHz直接用同一个MCLKRatio大约等于557.28ES8311内部无论怎么配都配不出精确的44.1kHz播放出来的声音就像磁带快放一样。这种问题比“完全无声”更难排查因为芯片不会报错I2C读写都正常寄存器也写进去了但听感就是不对。等你怀疑到时钟树上往往已经浪费了大半天。所以我的习惯是拿到一块新板子先不急着写驱动打开数据手册的时钟树章节把MCLK、BCLK、LRCLK、SYSCLK这几个频率在草稿纸上列出来验算无误之后再碰寄存器。这一步做扎实后面能省掉大量无头苍蝇式排查。2. 核心参数采样率、MCLK、BCLK、LRCLK的关系2.1 三个必须背下来的公式音频时钟配置绕不开这几个基础关系我建议直接背下来LRCLK频率 采样率fsBCLK频率 fs × 声道数 × 位深MCLK频率 fs × MCLK_Ratio其中MCLK_Ratio本质就是过采样率OSR常见取值有64、128、256、512。如果是双声道16bit的I2SBCLK就等于fs×32如果是双声道24bitBCLK就是fs×48双声道32bit就是fs×64。这些数字算起来很简单但很容易被忽略。举个具体例子目标采样率16kHz立体声16bit那么LRCLK必须是16kHzBCLK必须是512kHzMCLK如果用256fs就是4.096MHz用512fs就是8.192MHz。这三个频率之间存在严格的倍数关系任何一个对不上声音就不对。2.2 典型采样率的时钟参数速查表实际调试时我经常要对着表查频率这里整理了一份常用速查表建议收藏备用采样率fsLRCLKBCLK16bit双声道MCLK256fsMCLK512fs8 kHz8 kHz256 kHz2.048 MHz4.096 MHz16 kHz16 kHz512 kHz4.096 MHz8.192 MHz32 kHz32 kHz1.024 MHz8.192 MHz16.384 MHz44.1 kHz44.1 kHz1.4112 MHz11.2896 MHz22.5792 MHz48 kHz48 kHz1.536 MHz12.288 MHz24.576 MHz96 kHz96 kHz3.072 MHz24.576 MHz49.152 MHz注意44.1kHz和48kHz这两棵“频率树”是不能互相整除的。11.2896MHz、22.5792MHz和12.288MHz、24.576MHz之间没有任何整数倍关系。这也是很多低成本音频方案需要双晶振的根本原因。2.3 MCLK Ratio的含义与选择MCLK_Ratio代表每个采样点对应的主时钟周期数。Ratio越高MCLK频率越高Codec内部数字滤波器的工作状态可能更理想但代价是功耗高、EMI辐射大、主控的音频PLL压力也更大。所以在满足数据手册要求的前提下一般优先选较低的Ratio比如256fs。选Ratio时要看两个约束第一是SoC的音频时钟控制器能不能产生对应的MCLK频率第二是ES8311的数据手册里是否支持这个Ratio。比如说你的主控只能输出12.288MHz那在48kHz采样率下固定对应256fs在96kHz下对应128fs这时你要去查手册确认128fs是否被芯片支持。还有一个工程经验如果主控走的是USB音频或者系统的音频源来自48kHz帧结构的接口尽量让整个链路沿48k系列走如果走的是CD、WAV这类44.1k源就要想办法提供22.5792MHz或11.2896MHz的MCLK。不要指望一块Codec能同时完美伺候两种频率树除非你愿意把PLL用到极致。3. 分频计算实战从目标采样率反推寄存器配置3.1 计算流程五步法我总结了一套从需求到配置的流程每次换平台、换采样率都按这个走基本不会翻车第一步确定系统需求。目标采样率fs是多少声道数多少位深多少。这三个参数决定了I2S总线上的BCLK和LRCLK。第二步算BCLK和LRCLK。LRCLK直接等于fsBCLK等于fs×声道数×位深。如果是标准I2S双声道16bit直接按32fs算。第三步确认主控能给的MCLK。查看SoC数据手册看音频模块能输出哪些MCLK频率以及这个频率是从哪个PLL分出来的。这一步决定了后面能不能走“直通”路线。第四步判断MCLK和fs能不能整除。用MCLK除以fs看结果是不是一个整数并且这个整数是否在芯片支持的MCLK_Ratio范围内。如果整除且Ratio合法直接走MCLK旁路PLL的配置。第五步如果整除不了打开PLL。算出目标SYSCLK再反推PLL的分频倍频系数最后评估误差是否在可接受范围内。3.2 场景一MCLK12.288MHz、fs48kHz直接整除这是最简单的场景。主控给ES8311的MCLK是12.288MHz目标采样率是48kHz12.288MHz ÷ 48kHz 256刚好等于256而且是整数。所以MCLK_Ratio配置为256PLL可以完全旁路直接用MCLK作为内部系统时钟。此时的BCLK 48kHz × 32 1.536MHzLRCLK 48kHz这三个时钟的频率关系干净利落。寄存器配置上这一步基本不用动PLL相关寄存器只需要把时钟输入源选为MCLKMCLK分频系数设为1ADC/DAC分频按256fs设置即可。稳定性和音质都最容易做好因为本地晶振或SoC PLL输出的MCLK抖动通常比内部PLL环路小。这类场景在蓝牙音频、车载USB音频、语音采集方案里非常常见。尤其是USB音频芯片枚举之后一般都是48kHz家族直接用12.288MHz或者24.576MHz的MCLK就能覆盖绝大多数需求。3.3 场景二MCLK24.576MHz、fs44.1kHz必须动用PLL这个场景我踩过坑。主控只有24.576MHz的MCLK输出能力但音频源要求44.1kHz采样率比如播放标准CD格式的WAV文件。先算一下24.576MHz ÷ 44.1kHz ≈ 557.28这个结果不是整数没法直接配置成某个整数倍的MCLK_Ratio。这时候必须请出内部PLL。目标内部的系统时钟SYSCLK取多少一般取512 × fs也就是512 × 44100 22.5792MHz。当然也可以取256 × 44100 11.2896MHz这取决于芯片内部ADC/DAC分频支持的范围。接着计算分频比22.5792MHz ÷ 24.576MHz 0.91875 147/160这个比值本身是有理数但在PLL里实现起来并不容易。ES8311的PLL通常由参考时钟预分频和整数倍频组成有些版本支持小数分频设置。实际操作时需要根据手册提供的PLL配分表搜索预分频系数和倍频系数的组合让输出频率尽量接近22.5792MHz。我用过一种暴力但有效的做法在代码里穷举所有允许的MCLK预分频和PLL倍频组合算出输出频率与目标SYSCLK之间的误差选误差最小的一组写入寄存器。算法思路很简单就是两层循环嵌套。typedef struct { uint8_t mclk_div; uint8_t pll_mult; uint32_t fout_hz; int32_t error_ppm; } pll_cfg_t; pll_cfg_t best_cfg; best_cfg.error_ppm INT32_MAX; for (uint8_t mclk_div 1; mclk_div 8; mclk_div) { for (uint8_t pll_mult 1; pll_mult 64; pll_mult) { uint32_t fref mclk_hz / mclk_div; uint32_t fout fref * pll_mult; int32_t err_ppm (int32_t)(((int64_t)fout - target_hz) * 1000000 / target_hz); if (abs(err_ppm) abs(best_cfg.error_ppm)) { best_cfg.mclk_div mclk_div; best_cfg.pll_mult pll_mult; best_cfg.fout_hz fout; best_cfg.error_ppm err_ppm; } } }如果运气好PLL能精确1ppm以内都不差如果搜索出来的组合误差很大那就得考虑更换MCLK频率或者干脆在硬件上增加一颗22.5792MHz晶振。强迫一颗PLL去合成一个非常别扭的频率往往得不偿失。3.4 误差估算多少ppm才安全音频时钟误差用ppm百万分之一来衡量计算公式是error_ppm (f_actual - f_target) / f_target × 10^6举个直观的例子如果PLL实际输出22.6000MHz目标是22.5792MHz那么误差大约是(22600000 - 22579200) / 22579200 × 10^6 ≈ 920ppm也就是大约0.09%的偏差。这个偏差意味着播放44.1kHz的音频时实际以约44.14kHz的速度播放整段音频会比原版快约0.92‰。短时间听不出来但放一首五分钟的歌整体节奏会差零点几秒。如果误差到了1%也就是10000ppm那声音就已经明显变调到让人无法接受了。所以我的经验是对于一般消费类音频产品PLL输出误差控制在±500ppm以内是可以接受的如果要做专业录音、乐器演奏类设备尽量做到±50ppm以内。纯整数分频倍频是零误差小数分频受寄存器精度限制至少要把误差压到千分之一以内。4. 寄存器配置与代码实现4.1 关键寄存器组速览ES8311的控制接口是I2C常见7bit器件地址是0x18具体根据原理图ADDR引脚电平可能略有差异。寄存器不多但时钟相关寄存器的位段分配很容易记混。我这里列一个速览表具体每一位的定义请以你手里的官方datasheet为准不同批次芯片偶尔会有细微差异。寄存器作用配置要点0x00复位与软件控制上电后先写复位等待稳定0x01时钟输入选择、PLL使能选MCLK来源PLL旁路还是使能0x02PLL倍频与MCLK预分频写入穷举计算出的倍频、分频系数0x03~0x05ADC/DAC/BCLK分频配置按MCLK_Ratio和目标fs设置0x0C~0x0FADC/DAC模拟通路控制增益、静音、通路选择这里提醒一句我见过有人把0x02的配置从网上随便抄了一段结果芯片能出声但采样率偏了上千ppm。这是因为不同主控的MCLK频率不同寄存器值不能通用。每一个数值都必须从你的实际频率算出来而不是从别人的配置里复制过来。4.2 初始化序列先时钟后通路ES8311的初始化顺序很重要我的建议是“先时钟、后通路、再静音”。也就是说先把复位和时钟树配置好让内部时钟稳定跑起来再去配置ADC/DAC数据通路和模拟音量。如果反过来通路已经打开了时钟还没有稳定内部状态机可能会进入异常状态表现为初始化成功但声音出不来。一个标准的初始化骨架大概长这样void es8311_clock_init(uint32_t mclk_hz, uint32_t sample_rate) { // 1. 上电延时等待电源稳定 delay_ms(20); // 2. 软件复位 es8311_i2c_write(0x00, 0x1F); delay_ms(10); // 3. 根据计算配置时钟树 // 以下数值是占位实际由分频计算结果填入 uint8_t clk_ctrl1 0x10; // MCLK输入PLL旁路或使能按需配置 uint8_t clk_ctrl2 0x00; // PLL倍频和MCLK预分频按计算填入 uint8_t adc_div 0x00; // ADC分频到 256fs 或 512fs uint8_t dac_div 0x00; // DAC分频到 256fs 或 512fs uint8_t bclk_div 0x00; // BCLK/LRCLK分频匹配I2S格式 es8311_i2c_write(0x01, clk_ctrl1); es8311_i2c_write(0x02, clk_ctrl2); es8311_i2c_write(0x03, adc_div); es8311_i2c_write(0x04, dac_div); es8311_i2c_write(0x05, bclk_div); // 4. 使能PLL后等待锁定 delay_ms(10); // 5. 配置ADC/DAC通路、音量和静音状态 }不要小看这几个delay。PLL锁定需要时间主控的MCLK稳定输出也需要时间。我之前在某个平台上把初始化代码里的延时砍到1ms结果每几十次开机就有一次无声后来抓波形才发现是PLL还没锁定就开始传音频了。4.3 在Linux ALSA驱动里落地时钟配置如果项目跑的是Linux时钟配置不止要写在Codec驱动里还要在machine驱动和设备树里配合。设备树里至少要描述清楚MCLK从哪里来、频率是多少i2c0 { es8311: es831118 { compatible everest,es8311; reg 0x18; clocks audio_mclk; clock-names mclk; assigned-clocks audio_mclk; assigned-clock-rates 12288000; }; };machine驱动中的hw_params回调里需要根据应用层设置的采样率动态算出MCLK并传给Codecstatic int machine_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { unsigned int mclk_fs 256; unsigned int mclk params_rate(params) * mclk_fs; snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, SND_SOC_CLOCK_OUT); snd_soc_dai_set_pll(codec_dai, 0, 0, mclk, mclk); snd_soc_dai_set_clkdiv(codec_dai, 0, mclk_fs); return 0; }实际调试中我常遇到一种情况应用层请求播放48kHz但主控的音频PLL只能输出24.576MHz这没问题可如果应用层切换到44.1kHz主控没有22.5792MHz的输出能力hw_params里就会算出错误的MCLK。所以Linux方案里通常要在machine驱动里限制pcm节点的采样率范围或者做采样率转换而不是放任应用随意切采样率。5. 常见问题与排查实录5.1 现象一完全无声怎么查完全无声基本可以按这个顺序排查先确认Codec供电和I2C通信用i2cdetect或示波器看I2C引脚有没有ACK然后确认MCLK有没有波形频率对不对再确认I2S的BCLK和LRCLK有没有输出最后检查Codec寄存器里的数字通路和模拟通路有没有被静音。我见过一个典型的“无声”案例MCLK没有接主控I2S的时钟倒是正常但ES8311在slave模式下需要外部MCLK作为内部时钟源没MCLK连寄存器都跑不正常。这种情况I2C读写可能看似正常因为I2C本身不依赖MCLK但音频通路完全无法工作。所以排查时别一上来就怀疑I2S先用频率计量MCLK。5.2 现象二有声音但沙哑、失真声音沙哑失真的原因通常不是音量太大而是两种时钟分频配错或者I2S格式不匹配。我遇到过一次ES8311配的是16bit I2S但主控DMA实际发的是32bit数据BCLK频率对不上声音就像喉咙被掐住一样。这种情况拿示波器看BCLK和LRCLK数一下一个LRCLK周期内有多少个BCLK上升沿基本就能判断出来。如果是16bit双声道应该有32个BCLK周期如果数出来是64个说明数据格式位宽已经变了。时钟分频配错也会导致失真特别是ADC/DAC过采样率设置和MCLK_Ratio不一致时芯片内部数字滤波器工作点偏移声音会发毛、发刺。这种情况要先回头核对分频计算不要盲目改音量寄存器。5.3 现象三音调变快或变慢音调变快或变慢最典型的原因是采样率偏差过大。比如播放44.1kHz的音频实际Codec工作在48kHz声音会比正常快整体频率抬高。很多人第一反应是DMA配置错了其实DMA只是搬运数据真正决定采样率的是Codec侧的工作时钟。排查方法是拿频率计量LRCLK的实际频率和理论值对比。理论LRCLK应该等于采样率如果量出来是48kHz而预期是44.1kHz那就是时钟树配置或者MCLK供给错了。这类问题我建议直接在初始化函数里打日志把计算用的mclk_hz、sample_rate、MCLK_Ratio、PLL输出值全部打印出来人工验算一遍比猜寄存器快得多。5.4 几个被忽视的“隐性坑”除了上面三个经典现象还有几个坑很容易被忽略但影响很隐蔽。第一个是电源时序。ES8311的内部模拟电路对DVDD和AVDD的上电顺序比较敏感如果AVDD比DVDD晚太多内部LDO启动异常可能表现为I2C读写正常但没有模拟输出。硬件设计上尽量让DVDD和AVDD同源上电软件上初始化前多给一点延时。第二个是MCLK稳定时间。SoC的音频PLL从使能到稳定需要时间如果Codec配置执行太快MCLK当时可能还没有真正稳定。我习惯在开启MCLK之后加一个2ms到5ms的延时再进行I2C配置实测能明显降低偶发无声的概率。第三个是PLL锁定后的静音处理。刚使能PLL时频率会有一段牵引过程如果此时ADC/DAC已经解除了静音会听到“噗”的一声或者短暂杂音。处理办法是先保持静音等PLL锁定延时结束后再解除。我自己的经验里最耗时的一次排查就是音调轻微偏移症状很轻不仔细听根本发现不了但客户用仪器测频率就是超标。最后定位到是PLL小数分频寄存器少写了一位数导致输出频率偏差了大约300ppm。从那以后我每次改完时钟配置都会先用频率计量一下MCLK和LRCLK的实际频率确认误差在可接受范围后再继续做其他调试。这个习惯省了我非常多返工时间也推荐给你。
返回列表