欢迎光临
我们一直在努力

UPS电池管理核心代码拆解上篇(附源代码解析)

在XXX的在线式UPS的软件体系中,module_battery.c是当之无愧的“电池管家”——它掌管着电池的电压检测、状态判断、续航计算、充电控制等所有核心逻辑。

今天,我们就逐点拆解module_battery.c核心代码的前10个关键细节,从文件结构到函数实现,用通俗的语言解读嵌入式工程师是如何在有限的MCU资源下,实现对电池的精细化、高可靠管控,内容可直接复制发布。

文章来源于微信公众号:光储能源站

一、文件头部与包含文件:搭建模块的“依赖地基”

 

任何嵌入式模块的开篇,都是明确“依赖清单”——module_battery.c的头部的头文件,涵盖了模块运行所需的所有基础资源,是代码可维护、可扩展的前提。

 

/ ******************************************************************************* Include Head files ******************************************************************************* / #include "..\\include\\sfr_r82d.h"      // R8C/2D MCU特殊功能寄存器定义 #include "..\\include\\def_port.h"      // 端口定义 #include "..\\include\\def_const.h"     // 常量定义 #include "..\\rtos\\os_head.h"          // RTOS头文件 #include "include\\module_head.h"      // 模块头文件 #include "..\\task\\include\\eeprom.h"   // EEPROM接口函数

 

这些头文件各司其职,缺一不可,相当于给电池管理模块配备了“全套工具箱”:

  • sfr_r82d.h:MCU寄存器的“操作手册”,用于直接操控硬件,比如读取电池电压的AD采样值、控制充电电路的开关;

  • def_port.h:I/O端口的“映射表”,提前定义好电池检测、充电控制的引脚,避免硬编码,后期修改硬件更便捷;

  • def_const.h:系统核心常量的“仓库”,存储着电压阈值、频率等固定参数,改参数无需修改业务逻辑;

  • os_head.h:RTOS系统的“安全保障”,提供临界区保护等功能,避免多任务同时操作电池数据导致出错;

  • module_head.h:模块间的“沟通桥梁”,声明其他模块的接口函数,实现电池模块与显示、保护等模块的交互;

  • eeprom.h:配置参数的“读取接口”,用于从EEPROM中读取电池校准参数、型号配置等,实现多品牌电池适配。

二、常量定义:用位掩码“浓缩”电池所有状态

电池的运行状态复杂多样(低电压、过充、欠压等),如果每个状态都用一个独立变量,会浪费MCU宝贵的RAM(工业级MCU通常仅几KB内存)。工程师采用「位掩码」设计,将11种电池状态浓缩到一个16位变量中,既省内存,又便于状态判断。

/ ******************************************************************************* *Constant declaration ******************************************************************************* / // 电池状态位定义(用于wBatteryStatus变量) #define  cBitBatStatusBatLow     0x01    // 电池低电压状态位 #define  cBitBatStatusBatUnder    0x02    // 电池欠压状态位 #define  cBitBatStatus3SPOCVAlarm 0x04    // 3阶段充电OCV报警位 #define  cBitBatStatusOverCharged  0x08    // 过充状态位 #define  cBitBatStatusChargerFail  0x10    // 充电器故障状态位 #define  cBitBatStatusDisconnected  0x20    // 电池断开状态位 #define  cBitBatStatusPreOverCharged 0x40    // 预过充状态位 #define  cBitBatStatusUnbalance  0x80    // 电池不平衡状态位 #define  cBitBatStatusTooHigh    0x100   // 电压过高状态位 #define  cBitBatStatusTooLow    0x200   // 电压过低状态位 #define  cBitBatBad          0x400   // 电池损坏状态位

核心逻辑很简洁:每个宏定义对应一个“状态位”,通过位运算可同时表示多种状态。比如 wBatteryStatus = 0x03(二进制00000011),就表示电池同时处于「低电压」和「欠压」状态;判断是否过充,只需一行代码 if(wBatteryStatus & cBitBatStatusOverCharged) 即可实现。

除此之外,代码还定义了空载功耗和充电模式常量,进一步规范逻辑:

