当前位置:首页 > 汽车电子 > 汽车电子
[导读]车载OTA变砖基本都栽在同一件事:把新固件直接盖到正在跑的App区,中途掉电/校验失败就没退路。正确架构是「双分区A/B+写非活动Bank+分块续传+整包验签+试运行回滚」。


车载OTA变砖基本都栽在同一件事:把新固件直接盖到正在跑的App区,中途掉电/校验失败就没退路。正确架构是「双分区A/B+写非活动Bank+分块续传+整包验签+试运行回滚」。

一、Flash分区与不变原则

最低布局:Bootloader独立常驻、NVRAM存状态、Bank A/B各一份App。

0x08000000 Bootloader(锁死不擦)

0x08008000 NVRAM(版本/状态/offset/crc)

0x08010000 Bank A

0x08080000 Bank B

铁律三条:

升级只写非活动Bank,当前运行Bank不动

Bootloader不能被App升级覆盖

切换前新Bank必须过整包校验+签名

二、断点续传:分块CRC+偏移落盘

弱网/4G场景必须分块,单包建议1~4KB。每包带序号和CRC32,收完即校验,坏包重传。

断点状态结构存NVRAM(或EEPROM),别只存“百分比”:

typedef struct {

   uint32_t magic;        // 0x0A0A

   uint32_t fw_total;     // 固件总长

   uint32_t offset;       // 已写字节偏移

   uint32_t chunk_idx;    // 当前块号

   uint32_t chunk_crc;    // 上一块CRC缓存

   uint16_t ver;          // 版本号,防串包

   uint8_t  state;        // 0空闲 1下载中 2待校验

} ota_meta_t;

写NVRAM要原子:先写临时区再整体提交,掉电不能留下半截状态。

下载主循环伪代码:

int ota_write_chunk(uint8_t *buf, uint32_t off, uint16_t len, uint32_t crc){

   if(crc32(buf,len) != crc) return -1;            // 块校验失败

   uint32_t dst = inactive_bank_addr() + off;

   flash_erase_page_align(dst, len);

   flash_write(dst, buf, len);

   meta.offset = off + len;

   meta.chunk_idx++;

   nvram_atomic_save(&meta);                        // 每块落盘

   return 0;

}

重连恢复时先读meta,version对得上就从offset继续发Range或自定义续传指令,不对就清掉重下。

三、整包校验+签名验签

分块CRC只防传输位错,防不了篡改。整包收完后做两层:

SHA256整包哈希比对云端下发的digest

ECDSA/RSA验签,公钥来自HSM或eFuse,不进普通RAM

int ota_verify_image(uint32_t bank){

   uint8_t hash[32];

   sha256_flash(bank, fw_total, hash);

   if(memcmp(hash, hdr->expect_hash, 32)) return -1;

   if(hsm_verify_signature(hdr->pub_key_id, hash, hdr->sig))

       return -1;                                   // 验签失败拒刷

   if(hdr->version <= current_version())            // 防回滚攻击

       return -2;

   return 0;

}

验签过才置state=PENDING_SWITCH,否则清Bank标记上报失败。

四、Bootloader切换与试运行回滚

别写完直接永久激活。正确是「Trial态+看门狗守护」:

void boot_select(void){

   meta = nvram_read();

   if(meta.state == PENDING_SWITCH){

       if(ota_verify_image(inactive_bank())==0){

           set_boot_bank(inactive);     // VTOR指向新Bank

           set_trial_flag();            // 试运行标记

           wdt_enable(3000);            // 3秒守护

           jump_to_app(inactive_bank());

       } else rollback();

   }

   // 正常启动当前Bank

   jump_to_app(active_bank());

}

新固件起来后做自检(时钟、CAN、外设、业务心跳),确认OK再调:

mark_bank_permanent();   // 清trial,回滚失效

若新Bank卡死/不喂狗→复位→Bootloader发现trial+异常复位→切回旧Bank。

五、跳转前保命动作

直接函数指针跳App在车规里会HardFault,必须清场:

void jump_to_app(uint32_t addr){

   if((*(uint32_t*)addr & 0x2FFE0000)!=0x20000000) return; // SP合法性

   __disable_irq();

   RCC->CIR = 0; SysTick->CTRL = 0;          // 关SysTick/RCC中断

   SCB->VTOR = addr;                         // 重映射向量表

   __set_MSP(*(uint32_t*)addr);

   ((void(*)(void))(*(uint32_t*)(addr+4)))();

}

外设没复位干净、VTOR没改,是新固件“跳过去就死”的常见原因。

六、量产落坑清单

电量门禁:整车OTA前查12V电瓶/BMS,低于阈值不开始,写入中断电最危险

A/B空间:Flash≥2×App+Boot,空间不够别硬做A/B,退外部eMMC中转要接受安全性下降

断点原子写:状态半截=续传偏移错=整包CRC对不上

防回滚:版本号进签名内容,单调计数器挡旧版刷回

差分包谨慎:差分省流量但RAM占用高,MCU资源紧用全量分块更稳

安全启动链:BootROM→BL→App逐级验签,HSM存公钥,App层不信任网络数据

看门狗时长:Trial窗口别太短,复杂ECU初始化可能要几秒;也别太长,不然“假活”拖慢回滚

按这套写,断网可从块续传、掉电可重跑、新固件起不来自动回旧Bank,Bootloader常驻兜底——这才是车载OTA“不变砖”的完整闭环,不是把文件下进去就完事。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