尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

STM32内置VREFINT电池电量监测方案:替代库仑计的低成本高精度实现

发布时间:2026/9/29 16:15:37

资讯中心
01
ARTICLE

STM32内置VREFINT电池电量监测方案:替代库仑计的低成本高精度实现

STM32内置VREFINT电池电量监测方案:替代库仑计的低成本高精度实现
1. 为什么我要放弃库仑计改用VREFINT搞嵌入式电池供电项目的人迟早会撞上一个绕不开的问题怎么知道电池还剩多少电。我最早做手持设备的时候第一反应就是上库仑计比如TI的BQ系列或者MAXIM的燃料计芯片。贵是真的贵一颗芯片比主控还烧钱而且外围还得配采样电阻PCB面积也上去了。后来换了个思路用STM32自带的ADC去测电池分压结果发现读数飘得厉害满电和快没电的时候电压差只有零点几伏ADC参考电压本身又跟着VDD一起抖测出来的值根本没法看。直到我把STM32参考手册翻到VREFINT那一章才意识到芯片内部藏了一个现成的“标尺”。VREFINT是内部参考电压出厂时校准过典型值1.20V左右关键是它不随VDD变化。用它反推VDD再用VDD去修正电池ADC读数整个测量链路就稳了。这套方案不需要任何额外芯片成本几乎为零精度做下来在手持设备上完全够用。这篇文章适合谁看如果你正在做STM32的电池供电项目手头没有库仑计又想让电量显示别那么“玄学”那这套方法可以直接抄。我会把原理、代码、踩过的坑全部摊开讲代码基于STM32F1系列但F0、F3、F4、G0、G4这些带VREFINT的型号思路完全一样改改寄存器名就能跑。注意VREFINT不是所有STM32型号都有拿不准的话先去参考手册搜“VREFINT”或者“Internal reference voltage”确认你的芯片支持再往下看。2. 核心原理拆解VREFINT到底怎么当尺子用2.1 先搞懂ADC的参考电压从哪来STM32的ADC是逐次逼近型它的输出公式很简单ADC_Value (Vin / Vref) * Full_ScaleFull_Scale对于12位ADC就是4095。问题就出在Vref上。大部分低成本方案里Vref直接接VDD而VDD来自电池或者LDO。电池从4.2V掉到3.3VVref就跟着掉同一个Vin测出来的ADC_Value完全不一样。这就是为什么直接用VDD当参考去测电池读数会“越用越高”——因为尺子本身在缩短。VREFINT的作用就是提供一把不缩水的尺子。它内部是一个带隙基准源输出电压固定在1.20V左右具体值看芯片数据手册F1典型值1.20VF4是1.21V。这个电压不随VDD和温度大幅变化芯片出厂时还会在特定条件下测一次把校准值存到系统存储区里。2.2 用VREFINT反推VDD的数学过程思路很直接既然VREFINT的电压是已知的那我用ADC去测VREFINT得到一个ADC读数就能反推出当前VDD是多少。设VREFINT的实际电压为VrefintADC测VREFINT得到的读数为ADC_vrefint那么VDD Vrefint * 4095 / ADC_vrefint这里有个关键点Vrefint不能直接用数据手册的典型值1.20V因为每颗芯片都有个体差异典型值只是统计中心。STM32在出厂时会把这个芯片的实际Vrefint校准值存在特定地址F1系列存在0x1FFFF7BA16位单位mVF4系列存在0x1FFF7A2A。读出来代入公式精度能提高一大截。拿到VDD之后再去测电池分压Vbat ADC_bat * VDD / 4095 * (R1 R2) / R2R1和R2是分压电阻。这样整个链路里VDD的波动被VREFINT修正掉了电池电压的测量就只取决于分压电阻的精度和ADC本身的噪声。2.3 为什么不用内部温度传感器或者别的基准有人会问STM32不是还有内部温度传感器吗能不能用它来修正可以但温度传感器的绝对精度很差F1的数据手册标的是±1.5°C换算成电压误差很大而且它本身也受VDD影响。VREFINT是专门为“提供稳定参考”设计的温漂和电压调整率都比温度传感器好一个数量级。至于外部基准芯片比如REF3030精度确实高但要多一颗料、多一路电、多一块PCB面积对于成本敏感的项目来说不划算。2.4 精度到底能做到多少我实测过一批STM32F103C8T6用出厂校准值修正后VDD的测量误差在±20mV以内电池电压经过分压还原后误差在±30mV左右。对于判断“满电、中等、低电、关机”这四个档位这个精度绰绰有余。如果你要做电量百分比显示建议做分段映射不要直接线性换算因为锂电池的放电曲线不是直线。3. 硬件设计与参数计算分压电阻怎么选3.1 分压电阻的取值逻辑电池电压最高4.2VSTM32的ADC输入不能超过VDD通常VDD是3.3V。所以必须分压。分压比怎么定假设R1是上臂R2是下臂电池接R1R2接地ADC接中间。分压后的电压Vadc Vbat * R2 / (R1 R2)要求Vadc最大不超过3.3V留点余量取3.0V。电池最高4.2V那么R2 / (R1 R2) 3.0 / 4.2 ≈ 0.714取R1 10kΩR2 24kΩ分压比0.7064.2V时分压后2.96V安全。但这里有个坑电阻太大ADC采样保持电容充电慢读数会偏低电阻太小静态功耗大电池还没用就没电了。3.2 静态功耗与采样时间的平衡STM32的ADC采样保持电容大概8pF采样时间可配置。如果分压电阻是10k和24k并联等效源阻抗约7kΩ。F1的ADC手册建议源阻抗不超过10kΩ7kΩ勉强可以但采样时间要拉长。我一般把采样时间设成239.5个ADC周期这样充电充分读数稳定。静态功耗方面分压电阻一直在耗电I Vbat / (R1 R2) 4.2 / 34k ≈ 123μA123μA对于大容量电池不算什么但对于小容量电池或者长期待机设备这个电流不能忽略。我的做法是在分压电阻下臂串一个NMOS平时关断要测量的时候再打开测完立刻关掉。这样平均功耗可以降到几微安。3.3 滤波电容和PCB布局ADC输入端并一个0.1μF电容到地可以滤掉高频噪声。这个电容要尽量靠近MCU的ADC引脚走线短而粗。分压电阻的接地点要和MCU的模拟地单点连接不要和数字地混在一起。如果PCB空间允许在电池输入端再并一个10μF的电容应对电池内阻带来的瞬态压降。提示分压电阻选1%精度的金属膜电阻温漂选50ppm/°C以内的。普通碳膜电阻温漂大夏天和冬天测出来的电量能差一档。4. 软件实现从校准值读取到滤波输出4.1 读取出厂校准值F1系列的VREFINT校准值存在0x1FFFF7BA是一个16位值单位是mV。读的时候要注意这个地址是系统存储区直接指针访问即可#define VREFINT_CAL_ADDR ((uint16_t*)0x1FFFF7BA) uint16_t vrefint_cal *VREFINT_CAL_ADDR; // 单位mV典型值1200左右F4系列地址不同是0x1FFF7A2A而且F4的校准值是在VDD3.3V、温度25°C条件下测的单位也是mV。G0、G4系列有的型号校准值存在OTP区域具体地址查参考手册的“Device electronic signature”章节。4.2 ADC多通道配置与DMA搬运我习惯用DMA来搬ADC数据这样不占CPU采样也稳定。配置两个通道一个测VREFINT一个测电池分压。用规则组扫描模式DMA循环搬运到一个数组里。// 伪代码基于标准外设库 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 2; ADC_Init(ADC1, ADC_InitStructure); // 通道0测电池通道17测VREFINT ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_17, 2, ADC_SampleTime_239Cycles5); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE);VREFINT的通道号各系列不同F1是17F4是17G0是13具体查数据手册的“ADC channel selection”表。4.3 计算VDD和电池电压DMA搬回来的数组里第一个是电池通道第二个是VREFINT通道。计算顺序很重要先用VREFINT算VDD再用VDD算电池。uint16_t adc_bat adc_dma_buf[0]; uint16_t adc_vref adc_dma_buf[1]; // 用校准值算VDD单位mV uint32_t vdd_mv (uint32_t)vrefint_cal * 4095 / adc_vref; // 算电池电压单位mV分压比R2/(R1R2) uint32_t vbat_mv (uint32_t)adc_bat * vdd_mv / 4095 * (R1 R2) / R2;这里有个细节vrefint_cal是mVadc_vref是ADC读数算出来的vdd_mv也是mV。整个计算用32位整数避免溢出。4095是12位满量程如果你开了过采样或者用了16位ADC这里要改。4.4 滑动平均滤波与异常值剔除ADC读数一定有噪声直接算出来的电压会跳。我一般做两级处理先剔除明显异常的值再做滑动平均。#define FILTER_LEN 8 static uint32_t vbat_buf[FILTER_LEN]; static uint8_t buf_idx 0; uint32_t vbat_filter(uint32_t new_val) { // 如果新值和当前平均值差太多直接丢弃 uint32_t sum 0; for (int i 0; i FILTER_LEN; i) sum vbat_buf[i]; uint32_t avg sum / FILTER_LEN; if (new_val avg 200 || new_val avg - 200) { return avg; // 丢弃异常值 } vbat_buf[buf_idx] new_val; buf_idx (buf_idx 1) % FILTER_LEN; sum 0; for (int i 0; i FILTER_LEN; i) sum vbat_buf[i]; return sum / FILTER_LEN; }200mV的阈值是根据我的分压比和电池特性调的你可以根据实际情况改。滑动平均长度8对应8次采样如果采样率是10Hz响应时间0.8秒够快了。4.5 电量分段映射锂电池的放电曲线不是线性的4.2V到3.7V之间掉得慢3.7V到3.3V掉得快。直接线性映射百分比会出现“前80%用很久后20%瞬间没电”的体验。我一般做分段映射电池电压(mV)显示电量说明≥4100100%满电3900~410080%正常使用3700~390050%中等3500~370020%低电提醒3300~35005%即将关机33000%关机保护这个表是根据我用的某款锂聚合物电池的放电曲线实测出来的不同电池会有差异建议自己用电子负载测一遍。5. 实操中踩过的坑与排查技巧5.1 VREFINT读数跳动大怎么办第一次调的时候VREFINT的ADC读数在±30个LSB之间跳算出来的VDD也跟着跳。排查下来有几个原因一是采样时间太短源阻抗高采样保持电容没充满二是ADC时钟太快F1的ADC时钟不能超过14MHz我一开始设了72MHz分频6正好12MHz没问题但后来换了个板子分频设错ADC直接跑飞三是VREFINT通道使能后需要稳定时间数据手册建议第一次转换后丢弃我一般连续采8次丢掉前2次取后6次平均。5.2 电池电压测出来偏高或偏低如果测出来的电压和万用表差很多先检查分压电阻的实际阻值。我买过一批标称10k的电阻实际测出来9.7k分压比就偏了。另外ADC输入引脚如果有漏电流也会导致读数偏差。STM32的ADC输入漏电流在nA级别一般不影响但如果引脚上接了其他东西比如LED或者上拉电阻就会分流。测量引脚要专用不要复用。5.3 低电量时读数突然掉到0电池快没电的时候内阻变大ADC采样瞬间的电流会导致电压瞬间跌落采到一个很低的値。我的处理是在ADC采样前先打开分压电阻的NMOS延时1ms再采样采完立刻关断。这样分压电阻只在采样瞬间耗电电池内阻的影响也小了。另外软件上做异常值剔除连续3次低于关机阈值才真正关机避免误判。5.4 不同批次的芯片校准值差异我做过一批100台的小设备发现不同芯片的VREFINT校准值从1190mV到1215mV都有跨度25mV。如果不读校准值直接用1.20V算VDD误差能到2%电池电压误差更大。所以校准值必须读这是精度的关键。5.5 常见问题速查表现象可能原因排查方法VREFINT读数一直是0通道号配错查数据手册确认VREFINT通道VDD算出来是0校准值地址读错确认芯片型号对应的校准地址电池电压跳动大采样时间短/无滤波加长采样时间加滑动平均低电量误关机电池内阻导致瞬态跌落采样前延时连续多次确认满电显示不满分压电阻误差实测电阻值修正分压比温度变化后电量不准电阻温漂大换低温漂电阻或做温度补偿注意VREFINT的校准值是在特定温度下测的虽然带隙基准温漂小但在极端温度下还是会有偏差。如果产品要在-40°C到85°C工作建议做温度补偿用内部温度传感器读温度查表修正。6. 进阶优化让电量监测更稳更准6.1 用过采样提升有效位数STM32的ADC是12位但通过过采样可以提高到14位甚至16位。原理是连续采16次求和后右移2位得到14位结果。这样VREFINT和电池电压的分辨率都提高了小信号变化也能捕捉到。代价是采样时间变长16次采样如果每次239.5周期在12MHz ADC时钟下大概要3.2ms对于电量监测来说完全可以接受。// 过采样16次右移2位得14位结果 uint32_t adc_oversample(uint8_t channel) { uint32_t sum 0; for (int i 0; i 16; i) { ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_239Cycles5); ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); sum ADC_GetConversionValue(ADC1); } return sum 2; // 14位结果 }6.2 动态调整采样率省电电量变化很慢没必要一直高频采样。我的做法是正常工作时每10秒测一次低电量时每2秒测一次关机前每0.5秒测一次。这样平均功耗可以压到很低。用RTC唤醒或者定时器中断来触发采样采完就进低功耗模式。6.3 结合电池内阻估算健康状态电池用久了内阻会变大同样的电压下带载能力下降。我试过在采样瞬间打开一个已知负载比如一个100Ω电阻测带载前后的电压差反推内阻。内阻超过初始值2倍时提示电池老化。这个方法不需要额外硬件但负载电阻要选好太小了耗电太大了压差测不准。6.4 代码移植到其他STM32系列F1到F4的移植主要是改校准值地址和VREFINT通道号。F4的ADC是12位但参考电压计算方式一样。G0、G4系列有的型号VREFINT通道是13校准值在OTP里读法不同。H7系列ADC精度更高但VREFINT原理一样。移植的时候先查参考手册的“Internal voltage reference”章节确认校准值地址和通道号再改宏定义就行。我在实际项目里用这套方案做了三款产品最长的已经跑了两年多电量显示一直很稳。唯一一次出问题是因为分压电阻用了便宜的碳膜电阻夏天高温时阻值漂移导致电量显示偏高换了金属膜电阻就好了。所以电阻别省那几分钱选1%精度的温漂50ppm以内的整个方案的成本也就多一两毛。最后分享一个小技巧如果你觉得读校准值麻烦可以在产线上用高精度电源给每台设备校准一次把实际的VREFINT电压写进Flash这样精度比出厂校准值还高。批量生产的话这个校准步骤也就几秒钟的事。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。