ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

低功耗电压检测:MOS管+电阻分压+STM32 ADC实战指南

低功耗电压检测:MOS管+电阻分压+STM32 ADC实战指南 1. 项目背景与方案选型为什么用MOS管电阻分压做电压检测直接说结论这个方案的核心价值就是把电池电压测量从“持续耗电”变成“按需耗电”。很多做低功耗产品的工程师早期测电压都很随意——拿两个电阻搭个分压ADC引脚直接进单片机跑起来确实没问题但量一下整机静态电流发现白白多出来几十甚至上百微安的损耗。问题就出在这个分压网络一直在通电哪怕单片机已经睡死过去了电流照样从电池正极经电阻漏到地。对于电池供电的设备来说这个静态损耗是致命的。一节200mAh的纽扣电池如果分压电阻选得比较小、分压回路常驻100µA电流理论上2000小时就耗光了折合不到3个月。可实际上现在很多低功耗设备目标待机电流都是几µA甚至1µA以内常驻分压完全不可接受。所以思路就变成这样平时让分压网络和单片机的ADC完全脱离电池回路等需要读取电压的那一刻才临时把分压网络接上读完立刻切掉。谁来充当这个“临时接通”的开关MOS管最合适因为MOS管是电压控制型器件栅极几乎不取电流开关动作干净利落而且导通电阻很小不会破坏分压精度。STM32在这套方案里扮演的就是“大脑”角色它负责控制MOS管的开通和关断同时通过内置ADC采集分压后的模拟电压再做换算得到真实电压值。STM32的ADC精度在大多数电池监测场景下是够用的而且它本身在低功耗模式下有多个档位配合外部电阻分压方案能把整个系统的平均功耗压到极低。这篇文章适合谁就是那些用STM32做电池供电类产品、对静态功耗敏感、又不想用专门的电池监测芯片如MAX17048、BQ27441之类的工程师。用MOS管电阻分压是最朴素、最可控、成本最低的方案理解透了你也能摸到低功耗设计里“用开关截断泄漏路径”这一核心思想的门道。2. 电路原理与MOS管基础先把关键概念捋清楚2.1 MOS管的三个极与导通逻辑先把这个搞明白MOS管全称是金属-氧化物半导体场效应晶体管它有三个极栅极GateG、漏极DrainD、源极SourceS。栅极和另外两个极之间隔着一层氧化层相当于一个电容所以栅极基本不抽取电流这也是MOS管比三极管更适合做开关的原因之一——我用一个STM32的GPIO口去驱动它几乎不费电。按导电沟道类型分MOS管有N沟道和P沟道两大类。N沟道MOS管通常认为“栅极电压高于源极一定阈值时就导通”P沟道则是“栅极电压低于源极一定阈值时就导通”。这个阈值电压就是热词里经常提到的“导通条件”的关键参数-VGS(th)或VGS(th)。对于低功耗电压检测电路我建议首选N沟道MOS管做低边开关。原因很简单单片机输出高电平很容易3.3V单片机输出3.3V高电平驱动一个阈值电压在0.5V~1.5V的N-MOS管绰绰有余。而如果用P沟道做高边开关你就得想办法把栅极拉到比电源更高的电压才能完全关断这在电池供电的系统里非常麻烦往往需要额外的电平转换电路。2.2 电阻分压的计算思路把ADC引脚保护起来STM32的ADC输入引脚耐压通常在VDD以内也就是3.3V或者3.6V左右。如果你直接拿一个几十伏的电压去怼ADC引脚芯片直接报废。所以分压电阻的第一个作用就是把被测电压等比缩小到ADC安全范围以内。假设被测对象是单节锂电池工作电压范围2.8V~4.2V。我们要把它映射到0~3.3V的ADC量程内那就随便留一点余量让最高电压4.2V对应3.0V。分压公式其实大家都熟Vout Vin × R2 / (R1 R2)这里R1在上、R2在下Vout就是到ADC引脚的分压点。取R1100kΩ、R2100kΩ的话4.2V就变成了2.1V取R110kΩ、R220kΩ的话4.2V就变成了2.8V。具体怎么选取决于你的量程设计。但如果被测对象是一个3S锂电池组最高电压12.6V那么分压比就需要更大比如R1100kΩ、R233kΩ这样12.6V分压得到约3.1V2.5V时约0.62V都在合理范围。电阻值的原则是在ADC采集精度能接受的范围内尽量选大阻值。阻值越大分压支路本身的电流就越小。比如100k100k在4.2V下就是21µA200k200k就能降到10.5µA。不过阻值也不能无限大因为单片机的ADC内部有采样电容外部源阻抗过大会导致采样充不满精度下降。所以兼顾功耗和精度我一般控制在几十千欧到一百千欧这个量级具体怎么取舍后面第3部分细说。2.3 低边MOS管开关的完整拓扑一个NMOS解决所有问题我们接的“流行方案”大概是下面这种结构被测电池正极 → R1 → 分压节点 → R2 → N-MOS漏极N-MOS源极 → GNDN-MOS栅极 → STM32 GPIO比如PC13分压节点 → STM32 ADC输入引脚当GPIO输出低电平MOS管截止整个分压回路从R2下端断开电阻上没有电流流过。当需要测量时GPIO拉高MOS管导通R2下端相当于接到GND分压网络正常工作ADC引脚上就能读到稳定的电压。这个拓扑最大的好处是关断状态下回路里只有MOS管断开后的亚阈值漏电。选一个VGS(th)合适的NMOS室温下漏电流通常在nA级甚至pA级基本不影响整机的静态功耗。那有人会问“分压节点上不是还接着STM32的ADC引脚吗哪怕MOS管断了ADC引脚会不会通过内部ESD二极管往MCU的VDD漏电”这个问题问得很关键。实际处理办法也不复杂ADC引脚在采样结束后要配置成模拟输入模式这时候引脚是高阻态内部ESD保护二极管的反向漏电流极小可以忽略。如果实在不放心可以在分压节点对地并联一个1MΩ左右的电阻把残余电荷泄放掉同时也能给ADC引脚一个确定的电位避免悬空时乱跳。3. STM32 ADC配置实操要点3.1 分压电阻阻值如何权衡功耗与精度这部分值得单独拿出来说因为很多人第一步就栽在这里。电阻分压的采样误差主要来自两方面第一是电阻本身的精度第二是ADC输入阻抗对分压网络的负载效应。STM32的ADC内部采样保持电容大约在几pF到十几pF采样开关的导通电阻也有几百欧到几千欧。如果用标准库或者HAL库去配置采样时间设得不够、源阻抗又大那采样电容就充不满测出来的电压就会偏低。我的经验做法是先确定分压网络总电流预算。比如这个系统在测量瞬间可以接受20µA的分压电流那么对于4.2V电池总电阻就是4.2V/20µA210kΩ。我取R1110kΩ、R2100kΩ这样分压比约0.476ADC量程利用率还可以。ADC采样时间尽量拉长。在HAL库中配置采样周期为ADC_SAMPLETIME_247CYCLES_5或更高档位给采样电容足够时间通过外部电阻充电。如果需要更高的精度可以在分压节点和ADC引脚之间串联一个几十欧到几百欧的电阻再在ADC引脚对地并联一个0.1µF电容构成一个RC低通滤波器。这能同时抑制高频噪声和采样瞬间的电荷倾倒。当然也有特殊情况如果你追求极致的低功耗分压电阻选到MΩ级别那么分压网络电流可以压到1µA以内。但这种情况下ADC采样时必须等更久而且对外部走线和PCB绝缘电阻的要求也变高——PCB上如果有潮气或者助焊剂残留表面漏电可能比分压电流还大测量结果会飘得离谱。所以我通常会控制分压总电流不低于几微安。3.2 驱动MOS管的GPIO选型与电平配置STM32的GPIO驱动MOS管理论上是没什么难度的。但放到低功耗场景下面有几个容易被忽略的点。第一驱动NMOS的GPIO在待机期间必须确保输出低电平把MOS管死死关住。有些STM32型号在进入STOP/STANDBY模式后GPIO状态是默认浮空或者复位的这时候就麻烦了——如果GPIO悬浮MOS管栅极电位不确定可能处于半导通状态分压回路就在那漏电。碰到这种情况硬件上一定要在MOS管的栅极和源极之间并联一个100kΩ~1MΩ的下拉电阻确保单片机还没初始化的时候MOS管也是关闭的。第二驱动MOS管的GPIO本身不能太“弱”。STM32的GPIO输出模式可以配成推挽输出灌电流和拉电流都能到几毫安到几十毫安这对驱动MOS管栅极完全够了。但要注意MOS管的栅极是一个电容典型值几百pF到几nF如果直接用GPIO去推它上升沿下降沿会有充放电时间。在极低功耗的唤醒流程里我们一般会等待一段时间让分压点稳定这个时间通常吃GPIO驱动的边沿不至于成为瓶颈。第三如果选用的MOS管栅极电荷特别大比如大功率开关管那GPIO可能推不动要加专门的MOS管驱动芯片或者用三极管做一级缓冲。但咱们这里只是驱动一个分压电阻开关电流需求极小普通的2N7002、AO3400这种小信号MOS管GPIO直接驱动毫无压力。3.3 ADC关键参数配置与代码示例下面我直接给一段基于STM32 HAL库的代码片段演示“测量一次电压然后返回低功耗状态”的完整流程。这里假设使用STM32L系列或者F系列核心思路是一致的。// 假设ADC句柄已经初始化通道配置为ADC_CHANNEL_1 // 引脚分压节点接PA1MOS管控制接PC13 void MX_ADC1_Init(void) { hadc1.Instance ADC1; hadc1.Init.ClockPrescaler ADC_CLOCK_SYNC_PCLK_DIV4; hadc1.Init.Resolution ADC_RESOLUTION_12B; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; // 右对齐 hadc1.Init.ScanConvMode DISABLE; // 单通道模式 hadc1.Init.EOCSelection ADC_EOC_SINGLE_CONV; // 单次转换结束 hadc1.Init.LowPowerAutoWait DISABLE; hadc1.Init.LowPowerAutoPowerOff DISABLE; hadc1.Init.ContinuousConvMode DISABLE; // 单次转换不是连续转换 hadc1.Init.NbrOfConversion 1; hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_SOFTWARE_START; // 软件触发 HAL_ADC_Init(hadc1); } uint16_t ReadBatteryVoltage(void) { uint16_t adc_value; uint32_t sum 0; uint8_t i; // 1. 打开MOS管让分压回路导通 HAL_GPIO_WritePin(MOS_CTRL_GPIO_Port, MOS_CTRL_Pin, GPIO_PIN_SET); // 2. 等待分压节点稳定200us到2ms都可以根据RC时间常数调整 HAL_Delay(2); // 3. 读取几次求平均 for (i 0; i 8; i) { HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); adc_value HAL_ADC_GetValue(hadc1); sum adc_value; } adc_value sum / 8; // 4. 关断MOS管断开分压回路 HAL_GPIO_WritePin(MOS_CTRL_GPIO_Port, MOS_CTRL_Pin, GPIO_PIN_RESET); return adc_value; }这段代码里有一个细节值得注意我把ADC配置成了单次转换而不是连续转换。在低功耗设计里连续转换模式会让ADC在整个转换期间持续消耗电流而且转换完还得手动Stop。我们只需要在某一个瞬间读一次电压单次转换就够了读完就关闭功耗更可控。另外每次采样后最好调用一下HAL_ADC_Stop(hadc1)把ADC彻底复位确保下一次进入低功耗模式前ADC模块不持有任何使能状态。这一步很多人会漏掉结果就是从测量模式切到低功耗模式时ADC时钟没有被关断白白多耗电流。3.4 校准与换算把ADC原始值变成真实电压ADC的原始读数是一个12位的数字范围0~4095。把它换算成电压公式是Vmeasure (ADC值 / 4095) × VREF这里的VREF对于大多数STM32来说就是VDDA引脚上的电压。如果你的STM32跑在3.3V那就把VREF当成3.3V代入。但要注意VDDA不一定等于3.3V。如果你用LDO输出3.3V去供电那么VDDA就是3.3V如果你直接把电池电压接VDDA那这时候VDDA会随着电池放电而降低测量结果就会跟着“飘”。更严谨的做法是用STM32内部的VREFINT通道。STM32内部有一个稳定的带隙基准电压大约在1.2V左右而且出厂时校准过。你可以先通过ADC读取内部基准通道反推出当前的VDDA实际值再用这个实际值去换算电池电压。这个方法在经济型应用中能显著提高精度尤其在电池直接供电的场合。在我的项目里我一般这样换算// 先读取内部基准电压对应的ADC原始值内部通道比如ADC_CHANNEL_VREFINT float GetVdda(void) { uint16_t vrefint_adc; HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); vrefint_adc HAL_ADC_GetValue(hadc1); HAL_ADC_Stop(hadc1); return 3.3f * 4095.0f / vrefint_adc; // 这里以校准值为参考偏移量需要按芯片手册修正 }不过这个公式要结合具体型号的校准值调整不同批次芯片的VREFINT出厂校准值不一样。你把校准值写在程序里换算精度能做到1%以内对一般电池电压检测完全够用。4. 低功耗状态机与电源管理联动4.1 只在唤醒瞬间测电压测完立刻睡赛道已经很明确了电压不是一直要测而是在固定的唤醒周期里测一次就继续睡。这套功耗优化逻辑必须跟单片机的低功耗模式深度绑定。STM32有很多低功耗档位比如Sleep、Stop、Standby。对于电池供电设备用得最多的还是Stop或者Standby因为RTC可以继续运行定时唤醒MCU。拿Standby模式举例唤醒后程序从复位向量重新开始内部RAM数据不保证保留部分型号有备份寄存器所以你需要在上电初始化阶段就判断这次唤醒是“上电复位”还是“RTC唤醒复位”然后决定是否执行完整的初始化流程还是只做一次电压采样然后继续睡。伪代码大概是if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB) ! RESET) { // 清除Standby标志 __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB); // 这是RTC唤醒复位快速采样一次电压 voltage_mv get_battery_voltage(); update_low_power_record(voltage_mv); // 继续进入Standby EnterStandbyMode(); } else { // 这是真正的上电复位完整初始化 SystemInit(); MX_GPIO_Init(); MX_ADC1_Init(); MX_RTC_Init(); // ... }这种写法非常实用省去了从Standby模式返回时一堆外设重新初始化的开销。不过要留意从Standby唤醒后程序从头开始执行所以初始化一定要写成分支结构。如果你是直接睡进Stop模式然后由中断唤醒那所有的外设状态还保留着处理逻辑又不一样。4.2 进入低功耗前要处理的引脚与时钟很多人把程序写完功能正常一测功耗就傻眼明明睡了电流还是好几个毫安。排查起来发现不是MCU的问题而是分压电阻没断开或者GPIO配置不当导致外围器件漏电。进Standby/Stop模式之前必须给你的MCU列一个“断电清单”AD采集引脚确保已经切换到模拟输入模式并且ADC外设处于关闭状态。MOS控制引脚确保输出低电平然后在这个基础上用万用表量一下MOS管的栅极电压确认是0V。其他未使用的外设时钟用__HAL_RCC_ADC_CLK_DISABLE()、__HAL_RCC_GPIOx_CLK_DISABLE()等调用把外设时钟全部关掉。所有GPIO的电平策略能输出低的就输出低能配置成模拟的就配置成模拟绝不能让它悬空。我还见过一个坑有人把MOS管的控制引脚设成了GPIO输出程序里也拉了低但忘记把引脚外设时钟打开结果引脚处于复位状态默认是浮空输入MOS管栅极电位不明分压回路一直在漏电。这个问题很难查因为从代码上看“引脚输出低”的逻辑写得完全没问题实际上是GPIO时钟没使能。所以初始化GPIOC之后最好先把控制引脚强制输出低再做其他外设初始化。4.3 配合RTC定时唤醒做到无人值守自动检测如果你的系统用RTC定时唤醒比如每10秒钟醒来测一次电压那么可以把RTC闹钟中断设为这个周期。在中断服务函数里置一个标志位然后从Stop模式唤醒主循环看到标志位再执行测量。Stop模式和Standby模式的区别在于Stop模式下功耗稍高µA级但唤醒后能继续执行不用重新初始化外设代码逻辑简单Standby模式下功耗最低nA级但唤醒等于复位RAM清零必须用标志位区分复位源。具体选哪个要看你的整机功耗预算和开发复杂度。我做实际项目时如果功耗预算在10µA以内通常用Stop模式加RTC唤醒如果需要压到1µA以下就只能上Standby模式。前者调试简单后者省电但折腾各有利弊。5. 那些年我们踩过的坑MOSADC排查实录5.1 常见问题速查表现象可能原因解决措施静态电流超过预期几百µAMOS管未关断GPIO悬空导致栅极电平不定栅源之间加100kΩ下拉电阻GPIO配置推挽输出低测量电压明显偏低分压电阻阻值过大ADC采样时间不足拉长采样周期或减小分压电阻测量电压读取跳动很大ADC引脚悬空时引入噪声或外部走线太长分压节点并联0.1µF电容把采样次数加多再取平均电池电压上升时测出的ADC值反而下降参考电压波动VDDA随电池下降改用VREFINT内部基准反推VDDAMOS管发热导通后有较大压降MOS管选型不对导通电阻RDS(on)过大换用低RDS(on)的MOS管如AO3400第一次读的值明显异常后面正常采样电容初始电荷不确定丢弃第一次转换结果从第二次开始取平均进入低功耗模式后电流逐渐上升分压回路的电容还在缓慢放电MOS管关闭后仍有残压增加一个1MΩ泄放电阻加快节点放电5.2 关于“米勒效应”和开关瞬间功耗热词里很多人搜“MOS管的米勒效应”这个现象在咱们这种小电流开关电路里不是主要矛盾但还是要提一句MOS管的栅极和漏极之间存在寄生电容Cgd当栅极给一个电压阶跃在Vgs上升到米勒平台时因为Cgd要充电所以Vgs会短暂停留在平台电压附近这段时间MOS管处于半导通状态漏极电流和漏源电压同时存在产生开关损耗。在咱们的电压检测场景里MOS管开关频率极低每次开/关间隔至少几百毫秒甚至几秒所以米勒效应带来的开关损耗完全可以忽略。但如果有人想把这个开关电路复用做PWM控制比如控制分压回路以几百赫兹频率通断那就得认真考虑米勒平台的功率损耗了。这也是为什么“PWM接MOS管发热”会成为热词的原因——不是MOS管本身问题而是开关频率太高、米勒平台拖得太久热功耗爆表。5.3 采样值过滤为什么单次采样让人血压飙升用ADC采集电压时很多人发现相邻两次读数的差值能到几十甚至上百尤其当外部走线较长、附近有开关电源时。这时候你需要软件滤波而不是单纯怀疑ADC坏了。我的方案是“中值平均”组合连续采样5次去掉最大值和最小值剩下3次取平均。开销不大但效果立刻改善。如果系统对实时性要求不高这个滤波器的效果几乎等于硬件加了一个大电容。另外特别提醒内部基准通道VREFINT不要和外部分压通道混在一起连续采样。在STM32上ADC的采样序列可以配置多个通道按顺序转换但内部基准通道的采样时间要求不同于普通通道混在一起可能导致VREFINT采不准进而把电压换算也带偏。5.4 环境温度漂移带来的精度变化电阻分压的另一个暗坑是温度系数。普通厚膜电阻的温漂大约±100ppm/°C到±200ppm/°C如果R1和R2的温漂方向不一致分压比例就会随温度变化。在户外设备、车载、或者冬天户外夏天暴晒的场景里这个误差可能比你想象的更明显。我的办法是分压电阻选同一种类型、同一种封装的最好是同一批次这样它们随温度变化的趋势基本一致分压比变化就能相互抵消一部分。如果精度要求很高那就老老实实上0.1%精度的金属膜电阻甚至选温度系数更低的产品。反正分压电阻也就两颗成本多几毛钱但省去后面大量调试时间。6. 个人复盘与扩展思路这套“MOS管电阻分压STM32 ADC”的组合我在很多项目里反复用过从两节7号电池供电的温湿度计到24V工业传感器再到3S锂电池供电的便携设备基本思路都一样差别只在电阻分压比和MOS管选型上。真正做到最后最深的体会是低功耗不是一个外设的事而是硬件电路和软件状态机共同协作的结果。MOS管管住了外部分压网络的漏电路径STM32的低功耗模式管住了内部数字逻辑的功耗两者结合起来静态电流才能从毫安级降到微安级甚至更低。如果你后续想继续优化方向也有几个一是把MOS管换成更小漏电的型号比如带集成栅源电阻的专用负载开关二是把MCU换成STM32L4或者STM32U5这类超低功耗系列启用LPUART和LPTIM等低功耗外设三是在软件里做电量曲线校准根据开路电压估算剩余容量让检测数据更有工程意义。每个方向都能单独展开但核心思路不变——按需唤醒按需供电绝不浪费一点能量。
返回列表