BootLoader概述
BootLoader(引导加载程序)是嵌入式系统或计算机启动时运行的一段小型程序,负责初始化硬件、下载系统固件或加载系统固件(包含用户程序)并将其控制权移交。它是系统上电后执行的第一个软件。主要包含以下两部分内容:
第一阶段(汇编层)
初始化CPU、开关中断。
设置堆栈以及中断向量表指针,为C语言环境做准备。
复制自身到内存(如RAM),加速执行。
第二阶段(C语言层)
完成更复杂的硬件初始化(如串口、CAN口、网卡等)。
若识别到系统固件下载意图,则联合上位机完成系统固件下载更新。
若没有系统固件下载意图,则判断当前存储中的系统固件是否有效,有效则加载操作系统映像到内存,并启动系统固件。
汽车控制器的BootLoader相较于其他嵌入式硬件设备的实现,有以下特殊的要求:
需满足ISO 26262 ASIL等级要求:
诊断仪需要基于Seed-Key机制完成鉴权方可以进行对应安全等级操作。
下载的固件等内容需要进行CRC等校验和比较来进行完整性验证。
对下载的固件进行依赖性检查,防止不匹配固件刷写。
固件更新协议(汽车常用通信标准):
UDS(ISO 14229),CAN/CANFD诊断通信。
DoIP,以太网固件更新。
代码质量要求:BootLoader设计需符合AUTOSAR架构标准,并通过MISRA-C代码规范验证,确保汽车电子系统的功能安全与可靠性。
本篇文章介绍开发符合UDS诊断协议BootLoader,下面介绍必要的诊断服务。
应用服务
安全访问27服务
27服务它的核心目的是确保对车辆电子控制单元进行敏感操作(如刷写、参数修改)之前,必须通过一个安全验证过程,防止未授权的访问。诊断仪和ECU之间的交互如下所示:

27服务包含的常见子服务如下:
01/03...奇数:诊断仪请求对应安全等级对应种子。
02/04...偶数:ECU发送对应安全等级的密钥。
下图为ISO 14229-1对请求种子的对应协议格式说明。

下图为ISO 14229-1对发送密钥的对应协议格式说明。

下图为一个实际的诊断仪与ECU的鉴权过程。

可以看到,诊断仪发送请求种子报文(27 01),ECU接收到之后会回复一个带Seed数据的报文(67 01 00 95 00 06),此时ECU已经有此Seed计算出Key了,然后上位机收到了Seed之后,根据整车厂给出的算法,计算出Key,并通过发送密钥报文(27 02 05 D9 40 3C),ECU校验诊断仪发送的密钥与ECU自己计算出的密钥一致之后,则回复正相应(67 02),此时ECU即处于对应的安全访问等级。
刷写34/36/37服务
34/36/37三个服务联动,用以完成整个固件的刷写,我们首先来看34服务。
34服务用于向ECU请求启动数据下载流程,首先其传递此次下载的目标地址地址和数据长度以及传输模式(是否启动压缩或者加密算法,存储字节大小和存储地址长度),然后ECU回复一次36服务缓存数据的大小,为后边的36服务做准备。
下图为ISO 14229-1对34服务协议格式的定义。

下图为正响应的协议格式定义:

下图为一个实际34服务的交互:

可以看到,下载地址是0x010000(此处一般为一个基地址的偏移地址),下载长度是0x40460,从36服务的正响应可知道每次最多传输0x402(1026)个字节过来。
诊断仪发送完34服务并接收到正响应之后,就根据每次36服务最多传输的数据,将此次传输的所有数据分为一个个Block进行传输。
下图为ISO 14229-1对36服务协议格式的定义。

下图为一次完整传输过程中的36服务,其为36服务的不断发送,接收对应响应的过程,可以看到36服务多帧发完之后,ECU一般会回复一个0X78的NRC做一下挂起响应,提高ECU回复正响应时间(通常为500ms),因为此时ECU需要操作FLASH进行写入,然后再回复76 01这个正响应(注意Block的ID先从1到FF,然后FF过后再从0开始)。

