资讯详情

资讯详情

ES8311 Linux音频驱动开发实战:从ASoC框架到设备树配置与调音踩坑

简介面向嵌入式音频开发者该驱动资源围绕低功耗音频CODEC芯片ES8311聚焦I2S音频数据传输与I2C寄存器控制的典型场景适用于Linux或RTOS环境下的驱动移植、功能验证与问题排查可帮助解决音频子系统中常见的初始化失败、音量调节异常等基础问题。资源包共2个文件包含1个头文件与1个C源文件头文件声明寄存器地址、配置参数及接口函数C文件实现初始化、读写配置、音量调节等核心逻辑。压缩包仅5KB结构精简便于直接阅读或集成到现有工程。目前已有1315人关注学习说明其在嵌入式音频开发中具有一定参考价值。通过阅读这份驱动源码读者能快速掌握ES8311的寄存器映射与控制流程理解I2S/I2C协同工作的驱动框架并能对照实际硬件进行二次修改驱动中的宏定义与注释也有助于减少查阅数据手册的时间从而缩短音频子系统的开发周期。 最近在做一个ARM Linux音视频板卡的音频方案一条I2S总线上要挂CODEC选型阶段没有随大流最后敲定了Everest Semiconductor的ES8311。这颗低功耗音频CODEC芯片自带DSP、信噪比表现不错在智能音箱和便携设备里用得很广但和满大街资料的WM8960不同ES8311的Linux驱动在中小平台上移植时能直接抄的作业不多很多细节要靠自己拿数据手册一点点对。这篇文章就把ES8311驱动程序从框架理解、设备树配置到调音踩坑的完整过程记录下来给后面要在RK、NXP、全志这类Linux平台上用ES8311的工程师做个参考。1. 为什么选ES8311从产品需求到芯片选型的考量1.1 ES8311到底是什么ES8311是Everest Semiconductor凌云半导体推出的一颗低功耗单声道音频CODEC内部集成了一个DAC通道和ADC通道支持模拟麦克风输入、耳机输出也可以通过I2S接口对接主控的数字音频流。控制走I2C音频数据走I2S主从模式均可配置典型工作电压在1.8V到3.3V之间整体功耗非常低播放模式下DAC功耗在毫瓦级别待机模式下更是可以到微安级。为什么很多物联网音频产品选它核心原因是这颗芯片内部带DSP支持自动电平控制ALC、噪声门、动态范围压缩等处理。也就是说主控MCU不需要额外跑复杂的音频算法很多前级处理交给CODEC内部完成。比如做对讲机或者录音笔指望主控在低负载下做自动增益控制不现实用ES8311的ALC功能就能解决问题。同时它支持8kHz到96kHz采样率语音应用常用的16kHz、48kHz都能覆盖。1.2 对比几颗常见CODEC后我的选择选型那会儿我手头对比了几颗芯片TI的TLV320AIC31系列、Cirrus Logic的WM8960、国产的ES8388还有ES8311。简单整理如下芯片通道数内置DSP封装/面积典型功耗资料可获得性ES8311单声道有ALC等QFN-24较小极低数据手册完整开源驱动增多WM8960立体声无QFN-32较大中等资料非常多老牌经典ES8388立体声无QFN-28中低手册清晰驱动较成熟TLV320AIC31立体声部分BGA/QFN中等手册复杂配置繁琐从表格能看到ES8311的单声道定位其实是它的特点不是缺点。很多语音交互、安防监控、楼宇对讲的音频链路本身就是单声道买一颗立体声CODEC回来只占用其中一个通道等于白多付一半成本、白占一片PCB面积。ES8311的内置DSP在几颗芯片里比较突出这让它更适合处理动态范围大的语音信号。当然选ES8311也有代价单声道这颗芯片的寄存器配置比ES8388更繁琐时钟树也更讲究MCLK、BCLK、LRCK之间的关系没理对连声音都出不来。这也是我后面踩坑比较多的原因。你如果做的是立体声多媒体场景老老实实选WM8960或ES8388会更省心但如果是单声道语音链路、又要求低功耗ES8311是一个很有竞争力的选择。2. 驱动不是从零开始ASoC框架下CODEC驱动的工作方式在动手写驱动之前得先理解Linux音频驱动的ASoCALSA System on Chip框架。很多人拿到一颗新CODEC就急着看寄存器手册结果一头扎进去出不来是因为没有把CODEC在ASoC里的位置搞清楚。2.1 machine、platform、codec三层的关系ASoC标准地分为三层machine、platform、codec。platform负责管理CPU侧的DMA、FIFO、I2S控制器等比如RK3568的I2S控制器驱动codec负责管理CODEC芯片的寄存器、音频通路和电源控制machine把platform和codec连接起来描述两者之间用什么样的I2S格式、MCLK频率、音频路由。ES8311的驱动开发主要落在codec层但如果不配合machine层的DTS配置一起看codec驱动写得再完整系统也无法正确握手。比如simple-audio-card通过audio-routing属性告诉内核哪个控件连接到哪个物理端点这直接影响DAPM电源管理是否生效。2.2 DAPM是什么为什么会影响声音开关DAPMDynamic Audio Power Management是ASoC里控制音频链路电源的机制。CODEC内部有很多模块比如DAC、ADC、MIC偏置、耳机放大器等每个模块都有独立的电源开关。DAPM做的事情就是根据当前的音频路径只打开链路需要的模块自动关闭无关模块。ES8311的驱动里每个音频模块会被注册成一个widgetwidget之间通过route路由关联。比如你播放音乐DAPM会沿着“播放流 - DAC - 耳机放大器 - 耳机输出”的路径打开这条路径上的所有电源。DAPM的开关状态可以通过tinymix工具查看比如播放时tinymix DAC的状态应该是开启。这个机制带来的好处是省电但也带来一个调试坑如果没有正确配置audio-routing或者某个widget名称写错了DAPM就不会按你设想的路径打开电源结果就是寄存器配置正确但依然无声。这个坑后面细说。2.3 新旧驱动APIcomponent_driver和dai_driver当前Linux内核主流版本中老的snd_soc_codec_driver已经逐步被snd_soc_component_driver取代。ES8311的驱动需要注册两部分snd_soc_component_driver负责probe、suspend、resume、控件注册和bias level管理等snd_soc_dai_driver描述ES8311的DAI能力包括支持的采样率、格式、通道数和DAI操作集。在I2C探测函数里调用devm_snd_soc_register_component一次性把component和dai注册进去。这里有个容易踩的小坑devm_snd_soc_register_component的最后一个参数是需要注册的DAI数量如果写错为0系统不会报任何编译错误但在运行时ALSA会找不到DAI导致sound card初始化失败。我见过不止一个同事在这个参数上栽跟头。3. 让ES8311在RK3568上发出声音的完整流程下面以RK3568平台为例走一遍从硬件确认到声音输出的完整流程。这套流程同样适用于其他Linux主控平台。3.1 硬件连接与I2C地址确认ES8311的I2C地址由芯片的ADR引脚电平决定通常7位地址为0x18ADR接低电平8位写地址为0x30。拿到板子第一步不是写驱动而是用万用表确认ADR引脚电平再用I2C工具扫描确认地址。确认地址的方法很简单在系统起来后执行i2cdetect -y 1如果地址扫描出来是0x187位形式说明芯片在I2C总线上已经正常应答。如果扫描不到先查供电、I2C上拉电阻和ADR引脚不要急着怀疑驱动。另外注意MCLK的连接。ES8311需要主控提供MCLK常见配置是12.288MHz对应的fs48kHzMCLK256*fs。如果板子上MCLK没连或者接了但主控的时钟树没有输出后面无论怎么配置寄存器都没声音。3.2 设备树里的codec节点和sound节点RK3568的设备树节点参考如下写法i2c1 { clock-frequency 100000; status okay; es8311: es831118 { compatible everest,es8311; reg 0x18; pinctrl-names default; pinctrl-0 es8311_pins; clocks cru MCLK_ES8311; clock-names mclk; assigned-clocks cru MCLK_ES8311; assigned-clock-rates 12288000; #sound-dai-cells 0; status okay; }; }; sound { compatible simple-audio-card; simple-audio-card,name rk-es8311; simple-audio-card,format i2s; simple-audio-card,mclk-fs 256; simple-audio-card,cpu { sound-dai i2s1_8ch; }; simple-audio-card,codec { sound-dai es8311; }; };有几个关键点容易出错assigned-clock-rates必须和实际MCLK一致这里设成12288000simple-audio-card,mclk-fs 256表示MCLK是采样率的256倍48kHz采样率对应12.288MHz这个参数要和codec的时钟配置对得上#sound-dai-cells必须为0否则simple-audio-card解析DAI时会失败。设备树里的reg 0x18对应7位I2C地址。如果板子的ADR引脚接了高电平地址会变化这里要相应修改。3.3 驱动代码的骨架I2C匹配、regmap和component注册ES8311的驱动主体是I2C client驱动。先定义of_match_table和i2c_device_idstatic const struct of_device_id es8311_of_match[] { { .compatible everest,es8311 }, { } }; MODULE_DEVICE_TABLE(of, es8311_of_match); static const struct i2c_device_id es8311_i2c_id[] { { es8311, 0 }, { } }; MODULE_DEVICE_TABLE(i2c, es8311_i2c_id);regmap配置如下static const struct regmap_config es8311_regmap_config { .reg_bits 8, .val_bits 8, .max_register 0x3F, /* 按芯片手册实际寄存器数量填写 */ .cache_type REGCACHE_RBTREE, };I2C probe函数里做的事情很固定解析时钟、创建regmap、初始化寄存器、最后注册ASoC componentstatic int es8311_i2c_probe(struct i2c_client *client) { struct device *dev client-dev; struct regmap *regmap; int ret; regmap devm_regmap_init_i2c(client, es8311_regmap_config); if (IS_ERR(regmap)) return PTR_ERR(regmap); /* 获取mclk时钟并准备 */ es8311-mclk devm_clk_get(dev, mclk); if (!IS_ERR(es8311-mclk)) clk_prepare_enable(es8311-mclk); /* 复位和初始化寄存器序列 */ return devm_snd_soc_register_component(dev, soc_component_dev_es8311, es8311_dai, 1); }注意devm_snd_soc_register_component最后一个参数是DAI数量这里必须是1因为ES8311只有一条DAI链路HIFI同时支持播放和录音。component驱动的核心结构体static const struct snd_soc_component_driver soc_component_dev_es8311 { .probe es8311_component_probe, .resume es8311_component_resume, .suspend es8311_component_suspend, .set_bias_level es8311_set_bias_level, .controls es8311_snd_controls, .num_controls ARRAY_SIZE(es8311_snd_controls), .dapm_widgets es8311_dapm_widgets, .num_dapm_widgets ARRAY_SIZE(es8311_dapm_widgets), .dapm_routes es8311_dapm_routes, .num_dapm_routes ARRAY_SIZE(es8311_dapm_routes), };DAI driver描述音频接口能力static const struct snd_soc_dai_driver es8311_dai { .name es8311-hifi, .playback { .stream_name HiFi Playback, .channels_min 1, .channels_max 2, .rates SNDRV_PCM_RATE_8000_96000, .formats SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, }, .capture { .stream_name HiFi Capture, .channels_min 1, .channels_max 2, .rates SNDRV_PCM_RATE_8000_96000, .formats SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, }, .ops es8311_dai_ops, };这里把playback和capture的channels_max都设为2因为即使信号是单声道I2S链路上也可能以立体声格式传输多留一些余地没问题。3.4 寄存器初始化顺序复位、时钟、模拟通路初始化顺序对ES8311来说很重要我的习惯是严格按照数据手册推荐的流程走写复位寄存器让芯片回到默认状态配置时钟MUX和PLL相关寄存器配置I2S接口格式I2S、LJ、RJ、DSP模式配置模拟输入输出通路配置DAC/ADC音量、ALC参数。static int es8311_init(struct snd_soc_component *component) { struct regmap *regmap component-regmap; regmap_write(regmap, 0x00, 0x1F); /* 软件复位 */ msleep(20); /* 时钟相关选择MCLK作为时钟源根据12.288MHz MCLK配置CLK DIVIDER */ regmap_write(regmap, 0x01, 0x19); /* I2S格式配置标准I2S、从模式、16bit */ regmap_write(regmap, 0x03, 0x02); /* 模拟DAC通路使能、耳机输出 */ regmap_write(regmap, 0x0F, 0x00); /* 音量初始值 */ regmap_write(regmap, 0x40, 0x3F); /* DAC音量可按手册映射 */ regmap_write(regmap, 0x41, 0x3F); return 0; }这里寄存器地址和值仅供参考不同批次、不同手册版本的寄存器偏移可能不同千万不要直接照抄网上初始化数组务必以官方最新数据手册为准。我踩过的坑是某平台上照搬了另一个主控的初始化序列结果ALC开关的bit位颠倒录音音量时而爆音时而微弱。3.5 播放与录音的验证命令系统起来后用ALSA工具验证# 查看声卡设备 aplay -l # 播放一个wav先用plughw自动转换格式排除格式配置问题 aplay -D plughw:0,0 test.wav # 直接以硬件参数播放 aplay -D hw:0,0 -f S16_LE -r 48000 -c 2 test.wav # 录制音频 arecord -D hw:0,0 -f S16_LE -r 16000 -c 1 rec.wav如果aplay正常返回且耳机里听到声音说明ES8311的驱动主链路已经通了。plughw和hw的区别在于plughw会做采样率和格式转换hw要求参数严格匹配。建议先用plughw排除应用层参数不匹配的问题再用hw测试实际能力。4. 调试实录无声、单声道和底噪问题的排查链路这段是我在ES8311调试过程中真实遇到过的三个问题把排查过程完整写出来比直接给结论更有参考价值。4.1 一帧不振MCLK没有送到CODEC现象aplay执行成功但示波器看I2S数据线有波形耳机里就是没有任何声音。排查链路用i2cdetect确认I2C正常排除了寄存器无法读写的问题用示波器量ES8311的MCLK引脚发现完全没有波形查DTS里clocks和assigned-clock-rates配置发现MCLK引脚复用pinmux没有配置在pinctrl里加上对应引脚的iomux设置MCLK信号出来了声音立刻恢复。这个问题的根因在于RK平台的I2S控制器输出BCLK/LRCK但MCLK由独立的时钟引脚输出引脚复用没配置等于时钟树断了一截。很多看起来是CODEC寄存器配置不对的问题根源却在外部时钟。提示拿到板子先量MCLK、BCLK、LRCK三个信号的频率是否正常这步检查能省下大量无用功。MCLK频率一定要和采样率对应常见组合是12.288MHz配48kHz或96kHz16.384MHz配32kHz或64kHz。4.2 只有一边响I2S格式和DAC数据位宽的坑现象播放立体声测试音频只有左声道有声右声道完全无声。排查链路开始时怀疑是耳机座接触问题换耳机仍然如此用speaker-test -c 2 -t sine分别测试左右声道确认左声道正常、右声道无输出查DTS里simple-audio-card,format配置发现是按标准I2S配置但ES8311的I2S接口寄存器里设置成了左对齐LJ模式将codec的I2S格式配置改为标准I2S左右声道都正常了。这个问题的常见原因有两个一是DTS里的I2S格式和codec寄存器里的I2S格式不一致二是主控的I2S控制器配置成了DSP模式而codec配置的是I2S模式。I2S、LJ、RJ、DSP这些模式本质上都是BCLK和LRCK边沿的采样时序差异任何一端配错了都是灾难性的。排查时建议用左右声道相差较大的测试音而不是放音乐音乐内容左右声道相似度高很难听出声道错位。4.3 录音底噪大模拟输入增益和偏置配置现象录音功能正常但回放时底噪明显信噪比完全达不到数据手册标称值。排查链路先用arecord -D hw:0,0 -f S16_LE -r 16000 -c 1 rec.wav录一段静音用audacity看波形幅度发现底噪波形幅度大约在-60dBFS左右偏高检查MICBIAS设置确认MIC偏置电压是否匹配麦克风规格调整模拟输入增益把ADC PGA增益从30dB降下来同时打开ES8311的ALC功能设置合适的目标电平。经过调整后底噪降到了-80dBFS以下。这里关键的一点是麦克风的增益不是越高越好模拟PGA增益过高会把电源噪声和PCB噪声一起放大。ES8311内置DSP的ALC功能可以自动适应大动态范围的输入信号比手动固定增益要稳得多。4.4 I2C通信异常导致codec注册失败现象i2cdetect -y 1显示0x18地址有设备但内核日志报Failed to probe component es8311声卡注册失败。排查链路查内核日志dmesg | grep es8311看到regmap读取失败用逻辑分析仪抓I2C波形发现ACK响应时好时坏检查I2C总线频率发现I2C被配置到了400kHz但ES8311在板上I2C上拉电阻偏大400kHz下信号上升沿不够陡峭将I2C频率降到100kHz问题消失。ES8311对I2C频率没有特别苛刻的要求但高速模式对PCB布线、上拉电阻更敏感。如果你的板子I2C走线较长或者上拉电阻用了10kΩ这种偏大的值建议老老实实跑100kHz稳定压倒一切。5. 从能响到好用音量曲线、自动增益和低功耗的调节细节驱动跑通、声音能出来只完成了70%剩下的工作是把音质、音量和功耗调到产品可用的状态。5.1 DAC音量寄存器不是线性百分比很多初次接触CODEC的工程师会把音量寄存器当作线性百分比来用这在ES8311上行不通。DAC音量寄存器的映射是dB值通常是1dB一个步进范围大约从-95.5dB到12dB。这意味着用户界面的音量滑条如果是0到100你需要做一条对数映射曲线而不是直接线性映射。直接线性映射的结果是滑条在0-50之间音量变化非常剧烈50-100之间几乎听不出变化用户会明确反馈音量调节体验不对。我的做法是将ALSA的SOC_DOUBLE_R_SX_TLV控件暴露给上层用标准的TLV机制让上层直接使用dB值。应用层通过alsa-lib的snd_mixer_selem_set_playback_dB设置音量时内核会自动做线性到dB的换算不需要自己维护映射关系。5.2 ALC/DSP参数怎么调ES8311的ALC参数设置是这类带DSP芯片的特色。ALC的核心参数有三个目标电平期望输出信号的平均电平最大增益和最小增益ALC允许调整的增益范围启动时间和释放时间增益变化的速度。清理语音输入时我会把目标电平设置在-12dBFS左右最大增益和最小增益落在-6dB到30dB之间启动时间设置为几百毫秒释放时间稍长。这样设置的好处是说话音量小时自动提高增益说话音量大时迅速压下来不会出现明显的忽大忽小感。如果不需要ALC也务必把增益固定在合适的值。ES8311寄存器里ALC的使能位和常规增益设置位置接近经常有人本意是固定增益结果误开了ALC导致录音音量自己飘。5.3 低功耗待机时的时钟和电源控制ES8311的优势在低功耗但在驱动里不把DAPM配好这颗芯片的低功耗优势就发挥不出来。播放和录音时DAC/ADC以及相关模拟模块要供电MCLK要正常运行待机时DAC/ADC断电MCLK可以继续或者由主控关掉。在驱动里主要做两件事在set_bias_level回调中处理不同bias level下的寄存器配置比如SND_SOC_BIAS_STANDBY时关闭模拟输出放大器SND_SOC_BIAS_OFF时尽量关闭所有不需要的模块在suspend回调里保存必要寄存器在resume时恢复。ES8311在断电后寄存器内容丢失如果系统支持runtime PM恢复时一定要重新执行初始化序列否则会出现在睡眠唤醒后声卡设备还在但不出声的情况。Kernel里常见的一个边界问题assigned-clock-rates和实际使用采样率不匹配。比如系统启动时MCLK按12.288MHz配置但播放44.1kHz音频时通常需要11.2896MHz的MCLK。ES8311内部PLL可以处理一部分非标准MCLK场景但更可靠的方案是在DAI的hw_params回调里动态调整MCLK频率或者启用ES8311的PLL模式让芯片自己生成所需的时钟路径。我个人在这颗芯片上做得最多的一步调试就是播放不同采样率文件时用示波器同时看MCLK和LRCK的关系。ES8311在PLL模式下能适配的MCLK范围宽一些但PLL配置寄存器需要结合目标采样率计算每次切换采样率都重新配置PLL参数性能和稳定性都能接受。如果你的产品需要频繁切换采样率建议把这个逻辑提前设计进去而不是在驱动已经跑起来后再补。最后分享一个经验试产阶段如果出现偶发性无声音优先怀疑时序问题而不是驱动逻辑问题。ES8311的软件复位之后需要等待足够时间再继续写时钟寄存器如果复位后立即配置芯片内部时钟树还没稳定后续寄存器写入可能被忽略。我在原厂建议的20ms基础上又加了一些余量综合下来系统更稳。这类CODEC不会像复杂SoC那样有那么多玄学问题只要把时钟、电源、复位、I2C时序一一理顺驱动就能长期稳定运行。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →