
简介STM32电流电压检测模块是一套基于STM32微控制器的电流电压监测方案面向嵌入式初学者和工业监控开发人员用于实现电源电压、电流的实时采集、显示与上报。压缩包共183个文件包体约25.51MB其中包含40个H头文件、38个C源文件、工程配置文件、编译生成的HEX/AXF文件以及PDF设计文档、文本说明和板卡截图基本覆盖从固件编写到硬件调试的完整开发流程。目前已有2236人浏览学习。资料内详细讲解了ADC采样初始化与滤波、分压网络电压测量、电流互感器和霍尔传感器的接入方式并附有Modbus协议读取演示和界面截图同时提供用户手册与测试报告可帮助读者快速搭建电压电流检测原型理解实时监测系统的软硬件协同设计也为扩展无线通信或上位机监控提供了良好基础。整份包体结构清晰适合作为课程设计、项目预研和工程落地的直接参考。1. 方案选型先想清楚你要测什么再决定怎么测做电流电压检测模块这件事看起来就是把两个物理量送进ADC但实际上坑不少。我早期做电池管理系统的时候直接用STM32内部ADC去采电流结果一上大电流数据就飘得没法看后面老老实实换方案。这行确实没有通吃的方案第一步就是把需求理清楚。在选型之前先回答三个问题被测电压范围是多少被测电流范围是多少精度要求是几个百分比这三个问题决定了后面所有的硬件选型和软件策略。比如你做一个3.7V锂电池的充放电监测和一个做48V直流母线监测完全不是一个设计思路。前者你直接电阻分压进ADC就够用后者你需要考虑分压比例、共模电压、甚至隔离方案。针对“STM32电流电压检测模块”这个项目我按自己的实践经历把方案分成三档互相之间没有绝对好坏只看适不适合你的场景。方案路线电流采样方式电压采样方式精度量级成本量级适用场景入门级采样电阻运放放大后进STM32 ADC电阻分压后进STM32 ADC1%左右10元以内电池监测、小功率电源、教学实验主流级外部计量芯片INA226/ADS1115走I2C专用芯片内置分压或外部精密分压0.1%~0.5%20~50元电源检测、电池管理、面板仪表工控级霍尔传感器隔离运放隔离采样芯片0.5%~1%100元起步电机驱动、工业现场、高压环境我自己最常用的是第二档也就是“采样电阻INA226”这种组合。原因很简单INA226内部自带16位ADC和可编程增益放大器电流和电压同时采集内部还能直接算出功率值通过I2C读取就好了。STM32的ADC虽然也好用但要同时保证电流和电压的量程、精度软件校准和滤波工作量会明显增加做产品的话省下的那几块钱成本不够补人力。不过我也要强调不是所有场景都需要外部芯片。如果你只是做一个课程设计、毕设、或者给实验室设备加个简单的显示器STM32内部ADC加软件滤波完全够用。我的建议是如果电流在1A以下、电压精度要求不苛刻直接STM32 ADC如果电流上了几安培、需要长时间稳定测量、或者要做产品级的东西直接上INA226这类专用芯片省心太多。选型这件事如果一开始方向选错了后面硬件改版、软件重写都是成本。所以我在每个项目开工前都会先问自己一遍这个东西是给自己看的参考值还是要作为系统闭环控制的反馈源这两个角色对精度和实时性的要求差了一个数量级。2. 硬件设计细节采样电阻、分压电阻和运放的取舍2.1 采样电阻的选型与计算采样电阻是整个电流检测模块中最关键的元件没有之一。它决定了你能测多大的电流、损耗多少功率、以及测量精度到底能达到什么水平。采样电阻选型的核心逻辑是在满足“发热可接受”和“电压压降可接受”的前提下尽量选更大的阻值。阻值越大同样电流下产生的压降越大ADC采样信号越强精度越好但压降大了会带来两个问题一是功耗发热二是对被测系统产生明显的“插入损耗”。计算过程很简单假设被测电流最大5A我们选0.01Ω的采样电阻最大压降就是50mV。用INA226内部增益放大后这个信号已经足够大。如果同样5A选0.001Ω压降只有5mV接近芯片的分辨率下限精度会明显下降。如果是STM32内部ADC直接采样那采样电阻要选更大一些比如0.02Ω到0.1Ω因为STM32的ADC在低电压段线性度相对较差信号幅度太小会导致采样值跳变。功率计算也不难P I²R5A流过0.01Ω是0.25W选额定功率1W的电阻就留了充足余量。我见过有人用0402封装的0.1Ω电阻去测2A电流结果电阻直接烧了这是典型的设计失误。封装和功率的关系大致是0603约0.1W0805约0.125W1206约0.25W2512约1W选型的时候直接用这个对照表粗筛。采样电阻的材质建议选金属箔或者合金电阻温度系数TCR尽量小于50ppm/°C。普通厚膜电阻温度系数普遍在100~200ppm温度上来后阻值会漂直接影响测量精度。如果项目对精度有要求这里不能省钱。2.2 电压检测分压电路设计电压检测相对电流要简单但同样有讲究。STM32的ADC输入范围一般是0~3.3V如果你要测12V电池、24V电源或者48V母线电压不可能直接怼进去必须先用分压电阻把电压按比例降下来。分压计算没有悬念就是电阻分压公式。以测48V为例目标是把48V映射到3.0V左右留出一点余量。分压比是48:3等于16:1。选上臂电阻100kΩ、下臂电阻6.67kΩ实际可以用6.8kΩ空载状态下分压输出约3.06V。但这里有个容易被忽略的问题STM32 ADC的输入阻抗并不是无限大虽然内部有采样电容但采样瞬间会从分压节点抽取电流如果分压电阻阻值太大采样值会偏低。我实测过直接拿1MΩ级别的电阻分压去接STM32的ADC采样值比理论值低了不少。解决办法有几个最简单的是把分压电阻控制在几十千欧级别以内这样采样时的电压跌落可以忽略不计。另一个办法是在分压输出端加一个电压跟随器用运放做缓冲隔离一劳永逸。如果你用的是INA226这类专用芯片它内部输入阻抗足够高外部甚至可以用1MΩ级别的分压电阻去减小待机功耗这也是专用方案的一个隐性优势。分压电阻的精度建议选1%甚至0.1%的精密电阻。温漂也要留意尤其在高电压场景下1%的电阻加上温度漂移总误差很容易超过3%。2.3 运放与滤波电路要点入门级方案里电流采样电阻的压降非常小比如5A电流过0.01Ω电阻才50mV这个信号直接进STM32的ADC基本就是“淹没在噪声里”。所以需要一级放大把信号抬高到ADC的最佳输入范围。运放选型的几个关键参数失调电压尽量低、支持单电源供电、输出电压轨到轨。我在项目中常用的是LM358便宜、通用但失调电压最大能到7mV对小信号测量来说有点大。测几十毫伏的信号运放自身几毫伏的失调直接就把精度干掉了。要求高一点的场景我建议换MCP6002或者OPA340这类低失调、轨到轨的运放价格贵不了几块钱但性能提升明显。放大倍数一般在10到50倍之间。注意运放的输出不能超过供电电压如果单电源3.3V供电输出最多到3.1V左右那放大后的信号要在ADC输入范围内留出余量。比如最大电流对应50mV放大30倍是1.5V这个值就很合适。滤波方面建议在ADC采样引脚前加一级RC低通滤波截止频率放在几百赫兹到几千赫兹之间。如果是50Hz工频干扰严重的环境先滤掉工频及其谐波再做采样。RC参数的计算很简单f 1/(2πRC)10kΩ电阻配100nF电容截止频率约159Hz滤除高频干扰的效果很明显。PCB布线也有讲究。采样电阻的引线要走开尔文接法也就是在电阻两端单独拉两根细线去接运放输入不要直接从大电流铜皮上引。否则铜皮上的压降会被当成采样信号的一部分电流越大误差越大。这是我反复踩过的坑第一次画板子没注意结果3A电流时测量值偏了5%以上。3. 软件实现ADC采样、滤波算法与标定校准3.1 STM32标准库下的多通道ADC配置硬件焊好之后软件是整个模块的灵魂。入门级方案里最常用的方式是STM32 ADC多通道扫描模式搭配DMA传输让数据自动送到内存数组里CPU只在需要处理时才介入。我以STM32F103为例标准库环境下配置三个通道一路电压、一路电流、一路备用温度留个扩展。启用的核心思路是ADC1扫描模式、连续转换、DMA循环传输数组大小为3。// 使用库函数版本的ADC配置关键代码 ADC_InitTypeDef ADC_InitStructure; DMA_InitTypeDef DMA_InitStructure; __IO uint16_t ADC_ConvertedValue[3]; // 开启ADC1和DMA1时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)ADC_ConvertedValue; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize 3; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Circular; DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel1, DMA_InitStructure); RCC_AHBPeriphCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_Cmd(DMA1_Channel1, ENABLE); ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode ENABLE; // 扫描模式 ADC_InitStructure.ADC_ContinuousConvMode ENABLE; // 连续转换 ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 3; ADC_Init(ADC1, ADC_InitStructure); // 三个通道采样时间统一设为55.5周期降低信号源阻抗影响 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); // 校准步骤直接关系初始精度 ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); ADC_SoftwareStartConvCmd(ADC1, ENABLE);配置完后ADC_ConvertedValue[0]到[2]会被DMA自动刷新。主循环里读取并换算就行。这中间有个细节DMA配置时MemoryInc要设为Enable而PeripheralInc设为Disable因为外设地址只有ADC1-DR一个。BufferSize是3不是1正好匹配三通道一次扫描结果。如果你用的是HAL库逻辑也一样开启ADC多通道扫描配上DMA句柄然后HAL_ADC_Start_DMA(hadc1, buffer, 3)就完成了。虽然代码风格不同但配置项一一对应不存在“学A库就没法学B库”的问题。3.2 滤波算法滑动平均与中位值滤波的组合STM32内部ADC的原始数据噪声很大尤其在开关电源附近或者电机工作的电磁环境里单次采样结果几乎没法直接用于显示和闭环控制。所以滤波是必须的环节。我最常用的组合是“中位值滤波滑动平均”。中位值滤波的作用是剔除脉冲干扰滑动平均的作用是平滑随机噪声。实现起来成本很低。#define FILTER_N 8 // 滑动窗口长度实测8~16比较合适 uint32_t Filter_Process(uint16_t raw_value) { static uint16_t buf[FILTER_N]; static uint8_t index 0; static uint8_t count 0; uint32_t sum 0; uint8_t i; buf[index] raw_value; index (index 1) % FILTER_N; if (count FILTER_N) count; // 窗口未满时直接累加平均 // 窗口满后做简单剔除峰值的平均 if (count FILTER_N) { for (i 0; i count; i) sum buf[i]; return sum / count; } // 拷贝一份做排序找最大值和最小值剔除 uint16_t tmp[FILTER_N]; for (i 0; i FILTER_N; i) tmp[i] buf[i]; // 简单的冒泡找出最大最小值N小省事 for (i 0; i FILTER_N - 1; i) { if (tmp[i] tmp[i1]) { uint16_t t tmp[i]; tmp[i] tmp[i1]; tmp[i1] t; } } // 去掉最大最小后平均 for (i 1; i FILTER_N - 1; i) sum tmp[i]; return sum / (FILTER_N - 2); }这段代码的核心思路是保留一个环形缓冲区每来一个新数据就覆盖最老的数据。窗口不满的时候直接平均避免起始阶段输出跳动过大。窗口满之后排序剔除最大值和最小值再做平均这样既滤白噪声又抗脉冲干扰代码也只占几十行。窗口长度不是越大越好。窗口长了数据平滑度好但动态响应变差。比如你做一个快速响应的过流保护电流从1A突然涨到5A滑动平均窗口太长会导致保护动作延迟。我实际项目的经验是显示类应用窗口可以放到16甚至32闭环控制类应用窗口控制在4到8之间。还有一个我一直强调的细节滤波不要只做一次。可以级联两级先对原始数据做一次中位值滤波再做滑动平均效果会好很多。而且这两级滤波放在DMA中断里做也不会占用主循环太多时间。3.3 标定校准两段式校准法的落地无论硬件怎么做PCB上的电阻精度、运放失调、ADC参考电压误差都会叠加成一个总误差。软件标定就是把这个系统误差修掉。最常用的校准方法是“两点校准”。以电压检测为例你有两个已知的参考电压比如5V和12V可以用稳压源或万用表实测得到标准值然后在系统里读取对应的ADC滤波值。这样建立一条直线包含增益误差和零点偏移两个参数。// 两段标定后的线性换算 // cal_lo_val低端实际电压用万用表实测 // cal_lo_adc低端标定时的ADC滤波值 // cal_hi_val高端实际电压 // cal_hi_adc高端标定时的ADC滤波值 // 实测得到的换算关系value k * adc b float cal_k, cal_b; void Calibration_Init(void) { cal_k (float)(cal_hi_val - cal_lo_val) / (cal_hi_adc - cal_lo_adc); cal_b (float)cal_lo_val - cal_k * cal_lo_adc; } float GetVoltage_Cal(void) { uint16_t adc_val Get_Filtered_ADC(0); // 取滤波后的ADC值 return cal_k * adc_val cal_b; }这段逻辑的关键是两点跨越整个量程的中间区域不要在量程的两端取点。比如你的量程是0到50V那么取10V和40V做标定点比取1V和49V效果更好因为大部分系统的非线性在中段最小。标定后一定要做回读验证输入一个标定点之外的电压比如25V把系统显示值和万用表实测值做对比。如果误差在接受范围内标定完成如果误差还是大多半是硬件问题而不是标定问题重点检查分压电阻精度和运放失调。如果你的项目能用专用计量芯片如INA226就不需要人工标定芯片出厂时就做了大量校准直接读寄存器就是有效值。这也是为什么工业产品普遍选择专用芯片的一个重要原因节省的不仅是元器件成本更是产线校准的人工成本。4. 常见问题与排查技巧实录4.1 采样值跳动严重的原因排查这是被问得最多的一个问题。明明电源是稳定的但STM32采样出来的值就是上下跳幅度从几十个码值到上百码值不等。我按排查顺序列一个清单现象可能原因解决方案采样值随机大幅跳动参考电压不稳或受干扰检查VREF引脚去耦电容建议10uF100nF组合两个通道数值互相干扰通道间串扰或DMA配置错位检查采样时间是否过短延长到55.5周期以上值整体偏低分压电阻阻值过大采样瞬间电压跌落降低分压电阻阻值或加电压跟随器随负载变化出现规律性偏移地线走线长地电位抬升检查PCB地线采用星型接地或加宽地线开启电机或继电器后突变高频EMI干扰采样引脚前加RC低通或软件中位值滤波其中最容易被忽略的是电源去耦。STM32的内部ADC参考电压如果直接来自LDO输出而LDO输出端只有一颗几十uF的电解电容而没有高频去耦电容那开关噪声会直接串进参考电压导致全量程采样值不规律跳动。我处理过一个案例采样值在量程中段的跳动达到几十个码值最后就是在VREF引脚旁加了100nF陶瓷电容解决。4.2 电流通道零点偏移大怎么处理零点偏移是电流检测特有的坑。假设没有负载电流理论上ADC采样值对应的是0A但实际读出来可能对应几十毫安的“虚拟电流”。这个问题的根源有两个层面。第一是运放的失调电压第二是ADC自身的偏移。解决方法就是在系统里做一次“零点采集”在硬件完全断电但MCU运行的状态下读取一次电流通道的ADC值把这个值设为电流零点之后每次计算电流都把这个零点减掉。// 零点偏移校准 // zero_offset系统无电流时采集到的ADC值 float GetCurrent_Real(void) { uint16_t adc_val Get_Filtered_ADC(1); float current (float)(adc_val - zero_offset) * cal_k_current; return current; }这种软件调零做起来简单但要注意“零点”是会漂的。温度一变运放失调就会跟着变。所以产品级的方案会加一个“定期自动调零”的功能比如每隔一段时间或者每次系统启动时自动采集一次零偏。硬件层面降低零偏的方法是选低失调电压的运放以及让运放的输入共模电压处于芯片说明书推荐的范围内。AD8603这类的精密运放对标便宜货零偏能小一个数量级代价就是价格翻几倍。4.3 程序下载与调试的常见坑最后补充几个和STM32本身相关的常见问题这是每次搞嵌入式项目都会遇到的老朋友。“Error: No STM32 target found”这个报错十有八九是以下情况之一目标芯片处于低功耗模式、SWD引脚被程序复用成了GPIO、或者连接线太长导致通信不稳定。处理办法是先按住复位键不放点击下载的同时松开复位利用“连接期间复位”的技巧绕过程序限制。如果还不行就把SWD线尽量缩短到10cm以内并降低调试时钟频率到几百kHz。还有一个很典型的问题是使用ST-LINK时提示找不到目标但板子指示灯都在亮。不少情况是板子设计时没有把SWDIO和SWCLK上拉或者复位电路设计不合理导致芯片始终处于复位状态。建议画板子时就留好4Pin的SWD调试口加10kΩ上拉。另外提一句和本模块相关的一个常见坑如果你在代码里把STM32的调试引脚PA13/PA14复用成了ADC或GPIO功能那下一次烧录时会发现找不到芯片。解决办法只能通过设置boot引脚进入ISP模式使用串口下载重新刷固件。这点在功能测试阶段要特别留意硬件上预留一个BOOT跳线能省很多事。5. 从模块到系统扩展思路与实际体会做完了电流电压检测模块的硬件和软件它也就成了很多项目的“感官”基础——给台灯加个功率统计功能给鱼缸控制器加个水泵电流监测或者给宿舍的小电器做一个用电安全提示本质上都是在ADC采集出来的数据上做文章。我见过有人直接用这个模块的数据做多条I2C总线的扩展也见过有人把采集结果通过串口上传到机智云平台做远程监测。扩展方向其实很多关键在于这让模块具备了“对外输出数据”的能力。如果你也在一块做这类模块建议在最开始就把通信接口和数据协议预留好后续接项目不用重新设计硬件。就我自己的体会做这类测量类模块最大的技巧其实是“预留”。预留一个备用ADC通道、预留一档可调的放大倍数、预留一组I2C接口这些在初期可能觉得多余但项目迭代到中后期几乎都会用上。硬件上哪怕只是预留一个0201电阻的焊盘位置都能让后续调试少焊几根飞线。最后分享一个实用的小技巧在调试电流电压模块时建议用一个自己信得过的万用表同时读参考值每改一次硬件或软件都要做一次对比记录。把这些数据记录下来形成一张简单的对照表你会发现系统误差的规律比想象中要明显得多很多问题一眼就能从表格里看出来。这个方法看似传统但比靠眼睛盯屏幕上的跳变数字靠谱太多。本文还有配套的精品资源点击获取