37服务是退出此次传输,下图为ISO 14229-1对37服务协议格式的定义。

这块很简单,基本上就是诊断仪发送36,ECU回复76即可。
其他服务
刷写的过程中还涉及到了很多其他诊断服务,包括10服务用于切换扩展会话以及编程会话;28服务用于开启和禁用总线上其他ECU的业务报文;22服务读取软/硬件版本信息;2E服务写指纹信息;31服务用于启动检查编程预条件、擦除、完整性检查、依赖性检查历程;11服务重启;85服务开启/关闭DTC;14服务清除DTC。
刷写流程
车载ECU的BootLoader刷写流程主要分为以下三个阶段:
预编程阶段(Pre-Programming Phase):其为刷写操作准备环境,包括停用总线上ECU功能(如诊断通信外的其他服务),验证待刷写ECU当前状态(如电压、温度稳定性)是否可以刷写,以及将待刷写ECU进入BootLoader模式,等待主程序传输。
主编程阶段(Main-Programming Phase):完成新程序的传输与写入。
后编程阶段(Post-Programming Phase):复位ECU,退出BootLoader模式,并恢复总线上ECU功能。
下面我们分别详细的介绍一下这个三个阶段的内部的工作流程流程。
预编程阶段
预编程阶段的所有服务需要在主程序和Boot上被支持,其内部流程以及相关详述如下图所示。

主编程阶段
主编程阶段涉及的服务比较多了,我们分两段说明,我们先来看第一段。
第一段的第一个过程“进入编程模式”是在主程序实现的,别的均在Boot程序里实现。

写完指纹之后,就开始下载流程了,先下载Flash驱动(下载到RAM,所以在现在之前不用擦除),然后再擦除,再下载“应用程序”(这个应用程序可以是实际的主程序,也可以是标定区,也可以是某些出厂写入的DID),最后ECU复位,完成主编程阶段。

后编程阶段
后编程阶段的所有服务需要在主程序和Boot上被支持,其内部流程以及相关详述如下图所示。

BootLoader代码实现
下面介绍一种符合AUTOSAR规范的并且是基于UDS的BootLoader实现,下面是工程包含的目录以及目录包含的内容详述。
h:包含工程中英飞凌寄存器操作宏定义以及片内看门狗最底层操作接口。
ld:包含工程链接文件。
TC27x_Bootloader:文件夹包含生成的elf,hex以及map文件。
src:包含启动文件crt0-tc2x.S以及工程主函数。
Cfg:工程内包含模块的配置结构体。
CAN:驱动CAN模块配置。
Fls:片内Flash驱动配置。
MCU:芯片最小系统驱动配置。
Stm:系统定时器驱动配置。
Wdg:片内看门狗驱动配置。
CANIf:CanIf模块配置。
CANTp:CANTp模块配置。
DCM:DCM模块配置。
SchM:SchM模块配置。
FL:FL模块配置。
Driver:工程内的驱动模块实现,属于MCAL层内容。
CAN:片内CAN控制器驱动实现。
Fls:片内Flash驱动实现。
MCU:片内最小系统(晶振)等驱动实现。
Stm:片内系统定时器驱动实现。
Wdg:片内看门狗驱动实现。
Core:工程内的功能模块实现,属于BSW层内容。
Cal:CRC计算实现。
CANIf:CAN抽象层功能实现。
CANTp:CAN传输层功能实现。
DCM:诊断应用层功能实现。
FL:Flash操作功能实现。
SchM:27服务鉴权的相关接口实现。
Include:包含英飞凌底层寄存器定义等。
Appl:BootLoader的顶层任务。
下面我们来分别从不同模块的角度来介绍一下BootLoader的代码实现。
Appl_EcuStartup
下面是主函数:
/*=======[I N C L U D E S]====================================================*/#include "appl.h"
/*=======[F U N C T I O N I M P L E M E N T A T I O N S]====================*//******************************************************************************//** * @brief <main function> * * <task schedule and process all API. 10ms task for watch dog trigger> . * Service ID : <NONE> * Sync/Async : <Synchronous> * Reentrancy <Non Reentrant> * @param[in] <NONE> * @param[out] <NONE> * @param[in/out] <NONE> * @return <NONE> *//******************************************************************************/int main(void){ /* ECU Initialize */ (void)Appl_EcuStartup();
for ( ; ; ) { /* 10ms task and watch dog trigger */ Appl_UpdateTriggerCondition();
/* task for flash driver and checksum process */ FL_MainFunction(); }
return 1;}
/*=======[E N D O F F I L E]==============================================*/
可以看到,Appl_EcuStartup在程序中只进行了一次,它的主要工作是完成必要的初始化以及进入Boot之前的状态读取,下面是它的各部分组成以及完成的工作说明。