#define  cNoLoadBatWatt       130     // 空载时每节电池的功耗(0.1W单位) // 充电模式定义 #define  cFirstMode         0       // 第一阶段充电模式 #define  cSecondMode        1       // 第二阶段充电模式 #define  cThirdMode         2       // 第三阶段充电模式 #define  cResetMode         3       // 复位模式

这里有个细节:cNoLoadBatWatt = 130 实际表示空载时每节电池消耗13W(单位为0.1W);充电模式的宏定义,替代了生硬的“魔法数字”,让后续充电逻辑更易读、易维护。

三、输出变量定义:对外提供“可读取”的核心数据

电池模块并非孤立运行,需要向其他任务(如LCD显示、告警保护)提供关键数据。这部分输出变量,就是模块的“对外接口”,仅允许其他任务通过接口函数读取,禁止直接修改,避免误操作导致电池损坏。

/ ******************************************************************************* *Battery module output  definition *these parameters can be visited by other tasks through interface functions ******************************************************************************* / INT16U  wBatAvgVoltNew;  // 最新校准后的电池电压(单位:0.1V) INT16U  wBatteryVolt;      // 滤波后的电池电压(单位:0.1V) INT8U   bBatLevel;           // 电池容量百分比(0-100%) INT32U  dwBatRemainTimeNew;   // 电池剩余放电时间(单位:秒)

每个变量的作用清晰明确,贴合实际应用场景:

  • wBatAvgVoltNew:实时校准后的电池电压,用于面板显示和保护逻辑判断(如过充、欠压检测);

  • wBatteryVolt:经过滤波处理的电压,消除波动干扰,数据更稳定,适合用于精准控制;

  • bBatLevel:电池容量百分比(0-100%),直接用于UPS面板显示,让用户直观了解电池状态;

  • dwBatRemainTimeNew:预估的电池剩余放电时间(单位:秒),用于低电量告警和用户参考。

四、可配置参数定义:适配不同场景的“灵活开关”

不同品牌、不同型号的UPS,搭配的电池参数(如电压阈值、串联节数)各不相同。这部分可配置参数,就是为了适配多种场景,通常从EEPROM中读取,支持通过接口函数修改,无需重新编译代码。

/ ******************************************************************************* *Battery module parameters  definition *these parameters can be changed by other tasks through interface functions ******************************************************************************* */ INT16U  wBatteryVoltAdjA;         // A通道电压校准因子 INT16U  wBatteryVoltAdjB;         // B通道电压校准因子 INT16U  wBatPreOverChargeVolt;    // 预过充电压阈值 INT16U  wBatOverChargeVolt;       // 过充电压阈值 INT16U  wBatOverChargeBackVolt;   // 过充恢复电压阈值 INT16U  wBatLowVolt;              // 低压告警阈值 INT16U  wBatLowBackVolt;          // 低压恢复阈值 INT8U   bBatLowLevel;          // 低容量告警阈值(百分比) INT16U  wBatLowTime;           // 低剩余时间告警阈值(秒) INT16U  wBatUnderVolt;            // 欠压关机阈值 INT16S  wChargerPWMAdj;        // 充电器PWM校准值(有符号) INT8U   bBatteryPcs;              // 电池总节数 INT8U   bBatteryStr;              // 电池串联组数 INT8U   bBatteryCell;    // 每节电池的单元格数(12V电池=6格) INT16U  wBatBadHighVolt;          // 电池损坏高电压阈值 INT16U  wBatBadHighBackVolt;      // 电池损坏高电压恢复阈值 INT16U  wBatBadLowVolt;           // 电池损坏低电压阈值 INT16U  wBatBadLowBackVolt;       // 电池损坏低电压恢复阈值

这些参数的核心作用的分为三类:

  • 电压阈值类:用于电池保护和告警判断(如过充、欠压、损坏检测),同时设置“恢复阈值”,避免告警频繁触发;

  • 电池配置类:bBatteryPcs(总节数)、bBatteryStr(串联组数)、bBatteryCell(每节单元格数),用于计算电池总电压和总容量;

  • 充电控制类:wChargerPWMAdj 用于校准充电器PWM输出,确保充电电流、电压精准可控。

五、内部变量定义:模块“自用”的核心计算载体

与输出变量相反,这部分变量仅用于模块内部逻辑计算,不对外暴露,既避免了外部任务的干扰,也降低了代码的耦合度,让模块逻辑更独立、更易维护。

