
简介本资源是一套基于STM32F103C8的直流电压电流综合测量系统完整工程面向嵌入式初学者与硬件开发爱好者解决低功耗电子设备中实时电参数监测的实际需求。项目融合INA226高精度I²C电流/电压/功率三合一采集与ACS712霍尔效应电流传感技术支持0–36V直流电压、0–5A可扩展至20A直流电流测量并实现功率计算与电池电量估算功能适用于电源调试、电池管理系统及教学实验等场景。压缩包含134个文件涵盖27个编译中间文件.o/.d、18个核心源码.c、16个头文件.h、以及启动配置.sct、工程配置.uvprojx、输出镜像.hex/.axf等典型Keil MDK开发要素结构规范便于理解STM32底层驱动与外设协同逻辑。目前已有13492人学习下载提供OLED显示驱动、INA226寄存器配置、ACS712零点校准与ADC采样滤波等关键实现细节开箱即用适合动手实践与原理深化。1. 方案选型先想清楚这三个关键决策拿到基于STM32直流电压电流测量这个项目很多人的第一反应是ADC采集呗电压用分压电阻电流用采样电阻换算一下完事。真做起来你会发现从能测到测得准之间隔着不少坑。我先说结论这个项目真正核心的技术点不在于STM32本身而在于前端模拟电路的设计、基准参考的处理、以及软件校准的思路。STM32 F103这类芯片基本够用不用非上F4、H7。在动手画板子之前有三个关键决策会直接影响整个项目的走向。1.1 电流检测方案采样电阻还是霍尔传感器直流电流测量主流方案有两种低边采样电阻和高边电流检测芯片。低边采样是把采样电阻串在负载的GND回路里用运放放大电阻两端压降再送STM32的ADC。高边检测则是用INA219、INA226这类专用芯片串在电源正极和负载之间通过I2C接口直接读出电流值连运算放大器都省了。我个人的建议是如果你做的是一块独立的测量仪器或仪表模块用采样电阻加运放的方案更灵活成本低而且能顺便掌握模拟前端的设计能力。如果你做的是某个大系统里的一个子模块比如电池管理、电机驱动里的电流监控直接用INA226这类芯片是更稳的选择省去调试模拟电路的时间而且I2C读出的数据本身就带校准寄存器精度天然有保障。热词里出现了INA219说明这个方向也是大家常搜的。我两种方案都做过下面会展开讲各自的关键点。1.2 电压测量前端分压网络不只是两个电阻那么简单电压测量本质上是把高电压按比例缩小到ADC能接受的0~3.3V范围内。最常见的做法是电阻分压。但这里有两个细节容易被忽略一是输入阻抗问题二是分压电阻的温度系数和精度等级。有人用两个0805封装的普通电阻做分压测出来的电压跳得厉害而且换一个环境温度读数就飘。原因很简单普通厚膜电阻的温度系数往往在100ppm/℃以上200ppm的也很常见。假设你测50V电压电阻温度变化10℃读数可能偏个几十毫伏甚至上百毫伏。对仪表类产品来说这是不可接受的。更好的做法是用0.1%精度的金属膜电阻温度系数控制在25ppm/℃或更低。另外还要在ADC输入端并联一个100nF的电容做滤波同时用电压跟随器做阻抗变换避免ADC内部的采样电容充电时间不足导致测量值偏小。这个坑我后面会细讲。1.3 主控选型与资源分配F103C8T6够不够以最常用的STM32F103C8T6为例它内置三个12位ADC每个ADC最多支持16个外部通道采样速度最高1MHz在42MHz的ADC时钟下。对这个项目来说资源完全够用。你只需要两个模拟输入通道一个测电压一个测电流再加上DMA搬运数据、一个定时器做采样频率控制、一个I2C或SPI驱动OLED显示屏剩下的资源还能干点别的。需要留意的是F103的ADC是12位分辨率理论分辨率是3.3V/4096大概0.8mV。如果配合合理的分压比和运放增益你能达到的实际测量精度取决于基准源和校准水平而不是ADC位数。非要追求16位甚至24位精度那就得外挂ADC芯片比如ADS1115或ADS1256整个项目的复杂度会上升一个量级。对于直流电压电流测量这个需求12位ADC配合软件校准做到0.5%精度是完全现实的做到0.1%需要花点心思但也可行。2. 硬件电路设计参数计算与实际搭建方案定下来之后最花时间的其实是硬件环节。这一节我把电压测量、电流测量、以及主控周边的电路设计参数全部过一遍给出可以直接照抄的电路结构和计算公式。2.1 电压采集电路电阻分压的精确计算假设你希望测量的直流电压范围是0~50VSTM32的ADC参考电压为3.3V那分压比至少是50/3.3约为15.15。留出安全余量我习惯把满量程设计到55V左右对应的分压比大约为55/3.3即16.67。实际的电阻取值需要考虑两个因素输入阻抗够不够大否则从被测电路吸取的电流过大影响被测信号以及电阻阻值在标准系列里能不能凑出来。我用的方案是上臂两个电阻串联下臂一个电阻既提高了耐压能力又能灵活凑比例。具体取值为上臂R1100kΩR220kΩ下臂R310kΩ。分压比计算如下分压比 (R1 R2 R3) / R3 (100 20 10) / 10 13ADC输入端的最大电压为50V/13约3.85V稍微超出了3.3V意味着50V的量程上限需要做一点调整。如果把上臂改成R1100kΩ、R247kΩ下臂R310kΩ则分压比为(100 47 10) / 10 15.750V对应ADC输入约3.18V留了约0.12V的余量这样更合理。重要提醒分压电阻的等效输入阻抗是R1R2R3也就是157kΩ。这个值对应的分流电流约为50V/157kΩ大约0.32mA对大多数被测电路来说可以忽略不计。但如果被测源内阻很高这个负载效应就不能忽视了必要时得用仪表放大器或者再加一级缓冲。在ADC引脚前我习惯串联一个1kΩ电阻并并联一个100nF电容构成一个低通滤波器截止频率约1.6kHzf_c 1 / (2π × R × C) 1 / (2π × 1kΩ × 100nF) ≈ 1.59kHz这个滤波器的作用是滤掉高频噪声防止ADC采样混叠。注意这个1kΩ电阻和100nF电容必须在运放或分压点之后、ADC引脚之前布局要靠近MCU引脚。2.2 电流采集电路运放放大与偏置处理电流测量我用低边采样方案。假设电流量程为5A采样电阻取10mΩ康铜丝或锰铜合金电阻功率要按P I²R计算P 5A × 5A × 0.01Ω 0.25W考虑到电流可能短时过冲采样电阻建议用额定功率1W以上的型号留出4倍余量。10mΩ电阻在5A电流下产生50mV压降这个信号太小直接用ADC测量会很吃力需要放大。用轨到轨运放比如LMV358、MCP6002或OPA340放大50倍输出为2.5V。运放电路采用同相放大结构增益由Rf和Rg决定Gain 1 Rf / Rg取Rf49kΩRg1kΩ增益为50。如果希望保留一定的余量可以把增益调整到45左右防止电流过载时运放输出饱和到电源轨导致ADC读数失真。这里有个经验细节运放供电电压要和ADC参考电压保持一致。如果STM32的ADC参考是3.3V运放也接3.3V供电输出范围实际上达不到满幅最高只能到接近3.3V但稍低的位置。所以需要保证5A满量程时运放输出在2.8V以下留出足够的动态范围。采样电阻的位置也很有讲究。低边采样电阻接在负载的接地回路中会引入地弹噪声ground bounce对系统其他敏感电路产生干扰。如果系统里有精密模拟电路建议改用高边检测或者把采样电阻安排在靠近电源地的地方并做好单点接地。2.3 保护电路除了TVS还要防反接和过流作为测量仪器输入端会接触各种未知状态的电源和负载保护电路必须做足。电压输入端的保护设计是在分压电阻之后、ADC引脚之前并联一个双向TVS二极管选型击穿电压3.3V再加一个200Ω的限流电阻。一旦输入异常高压冲击TVS会把电压钳位在安全范围。电流输入端同样需要防护。采样电阻本身的低阻值决定了它不太容易损坏但误测高电压时比如把电流档直接并联到一个24V电源上10mΩ电阻会瞬间流过上千安培必然烧毁。所以要串一个保险丝选5A快断型或者用自恢复保险丝PPTC。另外运放输入端也应该加钳位二极管防止共模电压过高。主控板的电源也要单独处理。用AMS1117-3.3稳压给STM32供电输入加反接保护二极管和100uF电解电容。如果是从被测系统取电还要加LC滤波防止被测系统开关噪声传导进来。我第一版就是直接共用被测系统的电源结果ADC读数在电机启停时能跳几十个数后来加了LC电源滤波才压下去。3. 软件实现ADC多通道扫描DMA搬运数据滤波硬件搞定之后进入软件阶段。这一节是大多数人最容易能出数但测不准的地方我按ADC初始化、DMA配置、数据处理三个层次来拆解。3.1 初始化ADC别忽略采样时间和扫描模式在标准库或HAL库中ADC初始化的核心配置包括ADC时钟分频、分辨率、扫描模式、连续转换模式、触发方式以及每个通道的采样时间。F103的ADC时钟最高为14MHz来自APB2的72MHz时钟分频我这里用PCLK2/6得到12MHz确保ADC在安全范围内。分辨率选12位数据对齐方式右对齐。需要特别强调的是采样时间。STM32 ADC是一个逐次逼近型转换器转换前需要给内部采样电容充电。如果信号源阻抗高充电时间不足会直接导致测量结果偏小而且通道切换时串扰明显。F103的采样时间寄存器可以配置为1.5、7.5、13.5、28.5、41.5、55.5、71.5、239.5个周期。对于直流测量我建议直接配成55.5或71.5个周期虽然单通道采样速率下降但精度更稳。采样时间的性价比选择55.5个周期在12MHz下约4.6微秒多通道扫描完全够用。3.2 多通道配置与DMA搬运为什么必须用DMA项目要同时采电压和电流两个通道最简单的写法是每次转换完成后读ADC1-DR寄存器。但这种轮询方式有两个问题一是CPU被占用二是两次通道转换之间的间隔可能因为其他中断插入而不稳定影响测量的一致性。正确做法是用ADC的多通道扫描模式加DMA。配置方法如下ADC1开启扫描模式设置两个转换通道通道0电压输入和通道1电流输入顺序固定。开启连续转换模式这样ADC转换完成一轮后自动开始下一轮。配置DMA1的通道1数据从ADC1-DR寄存器搬运到内存数组方向外设到内存模式为循环。内存数组定义为uint16_t类型的数组长度为2每个通道一个。DMA循环模式的作用是整个ADC转换过程完全不需要CPU干预ADC转换完一轮DMA自动把两个通道的数据依次搬运到内存然后ADC接着下一轮DMA继续搬运。CPU随时可以读取内存数组的值。如果你在调试中发现只有第一个通道有数据、第二个通道为0先检查DMA的外设地址是否错误地写成了ADC1-DR地址以及DMA传输的数据宽度配置是否与ADC数据寄存器宽度一致。HAL库下用ADC多通道DMA采集时还需要注意调用HAL_ADC_Start_DMA()后ADC是连续转换模式但每次DMA缓冲区写满一半时会触发半传输中断如果你不需要这个中断务必在初始化时不使能它否则会频繁打断CPU。另外在循环模式下启动DMA前最好先调用__HAL_ADC_ENABLE(hadc)否则可能会出现首次转换数据为0的情况。3.3 数据滤波平均滤波的窗口大小选择ADC的原始数据即便用DMA搬回来了直接拿来用也还是会有几十毫伏的抖动。电源本身有纹波采样电路有热噪声环境里还有电磁干扰。滤波是必需的。最常用也最有效的是一阶滑动平均滤波。以50个数据点为窗口每来一个新数据就计算窗口内的平均值。为什么选50因为50个点的均方根噪声抑制率约为原来的1/√50约等于14%同时50个数据点在12MHz ADC 55.5周期采样时间下对应的时间大约是50×5.3微秒约0.27毫秒对直流测量来说响应够快又不会引入明显延迟。如果你需要更平滑的读数可以把窗口加大到200或500个点但要注意滑动平均本质上是一个低通滤波器窗口过大时输入电压突变后读数需要几百毫秒才能跟上这在观察动态负载时容易误判。我个人的建议是双层滤波先用200个点做滑动平均得到稳定的测量值然后额外加一个一阶低通滤波alpha系数0.3~0.5进一步抑制低频波动。实际测试中双层滤波后的读数波动能控制在±1个LSB以内。3.4 显示和交互OLED驱动与单位切换显示部分我用的是0.96寸SSD1306 OLED128×64分辨率通过I2C或SPI接口连接。SPI接口刷新速度快适合显示波形或频繁变化的数字但接线多两根占用引脚多。I2C只占两根线刷新速率对于显示电压电流数值来说绰绰有余。SSD1306的驱动例程网上很多核心操作就是初始化序列、设置显存、刷新函数。我一般直接操作显存数组把要显示的数值格式化后写入显存再整屏刷新。注意I2C模式下的写入速度如果每100ms刷新一次I2C时钟设400kHz完全够用。除了OLED我还会通过串口把原始ADC值和换算后的工程值周期性地发送到上位机串口助手或Python脚本读取方便在开发阶段观察滤波效果和校准情况。串口用HAL_UART_Transmit轮询方式就够了因为只是周期性发送数据不涉及接收。4. 校准方法与精度提升从能测到测得准硬件和基本软件跑通之后最影响用户口碑的就是精度了。STM32的12位ADC在出厂时并没有精确校准每个通道的增益和偏移再加上你电路里的电阻精度、运放偏置、参考电压误差直接从ADC读数换算出的电压值往往偏差在1%~3%之间。要做成一个像样的测量仪器必须自行校准。4.1 为什么要校准误差来源全分析第一类误差是ADC的偏移误差offset error和增益误差gain error。STM32数据手册里的典型值是±1.5LSB的偏移误差和±3LSB的增益误差这意味着对于12位ADC理论满量程4096但实际转换结果会整体偏大或偏小而且这种偏差不是固定值随温度还会漂移。第二类误差来自分压电阻和采样电阻的精度。即使选了0.1%精度的电阻也会带来0.1%的量程误差对电压测量影响约0.05V50V量程。这个误差在校准时可以被有效消除。第三类误差来自参考电压。F103内部的VREF直接连接到VDDA如果你的VDDA是来自普通LDO的3.3V它本身的精度只有±2%这直接决定了ADC测量精度天花板。想进一步提升可以在VDDA引脚外接一个高精度基准源比如REF33333.3V初始精度0.2%漂移极低但要注意LDO输出和基准源的切换电路设计。综合来看如果不校准误差3%不稀奇校完之后做到0.5%甚至0.2%完全可行。4.2 两点校准实操记录真实电压电流与ADC读数校准方法不复杂但需要一台精度较高的外部仪表作为参照比如四位半的万用表或可调电源上的数显。电压通道校准步骤将可调稳压电源输出调到0V记录此时OLED上显示的ADC原始值理想情况下这个值是0但由于偏移误差和运放输出不可能真正到0实际会有几十到几百的数值。调到10V记录ADC原始值。再调到30V或50V记录ADC原始值。然后用线性拟合的方式得到ADC原始值到真实电压的转换关系真实电压 k × ADC原始值 bk和b用两个点算出来k (V2 - V1) / (ADC2 - ADC1) b V1 - k × ADC1电流通道校准同理。用电子负载或大功率电阻设置已知电流比如1A、3A、5A记录ADC原始值算出k和b。如果没有电子负载也可以用一个已知阻值的功率电阻加上精密电压表根据欧姆定律计算出电流值作为校准参考。校准结果存到STM32的内部Flash里比如最后一个扇区每次开机时读取这样校准一次就长期有效。F103的Flash擦写寿命是1万次每次只写几个字节完全够日常校准使用。我做过的实测校准数据大致如下测一个12.35V的稳压电源校准前STM32显示为12.01V相对误差约2.8%校准后显示为12.34V相对误差0.08%。差距是肉眼可见的。所以一定要做软件校准这是让这个项目产生质变的一步。4.3 精度测试读数波动范围和线性度校准完成后做一个简单的线性度测试把可调电源从0V逐步调到50V每5V记录一次STM32读数和万用表读数画一条曲线观察是否存在明显的非线性点。通常在中段线性度很好在接近0V和接近满量程时容易发生稍大偏差这是运放输出饱和特性和ADC两端精度退化导致的正常现象。如果发现某个点误差突出排查方向有三个一是分压电阻是不是在高电压下发热导致阻值漂移二是运放的共模输入范围是否接近上限三是ADC引脚是不是受到了地平面噪声干扰。这三个问题的解法分别在电阻选型、运放选型、PCB布局上回头再检查一下电路设计。电流通道也做类似测试。用电子负载从0A逐步加载到5A记录读数和实际电流。注意大电流下采样电阻会发热阻值随温度变化会导致测量值漂移。锰铜合金电阻的温度系数较低但铜制电阻丝或普通贴片电阻温度系数明显电流超过3A时读数会慢慢往上飘这就是自热效应。解决方法是选用额定功率远大于实际功耗的采样电阻比如5A测量用10W额定功率的电阻。5. 常见问题与排查技巧实录最后这部分直接上干货是调试过程中最容易遇到、也最常被问到的问题。我整理成速查表加详细说明方便你对照排查。5.1 ADC读数全是4095或全是0全满或全零几乎可以断定是硬件连接问题不是软件配置问题。先量一下ADC引脚的对地电压如果引脚本身是3.3V或者0V说明前端电路有问题如果引脚电压正常但读数为0检查ADC的通道配置是否与引脚对应以及ADC是否被意外关闭了时钟。还有一个常见原因是GPIO没有配置成模拟输入模式。F103的ADC引脚默认可能是浮空输入如果代码里只配置了ADC外设而没有配置GPIO读出来就是0。正确做法是GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_0 | GPIO_PIN_1; GPIO_InitStruct.Mode GPIO_MODE_ANALOG; HAL_GPIO_Init(GPIOA, GPIO_InitStruct);5.2 只有一个通道有数据另一个始终为0这个现象的经典原因有三个一是DMA缓冲区大小配置不正确。在HAL_ADC_Start_DMA(hadc, DMA_buffer, 2)中第三个参数是Buffer Length这里要填2代表两个通道转换完成后DMA搬运两次。如果你填了1DMA只搬一个数据第二通道的数据就丢了。二是ADC扫描顺序里通道的rank配置错了。你一定要确保rank1对应的是电压通道rank2对应电流通道DMA按顺序搬运数组下标0对应rank1下标1对应rank2。三是连续转换模式的配置。有些例程里配了两次连续转换导致DMA只收到第一个通道的转换结果。检查ADC_InitStruct.ContinuousConvMode只应该有一个使能。5.3 读数跳得很厉害去电源纹波和电磁干扰之外先检查软件滤波是否生效。如果滤波生效但读数仍然跳建议用示波器看ADC引脚的波形确认是否有高频毛刺。没有示波器的话把采样时间调到最长239.5周期再观察如果波动明显减小说明源头是高频噪声注入。PCB布局上ADC引脚和采样前端要离功率电感、PWM线、继电器这类强干扰源远一些。我第一版PCB把电流采样线布在了电感下方读数跳了100多个LSB改板之后降到±1个LSB以内。5.4 通电瞬间读数异常然后慢慢恢复正常这是运放和ADC上电时序导致的。运放需要几百微秒的建立时间如果在建立过程中ADC就开始采样读到的自然是错误的瞬态值。解法是增加一条上电等待逻辑初始化时先延时100ms再启动ADC和DMA。更稳的做法是在主循环里加一个标志位系统上电2秒后才允许刷新显示。5.5 校准后仍然存在固定偏差如果校准后读数比实际值低1%左右先怀疑分压电阻的精度和运放增益电阻的精度。虽然校准时已经消除了一部分误差但如果校准点的范围覆盖不足比如只校了0V和30V但实际长期测50V外推偏差会被放大。再检查ADC的参考电压。如果VDDA波动超过50mV读数就会飘。解决方法是在VDDA引脚处加一个100nF的陶瓷电容和1uF的钽电容尽量靠近芯片引脚放置。5.6 电流为0时读数不为0这大概率是运放的输出偏移电压导致的。CMOS运放的偏置在微伏级别但如果是普通三极管输入的LM358偏置可达几毫伏放大50倍后就是几百毫伏的误差。校准流程里的b截距可以消掉这个偏置但不建议完全依赖软件正确做法是选择低偏置的轨到轨运放比如MCP6002的偏置典型值约±1mV比LM358强很多。6. 项目扩展方向基础功能做完之后这个项目可以往多个方向延展具体取决于你实际的应用场景。我这里列几个我认为比较有价值的扩展思路。第一个方向是数据记录与曲线显示。把采集到的电压电流数据加上时间戳定时存储到外部Flash比如W25Q64或通过串口上传到电脑用Python脚本或Processing生成实时曲线。这对观察电池放电特性、电机启动电流等场景非常实用。配合热词里的K210与STM32通讯或ESP8266 WiFi模块还能做成远程数据监控节点。第二个方向是加入过压过流报警和保护逻辑。F103的定时器PWM输出可以控制MOSFET开关当检测到电压或电流超限时通过中断立刻关断负载。这里要注意ADC DMA的循环采集本身有延迟加上滤波器的延迟整个响应时间可能达到几十毫秒用于保护快速过流是不够的。真正的硬件保护需要另外加比较器电路在硬件层面做快速关断而不是依赖软件。第三个方向是提升采样精度。如果需求从够用变成精确可以采用外置24位ADC芯片比如ADS1256搭配高精度基准源能做到微伏级别的分辨率和0.01%级别的精度。这个方向的成本和技术门槛都会上升但对仪表类产品来说是质的飞跃。第四个方向是低功耗设计。如果这个测量模块要用电池供电可以发挥STM32的睡眠模式、ADC单次采样加定时唤醒、OLED按需刷新等策略把整体功耗压到毫瓦级别。实测中F103在72MHz正常运行时的电流约为20~30mA进入Stop模式后降到微安级别如果配合RTC定时唤醒采样一节18650电池撑几个月没问题。本文还有配套的精品资源点击获取