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固件升级流程,我总结为三个阶段:
- 准备阶段:读取固件文件、校验完整性、确认芯片处于可升级状态。
- 传输阶段:通过I2C或SPI将固件数据分块写入芯片的Flash或RAM。
- 激活阶段:写入完成标志、复位芯片、验证新固件是否正常运行。
你想想看,这三个阶段任何一个出问题,后果都很严重。我曾经在传输阶段遇到I2C时钟拉伸导致超时,整批设备升级到一半卡死。后来我加了个“每包确认+重试机制”,才彻底解决。
我的经验: 传输阶段建议每包不超过256字节,并且每包都要等ACK。别贪快,稳定第一。
下面这张图是我自己画的升级流程,你看一眼就明白了:
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