BMS升级解密:从上位机到MCU的固件下载全流程
写在前面
想象一下:你手中的电动工具、智能手表、甚至是电动汽车,它们内部都有一颗”电池大脑”——电量计芯片。这颗芯片不仅要实时计算电池剩余电量,还要在漫长的使用过程中不断更新自己的”知识库”——固件。那么问题来了:固件是如何从电脑传入这颗微小芯片的?
本文将深入解析一款基于USB/无线通信的电量计固件升级方案,从架构设计到代码实现。
一、问题背景:为什么要设计固件升级?
1.1 固件升级的必要性
电量计芯片(Fuel Gauge IC)虽然不像手机SOC那样需要频繁更新,但固件升级仍然不可或缺:
- Bug修复:修正算法中的计算误差,提升电量精度
- 功能迭代:支持新的电池化学体系或通讯协议
- 参数优化:调整充放电曲线、告警阈值等配置
- 安全补丁:修复潜在的安全漏洞
1.2 升级方案的挑战
电量计的固件升级面临独特的挑战:
1 2 3 4 5 6 7 8 9 10 11 12 13
| ┌─────────────────────────────────────────────────────────┐ │ 升级方案的核心矛盾 │ ├─────────────────────────────────────────────────────────┤ │ │ │ ┌───────────────────┐ ┌───────────────────┐ │ │ │ 上位机(PC) │ │ 电量计芯片(Embedded)│ │ │ │ - 强大算力 │ ──▶ │ - 资源受限 │ │ │ │ - 丰富接口 │ USB │ - 独立运行 │ │ │ │ - 大容量存储 │ │ - 可靠性要求高 │ │ │ └───────────────────┘ └───────────────────┘ │ │ │ │ 核心问题:如何在资源受限的嵌入式芯片中实现可靠的固件烧录? │ └─────────────────────────────────────────────────────────┘
|
本文要介绍的方案,正是为解决这一矛盾而设计的。
二、系统架构:四级架构的通信模型
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 30 31 32
| ┌─────────────────────────────────────────────────────────────────────┐ │ 上位机 (PC Tool) │ │ 发送命令序列: CONFIG_INFO → BOOT_DOWNLOAD(N次) → BOOT_DOWNLOAD_COMPLETE│ └───────────────────────────────┬─────────────────────────────────────┘ │ USB/无线 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ wpb1025 MCU (主控) │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ dev_cps8610.c - app_fw_update() │ │ │ │ └─ 根据command路由到BST7001升级模块 │ │ │ └─────────────────────────────────────────────────────────────┘ │ └───────────────────────────────┬─────────────────────────────────────┘ │ 命令路由 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ dev_bst7001_upgrade.c (升级驱动) │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ bst7001_upgrade_process_command() - 命令处理入口 │ │ │ │ ├─ WL_CMD_GET_ID → 返回芯片ID │ │ │ │ ├─ WL_CMD_CONFIG_INFO → bst7001_download_start() │ │ │ │ ├─ WL_CMD_BOOT_DOWNLOAD → bst7001_write_chunk() │ │ │ │ ├─ WL_CMD_BOOT_DOWNLOAD_COMPLETE → bst7001_download_finish()│ │ │ │ └─ WL_CMD_PING_STATUS → 返回当前状态 │ │ │ └─────────────────────────────────────────────────────────────┘ │ └───────────────────────────────┬─────────────────────────────────────┘ │ I2C/SMBus (0x18) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ BST7001 电量计芯片 │ │ (通过I2C进行Flash编程) │ └─────────────────────────────────────────────────────────────────────┘
|
2.2 每一层的职责
| 层级 |
模块 |
核心职责 |
| L4 |
上位机 |
固件文件解析、分包、CRC校验、进度显示 |
| L3 |
MCU应用层 |
USB数据包解析、命令路由、状态管理 |
| L2 |
升级驱动 |
I2C通信、Flash操作、地址映射 |
| L1 |
电量计芯片 |
内部Flash编程、参数存储 |
三、核心数据结构:命令与状态的载体
3.1 USB命令协议
上位机与MCU之间的”语言”是精心设计的USB命令协议:
1 2 3 4 5 6 7 8
| typedef enum { WL_CMD_GET_ID = 0x20, WL_CMD_CONFIG_INFO = 0x21, WL_CMD_BOOT_DOWNLOAD = 0x22, WL_CMD_BOOT_DOWNLOAD_COMPLETE = 0x23, WL_CMD_PING_STATUS = 0x40, } wl_command_t;
|
这些命令构成了升级流程的”动词”,每一条都有明确的语义。
3.2 配置信息结构
1 2 3 4 5 6 7
| typedef struct { uint32_t boot_len; uint32_t boot_crc; uint32_t firmware_len; uint32_t firmware_crc; } wl_config_info_t;
|
3.3 升级上下文 - 状态机的”胃”
每个升级会话都对应一个上下文结构,它是状态机的”记忆”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| typedef struct { int32_t fd_dev_ops; uint8_t i2c_addr; bst7001_upg_state_t state; bst7001_upg_error_t last_error; uint32_t bytes_written; uint32_t expected_offset; uint8_t info_backup[512]; } bst7001_upg_ctx_t;
|
四、USB数据包格式:128字节的艺术
4.1 BOOT_DOWNLOAD数据包结构
这是最核心的数据传输格式,设计非常精巧:
1 2 3 4 5 6 7 8 9 10
| ┌────────┬────────┬────────┬─────────────┬─────────────┬──────────────┬─────────┐ │ Header │ Length │ Command│ Address │ Data Size │ Data Payload │ CRC16 │ │ 1 byte │ 1 byte │ 1 byte │ 4 bytes │ 4 bytes │ 128 bytes │ 2 bytes │ ├────────┼────────┼────────┼─────────────┼─────────────┼──────────────┼─────────┤ │ 0x55 │ 0x89 │ 0x22 │ Little-End │ Little-End │ FW Data │ CRC16 │ └────────┴────────┴────────┴─────────────┴─────────────┴──────────────┴─────────┘
实际示例: 55 89 22 00 00 00 00 80 00 00 00 [128字节固件数据] 15 3B └─Address=0x0─┘ └─Size=128─┘ └CRC16┘
|
4.2 为什么要这样设计?
- Header (0x55):数据包起始标记,用于帧同步
- Length:整个数据包长度,方便接收方分配缓冲区
- Command:命令类型,明确本次操作意图
- Address:Flash目标地址,支持随机写入
- Data Size:本次传输的数据长度(最大128字节)
- Data Payload:实际的固件数据
- CRC16:数据完整性校验
五、下载流程:五步走战略
5.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
| 上位机 MCU (wpb1025) BST7001 │ │ │ │─── WL_CMD_GET_ID ──────────────────────►│ │ │◄── Response (chipid=0x00007001) ────────│ │ │ │ │ │─── WL_CMD_CONFIG_INFO ─────────────────►│ │ │ (boot_len, boot_crc) │── CPU Hold ──────────────────►│ │ │── Backup Info ───────────────►│ │ │── Remap Eflash ──────────────►│ │ │── Page Erase ────────────────►│ │◄── (通过PING_STATUS获取结果) ───────────│ │ │ │ │ │─── WL_CMD_BOOT_DOWNLOAD ───────────────►│ │ │ (offset=0x0000, data[128], CRC16) │── Write Block (128B) ────────►│ │◄── (通过PING_STATUS获取结果) ───────────│ │ │ │ │ │─── WL_CMD_BOOT_DOWNLOAD ───────────────►│ │ │ (offset=0x0080, data[128], CRC16) │── Write Block (128B) ────────►│ │ ...重复直到全部数据发送完成... │ │ │ │ │ │─── WL_CMD_BOOT_DOWNLOAD_COMPLETE ──────►│ │ │ │── Restore Info ──────────────►│ │ │── Remap Eflash ──────────────►│ │ │── Close CADC Int ────────────►│ │ │── Soft Reset ────────────────►│ │ │── Clear CADC Flag ───────────►│ │ │── CPU Run ───────────────────►│ │◄── Success ─────────────────────────────│ │
|
5.2 关键步骤详解
第一步:CONFIG_INFO - 准备工作
1 2 3 4 5 6
| bst7001_download_start() { }
|
为什么要备份Info区?
BST7001的Flash中有一个特殊的Info区域,存储了校准参数、序列号等关键数据。擦除整个Flash会导致这些数据丢失,所以在擦除前必须备份,完成后恢复。
第二步:BOOT_DOWNLOAD - 数据传输
1 2 3 4 5 6
| bst7001_write_chunk() { }
|
地址跳跃是什么意思?
Flash地址不是连续的!代码区从0x0000开始,到0x8400结束。然后是Log区(不擦除),接着是表区(从0xD400开始)。写入时需要处理这种”跳跃”。
第三步:COMPLETE - 收尾工作
1 2 3 4 5 6 7 8 9
| bst7001_download_finish() { }
|
六、状态机:升级流程的”指挥官”
固件升级是一个典型的状态机场景,定义清晰的状态和转换关系至关重要:
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
| ┌──────────────────┐ │ IDLE (空闲) │◄───────────────────────────┐ └────────┬─────────┘ │ │ CONFIG_INFO │ ▼ │ ┌──────────────────┐ │ │ PREPARING (准备) │ CPU Hold, Backup, Erase │ └────────┬─────────┘ │ │ 成功 │ ▼ │ ┌──────────────────┐ │ ┌──────►│ DOWNLOADING │ 循环接收BOOT_DOWNLOAD │ │ │ (下载中) │ │ │ └────────┬─────────┘ │ │ │ BOOT_DOWNLOAD_COMPLETE │ │ ▼ │ │ ┌──────────────────┐ │ │ │ FINISHING (完成) │ Restore, Reset, Run │ │ └────────┬─────────┘ │ │ │ 成功 │ │ ▼ │ │ ┌──────────────────┐ │ │ │ COMPLETE (完成) │────────────────────────────┘ │ └──────────────────┘ │ │ 失败 ┌──────────────────┐ └───────│ ERROR (错误) │ └──────────────────┘
|
状态说明
| 状态 |
含义 |
可执行命令 |
| IDLE |
初始状态 |
CONFIG_INFO |
| PREPARING |
准备中(擦除、备份) |
PING_STATUS |
| DOWNLOADING |
下载中 |
BOOT_DOWNLOAD, PING_STATUS |
| FINISHING |
完成中(恢复、复位) |
PING_STATUS |
| COMPLETE |
成功完成 |
- |
| ERROR |
发生错误 |
- |
七、Flash地址映射:理解物理布局
BST7001的Flash布局如下:
1 2 3 4 5 6 7 8 9 10
| BST7001 Flash 布局: ┌───────────────────┬───────────────────┐ │ 0x0000-0x8400 │ 代码区 (Code) │ ← 需要下载 ├───────────────────┼───────────────────┤ │ 0x8400-0xD400 │ Log区 (跳过) │ ← 保留,不擦除 ├───────────────────┼───────────────────┤ │ 0xD400-0xF400 │ 表区 (Table) │ ← 需要下载 ├───────────────────┼───────────────────┤ │ 0x5000 (512B) │ Info区 │ ← 备份后恢复 └───────────────────┴───────────────────┘
|
这种分区设计的考量:
- 代码区:存储主程序固件
- Log区:存储运行时日志,擦除会丢失重要信息
- 表区:存储查表数据(如电池曲线)
- Info区:存储校准参数,每个芯片唯一
八、错误处理:容错与恢复
8.1 错误码定义
1 2 3 4 5 6 7 8 9 10 11 12
| typedef enum { BST7001_UPG_OK = 0, BST7001_UPG_ERR_I2C = -1, BST7001_UPG_ERR_PEC = -2, BST7001_UPG_ERR_UNLOCK = -3, BST7001_UPG_ERR_ERASE = -4, BST7001_UPG_ERR_WRITE = -5, BST7001_UPG_ERR_CHECKSUM = -6, BST7001_UPG_ERR_TIMEOUT = -7, BST7001_UPG_ERR_PARAM = -8, BST7001_UPG_ERR_STATE = -9, } bst7001_upg_error_t;
|
8.2 关键设计:看门狗保护
在长时间操作中,芯片的看门狗(Watchdog)可能会复位系统。因此需要定期”喂狗”:
1 2 3
| delay_ms(BST7001_DL_DELAY_MS); wwdt_feeddog();
|
九、架构设计亮点
9.1 分层设计
- 命令层:定义清晰的USB协议
- 路由层:根据配置选择不同芯片的驱动
- 驱动层:实现具体的I2C通信和Flash操作
9.2 可配置性
通过宏开关支持不同芯片:
1
| #define CFG_ENABLE_BST7001_UPGRADE 1
|
9.3 状态机模式
同步状态机简化了异步复杂度,每个命令都有明确的前置状态检查。
十、总结
本文详细解析了电量计固件升级的全流程,从上位机的USB协议到MCU的状态机管理,再到I2C通信和Flash操作。这个设计方案体现了嵌入式系统的典型设计模式:
- 协议驱动:用清晰的命令集定义升级流程
- 状态管理:用有限状态机控制流程
- 分层抽象:用模块化降低复杂度
- 容错设计:用错误码和喂狗机制保证可靠性
理解了这些设计思想,你也能应对类似的固件升级需求。下一篇文章,我们将介绍新一代芯片SD7001的升级方案,看看它是如何在BST7001的基础上进行演进和优化的。
相关阅读: