bb_update.c
bb_update(),:305)4.2 HEX 解析与拼包(parse_bb_hex_content(),:126)4.3 发送主循环(WRITE_FLASH 分支,:374)4.4 应答判定(deal_bb_update_data(),:240)4.5 通信协议帧格式汇总4.6 与 uCOS-II 的配合4.7 关键全局变量的读写5. 对外接口导出(供外部调用)调用(依赖的外部函数/变量)6. 依赖关系7. 备注与风险点源文件路径:
midware/meter/bb_update.c所属模块:midware/meter(电表/备用箱中间件) 代码行数:688
1. 文件职责
本文件实现 BackupBox(BB,备用箱)内置 MCU 的固件串口升级流程。BB 是 AlphaESS 备用电源箱,其内部还有一颗独立 MCU,EMS 侧需要把新的 .hex 固件经 UART1(RS485/串口)通过一套"复位 → 进 Bootloader → 改波特率 → 擦除 → 分块写 Flash → 校验 Flash → 写关键字 → 结束"的私有协议烧写进去。
固件原始数据来自服务器下发,已由 net/ 层的下载模块落到 SDRAM 缓冲区 g_u8_fw_sdram1(S19/HEX 文本格式),本文件负责把 HEX 文本逐行解析成二进制、按片拼凑成一次可发送的负载,再按帧发送并等待 Bootloader 的固定应答。
驱动方式:midware/meter/meter.c:5738 的 task_meter() 任务在检测到 Is_Updated_From_Server(UPDT_Meter) 为真、且电网侧电表型号为 BACKUPBOX_5K/10K/23K 时,每轮循环调用一次 bb_update();bb_update() 内部通过 OSTimeDlyHMSM() 自阻塞按毫秒级节拍推进状态机,是典型的"单任务状态机 + 阻塞等待应答"实现。
进程内还对外提供升级进度(GetPercentUpgradeBB())与已传字节偏移(GetUpgradeFileOffsetBB())供 midware/meter/meter.c 与 net/ems.c 上报云端。
2. 关键数据结构与宏
本文件宏
| 名称 | 类型 | 说明 |
|---|---|---|
USART_TX_TIMEOUT | 宏 | 1000,串口发送超时(ms),传给 usart1_send_data() |
USART_RX_TIMEOUT | 宏 | 1000,接收基准超时(ms);实际接收等待用 USART_RX_TIMEOUT*10 = 10s |
BB_APP_FLASH_ADDR_HEAD | 宏 | 0x003D8000,BB 应用区起始地址(第一个 0xF000 半区起点) |
BB_APP_FLASH_ADDR_HALF | 宏 | 0x003E7000,应用区中点(第二个半区起点) |
BB_APP_FLASH_ADDR_END | 宏 | 0x003F6000,应用区结束;实际未被引用(:446 用裸常量写了同一个值) |
BB_APP_FLASH_HALF_LEN | 宏 | 0xF000,每个校验半区的字节长度 |
BB_APP_KEYWORD_ADDR | 宏 | 0x003F3FF0,升级关键字(0x6D83)存放地址,写入即触发 Bootloader 跳转 |
BB_BOOT_FLASH_ADDR_END | 宏 | 0x003F7FFF,HEX 解析越界判断上界(超过则视为解析结束) |
本文件用到的头文件定义
| 名称 | 类型 | 说明 |
|---|---|---|
BB_INSTRUCTIONS | 枚举(bb_update.h:25) | REPLY_FRAME / SEQUENCE_FRAME / COMMAND_DATA_FRAME,三种帧型;本文件未直接使用(仅语义注释) |
BB_STATE | 枚举(bb_update.h:32) | 升级状态机:RESET_INSTRUCTIONS、BOOT_INSTRUCTIONS、ADJUST_RATE、ERASE_FLASH、WRITE_FLASH、CHECK_FLASH、SEND_KEY_WORD、UPGRADE_END、UPGRADE_INIT、RECV_DATA(RECV_DATA 未使用) |
struct BB_UPDATE_CONTENT | 结构体(bb_update.h:47,#pragma pack(1)) | 升级上下文:文件长度、Flash 基址/偏移、拼包缓冲 u8_buffer[256]、通信序列号 u8_loop_addr、校验码 u16_check_code、状态 u8_insteructions_state、序列帧字节数 u32_amount_sequence_frame 等 |
struct BB_CHECK | 结构体(bb_update.h:63 #pragma pack(1)) | Flash 校验累加:u8_time 校验轮次,u16_check_length[10] 参与累加的字节数、u16_check_code[10] 累加和(只用到下标 0/1/2) |
MAX_PRG_LENGTH | 宏(bb_update.h:21) | 112*2 = 224,单帧最大负载字节数(112 个 16bit 字) |
RECV_SEQUENCE_FRAME / RECV_COMMAND_DATA_FRAME | 宏(bb_update.h:19-20) | 0x06 / 0x01,接收侧应答帧类型判别值(本文件未直接引用) |
HEX_FILE_T | 结构体(common/hex_file_parse.h:31) | HEX 单行解析结果:u8_data_len、u16_addr、u8_code_type、u16_data_buf[32]、u8_ascll_len |
PARSE_STATE_E | 枚举(common/hex_file_parse.h:15) | PARSE_OK / PARSE_ADDR / PARSE_END / DATA_ERR / PARSE_ERR |
HEX_CODE | 枚举(common/hex_file_parse.h:24) | GET_DATA_CODE=0x00、GET_END_CODE=0x01、GET_BASE_ADDRESS_CODE=0x04、GET_LINEAR_ADDRESS_CODE=0x05 |
本文件全局变量
| 名称 | 类型 | 说明 |
|---|---|---|
g_bb_update | struct BB_UPDATE_CONTENT(非 static,:36) | 升级主上下文 |
g_check_code | struct BB_CHECK(非 static,:37) | Flash 校验累加器 |
s_hex_file_data | static HEX_FILE_T(:39) | HEX 解析工作区,hex_parse() 的输出 |
s_u32_parse_length | static u32(:41) | 已成功发送的 HEX 文本字节数,用于算进度 |
g_u8_bb_recv_buf[20] | u8(非 static,:42) | 应答帧接收缓冲 |
g_u8_reset_instructions0[9] | 帧模板 | 复位指令,全 0(9 字节,实际只发前 9/8 字节) |
g_u8_boot_instructions[8] | 帧模板 | "PSLAVER\r" = 50 53 4C 41 56 45 52 0D,Bootloader 跳转/进入口令 |
g_u8_adjust_rate[1] | 帧模板 | {0x41},触发 BB 侧波特率自适应 |
g_u8_reply_frame[8] | 帧模板 | {0x06,0x99,0x66,seq,0x00,0x08,0x00,0x00},回复帧,下标 3 为通信序列号 |
g_u8_sequence_frame[8] | 帧模板 | {0x01,0x66,0x99,seq,len_h,len_l,0x00,0x00},序列帧,下标 4/5 为后续"指令与数据帧"总字节数 |
g_u8_erase_flash[20] | 帧模板 | 擦除指令,[0..1]=00 0A,[10..11]=00 0E,尾部 FF FF |
g_u8_endcode[20] | 帧模板 | 结束指令,[0..1]=00 0D,尾部 FF FF |
g_u8_write_flash[256] | 帧模板 | 写 Flash / 写关键字复用帧,[0..1]=00 0B,[2..5] 为目标地址,[8..9] 为字数,[20..243] 为负载 |
g_u8_check_flash[20] | 帧模板 | 校验指令,[0..1]=00 0C,[2..5] 地址,[8..9] 长度,[16..17] 期望校验和,[18..19]=FF FF |
3. 函数清单
| 函数名 | 作用域 | 说明 |
|---|---|---|
clear_bb_update_data | static | 清零 g_bb_update/g_check_code,清 UPDT_Meter 服务器升级标志,清串口缓冲 |
parse_bb_hex_content | static | 从 g_u8_fw_sdram1[offset] 起解析一行 HEX,把数据拼进 u8_buffer,返回 PARSE_OK/PARSE_ADDR/PARSE_END/PARSE_ERR |
bb_send_data | static | 清缓冲后 usart1_send_data() 发送一段数据,恒返回 TRUE |
deal_bb_update_data | static | 阻塞接收最多 18 字节应答,按固定字节模式判定是否应答成功 |
bb_update | 全局 | 升级主状态机入口,每次调用推进一个状态 |
GetPercentUpgradeBB | 全局 | 返回 BB 升级百分比 = s_u32_parse_length*100/get_length_file_updating(UPDT_Meter) |
GetUpgradeFileOffsetBB | 全局 | 返回已传输的 HEX 文本字节数 s_u32_parse_length |
4. 核心逻辑解析
4.1 主状态机(bb_update(),:305)
状态存放在 g_bb_update.u8_insteructions_state,switch 每调用一次只执行一个 case(除 WRITE_FLASH 内部有 while(1))。
UPGRADE_INIT ──► RESET_INSTRUCTIONS ──(延时后)──► BOOT_INSTRUCTIONS
│
(延时 2s) ▼
ADJUST_RATE
│
(延时 1s) ▼
┌────────────────────► ERASE_FLASH
│(应答成功) │
│ ▼
│ WRITE_FLASH ◄─┐
│ │ (while(1) 内循环分块发送)
│ 解析到关键字地址 ▼
│ CHECK_FLASH
│ │ (s_u8_check_time==2)
│ ▼
│ SEND_KEY_WORD
│ │
│ └──► CHECK_FLASH (重复)
│ │ (s_u8_check_time>=10)
│ ▼
└──────────────────────── UPGRADE_END
各状态动作与迁移条件:
RESET_INSTRUCTIONS(:319):连发两次全 0 复位指令(9 字节、8 字节,中间 250ms),置s_u32_tick_timeout,转BOOT_INSTRUCTIONS。这是硬复位 BB 让其重新进入 Bootloader。BOOT_INSTRUCTIONS(:333):发送"PSLAVER\r"(8 字节)后OSTimeDlyHMSM(0,0,2,0)等 2 秒(等 BB 侧启动 Boot),转ADJUST_RATE。ADJUST_RATE(:341):发送0x41,延时 1 秒等波特率协商,转ERASE_FLASH。ERASE_FLASH(:348):先发回复帧(帧内[3] = u8_loop_addr,随后u8_loop_addr++),再发序列帧(序列号的长度域固定00 1C= 28),再发 20 字节擦除指令;然后调用deal_bb_update_data()等应答。应答成功 → 转WRITE_FLASH;失败 → 保持在本状态,下一轮重新发帧(隐式重试,无计数上限)。WRITE_FLASH(:374):核心循环,见 4.3。CHECK_FLASH(:496):发回复帧 + 序列帧(长度域固定 28),清空并填充g_u8_check_flash[20],按s_u8_check_time分三档发送校验请求:s_u8_check_time == 0:地址BB_APP_FLASH_ADDR_HEAD,长度0xF000,期望校验和check_code[0](第一个半区)。s_u8_check_time == 1:地址BB_APP_FLASH_ADDR_HALF,长度0xF000,期望校验和check_code[1](第二个半区)。- 其他(≥2):地址仍是
BB_APP_FLASH_ADDR_HALF、长度0xF000,但期望校验和换成check_code[2](关键字区相关的第三次校验)。
应答成功时 s_u8_check_time++;==2 时转 SEND_KEY_WORD;>=10 时转 UPGRADE_END(隐含最多校验 10 轮的重试上限)。应答失败时不递增、也不做任何处理,本轮直接结束,下一轮重发同一档校验请求。
SEND_KEY_WORD(:584):发回复帧 + 序列帧(长度域 30),复用g_u8_write_flash把地址改成BB_APP_KEYWORD_ADDR、字数域g_u8_write_flash[9]=0x01、负载[20]=0x6D, [21]=0x83(即关键字0x6D83),发送30-8 = 22字节。应答成功 → 回到CHECK_FLASH(注意此时s_u8_check_time仍为 2,没有被复位)。UPGRADE_END(:621):发回复帧 + 序列帧(长度域00 1C)+ 20 字节结束指令,随后立刻转UPGRADE_INIT(不等待应答)。UPGRADE_INIT(:639):复位全部局部静态变量(s_u8_parse_cnt/s_u8_parse_flag/s_u16_length/s_u32_parse_length/s_u8_check_time)、清u8_loop_addr,转RESET_INSTRUCTIONS,并调用clear_bb_update_data()清零上下文并复位服务器升级标志。default(:655):任何非法状态直接回UPGRADE_INIT。
总超时看门狗:函数末尾 bb_update.c:661 判断 abs(s_u32_tick_timeout - OSTimeGet()) >= 1000*60(60 秒),超时强制回 UPGRADE_INIT。各状态在收到有效应答后都会刷新 s_u32_tick_timeout,因此该定时器实质是"整流程无响应 60 秒即重来"。
4.2 HEX 解析与拼包(parse_bb_hex_content(),:126)
调用 hex_parse(&s_hex_file_data, &g_u8_fw_sdram1[offset], 80) 解析 SDRAM 中的一行文本(固定长度 80 字节)。
- 返回非
PARSE_OK直接上抛错误。 GET_BASE_ADDRESS_CODE(扩展线性地址记录):把u16_data_buf[0]存入s32_addr_flash_base(高 16 位基址)。若当前拼包长度不为 0,说明要跨越一个不连续段,置s32_length = 0并返回PARSE_OK(先把已攒的数据发出去);否则返回PARSE_ADDR。GET_DATA_CODE(数据记录):s32_addr_flash_offset = u16_addr。- 若本次偏移与上次不同,取
s32_len_buff = u8_data_len;若偏移跳变大于0x10(即中间有空洞,说明不连续),且已有拼包数据和历史偏移,则置s32_length = 0返回PARSE_OK(先发旧包)。 - 若偏移与上次相同(同一地址重复行),把
s32_len_buff置 0,避免重复累加。 - 首次拿到数据(
s32_length == 0)时:把u8_buffer[256]全填0xFF(Flash 擦除态填充),计算绝对烧写地址s32_addr_flash = (base<<16) | offset;若该地址已经落在关键字地址BB_APP_KEYWORD_ADDR之后且不超过BB_BOOT_FLASH_ADDR_END,直接返回PARSE_END(表示整包写完)。 - 累加拼包长度:
s32_length = (当前绝对地址 - 包起始绝对地址)*2 + s32_len_buff。这里的*2是因为 HEX 的地址是字节地址,而每行 16 个字节被hex_parse()打包成了 16 个 16bit 字,u8_buffer是按字展开存的,见下面循环。 - 长度 ≤
MAX_PRG_LENGTH(224) 时,把u16_data_buf[i]按大端拆分写入u8_buffer:高字节在前。写入位置为s32_length - s32_len_buff + 2i,即"从包尾对齐",这样多次调用即可把连续的多行 HEX 拼成一个大端字节流。返回PARSE_ADDR(表示还要继续攒)。 - 长度超过
MAX_PRG_LENGTH时g_check_code.u8_time++(该字段后续从未被读,是死代码/遗留计数),置s32_length = 0返回PARSE_OK。
- 若本次偏移与上次不同,取
GET_END_CODE:若当前拼包长度非 0,先置 0 并返回PARSE_OK(把最后一包发出去);否则返回PARSE_END。- 其他返回
PARSE_ERR。
4.3 发送主循环(WRITE_FLASH 分支,:374)
while(1) 内两段:
第一段:解析(仅当上一轮没有接收错误 s_u8_recv_err_flag == FALSE)
调用 parse_bb_hex_content(s_u32_parse_length + l_u32_parse_length_temp),用两个游标区分"已发送的文本长度"和"本轮已累积但未发送的文本长度":
PARSE_OK:s_u8_parse_cnt = 0,进入第二段发送。PARSE_END/PARSE_ADDR:l_u32_parse_length_temp += s_hex_file_data.u8_ascll_len(累加本行文本长度),清零错误计数,继续循环攒下一行。- 其他(
PARSE_ERR):错误计数s_u8_parse_cnt++,连续超过 3 次 → 状态置UPGRADE_INIT并return;否则break退出while。
第二段:发送(当 s_u8_parse_flag == PARSE_OK 或本轮接收出错)
- 发回复帧:
g_u8_reply_frame[3] = u8_loop_addr,发送后u8_loop_addr++。 - 发序列帧:长度域
s_u16_length = 28 + g_bb_update.u32_amount_sequence_frame,g_u8_sequence_frame[3] = u8_loop_addr,[4]/[5]为大端长度。 - 发指令与数据帧(复用
g_u8_write_flash):[2..5]=s32_addr_flash的 big-endian 四字节(注意顺序是>>24, >>16, >>8, >>0)。[8..9]= 字数 =u32_amount_sequence_frame / 2(大端)。memcpy(g_u8_write_flash+20, g_bb_update.u8_buffer, 256)把拼好的负载拷入。- 发送长度 =
s_u16_length - 8(序列帧 8 字节在总长里被扣除,因为序列帧已单独发过)。
- Flash 校验和累加(仅当
s32_addr_flash与上次不同u32_old_flash_addr):按地址落点把本帧数据累加进g_check_code.u16_check_code[0](前半区)或[1](后半区),并递增对应u16_check_length。 - 调用
deal_bb_update_data()等应答:- 成功:刷新
s_u32_tick_timeout,清s_u8_recv_err_flag,s_u32_parse_length += l_u32_parse_length_temp(推进进度游标),清零临时游标和s_u8_send_err_cnt。 - 失败:清
s_u8_recv_err_flag(故意置 FALSE,让下一轮重新解析同一段数据),清l_u32_parse_length_temp,s_u8_send_err_cnt++;连续超过 3 次 → 置UPGRADE_INIT并return。 - 无论成败都
return(一次调用只发一帧)。
- 成功:刷新
- 若解析结果为
PARSE_END(说明已到关键字地址),计算最终校验和并转CHECK_FLASH:check_code[2] = check_code[1] + 0x6D83check_code[0] += (0xF000 - check_length[0]) * 0xFFFFcheck_code[1] += (0xF000 - check_length[1]) * 0xFFFFcheck_code[2] += ((0xF000 - check_length[1]) - 1) * 0xFFFF
即先用 0xFFFF(Flash 擦除后的空白字)把不足整半区的部分补齐,再算和,以匹配 BB 侧对整块区域求和的口径。
4.4 应答判定(deal_bb_update_data(),:240)
- 先
memset(g_u8_bb_recv_buf, 0, 20)。 - 循环接收:超时
USART_RX_TIMEOUT*10 = 10s;串口无数据则OSTimeDlyHMSM(0,0,0,30)(让出 30ms 给其他任务,避免纯忙等);每收到一字节存入g_u8_bb_recv_buf[l_u8_data_len++],收满 18 字节即 break。 - 判定(全部满足才算成功):
[4..5] == 00 08(回复帧长度域)[6..7] == 00 00[8..10] == 01 99 66(序列帧头)[12..17] == 00 0A 00 00 00 00(擦除指令回显)
也就是说这是模式匹配而非 CRC 校验:BB 的 Bootloader 应答内容固定,代码只校验其中 14 个字节的常量特征。
4.5 通信协议帧格式汇总
| 帧 | 长度 | 字节布局 |
|---|---|---|
| 复位指令 | 9 / 8 | 全 0x00 |
| Boot 进入 | 8 | 50 53 4C 41 56 45 52 0D(ASCII "PSLAVER\r") |
| 波特率自适应 | 1 | 41 |
| 回复帧 | 8 | 06 99 66 <seq> 00 08 00 00 |
| 序列帧 | 8 | 01 66 99 <seq> <len_H> <len_L> 00 00;len = 28 + 负载字节数(擦除/校验/结束时固定 00 1C=28,写关键字时为 30) |
| 擦除指令帧 | 20 | 00 0A 00 00 00 00 00 00 00 00 00 0E 00 00 00 00 00 00 FF FF |
| 写 Flash 帧 | 8 + 负载 | 00 0B <addr BE 4B> <填充> <字数 BE 2B> ... <负载> FF FF,负载从下标 20 开始 |
| 校验 Flash 帧 | 20 | 00 0C <addr BE 4B> ... <len BE 2B> <crc BE 2B> FF FF |
| 结束指令帧 | 20 | 00 0D 00 00 ... FF FF |
无 CRC 字段——校验依赖帧内固定字节特征与 Flash 内容校验和。
4.6 与 uCOS-II 的配合
- 本文件不使用信号量/消息邮箱,完全靠
OSTimeDlyHMSM()阻塞延时 +OSTimeGet()节拍比较实现时序。 OSTimeDlyHMSM(0,0,0,250)、(0,0,2,0)、(0,0,1,0)、(0,0,0,30)分别对应 250ms、2s、1s、30ms 延时;调用者task_meter()在本函数返回后还会OSTimeDlyHMSM(0,0,0,50)+IWDG_Feed()。- 由于
deal_bb_update_data()最长阻塞 10 秒,task_meter()在这段时间内不会喂狗、也不处理其它电表;meter.c:5769在升级分支末尾才喂狗,属可接受的取舍(但 10s 阻塞期间其他电表通讯完全停滞)。 - 依赖的标号:
OSTimeGet()、OSTimeDlyHMSM()。
4.7 关键全局变量的读写
g_bb_update.u8_insteructions_state:状态机唯一驱动量,由本文件写、g_bb_update.u8_insteructions_state初值为 0(RESET_INSTRUCTIONS是枚举首项,故上电即从复位开始)。g_bb_update.u8_loop_addr:通信序列号,ERASE_FLASH之后每发一次帧递增;UPGRADE_INIT时清零。g_check_code.*:只在WRITE_FLASH累加、CHECK_FLASH/PARSE_END读取;UPGRADE_INIT通过clear_bb_update_data()清零。s_u32_parse_length:升级进度来源,被GetPercentUpgradeBB()/GetUpgradeFileOffsetBB()读取,进而被meter.c:6844/6857、net/ems.c:6706读取上报。g_u8_fw_sdram1(外部,net/server.h:258,位于 SDRAM):只读,源固件文本。
5. 对外接口
导出(供外部调用)
| 名称 | 声明位置 | 调用方 |
|---|---|---|
void bb_update(void) | bb_update.h:73 | midware/meter/meter.c:5738(task_meter() 内) |
u8 GetPercentUpgradeBB(void) | bb_update.h:74 | midware/meter/meter.c:6844、:6857(GetPercentUpgradeMeter()) |
u32 GetUpgradeFileOffsetBB(void) | bb_update.h:75 | net/ems.c:6706(上报 g_t_upgrade_run_info.u32_upgrade_file_offset) |
struct BB_UPDATE_CONTENT g_bb_update | 无头文件声明 | 仅本文件使用(外部无引用) |
struct BB_CHECK g_check_code | 无头文件声明 | 仅本文件使用(外部无引用) |
u8 g_u8_bb_recv_buf[20] | 无头文件声明 | 仅本文件使用(外部无引用) |
调用(依赖的外部函数/变量)
来自 bb_update.h → includes.h 的公共定义,以及:
| 名称 | 来源 |
|---|---|
hex_parse()、HEX_FILE_T、PARSE_*、GET_*_CODE | common/hex_file_parse.h |
usart1_clear_buffer()、usart1_send_data()、usart1_get_data_len()、usart1_read_data_async() | hardware/usart1/usart1.h |
OSTimeGet()、OSTimeDlyHMSM()、memset/memcpy | uCOS-II / includes.h |
Set_Status_Is_Updated_From_Server() | net/upd_download.h:39 |
get_length_file_updating() | net/upd_download.h:37 |
g_u8_fw_sdram1 | net/server.h:258(SDRAM 固件缓冲区) |
UPDT_Meter | net/upd_download.h(ENUpdateType) |
TRUE/FALSE | includes.h |
6. 依赖关系
- 依赖:
bb_update.h、includes.h、hardware/usart1/usart1.h、net/upd_download.h、net/meter_download.h、net/server.h、hardware/sdram.h、common/hex_file_parse.h。 - 被依赖:
midware/meter/meter.c(:25includebb_update.h;:5738调bb_update();:6844/6857调GetPercentUpgradeBB())。net/ems.c(:50includebb_update.h;:6706调GetUpgradeFileOffsetBB())。
7. 备注与风险点
- 校验和地址累加存在明确的算术错误(高优先级)——
bb_update.c:436-451:u32 l_u32_flash_addr = g_bb_update.s32_addr_flash; for(u8 i = 0;i < l_u8_flash_len;i++) { l_u32_flash_addr += i - 10; // i 为 u8,i<10 时 i-10 为负数 ... }
i - 10 在 i < 10 时为负整数,与 u32 相加会回绕,且该值在循环中累加(等价于 addr + Σ(i-10)),导致 l_u32_flash_addr 相对真实写地址整体漂移约 5KB(224 字节负载时偏移 0x13E8)。因此前半区/后半区的边界判断(>=BB_APP_FLASH_ADDR_HEAD && <BB_APP_FLASH_ADDR_HALF)会落在错误位置,一段跨越半区边界的固件可能在两侧都漏计或重复计入校验和。正确写法应是 l_u32_flash_addr = g_bb_update.s32_addr_flash + i 或用独立下标。这一点直接决定 CHECK_FLASH 上报给 BB 的期望校验和是否正确。
CHECK_FLASH的第三次校验地址与语义不自洽——:546-562的else分支地址仍用BB_APP_FLASH_ADDR_HALF、长度仍为0xF000,但期望值用check_code[2],而check_code[2] = check_code[1] + 0x6D83(:481)。同时check_code[2]的补齐项用的是(0xF000 - check_length[1]) - 1(:484),与[0]/[1]的补齐口径不一致。0x6D83是关键字值的魔数,这里属于针对 BB Bootloader 具体实现的"对表"写法,缺少注释,一旦 BB 侧固件版本变化即失效。CHECK_FLASH在SEND_KEY_WORD之后不会被复位——SEND_KEY_WORD成功回到CHECK_FLASH时s_u8_check_time已是 2,于是会再走 8 轮(2→10)第三次校验才结束。看起来:573的判断本意是"只在第一/二档校验完成后写关键字",但这个多出来的循环靠>=10兜底,属于设计含糊处。ERASE_FLASH无重试上限——:364-370只在成功时迁移,失败时原地重发;只有外层 60 秒总超时可以解困,但每次进入该状态都会重新发帧且不刷新s_u32_tick_timeout(仅成功时刷新),因此 60s 后会被强制打断。deal_bb_update_data()使用固定字节模式而非 CRC——:274-291硬编码了 14 个字节的特征值,且g_u8_bb_recv_buf大小为 20 但循环以 18 字节为终止条件,读取下标最大 17,无越界;但若 BB 应答长度变化(例如错误码不同),将永远被判为失败。u8_buffer[256]被整块memcpy到g_u8_write_flash+20(:430),写入范围[20, 276),而g_u8_write_flash数组只有 256 字节——越界 20 字节。由于实际发送长度仅为s_u16_length-8(最大 244),越界部分不会被发出,但会破坏g_u8_write_flash之后的相邻全局数据(链接顺序决定受害对象)。这是真实的缓冲区溢出隐患。WRITE_FLASH的while(1)缺少重入保护:若parse_bb_hex_content反复返回PARSE_ADDR(HEX 文件异常长且没有PARSE_OK触发点),该while会在单次调用内长时间占住 CPU,延时只在deal_bb_update_data()里出现,外层 60s 看门狗也检查不到(检查在switch之后)。s_u32_parse_length在PARSE_END路径不更新(:479-488未累加l_u32_parse_length_temp),所以升级成功时的进度百分比可能停在小于 100 的值。- 魔法数字密集:
:481的0x6D83、:482-484的0xFFFF/-1、:504/:593的28/30、:431的s_u16_length-8、:267的 18 字节长度,均无宏定义。 BB_APP_FLASH_ADDR_END(:27)定义了但从未使用,:446直接写了字面量0x003F6000,修改宏不会生效——典型的版本遗留。g_check_code.u8_time(:197)只自增、从不读取,是死代码。g_u8_reset_instructions0声明为[9]但第一次按 9 字节、第二次按 8 字节发送,属有意为之(差异复位),但注释里写的是{0x00×9},可读性差。s_hex_file_data与g_u8_fw_sdram1[offset]的 HEX 行固定按 80 字节解析(:130),隐含固件 HEX 每行文本长度恒为 80;若打包工具换行格式变化会解析失败。GetPercentUpgradeBB()未做除零保护(:675):get_length_file_updating(UPDT_Meter)在文件长度未知时为 0,会触发除零异常。