☰
MOS管+电阻分压实现STM32低功耗电压检测电路设计
2026/9/28 18:30:38 网站建设 项目流程

1. 为什么电压检测要“绕”这么一大圈

做低功耗设备的人都知道,最难搞的不是主控休眠,而是那些你意识不到一直在耗电的外围。电压检测就是典型例子。直接拿STM32的ADC去量电池电压,要么读数飘,要么整机待机电流被一个分压电阻拉到几百微安。我前段时间做电池供电的环境监测节点,在这个环节折腾了整整两个晚上,最后用MOS管+电阻分压把电压检测电路的平均功耗压到了微安级,STM32 ADC的采样稳定性也一起解决了。下面直接给出我验证过的电路、计算过程和避坑清单,给正在做电池供电产品或者想搞懂低功耗电压检测的开发者当个参考。

1.1 直接怼ADC的三个问题

很多人第一反应是“我电池电压不超过3.3V,ADC引脚直接量不就行了?”这句话对一半。对的是电压范围,错的是你忽略了ADC引脚不是真空测量探头。它有三个实际限制:

  • 电池电压往往不是稳定值。锂电充满4.2V,低压保护3.0V,中间波动很大;如果是12V铅酸或者24V工业总线,ADC引脚根本扛不住。
  • 电阻分压解决了电压范围,但引入了另一个问题:分压电阻从电池正极到负极是一条常通回路,只要不断电就在耗电。
  • ADC采样本身会抽取电荷。分压网络输出阻抗太高时,采样电容充不满,读数偏低且不稳定。

把这三个问题放一起看,就能明白为什么一个看似简单的“测电压”功能,最终要惊动MOS管。

1.2 MOS管在这里是“看门大爷”

MOS管在这里不是用来驱动电机或者做电源开关的,它的角色很纯粹:在不需要测量时,人为切断分压电阻到地的回路。以低边N-MOS方案为例,MOS管放在分压电阻下端和GND之间,栅极接MCU的GPIO。GPIO输出低电平时,MOS管关断,分压电阻下端悬空,整条检测回路没有电流;GPIO输出高电平时,MOS管导通,分压网络正常工作,ADC读到的电压就是真实分压值。

这里有一个关键点:MOS管必须是逻辑电平型号。普通功率MOS管(比如IRF540)的开启阈值可能到3V以上,STM32 GPIO只有3.3V驱动能力,不能保证完全导通。我用的AO3400A在Vgs=2.5V时Rds(on)已经很小,适合3.3V系统。

1.3 先算清楚分压比,不能拍脑袋

分压计算的公式很简单:

Vout = Vbat * R2 / (R1 + R2)

但选R1、R2不是凑一个比例就完事,还要考虑三点:ADC量程、分压网络输出阻抗、导通时的回路电流。

我最终用的参数是:

  • R1 = 470kΩ
  • R2 = 220kΩ
  • C1 = 100nF(并联在R2两端)
  • Q1 = AO3400A
  • R3 = 1MΩ,栅极下拉

对应比例:

K = R2 / (R1 + R2) = 220k / 690k ≈ 0.319

锂电池3.0V到4.2V时,分压点电压:

Vout_min = 3.0 * 0.319 ≈ 0.956V Vout_max = 4.2 * 0.319 ≈ 1.339V

用3.3V基准、12位ADC,原始值范围大约是:

raw_min = 0.956 / 3.3 * 4095 ≈ 1186 raw_max = 1.339 / 3.3 * 4095 ≈ 1662

分辨率约为3.3V / 4095 / 0.319 ≈ 2.53mV/bit,对判断电池电量完全够用。

分压网络的输出阻抗等于R1和R2并联:

Rout = (R1 * R2) / (R1 + R2) ≈ 150kΩ

这个阻抗不算低,所以必须用后面的采样时间和外接电容来补偿。如果你测的是12V电池,可以把R1取1MΩ、R2取100kΩ,分压比约1/11,最高13.8V时分压点电压也只有1.255V,同样安全。

2. 低功耗检测电路的两种MOS管接法

低功耗电压检测的MOS管接法不是只有一种,初次接触容易在“高边”和“低边”之间犹豫。我两种都焊过,直接说结论:低边N-MOS最省事,高边P-MOS更适合需要彻底切断电源正极的场景。

2.1 低边N-MOS方案:简单但要注意地弹

以AO3400A放在低边为例,电路从电池正极开始:电池正极 → R1 → 分压节点 → R2 → MOSFET漏极 → 源极 → GND,采样节点接在R1、R2之间。栅极通过1MΩ电阻下拉,再接STM32的GPIO。

