C2000 不掉电升级(LFU)方案详解(含流程、代码与官方方案适配)
一、方案核心定位
基于 TI C2000 系列 MCU(F28x/F29x)的不掉电升级(LFU,Live Firmware Update),核心目标是在设备不复位的情况下完成固件更新,同时保证原有应用的实时中断(ISR)正常运行,避免升级过程中业务中断。与传统掉电升级(依赖复位切换固件)相比,LFU 更适用于工业 PSU、汽车电子等对连续性要求极高的场景,而 FOTA(Firmware-Over-The-Air)本质是"带复位的 LFU",属于汽车领域术语。
方案核心架构延续 TI 官方推荐的 双 Flash bank 分区 + 自定义 Bootloader + 编译器 LFU 支持,解决了 C2000 无硬件 A/B 地址映射的限制,实现"运行一个 bank、更新另一个 bank"的并行操作,同时通过断点续传、状态校验避免掉电/通信异常导致的固件损坏。
二、方案详细介绍
1. 核心设计依据(结合 TI 官方文档)
(1)硬件基础:Flash 分区规划
C2000 内部 Flash 划分为多个独立 bank(如 F28003x/F28P55x 支持多 bank),单个 bank 不支持"边读边写(RWW)",因此必须通过双 bank 实现 LFU:
- 运行区(Active Bank):执行当前有效固件,保障业务和 ISR 正常运行;
- 更新区(Inactive Bank):接收并烧写新固件,不影响运行区;
- Bootloader 区:固化在独立扇区(如 Sector 0),包含 bank 选择逻辑、SCI 通信内核、Flash API,不可被升级覆盖;
- 状态标志区:存储升级状态(未升级/升级中/升级完成)、断点偏移、固件校验值(KEY/REV/CRC),建议位于 Bootloader 区预留空间。
分区示例(F28003x 双 bank 配置):
| 分区名称 | 地址范围 | 功能说明 |
|---|---|---|
| Bootloader 区 | 0x080000~0x08FFFF | 包含 bank 选择逻辑、SCI 内核、Flash API、状态标志区 |
| Active Bank(A) | 0x090000~0x09FFFF | 运行当前固件(v1.0),含应用代码、ISR 向量表 |
| Inactive Bank(B) | 0x0A0000~0x0AFFFF | 接收新固件(v2.0),升级完成后通过 Bootloader 切换为 Active Bank |
| 预留区(Rollback) | 0x0B0000~0x0BFFFF | 可选,存储旧固件备份,支持升级失败回滚(FOTA 场景常用) |
(2)关键技术特性(TI 官方 LFU 核心能力)
- 编译器 LFU 支持 :TI CCS 编译器提供
--lfu_default=preserve/all选项,以旧固件为"参考"编译新固件,自动保留公共变量的 RAM 地址和状态,新变量通过__attribute__((update))标记初始化,避免升级后状态丢失; - 快速向量切换:F28003x 等支持 PIE 向量表冗余设计,切换时间从 960 周期缩减至 1 周期,满足实时性要求;
- RAM 交换机制:通过 LS0/LS1 RAM 交换函数指针,实现新旧固件函数的快速切换(仅需 1 CPU 周期);
- CLA 协同升级:若涉及 CLA 核(如电机控制场景),需先将 CPU 设为主控,拷贝 CLA 代码至 LSxRAM,停止 CLA 后台任务后再激活新固件,支持 C28 核与 CLA 核异步升级。
(3)核心约束与解决策略
| 约束问题 | 解决策略 |
|---|---|
| 无硬件 A/B 地址映射 | 编译时指定新固件目标 bank,Bootloader 通过 KEY/REV 校验选择启动 bank |
| 单 Flash bank 设备(如 F28002x) | 将 Bootloader 核心代码、ISR 拷贝至 RAM 运行,再擦写 Flash bank(见下文扩展方案) |
| 升级中掉电/通信中断 | 状态标志区记录断点偏移,重启后从断点续传,仅重传未完成数据块 |
| 新固件初始化与旧 ISR 冲突 | 新固件初始化(__TI_auto_init_warm)在旧 ISR 运行时并行执行,无时间约束 |
2. 升级核心流程(融合官方 LFU 流程与断点续传)
LFU 流程分为"下载安装"(无时间约束)和"激活切换"(时间约束,需在 ISR 空闲期完成)两个阶段,具体步骤如下:
- 触发升级 :上位机通过 SCI/CAN 下发 LFU 指令,当前应用(Active Bank)的
CommandLogISR()注册指令,移交控制权给 Bootloader 的RunLFU(); - 初始化升级状态:Bootloader 标记状态为"升级中",记录断点偏移为 0(新升级)或读取历史断点(续传),向上位机反馈续传起始地址;
- 分块下载与烧写:上位机下发固件数据块(建议 1KB~4KB,适配 Flash 扇区大小),Bootloader 校验数据完整性后,烧写至 Inactive Bank 对应地址,每完成一块更新断点偏移;
- 固件校验:所有数据传输完成后,Bootloader 计算 Inactive Bank 固件的 CRC,与上位机下发的 KEY/REV 校验值比对,确保固件完整;
- 预初始化新固件 :Bootloader 跳转至新固件的固定入口
c_int_lfu,调用编译器 LFU 初始化函数__TI_auto_init_warm,并行执行新固件的 RAM 拷贝、外设配置(不影响旧 ISR); - 激活切换 :
IdentifyIdleTime()检测 ISR 空闲期,ActivateApp()禁用全局中断,1 周期内完成向量表/函数指针切换,启用新固件 ISR,恢复中断(此阶段耗时<1us,满足实时性); - 完成升级 :新固件主函数
main()检测 LFU 标志,跳过复位初始化流程,直接运行业务逻辑,状态标志更新为"升级完成"。
三、LFU 升级流程图(含断点续传与异常处理)





