电池供电物联网终端续航设计实战:从电流剖面到 Python 寿命估算工具

举报
yd_287299255 发表于 2026/10/28 15:40:12 2026/10/28
【摘要】 文章结构 开头问题:为什么"理论续航 8.5 年"的设备,冬天一来就成片掉线。 电流剖面:以一个 LoRaWAN 温湿度节点为例,拆出各阶段的电流和时间,算出平均电流 34.7 µA。结论是发射占了约 74% 的电量。 四个吃掉续航的因素:截止电压、低温、自放电、峰值电流造成的压降。 Python 估算脚本:不到 100 行,按月模拟用电和自放电,并检查峰值电流时的电压。

电池供电物联网终端续航设计实战:从电流剖面到 Python 寿命估算工具

分类:物联网 | 关键词:低功耗、LoRaWAN、NB-IoT、电池寿命估算、峰值电流、IoTDA

一、为什么"理论续航 5 年"的设备,半年就没电了?

做过电池供电终端(水表、烟感、温湿度、门磁、资产追踪器)的同学,大概率遇到过这样的场景:

  • 方案评审时:电池容量 2600 mAh ÷ 平均电流 35 µA ≈ 8.5 年,大家很满意;
  • 实际部署后:冬天一来,北方的设备成片"掉线";拆回来测电池,电压还有 1.3 V/节,明明"还有电"。

问题不在于除法算错了,而在于这道除法只在实验室里成立。真实世界里,至少还有四个因素在悄悄吃掉续航:

  1. 截止电压:系统在多少伏就"罢工"了?电池并不是放到 0 V 才算用完;
  2. 温度:低温下电池内阻成倍上升,可用容量大幅缩水;
  3. 自放电:放在那里不用,电池也在一点点流失电量,对"十年级"设备影响很大;
  4. 峰值电流 × 内阻:射频发射瞬间的大电流会把电压"拽"下去,触发掉电复位。

本文用一个 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 峰值电流 × 内阻:看不见的"压降陷阱"

电池可以近似看作"理想电压源 + 内阻"。发射瞬间的端电压:

Vload=n⋅(Voc−Ipeak⋅Rint)V_{load}=n \cdot (V_{oc}-I_{peak}\cdot R_{int})

新电池、常温下问题不大;但寿命末期 + 低温时,开路电压下降、内阻成倍上升,同样的峰值电流可能直接把电压拽到 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 分钟上报时休眠占比不到 1%,压休眠电流几乎没有意义;60 分钟上报时休眠占比超过 30%,几 µA 的漏电就很关键了。
  2. "小漏电"要逐项排查。 休眠从 2.5 µA 变成 20 µA(比如一个忘记关的上拉电阻、一颗静态电流偏大的 LDO、一个漏电偏大的储能电容),寿命直接少一半。
  3. 系统最低工作电压是"免费的续航"。 截止电压从 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. 储能电容"削峰"

让电容在发射瞬间提供峰值电流,电池只需要提供平均电流。所需电容量的粗略估算:

C≥Ipeak⋅tpulseΔVC \geq \frac{I_{peak}\cdot t_{pulse}}{\Delta V}

例如 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 的数据转发规则,把这些属性转发到时序数据库或数据分析服务,按设备批次、部署区域做统计,为下一代产品选型提供真实数据。

九、设计检查清单

上线前,建议逐项确认:

  • [ ] 用功耗分析仪实测了完整周期的电流波形,而不是只看手册典型值;
  • [ ] 明确了系统最低工作电压,并据此折算了电池可用容量;
  • [ ] 按部署地最冷月温度评估了容量和内阻;
  • [ ] 计入了自放电和货架寿命上限;
  • [ ] 用寿命末期 + 低温的内阻验算了峰值电流下的端电压,有足够余量;
  • [ ] 储能电容的漏电流已计入休眠电流;
  • [ ] 固件中有负载电压采样、低电量降级和防复位死循环机制;
  • [ ] 设备会上报电池状态,平台侧有趋势预警而非仅单点阈值。

十、结语

电池寿命估算不是一道简单的除法,而是一个"电流剖面 × 电池特性 × 环境条件"的系统问题。把截止电压、温度、自放电、峰值内阻压降这四个因素量化之后,你会发现:很多"续航问题"并不需要换更大的电池,而是可以通过降低系统最低工作电压、合理选择电池化学体系、加一颗储能电容、改一下上报策略来解决。

文中的脚本参数都是量级参考值,欢迎大家替换成自己项目的实测数据跑一跑。如果你在电池选型或低功耗设计上踩过有意思的坑,也欢迎在评论区交流。


参考资料

  1. IEC 60086-1 / IEC 60086-2:原电池通用要求与物理、电气规格
  2. 各电池厂商公开的产品规格书与放电曲线(不同温度、不同负载条件)
  3. 华为云 IoTDA 官方文档:产品模型定义、设备属性上报(MQTT Topic:$oc/devices/{device_id}/sys/properties/report)
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。