这种接法最大的好处是控制简单,GPIO推挽输出直接驱动栅极就行。由于分压回路电流只有微安级,即使MOS管导通电阻Rds(on)有几十毫欧,在分压回路上产生的附加压降也基本可以忽略。你不需要额外关心“地弹”这种高频信号概念,但要注意采样节点和主控GND的走线尽量短,避免回流路径上串入大电流开关产生的尖峰。如果板子上还有电机、继电器之类的大电流负载,ADC采样最好避开它们动作的时间窗口。

2.2 高边P-MOS方案:适合“高端”场景

如果电池正极到后级电路之间需要彻底断开,或者你想让分压电阻上端也随时彻底断开,就得用高边P-MOS。P-MOS源极接电池正极,漏极接R1,栅极通过一个NPN/N-MOS电平转换后接GPIO。MCU GPIO高电平时,NPN导通,P-MOS栅极被拉到低电平,P-MOS导通;GPIO低电平时,NPN截止,P-MOS栅极被上拉电阻拉回高电平,P-MOS关断。

高边方案能处理更高的电池电压,但驱动电路多一只管子,栅极电平翻转还需要注意Vgs耐压。以12V电池为例,P-MOS栅极直接用3.3V GPIO拉低并不能可靠导通,必须经过电平转换;一旦电池电压超过20V,还要考虑栅极钳位。对大部分3.7V锂电产品,低边N-MOS已经完全够用,没必要为“高端”两个字额外增加复杂度。

2.3 我的选型结论:低边N-MOS + 1M下拉

最终我采用低边N-MOS,原因有三:

  1. 控制路径最短,GPIO直接驱动,不需要电平转换;
  2. 分压回路电流小,MOS管导通压降可忽略;
  3. 关断后两个电阻都被断开回路,只有亚微安级漏电流。

电路参数参考下表:

元件型号/参数作用
Q1AO3400A低边开关,逻辑电平N-MOS
R1470kΩ 1%分压上臂
R2220kΩ 1%分压下臂
C1100nF X7RADC采样储能与滤波
R31MΩMOS栅极下拉,防悬空

另外强调一下MOS管导通条件:对低边N-MOS来说,Vgs = GPIO高电平时分压点? 不对,是栅极电压减去源极电压。源极接GND,所以Vgs就等于GPIO输出电压。选管子时必须看阈值电压,选择Vgs(th)在1V到1.5V以内的逻辑电平MOS管,不要选普通标准栅极阈值动辄3V的管子。

3. STM32 ADC配置里最容易翻车的几个参数

电路图看着简单,真正容易翻车的是STM32 ADC配置。很多人把CubeMX点一遍就以为结束了,实际上有好几个参数会直接影响读数的稳定性。

3.1 参考电压VDDA与内部VREFINT

很多教程默认ADC满量程是3.3V,但板子实际给VDDA的电压不一定正好是3.3V。AMS1117输出3.28V太常见了,如果不做修正,算出来的电池电压会有百分之几的误差,对电量判断可能不算致命,但如果你要卡低压报警,就会差很多。

我建议采集一次内部参考电压VREFINT,用它反推实际VDDA。不同STM32系列VREFINT的校准值和通道不同,大体思路是:

#define VREFINT_CAL_ADDR 0x1FF80078UL // 仅适用于部分系列,请按手册核对 ... uint16_t cal = *(uint16_t *)VREFINT_CAL_ADDR; uint16_t raw_vref = read_adc(ADC_CHANNEL_VREFINT); float vdda = 3.3f * cal / raw_vref;

得到vdda之后,再用这个值做电压换算。CubeMX里如果找不到VREFINT通道,多半是因为没有在ADC配置界面里把它加入Conversion Channel。

3.2 采样时间不是越长越好,但这里必须长

ADC引脚内部是一个采样保持电容,转换前需要在特定时间内充电到外部电压。分压网络输出阻抗约150kΩ,如果采样时间按默认的几十周期直接转,结果会偏低且跳动。解决办法很粗暴:用这颗MCU支持的最大采样时间。

CubeMX配置里,在ADC1的Parameter Settings中把Sampling Time选成最大档;如果是代码配置,类似这样:

sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES; // 不同型号取最大值 HAL_ADC_ConfigChannel(&hadc1, &sConfig);

