ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

24bit/96kHz音频采集系统设计:GC1808与STM32实战

24bit/96kHz音频采集系统设计:GC1808与STM32实战 音频采集这件事听起来不难真正把性能做出来才知道坑有多深。前两年我做一个环境声学监测终端一开始图省事直接用SoC自带的音频codec结果开会演示时被现场扩音器的底噪搞得满脸黑线。后来整个采集链路改用独立ADC问题才彻底解决。也是那次项目我第一次把GC1808这颗器件吃透了24bit量化、96kHz采样率、内置Δ-Σ调制器和数字抽取滤波器直接输出标准I2S数据流而且价格友好到可以拿它当量产物料。这篇文章不是datasheet的机器翻译是我从选型、原理图设计、PCB布板到STM32驱动调试完整走一遍之后的实操记录。如果你手头正好在建音频采集板或者正准备从普通codec往高分辨率方案迁移按这套流程走下来能少走很多弯路。1. 项目定位与设计思路1.1 先搞清楚我们要一个什么样的ADC音频采集产品的需求其实比大多数人想象的要具体。拿我做的噪声监测终端来说它要能分辨出安静楼宇里一台设备从待机切换到运行的细微声音变化对ADC的基本要求就摆在那儿了量化位数至少24bit否则低幅值信号的细节全被量化噪声吃掉了采样率要覆盖48kHz和96kHz既能做常规语音通讯也能做高解析录音数字输出接口必须简单通用最好是I2S这样才能无缝对接MCU、DSP或者FPGA成本要压得住毕竟消费类和工业类产品一生产就是成千上万片。GC1808的基础规格24bit/96kHz正好卡在这个需求点上。它属于Δ-ΣDelta-Sigma架构ADC核心思路是先把输入信号高速过采样成一位或者几位的数据流再利用负反馈把量化噪声推到高频段最后由内置数字滤波器把这些带外噪声滤掉输出干净的PCM数据。这样做的好处非常直接外部模拟电路可以做得非常简单不再需要高阶抗混叠滤波器一个一阶RC低通就能应付采样时钟带来的射频干扰。这也是它和SAR型ADC最大的区别。SAR逐次逼近结构在中等速度、多通道场景里很好用但要达到录音级动态范围内部电阻和电容匹配精度必须极其苛刻成本和功耗都不划算。Δ-Σ是拿速度换分辨率用成熟的数字信号处理手段去弥补模拟电路的不足所以今天市面上专业音频ADC百分之九十九都是它。1.2 Δ-Σ到底在做什么打个比方很多人初学Δ-Σ时被各种数学公式劝退我习惯用一个厨房里的例子你要称一小撮盐手头只有一把最小刻度是1克的天平。SAR的做法是直接称读出来可能不是0就是1克误差巨大。Δ-Σ的做法是准备一个高精度砝码反复加上去、拿下来观察天平的状态变化然后把上百次观察结果综合起来——通过一种被称为“噪声整形”的反馈机制让天平读数的误差集中到高频成分再用数字低通把高频误差平均掉最后得到的有效分辨率远高于天平本身的刻度。放在GC1808内部就是模拟输入先经过采样保持电路以过采样率比如64倍或128倍Fs高速采样调制器把采样值和反馈值做差通过积分器累积再由量化器输出低位数码流数字抽取滤波器对数码流做低通滤波和抽取把高频噪声去掉输出标准的24bit PCM数据。这套链路相当于把一个“物理精度不高但反应很快”的前端变成一个“等效精度极高”的数字化仪表。工程上最直观的收益是模拟输入链路不再需要复杂的滤波器、精密电阻网络PCB面积和BOM成本都下来了。1.3 什么样的产品适合选它GC1808这种高性价比音频ADC最适合下面这几种场景噪声监测与声学测量24bit动态范围意味着能同时处理极低的背景噪声和相对较高的突发声不会因为量化位不足出现“丢细节”的问题语音前端采集做远场拾音、语音识别麦克风阵列时I2S输出直接给MCU或DSP做波束成形链路简单高解析录音设备便携录音机、USB声卡里需要一颗独立ADC保证转换质量而不是把codec和ADC混在一起高校实验和DIY项目因为价格低、外围电路少用来做示波器前的模拟信号数字化也非常合适。对初学者来说这颗芯片还有个大优点数据手册里的典型电路只要照抄基本就能跑起来。不像某些专用ADC动不动要求外接精密基准、外部时钟锁相环GC1808的集成度让它成了“低门槛高性能”的典型代表。2. 核心参数与信号链路拆解2.1 采样率、主时钟和I2S时序的关系调I2S类的ADC第一步就是把时钟关系搞清楚这个搞不明白后面全是玄学。I2S总线里三条时钟线各有各的职责LRCK也叫帧时钟或左右声道时钟频率就等于采样率Fs。高电平输出右声道数据低电平输出左声道数据BCLK位时钟一个脉冲对应一个数据位。它和采样率、位深、声道数的关系是BCLK ≥ Fs × 2 × 位深。以96kHz、24bit立体声为例理论最低是4.608MHz但为了对齐左右声道边界习惯上直接用64 × Fs也就是6.144MHzMCLK主时钟一般是256 × Fs96kHz采样时就是24.576MHz也有一部分模式用128 × Fs12.288MHz。采样率FsMCLK256×FsBCLK64×FsLRCK48kHz12.288MHz3.072MHz48kHz96kHz24.576MHz6.144MHz96kHz这里要特别提醒一句如果MCU侧的I2S时钟源和ADC侧的MCLK来自不同的振荡器时间长了会有累计漂移表现出来就是周期性爆音或丢字。后面第5节我会专门讲这个问题。GC1808支持主/从两种模式。主模式下芯片自己产生BCLK和LRCKMCU只是被动接收接线简单但多个设备同步时显得不灵活从模式下MCU提供BCLK和LRCK芯片作为从机跟随时钟。实际项目里我更推荐从模式因为系统里往往还有其它音频设备统一由一颗主控产生时钟同步问题最好解决。2.2 满幅输入范围与模拟前端怎么搭音频ADC的满幅输入通常按差分信号标称常见值是2Vrms左右对应专业音频设备的4dBu标准。如果你打算接麦克风或者压电传感器前置放大器的输出摆幅必须和ADC的满幅范围匹配不然容易出现两种情况信号太大直接削波信号太小白白浪费动态范围。前级运放的选择有个简单经验把最大期望输入信号放大到ADC满幅的80%到90%。比如麦克风输出是100mVrms想让它在ADC满幅2Vrms附近增益做到约20倍26dB就差不多而且留出几个dB的余量防止现场突发高声压。模拟输入前端我习惯放一个RC低通位置紧贴ADC输入引脚。按常用的10Ω 1000pF组合截止频率约16MHz主要作用是过滤高频杂散干扰和采样开关的电荷注入毛刺而不是滤音频信号。真正决定音频带宽的是芯片内部的数字滤波器所以外部RC的取值不需要很严格关键是靠近引脚、走线要短。输入耦合电容也不能随便拿普通陶瓷电容了事。放在音频通路里的耦合电容我踩过MLCC压电效应的坑——陶瓷介质在振动和电压变化时会产生微小的电信号混进音频里变成“咔嗒”声。这个位置宁可选择薄膜电容或者高品质电解电容容值通常1μF到10μF都行下限取决于你的最低目标频率。2.3 电源与参考电压决定底噪的底线这颗芯片虽然集成度高但电源处理不好性能照样腰斩。我调试时发现一个规律绝大多数“底噪偏大”的问题最后都指向电源而非ADC本身。电源设计上我的做法是模拟电源AVDD单独走一个低噪声LDO不要直接从数字3.3V拉出来。低噪声LDO可以用典型产品如TPS7A20系列或同级输出噪声指标尽量低于10μVrms数字电源DVDD可以共用系统3.3V但必须星形连接绝不能和模拟电源串在一起每个电源引脚旁边放0.1μF 10μF的组合去耦电容并且紧贴引脚过孔直落到底层地平面环路面积越小越好参考电压引脚的处理更加讲究滤波电容用低ESR的X7R 0.1μF并联一个4.7μF钽电容或者高容值MLCC把这个引脚的纹波压到最小。模拟地与数字地的连接也是老生常谈但总有人翻车。正确做法是把AGND和DGND在芯片下方的焊盘处单点连接而不是在两个地平面之间密密麻麻打一堆过孔。单点连接的意思是让数字回流电流和模拟回流电流只在这一点交汇避免数字噪声沿着地平面扩散到模拟区域。3. 硬件电路实操一块96kHz采集板是怎么画出来的3.1 原理图按四块模块搭建我习惯把采集板原理图拆成四个模块每个模块单独评审、单独调试这样问题出来容易定位。电源模块输入5V经两级LDO分别得到3.3V模拟和3.3V数字模拟部分再加一级LC滤波比如10μH电感加10μF电容进一步衰减开关电源带入的纹波。时钟模块有源晶振24.576MHz输出接MCLK引脚。这里要注意晶振选型普通晶振的抖动指标太差的话会直接影响ADC的信噪比和边带杂散。最好选用相位噪声在1kHz偏移处低于-120dBc/Hz左右的晶振或者对成本敏感的用高精度有源晶振。模拟输入模块差分信号先经过静电防护和RC滤波再进入ADC输入引脚。如果信号源是单端可以配置芯片的输入模式让单端信号接到正输入端反相输入端通过电容接地这样能获得比直接单端连接更好的共模抑制效果。数字接口模块SDATA接MCU串行数据输入LRCK接帧同步引脚BCLK接位时钟引脚MCLK接主时钟。控制总线如果是I2C记得上拉电阻如果是SPI注意片选逻辑。原理图模块化带来的直接好处是后面调试出现问题时我可以拿示波器逐模块判断“信号是否进来”“时钟是否稳定”“数据有没有出来”不用对着整块板子瞎猜。3.2 与STM32的典型连接方式我们项目用的主控是STM32F407它内置的I2S外设说穿了是SPI接口扩展出来的音频模式正好能满足需求。GC1808配置为从模式时接线如下GC1808引脚STM32引脚说明MCLK主控输出MCLK的引脚视I2S时钟源选择提供主时钟BCLKCKI2S位时钟由STM32产生LRCKWS帧同步由STM32产生SDATASD数据输入芯片输出到STM32I2C_SCLPB6寄存器配置I2C_SDAPB7寄存器配置如果芯片配置为主模式BCLK和LRCK就反过来由芯片输出给STM32。但从我系统同步的角度还是更愿意让STM32做I2S主设备这样后续如果还要加别的音频芯片大家共用一个时钟域。3.3 PCB Layout的三个硬性要求这块板的Layout我改过两版总结出三条硬性要求概括成一句话就是模拟输入要安静、时钟走线要干净、退耦电容要够近。第一模拟输入走线避开MCLK和一切数字信号。MCLK是方波频率高、谐波多一旦和模拟输入线并行哪怕一小段串进来的谐波就会成为音频频段内的干扰。实在避不开就加大地间距或者用底层地平面包一圈形成屏蔽。第二MCLK走线短而直。我的经验是MCLK线长超过3cm就要考虑做包地处理同时避免打过孔换层最好从晶振出来直接到ADC引脚中途不要分叉给其它器件。等长不是这里的主要矛盾干净才是。第三去耦电容的放置位置比容值更重要。0.1μF电容距离电源引脚不能超过2mm过孔直连引脚底部地平面形成最小回流环路。数字侧的去耦电容和数据线之间也不要共享过孔防止数字电流串进模拟环路。4. STM32侧I2S采集与数据处理4.1 I2S外设初始化STM32的I2S外设看起来像SPI但配置逻辑要按音频的思维方式来。我用的是HAL库关键参数如下hi2s2.Instance SPI2; hi2s2.Init.Mode I2S_MODE_MASTER_RX; // 主设备接收模式 hi2s2.Init.Standard I2S_STANDARD_PHILIPS; // 标准I2SPhilips格式 hi2s2.Init.DataFormat I2S_DATA_24BIT; // 24位数据 hi2s2.Init.MCLKOutput I2S_MCLKOUTPUT_ENABLE; // 使能MCLK输出 hi2s2.Init.AudioFreq I2S_AUDIOFREQ_96K; // 采样率96kHz hi2s2.Init.CPOL I2S_CPOL_LOW; // 时钟极性 hi2s2.Init.ClockSource I2S_CLOCK_SYSCLK; // 时钟源选择 if (HAL_I2S_Init(hi2s2) ! HAL_OK) { Error_Handler(); }这里有个新手容易忽略的点STM32F407内部PLL产生I2S时钟时需要根据MCLK输出频率正确配置PLLI2SR分频系数。如果你发现实测采样率不是96kHz而是偏离了百分之几先查PLL分频配置系统主频和PLL参数不匹配时总会这样。数据格式务必选I2S_DATA_24BIT而不是16BIT否则芯片输出24bit数据的高8位会被截掉动态范围直接掉到16bit级别。之前就有朋友拿着这颗芯片说“声音总是闷闷的”最后发现是F407这边设成了16bit格式低8位丢了——这属于低级失误但非常容易发生。4.2 DMA双缓冲接收别用轮询音频数据流是连续不断的用轮询方式接收I2S数据会频繁打断主循环CPU占用率飙高不说还容易丢数据。正确姿势是启用DMA循环模式配合半传输和全传输两个中断来做双缓冲。#define BUFFER_SIZE 4096 uint32_t dma_buf[BUFFER_SIZE]; uint8_t dma_buf_ready 0; uint8_t dma_buf_id 0; HAL_I2S_Receive_DMA(hi2s2, dma_buf, BUFFER_SIZE);DMA配置好之后接收数据会自动存入数组。当缓冲区存满一半时触发半传输回调存满时触发全传输回调两个回调都置位标志。主循环检测标志后取走刚填满的那半块数据同时更新指向另一半的索引。void HAL_I2S_RxHalfCpltCallback(I2S_HandleTypeDef *hi2s) { if (hi2s hi2s2) { dma_buf_ready 1; dma_buf_id 0; } } void HAL_I2S_RxCpltCallback(I2S_HandleTypeDef *hi2s) { if (hi2s hi2s2) { dma_buf_ready 1; dma_buf_id 1; } }主循环里检查dma_buf_ready后处理对应数据块处理完清零标志。双缓冲能保证在数据处理期间DMA还能持续往另一块缓冲区写数据不会出现“新旧数据打架”的问题。4.3 把原始24bit数据转成可用的浮点值从DMA缓冲区里拿到的其实是32位容器存放的24bit数据但这个24bit并不是直接放在低24位就能用的。以I2S标准格式传输24bit数据时数据左对齐存储在32位字里低8位无效符号位在最高位。所以正确的转换是这样float sample_to_float(uint32_t raw_data) { int32_t signed_val (int32_t)(raw_data 8) 8; // 左移8位取符号右移还原 return (float)signed_val * (1.0f / 8388608.0f); // 2^23 8388608归一化到[-1.0, 1.0] }这一步做完之后数据就是标准的浮点PCM流后面无论是做FFT分析、音量调节还是文件存储都直接基于这个浮点值。如果跳过归一化直接用原始整数后续处理的增益、阈值这些参数就都得跟着位深走非常容易被绕晕。4.4 C语言实现ADC值滤波附代码音频采集系统里滤波分为两种一种是为了平滑静音底噪一种是为了剔除突发毛刺。我根据热词里大家常见的需求整理了三个最常用的滤波函数。滑动平均滤波适合平滑连续、小幅度的抖动。原理是维护一个长度为N的窗口每来一个新采样就入队窗口整体的平均值就是输出。N取值不能太大太大信号细节就被抹平了用在采样率96kHz的音频里8点平均对应约12kHz的低通效果。#define FILTER_LEN 8 float filter_buf[FILTER_LEN] {0}; uint8_t filter_index 0; float sliding_average_filter(float new_sample) { filter_buf[filter_index] new_sample; filter_index (filter_index 1) % FILTER_LEN; float sum 0.0f; for (uint8_t i 0; i FILTER_LEN; i) { sum filter_buf[i]; } return sum / FILTER_LEN; }中值滤波专门对付脉冲尖峰比如电磁环境干扰产生的瞬间跳变。原理是对连续N个采样排序取中间值输出。N常用3或5音频场景下不要取到9以上否则瞬态响应会明显变差。#define MEDIAN_WINDOW 5 float median_filter(float new_sample) { static float hist[MEDIAN_WINDOW]; float sort_buf[MEDIAN_WINDOW]; for (uint8_t i MEDIAN_WINDOW - 1; i 0; i--) { hist[i] hist[i - 1]; } hist[0] new_sample; memcpy(sort_buf, hist, sizeof(sort_buf)); // 简单排序 for (uint8_t i 0; i MEDIAN_WINDOW - 1; i) { for (uint8_t j i 1; j MEDIAN_WINDOW; j) { if (sort_buf[j] sort_buf[i]) { float tmp sort_buf[i]; sort_buf[i] sort_buf[j]; sort_buf[j] tmp; } } } return sort_buf[MEDIAN_WINDOW / 2]; }一阶IIR低通滤波需要有一定平滑频响的场合用。公式是y[n] α * x[n] (1 - α) * y[n-1]α越小越平滑、响应越慢。采样率96kHz下想要大约1kHz的平滑效果α可以取0.01到0.02之间。float alpha 0.015f; float iir_prev_out 0.0f; float iir_lowpass_filter(float new_sample) { iir_prev_out alpha * new_sample (1.0f - alpha) * iir_prev_out; return iir_prev_out; }三种滤波器用哪个、混不混用取决于你在处理什么信号。如果只是消除ADC自身的随机抖动滑动平均足够如果信号链路可能受脉冲干扰先过中值再平滑更稳如果要做频带限制拿到平滑频谱IIR低通是常规路数。5. 常见问题与排查技巧实录5.1 录出来的“安静”信号里底噪明显这是碰到最多的一个问题。排查思路按顺序走先用示波器看模拟电源引脚的纹波。如果纹波超过5mVrms问题基本就在电源上换更低噪声的LDO或者加一级LC滤波把ADC模拟输入引脚用0Ω电阻断开让输入悬空或通过电容短接到地录一段静音数据。如果底噪还是大问题在ADC自身或数字电源如果底噪变小问题在输入前端或前级运放检查地平面。模拟区域和数字区域如果被分割成两块且中间没有单点连接回流电流会走很长的路径形成地环路底噪会明显恶化。我项目里的第一版板子就栽在第三步。当时模拟地和数字地之间打了十几个过孔想着“多点共地更稳”结果底噪反而比分开时更大。后来改回在ADC芯片正下方单点连接底噪降低了约6dB。多点共地听着合理实际会造成数字回流噪声扩散到模拟区域。5.2 出现50Hz工频“嗡嗡”声如果你录出来的信号里有个持续的低频嗡嗡声那基本和ADC无关而是外部电磁耦合进来的工频干扰。排查方向检查测试环境的设备地、机壳地和信号源地是不是形成了环路尝试断开信号源的接地端用电池供电的前级试试模拟输入线换成屏蔽线屏蔽层单端接地。这种问题在实验室里常见的原因是使用了非平衡输入接口而信号源又是大电流设备地电位差在屏蔽层上产生工频电流。有条件的话把前级和ADC之间改成差分连接整套链路的抗干扰能力会强很多。5.3 周期性爆音、POP声和采样率失配之前有个同事拿着同样的方案做多机同步采集板子单独跑都正常连在一起偶尔爆音。查了三天最后定位到时钟GC1808从模式下由STM32提供BCLK和LRCK但另外一块板子用了自己的晶振做ADC主时钟两台设备虽然有标称相同的采样率实际频率却差了几十个ppm。长时间运行后数据缓冲会偶尔溢出或者下溢表现就是爆音。解决办法就是让整个系统共用同一个时钟源。如果是多板卡同步可以把主控的MCLK通过时钟buffer分发到各ADC如果条件不允许就用支持异步采样的中间方案比如在MCU侧挂一颗异步采样率转换器从根上把时钟域差异抹掉。5.4 大信号失真、小信号却正常这个问题几乎都出在输入增益配置上。前级运放增益太高输入信号超过ADC满幅范围的头几个dBΔ-Σ调制器会被“压”到过载区输出信号出现明显非线性失真。小信号正常是因为没有触及满幅。处理办法用示波器或万用表实测前级输出最大峰峰值反推所需增益然后留3dB左右的headroom。如果输入源本身就有很大动态范围还可以在数字侧做动态范围压缩而不是一味调低模拟增益。5.5 寄存器配置与默认状态GC1808一般支持I2C接口配置部分工作模式比如输入格式选择、数字滤波特性、是否启用去加重等。头一回上电建议先不写任何寄存器让芯片跑默认状态确认I2S格式能正常出数据再逐步改寄存器验证各功能。这样即使配置出问题也能快速恢复对比而不是在多个可能因素里猜。调试寄存器时记得把I2C时序对准芯片的时钟要求。如果I2C速率太高、时序不满足datasheet规格偶尔会出现读回值和写进去对不上的情况那个问题特别隐蔽——表面上“写成功了”实际芯片没收到。6. 低成本方案和高性能方案怎么选6.1 和同级及专业级产品的差距在哪选型时我把常见方案分成三档GC1808在中间这档正好是“规格够用、价格友好、外围省心”。但也要客观看待它和更贵的专业音频ADC之间确实存在规格差距。方案档位典型规格动态范围参考外围复杂度适合场景集成codecSoC自带24bit/48kHz90dB上下低语音通话、普通录音独立高性价比ADCGC1808这类24bit/96kHz100dB级中低噪声监测、中级音频采集专业音频ADC24bit/192kHz110dB中高录音室、声学实验室和专业级产品比GC1808的采样率上限低一些动态范围也差一个档次。但对大多数嵌入式产品和工业采集场景来说100dB级动态范围已经能覆盖从很安静到很吵的巨大跨度96kHz采样也保证了对20kHz以上超声边缘成分的留余。真正瓶颈往往在麦克风、前级运放和结构声学上而不是这颗ADC。6.2 省钱的本质是省在外围电路高性价比不等于单纯看芯片单价要看整个采集链路的综合成本。GC1808内置数字抽取滤波器和抗混叠处理省掉了外部高阶滤波器、精密电阻匹配网络这些昂贵器件。同时它对时钟的要求不像顶级ADC那么苛刻普通有源晶振就能上BOM和PCB面积双省。我算过一笔账如果做一个双通道96kHz的采集模块用这颗芯片的整体方案成本芯片晶振LDO无源器件大约只有专业级方案的三分之一到二分之一而性能指标已经满足绝大多数监测类产品的设计要求。在量大的产品里这个差距就是实打实的利润。6.3 哪些情况不建议硬选它再好的方案也有边界。以下情况建议升级方案或者换芯片需要192kHz甚至更高采样率用来做超声波或超高解析音频这颗芯片规格就不够了需要四通道以上的同步采集应该选多通道TDM接口的ADC而不是用多颗芯片拼需要DSD等特殊数字音频格式那是另一类转换器的活儿对动态范围有硬指标要求比如专业录音棚标准超过110dB必须上更贵的专业级ADC。选型这事没有“最好”只有“最合适”。把需求边界划清楚再把方案在成本、功耗、面积、性能四象限里摆一摆答案自己就浮出来了。最后说句实在的我在调试GC1808这套方案的过程中最大的体会是高性价比芯片不等于随便画个板子就能发挥出性能。前面几个月走过的坑——电源纹波、地环路、时钟抖动——每一个都真真切切地反映在最终录出来的音频质量上。只要你愿意在硬件设计上多花心思这颗芯片完全能交付超出你预期的采集效果。
返回列表