电量计 -- 新国标安全时间与保护记录系统架构

电量计新国标安全时间与保护记录系统架构

1. 概述

本文档系统说明基于 BST85226 电量计芯片的新国标安全时间和保护记录功能的系统架构设计。该系统实现了电池安全事件的实时检测、状态记录、Flash 持久化存储以及主机端数据获取的完整闭环。

1.1 系统架构总览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
┌─────────────────────────────────────────────────────────────────────────┐
│ 系统架构图 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ I2C/SMBus ┌──────────────────────────┐ │
│ │ 主机 MCU │◄──────────────────►│ BST85226 电量计 │ │
│ │ (dev_bst7001.c) │ + PEC校验 │ (Firmware) │ │
│ │ │ │ │ │
│ │ ┌────────────┐ │ GPIO INT │ ┌────────────────────┐ │ │
│ │ │ 故障查询 │ │◄────────────────────│ │ 保护检测模块 │ │ │
│ │ │ SBS 0x8D │ │ 下降沿触发 │ │ (protect_handle) │ │ │
│ │ │ SBS 0x90 │ │ │ └────────────────────┘ │ │
│ │ └────────────┘ │ │ │ │ │
│ └──────────────────┘ │ ▼ │ │
│ │ ┌────────────────────┐ │ │
│ │ │ LOG 记录模块 │ │ │
│ │ │ (update_log_data) │ │ │
│ │ └────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌────────────────────┐ │ │
│ │ │ Flash 存储 │ │ │
│ │ │ (21KB 循环存储) │ │ │
│ │ └────────────────────┘ │ │
│ └──────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘

1.2 核心需求

根据新国标要求,电量计需要:

  • 存储 4800 条异常记录(电芯最大 1200 循环 × 4 条异常记录)
  • 记录异常类型、触发时间、异常数据、充放电状态、触发电芯 ID
  • 支持主机按索引查询历史记录

2. LOG 数据结构设计

2.1 LOG_T 结构体定义

1
2
3
4
5
6
7
8
9
typedef struct
{
uint16_t logindex:14; // LOG索引: 最大16383, 足够128KB/8bytes=16384条
uint16_t charge_status:2; // 充放电状态: 0-放电, 1-充电, 2-空闲
uint16_t raw:16; // 原始数据: OV电压/UV电压/OT温度/UT温度/OC电流/SC电流/ISC电流
uint32_t timefw:24; // 时间戳: 单位分钟, 基准2025-01-01 00:00, 最大31.92年
uint32_t safety_status:4; // 安全状态: 0-OV, 1-UV, 2-OT, 3-UT, 4-OCC, 5-OCD, 6-SC, 7-ISC
uint32_t cell_id:4; // 电芯ID: OV/UV时记录触发的电芯(1-cell1, 2-cell2)
} LOG_T;

结构体大小:8 字节(紧凑设计,满足存储效率要求)

2.2 字段说明

字段 位宽 说明
logindex 14 bits 日志索引,从0开始递增,支持循环覆盖
charge_status 2 bits 故障发生时的充放电状态
raw 16 bits 故障原始数据(电压mV/温度℃/电流mA)
timefw 24 bits 相对时间戳(分钟),最大约31.92年
safety_status 4 bits 安全事件类型编码
cell_id 4 bits OV/UV时标识触发的电芯编号

2.3 Safety Status 编码表

编码 事件类型 raw 字段含义 cell_id 含义
0x01 OV (过压) 触发电芯电压 (mV) 1=Cell1, 2=Cell2
0x02 UV (欠压) 触发电芯电压 (mV) 1=Cell1, 2=Cell2
0x03 OT (过温) 温度 (℃) 0 (不适用)
0x04 UT (欠温) 温度 (℃) 0 (不适用)
0x05 OCC/OCD (过流) 电流 (mA) 0 (不适用)
0x06 SC (短路) 电流 (mA) 0 (不适用)
0x07 ISC (内短路) 漏电流 (mA) 0 (不适用)

3. 安全事件检测机制