四、代码案例(F28003x 双 bank 实现,基于 TI 官方库)
1. 前置条件
- 开发环境:CCS12.x + C2000Ware 6.00+ + TI 编译器(支持
--lfu_default); - 硬件:F28003x LaunchPad + 双 bank Flash 配置;
- 通信接口:SCI(UART),波特率 115200;
- 依赖库:
F28003x_FlashAPI_Library、driverlib、CRC 库。
2. 核心代码实现
(1)分区与状态定义(linker 与头文件)
c
// 1. 分区地址定义(需与 linker cmd 文件一致)
#define BOOTLOADER_ADDR 0x080000U // Bootloader 起始地址
#define ACTIVE_BANK_ADDR 0x090000U // Active Bank 起始地址
#define INACTIVE_BANK_ADDR 0x0A0000U // Inactive Bank 起始地址
#define STATE_ADDR 0x08FF00U // 状态标志区地址(Bootloader 区预留)
#define BLOCK_SIZE 2048U // 数据块大小(适配 4KB 扇区)
#define FIRMWARE_MAX_SIZE 0x10000U // 固件最大大小(64KB)
// 2. 升级状态枚举
typedef enum {
LFU_NONE = 0, // 未升级
LFU_ING = 1, // 升级中
LFU_DONE = 2, // 升级完成
LFU_ERR = 3 // 升级错误
} LfuState;
// 3. 状态标志结构体(需 16 位对齐,适配 Flash 写入)
typedef struct {
LfuState state; // 升级状态
uint32_t offset; // 断点偏移地址
uint32_t firmware_crc; // 固件 CRC32
uint32_t key; // 固件校验 KEY(32 位,上位机下发)
uint32_t rev; // 固件版本 REV(32 位)
} LfuFlag __attribute__((aligned(2)));
LfuFlag g_lfu_flag;
uint8_t g_data_block[BLOCK_SIZE]; // 数据块缓存
(2)Flash 操作与状态读写(基于官方 Flash API)
c
#include "F28003x_Flash.h"
#include "crc.h"
// 读取升级状态(从 Flash 加载到 RAM)
void Lfu_ReadFlag(void) {
memcpy(&g_lfu_flag, (void*)STATE_ADDR, sizeof(LfuFlag));
// 状态合法性校验(防止乱码)
if (g_lfu_flag.state > LFU_ERR || g_lfu_flag.offset > FIRMWARE_MAX_SIZE) {
g_lfu_flag.state = LFU_NONE;
g_lfu_flag.offset = 0;
}
}
// 更新升级状态(先擦除扇区,再写入 Flash)
void Lfu_WriteFlag(void) {
FlashStatus status;
// 擦除状态标志所在扇区(4KB 扇区)
status = Flash_Erase(STATE_ADDR, STATE_ADDR + 0xFFF, FLASH_API_HWREG_BASE);
if (status != FLASH_SUCCESS) ESTOP0; // 擦除失败停机
// 写入状态数据(Flash 按 16 位宽写入)
status = Flash_Program(STATE_ADDR, (uint16_t*)&g_lfu_flag, sizeof(LfuFlag)/2, FLASH_API_HWREG_BASE);
if (status != FLASH_SUCCESS) ESTOP0;
}
// 烧写数据块到 Inactive Bank
void Lfu_WriteBlock(uint32_t dest_addr, uint8_t* data, uint16_t len) {
FlashStatus status;
// 若目标地址是扇区起始,先擦除扇区
if ((dest_addr % 0x1000) == 0) {
status = Flash_Erase(dest_addr, dest_addr + 0xFFF, FLASH_API_HWREG_BASE);
if (status != FLASH_SUCCESS) ESTOP0;
}
// 写入数据块
status = Flash_Program(dest_addr, (uint16_t*)data, len/2, FLASH_API_HWREG_BASE);
if (status != FLASH_SUCCESS) ESTOP0;
}
// 计算固件 CRC32(适配 C2000 CRC 模块)
uint32_t Lfu_CalcCRC(uint32_t start_addr, uint32_t len) {
uint32_t crc = 0xFFFFFFFF;
uint16_t* data_ptr = (uint16_t*)start_addr;
for (uint32_t i = 0; i < len/2; i++) {
crc = CRC32_calc(crc, data_ptr[i]);
}
return ~crc;
}
(3)LFU 核心逻辑(含断点续传与激活)
c
#include "scia.h"
// 上位机通信协议(简单示例)
#define LFU_CMD_START 0x01 // 启动升级
#define LFU_CMD_DATA 0x02 // 数据块
#define LFU_CMD_CRC 0x03 // CRC 校验
#define LFU_ACK_SUCCESS 0xAA // 成功应答
#define LFU_ACK_RETRY 0xBB // 重传请求
void Lfu_Process(void) {
uint16_t recv_len = 0;
uint32_t target_crc = 0;
Lfu_ReadFlag(); // 读取当前状态
// 续传场景:向上位机发送断点偏移
if (g_lfu_flag.state == LFU_ING) {
Sci_SendByte(LFU_ACK_SUCCESS);
Sci_SendUint32(g_lfu_flag.offset); // 发送断点偏移
} else {
// 新升级:初始化状态
g_lfu_flag.state = LFU_ING;
g_lfu_flag.offset = 0;
Lfu_WriteFlag();
Sci_SendByte(LFU_ACK_SUCCESS);
}
// 循环接收数据块
while (g_lfu_flag.offset < FIRMWARE_MAX_SIZE) {
// 等待接收上位机指令
uint8_t cmd = Sci_RecvByte();
switch (cmd) {
case LFU_CMD_DATA: {
// 接收数据块长度(2 字节)
uint16_t block_len = Sci_RecvUint16();
if (block_len > BLOCK_SIZE) block_len = BLOCK_SIZE;
// 接收数据块内容
recv_len = 0;
while (recv_len < block_len) {
g_data_block[recv_len++] = Sci_RecvByte();
}
// 烧写至 Inactive Bank
uint32_t dest_addr = INACTIVE_BANK_ADDR + g_lfu_flag.offset;
Lfu_WriteBlock(dest_addr, g_data_block, block_len);
// 更新断点偏移
g_lfu_flag.offset += block_len;
Lfu_WriteFlag();
Sci_SendByte(LFU_ACK_SUCCESS); // 应答成功
break;
}
case LFU_CMD_CRC: {
// 接收上位机下发的 CRC
target_crc = Sci_RecvUint32();
// 计算 Inactive Bank 固件 CRC
uint32_t calc_crc = Lfu_CalcCRC(INACTIVE_BANK_ADDR, g_lfu_flag.offset);
if (calc_crc == target_crc) {
Sci_SendByte(LFU_ACK_SUCCESS); // CRC 校验通过
g_lfu_flag.firmware_crc = calc_crc;
Lfu_WriteFlag();
} else {
Sci_SendByte(LFU_ACK_RETRY); // 校验失败,请求重传
return;
}
break;
}
default:
Sci_SendByte(LFU_ACK_RETRY);
break;
}
}
// 预初始化新固件,跳转至 c_int_lfu
if (g_lfu_flag.state == LFU_ING && g_lfu_flag.firmware_crc == target_crc) {
// 标记升级完成,准备激活
g_lfu_flag.state = LFU_DONE;
Lfu_WriteFlag();
// 跳转至新固件 LFU 入口(固定地址)
void (*lfu_entry)(void) = (void (*)(void))(INACTIVE_BANK_ADDR + 0x100); // c_int_lfu 地址
lfu_entry();
}
}
// Bootloader 主函数
void main(void) {
// 系统初始化
InitSysCtrl();
InitSciaGpio();
Scia_Init(115200); // SCI 初始化
Flash_InitModule(FLASH_API_HWREG_BASE); // Flash API 初始化
Lfu_ReadFlag();
// 分支逻辑
if (g_lfu_flag.state == LFU_ING || g_lfu_flag.state == LFU_DONE) {
Lfu_Process(); // 进入升级/激活流程
} else {
// 启动 Active Bank 应用
void (*app_entry)(void) = (void (*)(void))(ACTIVE_BANK_ADDR + 0x00);
app_entry();
}
while (1);
}
(4)新固件 LFU 初始化(c_int_lfu 入口)
c
// 新固件代码(需开启编译器 LFU 支持:--lfu_default=all --lfu_reference_elf=旧固件.out)
#include "F28003x_Device.h"
// 标记需要初始化的新变量
volatile float32_t __attribute__((update)) g_new_var = 0.0f;
// LFU 初始化函数(预初始化新固件)
void init_lfu(void) {
// 1. Flash 到 RAM 代码拷贝(如性能敏感的 ISR)
memcpy(&RamFunc, &FlashFunc, sizeof(FlashFunc));
// 2. 更新备用向量表(Inactive PIE Vector)
memcpy(&PieVectTableInactive, &PieVectTableNew, sizeof(PieVectTableNew));
// 3. 外设配置(不影响旧 ISR 运行)
InitPeripheralNew();
}
// 检测 ISR 空闲期
void IdentifyIdleTime(void) {
while (PieCtrlRegs.PIEACK.all != 0); // 等待所有 ISR 完成
}
// 激活新固件(时间约束步骤)
void ActivateApp(void) {
DINT; // 禁用全局中断
// 切换 PIE 向量表(1 周期完成)
PieCtrlRegs.PIECTRL.bit.VECTACTIVE = 0;
PieCtrlRegs.PIECTRL.bit.VECTSWAP = 1;
// 切换函数指针(RAM LS0/LS1 交换)
EALLOW;
SysCtrlRegs.RAMSWAP.bit.LS0LS1SWAP = 1;
EDIS;
EINT; // 启用中断
}
// LFU 入口(固定地址,供 Bootloader 跳转)
#pragma CODE_SECTION(c_int_lfu, "lfu_entry")
void c_int_lfu(void) {
// 编译器 LFU 初始化(保留公共变量状态,初始化新变量)
__TI_auto_init_warm();
// 预初始化新固件
init_lfu();
// 检测 ISR 空闲期,激活新固件
IdentifyIdleTime();
ActivateApp();
// 跳转至新固件 main 函数
main();
}
void main(void) {
// 检测 LFU 标志,跳过复位初始化
if (SysCtrlRegs.LFUCTRL.bit.LFUACTIVE == 1) {
// 直接运行业务逻辑
while (1) {
// 新固件业务代码
g_new_var += 0.1f;
}
} else {
// 正常复位初始化(兼容传统启动)
InitSysCtrl();
InitPeripherals();
while (1);
}
}
3. 编译器配置(关键)
需在 CCS 中配置编译器选项,确保 LFU 兼容性:
- 启用 LFU 模式:
--lfu_default=all(自动保留公共变量,初始化新变量); - 指定参考固件:
--lfu_reference_elf=../old_fw/old_fw.out(以旧固件为参考编译); - Linker 配置:分离 Active/Inactive Bank 地址,确保无内存重叠。
五、扩展方案:单 Flash Bank 设备(如 F28002x)
对于仅单 Flash bank 的 C2000 设备,需通过"RAM 运行核心代码"实现 LFU:
- Bootloader 启动后,将 Flash API、SCI 内核拷贝至
RAM_BL_X; - 启动应用后,将关键 ISR 拷贝至
RAM_APP_X,确保升级时 ISR 正常运行; - 接收新固件时,Bootloader 擦写 Flash bank(覆盖旧固件),新固件激活逻辑与双 bank 一致;
- 约束:RAM 需划分独立区域(
RAM_BL_X/RAM_APP_X不重叠),无硬件向量表交换,需手动更新向量表。
参考官方示例:C2000Ware/driverlib/f28002x/examples/flash/f28002_lfu_singlebank
六、方案验证与官方资源
1. 验证要点
- 实时性:激活切换阶段耗时<1us,不影响 200kHz 高频 ISR;
- 可靠性:升级中掉电后,重启可从断点续传,Active Bank 固件正常运行;
- 兼容性:新固件可访问旧固件公共变量,状态不丢失。
2. 官方参考资源
- 参考设计:TIDM-02011(F28003x/F28004x LFU 参考设计)、TIDA-010062(PFC 应用 LFU);
- 文档:TIDUEY4(LFU 设计指南)、SPRUIU9(无复位 LFU 应用笔记)、SDAA064(F29x LFU 文档);
- 工具:C2000Ware 6.00+(含 Flash API、LFU 编译器支持)。
七、总结
本方案完全基于 TI C2000 官方 LFU 架构与技术特性,融合断点续传机制,解决了"不掉电升级+实时性+可靠性"三大核心需求:
- 架构优势:双 bank 分区隔离,避免升级对业务的影响,兼容单 bank 设备;
- 工具支撑:利用 TI 编译器 LFU 支持,简化新旧固件状态同步;
- 工程落地:代码基于官方库开发,可直接适配 F28003x/F28P55x/F29x 等系列,提供完整的通信、Flash 操作、激活逻辑。
实际应用中需注意:Bootloader 需做只读保护(通过 Security Key),避免被意外覆盖;数据块大小建议适配 Flash 扇区(4KB),减少擦写次数;可增加回滚机制,若新固件运行异常,Bootloader 切换回旧固件。