不要担心“采样时间长了会多耗电”,因为ADC只在极短的测量窗口内工作,即使采样周期大几倍,对平均功耗的影响也可忽略。

3.3 用外接电容撑住采样瞬间的电荷需求

只调大采样周期还不够,因为外部150kΩ输出阻抗实在太弱,采样电容在瞬间抽取电荷后,分压点电压会跳一下。并联一个100nF电容在R2两端,相当于在ADC前面加了一只“缓冲水库”,采样瞬间由电容提供电荷,而不是从高阻分压网络里硬取。

电容选100nF不是拍脑袋:它和输出阻抗150kΩ组成的时间常数约15ms,开启MOS后要留出至少3到5个时间常数(约50ms)再采样,否则分压点没稳定,读数偏低。如果产品对响应时间敏感,可以换10nF,时间常数降到1.5ms,但抗噪声能力会弱一些。

4. 把功耗压下去:唤醒时序和代码清理

电路和ADC参数都到位后,剩下比较难的就是唤醒时序和关机时的外设状态。这部分决定了你“低功耗”到底低到什么程度。

4.1 采样窗口到底开多久:50ms这个数怎么来的

先算周期功耗。开启MOS管后,分压回路电流为:

I_div = Vbat / (R1 + R2) ≈ 4.2V / 690kΩ ≈ 6.1μA

如果每10秒唤醒一次,每次MOS开启50ms,平均电流:

I_avg = 6.1μA * 50ms / 10000ms ≈ 0.03μA

这个量级几乎可以忽略。但注意公式里没有算GPIO高电平期间栅极下拉电阻R3的电流,R3=1MΩ时是3.3V/1MΩ≈3.3μA,同样按占空比折算成平均约16nA,也不大。怕就怕你把R3取成100kΩ,栅极平均电流就变成0.16μA,虽然还是不高,但设计习惯不好。

所以唤醒时序的重点不是“把1秒改成10秒”,而是“每次测量都让电路充分稳定再关断”。我用的时序是:唤醒 → 拉高MOS栅极GPIO → 延时50ms → ADC连续采样5次取中值 → 停止ADC → 拉低MOS栅极GPIO → 进入STOP模式。

