尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

用MCU自制功耗采集系统:从ADC采样到DMA串口全解析

用MCU自制功耗采集系统:从ADC采样到DMA串口全解析 本人平时主要做低功耗嵌入式相关的东西这几年跟功耗打交道的时间比跟代码打交道还多。低功耗设计最头疼的不是写sleep代码而是“怎么知道设备到底耗了多少电”。台式万用表不够快示波器电流探头又太贵实验室的专用功耗分析仪别人排队用每次想抓一个传感器节点唤醒瞬间的电流尖峰都像碰运气。后来我干脆用一片常见的MCU自己搭了一套功耗采集系统用ADC定时采样、DMA自动搬运、串口把数据吐出来再配合脚本画成功率曲线。这篇文章就把这套系统的完整方案拆开讲清楚从采样电阻选型、运放偏置、ADC触发配置、标定流程到上位机回放全是我实际调过的路。1. 功耗测试的痛点为什么我决定用MCU自己搭一套采集系统1.1 台式功耗仪测不到的“瞬间”先说我遇到的真实场景。一个使用两节AA电池供电的温湿度传感器节点工作状态是每10秒醒来一次打开无线模块发数据然后马上睡回去。按规格书估算平均电流应该在20uA左右。我用台式万用表串联在电池回路里测到的也是20uA上下看起来一切正常。但设备在客户那边就是撑不到标称的电池寿命甚至两个月就没电了。问题出在哪万用表走的积分路径测的是长时间的平均值。节点醒来那几十毫秒内可能瞬间拉到30mA睡眠时回到3uA万用表看到的是一个被平均化之后的小数值。某些糟糕的电路设计刚好在唤醒瞬间产生持续的闩锁电流、GPIO竞争电流或者去耦电容异常充电这些只有几毫秒甚至几百微秒的现象万用表完全来不及反应但每一轮唤醒周期都在消耗电池能量。示波器配合电流探头当然能看见这些瞬态可电流探头一个探头抵得上半块开发板的钱不是每个项目都配得起。就算配了示波器抓波形也只适合单次观测做长时间连续监测、统计多次唤醒周期的功耗分布非常别扭。这让我意识到真正需要的是“一个能连续记录电流时间序列的小盒子”——既有足够的采样率抓住几毫秒的变化又能连续记录几十秒甚至几分钟的数据用于统计。市面上的专业功耗分析仪当然能做到但价格确实不低而且很多时候我们只是需要快速看个大概不是做高精度计量。既然手头MCU资源充足干脆自己做一个。1.2 几种测量方案的取舍对比在决定自制之前我把手边的可行方案列了一个对比表也建议你先做这一步免得像我一样走弯路。方案可测带宽/响应动态范围成本适合场景台式万用表(积分模式)极低只出平均值高量程自动换中高粗看平均功耗示波器电流探头高几MHz中档位固定很高单次瞬态分析专用功耗分析仪高可编程滤波极高自动量程很高严谨的能耗认证测试MCU内部ADC采样电阻中等几十kHz低靠前端设计极低日常开发自测、多次统计MCU方案的定位不是替代专业仪表而是补上“日常快速验证”这个空缺。它最大的好处是你手里本来就有一块开发板加一个采样电阻和一个运放就能跑起来逻辑分析仪、串口工具都是现成的。对于固件工程师来说这套东西能回答“我的低功耗代码改完到底有没有变好”这种高频问题比把设备抱到实验室排队强太多。我选的器件是一颗常见的Cortex-M内核MCU内置12位ADC主频跑在几十MHz片上自带DMA和定时器。这算是这类项目的黄金配置——太老的8位机ADC采样率和DMA能力不够太新的高性能MCU又大材小用。具体型号不关键你手头的STM32、GD32、APM32乃至国产新锐MCU都能干这活核心思路完全通用。2. 采样前端与硬件连接精度的大头其实在这2.1 采样电阻和差分放大为什么要浪费一个运放ADC内部采集的是电压测电流必须先把电流转成电压最简单的办法就是在电流通路上串一个采样电阻测电阻两端的压降。这里有两个关键决策采样电阻放在高端还是低端以及压降信号够不够大。低端采样是把电阻串在负载和地之间测量对地电压电路最简单。但它的毛病在于抬高了负载的“地”电位对射频电路、音频电路、高精度模拟电路都很不友好。我在无线节点上试过采样电阻一接地无线模块的发射频谱直接变差因为地电位被“污染”了。高端采样是把采样电阻放在电源正端和负载之间不干扰地回路。但MCU的ADC通常只能测正电压高端采样电阻两端的压降是“电源电压减去负载电压”共模电压很高直接接ADC肯定不行得用差分放大器把压降提取出来。我最终选了高端采样加差分放大。运放用的是常见的高边电流检测运放比如INA240这类自带固定增益的型号或者通用运放搭建差分电路也行关键是增益要确定、温漂要小。采样电阻阻值的选择需要算一笔账。电阻越大压降越大信噪比越好但压降本身会消耗系统能量影响被测设备的工作电压。以我的节点为例最大瞬态电流约50mA我希望在满量程时采样电阻上的压降不超过100mV采样电阻取2欧姆比较合适。对于更低功耗的设备采样电阻可以取10欧姆甚至100欧姆但这时即使睡眠电流才几个微安压降也会有几毫伏后面的放大电路就不太容易处理了。2.2 偏置到Vref/2让ADC既能测正电流也能测“负方向”用高端采样时电流方向通常是电源流向负载压降方向固定。但在实际调试中我遇到过负载电路向电源反灌电流的情况比如电机减速回馈、电池充电回路、负载电容放电瞬间。如果只做单方向的测量这些负向电流会被ADC当成0数据就失真了。解决办法是给运放输出加一个直流偏置把信号的零电流点偏置到ADC参考电压的一半也就是Vref/2。这样正电流让输出电压向Vref方向偏负电流让输出电压向GND方向偏两边都有测量余量。具体电路上用INA240配合一个基准电压源做偏置或者在差分运放的同相输入端用电阻分压给一个Vref/2偏置。前者更精整后者省钱。我用的是电阻分压加运放跟随的方式成本最低实测稳定性也能接受。这里有一个很实际的注意点偏置电压的噪声会直接叠加到测量信号上。Vref/2如果是从MCU的电源上分的而MCU电源本来就有纹波那测出来的波形就会叠上一层纹波。我后来把偏置基准单独用一个低噪声LDO供电或者用MCU内置的VREF输出引脚效果立刻改善。2.3 第一版失败给我的教训动态范围不是ADC位数决定的第一次搭这套系统时我把采样电阻设成2欧姆运放增益设成20倍ADC满量程对应100mA左右的电流心想12位ADC怎么着也能分辨0.1mA以下够用了。结果一测睡眠电流直接翻车。睡眠状态下负载电流只有3uA在2欧姆采样电阻上产生的压降只有6uV放大20倍之后是120uV。12位ADC在3.3V参考下的1个LSB大约0.8mV也就是说120uV的信号连一个LSB都不到ADC读出来的全是噪声和量化台阶。这就是动态范围的残酷现实ADC位数决定的是分辨率的上限但实际分辨率受制于前端增益和信号的绝对幅度。想同时测3uA的睡眠电流和50mA的发射电流等于要求系统有16000倍以上的动态范围12位ADC加固定增益前端根本做不到。这个教训让我放弃了“一块电路测所有”的想法改为给前端做两档增益。睡眠电流测试时用高增益档比如100倍瞬态大电流测试时切到低增益档比如5倍。切换通过两个运放增益电阻并联、用GPIO控制的模拟开关完成。虽然不能自动切换但至少覆盖了两种最关键的测量场景。如果你想做全自动量程后面扩展一个可编程增益放大器就行思路是一样的。3. ADC配置与采样策略决定数据质量的三件事3.1 基准、采样时间与引脚阻抗ADC不是读一下就完事大多数MCU的ADC模块如果只是用轮询方式读一下单通道很难发挥出高性能。要把ADC用好得同时关注基准电压、采样时间和引脚阻抗三个参数。基准电压方面很多开发板把ADC的Vref直接接到VDD上。如果VDD本身有较大的纹波或者负载波动ADC的量化台阶就是浮动的。测量功耗时被测设备的电流变化恰好会引起电源电压波动这就相当于测量基准在被测信号干扰数据必然不准。我改用MCU内部的独立参考电压VREFINT或者外接一颗精密基准源虽然麻烦一点但数据稳定性的提升非常明显。采样时间决定了采样电容充电的程度。ADC内部有一个采样电容每次采样前需要足够的时间让这个电容充到输入电压。如果引脚串联电阻大或者前端运放驱动能力弱采样时间太短会导致读数偏低而且误差不是固定的——输入阻抗越高的电路越明显。我把ADC采样时间调到最大档比如STM32上的最大采样周期档位同时确保运放输出能驱动ADC的采样电容这两条配合做下来ADC读数的一致性好了很多。引脚阻抗的问题容易被忽略。如果被测信号经过一个10k欧姆的限流电阻再进ADC引脚内部采样电容并联的等效阻抗会让读数出现系统性的比例误差。这不是ADC硬件坏了而是电路设计没考虑到驱动能力。功耗采集系统的前端是运放输出阻抗通常只有几十欧姆但PCB走线太细太长也会引入额外阻抗这里走线尽量短粗。3.2 定时器触发DMA不用中断也能连续采样直接在主循环里轮询ADC采样率不稳定而且高频率轮询会占用CPU影响被测事件的执行时序。对于功耗采集这种需要“同时观察系统行为和功耗变化”的场景轮询方案不可取。我用的方案是定时器输出触发信号接到ADC的硬件触发引脚让ADC按固定频率自动启动转换ADC转换完成后DMA自动把结果搬运到内存缓冲区。整个采集过程不需要CPU介入CPU可以在采集的同时执行被测任务或者进入sleep模式观察系统的真实功耗。配置关键点有三个定时器周期决定采样率。定时器计数频率除以重载值就是采样率。比如定时器时钟是72MHz重载值设为720那么采样率就是100kHz也就是每10us采一个点。开启DMA循环模式让DMA在缓冲区写满后自动回到起始地址继续写避免频繁进中断。在DMA传输完成中断里只是设置一个标志位由主循环去处理缓冲区数据绝不在中断里做数据解析或串口发送。DMA循环模式有一个坑要特别提醒如果主循环来不及消费缓冲区DMA会把新数据覆盖到旧数据上。解决方法是把缓冲区设为DMA循环模式下的双缓冲或者把缓冲区开大一点再配合一个简单的读写指针判断数据是否溢出。我在项目里把缓冲区开到4096个32位字按100kHz采样率算能存约40ms的数据足够主循环从容处理。3.3 采样率规划平均功耗与瞬态事件的取舍采样率设多少需要结合被测场景来定不是越高越好。如果要统计长时间的平均功耗比如1分钟内设备消耗了多少mAh采样率可以低到1kHz甚至100Hz每秒钟只采几百个点数据量小处理简单统计精度足够。如果要抓无线模块发射时的瞬态尖峰比如那几十毫秒的电流爬升和回落采样率至少需要10kHz以上。射频模块的电流上升沿可能只有几十微秒10kHz采样率每100us一个点勉强能勾勒出轮廓但峰值的绝对高度可能偏低。想更准确地捕捉峰值20kHz到50kHz会更合适。再往上就没有什么收益了。50kHz以上采样率数据量会爆炸而面对通常的电池供电设备功耗事件的时间尺度都在百微秒以上过高的采样率只是在浪费存储和分析时间。我实际建议做两套配置日常统计用1kHz采样存储开销小可以连续监测很长时间要分析瞬态时切换成20kHz配合触发模式捕捉特定事件。这两套配置通过一个简单的串口命令切换性价比最高。4. 标定与校准流程零漂、增益和逐点修正4.1 零电流标定为什么系统“应该”输出Vref/2实际却不是理论上零电流流过采样电阻时运放输出应该恰好是Vref/2ADC读数应该在2048附近12位ADC。实际一测大概率不是2048可能偏到2100或者2000。这个偏差来自运放的输入失调电压、偏置电阻的误差、以及ADC本身的偏移误差。不要小看这几个LSB的偏移在小电流测量时它对应的电流误差可能是几个微安直接让睡眠电流的测试结果失去意义。解决办法是先做零电流标定让负载完全断开确保采样电阻上没有电流然后采集一段ADC数据取平均把这个平均值记录为“零电流读数”。之后每次把ADC读数减去这个零漂值再乘以比例系数就能得到真实电流。注意标定时的环境温度和实际测量时尽量一致。运放的失调电压随温度变化如果在实验室调零好了拿到户外环境实测零漂可能又变了。对精度要求高的场合我会在固件里保存多个温度点的零漂值运行时根据温度传感器读数插值补偿。4.2 用精密电流源做增益校准两点定标和分段修正消除了零漂之后还需要确定“电压转电流”的比例系数。虽然采样电阻阻值和运放增益在理论上都知道但电阻有精度误差比如1%精度的电阻就会带来1%的电流测量误差手册标称增益也有典型值和最大最小值的差异。这些误差累加起来在100mA量程上可能偏出好几毫安。标定方法很简单用一个精密电流源给采集系统输入一个已知电流比如10mA记录ADC读数再输入另一个已知电流比如50mA记录读数。两点之间可以算出斜率和截距这就是线性校准参数。中间任何电流值用这个线性关系换算即可。如果要更高的精度可以做分段线性校准在1mA、5mA、10mA、20mA、50mA等多个点采集相邻点之间各自做线性插值。实测下来分段校准能把非线性误差从百分之几压到千分之几。这里用到的精密电流源买一台小型台式源表比较方便没有的话用精密电阻加可调电源也能凑合但校准精度就受限于你的参考源了。标定参数存储在MCU内部Flash或者外接EEPROM里系统上电时自动读取。这样每次测量不需要重新标定同一块板子在不同环境下的测试结果也能横向对比。4.3 实测误差验证我对标台式仪表的结果为了确认这套自制的采集系统到底有多准我把一个恒定电流负载接上去同时用台式源表测电流和采集系统的读数做对比。在10mA标定点上自制系统读数10.03mA偏差0.3%在50mA标定点读数50.12mA偏差0.24%。在1mA小电流点读数1.05mA偏差5%。这个结果符合预期大电流段线性度好小电流段因为零漂和放大倍数误差的影响相对偏差就大了。这也说明了一个重要的使用原则任何仪器的精度都是相对的关键看量程与信号是否匹配。用这套系统测睡眠电流时一定要切到高增益档让信号幅度尽可能占满ADC量程。如果贪图方便全程用低增益档测小电流测出来的数据只能算“数量级正确”绝对值不能用来写测试报告。5. 固件结构、串口协议与上位机数据回放5.1 数据缓冲与传输协议DMA采集之后怎么把数据发出来ADC通过DMA写进缓冲区之后数据还只是内存里的一个数组要变成能分析的东西需要把它发送给上位机。如果直接在裸机上全部发出去串口带宽会成为瓶颈。100kHz采样率、16位ADC数据每秒就是200KB的数据量普通UART在115200波特率下每秒只能传约11.5KB差了将近20倍。这还没算协议开销。所以固件端必须做压缩或者降采样。我的做法是按场景区分统计模式在MCU端直接计算平均值、最大值、最小值、累积能量估算只把统计结果通过串口发出去一包数据不到100字节。瞬态模式采样率降到10kHz16位数据每包256个采样点加上帧头帧尾和校验串口用2Mbps波特率勉强能实时传输如果波特率上不去就在MCU端做抽点每两个点保留一个。串口协议我设计得很简单方便上位机解析帧头0xAA 0x55接着是数据长度、数据区、累加和校验。每一帧数据区里包含采样率、通道号和ADC原始值数组。这个协议不用任何第三方库纯手工拼字节上位机用Python的pyserial读串口按同样的格式解包非常简单可靠。重要提醒串口发送用的是DMA加环形缓冲配合空闲中断逐包发送。不要在ADC的DMA传输完成中断里直接操作串口发送否则中断延迟会影响ADC采样的连续性尤其是低功耗模式下系统时钟不稳定的时候。5.2 上位机从串口日志到功耗曲线数据到了上位机我的首选工具是Python加matplotlib。pyserial读串口数据struct.unpack解包然后按时间戳画电流-时间曲线。对于长时间的功耗统计还可以画直方图看不同电流水平的占比。画曲线的时候有一点要注意电流数据的纵轴范围要合适。如果睡眠电流只有几个微安发射电流有几十毫安直接画在一张图里睡眠电流会贴在地板上看不出任何细节。我一般会画两张图一张线性坐标看全貌一张对数坐标看小电流细节。或者把Y轴设置成分段但编程复杂一些。如果你愿意折腾还可以把数据导出成CSV扔进Excel或者Origin里做进一步分析。我在实际测试中喜欢用Python脚本一键生成报告包含平均电流、峰值电流、积分电荷量、等效工作时间等指标。这让整个功耗评估流程自动化了很多。5.3 触发模式如何精确抓住一个跳变事件实时连续传输虽然直观但是有一个问题功耗事件发生的时间点不确定。要捕捉无线模块发射那一瞬间总不能让上位机一直录着等它发生那样不仅浪费存储而且数据里大部分是无聊的睡眠平台。解决办法是给采集系统加一个触发功能。我实现了两种触发软件电压触发MCU持续采样但不立即传输。每一次采样值如果突破了预设的电流阈值比如超过10mA就认为触发事件发生从触发点往前回传一段缓存的历史数据再触发点之后继续采集一段数据。这样拿到手的波形正好覆盖触发前和触发后能非常清楚地看到事件前后的细节。GPIO外部触发如果被测系统本身有事件信号比如一个GPIO在发射开始时拉高可以把这根信号引到MCU的外部中断引脚触发采集系统开始高速采集。这个模式对“事件和功耗之间的精确时序关系”分析特别有用。触发模式在固件里实现起来并不复杂就是开一个环形缓冲区持续写入采样数据同时比较当前采样值和阈值。触发后关闭环形覆盖再采够指定的点数把整段数据打成一个大包发出。6. 实测案例与项目复盘6.1 一个真实案例无线传感器节点的唤醒电流为了验证整套系统的实战价值我拿它测试了一个低功耗无线节点。节点平时睡眠电流约3.2uA每10秒醒来一次启动传感器、无线发射、监听回包后再次休眠。用高增益档、1kHz采样率跑了几分钟统计模式给出的平均电流是24.6uA。这个数看起来和万用表测到的差不多。但把瞬态模式打开在20kHz采样率下抓到一次唤醒过程的完整波形后发现了一个万用表看不到的问题唤醒瞬间的电流尖峰达到42mA持续了约35ms之后才落到正常发射平台的28mA。也就是说这35ms的尖峰并不完全是无线发射造成的更像是有源器件同时开启时的浪涌电流。按10秒周期算这个尖峰每次浪费的电荷量大约是42mA乘以35ms换算过来每个周期约0.41mAh/千周期。虽然看上去不多但在两年电池寿命的预算里这已经是不可忽视的比例了。顺着这个波形再查代码发现传感器上电和无线模块初始化的顺序重叠了而且某个GPIO在初始化阶段短暂处于浮空输入状态引脚通过内部保护二极管产生了几毫安的漏电。调整了初始化顺序、把GPIO改为上拉或下拉后再测唤醒尖峰从42mA降到了31mA平均电流从24.6uA降到了19.8uA。这个改进只花了一个晚上但电池寿命预期提升了差不多20%。这个案例让我更确信做低功耗优化没有时间序列数据只看平均值等于蒙着眼睛过河。6.2 这套系统带给我的一些经验与后续扩展方向MCU方案做功耗采集说到底是在精度、成本和易用性之间找平衡。它的上限不如专用仪器但足以覆盖固件开发和硬件调试中绝大多数需求。以下几个经验是踩坑踩出来的如果MCU引脚和PCB空间允许采样电阻和运放电路尽量做成可插拔模块方便换不同增益和不同电流范围。测量时被测设备的供电回路里不要再串万用表多一个串联接触电阻小电流路径上的压降会明显影响设备实际工作电压。串口传输数据时尽量用硬件流控或者降低波特率避免丢包导致波形断断续续。分析波形之前先检查数据包的连续性。保持ADC的参考电压稳定是一切精度的前提。如果MCU本身就在被测系统里而你同时用它的ADC测自己的电流要注意MCU工作状态对参考电压的影响必要时候在硬件上把测量链路和被测系统隔离。扩展方向上我正在考虑加入SD卡记录让系统脱离上位机独立长时间记录功耗曲线还想把输入前端升级成可编程增益放大器实现全自动量程切换。如果你手头正好有低功耗项目要调建议先别急着买昂贵的功耗分析仪一块带ADC和DMA的MCU、一个采样电阻、一个运放就能先把大部分问题看清楚。这套东西做出来之后我在实验室里使用它的频率比示波器电流探头高得多因为它可以长时间记录、自动统计结果这种“挂在系统上不管它”的测试方式才最贴近真实使用场景下的功耗表现。
返回列表