【C2000系列DSP的不掉电升级】C2000 不掉电升级(LFU)方案详解(含流程、代码与官方方案适配)

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 核心能力)
  1. 编译器 LFU 支持 :TI CCS 编译器提供 --lfu_default=preserve/all 选项,以旧固件为"参考"编译新固件,自动保留公共变量的 RAM 地址和状态,新变量通过 __attribute__((update)) 标记初始化,避免升级后状态丢失;
  2. 快速向量切换:F28003x 等支持 PIE 向量表冗余设计,切换时间从 960 周期缩减至 1 周期,满足实时性要求;
  3. RAM 交换机制:通过 LS0/LS1 RAM 交换函数指针,实现新旧固件函数的快速切换(仅需 1 CPU 周期);
  4. 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 空闲期完成)两个阶段,具体步骤如下:

  1. 触发升级 :上位机通过 SCI/CAN 下发 LFU 指令,当前应用(Active Bank)的 CommandLogISR() 注册指令,移交控制权给 Bootloader 的 RunLFU()
  2. 初始化升级状态:Bootloader 标记状态为"升级中",记录断点偏移为 0(新升级)或读取历史断点(续传),向上位机反馈续传起始地址;
  3. 分块下载与烧写:上位机下发固件数据块(建议 1KB~4KB,适配 Flash 扇区大小),Bootloader 校验数据完整性后,烧写至 Inactive Bank 对应地址,每完成一块更新断点偏移;
  4. 固件校验:所有数据传输完成后,Bootloader 计算 Inactive Bank 固件的 CRC,与上位机下发的 KEY/REV 校验值比对,确保固件完整;
  5. 预初始化新固件 :Bootloader 跳转至新固件的固定入口 c_int_lfu,调用编译器 LFU 初始化函数 __TI_auto_init_warm,并行执行新固件的 RAM 拷贝、外设配置(不影响旧 ISR);
  6. 激活切换IdentifyIdleTime() 检测 ISR 空闲期,ActivateApp() 禁用全局中断,1 周期内完成向量表/函数指针切换,启用新固件 ISR,恢复中断(此阶段耗时<1us,满足实时性);
  7. 完成升级 :新固件主函数 main() 检测 LFU 标志,跳过复位初始化流程,直接运行业务逻辑,状态标志更新为"升级完成"。

三、LFU 升级流程图(含断点续传与异常处理)



四、代码案例(F28003x 双 bank 实现,基于 TI 官方库)

1. 前置条件

  • 开发环境:CCS12.x + C2000Ware 6.00+ + TI 编译器(支持 --lfu_default);
  • 硬件:F28003x LaunchPad + 双 bank Flash 配置;
  • 通信接口:SCI(UART),波特率 115200;
  • 依赖库:F28003x_FlashAPI_Librarydriverlib、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 兼容性:

  1. 启用 LFU 模式:--lfu_default=all(自动保留公共变量,初始化新变量);
  2. 指定参考固件:--lfu_reference_elf=../old_fw/old_fw.out(以旧固件为参考编译);
  3. Linker 配置:分离 Active/Inactive Bank 地址,确保无内存重叠。

五、扩展方案:单 Flash Bank 设备(如 F28002x)

对于仅单 Flash bank 的 C2000 设备,需通过"RAM 运行核心代码"实现 LFU:

  1. Bootloader 启动后,将 Flash API、SCI 内核拷贝至 RAM_BL_X
  2. 启动应用后,将关键 ISR 拷贝至 RAM_APP_X,确保升级时 ISR 正常运行;
  3. 接收新固件时,Bootloader 擦写 Flash bank(覆盖旧固件),新固件激活逻辑与双 bank 一致;
  4. 约束: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 架构与技术特性,融合断点续传机制,解决了"不掉电升级+实时性+可靠性"三大核心需求:

  1. 架构优势:双 bank 分区隔离,避免升级对业务的影响,兼容单 bank 设备;
  2. 工具支撑:利用 TI 编译器 LFU 支持,简化新旧固件状态同步;
  3. 工程落地:代码基于官方库开发,可直接适配 F28003x/F28P55x/F29x 等系列,提供完整的通信、Flash 操作、激活逻辑。

实际应用中需注意:Bootloader 需做只读保护(通过 Security Key),避免被意外覆盖;数据块大小建议适配 Flash 扇区(4KB),减少擦写次数;可增加回滚机制,若新固件运行异常,Bootloader 切换回旧固件。

相关推荐
ZHHHHHJ663 小时前
LL层-PAST
运维·服务器·网络
百***07453 小时前
GPT-5.2 极速接入指南:流程详解与主流模型对比
网络·人工智能·gpt
REDcker4 小时前
TCP/IP 协议栈详解:协议栈是什么意思?为什么叫“协议栈”?
网络·网络协议·tcp/ip
凯子坚持 c5 小时前
Docker网络架构深度解析:从原理到实战
网络·docker·架构
cdprinter5 小时前
信刻光盘数据自动回读系统,多重保障数据安全及调阅便捷性!
网络·安全·自动化
发光小北6 小时前
SG-CAN (FD) NET-210(双通道 CAN (FD) 转以太网网关)特点与功能介绍
开发语言·网络·php
larance6 小时前
kylinv10 设置网卡自启动和固定ip
网络·网络协议
湫一刀6 小时前
WireShark下载说明
网络·测试工具·wireshark
Ha_To7 小时前
2025.12.18 NAT地址转换、PAT
linux·服务器·网络