2. 刷写任务模型:刷写任务的生命周期与调度策略
刷写任务,说白了就是ECU里那套“烧录”动作的完整流程。很多人以为刷写就是发个文件、等进度条走完就完事了。其实没那么简单。一个刷写任务从创建到结束,要经历好几个阶段,每个阶段都有严格的检查和状态管理。
我刚开始做刷写开发时,就吃过亏。有一次任务状态没处理好,刷到一半断电了,结果ECU变砖,只能拆下来用编程器救。嗯,从那以后,我对任务生命周期就特别较真。
2.1 刷写任务的四个核心阶段
一个标准的刷写任务,生命周期分为四个阶段:初始化、执行、完成、回滚。每个阶段都有明确的任务状态和触发条件。
| 阶段 | 状态码 | 触发条件 | 关键动作 |
|---|---|---|---|
| 初始化 | INIT | 刷写请求到达 | 资源分配、环境检查、版本校验 |
| 执行 | RUNNING | 初始化成功 | 数据传输、擦除、编程、验证 |
| 完成 | COMPLETED | 所有步骤成功 | 资源释放、状态上报、日志记录 |
| 回滚 | ROLLBACK | 执行阶段出错 | 恢复备份、标记失败、通知上层 |
2.2 初始化阶段:别急着干活,先检查清楚
初始化阶段,说白了就是“准备工作”。我个人习惯,在这个阶段至少做三件事:
- 资源检查:确认Flash空间够不够、RAM缓冲区有没有分配好、通信通道是否正常。
- 环境验证:检查ECU当前状态是否允许刷写(比如车速为0、发动机没转、电源电压稳定)。
- 版本比对:对比待刷文件和当前固件的版本号,防止刷错或降级。
初始化成功后,任务状态从 INIT 切换到 RUNNING。这时候才开始真正的数据传输。
2.3 执行阶段:核心动作,步步惊心
执行阶段是刷写任务的主体。它包含几个子步骤,每个子步骤都可能失败:
- 擦除(Erase):把目标Flash区域清空。注意,有些芯片擦除时间很长,要设置合理的超时。
- 编程(Program):把数据写入Flash。通常按块传输,每块都要校验。
- 验证(Verify):读回写入的数据,和源文件比对。这一步不能省。
执行阶段最怕什么?怕通信中断。我记得有一次在车上刷写,诊断线接触不良,数据块传了一半就断了。还好我做了断点续传机制,不然又得从头来。
2.4 完成阶段:收尾工作不能马虎
所有数据写完了,不代表任务就结束了。完成阶段要做几件事:
- 释放占用的内存和缓冲区
- 更新ECU的刷写状态标志位
- 记录刷写日志(时间、版本、结果)
- 通知上层应用:刷写已完成
完成阶段如果出问题,比如日志写失败,我个人建议不要影响主流程。日志丢了可以补,但ECU功能必须正常。
2.5 回滚阶段:最后的保命手段
回滚,说白了就是“后悔药”。当执行阶段出现不可恢复的错误时,任务必须回滚到刷写前的状态。
回滚策略有两种:
| 策略 | 原理 | 适用场景 |
|---|---|---|
| 备份恢复 | 刷写前把原固件备份到另一个区域,出错后恢复 | Flash空间充足,安全性要求高 |
| 标记回滚 | 不恢复数据,只标记刷写失败,下次启动时自动重试 | Flash空间紧张,允许重试 |
2.6 任务优先级与调度策略
一个ECU里可能同时有多个刷写任务在排队。比如同时刷写多个ECU,或者刷写任务和诊断任务冲突。这时候就需要调度策略。
我常用的优先级规则:
- 紧急刷写(如安全更新):优先级最高,抢占式调度
- 普通刷写:中等优先级,按FIFO排队
- 诊断任务:低优先级,刷写任务执行时暂停诊断
调度策略的核心原则:刷写任务不能被其他非关键任务打断。你想想看,刷写到一半,突然来个诊断请求要读故障码,这时候如果切换上下文,Flash写入就可能失败。
2.7 任务状态机总览
下面这张图,是我画的一个刷写任务状态机。它把四个阶段和状态切换关系都串起来了。你仔细看看,每个箭头都代表一个触发条件。
这张图里,我特意画了回滚到初始化的虚线路径。为什么?因为有些场景下,回滚完成后可以自动重试。比如通信临时中断,恢复后重新刷写,不用等人工干预。
2.8 实际项目中的调度配置
最后,我分享一个实际项目中的调度配置模板。这个配置用在某款量产ECU上,经过多轮测试,效果不错。
/* 刷写任务调度配置 */
typedef struct {
uint8_t taskPriority; /* 0-紧急,1-普通,2-低优 */
uint8_t maxRetryCount; /* 最大重试次数 */
uint16_t timeoutMs; /* 单步超时时间 */
bool enableRollback; /* 是否启用回滚 */
bool enableBackup; /* 是否启用备份恢复 */
} FlashTaskConfig;
/* 默认配置实例 */
const FlashTaskConfig g_defaultFlashConfig = {
.taskPriority = 1, /* 普通优先级 */
.maxRetryCount = 3, /* 最多重试3次 */
.timeoutMs = 5000, /* 5秒超时 */
.enableRollback = true, /* 启用回滚 */
.enableBackup = true /* 启用备份 */
};
maxRetryCount 不要设太大。重试3次还失败,说明问题不是偶然的,再重试也没意义。不如直接报错,让上层决定下一步。
好了,刷写任务的生命周期和调度策略,就讲到这里。你记住四个阶段、三种调度优先级、以及状态机的切换逻辑,基本就能应对大部分刷写场景了。