在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通道完全一致,双通道设计的核心价值有两个:
冗余检测:若一个通道故障,另一个通道可正常工作,避免单点故障导致电压检测失效;
不平衡检测:对比两个通道的校准电压,若差值过大,可判断电池组存在不平衡问题,提前触发告警。
写在最后
不难发现工业级嵌入式代码的核心逻辑——在有限的资源下,实现“精准、可靠、可适配”。
位掩码、位域结构体节省内存,查表法降低算力消耗,双通道校准提升可靠性,参数化设计实现多场景适配,每一个细节都体现了嵌入式工程师的严谨与考量。
如果这篇文章对你有帮助,欢迎点赞、在看、转发,让更多人看到。
欢迎关注微信公众号:光储能源站




