电池供电物联网终端续航设计实战:从电流剖面到 Python 寿命估算工具
电池供电物联网终端续航设计实战:从电流剖面到 Python 寿命估算工具
分类:物联网 | 关键词:低功耗、LoRaWAN、NB-IoT、电池寿命估算、峰值电流、IoTDA
一、为什么"理论续航 5 年"的设备,半年就没电了?
做过电池供电终端(水表、烟感、温湿度、门磁、资产追踪器)的同学,大概率遇到过这样的场景:
- 方案评审时:
电池容量 2600 mAh ÷ 平均电流 35 µA ≈ 8.5 年,大家很满意; - 实际部署后:冬天一来,北方的设备成片"掉线";拆回来测电池,电压还有 1.3 V/节,明明"还有电"。
问题不在于除法算错了,而在于这道除法只在实验室里成立。真实世界里,至少还有四个因素在悄悄吃掉续航:
- 截止电压:系统在多少伏就"罢工"了?电池并不是放到 0 V 才算用完;
- 温度:低温下电池内阻成倍上升,可用容量大幅缩水;
- 自放电:放在那里不用,电池也在一点点流失电量,对"十年级"设备影响很大;
- 峰值电流 × 内阻:射频发射瞬间的大电流会把电压"拽"下去,触发掉电复位。
本文用一个 LoRaWAN 温湿度节点作为例子,把这四个因素量化到一个不到 100 行的 Python 脚本里,并给出峰值电流、低温场景下的工程对策,最后介绍如何把电池状态上报到华为云 IoTDA,做到"提前换电"而不是"掉线才知道"。
二、第一步:把电流剖面画出来
续航估算的起点永远是电流剖面(current profile),而不是芯片手册首页的那个"超低功耗 xx µA"。
以一个典型的 LoRaWAN 节点为例(STM32L0 + SX1276 + 温湿度传感器,2 节 AA 串联直供),每 10 分钟上报一次,SF10:
| 阶段 | 电流 | 持续时间 | 每周期电荷量 |
|---|---|---|---|
| MCU 唤醒 + 传感器采样 | 3.0 mA | 0.50 s | 1.50 mA·s |
| LoRa 发射(14 dBm,SF10) | 44 mA | 0.35 s | 15.40 mA·s |
| RX1/RX2 接收窗口 | 12 mA | 0.20 s | 2.40 mA·s |
| 休眠(RTC + 传感器待机) | 2.5 µA | 598.95 s | 1.50 mA·s |
| 合计(周期 600 s) | 20.80 mA·s |
平均电流:
I_{avg}=\frac{\sum I_i \cdot t_i}{T}=\frac{20.80\ \text{mA·s}}{600\ \text{s}}\approx 34.7\ \mu A两个值得注意的细节:
- 发射阶段占了约 74% 的电荷量,休眠只占约 7%。在这个工况下,与其花力气把休眠电流从 2.5 µA 压到 1 µA,不如优化扩频因子(ADR)、减少重传、合并上报。
- 上表的数值要用实测替换。推荐用功耗分析仪(如 Nordic PPK2、Joulescope、Keysight N6705 等)抓一个完整周期的波形,积分得到每个阶段的电荷量。仅凭手册"典型值"估算,误差常常在 30% 以上。
三、影响"可用容量"的四个因素
3.1 截止电压:系统在几伏"罢工"?
电池规格书上的容量,通常是放电到 0.9 V/节(碱性)测得的。但如果你的系统是 2 节串联直供,而 MCU/射频在 2.4 V(即 1.2 V/节) 以下就不能稳定工作,那么后面那一段电量你根本用不上。
以碱性电池低倍率放电曲线为参考,不同截止电压下的可用容量比例大致如下(示意值,请以所选电芯的规格书曲线为准):
| 截止电压(V/节) | 1.2 | 1.1 | 1.0 | 0.9 |
|---|---|---|---|---|
| 碱性可用比例 | ~60% | ~80% | ~92% | 100% |
结论:系统最低工作电压每降低 0.1 V/节,可能多出 10%~20% 的续航。这也是很多长寿命产品宁可多花几毛钱,也要加一颗低静态电流升降压(buck-boost)芯片、把系统工作电压下探到 1.8 V 甚至更低的原因。
3.2 温度:低温是续航的头号杀手
化学电池在低温下电解液离子迁移变慢,表现为内阻上升、可用容量下降。以碱性电池为例,-20 ℃ 时内阻可能达到常温的 2~3 倍,可用容量可能只剩常温的一半左右;碳性电池更差。而锂铁(Li-FeS₂)、锂亚硫酰氯(Li-SOCl₂)等锂原电池在低温下表现好得多。
户外设备一定要按部署地最冷月来评估,而不是按 25 ℃ 实验室数据。
3.3 自放电:十年级设备不能忽略
| 电池类型 | 典型年自放电率(常温) |
|---|---|
| 碱性锌锰 | 约 2%~3% |
| 碳性锌锰 | 约 5%~8% |
| 锂铁 Li-FeS₂ | 约 1% 以内 |
| 锂亚硫酰氯 Li-SOCl₂ | 约 1% 以内 |
| 低自放电镍氢(LSD NiMH) | 约 10%~15% |
对于平均电流只有几 µA 的设备,自放电甚至可能比负载本身消耗得还多。
3.4 峰值电流 × 内阻:看不见的"压降陷阱"
电池可以近似看作"理想电压源 + 内阻"。发射瞬间的端电压:
新电池、常温下问题不大;但寿命末期 + 低温时,开路电压下降、内阻成倍上升,同样的峰值电流可能直接把电压拽到 MCU 的掉电复位(BOR)阈值以下。后面我们会用代码把这个问题算出来。
四、上代码:一个可复用的寿命估算脚本
下面的脚本把上述四个因素都建了模:
- 按阶段累加电荷,计算平均电流;
- 按截止电压、温度折算有效容量;
- 按月仿真负载消耗 + 自放电(自放电按剩余电量比例计算,比简单相减更接近实际);
- 计算峰值电流下新电池 / 寿命末期的端电压。
"""低功耗 IoT 节点电池寿命估算 + 峰值压降检查(教学示例,参数请按规格书/实测替换)"""
from dataclasses import dataclass, field
@dataclass
class Phase: # 一个上报周期内的某个工作阶段
name: str
current_mA: float
duration_s: float
@dataclass
class Chemistry:
name: str
cells: int # 串联节数
v_oc: float # 新电池单节开路电压 (V)
r_int: float # 新电池单节内阻 (Ω, 25℃)
capacity_mAh: float # 低倍率放电到 0.9 V/节 的容量
self_dis_yr: float # 年自放电率
r_eol_factor: float # 寿命末期内阻放大倍数
cold_r_factor: float # -20℃ 内阻放大倍数
cold_cap_factor: float # -20℃ 可用容量系数
usable: dict = field(default_factory=dict) # 截止电压(V/节) -> 可用容量比例
def average_current_mA(phases, period_s, sleep_uA):
q_active = sum(p.current_mA * p.duration_s for p in phases) # mA·s
t_active = sum(p.duration_s for p in phases)
q_sleep = sleep_uA / 1000 * (period_s - t_active)
return (q_active + q_sleep) / period_s, q_sleep / (q_active + q_sleep)
def life_years(chem, i_avg_mA, cutoff_V, cold=False, shelf_cap_yr=10):
cap = chem.capacity_mAh * chem.usable[cutoff_V] * (chem.cold_cap_factor if cold else 1)
remain, month = cap, 0
while remain > 0 and month < shelf_cap_yr * 12: # 按月仿真
remain -= i_avg_mA * 24 * 30 + remain * chem.self_dis_yr / 12 # 负载 + 自放电
month += 1
return cap, month / 12
def pulse_voltage(chem, i_peak_mA, v_oc_cell, eol=False, cold=False):
r = chem.r_int * (chem.r_eol_factor if eol else 1) * (chem.cold_r_factor if cold else 1)
return max(0.0, chem.cells * (v_oc_cell - i_peak_mA / 1000 * r)) # 线性模型,跌到 0 即视为崩溃
# 以下参数为常见 AA 电池的量级参考值,实际项目请替换为所选电芯规格书/实测数据
ALKALINE = Chemistry("碱性AA×2", 2, 1.55, 0.20, 2600, 0.03, 3.0, 2.5, 0.55,
{1.2: 0.60, 1.1: 0.80, 1.0: 0.92, 0.9: 1.00})
ZINC_C = Chemistry("碳性AA×2", 2, 1.60, 0.60, 900, 0.06, 3.0, 3.0, 0.40,
{1.2: 0.50, 1.1: 0.70, 1.0: 0.88, 0.9: 1.00})
LI_FES2 = Chemistry("锂铁AA×2", 2, 1.75, 0.10, 3200, 0.01, 1.5, 1.5, 0.90,
{1.2: 0.95, 1.1: 0.98, 1.0: 1.00, 0.9: 1.00})
if __name__ == "__main__":
lora = [Phase("mcu+sensor", 3.0, 0.50), Phase("lora_tx", 44.0, 0.35), Phase("lora_rx", 12.0, 0.20)]
i_avg, sleep_share = average_current_mA(lora, 600, sleep_uA=2.5)
print(f"LoRa 节点平均电流 {i_avg*1000:.1f} uA,休眠占比 {sleep_share*100:.1f}%\n")
print("== 寿命估算(10 分钟上报)==")
for c in (ALKALINE, ZINC_C, LI_FES2):
for cut in (1.2, 1.0):
row = [c.name, f"{cut} V/节"]
for cold in (False, True):
cap, yrs = life_years(c, i_avg, cut, cold)
row.append(f"{cap:.0f} mAh / {yrs:.1f} 年")
print(" | ".join(row))
print("\n== 上报周期敏感性(碱性AA×2,截止 1.0 V/节,25℃)==")
for period in (60, 300, 600, 1800, 3600):
i, share = average_current_mA(lora, period, 2.5)
_, yrs = life_years(ALKALINE, i, 1.0)
print(f"{period:>5}s | {i*1000:6.1f} uA | 休眠占比 {share*100:4.1f}% | {yrs:4.1f} 年")
print("\n== 休眠电流敏感性(30 分钟上报)==")
for s_uA in (1, 2.5, 5, 10, 20):
i, share = average_current_mA(lora, 1800, s_uA)
_, yrs = life_years(ALKALINE, i, 1.0, shelf_cap_yr=30)
print(f"休眠 {s_uA:>4} uA | {i*1000:5.1f} uA | {yrs:4.1f} 年(不计货架上限)")
print("\n== NB-IoT 250 mA 峰值压降(寿命末期 OCV=1.15 V/节)==")
for c in (ALKALINE, ZINC_C, LI_FES2):
for cold in (False, True):
v_new = pulse_voltage(c, 250, c.v_oc, cold=cold)
v_eol = pulse_voltage(c, 250, 1.15 if c is not LI_FES2 else 1.45, eol=True, cold=cold)
flag = " <-- 低于 1.8 V,系统会掉电复位" if v_eol < 1.8 else ""
print(f"{c.name}{'(-20℃)' if cold else '(25℃)'} | 新电池 {v_new:.2f} V | 末期 {v_eol:.2f} V{flag}")
说明:寿命仿真默认设了 10 年上限,代表电池货架寿命、密封老化等因素带来的实际天花板。"十年以上"的理论值在工程上意义不大。
五、运行结果与解读
5.1 不同电池 × 截止电压 × 温度(10 分钟上报,平均 34.7 µA)
| 方案 | 截止电压 | 25 ℃:有效容量 / 寿命 | -20 ℃:有效容量 / 寿命 |
|---|---|---|---|
| 碱性 AA×2 | 1.2 V/节 | 1560 mAh / 4.9 年 | 858 mAh / 2.8 年 |
| 碱性 AA×2 | 1.0 V/节 | 2392 mAh / 7.2 年 | 1316 mAh / 4.2 年 |
| 碳性 AA×2 | 1.2 V/节 | 450 mAh / 1.5 年 | 180 mAh / 0.7 年 |
| 碳性 AA×2 | 1.0 V/节 | 792 mAh / 2.5 年 | 317 mAh / 1.1 年 |
| 锂铁 AA×2 | 1.2 V/节 | 3040 mAh / 9.7 年 | 2736 mAh / 8.8 年 |
| 锂铁 AA×2 | 1.0 V/节 | 3200 mAh / 10.0 年 | 2880 mAh / 9.2 年 |
同样是碱性电池、同样的固件,"1.2 V 截止 + -20 ℃"只有 2.8 年,"1.0 V 截止 + 25 ℃"有 7.2 年,相差 2.5 倍以上。开头那个"理论 8.5 年、实际半年掉线"的故事,基本就是这样来的。
5.2 上报周期敏感性(碱性 AA×2,截止 1.0 V/节,25 ℃)
| 上报周期 | 平均电流 | 休眠电荷占比 | 估算寿命 |
|---|---|---|---|
| 1 分钟 | 324.1 µA | 0.8% | 0.9 年 |
| 5 分钟 | 66.8 µA | 3.7% | 3.9 年 |
| 10 分钟 | 34.7 µA | 7.2% | 7.2 年 |
| 30 分钟 | 13.2 µA | 18.9% | ≥10 年 |
| 60 分钟 | 7.9 µA | 31.8% | ≥10 年 |
5.3 休眠电流敏感性(30 分钟上报,不计货架上限)
| 休眠电流 | 平均电流 | 估算寿命 |
|---|---|---|
| 1 µA | 11.7 µA | 17.8 年 |
| 2.5 µA | 13.2 µA | 16.2 年 |
| 5 µA | 15.7 µA | 14.2 年 |
| 10 µA | 20.7 µA | 11.2 年 |
| 20 µA | 30.7 µA | 8.0 年 |
三个结论:
- 高频上报时,优化"发射";低频上报时,优化"休眠"。 1 分钟上报时休眠占比不到 1%,压休眠电流几乎没有意义;60 分钟上报时休眠占比超过 30%,几 µA 的漏电就很关键了。
- "小漏电"要逐项排查。 休眠从 2.5 µA 变成 20 µA(比如一个忘记关的上拉电阻、一颗静态电流偏大的 LDO、一个漏电偏大的储能电容),寿命直接少一半。
- 系统最低工作电压是"免费的续航"。 截止电压从 1.2 V/节降到 1.0 V/节,碱性方案寿命从 4.9 年提升到 7.2 年,固件一行没改。
六、峰值电流:最容易被忽视的"掉线元凶"
NB-IoT / Cat.1 模组在弱信号、高功率发射时,峰值电流可达数百 mA(具体以模组手册为准)。脚本对 250 mA 峰值的估算结果如下:
| 方案 | 新电池峰值端电压 | 寿命末期峰值端电压 |
|---|---|---|
| 碱性 AA×2(25 ℃) | 3.00 V | 2.00 V |
| 碱性 AA×2(-20 ℃) | 2.85 V | 1.55 V ⚠ 掉电复位 |
| 碳性 AA×2(25 ℃) | 2.90 V | 1.40 V ⚠ 掉电复位 |
| 碳性 AA×2(-20 ℃) | 2.30 V | 崩溃 ⚠ |
| 锂铁 AA×2(25 ℃) | 3.45 V | 2.82 V |
| 锂铁 AA×2(-20 ℃) | 3.42 V | 2.79 V |
这里最危险的不是"没电",而是**“复位死循环”**:
发射 → 电压被拽到 BOR 以下 → MCU 复位 → 电压回升(空载电压还挺高)
→ 重新启动、入网、再发射 → 再次复位 → ……
设备在这个循环里会以远高于正常的速度耗尽残余电量,而且平台侧只能看到"频繁上线又掉线",很难定位。
工程对策
1. 储能电容"削峰"
让电容在发射瞬间提供峰值电流,电池只需要提供平均电流。所需电容量的粗略估算:
例如 LoRa 发射 44 mA × 0.35 s,允许电容压降 0.3 V:C ≥ 0.044 × 0.35 / 0.3 ≈ 51 mF。可以选用低漏电的超级电容或锂离子电容(锂亚电池方案中常见的 HLC 就是这个思路)。注意:电容自身的漏电流会计入休眠电流,选型时要看漏电指标。
2. 发射前"预检"负载电压
不要只测静置电压。静置电压对剩余电量的分辨率很差,负载电压(发射过程中或发射结束瞬间的电压)才是真正的"健康指标"。
/* 伪代码:在 TX 期间采样电池电压,作为低电量判定依据 */
static uint16_t vbat_idle_mV, vbat_tx_mV;
void before_tx(void) {
vbat_idle_mV = adc_read_vbat_mV(); // 发射前(近似开路电压)
}
void on_tx_peak_timer(void) { // 发射开始后 ~50 ms 触发
vbat_tx_mV = adc_read_vbat_mV(); // 负载电压
}
bool battery_low(void) {
// 阈值按 BOR 阈值 + 余量设定,并结合温度补偿
return vbat_tx_mV < (BOR_THRESHOLD_mV + 250);
}
3. 低电量降级策略
检测到负载电压逼近阈值时:降低发射功率 / 改用更高速率、拉长上报周期、只上报告警数据,并在复位次数异常时进入"保护模式"(例如连续 3 次因 BOR 复位,就进入长休眠,只保留最低限度的告警上报),避免复位死循环。
七、电池化学选型速查
| 类型 | 标称电压 | 低温表现 | 脉冲能力 | 自放电 | 适用场景 |
|---|---|---|---|---|---|
| 碳性锌锰 | 1.5 V | 差 | 弱 | 较高 | 遥控器、钟表等低电流、室内、易更换场景 |
| 碱性锌锰 | 1.5 V | 一般 | 中 | 低 | 室内传感器、门锁、中等电流设备 |
| 锂铁 Li-FeS₂ | 1.5 V | 好 | 强 | 很低 | 户外、低温、需要较大脉冲电流的设备 |
| 1.5 V 恒压锂电(内置降压) | 1.5 V 恒定 | 视电芯 | 受内部电路限制 | 视方案 | 需要电压平稳的设备;注意末期电压骤降、电量难以通过电压判断 |
| 锂亚硫酰氯 Li-SOCl₂ | 3.6 V | 很好 | 弱(需配电容) | 极低 | 表计、十年级超低功耗终端 |
| 低自放电镍氢 | 1.2 V | 一般 | 强 | 中 | 可充电、可维护的设备 |
几点经验:
- 碳性电池不适合有射频峰值电流的 IoT 终端,它的优势在于低成本、低电流、易更换的场景;
- 户外 + 低温 + 中等脉冲,优先考虑锂铁电池;
- 锂亚电池容量大、自放电极低,但存在钝化效应,长时间静置后首次大电流放电会出现电压滞后,需要配合储能电容或做好激活设计;
- 1.5 V 恒压锂电池的电压在大部分放电过程中几乎不变,用"测电压估电量"的老办法会失效,需要换用库仑计数或"负载电压 + 次数"综合判断。
八、把电池状态"上云":基于华为云 IoTDA 的低电量预警
续航估算做得再好,现场也总有意外(极端低温、信号差导致重传增多、批次差异)。最可靠的做法是让设备持续上报电池相关数据,在平台侧做趋势判断,在掉线之前安排换电。
8.1 产品模型设计
在 IoTDA 控制台为产品新增一个 Battery 服务,建议包含以下属性:
| 属性名 | 类型 | 单位 | 说明 |
|---|---|---|---|
vbat_idle_mV |
int | mV | 发射前电压(近似开路电压) |
vbat_tx_mV |
int | mV | 发射期间负载电压(核心指标) |
temperature_c |
decimal | ℃ | 板载温度,用于温度补偿 |
tx_count |
int | 次 | 累计发射次数,用于库仑估算 |
bor_reset_count |
int | 次 | 累计掉电复位次数(异常指标) |
8.2 设备侧上报
设备通过 MQTT 向属性上报 Topic 发布消息:
Topic: $oc/devices/{device_id}/sys/properties/report
{
"services": [
{
"service_id": "Battery",
"properties": {
"vbat_idle_mV": 3020,
"vbat_tx_mV": 2860,
"temperature_c": -8.5,
"tx_count": 52314,
"bor_reset_count": 0
}
}
]
}
LoRaWAN 等非 IP 设备通常经网关/网络服务器转发,可以在边缘或编解码插件中把原始字节解析成上述属性格式;电压字段用 2 字节整数上传即可,不必为它额外增加空口开销。
8.3 平台侧判定建议
- 不要用单点阈值告警:低温夜间负载电压本就偏低,单点阈值容易误报。建议用"同温度区间下的负载电压趋势"判断;
vbat_tx_mV与vbat_idle_mV的差值变大,说明内阻在上升,是电池进入末期的典型信号;bor_reset_count一旦增长,应直接触发高优先级告警,这往往意味着设备已接近"复位死循环";- 可以通过 IoTDA 的数据转发规则,把这些属性转发到时序数据库或数据分析服务,按设备批次、部署区域做统计,为下一代产品选型提供真实数据。
九、设计检查清单
上线前,建议逐项确认:
- [ ] 用功耗分析仪实测了完整周期的电流波形,而不是只看手册典型值;
- [ ] 明确了系统最低工作电压,并据此折算了电池可用容量;
- [ ] 按部署地最冷月温度评估了容量和内阻;
- [ ] 计入了自放电和货架寿命上限;
- [ ] 用寿命末期 + 低温的内阻验算了峰值电流下的端电压,有足够余量;
- [ ] 储能电容的漏电流已计入休眠电流;
- [ ] 固件中有负载电压采样、低电量降级和防复位死循环机制;
- [ ] 设备会上报电池状态,平台侧有趋势预警而非仅单点阈值。
十、结语
电池寿命估算不是一道简单的除法,而是一个"电流剖面 × 电池特性 × 环境条件"的系统问题。把截止电压、温度、自放电、峰值内阻压降这四个因素量化之后,你会发现:很多"续航问题"并不需要换更大的电池,而是可以通过降低系统最低工作电压、合理选择电池化学体系、加一颗储能电容、改一下上报策略来解决。
文中的脚本参数都是量级参考值,欢迎大家替换成自己项目的实测数据跑一跑。如果你在电池选型或低功耗设计上踩过有意思的坑,也欢迎在评论区交流。
参考资料
- IEC 60086-1 / IEC 60086-2:原电池通用要求与物理、电气规格
- 各电池厂商公开的产品规格书与放电曲线(不同温度、不同负载条件)
- 华为云 IoTDA 官方文档:产品模型定义、设备属性上报(MQTT Topic:
$oc/devices/{device_id}/sys/properties/report)
- 点赞
- 收藏
- 关注作者
评论(0)