/ ******************************************************************************* *Battery module parameters definition                        these variables can not be visited by other tasks ******************************************************************************* */ INT16U  wBatAvgVoltNewA;          // A通道校准后电压 INT16U  wBatAvgVoltNewB;          // B通道校准后电压 INT32U  dwOriginalBatCapacity;    // 原始电池总容量 INT32U  dwReservedBatCapacity;    // 剩余电池容量 INT32U  dwSumUsedBatCapacity;     // 累计已使用容量 INT32U dwBatWattTable[25];        // 电池功率表(用于计算剩余时间) INT16U  wChargingCapacity;        // 每秒充电容量 INT8U   bChargingChangePT;        // 充电速率切换点(百分比) INT8U   bChargingChangeRate;      // 充电速率比 INT8U   bBatFullCharged= 0;       // 电池已充满标志

关键变量解读:

  • 双通道电压变量:wBatAvgVoltNewA、wBatAvgVoltNewB,用于对比两个通道的检测结果,判断电池组是否不平衡;

  • 容量相关变量:用于计算电池剩余容量、容量百分比和剩余放电时间,是续航计算的核心;

  • 功率表数组:dwBatWattTable[25] 存储电池功率与放电时间的对应关系,为后续续航计算提供数据支撑;

  • 充电相关变量:控制充电速率的切换,实现多阶段精准充电,延长电池寿命。

六、状态标志位结构体:用位域“抠”出每一分内存

为了进一步节省内存,同时让状态判断更直观,代码在“位掩码”的基础上,设计了「位域结构体」——每个状态标志仅占1位,整个结构体仅占用2字节(16位),最大化利用MCU的有限内存。

INT16U  wBatteryStatus;           // 电池状态字(位掩码组合) struct{         INT16U  OverCharged:1;        // 过充标志         INT16U  BatVoltLow:1;         // 电压低标志         INT16U  BatLevelLow:1;        // 容量低标志         INT16U  BatTimeLow:1;         // 剩余时间低标志         INT16U  ChargerFail:1;        // 充电器故障标志         INT16U  PreOverCharged:1;     // 预过充标志         INT16U  RestOCVAlarm:1;       // 静置OCV报警标志         INT16U  BatVoltUnder:1;       // 欠压标志         INT16U  Disconnected:1;       // 电池断开标志         INT16U  LightLoadDischarging:1; // 轻载放电标志         INT16U  Unbalance:1;         // 电池不平衡标志         INT16U  BatTooHigh:1;         // 电压过高标志         INT16U  BatTooLow:1;          // 电压过低标志         INT16U  Bad:1;               // 电池损坏标志         INT16U  LoadChanged:1;        // 负载变化标志     }fBat;

核心优势:相较于直接操作位掩码,位域结构体的可读性大幅提升。比如 fBat.OverCharged = 1,直接表示“电池处于过充状态”,无需记忆位掩码数值;而 wBatteryStatus 则是将所有标志位组合后的状态字,用于多任务间的状态传递。

七、电池放电时间表:续航计算的“标准参考表”

用户最关心的“UPS还能撑多久”,核心依赖这份放电时间表。代码提前定义了25个标准放电时间点,覆盖从高功率(满载)到低功率(空载)的所有场景,通过查表法快速计算剩余续航,避开复杂的数学计算,节省MCU算力。

/ *******************************************************************************  Battery module Constant for Battery Remain Time ******************************************************************************* / // cDischargingTimeTable Unit:minute  const INT16U cDischargingTimeTable[25]= {     3,   5,   6,   7,   8,      // 高功率放电(短时间)     9,   10,  12,  15,  20,     25,  30,  35,  40,  45,     50,  60,  70,  80,  90,     100, 120, 300, 600, 1200    // 低功率放电(长时间) };

表格解读很直观:

  • 索引0:对应高功率放电,仅能持续3分钟(比如UPS带满负载);

  • 索引24:对应低功率放电,可持续1200分钟(20小时,比如仅带少量关键设备);

  • 核心作用:后续通过检测当前负载功率,查找对应索引,再通过插值计算,即可精准得到剩余放电时间。