3.1 事件触发流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
┌─────────────────────────────────────────────────────────────────────────┐
│ Safety Event 触发与记录流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ 硬件检测 │ OV/UV/OT/UT (DFE 寄存器) │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ protect_ │────►│ fault_imode │ 保存当前充放电状态 │
│ │ handle() │ │ = stm_check │ (在MOS关闭前捕获) │
│ └──────┬──────┘ │ _imode() │ │
│ │ └─────────────┘ │
│ ▼ │
│ ┌─────────────┐ │
│ │ update_log │ 设置标志位 │
│ │ = 1 │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 关闭 MOS │ Set_TXGPIO(0) 保护电路 │
│ └──────┬──────┘ │
│ │ │
│ ▼ (下一轮 mainloop) │
│ ┌─────────────┐ │
│ │ update_log_ │ 检测 safe_event,记录到 LOG_T │
│ │ data(1) │ 使用之前保存的 fault_imode │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ write_log_ │ 写入 Flash (循环存储) │
│ │ into_flash()│ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ INT GPIO │ 下降沿通知主机有新故障 │
│ │ 触发 │ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

3.2 各类事件检测方式

3.2.1 硬件检测事件 (OV/UV/OT/UT)

这些事件由 DFE (Digital Front End) 硬件自动检测:

1
2
3
4
5
6
7
8
9
10
// protect_handle() 函数中的检测逻辑
if (hw_prot_new & GSTATUS_PROT_HWOVCELL1) {
hw_prot_status.cell1_ov_status = 1;
need_close_mos = 1;
}
if (hw_prot_new & GSTATUS_PROT_HWOVCELL2) {
hw_prot_status.cell2_ov_status = 1;
need_close_mos = 1;
}
// ... UV/OT/UT 类似

特点

  • 硬件实时检测,响应速度快
  • 通过 DFE STATUS 寄存器读取状态
  • 触发后立即关闭 MOS 保护电路

3.2.2 软件检测事件 (OCC/OCD)

过流保护由软件算法判断:

1
2
3
4
5
6
7
// protect_handle() 函数中的软件过流检测
if (occ_prot.status == 1 && occ_status_prev == 0) {
need_close_mos = 1; // 新触发OCC
}
if (ocd_prot.status == 1 && ocd_status_prev == 0) {
need_close_mos = 1; // 新触发OCD
}

特点

  • 软件定时采样电流并判断
  • 支持可配置的阈值和延时
  • 状态变化触发保护

3.2.3 短路复位事件 (SC)

SC 是硬件级保护,触发后系统复位:

1
2
3
4
5
6
7
8
9
// 系统启动时检测 SC 标志
log_init();

// SC检测:从复位标志寄存器读取
uint16_t rst_status = Chip_BLK_Get_Buffer(CH_BLANK2);
if (rst_status & 0x08) { // SC flag
fault_imode = STM_IMODE_INDSG; // SC通常发生在放电时
update_log = 1;
}

特点

  • 硬件保护触发系统复位
  • 复位后从寄存器恢复 SC 标志
  • 无法获取实际发生时的 charge_status,推断为放电状态

3.2.4 内短路检测事件 (ISC)

内短路通过 ISRW1 算法检测:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// mainloop 中的 ISC 检测
#if __ISRW1_ENABLE__
isrw1_integration_update();
{
static uint8_t isc_alert_prev = 0;
uint32_t isc_result = isrw1_integration_get_alert_result();
uint8_t isc_alert_now = isc_result & 0xFF; // severity level

// 新检测到ISC alert时,保存charge状态并触发log记录
if (isc_alert_now > 0 && isc_alert_prev == 0) {
fault_imode = stm_check_imode();
update_log = 1;
}
isc_alert_prev = isc_alert_now;
}
#endif

特点

  • 基于电压-库仑法检测内部漏电流
  • 分级告警 (mild/moderate/severe)
  • 周期性检测,检测到新告警时记录

3.3 充放电状态捕获机制

为保证记录的是故障发生时的充放电状态,而非写入 LOG 时的状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 全局变量保存故障发生时的状态
STM_IMODE_T fault_imode = STM_IMODE_IDLE;

// 保护触发时立即保存状态(在 MOS 关闭前)
if (need_close_mos) {
fault_imode = stm_check_imode(); // 捕获当前状态
update_log = 1;
Set_TXGPIO(0); // 然后才关闭 MOS
}

