11、TSP固件升级:固件格式、升级流程、异常处理

触摸屏固件升级,说白了就是给TSP芯片“刷系统”。

我刚开始接触这个模块时,觉得不就是写个bin文件嘛,能有多复杂?结果第一次量产就翻车了——升级到一半断电,整批触控板全部变砖。嗯,从那以后,我对固件升级流程就多了一份敬畏。

11.1 固件格式:不只是二进制那么简单

TSP固件通常以.bin.hex格式存在。但内部结构远不止是裸数据。

我个人习惯把固件包拆成三个部分来看:

  • 头部信息:包含固件版本号、芯片型号、校验码、固件大小等元数据。我见过有些厂商把CRC32直接写在头部前4字节,方便驱动快速校验。
  • 固件主体:真正的可执行代码或配置表。注意,有些IC支持分段升级,比如只更新“校准参数区”而不动“算法区”。
  • 尾部校验:通常是整个固件的哈希值或CRC。升级前驱动会先算一遍,不对就直接拒绝。

一个典型的固件头部结构(我习惯这么定义):

typedef struct {
    uint32_t magic;          // 魔数,比如 0x544F5543
    uint32_t fw_version;     // 版本号,如 0x01020003
    uint32_t chip_id;        // 目标芯片ID
    uint32_t fw_size;        // 固件主体大小
    uint32_t checksum;       // 整个包的CRC32
    uint8_t  reserved[16];   // 保留字段
} tsp_fw_header_t;

为什么要有magic?我在项目中遇到过,有人拿错固件包刷到不兼容的芯片上,结果触控完全乱跳。加了magic校验后,至少能拦住90%的误刷。

11.2 升级流程:三步走,步步惊心

TSP固件升级流程,我总结为三个阶段:

  1. 准备阶段:读取固件文件、校验完整性、确认芯片处于可升级状态。
  2. 传输阶段:通过I2C或SPI将固件数据分块写入芯片的Flash或RAM。
  3. 激活阶段:写入完成标志、复位芯片、验证新固件是否正常运行。

你想想看,这三个阶段任何一个出问题,后果都很严重。我曾经在传输阶段遇到I2C时钟拉伸导致超时,整批设备升级到一半卡死。后来我加了个“每包确认+重试机制”,才彻底解决。

我的经验: 传输阶段建议每包不超过256字节,并且每包都要等ACK。别贪快,稳定第一。

下面这张图是我自己画的升级流程,你看一眼就明白了:

开始升级 1. 读取固件文件 + 校验头部 校验通过? 返回错误 2. 分块写入固件到Flash 3. 写入完成标志 + 复位芯片 升级完成

11.3 异常处理:别让设备变砖

升级过程中最常见的异常,我列了个表:

异常类型 原因 我的处理方式
校验失败 固件文件损坏或下载不完整 拒绝升级,返回错误码,不写Flash
写入超时 I2C/SPI通信异常或芯片忙 重试3次,每次间隔50ms,仍失败则回滚
中途断电 用户拔电池或USB断开 上电后检测“升级未完成标志”,重新开始
版本回退 刷了比当前更旧的固件 默认禁止,除非强制模式开启

警告: 千万不要在升级过程中直接断电或复位芯片。我曾经因为测试时手贱按了复位键,结果芯片的bootloader都坏了,只能拆机用烧录器救回来。

关于回滚机制,我建议在Flash里保留两个区域:

  • Active区:当前运行的固件
  • Backup区:上一次成功升级的固件

升级时先写Backup区,校验通过后再交换标记。这样即使新固件挂了,还能从Backup区启动。嗯,这个方案我在多个项目里用过,从来没翻过车。

11.4 代码片段:升级核心逻辑

下面是我写的一个简化版升级函数,去掉了一些平台细节,保留了核心思路:

int tsp_fw_upgrade(const uint8_t *fw_data, uint32_t fw_len) {
    tsp_fw_header_t *hdr = (tsp_fw_header_t *)fw_data;
    
    // 1. 校验魔数和版本
    if (hdr->magic != TSP_FW_MAGIC) {
        return -ERR_INVALID_MAGIC;
    }
    if (hdr->fw_version <= get_current_fw_version()) {
        return -ERR_VERSION_ROLLBACK;
    }
    
    // 2. 计算并校验CRC
    uint32_t calc_crc = crc32(fw_data + sizeof(tsp_fw_header_t), 
                               fw_len - sizeof(tsp_fw_header_t));
    if (calc_crc != hdr->checksum) {
        return -ERR_CHECKSUM;
    }
    
    // 3. 进入升级模式
    tsp_enter_upgrade_mode();
    
    // 4. 分块写入(每块256字节)
    uint32_t offset = sizeof(tsp_fw_header_t);
    uint32_t remain = fw_len - offset;
    uint8_t retry = 3;
    
    while (remain > 0) {
        uint32_t chunk = (remain > 256) ? 256 : remain;
        int ret = tsp_write_flash(offset, fw_data + offset, chunk);
        if (ret != 0) {
            if (--retry > 0) {
                delay_ms(50);
                continue;
            }
            tsp_exit_upgrade_mode();
            return -ERR_WRITE_FAIL;
        }
        offset += chunk;
        remain -= chunk;
        retry = 3;  // 每块成功重置重试计数
    }
    
    // 5. 写入完成标志并复位
    tsp_write_complete_flag();
    tsp_reset_chip();
    
    return 0;
}

这段代码我用了很多年,核心就是“校验-分块-重试-确认”。你想想看,如果没有重试机制,I2C偶尔一次毛刺就能让整个升级失败,那用户体验得多差。

一个小技巧: 升级过程中可以每写10%报一次进度。我习惯用GPIO翻转或者打印日志,方便产线工人判断是否卡住。

好了,关于TSP固件升级,核心就是这些。格式要严谨,流程要稳健,异常要兜底。做到这三点,你的设备基本不会因为升级变砖。


公众号:蓝海资料掘金营,微信deep3321