八、电池功率表:适配多品牌电池的“核心数据库”

不同品牌、不同型号的电池,功率特性差异较大。代码通过“功率表+指针数组”的设计,支持9种不同品牌的电池,无需修改核心逻辑,只需切换功率表即可适配。

// Battery Watt Table unit of Watt: 0.1 Watt  const INT16U YUASA_REW4512[25]=      //Battery Type = 0  {     5340,   4340,   3905,   3520,   3210,   // 单位:0.1W     2950,   2742,   2387,   2020,   1617,     1360,   1178,   1041,   937,    853,     783,    675,    594,    531,    480,     439,    375,    146,    81,     40 };

以YUASA REW4512型电池为例,功率表的核心含义:

  • 索引0:5340(单位0.1W)→ 534.0W功率下,可放电3分钟(对应放电时间表索引0);

  • 索引24:40(单位0.1W)→ 4.0W功率下,可放电1200分钟(对应放电时间表索引24);

  • 通过“负载功率→查找功率表索引→匹配放电时间表→插值计算”,即可得到任意负载下的精准剩余续航。

而电池类型指针数组,则实现了多品牌快速适配:

const INT16U *BatteryType[9]= { YUASA_REW4512,  YUASA_REW2812,  YUASA_NP7_12,   YUASA_RE7_12,    CSB_GP1270,     CSB_HR1221,     CSB_GPL1272,    CSB_HRL1234,             GS_PXL12072F2V0 };

数组索引对应不同品牌电池,比如索引0是YUASA REW4512,索引1是YUASA REW2812,后续只需根据实际电池型号,切换数组索引即可完成适配。

九、初始化函数 sBatModuleInit():系统启动的“兜底保障”

初始化函数是模块的“开局第一步”,在系统启动时仅调用一次,核心作用是“清零变量、读取配置、校验参数”,确保电池管理模块从“已知、安全的状态”开始运行,避免上电后出现异常。