// stm_check_imode() 返回值映射
// STM_IMODE_INDSG (-1) → charge_status = 0 (放电)
// STM_IMODE_INCHG (1) → charge_status = 1 (充电)
// STM_IMODE_IDLE (0) → charge_status = 2 (空闲)

4. Flash 存储设计

4.1 存储区域规划

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌─────────────────────────────────────────────────────────────────┐
│ Flash 存储布局 (64KB) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Page 0-32: Bootloader & Firmware Code │
│ ────────────────────────────────────────────────────────────── │
│ Page 33: 持久化数据区 (1KB) │
│ - 激活信息 (activate_time, lifetime_en) │
│ - 校准数据 (cell offset, current slope) │
│ - 累计放电容量 (agemah) │
│ ────────────────────────────────────────────────────────────── │
│ Page 34-52: LOG1 区域 (19KB) │
│ - 19 × 1024 / 8 = 2432 条记录 │
│ ────────────────────────────────────────────────────────────── │
│ Page 53-60: Reserved │
│ ────────────────────────────────────────────────────────────── │
│ Page 61-62: LOG2 区域 (2KB) │
│ - 2 × 1024 / 8 = 256 条记录 │
│ ────────────────────────────────────────────────────────────── │
│ Page 63: Reserved │
│ │
│ 总 LOG 容量: 21KB / 8B = 2688 条 (满足 4800 条需求需扩展) │
└─────────────────────────────────────────────────────────────────┘

4.2 循环写入策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void write_log_into_flash(void)
{
// 计算循环位置
uint16_t total_log_capacity = FLASH_LOG_DATA_SIZE / sizeof(LOG_T);
uint16_t circular_index = log_p->logindex % total_log_capacity;
uint16_t log1_capacity = FLASH_LOG1_DATA_SIZE / sizeof(LOG_T);
uint16_t area_offset;

if (circular_index < log1_capacity) {
// 在 LOG1 区域 (pages 34-52)
page_log_now = PAGE_LOG1_START;
area_offset = circular_index * sizeof(LOG_T);
} else {
// 在 LOG2 区域 (pages 61-62)
page_log_now = PAGE_LOG2_START;
area_offset = (circular_index - log1_capacity) * sizeof(LOG_T);
}

// 计算页内偏移
uint16_t page_offset = area_offset / FLASH_PAGE_SIZE;
uint16_t offset_in_page = area_offset % FLASH_PAGE_SIZE;
page_log_now += page_offset;

// 页边界自动擦除
if (0 == offset_in_page) {
eflash_erase_page(page_log_now);
}

eflash_mem_to_flash(page_log_now, offset_in_page, sizeof(LOG_T), (uint8_t *)log_mem);
log_p->logindex++;
}

设计要点

  • logindex 从 0 开始,持续递增
  • 使用取模运算实现循环覆盖
  • 页边界自动擦除,无需预读保护
  • 支持跨区域连续存储

4.3 LOG 读取接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int16_t read_log_by_index(uint16_t logindex, LOG_T *log_data)
{
// 检查 logindex 是否在有效范围内
if (log_p->logindex > total_log_capacity) {
uint16_t oldest_valid = log_p->logindex - total_log_capacity;
if (logindex < oldest_valid) {
return -1; // 已被循环覆盖
}
}

// 计算物理位置并读取
uint16_t circular_index = logindex % total_log_capacity;
// ... 读取 Flash 数据 ...

// 验证 logindex 匹配
if (log_data->logindex != logindex) {
return -1; // 数据不匹配
}

return 0;
}

5. 主机驱动接口

5.1 SBS 寄存器定义

寄存器地址 名称 读写 长度 说明
0x8D LATESTLOG R 8B 读取最新一条 LOG 记录
0x90 LOGBYINDEX R/W 8B 按索引查询 LOG (写2B索引,读8B数据)
0x1F SAFETYSTATUS R 2B 当前安全状态标志

5.2 驱动代码示例

