|
0 1 简介 随着工业类和家电类产品对ClassB功能安全认证需求的不断增加,客户面临着不菲的认证费用。对于同一系列的产品,不同子系列之间仅在应用软件上存在部分差异,而功能安全相关的软件代码则完全相同。如果对每个子系列都单独进行认证,将带来巨大的时间和经济成本。 针对这一情况,客户通过与认证机构协商,达成了只需对功能安全部分代码进行认证即可覆盖所有子系列产品的方案。即使应用软件存在差异,也能共用同一份认证证书。 为了满足这一需求,软件设计上必须实现应用代码与功能安全代码的分区,确保应用代码的修改不会影响功能安全代码的一致性。 本文将详细说明应用代码与功能安全代码分区的实现要点以及调试方法,供有此需求的用户作为参考。 0 2 实现原理 本文以STM32Cube_FW_ClassB_V2.3.0为例,参考工程为STM32G081B_EVAL,编译器使用MDK-ARM,描述应用代码和功能安全代码分区的实现要点。读者需要先熟悉STM32Cube_FW_ClassB_V2.3.0自检库,才能更好的理解本文内容。 因为认证的目标文件是编译生成的二进制代码,所以确保待认证的二进制码不随应用程序的改变而发生改变是最终目标。 实现代码分区的基本思路是通过修改散装加载文件(Scatter文件),将工程代码划分为不同的分区。其中,存放功能安全代码的分区应保持独立,不应因其他分区代码的修改而发生变化。实际应用中,功能安全模块往往与其他模块存在耦合关系。例如,功能安全模块可能会调用应用程序的函数接口,或访问其他模块的全局变量。这种耦合既有显式的情况——比如直接调用其他模块的函数接口,也有隐式的情况——例如代码中的Switch语句被编译器转换为调用ARM库中的某些函数接口。 如图所示,功能安全代码stm32xx_STLmain.c编译生成的代码中既包含显式调用,也包含隐式调用。其中,蓝色标记部分对应于C源码中直接调用FailSafePOR函数的语句,而红色标记部分在C源码中并无对ARM_common_switch8函数调用的对应语句。
▲ 图1.显式调用与隐式调用 因为应用代码的修改必然会导致相关函数地址或全局变量(包括静态变量)地址的重新分配。以图中代码为例,FailSafePOR函数通常实现于应用代码中,应用代码的变动会引起该函数地址的重新分配,进而导致与BL FailSafePOR对应的二进制代码发生变化,无法保证功能安全模块二进制代码的一致性。 在编译选项保持一致的前提下,要确保功能安全模块编译生成的二进制代码保持不变,必须满足以下前提条件: 1)功能安全模块自身代码不变。 2)功能安全模块调用的其他模块的函数地址不变。 3)功能安全模块访问的全局变量(包含静态变量)地址不变。 基于上面条件,实际应用中就ROM需要划分为三个区才能满足要求,如下图所示,Section ClassB存放功能安全代码。 Section App存放应用代码,这部分不会被Section ClassB中的代码直接访问,所以用户可以任意修改Section App中的代码。 Section Wrapper存放的代码也属于应用代码部分,不过这部分的函数会被Section ClassB中的代码访问,所以需要确保这部分代码的函数地址空间是不变的。 那么为了保证Section ClassB访问到的全局变量(包括静态变量)地址不变,同样需要对RAM区划分为两个区,一个地址不可变区存放ClassB功能安全代码访问的全局变量,另外一个区存放地址可变的全局变量。 根据上面原则,下图给出了RAM以及ROM的分区示意图。
▲ 图2.分区示意图 0 3 参考案例 上面章节阐述了实现原理,本章节以STM32Cube_FW_ClassB_V2.3.0软件包中的STM32G081B_Eval参考工程为例,编译器使用ARM MDK 5.41.0版本,演示实现应用代码和功能安全代码分区的实现过程。 工程包含的源文件有三部分,ClassB功能安全代码(stm32g0xx_STL*),官方驱动代码(HAL驱动,BSP驱动,CMSIS接口),其他用户代码。
图3. 代码分类 通过修改Scatter文件,将代码划分为三个ROM区域:SECTION_CLASSB、SECTION_WRAPPER和SECTION_APP。
1)代码本身稳定不变,例如HAL库函数。ClassB功能安全代码可能会调用HAL库中的某些函数(如CRC、WDG、RCC等外设驱动),因此这些相关代码需要放在SECTION_WRAPPER区域。 2)代码会随着应用变化而修改,但又会被ClassB功能安全代码调用。针对这种情况,不能直接将这类代码放入SECTION_WRAPPER。用户需要对这类函数进行封装,封装后的函数放在SECTION_WRAPPER区域,ClassB功能安全代码通过调用封装函数来间接访问原始函数。 例如,时钟自检模块中的STL_InitClock_Xcross_Measurement函数,用户可能会在不同工程版本中对其进行修改。为保证功能安全代码的一致性,我们定义一个封装函数STL_InitClock_Xcross_Measurement_Wrapper,该函数内部调用原始的STL_InitClock_Xcross_Measurement。同时,将ClassB功能安全代码中对STL_InitClock_Xcross_Measurement的调用替换为对STL_InitClock_Xcross_Measurement_Wrapper的调用,并将封装函数的源文件放置于SECTION_WRAPPER区域。这样,即使用户修改了原始函数,也不会影响功能安全模块的一致性。
ROM区划分完成之后,需要确定ClassB功能安全代码用到了哪些全局变量,把这些全局变量放在固定地址的RW/ZI段。根据上面原则,最终我们得到的Scatter文件如下。
图4. Scatter 上面操作还是无法保证ClassB功能安全代码的一致性,这是由于编译过程产生的隐式调用,如图1所示的BL ARM_common_switch8语句,这个函数位于ARM运行库,链接过程中,这个地址也会随着应用程序的改变而发生改变,所以这个地址要么固定,要么通过编译选项避免这种语句。 比如stm32xx_STLmain.c代码中的STL_DoRunTimeChecks函数就会产生该条指令,我们可以针对该文件加--no_branch_tables编译选项避免产生该指令。 0 4 调试技巧 如果修改了Scatter文件之后,如果发现编译之后不同版本产生的SECTION_CLASSB段依然存在不一样,那么我们需要使用fromelf功能把对应的axf可知执行文件转为汇编代码,通过比较汇编代码,可以很直观看到产生问题的原因。 fromelf命令默认位于Keil安装目录的ARM\ARMCLANG\bin路径下面,转汇编代码的参数如下图所示。
图5.Fromelf命令参数 0 5 小结 参考工程通过__VERSION_UPDATE宏定义更改应用程序的代码,用户可以尝试打开或关闭该宏,编译两个版本的程序,比较实际生成的二进制程序,可以发现虽然应用程序变化了,SECTION_CLASSB部分程序的一致性还是完整的,这就满足了本文开头部分提到客户的实际需求。 客户实际的项目中应用程序的文件远比参考工程的多,用户需要根据实际项目需求,参考本文的分区方法,定制符合实际项目需求的分区方式。 详细文档查看: LAT1630 ClassB功能安全认证代码与应用代码进行分区的实现要点 |
【STM32U3 评测】人体行为识别
【STM32U3 评测】串口控制步进电机与LabVIEW数据采集
实战经验 | STM32CubeIDE实用技巧之如何指定变量、函数、文件到指定内存
【STM32U3 评测】步进电机驱动
【STM32U3 评测】MNIST 数字识别
【STM32U3 评测】介绍、环境搭建、工程测试
STM32Cube嵌入式软件更新
一步到位!STM32CubeIDE 搭建 ClassB 功能安全工程
扩展器件适配、优化开发流程——STM32CubeIDE 2.1.0新版本发布
留言赢礼 | 全新升级的STM32CubeIDE for Visual Studio Code
微信公众号
手机版