/ ******************************************************************************* Function Name:     sBatModuleInit Input:     swGetEEBatteryVoltWeight(): Get wEEBatteryVoltWeight from EEProm Module     swGetEEBatteryVoltAdj(): Get wEEBatteryVoltAdj from EEProm Module     bBatteryPcs: Quantity of batteries is series (Set during PowerOn Mode) Output:     wBatteryVoltAdjA:     wBatteryVoltAdjB:     wBatPreOverChargeVolt:     wBatOverChargeVolt:     wBatOverChargeBack:     wBatLowVolt:     wBatLowBackVolt: ******************************************************************************* / void sBatModuleInit(void) {     // 初始化电压变量     wBatAvgVoltNewA= 0;              // A通道电压清零     wBatAvgVoltNewB= 0;              // B通道电压清零     wBatAvgVoltNew= 0;               // 最终电压清零     // 从EEPROM读取校准参数 wBatteryVoltAdjA= swGetEEBatteryVoltAdjA();   // A通道校准因子 wBatteryVoltAdjB= swGetEEBatteryVoltAdjB();   // B通道校准因子 wChargerPWMAdj= swGetEEChargerPWMAdj();       // 充电器PWM校准 dwOriginalBatCapacity= sdwGetEEOriginalBatCapacity();  // 原始电池容量     // 容量范围限制(防止EEPROM数据异常)     if(dwOriginalBatCapacity<6000000)   // 最小容量限制     {         dwOriginalBatCapacity=6000000;  // 约1.67Ah     }     if(dwOriginalBatCapacity>235926000)   // 最大容量限制     {         dwOriginalBatCapacity=235926000;  // 约65.5Ah     }     // 初始化输出变量     dwBatRemainTimeNew= 65535;       // 剩余时间初始化为最大值     wBatteryStatus= 0;               // 状态字清零     // 清零所有状态标志位     fBat.OverCharged=0;               // 过充标志     fBat.BatVoltLow=0;               // 低压标志     fBat.BatLevelLow=0;              // 低容量标志     fBat.BatTimeLow=0;               // 低时间标志     fBat.ChargerFail=0;              // 充电器故障标志     fBat.PreOverCharged=0;           // 预过充标志     fBat.RestOCVAlarm=0;             // OCV报警标志     fBat.BatVoltUnder=0;             // 欠压标志     fBat.Disconnected=0;             // 断开标志     fBat.LightLoadDischarging=0;     // 轻载放电标志     fBat.Unbalance=0;                // 不平衡标志     fBat.BatTooHigh=0;               // 过高标志     fBat.BatTooLow=0;                // 过低标志     fBat.Bad=0;                      // 损坏标志     fBat.LoadChanged=0;              // 负载变化标志 }

函数核心逻辑分3步,每一步都体现了工业级代码的严谨性:

  • 清零变量:将电压、状态等所有变量清零,避免上电后出现随机值,导致误告警、误控制;

  • 读取参数:从EEPROM中读取校准因子、原始容量等配置参数,为后续运行提供基础;

  • 参数校验:对原始电池容量做范围限制,防止EEPROM数据损坏(如掉电、干扰)导致参数异常,用默认值兜底,保障系统稳定启动。

  • 十、电压校准函数:双通道冗余,确保检测精准

    电池电压是所有逻辑的基础,检测精度直接影响保护、续航计算的准确性。代码采用“双通道校准”设计,既补偿硬件误差,又实现冗余检测,提升可靠性。

    10.1 A通道电压校准 sBatAvgVoltCalA()

    / ******************************************************************************* Function Name:     sBatAvgVoltCalA Purpose:     According to the coefficient of battery adjustment to calculate battery     average voltage. Parameter:     wBatAvgVoltOrg: the original value of battery average voltage  Input:     wBatteryVoltAdjA: the coefficient of battery adjustment Output:     wBatAvgVoltNewA: Updated battery average voltage A     wBatAvgVoltNew: Updated battery average voltage ******************************************************************************* / void    sBatAvgVoltCalA(INT16U wBatAvgVoltOrg) {         // AD值校准转换:实际电压 = 原始AD值 × 校准因子 / 1024 wBatAvgVoltNewA = (INT16U)((INT32U)wBatAvgVoltOrg * wBatteryVoltAdjA/1024);  // 选择两个通道中较高的电压作为最终结果(安全考虑) if(wBatAvgVoltNewB> wBatAvgVoltNewA)     {         wBatAvgVoltNew= wBatAvgVoltNewB;     }

     else     {         wBatAvgVoltNew= wBatAvgVoltNewA;     } }

    关键细节解读:

    • wBatAvgVoltOrg:AD转换器采集的原始电压值,未经过校准,存在硬件误差;

    • 校准公式:实际电压 = 原始AD值 × 校准因子 / 1024,用于补偿分压电阻、AD基准等硬件差异;

    • 保守设计:选择两个通道中较高的电压作为最终结果,避免因单通道误差导致误判欠压,保障UPS应急供电的可靠性。

    10.2 B通道电压校准 sBatAvgVoltCalB()

    void sBatAvgVoltCalB(INT16U wBatAvgVoltOrg) {         // AD值校准转换 wBatAvgVoltNewB = (INT16U)((INT32U)wBatAvgVoltOrg * wBatteryVoltAdjB/1024);  // 选择两个通道中较高的电压作为最终结果  if(wBatAvgVoltNewB> wBatAvgVoltNewA)     {         wBatAvgVoltNew= wBatAvgVoltNewB;     }  else     {         wBatAvgVoltNew= wBatAvgVoltNewA;     } }

    B通道校准逻辑与A通道完全一致,双通道设计的核心价值有两个:

  • 冗余检测:若一个通道故障,另一个通道可正常工作,避免单点故障导致电压检测失效;

  • 不平衡检测:对比两个通道的校准电压,若差值过大,可判断电池组存在不平衡问题,提前触发告警。

  • 写在最后

    不难发现工业级嵌入式代码的核心逻辑——在有限的资源下,实现“精准、可靠、可适配”。

    位掩码、位域结构体节省内存,查表法降低算力消耗,双通道校准提升可靠性,参数化设计实现多场景适配,每一个细节都体现了嵌入式工程师的严谨与考量。

    如果这篇文章对你有帮助,欢迎点赞、在看、转发,让更多人看到。

    欢迎关注微信公众号:光储能源站

     

    赞(0)
    未经允许不得转载:171主机测评 » UPS电池管理核心代码拆解上篇(附源代码解析)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址