自制电池监控器:从采样电路到SOC估算的完整实战指南

发布时间:2026/8/26 3:08:16
自制电池监控器:从采样电路到SOC估算的完整实战指南 想象一个场景你的储能电池接线完了第二天屏幕上报 87% 电量但你心里清楚昨晚这组电池根本没充进去多少。那个 87% 到底从哪来的很多成品 Battery Monitor 计算逻辑是个黑盒你只能选择相信它没法验证它。与其猜不如自己动手做一个。这篇内容梳理的是我搭建电池监控器的完整过程从电压、电流、温度三路采样电路的设计到主控数据处理、剩余电量 SOC 估算再到校准和实测踩坑基本覆盖了做一个能长期信任的 Battery Monitor 所需要的全部关键环节。适合手里有电瓶或锂电池组、会一点 Arduino 或 ESP32、想深入理解电池状态而不是只看个百分比的朋友。1. 先别急着买零件想清楚监控器要回答的四个问题1.1 电压、电流、温度、时间监控的底层维度很多人以为电池监控器就是一个高精度电压表这是最大的误解。电压表只告诉你当前端电压是多少伏而监控器真正要回答的是这组电池此刻处在什么状态。为了支撑这个判断底层需要四个数据维度电压、电流、温度和时间。电压是最直观的。但它不是孤立使用的必须和电流方向、温度、静置时间组合起来才有意义。比如一节磷酸铁锂电芯在 0.2C 放电时电压是 3.2V停止放电 10 分钟后电压回落到 3.30V这两个数字对应的剩余电量完全不同。如果监控器只采样电压而不记录电流和时间就会把极化压降误判成电池没电了。电流维度同样关键。它不只是用来显示当前功率更重要的作用是作为时间的积分项算出累计充入和放出多少安时。很多 DIY 项目把电流采样做成单向的只能测放电不能测充电这就大大限制了监控器的使用场景。我建议一开始就做双向电流采样成本和电路复杂度增加很少但可用性高很多。温度维度经常被忽略。锂电池在低温下可用容量明显下降铅酸电池在高温下寿命快速衰减。温度采样不只为了安全和热失控预警也是 SOC 估算的重要修正参数。我自己在早期版本里省掉了温度采样结果冬天户外使用时电压判断的 SOC 偏差一度达到 15%后面还是乖乖加了 NTC。时间维度由主控内部时钟提供看起来最简单但它决定了库仑计积分的精度。如果主控在低功耗模式下频繁休眠时间基准必须用 RTC 或者带 tick 的低功耗定时器维护而不是每次开机重新计时。1.2 不同电池体系对监控方案的影响监控对象不同硬件的采样范围和处理逻辑差异会非常大。我做过的三个典型场景可以给你一个直观参照应用场景电池体系电压范围电流特点监控重点房车副电瓶12V 铅酸9~15V充放电双向浪涌大防止过放、记录充入安时太阳能储能48V 磷酸铁锂40~58V小电流浮充/持续放电长期静置下的 SOC电动工具电池包10.8V 三元锂8~13V大电流冲击动态范围大电流积分、温度保护以太阳能储能为例磷酸铁锂的放电平台非常平缓3.0V 到 3.3V 之间对应了绝大部分容量。如果你参照三元锂的电压-容量曲线去估算 SOC根本没法用。而铅酸电池的高倍率放电末端电压又会急速跌落不结合电流方向看电压很容易误判为电池故障。不同的电压范围决定了采样电路的分压比例不同电流特性决定了你选分流电阻还是霍尔传感器。这就是为什么我不建议直接照抄网上的某个成品原理图最好先明确自己的电池体系。1.3 为什么不用成品而要自己动手市面上的电池监控器从几十到几百都有功能看着也挺全但自己动手的价值不在省钱而在三个成品给不了的地方。第一是透明性。成品监控器的 SOC 算法通常是封闭的你不知道它用的是纯电压法、纯库仑计还是两者融合。遇到显示异常的时候无法定位是传感器漂了还是算法 bug。自己做的东西每一行算法都清楚出了问题能排查到具体环节。第二是灵活性。你需要自定义报警阈值、加装额外的继电器控制、把数据推到自己的私有服务器这些在成品的固件里往往做不到。自己做的话这只是改几行代码的事。第三是学习价值。电池监控是连接模拟电路、微弱信号采样、嵌入式处理和数据分析四个领域的极佳项目。做完这个项目你对 ADC 采样精度、传感器标定、积分算法的理解都会上一个大台阶。后面如果想做 BMS、UPS 控制、储能系统监控很多代码和电路经验可以直接复用。2. 采样链路设计电压、电流、温度的电路实现2.1 电压采样分压网络不是随便算个比例就行电压采样的第一步是把电池电压降到 MCU ADC 可接受的范围。以 12V 铅酸系统为例最高充电电压约 14.4V如果 MCU 的 ADC 参考是 3.3V直接分压算比例就行。我常用的组合是 R1100kΩ、R227kΩVout Vin × R2 / (R1R2)在 14.4V 时Vout 14.4 × 27 / 127 ≈ 3.06V留了约 0.24V 的安全余量在 10V 时Vout ≈ 2.13V。10bit ADC 下对应的分辨率大约是 16mV/格对 12V 系统够用。如果你用 ESP32 的 12bit ADC分辨率能做到 4mV 左右。分压电阻的阻值选择有个折中选得太小比如 1k 级别分压网络本身就会耗电长期挂在电瓶上是个不小的负载选得太大比如几个 MΩADC 的输入阻抗会和分压电阻构成新的分压导致读数偏低。我的经验是 100k 到 470k 这个区间比较安全对大多数 MCU 的输入级都能接受。电路上还要加一级 RC 低通滤波。在 ADC 引脚和地之间并联一个 0.1μF 陶瓷电容分压输出端串联一个 1k~10k 的电阻。这不仅能滤掉 PWM 充电器带来的开关噪声还能降低电荷再分配造成的采样误差。如果你用的是 48V 系统分压电阻换成 200k10k 组合但分压后电压仍然不能超过 ADC 输入范围这点务必用型号计算器确认。另一个容易被忽视的点是多节串联电池组的电压采样。不要用多个电阻分压后直接进多个 ADC 引脚因为电池组两端共模电压很高普通 ADC 引脚扛不住。要么用专门的电池采样芯片比如 BQ76920要么用隔离差分放大器。我自己在 16 串磷酸铁锂项目里直接选了 BQ76920单芯片解决 16 节采样性价比远高于自己搭 16 路差分电路。2.2 电流采样分流电阻与 INA226 的组合是性价比之王电流采样是电池监控器里最需要用心的地方。主要有两条路线分流电阻加差分放大器或者霍尔电流传感器。霍尔的好处是隔离、不插到主回路里但小电流下精度差、温漂大、价格也高。分流电阻方案线性度好、精度高、成本低只要做好绝缘和功率余量是 DIY 监控器最平衡的选择。我推荐用 INA226 这颗专用芯片而不是用运放搭差分电路。INA226 是 I2C 接口内部集成了可编程增益放大器、16bit ADC 和校准寄存器硬件设计大大简化。最关键的是它能同时采集分流电阻两端压差和母线电压一个芯片就解决了电流和电压两路采样。举个实际计算例子。最大电流 20A分流电阻选 0.001Ω1mΩ满载时压降只有 20mV。INA226 在合适的 PGA 增益下电流分辨率可以做到 2.5μV/LSB 级别也就是每 LSB 对应约 0.8mA 的电流。这个分辨率对绝大多数监控场景足够。需要注意的是虽然压降只有 20mV但分流电阻上消耗的功率是 I²R 20² × 0.001 0.4W一定要选 1W 以上功率的电阻并留出散热空间。有些朋友为了省元件用 INA219 代替 INA226。两者虽然引脚兼容但 INA219 的精度和分辨率略低而且母线电压测量范围只有 26V 或 32V对于 48V 系统不够。如果你只做 12V 铅酸INA219 可以做 48V 或者未来想扩展直接上 INA226 更省事。更高电压场景比如 60V 以上可以考虑 INA238 或者外部分压后输入。分流电阻的安装位置也有讲究。我习惯放在电池负极和负载/充电器的公共负极之间也就是低边采样。这样 INA226 的输入共模电压接近地安全性好。但要注意不要在采样电阻附近形成大的电流环路采样线要从电阻两端单独引出这个做法叫作开尔文连接后面实测章节会细说。2.3 温度采样NTC 的位置比精度更重要温度采样最简单也最容易被做错。我初期用的是 10k NTC 加 10k 上拉电阻分压后进 ADC。25℃ 时 NTC 阻值 10k分压正好是电源电压的一半非常方便。温度换算用 B 值公式T 1 / (1/T0 ln(R/R0) / B) - 273.15其中 T0 298.15KR0 是 25℃ 下的阻值B 是 NTC 的 B 值常数。代码里用这个公式算出来就是摄氏度。如果不想做浮点运算也可以预先查表把 -20℃ 到 80℃ 的温度对应 ADC 值做成数组线性插值读取。硬件上最大的坑不是换算而是测量位置。NTC 本体必须贴在电芯表面而不是贴在电池外壳的空气层里。我曾经图省事把 NTC 贴在聚合物电池外皮上结果测得温度比电芯实际温度低了近 5℃因为中间隔了一层空气。后来涂了导热硅胶让探头和电芯表面紧密贴合读数才算稳定。还有一个容易被忽略的点温度采样不要用长线直接拉到 ADC 引脚。长线会引入干扰建议用双绞线或者在 ADC 引脚附近加 0.1μF 电容。NTC 的 B 值本身有误差如果你对温度精度要求高可以用冰水混合物和沸水做两点标定然后修正 B 值。不过对电池监控这个场景±2℃ 的误差完全够用不需要过度追求。3. 主控选择与整体电路搭建3.1 主控选型从 Arduino 原型到 ESP32 联网主控的选择基本决定项目的后续空间。如果你只想在实验室里快速验证采样电路Arduino Nano 加 INA226 足够了I2C 库非常成熟十几行代码就能出数据。但 Arduino Nano 的 10bit ADC 只是作为传感器数据入口的话精度瓶颈并不明显因为 INA226 本身就是 16bit ADC最后的数据精度由它决定。如果要做联网监控直接用 ESP32。它自带的 WiFi 和蓝牙模块解决了数据查看问题我在第二个迭代版本里就是用 ESP32 读取 INA226数据通过 MQTT 推送到局域网手机随时能看。ESP32 的 ADC 比较差非线性明显不适合直接采样电池电压所以不要让 ESP32 承担分压采样的角色而是交给 INA226 这类独立 ADC 芯片ESP32 只做 I2C 读取和网络上报。对精度要求更高的场景STM32 是更好的选择。它的 ADC 线性度好支持 DMA可以连续采样后做软件平均。但 STM32 的开发门槛比 Arduino 高一些如果是第一次做项目不建议一上来就上 STM32会分散你对采样电路的注意力。我最终长期跑着的版本是 ESP32 INA226 NTC 的方案。ESP32 负责读数据、驱动 OLED、MQTT 上报INA226 负责电压电流采样。这个组合功耗约 100mA如果监控器由电池直接供电需要考虑功耗问题。如果电池容量大比如房车副电瓶 100Ah那这点耗电无所谓如果监控小容量的电动工具电池包就要考虑用低功耗模式或者只在需要查看时开机。3.2 供电与前级保护监控器不能成为电池的负担监控器本身由被测电池供电这是最方便的取电方式但有两个风险。一是电池电压波动大12V 铅酸在充电时可能到 14.4V放电时可能掉到 10V必须经过稳压才能给 MCU 供电。二是一旦接反监控器会立刻烧毁所以前级必须有防反接保护。稳压电路我用的是降压模块把 9~60V 输入降到 5V再通过 AMS1117 降到 3.3V。两级稳压的好处是效率和安全兼顾一级降压承受高压差二级 LDO 提供低噪声的 3.3V 给模拟电路。如果你不希望买现成模块LM2596 一类的降压芯片也能胜任但要注意电感选型和纹波纹波会在 ADC 采样时叠加噪声。静态功耗是很多人做完之后才发现的坑。ESP32 不处理网络时功耗也有几十毫安如果监控器长期挂在电瓶上一个月耗电就是几十安时相当于把电池自己吃空了。我的处理办法是监控器默认进入 ESP32 深度睡眠每 10 秒醒来采样一次并更新 OLED 缓存网络只在需要时建立连接。这样平均功耗能压到 10mA 以下。如果不需要 WiFi用 ATtiny 这类低功耗 MCU 配合 INA226 的 Alert 中断可以把平均功耗做到微安级别。3.3 显示与反馈本地 OLED 与远端推送本地显示我用的 0.96 寸 SSD1306 OLED128x64 分辨率I2C 接口四根线就能接好。显示内容分成三屏第一屏是电压、电流、功率第二屏是剩余 SOC、累积安时、温度第三屏是历史最大值。用按键切换上拉电阻加简单防抖即可。OLED 的刷新有个细节SSD1306 是全屏缓冲如果每 100ms 刷新一次MCU 大部分时间都在刷屏。我通常只刷新变化的数字区域调用 drawPixel 或者提前裁剪矩形区域刷新率能提高不少。不过这个对电池监控这种低频显示场景影响不大1 秒刷新一次就够。远端推送是 ESP32 版本的主打功能。我用 MQTT 协议把电压、电流、SOC 等数据以 JSON 格式发布到本机运行的 Mosquitto用 Home Assistant 或者其他面板订阅展示。这样即使人在外面也能通过内网穿透看到家里的电池状态。如果你不想搭这么重的基础设施也可以用 BLE 直接广播手机上一个轻量级 App 就能收到数据。4. 从 ADC 原始值到剩余电量数据处理与 SOC 估算4.1 原始数据预处理滤波、标定、零漂采样电路只解决测没测到的问题要把 ADC 原始值变成能用的物理量还得经过三步预处理。第一步是滤波。我的经验是连续取 50 次采样去掉最大值和最小值后再取平均这个简单的中值平均滤波能有效剔除脉冲干扰。如果想更平滑可以叠加一个一阶低通滤波每次更新时让新值只占 10% 权重filteredV (rawV - filteredV) * 0.1;这个公式的响应速度比较慢适合电池这种变化不快的对象如果用在电流突变监测上需要把系数调大或者改用移动平均。第二步是标定。ADC 的参考电压往往不是精确的 3.3VI2C 芯片的基准也有误差。必须用万用表或可调电源在至少两个电压点记录实际值和 ADC 显示值然后求出线性标定系数。具体操作我在第 5 节详细展开。第三步是零漂校准。INA226 这类放大器在小信号下总有过零偏移表现为没有电流流过时读数不是 0mA。解决办法是每次开机时在确认负载断开的等待窗口内连续读取 30 次电流值取平均作为 offset后续所有电流读数都减去这个偏移。这个操作别省直接影响库仑计的准确性。4.2 SOC 估算OCV 法与库仑计数法的正确组合SOCState of Charge是最能体现监控器水平的指标。估算思路有两类开路电压法和库仑计数法。开路电压法利用电池静置后的电压与 SOC 的映射关系查表。它的优点是静态准确缺点也明显只要电池带载极化电压就让电压读数不可信而且磷酸铁锂这种放电平台很平的电池电压查表误差能到 20% 以上。所以 OCV 法只适合电池静置足够长时间之后做校准不能实时使用。库仑计数法是对电流做时间积分电流积分得到安时。它的逻辑是累计安时 电流(A) × 时间间隔(h) SOC (剩余安时 / 额定容量) × 100%库仑计法的优点是在线连续能看实时变化缺点是误差会累积。电流的零点漂移哪怕只有 20mA一天下来就有 0.48Ah 的误差。所以必须有复位机制检测到电池充满电压达到截止值且电流降到截止电流以下时强制把 SOC 设为 100%同样在保护板截止放电时把 SOC 设为 0。实操中的正确组合是运行时用库仑计静置一段时间后用 OCV 查表对 SOC 做一次校准。具体做法是检测到主回路电流小于某个阈值比如 50mA并持续 30 分钟以上就标记为静置状态此时用电压查表值替代积分值。这个静置检测 电压校准 满充复位的三段式策略是我在多个电池体系上验证过的最稳方案。还要考虑充电效率。铅酸电池充电过程中有一部分能量变成热量和气体充进去的安时不等同于放出来的安时。简单做法是做双向积分时充电侧乘一个效率系数铅酸取 0.85锂电池接近 1。这个系数如果不是实测得到宁可保守一点避免把 SOC 估高。4.3 最小可用版本先砍 SOC做累积安时曲线如果你第一次做不一定要把 SOC 百分比做得很好看。我建议先做一个最小可用版本不估算 SOC只显示实时电压、电流、功率以及累计充入/放出的安时数。原因有二。第一SOC 估算的准确性依赖长期数据和电池模型短时间做出来的百分比很可能不准反而误导你。第二累积安时是库仑计的直接输出没有模型假设长期记录下来能看出电池的衰减趋势。比如同一组电池充满后放出的安时数从 98Ah 掉到 80Ah这就说明电池健康度下降了比任何当前 SOC 百分比都更有参考价值。在这个版本里算法逻辑很简单static float accumulatedAh 0; static uint32_t lastTime 0; void updateBatteryMonitor() { uint32_t now millis(); float dtHours (now - lastTime) / 3600000.0; accumulatedAh current * dtHours; lastTime now; }把 data 存在 EEPROM 或 flash 中每次开机都接着上一次的累积值继续计。这就是一个可使用的电池监控器接下来再逐步加入 SOC 估算你会更容易定位问题。5. 校准方法与实测踩坑记录5.1 两/三点校准流程校准是电池监控器从能显示数字到读数可靠的分水岭。我用的是两段电压校准加一段电流校准整个过程不超过 20 分钟。电压校准用可调电源和四位半万用表。把 12V 铅酸系统定在 10V 和 14V 两个校准点记录 ADC 读数求出线性映射 y ax b 的两个系数。如果你的系统电压范围很宽建议做三点校准中间点放在最常见的标称电压上这样能补偿 ADC 的非线性。校准系数存入 EEPROM每次开机加载。电流校准更简单。断开负载在 0A 状态下做零漂采样然后接一个恒流负载比如 5A记录 INA226 读数计算出实际电流与显示电流的比例系数。因为 INA226 本身支持校准寄存器你可以直接把比例写入芯片让它输出经过校准的电流值。校准后的检验方法接一个已知阻值的加热丝或电子负载对比监控器读数与万用表读数误差在 ±1% 以内就是合格。我测过自己做的这个版本电压误差约 0.03V电流约 30mA对监控用途非常满意。5.2 实测中必须警惕的误差源先说一个最常见的误差PCB 走线电阻对分流电阻的串联。我曾用过 0.005Ω 的分流电阻做 5A 电流规格PCB 走线太长走线自身多了几毫欧。5A 电流流过时走线压降接近分流电阻的压降读数直接偏高 20% 以上。排查了很久才意识到问题。解决办法就是开尔文四线连接把 INA226 的采样线从分流电阻的两端单独引出不走负载电流路径。这样即使走线有电阻也不会串入测量回路。第二个误差源是 ADC 参考电压漂移。ESP32 的 ADC 参考电压不是绝对稳定温度变化会引起读数偏移。所以我在电路里用了 3.3V LDO 给 MCU但 INA226 的基准是芯片内部的和 MCU 参考无关这个组合对温度漂移没那么敏感。如果你直接用 MCU 内置 ADC 采样分压电阻建议给模拟供电额外加一个低压差基准或者定期用校准值修正。第三个误差是电流和电压采样不同步。INA226 可以配置成同时采样但如果你用的是独立 ADC 分时读取电流和电压之间存在时间差算出来的瞬时功率可能不是真实的。对长时间累积的安时影响不大但对需要实时功率监控的场景最好用同一颗芯片同时读。5.3 两个印象深刻的 Bug先说第一个自放电把库仑计打懵了。现象是电池放了一晚上第二天早上显示 SOC 还是 100%但用万用表测单节电芯开路电压只有 3.34V明显不是满电。原因很简单电池离线期间没有电流采样库仑计依然保留昨天的积分值而电池恰好有微弱自放电或者保护板待机电流几十毫安一晚上也接近 0.5Ah。这个误差在满电附近尤其明显。解决方案就是我前面说的开机静置 OCV 校准只要检测到主回路长时间无电流就用电压表去修正 SOC。第二个 Bug 和地线环路有关。最初我用低边分流电阻采样但 INA226 的 GND 和 MCU 的 GND 接在了不同位置导致大电流时参考地之间有压差电流读数在大负载下乱跳。后来我把所有模拟地线按星形方式汇聚到分流电阻的电池负极端单独走线ADC 读数才稳定下来。这个经验在画 PCB 时特别重要电源地、采样地、数字地要分开最后单点接地。6. 从监控器走向简易 BMS可以继续深挖的方向6.1 充放电保护控制当你能稳定监控电压、电流、温度之后下一步很自然就是加上控制逻辑把它从监控器升级成保护板。硬件上只需要在放电回路里串一个 P-MOS在充电回路里串一个 N-MOS由 MCU 的 GPIO 控制通断。保护逻辑的核心是阈值判断。电压高于 3.65V/电芯磷酸铁锂时断开充电 MOS低于 2.5V 时断开放电 MOS温度高于 60℃ 或者低于 -10℃ 时同时关断充放电大电流超过设定值后做一个短延时再关断避免电机启动瞬间的浪涌误触发。这些控制逻辑本身不复杂难点在于异常恢复策略比如电压跌落触发的过放保护需要充电器接入后才允许重新导通否则电池反复进入保护状态会对电极造成损伤。自主做保护板有风险配置错误可能导致电池损坏甚至安全事故所以这一步建议只在小容量、自己熟悉的电池组上实验并在保护 MOS 之外保留物理熔断器。6.2 数据上云与长期趋势统计监控器跑稳定后我加了一个版本把数据写成 CSV 存到 SD 卡然后导入分析工具看长期数据。记录格式很简单时间戳、电压、电流、温度、SOC、累积安时。连续记录一个月后你就能看到自己实际用电池的习惯比如每天充放深度多少、最大峰值电流什么时候出现、温度变化对电压的影响如何。如果不想插 SD 卡ESP32 版本可以直接跑 MQTT 把数据上报到本机的 MySQL 或 InfluxDB用 Grafana 画曲线。我在这个方向上的体会是原始数据比结论更值钱。看到电池在某个温度下的容量表现比任何厂商宣称的指标都更贴合你自己的电池。6.3 适合扩展的硬件方案如果你想走得更远建议看几个方向。多串锂电池组采样直接用 BQ76920 / BQ76940 这类电池监控 AFE它除了采集电压和温度还内置了库仑计接口通过 SPI 或 I2C 与 MCU 通信。配合高边/低边 MOS 驱动可以做成完整的电池管理单元。电流传感器如果想做到几十安以上而且不想在回路里串电阻可以用 ACS758 这类霍尔传感器但要注意霍尔温漂最好在软件里用温度做补偿。更高端的方向是使用电池电量计芯片比如 TI 的 BQ27441、MAX17048它们内部有电池模型能自动估计 SOC不过模型需要针对具体电芯做配置上手门槛较高。我在实际项目里最深刻的体会是电池监控器没有一步到位的方案它是随着你对电池理解加深而不断迭代的项目。最开始只需要一个可信的电压和电流跑一段时间积累数据后你自然会知道下一步该加什么算法、该调整哪个阈值。这也是动手做这个项目最值得的部分。