Appl_UpdateTriggerCondition
这个里边包含了boot主要处理的10ms任务实现。
/******************************************************************************//** * @brief <10ms task> * * <This routine shall be called by functions of the flash loader runtime * environment and the security module at least every 500 milliseconds.> . * Service ID : <NONE> * Sync/Async : <Synchronous> * Reentrancy <Non Reentrant> * @param[in] <NONE> * @param[out] <NONE> * @param[in/out] <NONE> * @return <NONE> *//******************************************************************************/void Appl_UpdateTriggerCondition(void){ /* 10ms time out flag */ boolean stmTime;#ifdef LED_DEBUG static uint8 led_cnt = 0;#endif Wdg_Kick();
if (1 == Fl_GetActiveJob()) { uint16 delayloop = 1000;
/* force sending 78 for every sector if it is doing erase job */ Dcm_ForcePending(); Dcm_MainFunction(); CanTp_MainFunction(); CanIf_MainFunction();
while (delayloop > 0) { delayloop--; } }
stmTime = STM_GetFlag(); /* check if tick time is overflow */ if (TRUE == stmTime) { CanTp_MainFunction(); Dcm_MainFunction();
#if (FL_SLEEP_TIMER > 0) Appl_EcuShutdownTimer();#endif
#if (FL_MODE_STAY_TIME > 0) Appl_BootStayTimer();#endif /* if state255 is received, reponse 0x28 */ if (2 == FunctService255) { PosResponse255(); /* keep in state255 */ FunctService255 = 3; }#ifdef LED_DEBUG led_cnt++; if (led_cnt >= 30) { led_cnt = 0; P10_OUT.B.P7 = ~(P10_OUT.B.P7); }#endif } else { /* empty */ }#if 0 if (0xFFFFFFF0 == SecM_Seed) { SecM_Seed = 0x0; } SecM_Seed++;#endif // add by 10086 2018.4.9
CanIf_MainFunction();
return;}
首先它判断了是否当前正在执行擦除任务,若在执行擦除任务,则调用Dcm_ForcePending,使Pending的发送不再归DCM负责,而由FL模块负责,并调用Dcm_MainFunction、CanTp_MainFunction、CanIf_MainFunction完成对应BSW模块的MainFunction调用,他们会根据需要调用对应驱动完成相应功能。下图详细的介绍了每个MainFunction包含的主要接口和对应完成的功能。

然后,就是正常情况下的10ms定时器超时,完成对应模块的MainFunction调用以及是否跳转APP的判断。

FL_MainFunction
最后,是仿照AUTOSAR风格写的操作片内Flash的BSW模块。
void FL_MainFunction(void){ switch (FldownloadStatus.activeJob) { case FL_JOB_ERASING: /* do the flash erase */ FldownloadStatus.errorCode = FL_Erasing(); break;
case FL_JOB_PROGRAMMING: /* do the flash program */ FldownloadStatus.errorCode = FL_Programming(); break;
case FL_JOB_CHECKING: /* do the flash checksum */ FldownloadStatus.errorCode = FL_CheckSuming(); break;
default: break; }
if (FldownloadStatus.errorCode != (uint8)FL_OK) { /* initialize the flash download state */ FL_InitState(); } else { /* empty */ }
FldownloadStatus.activeJob = FL_JOB_IDLE;
return;}
FL模块主要完成了对片内Flash的擦除,写入,以及计算下载内容完整性的功能。


评论区
登录后即可参与讨论
立即登录