ADC-DMA协同:多通道电压采样效率、稳定性与滤波实战
做电源管理板子或者电池巡检仪的朋友大概率都撞过这么一个场景板上八路、十六路电压要盯着用定时器或者主循环轮着去读ADC数据寄存器代码跑起来CPU占用率直接顶到三四成采样值还一跳一跳的明明输入是稳的读回来能飘几十毫伏。更糟的是通道一多采样节拍就乱前一轮刚读完后一轮的转换结束标志已经被覆盖数据对不上号。这类问题的根子不在ADC本身而在于数据搬运这件事被交给了CPU去干。把搬运的活儿转交给DMA让ADC自己转换、DMA自己搬数据、CPU只在数据攒够一批之后统一处理这套ADC-DMA协同工作的思路就是解决多路电压采样效率与稳定性的核心手段。这篇内容面向做嵌入式硬件采集、电池管理、工业仪表、电机相电流检测的开发者从痛点分析、硬件前置功课、参数计算、配置代码到滤波处理和排查技巧把这条链路完整走一遍新手能照着复现有基础的能从中抠出几个容易忽略的细节。1. 为什么轮到DMA出场——从轮询采样的三个坑说起1.1 轮询和中断采样到底卡在哪里先说轮询。最朴素的写法是在主循环里不停查ADC的状态寄存器等着转换完成标志位被置起然后读数据寄存器。这种做法在单通道、低速率、对实时性没要求的场合勉强能用一旦通道数量上去问题就暴露得很彻底。假设采样目标是1kHz、8个通道主循环每轮要查8次标志、读8次数据看起来不多但转换一次的时间在微秒量级主循环其他任务一旦耗时稍长就会错过转换窗口。ADC是连续转换的你在处理别的逻辑时它已经转完并覆盖了上一轮的数据等你回来读拿到的可能是新值也可能是旧值时序上完全不可控。再说中断方式。每个通道转换完成触发一次中断在中断服务函数里读数据。这比轮询好一点但也只是在谁来读上做了优化搬运的动作还是CPU在做。中断频率等于采样率乘通道数8通道1kHz就是8000次每秒的中断。每次进出中断都要压栈、出栈、保存现场开销放在那儿。中断一密集主程序被切得稀碎通信、显示、控制逻辑全都受影响还容易和高优先级中断抢时间片。更麻烦的是多通道扫描模式下中断标志的处理和清除顺序如果没理清很容易出现某个通道被漏读或者重复读。注意这两个方案的共同问题在于ADC转换和数据处理这两件事被绑在了同一个执行流里采集节拍受CPU调度影响无法做到严格等间隔。1.2 DMA搬运数据的机制拆解DMA全称是直接内存访问它是一块独立的硬件搬运单元能在不占用CPU指令周期的情况下把数据从一个地址搬到另一个地址。用在ADC上就是配置一条通道源地址固定指向ADC的数据寄存器目标地址指向内存里的一个数组每次ADC转换完成产生一个DMA请求DMA控制器就把数据寄存器里的值搬到数组的下一个位置搬够指定数量后给出一个传输完成的信号。这个机制之所以高效是因为搬运过程CPU完全不参与。ADC按时序转换DMA按硬件信号搬运两条线各走各的CPU只在DMA搬完一整批数据后收到一次中断然后去处理整个数组。中断频率从采样率乘通道数降到了采样率除以每批样本数量级上降了一到两个数量级。举个直观的例子8通道、每个通道采100点构成一批那么DMA搬800次数据只触发一次中断CPU负担和中断方案的差距非常明显。配置上几个关键点必须搞明白。DMA外设地址不递增因为ADC数据寄存器地址是固定的内存地址必须递增否则每次搬过来都覆盖同一个变量数组里只有最后一个通道的值。数据宽度要匹配ADC分辨率12位ADC的结果放在16位寄存器里所以外设和内存的数据宽度都设成半字也就是16位。传输方向设成外设到内存。模式上分普通模式和循环模式循环模式下搬完指定数量后自动回到数组开头继续搬适合连续采集普通模式搬完就要重新启动。多通道扫描采集通常用循环模式配双缓冲。1.3 哪些项目必须上ADC-DMA组合不是所有采集场景都必须用DMA。如果只是偶尔读一路电压比如按键检测旁边顺手采一个分压点轮询完全够用上DMA反而是杀鸡用牛刀配置还容易出错。判断标准大致可以看三条通道数是否超过两个、采样率是否超过几百赫兹、采样值是否后续要做批量算法处理。多路电压采样是典型的必须用DMA的场景。电池管理板要同时监控每一节电芯的电压工业仪表要循环采集多个传感器信号电机控制要同步采三相电流这些场景通道数都是三个起步采样节拍要求严格采样值还要做滤波和计算。如果用轮询通道间的时间间隔会随主循环耗时波动相位信息就丢了对电机控制这类需要严格同步的应用来说是致命的。还有一个容易被忽略的点是数据一致性。多路电压如果分在多个时刻采样严格来说反映的不是同一时刻的系统状态。DMA配合定时器触发可以让所有通道在同一节拍下顺序转换这一批数据虽然仍有微秒级的先后差但节拍是稳定的后续做差分、做功率计算时误差可控。这一点在需要计算功率或者做比例运算的场合很关键。2. 动手前的硬件功课——采样链路与信号调理2.1 采样保持电容与输入阻抗的匹配问题很多人配DMA配置调得很顺代码跑起来也能读到数但读回来的电压就是不准偏差随输入电压变化。这时候先别怀疑软件大概率是硬件来源阻抗和采样时间没匹配好。ADC内部有一个采样保持电容转换前它要充电到与输入电压相同的电平。这个充电过程需要时间充电速度取决于源阻抗。如果源阻抗太大而采样时间给得太短电容充不满采样值就会偏低。具体怎么算需要知道源头阻抗加上ADC内部开关阻抗再乘上内部采样电容这个乘积是时间常数。要充到12位精度理论上需要大约9倍时间常数。以常见的一款12位ADC为例内部采样电容约8皮法开关电阻约1千欧。如果外部源阻抗是10千欧时间常数就是10k加1k乘8pF约88纳秒9倍就是约0.79微秒。也就是说采样窗口至少要接近0.8微秒才能保证充电充分。如果配置里采样时间只给了0.5微秒精度就保不住。反推配置侧采样周期数乘上ADC时钟周期就是采样时间。比如ADC时钟12兆赫兹一个周期约83纳秒采样周期选7.5个采样时间约0.62微秒对上面10千欧的源阻抗就偏紧选55.5个周期约4.6微秒则绰绰有余。这就是为什么高阻信号源要加大采样周期。如果源阻抗实在太大比如上百千欧的分压电路光靠加大采样时间还不够前面得加一个电压跟随器做阻抗变换把源阻抗降下来。这是很多人调不准时忽略的一步。2.2 VREF与参考源的选择ADC的转换结果是相对参考电压的比值参考电压的稳定性直接决定测量精度。很多人习惯直接把参考电压接到供电轨上觉得省事。但供电轨上往往挂着数字电路开关噪声、负载波动都会串进来参考电压一动采回来的值就跟着动而且是全部通道一起动看起来像信号在漂。正确的做法是参考电压单独处理。如果芯片支持外部参考接一个专用的基准源这类基准源的初始精度和温漂都比普通供电轨好得多。基准源输出端要就近放去耦电容通常是一个大容量的钽电容或电解电容配一个小容量的陶瓷电容大电容稳住低频波动小电容滤掉高频噪声两点都要紧贴参考引脚走线越短越好。如果芯片只有内部参考那就要在软件上做参考电压的实测校准用高精度万用表测出实际参考值写进换算公式里而不是想当然地按标称值算。参考电压的选择还要看量程。如果待测电压满量程是3.3伏参考选3.3伏那满量程对应的码值就是4095没有浪费。但如果待测电压最大只有1.2伏参考还是3.3伏那实际只用到了三分之一量程分辨率白白损失。这种情况下要么把信号放大后再采要么换更低的参考电压。量程匹配这件事直接决定了有效分辨率的多少。2.3 布局与滤波的前置设计采样链路的抗干扰一半靠电路设计一半靠布局。PCB上模拟部分和数字部分要分开ADC的模拟输入走线尽量短远离时钟线、开关电源的开关节点和大电流回路。模拟地和数字地一般采用单点连接的方式把数字回流限制在数字区域不让它从模拟区穿过。参考电压引脚的去耦要就近这是老生常谈但确实有效。输入端的滤波也不能省。通常会在ADC输入引脚前串一个小电阻再对地接一个小电容构成低通滤波把高频噪声挡在外面。电阻和电容的取值要在带宽和源阻抗之间平衡。电阻太大会增加源阻抗影响前面的采样时间计算电容太大会拖慢响应速度。一般取值在几百欧到几千欧电容在几百皮法到几十纳法之间转角频率设在有用信号带宽的几倍以上。对于差分类或需要抑制共模的场合可以考虑差分输入。差分方式对共模噪声的抑制能力比单端强不少尤其是在电机驱动这类强干扰环境里。但要留意差分信号的共模电压范围要落在ADC允许的输入范围内不然会引入额外误差。这部分在选型阶段就要确认清楚别等板子打回来才发现星级电压不对。3. 配置与代码实操——多通道ADCDMA落地全过程3.1 时钟树与ADC基础参数怎么定配置从时钟开始。ADC的时钟来源通常是外设总线时钟经过一个预分频器得到。预分频系数要满足ADC时钟不超过芯片规定上限这个上限在很多12位逐次逼近型ADC上大约是14兆赫兹。假设外设总线时钟是72兆赫兹分频系数选6得到12兆赫兹落在范围内。这个值不是随便定的时钟越高转换越快但精度和噪声表现越差时钟越低精度好但采样率低需要在速度和精度之间找平衡。分辨率选择看需求。12位能提供约零点八毫伏的分辨率以3.3伏参考计算绝大多数电压监控场景够用。如果对精度要求更高可以开启过采样功能用多次采样平均换取更高有效位数代价是速度下降。分辨率设得越高单次转换需要的时间也越长这个在算采样率时要一并考虑。数据对齐方式选右对齐这样读到的数值直接就是转换码值换算方便。触发源的选择很关键如果用软件触发那就是启动后自由奔跑节拍由ADC自己决定如果用定时器触发每次触发转换一轮节拍由定时器精确控制。追求稳定节拍的场合定时器触发是首选后面单独展开。3.2 采样周期的手工计算过程采样周期必须动手算不能凭感觉填。整个转换时间等于采样时间加上逐次逼近的固定转换时间。对多数12位逐次逼近型ADC固定转换周期是12.5个时钟周期。所以单次转换总周期等于采样周期数加12.5再除以ADC时钟频率得到实际时间。拿前面12兆赫兹的时钟做例子。采样周期选55.5加12.5等于68个周期除以12兆赫兹约等于5.67微秒。这是单个通道的转换时间。8个通道扫描一轮就是8乘5.67约45.3微秒折算采样率约22千赫兹。如果采样周期改成7.5总周期20单次约1.67微秒8通道一轮约13.3微秒折算约75千赫兹。同样的时钟采样周期一改采样率差了三四倍。选择依据回到源阻抗。前面算过10千欧源阻抗至少需要约0.8微秒采样窗口55.5周期给出的4.6微秒有很大余量7.5周期给的0.62微秒就不够。所以低阻信号可以选短采样周期换速度高阻信号必须选长采样周期保精度。这里给大家一个速查思路源阻抗低于1千欧采样周期可选最短一档1千欧到10千欧选中等档超过10千欧要么选最长档要么加缓冲。这个经验值在多数场合适用具体还是以实测为准。3.3 扫描模式与DMA循环模式的配合多通道采集要用扫描模式。开启扫描后ADC按预先设好的通道顺序依次转换每转完一个通道自动切到下一个一轮结束后再从头开始。通道顺序是在配置里逐个添加的顺序决定了DMA数组里每个位置对应哪个通道这个对应关系要记牢后面换算时不能搞错。DMA这边要配成循环模式外设地址不递增、内存地址递增、数据宽度半字、传输数量等于通道数。循环模式下DMA搬够通道数个数据后自动回到数组开头继续搬ADC也从头开始下一轮扫描两边节拍天然对齐。这样数组里就是一轮一轮的采样值循环覆盖CPU定期去读即可。如果要用双缓冲传输数量设为通道数的两倍配合半传输中断和传输完成中断。前半段填满时触发半传输中断CPU可以处理前半段同时DMA在填后半段后半段填满触发传输完成中断处理后半段。这样理论上可以做到数据流不断处理也不耽误是连续高速采集的常用套路。配置代码大致长这样#define ADC_CH_NUM 8 #define ADC_BUF_DEPTH (ADC_CH_NUM * 20) /* 每通道20点共160点 */ static uint16_t adc_dma_buf[ADC_BUF_DEPTH]; void adc_dma_init(void) { /* ADC扫描模式、连续转换、DMA请求使能 */ /* DMA外设地址ADC-DR内存地址adc_dma_buf */ /* 外设不递增内存递增半字循环模式 */ /* 传输长度ADC_BUF_DEPTH */ HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_dma_buf, ADC_BUF_DEPTH); }3.4 代码落地从初始化到启动初始化的顺序有讲究。先开时钟再配GPIO把采集引脚设成模拟输入模式。这一步新手很容易漏引脚还是默认的浮空输入或者复用功能ADC读回来的值就不对而且这种错误不报异常只是数据不准排查起来费劲。GPIO模式一定要明确设成模拟输入。然后配ADC参数包括分辨率、扫描模式、连续转换、触发源、数据对齐。接着是ADC的通道配置逐个添加通道并设置各自的采样周期。通道的采样周期可以不一样低阻通道给短的高阻通道给长的这样整体速度能优化。再配DMA参数如上。启动流程是先启动DMA再启动ADC或者说调用带DMA的启动函数把两者一起启动。用HAL库的话调Start_DMA这个函数它会先配好DMA再使能ADC顺序内部处理好了。启动后建议检查一下返回值确认DMA真的挂上去了返回错误的话大概率是DMA通道和别的外设冲突或者传输长度设成了0。中断方面至少要用传输完成中断。如果做双缓冲还要开半传输中断。中断服务函数里不要做耗时操作最好只是置个标志位把处理放到主循环里做。中断里做滤波和换算容易拖长中断时间反而把采集节拍拖乱得不偿失。3.5 定时器触发让采样节奏稳下来自由奔跑的ADC节拍理论上稳定但受启动时刻、通道切换、DMA仲裁等因素影响轮与轮之间还是会有微小抖动。对节拍要求严格的应用比如要计算有功功率、要做同步采样最好用定时器触发。定时器按固定周期产生触发信号ADC每收到一次触发扫描一轮节拍由定时器硬件保证抖动在纳秒级。配置上定时器设成输出比较或者更新事件产生触发通过内部触发线连到ADC的外部触发源。ADC触发源选对应的定时器触发事件并关闭连续转换模式这样每次触发只扫一轮。定时器周期就是你的采样周期比如要10千赫兹的轮采样率定时器周期设100微秒。这个周期的稳定性直接决定了整批数据的等间隔程度。有个细节要注意定时器触发频率不能超过ADC一轮转换所需的时间。8通道一轮45微秒触发周期就得大于45微秒否则这一轮还没转完下一次触发就来了数据会乱。配置前先把一轮转换时间算清楚留出余量触发周期取略大于它即可。这个约束在改采样周期或者改通道数时都要重新校验很容易改了一处忘了另一处。4. 采回来的数据怎么处理——缓冲结构与滤波算法4.1 双缓冲与半传输中断的使用数据搬进数组只是第一步怎么组织这批数据决定了系统能不能顺畅跑下去。最简单的做法是等一整批采完在传输完成中断里统一处理。这种单缓冲方式在低速场合没问题但采样率高、批次大的时候CPU处理这批数据期间DMA还在往同一个数组里写处理到一半的数据可能被新数据覆盖读到的是半新半旧的混合值逻辑上就错了。双缓冲解决的就是这个冲突。把数组分成两半DMA先填前半填满触发半传输中断这时CPU开始处理前半DMA同时填后半两边互不干扰。后半填满触发传输完成中断CPU处理后半DMA回到前半继续填。这样读写分离不会打架。实现上要注意处理时间必须短于填满半块的时间否则CPU还没处理完DMA已经绕回来把这块覆盖了。所以块的大小要设计合理既要减少中断次数又要给处理留够时间。如果不方便用半传输中断还有一个思路是用标志位加数组拷贝。中断里只做一件事——把DMA数组整块拷到处理缓冲区并置标志主循环看到标志就处理。拷贝动作很快占用中断时间短主循环处理慢一点也没关系因为处理的是拷贝出来的副本。代价是多一次内存拷贝的开销对多数单片机来说这点开销可以接受换来的是逻辑清晰和不易出错。4.2 几种常用滤波算法的取舍采回的原始值带噪声是常态必须滤波。常用的几种算法各有适用场景选错了要么效果不好要么浪费算力。算术平均滤波最简单把一批点加起来除以个数。它对随机噪声有不错的平滑效果但对脉冲干扰无能为力一个大尖峰就能把平均值拉偏。而且点数越多平滑越好但响应越慢阶跃信号来了要好几批才跟上。适合信号平稳、干扰以随机噪声为主的场合。中值滤波是把一批点排序取中间值。它对脉冲干扰非常有效一两个离群的尖峰直接被剔掉这是它的核心优势。缺点是排序耗时点数多的时候计算量大对算力紧张的单片机不友好。点数一般取奇数三到五个比较实用。适合工业现场那种偶发干扰的环境。中位值平均滤波是前两者的结合先去掉一批点里的最大值和最小值剩下的再平均。这样既有中值抗脉冲的能力又有平均的平滑效果代价是每批要多做几次比较找出极值。它在很多仪表类产品里是默认选择综合表现均衡。一阶低通滤波计算量最小一个乘法加一个加法就能算出。它相当于给信号加了个RC低通系数越小滤波越强但响应越慢。它的软肋是对快变的脉冲干扰抑制不够更适合信号连续、噪声以高频为主的场合。优点是实时性极好适合放在高频采样链路的末端。算法计算量抗脉冲干扰平滑效果响应速度典型场景算术平均中差好慢平稳信号中值滤波高排序强一般中偶发尖峰中位值平均高强好中工业仪表一阶低通极低弱中快实时链路滑动平均低差好中通用场合4.3 一阶低通滤波的系数计算一阶低通滤波用的人多但系数随便填的人也不少结果要么滤不干净要么跟得太慢。这个系数是有明确计算方法的。公式是当前输出等于上次输出加上系数乘以当前输入与上次输出之差也就是输出向输入逼近逼近速度由系数决定。系数的取值和采样频率、目标截止频率有关。计算式是系数等于2π乘截止频率乘采样周期除以1加2π乘截止频率乘采样周期。举个例子采样频率10千赫兹采样周期100微秒想要截止频率10赫兹那2π乘10乘0.0001约等于0.00628系数约等于0.00628除以1.00628约0.0062。这个系数很小说明每次只向新值挪一点点滤波效果强但响应慢适合缓慢变化的电压监控。如果采样频率是1千赫兹同样10赫兹截止系数约0.059比上面大十倍响应快一些但滤波弱一些。可见系数和采样频率是强相关的换了采样率必须重算系数不能直接沿用。这个坑很常见有人把参数改了采样率忘改系数滤波效果全变了。实际跑在单片机上浮点运算有的芯片支持得不好可以用定点数实现。系数乘上2的15次方转成整数运算时先乘后右移精度基本够用。前面0.059的系数乘32768约1934代码里写成向右移位即可。4.4 定点运算与数据转换滤波做完还要把码值换算成真实电压。公式是电压等于码值除以满量程码值再乘以参考电压。12位满量程是4095。如果参考电压3.3伏码值2048对应约1.65伏。这一步的精度取决于参考电压的准确度前面说过参考要实测校准就是为了这里。浮点换算比较慢可以改成整型优化。把参考电压乘1000取整运算时用码值乘这个整数再除以4095得到毫伏值。这样全程整型速度快。要注意中间结果别溢出码值最大4095乘个3300再除32位整型完全够用。如果要做成浮点显示最后一步再转一次即可。多通道的换算要注意数组下标的映射。DMA数组是按通道顺序排的第一轮的第一个元素对应第一个通道第二个对应第二个通道以此类推。换算时按下标模通道数来定位通道别把顺序搞错。如果通道顺序在配置里改过换算代码这里也要同步改这是改配置时最容易漏的地方。5. 实测问题排查与避坑记录5.1 常见问题速查表调采集链路问题现象和原因之间的对应关系摸清了排查会快很多。下面这张表是实际项目里反复遇到的几类问题和对应方向可以对着先筛一遍。现象可能原因排查方向数据全为0DMA未启动或时钟未开检查启动函数返回值、DMA时钟数据恒为满量程引脚悬空或模式没设成模拟输入检查GPIO配置、输入是否接好通道间数据错位DMA内存地址未递增或通道数不符检查DMA递增设置、传输长度部分通道偏低采样周期不够充电不充分加大高阻通道的采样周期数值周期性跳动触发源冲突或触发太快检查触发周期是否大于一轮转换时间偶发大幅跳变干扰串入或参考波动检查滤波、参考去耦、地平面批次数据不更新循环模式没开或中断没开检查DMA模式和中断使能精度一直差参考电压未校准实测参考值并写进换算公式5.2 几个真实踩坑案例第一个坑是采样时间不足导致的高阻通道偏低。当时一块板子上有一个分压网络源阻抗大概二十几千欧其他通道都是低阻。配置时图省事给所有通道设了同样的短采样周期结果那个高阻通道读数比实际低一截且随输入电压变化偏差还不一样。一开始怀疑是分压电阻精度换了几批电阻没用。后来按前面说的采样时间计算一算短采样周期给的时间不到一秒的几百纳秒远远不够充电。把那个通道单独改成最长采样周期后读数立刻正常。这件事的教训是通道采样周期可以差异化配置别一刀切。第二个坑是多通道数据错位。有位朋友配了扫描加DMA读回来的数组里第一个通道的值总是出现在第二个位置最后一个通道的值跑到第一个位置。查了半天配置发现是DMA的内存地址递增没开同时通道在扫描序列里的起始位置和数组下标没对齐。修正递增设置并核对通道顺序后恢复正常。这类错误的特点是数据看着像对的只是顺序乱了如果不做通道校验很难发现所以调试时给每个通道接一个已知的、互不相同的电压扫一遍就能看出顺序对不对。第三个坑是双缓冲处理超时。用了双缓冲处理函数里做了比较重的滤波和目标计算结果在高采样率下前半块还没处理完DMA已经把这一块重新覆盖了导致处理的数据里混入了新数据表现为偶发的大数值跳变。后来把处理精简只做必要换算复杂计算挪到主循环或者降低采样率问题消失。双缓冲的时间余量一定要留够处理时间最好控制在半块填充时间的一半以内给调度留点缓冲。还有一个容易忽略的点是DMA和别的高优先级外设抢总线。当多个DMA通道同时活跃比如串口收发加ADC采集总线仲裁会有延迟极端情况下ADC的DMA请求被拖后数据搬运的时效受影响。一般低速场合不明显但高速采集同时跑高速通信时要留意可以调整DMA通道优先级把ADC这条设高一点。注意调试采集链路的时候先别急着接真实信号用基准源或者分压后的稳定电压去测排除信号源本身的问题再看采集侧能省很多时间。5.3 提高精度的补充技巧硬件上还有几个提升精度的小手段可以叠加使用。第一个是在ADC输入前加一级RC滤波的同时注意这个电阻不要选得太大否则又回到源阻抗过高的问题上通常几百欧比较稳妥。第二个是参考电压除了去耦还可以在软件里做两点校准用两个已知的准确电压分别测一遍算出实际增益和偏移写进换算公式做线性修正。这个方法对参考源本身精度一般的情况特别有效。第三个是充分利用过采样。如果采样率有余量可以对同一个通道连采多次求平均等效提高位数和信噪比。每提高4倍采样数理论上有效位数能提升1位。代价是采样率下降到四分之一。在电压监控这种对速度要求不高的场合过采样是很划算的精度提升手段而且它在软件里就能实现不需要改硬件。最后一个技巧是关于温度漂移的。参考源和分压电阻都会随温度变化精密场合要选低温漂的元件同时在软件里留一个温度补偿的接口。如果板上有温度传感器可以根据温度对参考电压做修正或者直接对测量结果做补偿曲线。这个做法在宽温环境的产品里几乎是标配常温调试得很准一到高低温就偏多半就是漂移没处理。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →