简介:KEA128_CAN_BootLoader_v1.0.0.rar 是一套针对恩智浦 KEA128 微控制器设计的 CAN BootLoader 完整工程,面向汽车电子、工业控制等嵌入式开发者,用于通过 CAN 总线对设备进行固件远程升级,解决产线批量烧录或现场维护不便的痛点。压缩包体积仅 402KB,共 67 个文件,包含 Bootloader 主程序、CAN 驱动、Flash 操作、中断处理等 C 源码与头文件,以及编译生成的 .o、.elf、.map、Makefile 和工程与调试配置文件,可直接导入 CodeWarrior 等集成开发环境查看和构建。目前已有 300 人学习下载。工程清晰展示了 BootLoader 的上电初始化流程、CAN 报文收发与帧解析、固件校验及跳转逻辑,同时提供启动代码与链接脚本,便于深入理解 KEA128 的存储映射和底层外设配置。作为 v1.0.0 正式版,该工程已经过初步稳定性验证,可作为实际产品远程升级方案的参考与二次开发基础,尤其适合有嵌入式基础、正在研究车载网络升级和 MCU 在线编程的工程师。 从工程角度拆解这个包的思路会很有意思。名字写得很直白:KEA128 芯片、CAN 总线、BootLoader、版本 v1.0.0,明眼人一看就知道这是一个基于 NXP KEA128 做 CAN 在线升级的引导程序工程包。汽车电子开发里,这玩意儿几乎绕不开,产线烧录、售后刷写、远程升级,全靠它。
如果你正准备做 ECU 的 CAN BootLoader,或者刚拿到这个包不知道怎么改、不知道烧进去怎么测,这篇文章会把整个方案的骨架、协议设计、代码实现和实战坑位一次说透,按“能直接抄作业”的标准来聊。
1. 项目背景:拿到手的到底是什么
1.1 BootLoader 在汽车电子里解决什么问题
先想清楚一个最核心的问题:为什么不能每次都用调试器烧程序?开发阶段当然无所谓,SWD/JTAG 拿过来一插就能下,但到了产线和售后就不现实了。产线上几十上百块板子,每一块都拆壳、接调试器、点下载,效率太低,还容易搞坏座子;到了售后升级场景更麻烦,控制器装在车身上,拆下来返厂成本极高,用户也不能接受。
所以行业里通行的做法就是:芯片出厂时先用调试器烧一个很小的 BootLoader,然后产线通过 CAN 总线把应用固件传进去,售后也能走同一个通道升级。这个包就是干这件事的,它把 CAN BootLoader 的完整工程、协议示例、升级流程都给你备好了。
1.2 KEA128 平台:汽车级 Cortex-M0+ 的性价比之选
KEA128 是 NXP 面向车身电子推出的 ARM Cortex-M0+ 内核 MCU,主频最高 48MHz,128KB Flash、16KB RAM,工作温度范围 -40℃~125℃,通过了 AEC-Q100 认证。用在车窗、雨刮、座椅控制器、BCM 这类车身节点上,性能和成本都卡得很准,属于汽车电子里特别常见的“干杂活但很重要”的一颗芯片。
做 BootLoader 时,128KB Flash 这个容量要精打细算。Boot 区留 8KB 甚至 16KB,App 区剩下的足够放一层功能逻辑。RAM 只有 16KB,意味着做固件缓存的时候不能一次性把整包固件收进内存再写 Flash,必须边收边写,这个约束直接影响了后面帧协议的设计。
1.3 为什么传输通道必须选 CAN
车上现成的通信总线就是 CAN。只要控制器已经挂在车上,CAN 线就在那里,升级时不用额外布线。对比一下 UART,虽然实现简单,但车上 ECU 不一定把 UART 引脚引出到诊断口,而且 UART 抗干扰能力弱,波特率稍微提高就容易出错。RS485 在工业现场用得多,汽车上反而是 CAN 一统天下。
CAN 是差分信号,抗共模干扰能力强,双绞线就能跑得很稳;总线仲裁机制天然支持多节点并发,升级的时候诊断仪和 BootLoader 之间不会因为总线冲突反复重传。再加上 CAN 本身就是链路层协议,有 CRC、帧格式、错误处理机制,比裸 UART 加自定义协议省心得多。
2. BootLoader 核心机制与方案设计
2.1 Flash 分区规划:把地盘先划清楚
BootLoader 最忌讳的一件事:Boot 和 App 互相踩踏。所以第一件事就是把 Flash 地址空间分成边界清晰的几个区域。以 128KB Flash 为例,常见分区是:
| 区域 | 起始地址 | 大小 | 说明 |
|---|---|---|---|
| Boot 区 | 0x00000000 | 8KB(0x2000) | BootLoader 代码、中断向量表 |
| App 区 | 0x00002000 | 约 120KB | 应用固件,含自己的中断向量表 |
| 配置/标志区 | Flash 顶部 | 预留 | 固件版本号、升级标志、校验结果 |
Boot 区 8KB 看起来不大,但对一个不含复杂协议栈的 CAN BootLoader 来说绰绰有余。App 区一般从 0x00002000 开始,这样两个区域的地址边界不会重叠。升级标志位要单独放一个固定地址,App 启动时检查这个标志,判断自己是正常运行还是刚从 Boot 跳转过来,这个设计在做“升级失败后回滚”时会很有用。
2.2 CAN 通信协议与帧格式设计
CAN 报文一次最多带 8 字节数据,而一个编译出来的 HEX/S19 固件动辄几十上百 KB,所以协议必须解决“拆包、编号、校验、重传”这四个问题。
我的建议是划分几类帧 ID,标准帧 11 位 ID 足够用:
| 帧 ID | 方向 | 功能 |
|---|---|---|
| 0x100 | 上位机 → Boot | 命令帧(握手、擦除、跳转、校验) |
| 0x101 | 上位机 → Boot | 数据帧 |
| 0x102 | Boot → 上位机 | 应答帧/状态上报 |
数据帧的 8 字节建议这样分配:前 2 字节放包序号(大端或小端,项目里统一定死),后面 6 字节放固件数据,一个包最多传 6 字节。为什么不用 8 字节全放数据?因为序号是必须的,没有序号,一帧丢了根本不知道丢在哪。
上位机和 Boot 之间的交互建议采用“停等 + 超时重传”的机制:上位机发一帧,等 Boot 的应答帧,收到后再发下一帧。如果 50ms 内没收到应答,自动重发当前帧。效率低一点没关系,BootLoader 场景对升级时间没那么苛刻,可靠性才是第一位。
2.3 中断向量表偏移与跳转流程
App 固件编译时起始地址已经改到了 0x2000,它的中断向量表也在这个位置。但芯片上电后 CPU 默认去 0x00000000 取向量表,所以 Boot 跳转到 App 之前,必须把中断向量表重定向到 0x2000。Cortex-M0+ 内核支持向量表偏移寄存器 VTOR,赋值方式很直接:
#define APP_START_ADDR 0x00002000u void jump_to_app(void) { uint32_t app_sp = *(volatile uint32_t *)APP_START_ADDR; uint32_t app_pc = *(volatile uint32_t *)(APP_START_ADDR + 4); pFunction jump_fn = (pFunction)app_pc; __disable_irq(); SCB->VTOR = APP_START_ADDR; __set_MSP(app_sp); jump_fn(); }这里有两个检查不能少。第一,app_sp必须落在 RAM 地址范围内,否则说明 App 区根本没有有效固件,跳过去就是死机。第二,跳转前必须关闭全局中断,因为 Boot 初始化过的外设中断如果残留到 App 里,App 的中断处理函数可能还没初始化好,一个中断进来就直接跑飞了。
3. 实操过程与关键代码实现
3.1 FlexCAN 底层初始化与位时序计算
CAN 通信好不好用,90% 取决于波特率和采样点配置。KEA128 的 FlexCAN 模块需要根据输入时钟计算位时序。举个例子:假设 FlexCAN 输入时钟是 20MHz,目标波特率 500kbps,那么每个位时间就是 20MHz / 500kbps = 40 TQ。但 FlexCAN 的段长度有限制,所以需要先用预分频器把时钟降下来。
我用的是预分频 4(PRESDIV = 3),得到 5MHz 的 CAN 时钟,这样每位时间就是 10 TQ。分配方案:
- 同步段 Sync_Seg:1 TQ(固定)
- 传播段 Prop_Seg:4 TQ
- 相位缓冲段 PS1:2 TQ
- 相位缓冲段 PS2:3 TQ
- 同步跳跃宽度 SJW:1 TQ
采样点位置 = (Sync_Seg + Prop_Seg + PS1) / 总位时间 = (1 + 4 + 2) / 10 = 70%。CAN 总线一般推荐采样点落在 70% 到 80% 之间,70% 是个稳妥的起点,总线较长、干扰较大时甚至可以往后调一点。
SJW 这个参数容易被忽略,它的作用是容忍节点间时钟微小偏差。SJW 越大,重同步能力越强,但对整个位时间的扰动也越大。车身控制这种对实时性要求不是极端的场景,SJW 取 1 到 2 TQ 就够了。
初始化代码核心如下:
void flexcan_init(void) { // 开启 FlexCAN 模块时钟,关闭模块后再配置 CAN0->MCR &= ~CAN_MCR_MDIS_MASK; CAN0->MCR |= CAN_MCR_FRZ_MASK; CAN0->CTRL1 = CAN_CTRL1_PRESDIV(3) // 预分频 4 | CAN_CTRL1_PROPSEG(3) // PropSeg = 4 TQ | CAN_CTRL1_PSEG1(1) // PS1 = 2 TQ | CAN_CTRL1_PSEG2(2) // PS2 = 3 TQ | CAN_CTRL1_RJW(0) // SJW = 1 TQ | CAN_CTRL1_CLKSRC_MASK; // 选择时钟源 // 退出冻结模式 CAN0->MCR &= ~CAN_MCR_FRZ_MASK; }注意,不同芯片的寄存器定义和时钟源选择位会有差异,一定要对着参考手册看,别直接套。
3.2 ACCCODE 与 ACCmask:接收滤波配置
BootLoader 里 CAN 中断如果每个报文都唤醒 CPU 一次,对实时性有影响,而且很多无关报文会干扰 BootLoader 的逻辑。所以一定要把接收滤波打开,只放行跟自己约定的帧 ID。
KEA128 的 CAN 模块支持接收码与接收屏蔽寄存器,这就是 ACCCODE 和 ACCMASK。规则类似:ACCCODE 里存期望接收的 ID,ACCMASK 里某一位为 0 表示这一位必须严格匹配,为 1 表示不关心。比如我只想接收 0x100、0x101、0x102 这三类帧,核心 ID 范围是 0x100 ~ 0x102,可以设置:
CAN0->IDAC = 0; // 使用单滤波模式 CAN0->IDAR0 = (0x100 << 21); // 接收码,标准帧左对齐 CAN0->IDMR0 = 0x1FFFFF03; // 低 2 位不关心,可接收 0x100~0x103这里的滤波设置要结合具体寄存器位段来调整。实操时建议先过滤掉广播帧、错误帧之外的所有 ID,等 BootLoader 联调通过后再逐步放宽,否则调试时总线上一堆无关报文会让排查变得非常痛苦。
3.3 App 端配合要点与上位机联调
Boot 端只是升级流程的一半,App 端配合不到位,升级流程照样跑不通。
App 工程必须做的事有三件。第一,在编译环境里把程序起始地址改到 0x2000,Keil 里就是 IROM1 的 Start 改成 0x2000,Size 改成可用 Flash 大小;IAR 里则需要在链接器配置里改起始地址。第二,在系统初始化代码最开始的位置设置 VTOR,让中断向量表指向新地址。第三,预留一个“软件跳转到 Boot”的入口,比如收到某个特定 CAN 报文或者擦除一个标志位后复位重启,保证 App 运行时也能主动进入升级模式。
上位机联调时,我习惯先用现成的 CAN 分析仪工具,比如周立功的 CANTest、PCAN 的免费上位机,或者自己用 python-can 写一个简单的发送脚本,先手动发握手命令,确认 Boot 能应答,再逐步跑完整升级流程。这样比直接去写完整上位机方便,能快速定位问题是出在底层还是协议层。
完整升级流程大致是:
- 上位机发 0x01 握手命令,Boot 应答 0x01,带版本号;
- 上位机发 0x02 擦除命令,Boot 擦除 App 区;
- 上位机逐帧发 0x101 数据帧,Boot 每收到一帧写一次 Flash;
- 全部发送完成后,上位机发 0x04 校验命令,Boot 回读 Flash 计算 CRC,返回比对结果;
- 校验通过,上位机发 0x03 跳转命令,Boot 跳入 App。
4. 常见问题与排查技巧实录
4.1 最容易翻车的三个地方
第一个坑:看门狗没关。KEA128 内部有 COP 看门狗,擦除一块 Flash 要好几毫秒,如果擦除期间没有及时喂狗,狗一叫,MCU 复位,升级直接中断。很多第一次做 BootLoader 的人在这个问题上卡好几天。解决方式是在 Boot 初始化时禁用 COP,或者在整个擦写流程里加喂狗点,但强烈建议直接禁用,升级过程短,不需要看门狗兜底。
第二个坑:Flash 擦写函数没放到 RAM 里执行。KEA128 这类芯片的 Flash 控制器有约束:执行 Flash 编程/擦除命令时,CPU 不能同时从 Flash 取指令,否则操作会失败或者触发总线错误。解决办法是把 Flash 操作函数复制到 RAM 中,再从 RAM 跳过去执行。这个细节在芯片参考手册的 Flash 章节里写得比较隐晦,但实际项目里不处理必挂。
第三个坑:跳转 App 后一切正常,但任何中断一触发就死机。原因通常是 VTOR 设置时机太晚,或者 App 启动文件里中断向量表没有重新定位。检查方法就是单步看 SCB->VTOR 的值是否等于 0x2000,同时确认 App 的启动文件有没有把 .isr_vector 段放到正确位置。
4.2 用示波器判断 CAN 通信是否健康
排查 CAN 物理层问题时,示波器比任何软件工具都直观。拿示波器探头分别测 CAN_H 和 CAN_L 对地波形,正常情况下:
- 隐性电平:CAN_H 约 2.5V,CAN_L 约 2.5V,差分电压接近 0V;
- 显性电平:CAN_H 约 3.5V,CAN_L 约 1.5V,差分电压约 2V。
如果测到显性电平只有 1V 左右,大概率是终端电阻没接或总线分叉太多,信号衰减严重。如果波形边沿很缓、上升沿拉得很长,说明线缆过长或者总线节点数太多,需要降波特率或者改善布线。如果 CAN_H 和 CAN_L 波形完全一样、没有差分变化,收发器可能已经损坏。
实际调试时最好在总线两端各接一个 120Ω 终端电阻,别为了省事只在一端接,波形会非常难看。
4.3 常见问题速查表
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| Boot 完全收不到 CAN 报文 | 终端电阻缺失、波特率不一致、发送端没发出来 | 示波器看物理波形,确认 ID 滤波 |
| 能收到握手命令,但擦除失败 | 看门狗复位、Flash 操作未在 RAM 执行 | 禁用 COP,检查 Flash 状态寄存器 |
| 数据写到一半失败 | Flash 擦除不完整、地址越界 | 分段擦除,每次写入前检查地址范围 |
| 跳转到 App 后跑飞 | VTOR 未设置、中断残留、App 起始地址错误 | 检查向量表偏移,跳转前关闭中断 |
| 升级完成后 App 功能异常 | 固件校验失败、CRC 算法不一致 | 增加回读校验,核对上下位机 CRC 算法 |
| 总线偶尔报错帧 | 采样点偏早、线缆劣化 | 用 BW 值调整采样点,检查总线拓扑 |
4.4 一个被反复问到的协议问题:CRC 到底校验哪几个字节
数据帧是边收边写的,帧序号已经保证了顺序和完整性,是否每帧都要带 CRC?我的做法是:每帧带 1 字节 CRC 针对序号 + 数据计算,收到后先算一遍,对不上直接丢弃并请求重发;整包固件传输完成后,再对整个 App 区做一次 CRC 汇总校验,防止出现“每一帧都对,但中间漏了一帧”的边界情况。第一次写 BootLoader 的人容易漏掉最后的全量校验,这个坑很隐蔽。
如果这个包是你接手别人项目的第一个素材,我建议拿到后先干三件事:对着手册确认 KEA128 的 Flash 分页大小,确保 Boot 区和 App 区边界是页大小的整数倍;再确认 FlexCAN 的时钟源和波特率配置是否和生产一致;最后用一块开发板,把上位机的 S19 解析脚本先写好、用模拟数据把协议流程跑通,再碰真机。BootLoader 这东西调试窗口很短,一旦程序跳飞就得重新烧录,准备越充分,后面越省心。
本文还有配套的精品资源,点击获取