
zlinear开源电子前言大家好我是ZLinear的硬件工程师。在前面的系列博文中我们从FPGA双核架构聊到QSPI四线DMA从RT-Thread多线程调度聊到上位机协议解析。但有一个问题几乎每隔几天就会有读者在后台问我张工你们的采集卡标称16bit/24bit双模可AD7606芯片明明就是16位的ADC那个24位是怎么来的是不是营销噱头这个问题问得好而且非常犀利。今天我就不卖关子了——24位不是硬件给的是软件算出来的。这背后用到的核心技术叫做软件过采样配合滑动均值滤波。这篇文章我们就以DABL-7606和DABM-D223的固件代码为蓝本硬核拆解这套算法的工程实现看看一个16位的ADC原始值是怎样经过三重缓冲区的接力、256点滑动窗口的累加最终蜕变成24位高精度数据的。我会带你逐行读懂那些看似枯燥的结构体定义和定时器中断看清楚每一步数据流向。一、 精度的天花板为什么16位硬件不够用在讲算法之前我们先搞清楚一个基本问题16位ADC到底差在哪AD7606是一颗非常优秀的16位同步采样ADC±5V量程下1个LSB最小分辨电压约为±5V ÷ 32768 ≈ 152.6μV这意味着任何低于152.6μV的电压变化ADC都无法分辨。对于一般的工业变送器4-20mA、0-10V来说这个精度绰绰有余。但是当你去采集热电偶的mV级信号、应变片的微伏级电桥输出、或者对缓慢温度过程做高精度监测时152.6μV的台阶就太粗糙了。信号的变化可能只有几十微伏直接被量化噪声淹没了。换一颗24位ADC且不说成本飙升真正高性能的24位ADC如ADS1256采样率往往只有几kSPS甚至几十SPS根本做不到AD7606那样的8通道同步40kSPS。ZLinear的工程解法不换芯片用软件过采样把16位撑到接近24位的有效精度。代价是——采样率会降低。这本质上是一种以速度换精度的信号处理策略。二、 三重缓冲区架构数据蜕变的三级火箭打开《7606代码分析》文档你会看到一个结构体定义它是整个算法的核心数据容器typedef struct { int16 getAdc_Buf200[200]; // 200点实时波形缓冲用于USB上传显示 int32 getAdc_Buf256[256]; // 256点滑动平均滤波缓冲 int32 getAdcFilter16[8]; // 16位滤波结果256点均值 int32 getAdcFilter24[8]; // 24位滤波结果256点累加和 u16 getAdc_BufIndex200; // 200点缓冲区写索引 u8 recentRecordSampleNeedTimsMs; // 实际记录间隔 } adcDataStruct; adcDataStruct _uadc; // 全局ADC数据实例这个结构体里藏着三重缓冲区每一重都有明确的使命。我把它称为三级火箭第一级getAdc_Buf256 —— 256点滑动窗口原料仓int32 getAdc_Buf256[256];这是一个8通道×256深度的二维数组。每一次ADC转换完成后最新的16位原始采样值会被写入这个缓冲区。它的工作方式是环形滑动窗口——写满256个点后新数据会从位置0开始覆盖最老的数据始终保持窗口内是最新的256个采样值。为什么是256而不是255或200因为256 2的8次方。在嵌入式系统中后续的除法运算可以用右移8位来替代这在没有硬件除法器的MCU上能节省大量运算周期。这个数字不是随便选的是工程上精心设计的。第二级getAdcFilter24 / getAdcFilter16 —— 精度提升炼丹炉int32 getAdcFilter24[8]; // 24位滤波结果256点累加和 int32 getAdcFilter16[8]; // 16位滤波结果256点均值这是算法最精妙的地方。文档中给出了清晰的数据流路径AD7606原始数据(16位) → getAdc_Buf256[256] (滑动窗口)↓ (256点累加)getAdcFilter24[8] (24位精度提升)↓ (÷256)getAdcFilter16[8] (16位滤波值)24位是怎么变出来的原理说穿了并不神秘。当你把256个16位的采样值累加在一起时结果是一个最多需要24位才能表示的数因为 256 2^816 8 24。这个累加和_uadc.getAdcFilter24[ch]就是所谓的24位精度结果。但这并不是真正的免费午餐。这种精度提升成立的前提条件是信号上必须叠加了足够随机的高斯白噪声。如果信号是纯粹干净的直流256次采样值完全相同累加后只是简单的256倍放大有效位数不会增加。噪声必须是不相关的随机噪声不能是固定的偏置或周期性干扰。好在现实世界中ADC的量化噪声本身就带有一定的随机性加上电源纹波、热噪声等通常能满足这个条件。这就是为什么我们说在低采样率下有效位数可近似达到20位——不是满24位而是实际有效位数约20位左右但相比16位已经是质的飞跃。16位滤波值则是把累加和再除以256右移8位得到的是256点的算术平均值。它没有提升位宽但通过均值滤波显著降低了噪声方差使数据更平滑稳定。第三级getAdc_Buf200 —— 波形显示缓存展台int16 getAdc_Buf200[200];经过滤波后的数据最终被写入这个200点的环形缓冲区专门用于USB上传给上位机做实时波形显示。为什么是200点这是在显示流畅度和数据延迟之间权衡的结果。200个点足以在屏幕上画出一段连续且平滑的波形如果太大从采集到显示的延迟会变长用户会感觉波形跟不上如果太小波形会出现明显的锯齿和断裂。三、 1ms节拍器定时器中断里的精密调度有了三重缓冲区这个容器还需要一个推手来驱动数据流动。这个推手就是定时器中断。在DABM-D223的代码解析中我们可以看到定时器中断回调函数的全貌void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim htim1) TIM1_task(); // PWM通道1输出 else if(htim htim2) TIM2_task(); // PWM通道6输出 // ... TIM3~TIM8 PWM通道 else if(htim htim12) { usbMsg_timer_task(); // USB接收超时检测1ms周期 dio_tim_task(); // DIO状态更新 PWM加减速计算 } else if(htim htim13) { HAL_GPIO_WritePin(GPIOC,GPIO_PIN_6,1); // 示波器调试引脚拉高 qspiAdcTimTask(); // ★核心QSPI读取ADC数据按采样率触发 HAL_GPIO_WritePin(GPIOC,GPIO_PIN_6,0); // 调试引脚拉低 } else if(htim htim14) { ddsDac_tim_task(); // DAC信号输出按DAC输出率触发 } }而在DABL-7606的固件中对应的是adcFunction_tim_task()每1ms执行一次void adcFunction_tim_task() { static u16 tim_count 0; // 采样率分频控制 if(tim_count _framDatas._adcParam.samplingRate_divider) { tim_count 0; // 启动新一轮ADC转换 ad7606_startConvert(); // 将最新数据写入200点环形缓冲区 for(u8 ch0; ch8; ch) { _uadc.getAdc_Buf200[ch][_uadc.getAdc_BufIndex200] _uadc.getAdc_Buf256[ch][255]; } _uadc.getAdc_BufIndex200 (_uadc.getAdc_BufIndex200 1) % 200; // Flash/SRAM记录模式处理 localRecord_tick; } }这里有几个极其精妙的工程设计1. 采样率分频器samplingRate_divider定时器的基础节拍是固定的1ms。但用户的采样率需求千差万别——50Hz、1kHz、10kHz各不相同。怎么用同一个1ms节拍满足不同需求答案就是这个samplingRate_divider采样率分频系数。如果用户要1kHz采样率每1ms采一次divider 1每次中断都触发ADC转换。如果用户要500Hz采样率每2ms采一次divider 2每两次中断触发一次。如果用户要50Hz采样率每20ms采一次divider 20每20次中断触发一次。这种设计的好处是定时器本身的配置永远不用改只需要改一个软件变量就能动态调整采样率极其灵活。2. 调试引脚的示波器思维HAL_GPIO_WritePin(GPIOC,GPIO_PIN_6,1); // 拉高 qspiAdcTimTask(); // 执行QSPI读取 HAL_GPIO_WritePin(GPIOC,GPIO_PIN_6,0); // 拉低这两行GPIO翻转代码看似多余实则是老练工程师的必备习惯。在PCB上把PC6引脚引出一个测试点用示波器探头一搭就能直接量出qspiAdcTimTask()执行了多长时间。当采样率拉到500KSPS时如果这个脉冲宽度超过了采样周期就说明CPU来不及读完数据需要优化。这种硬件可观测性的设计思维是工业级固件和实验室代码的本质区别。3. 8通道循环写入的简洁之美for(u8 ch0; ch8; ch) { _uadc.getAdc_Buf200[ch][_uadc.getAdc_BufIndex200] _uadc.getAdc_Buf256[ch][255]; }注意这里取的是getAdc_Buf256[ch][255]——即256点滑动窗口中最新写入的那个点索引255是最新位置。这意味着200点波形显示缓存里存的是原始最新值而不是滤波后的值。为什么要这样设计因为波形显示追求的是实时性和细节如果用滤波后的值画波形会丢失高频信息工频干扰的毛刺就看不到了。而滤波值getAdcFilter16/24则通过另一条路径单独上传用于数值显示和数据记录。看的归看的算的归算的——显示和计算走两条数据通路这是采集卡软件设计中一个极其重要的工程原则。四、 上位机侧的最后一公里电压换算数据从下位机通过USB/以太网上传到上位机后还需要做最后一步转换——从原始ADC码值换算成实际电压值。在《7606上位机代码分析》文档中我们可以看到这行C#代码// 数据转换: ADC原始值 → 电压值 adcWave1.Add(Convert.ToDouble(SWAP16(_prxData.adcData[i])) * 5.0 / 32768.0 - 5.0);拆解这个公式不展开数学推导只讲工程含义步骤操作含义第1步SWAP16(_prxData.adcData[i])大端转小端解决通信字节序问题第2步× 5.0 / 32768.0将16位码值0~65535映射到0~5V范围第3步- 5.0偏移到±5V双极性量程-5V~5V上位机使用ScottPlot图表库进行实时波形绘制waveform.Plot.Clear(); if (adcCheck1.Checked) waveform.Plot.Add.ScatterLine(xAxisIndex, adcWave1, ScottPlot.Colors.Brown); waveform.Plot.Axes.SetLimits(0, axisMaxLen, -5.5, 5.5); // ±5V量程 waveform.Refresh();特性包括支持8通道同时显示、实时刷新波形、可选通道显示/隐藏、X轴为时间轴根据采样率计算。这里有一个细节值得注意上位机接收的波形数据是16位原始码值来自200点缓存而不是下位机滤波后的24位值。这意味着上位机看到的波形是未经滤波的原始信号用户可以看到包括噪声在内的全部细节。如果需要高精度的数值分析则通过另一条通道读取24位滤波值。这种波形看原始、数值看滤波的双轨设计和下位机的数据分流策略是一脉相承的。五、 实战对比不同采样率下你看到的世界完全不同为了让大家有更直观的感受我用一个实际场景来说明这套算法的价值。假设你要监测一个50Hz的工频电压信号有效值220V经变压器和分压后送入采集卡为±5V范围内的正弦波采样率设置256点窗口耗时有效精度你看到的波形适合的分析50Hzdivider20约5.1秒≈20bit只能看到极其缓慢的直流漂移趋势长时间温度/压力漂移监测1kHzdivider1256ms≈18bit波形轮廓清晰高频细节被滤除工频有效值测量、基本波形监视10kHz25.6ms≈16bit波形细节丰富但精度无提升电机振动基频分析、谐波初步观察35kHz7.3ms16bit波形细节最丰富可看到高频毛刺谐波详细分析、瞬态捕获可以看到一个清晰的规律采样率越低256点窗口覆盖的时间越长滤波效果越强精度提升越大但能看到的高频细节越少。这不是缺陷而是物理法则决定的必然权衡。工程选型建议如果你的被测信号变化缓慢温度、压力、液位大胆地把采样率降到50Hz甚至更低你会获得近乎20位的高精度远超16位硬件的极限。如果你的被测信号是振动、瞬态冲击不要降采样率保持几十kHz才能看到细节此时精度就是硬件的16位但波形保真度最高。六、 总结精度不是芯片给的是算法榨出来的设计维度核心机制工程价值三重缓冲区256点滑动窗口 24位累加 200点显示缓存数据分流滤波归滤波显示归显示互不干扰256点累加16位原始值 × 256次 24位累加和以速度换精度低频下有效位数提升约4位1ms节拍分频器定时器固定1ms软件分频调整采样率无需重配硬件即可动态调采样率灵活且稳定调试引脚可观测GPIO翻转标记任务执行时间示波器直接量出CPU负载工业级可测试性设计双轨数据通路波形上传原始16位数值上传滤波24位波形看细节数值看精度各取所需写到这里相信大家已经明白所谓16bit/24bit双模不是一块芯片里装了两种ADC而是一颗16位ADC配合精妙的软件过采样算法在不同的采样率档位下呈现出不同的精度表现。这不是营销话术而是经过严格数学论证和工业现场验证的信号处理工程实践。ZLinear之所以把这套算法的源码和结构体定义完全开源是因为我们坚信真正的精度不藏 在芯片的数据手册里而藏在工程师对数据流的每一步精心设计里。当你理解了256点滑动窗口的意义、理解了1ms节拍分频器的灵活性、理解了波形看原始、数值看滤波的双轨哲学你就掌握了数据采集系统中最核心的软件设计密码。如果你在自己的采集中遇到了低速信号精度不够或高速信号细节丢失的问题或者对过采样算法的窗口大小选择有疑问欢迎在评论区留言交流。我们一起把以速度换精度这门手艺琢磨透我是 ZLinear 开源电子。我们坚信好的算法能让平凡的芯片绽放不平凡的光芒。如果觉得今天的分享对你有帮助欢迎点赞、收藏、关注三连我们下期再见