5.2.1 获取最新故障记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* @brief 获取最新的电池异常事件
* @param[out] fault_record 指向故障记录结构体的指针
* @return true 成功获取, false 失败
*/
static bool batt_get_latest_fault(dev_id_t id, fault_record_t* fault_record)
{
int32_t ret = 0;
uint8_t tempBuff[8] = {0};

ret = BST7001_read_bytes(id, SBS8D_LATESTLOG, tempBuff, 8);
if (ret < 0) return false;

// 解析 LOG_T 结构 (8 字节)
// Byte0-1: logindex(14b) + charge_status(2b)
// Byte2-3: raw(16b)
// Byte4-6: timefw(24b)
// Byte7: safety_status(4b) + cell_id(4b)

uint16_t word0 = (uint16_t)tempBuff[0] | ((uint16_t)tempBuff[1] << 8);
fault_record->logindex = word0 & 0x3FFF; // bit0-13
fault_record->charge_status = (word0 >> 14) & 0x03; // bit14-15
fault_record->rawData = (int16_t)((uint16_t)tempBuff[2] | ((uint16_t)tempBuff[3] << 8));
fault_record->timefw = (uint32_t)tempBuff[4] | ((uint32_t)tempBuff[5] << 8) | ((uint32_t)tempBuff[6] << 16);
fault_record->safetystatus = tempBuff[7] & 0x0F; // bit0-3: safety_status
fault_record->cellID = (tempBuff[7] >> 4) & 0x0F; // bit4-7: cell_id

return true;
}

5.2.2 按索引查询历史记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* @brief 根据 logindex 获取指定的电池异常事件
* @param[in] logindex 要查询的日志索引
* @param[out] fault_record 查询结果
* @return true 成功, false 该索引不存在或已被覆盖
*/
static bool batt_get_fault_by_index(dev_id_t id, uint16_t logindex, fault_record_t* fault_record)
{
int32_t ret = 0;
uint8_t writeBuff[2] = {0};
uint8_t readBuff[8] = {0};

// Step 1: 写入要查询的 logindex
writeBuff[0] = (uint8_t)(logindex & 0xFF);
writeBuff[1] = (uint8_t)((logindex >> 8) & 0xFF);
ret = BST7001_write_bytes(id, SBS90_LOGBYINDEX, writeBuff, 2);
if (ret < 0) return false;

// Step 2: 读取对应的 LOG 数据
ret = BST7001_read_bytes(id, SBS90_LOGBYINDEX, readBuff, 8);
if (ret < 0) return false;

// Step 3: 检查数据有效性 (全 0xFF 表示无效)
if (readBuff[0] == 0xFF && readBuff[1] == 0xFF && /* ... */) {
return false; // 该 logindex 不存在
}

// Step 4: 解析数据 (同上)
// ...

return true;
}

5.2.3 GPIO 中断处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 驱动初始化时配置中断
hsd->pin_int.id = IO_BST7001_INT;
hsd->pin_int.active_val = BST7001_GPIO_ACTIVE_LOW;
drv_io_port.quick_init(hsd->pin_int.id, GPIO_MODE_INPUT, GPIO_PULL_UP);
drv_io_port.quick_interrupt_config(hsd->pin_int.id, EXTI_TRIGGER_FALLING, 0x02);

// 中断服务函数示例
void BST7001_INT_IRQHandler(void)
{
// 检测到下降沿,表示有新的安全事件
fault_record_t fault;
if (batt_get_latest_fault(dev_id, &fault)) {
// 处理故障记录
process_battery_fault(&fault);
}
}

5.3 主机端使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1. 获取最新故障
fault_record_t latest_fault;
if (batt_get_latest_fault(id, &latest_fault)) {
printf("Latest fault: idx=%d, type=%d, time=%d min\n",
latest_fault.logindex,
latest_fault.safetystatus,
latest_fault.timefw);
}

// 2. 遍历所有历史记录
for (uint16_t i = 0; i <= latest_fault.logindex; i++) {
fault_record_t record;
if (batt_get_fault_by_index(id, i, &record)) {
printf("Log[%d]: safety=0x%X, raw=%d, charge=%d\n",
record.logindex,
record.safetystatus,
record.rawData,
record.charge_status);
}
}

6. 时间戳系统

6.1 时间基准

  • 基准时间: 2025-01-01 00:00:00
  • 单位: 分钟
  • 位宽: 24 bits
  • 最大范围: 16,777,215 分钟 ≈ 31.92 年