void sample_battery(void) { HAL_GPIO_WritePin(ADC_EN_GPIO_Port, ADC_EN_Pin, GPIO_PIN_SET); HAL_Delay(50); uint16_t buf[5]; for (int i = 0; i < 5; i++) { HAL_ADC_Start(&hadc1); if (HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK) buf[i] = HAL_ADC_GetValue(&hadc1); HAL_ADC_Stop(&hadc1); } // 简单冒泡排序取中值 for (int i = 0; i < 4; i++) for (int j = i + 1; j < 5; j++) if (buf[i] > buf[j]) { uint16_t t = buf[i]; buf[i] = buf[j]; buf[j] = t; } adc_value = buf[2]; HAL_GPIO_WritePin(ADC_EN_GPIO_Port, ADC_EN_Pin, GPIO_PIN_RESET); }

4.2 进入低功耗之前,ADC和GPIO必须“清干净”

很多人写完HAL_PWR_EnterSTOPMode之后发现待机电流多几十微安,第一反应是电源芯片不行,其实往往是ADC或者GPIO还挂在不停的外设上。

具体清理步骤:

  • 先调用HAL_ADC_Stop(&hadc1),把ADC转换停掉,不能只靠停止模式自动断电。
  • 把MOS栅极GPIO拉低,并确认引脚模式是GPIO_MODE_OUTPUT_PP,不是模拟输入。模拟输入状态下引脚高阻,MOS栅极可能浮空。
  • 如果C1两端的电压在停止模式期间被外部漏电路径慢慢泄放,下次唤醒后采样点会从0V开始充电,这就是为什么每次唤醒最好都固定延时,而不是读到第一个数就走。

为什么栅极要外部下拉而不依赖GPIO?因为STM32上电配置完成之前,GPIO处于浮空输入状态。如果此时MOS管栅极碰巧被外部信号抬起,分压回路就会在系统还没醒来时开始耗电。1MΩ下拉电阻成本不到一分钱,能省掉大量排查时间。

4.3 用功耗仪复测,别用万用表测“当前值”

测低功耗平均电流最忌讳用普通万用表的电流挡盯着看,因为它显示的是当前瞬时值,平均电流可能远低于量程。我习惯在电源输入端串一个10Ω采样电阻,用示波器抓压降,再换算成电流;或者直接用带统计功能的功耗仪。至少也要用万用表的mA档和uA档分别测“唤醒期间”和“停止期间”的电流,再按占空比估算平均。

5. 实测数据:从数字反推电路行为

数据比理论更能说明问题。以下是我在这套电路上实测的一组结果,用的主控是STM32L151,电池是3.7V锂电。

5.1 用VREFINT校准后的电压换算

在代码里做换算时,不要写成3.3f常量。假设某块板子经VREFINT反推实际VDDA为3.28V,测得ADC原始值raw=1523,那么分压点电压:

Vout = 1523 / 4095 * 3.28 ≈ 1.220V Vbat = Vout / K = 1.220 / 0.319 ≈ 3.825V

如果还按3.3V算,会得到1.228V,对应电池3.849V,误差约24mV,虽然不大,但对低压报警阈值的判定不够利索。

5.2 不同实测电压下的ADC原始值参考

以下是一组在3.7V锂电池实测时的参考数据,VDDA校准为3.28V,K=0.319:

电池电压分压点理论值ADC原始值软件换算值
3.00V0.956V11943.02V
3.50V1.115V13933.51V
3.70V1.179V14733.69V
4.00V1.275V15933.99V
4.20V1.339V16734.19V

误差主要来自电阻1%精度,想要更准就换成0.1%电阻,或者在出厂时用高精度万用表标定一组校正系数存进Flash。

5.3 待机功耗实测

在3.7V电源下,用示波器+10Ω采样电阻观察:

  • 停机模式下,MOS栅极为低、分压回路关断,整机静态电流约2.6μA,主要是主控低功耗电流和外设漏电流。
  • 唤醒后MOS导通、ADC正在采样的那50ms,回路电流约为1.8mA,包括主控运行电流、分压电流和GPIO电流。这个尖峰时间极短。
  • 按10秒一次估算整机平均电流约2.6μA + (1.8mA * 50ms / 10000ms) ≈ 11.6μA。
  • 如果每1秒唤醒一次,平均会变成90μA以上,依然比一直开分压电阻低很多,但对超长待机产品不建议。

6. 三次故障复盘:从2M电阻到漏电GPIO

这部分最有价值。我在调试这套电路时踩了好几次坑,有些问题不是看数据手册能发现的,必须实际测过才知道。

6.1 分压电阻取了2M,ADC读数一路漂

第一版我想当然觉得“既然要低功耗,电阻越大越好”,把R1取到2MΩ、R2取到1MΩ,比例1/3倒是没问题,但分压节点输出阻抗高达667kΩ。结果ADC原始值不但低,而且每测一次都有十几个LSB的漂移,怎么滤波都压不平。换了470k/220k并加上100nF电容之后,问题立刻消失。教训是:分压电阻要在“导通电流可接受”和“输出阻抗可接受”之间取平衡,不能只看功耗。

6.2 开启MOS后立刻采样,电压少读50mV

第二版没有加延迟,GPIO拉高后马上读ADC,得到的电压比实际低。原因在于100nF电容要先从0V充到分压值,而充电回路经过高阻分压网络,不是瞬间完成的。我记得当时3.7V电池读出来只有3.65V左右,以为MOS管压降太大,最后一查是欠稳定。加了50ms延时后读数恢复正常。建议在软件里把延时参数做成宏,后面换小电容或者其他分压值时,不用到处改。

6.3 栅极没放下拉,晚上停电再上电后电流异常

第三版的教训更隐蔽。栅极只接了GPIO,没有外部下拉电阻,实验室里现象一切正常。后来模拟“系统彻底断电再上电”场景,把电池断开后重新接上,发现整机电流比正常状态大不少。原因是STM32复位期间GPIO处于浮空输入,MOS管栅极被PCB走线耦合到高电平,分压回路被提前打开。加了一个1MΩ下拉电阻后,这个现象再没出现过。如果你用的是高边P-MOS,复位期间栅极状态更要小心,需要合适的上下拉电阻保证默认关断。

6.4 Miller效应在低功耗场景里的存在感

很多人一听到MOS管就想到米勒效应,担心开关瞬间出问题。在这个低频测量电路里,米勒电容确实存在,但只要别故意在栅极串一个大电阻把开关边沿拖到毫秒级,影响可以忽略。真正值得注意的是:不要在高边P-MOS开关切换的瞬间去启动ADC,因为Vds跳变会通过Cgd耦合到分压点,制造一个假尖峰。正确做法是让MOS管稳定导通后再采样,测量完成后也等几十微秒再关断,避免采样和开关动作重叠。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询