6.2 时间同步机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 主机激活时下发时间戳 (通过 SBSB0 寄存器)
// 格式: [bit31-24: lifetime_en] [bit23-0: timefw (分钟)]

void log_init_activate_time(uint32_t activate_time)
{
time_activate = activate_time; // 记录激活时间点
}

void log_update_time(uint32_t calitime)
{
if (calitime) {
timefw_now = calitime & 0xFFFFFF; // 校准时间
}
}

// 系统每分钟自动递增
void log_sync_time(uint32_t minutes)
{
timefw_now += minutes;
timefw_now &= 0xFFFFFF; // 保持 24 位
}

6.3 时间用途

  1. 故障定位: 记录故障发生的精确时间点
  2. 寿命管理: 计算电池使用时长
  3. CV 调整: 超过一定年限后自动降低充电截止电压
1
2
3
4
5
6
7
8
9
// 根据使用时长调整充电电压 (单位: 分钟)
// 1年 = 365.25天 * 24小时 * 60分钟 = 525960 分钟
// 2年 = 525960 * 2 = 1051920 分钟
if (timefw_now - time_activate > 525960) { // 超过1年
fg_chg_update(log_cfg, design_cv - 100, ...); // CV降低100mV
}
else if (timefw_now - time_activate > 1051920) { // 超过2年
fg_chg_update(log_cfg, design_cv - 150, ...); // CV降低150mV
}

7. 系统可靠性设计

7.1 通信校验

主机驱动采用 SMBus PEC (Packet Error Checking) 校验:

1
2
3
4
5
6
7
8
9
10
11
static uint8_t calculate_pec_byte(uint8_t data, uint8_t crcin)
{
uint8_t crc8 = data ^ crcin;
for (uint8_t i = 0; i < 8; i++) {
if (crc8 & 0x80)
crc8 = (crc8 << 1) ^ I2C_PEC_POLY; // 多项式 0x07
else
crc8 = (crc8 << 1);
}
return crc8;
}

7.2 重试机制

1
2
3
4
5
6
7
// I2C 通信失败时自动重试 4 次
for (i = 0; i < 4; i++) {
ret = dev_recv(hsd->fd_dev_ops, rxbuf, read_len + 1);
if (ret && rxbuf[read_len] == calculated_pec) {
break; // 校验通过
}
}

7.3 数据完整性

  • LOG 记录包含 logindex 用于验证数据有效性
  • 读取时比对 logindex 是否匹配请求的索引
  • 无效数据返回全 0xFF

8. 扩展与优化建议

8.1 存储容量扩展

当前设计支持约 2688 条记录,如需满足 4800 条需求:

  • 方案1: 扩展 LOG2 区域或增加 LOG3 区域
  • 方案2: 压缩 LOG_T 结构(如时间精度降低)
  • 方案3: 使用外部 Flash 存储

8.2 事件优先级

建议实现事件优先级机制:

  1. Critical: SC, ISC (立即记录)
  2. High: OV, UV, OCC, OCD (立即记录)
  3. Medium: OT, UT (可合并相近时间的事件)
  4. Low: 周期性状态记录

8.3 主机端缓存

建议主机端实现本地缓存机制:

  • 定期同步新增记录
  • 断电恢复后只需同步增量
  • 减少 I2C 通信开销

9. 总结

本系统实现了完整的电池安全事件检测、记录和查询功能:

功能模块 实现方式 关键技术
事件检测 硬件+软件混合 DFE 寄存器、软件算法
状态捕获 实时保存 fault_imode 机制
数据存储 Flash 循环 页边界擦除、跨区域存储
主机查询 SBS 接口 PEC 校验、中断通知
时间管理 24-bit 时间戳 主机校准、分钟精度

该设计满足新国标对电池安全记录的要求,具备良好的可靠性和扩展性。


附录 A: 相关文件清单

文件 说明
flash/log.c LOG 模块核心实现
flash/log.h LOG 数据结构定义
user/main.c 主循环、保护检测
flash/stm.c 状态机、充放电状态判断
dev_bst7001.c 主机 MCU 驱动
dev_bst7001.h 驱动接口定义

附录 B: 修订历史

版本 日期 修改内容
V1.0 2026-01-30 初始版本