本页面包含有关如何自定义 RAMN 的信息,以及实施某些项目(例如添加自定义引导加载程序、添加消息认证等)的指导。如果您想了解默认固件的功能,请查看快速入门指南或用户指南。
摘要:
#define TARGET_ECUA、#define TARGET_ECUB、#define TARGET_ECUC 或 #define TARGET_ECUD)。ramn_config.h 保存当前配置,ramn_customize.c 为常用函数提供钩子(例如,在接收到 CAN 消息时调用的函数)。ramn_config.h 中)。RAMNV1.ioc),请务必小心,不要让它覆盖您自己的代码。RAMN 是一个基于 HAL 和 FreeRTOS、采用 STM32CubeIDE 开发的常规 STM32 项目,适用于 STM32L552 和 STM32L562 微控制器。如果您需要本页面未提供的信息,可以随时通过互联网搜索通用的 STM32、HAL 和 FreeRTOS 相关资源。
内容:
RAMN 固件是使用 GCC 工具链构建的。虽然您可以自由选择任何喜欢的 IDE,但建议您从意法半导体的网站上安装最新版本的 STM32CubeIDE。
您可以从 GitHub 仓库下载 RAMN 的源代码(点击“Code”然后“Download ZIP”)。源代码位于 firmware/RAMNV1 文件夹中。
安装 STM32CubeIDE 后,打开它,创建或打开一个工作区,然后依次选择“File” > “Import…” > “Existing Projects into Workspace”,接着选择 firmware/RAMNV1 所在的路径。点击“Next”,然后使用默认设置导入项目,点击“Finish”。
这样,RAMN 项目就会出现在您的工作区中。所有 ECU 的源代码是相同的:
TARGET_ECUA 标志位构建的。TARGET_ECUB 标志位构建的。TARGET_ECUC 标志位构建的。TARGET_ECUD 标志构建的。您可以通过修改 ramn_config.h,并将 #define TARGET_ECUA 替换为您所需的目标,来更改默认的目标 ECU。
当您修改源代码时,这些修改将应用于所有 ECU。要编写特定于单个 ECU 的代码,请在其周围使用 #ifdef 和 endif 预处理器指令。例如,要仅将修改应用于 ECU A:
或者,你也可以维护四份独立的源代码副本,使每个 ECU 拥有自己独立的源代码,从而降低意外修改的风险。
要为在 ramn_config.h 中定义的默认目标 ECU 构建固件,请选择“项目”>“构建项目”,或按下锤子图标。固件二进制文件(.elf、.bin、.hex)以及调试信息文件(.list、.map)将位于 RAMNV1/Debug 文件夹中。
实用快捷键:
在更改目标 ECU 时,STM32CubeIDE 可能需要一些时间来更新引用,并且在搜索项目时可能会遇到问题。为避免此问题,在更改目标 ECU 后,应选择 “项目” > “C/C++索引” > “重建”。
要一次性为所有 ECU 构建固件,可以使用 scripts/build 文件夹中的构建脚本(请确保首先关闭 STM32CubeIDE)。 您可能需要在构建脚本(.bat 或.sh 文件)中更新 STM32CUBEIDEPATH,以匹配您的安装路径 。
BUILD_Clean_Debug.bat 将为所有四个 ECU 构建固件,并将新的固件文件放置在 scripts/firmware 文件夹中。
默认 RAMN 已优化,以使用大部分可用内存。如果您的应用程序需要大量内存,可能会遇到以下情况:
calloc 返回 NULL。您可以通过减小 USB 和 CAN 缓冲区的大小,轻松为您的应用程序释放内存。请阅读 理解 RAMN 的安全特性部分,以了解如何为您的应用程序释放更多内存。
默认情况下,RAMN 已配置为支持内存保护。如果您的应用程序需要严格的内存保护(例如,您希望将 RAMN 用于“夺旗”比赛,参赛者不得转储内存),请阅读 理解 RAMN 的安全特性部分。
如果内存保护不是问题(例如,您将 RAMN 用作研究或教育平台),您可以将 STM32L552CETX_FLASH.ld 的内容替换为 STM32L552CETX_FLASH_INSECURE.ld (位于 RAMNV1 文件夹中)的内容,从而为您的应用程序释放更多内存。
有许多接口可用于刷写新的固件。其中最快且最易用的是 STM32 嵌入式引导加载程序接口(可通过 USB 使用 DFU 对 ECU A 进行重新编程,也可通过 ECU A 经由 CAN-FD 对 ECU B/C/D 进行重新编程)。
位于 scripts/STbootloader/windows 中的脚本 ProgramECU_A.bat 和 ProgramECU_BCD.bat 可用于刷新位于 scripts/firmware 文件夹中的固件.hex 文件(ECUA.hex、ECUB.hex、ECUC.hex 和 ECUD.hex)。
有关更多详细信息,请参阅刷写脚本。
要调试 RAMN ECU 的源代码,您需要购买一个外部 JTAG 调试器,并将其连接到您想要调试的 ECU。详细信息请参阅 JTAG 硬件接口部分(STM32CubeIDE 也可以为 ECU 刷写程序,因此您无需使用STM32CubeProgrammer)。
首先,请确保您已在 ramn_config.h 中定义了您想要调试的目标 ECU。将调试器连接到该 ECU,然后在 STM32CubeIDE 中点击绿色的虫子图标(或选择“运行”>“调试”)。如果这是您首次运行调试器,可能会出现提示窗口,此时应保持默认设置。
如果调试失败(或不可靠),请尝试删除 RAMNV1 Debug.cfg 和 RAMNV1 Debug.launch,以强制创建一个新的调试配置。然后,选择“运行”>“调试配置…”,切换到“调试器”选项卡,并尝试不同的设置(可尝试不同的“调试探针”设置,同时测试 SWD 和 JTAG)。此外,请确保在主选项卡中的 C/C++应用程序指向当前配置的.elf 文件(通常为 Debug/RAMNV1.elf)。您可使用“搜索项目…”按钮来定位正确的二进制文件。
如果可能,建议启用 RTOS 内核感知功能(启用“RTOS 代理”,驱动程序:“FreeRTOS”,端口:“ARM_CM33_NTZ”),以获取更多调试信息;但如果遇到调试问题,则应将其关闭。
STM32CubeIDE 在调试过程中会自动通过 JTAG 重新刷新连接的 ECU。然而,它无法刷新其他 ECU。如果您的修改会影响所有 ECU(例如更改 CAN 波特率),则需要使用刷写脚本,以确保所有 ECU 都能应用您的修改。
您可以通过编辑 Core/Inc 文件夹中的 ramn_config.h 来配置固件。
如前所述,该文件用于定义在 STM32CubeIDE 中用于调试或构建固件的目标 ECU。
请阅读 ramn_config.h 中的注释,并根据您的需求调整设置。关键配置选项包括:
ENABLE_USB_DEBUG:通过 USB 启用额外的调试输出(例如,人类可读的 CAN 错误)。LED_TEST_DURATION_MS:设置为 0 以在启动时跳过 ECU D 的 LED 测试。WATCHDOG_ENABLE:启用看门狗定时器,以便在主周期性任务崩溃时重置 RAMN。AUTO_RECOVER_BUSOFF:如果 ECU 进入总线关闭模式,则重置 CAN/CAN-FD 外设。HANG_ON_ERRORS:在某些非关键错误发生时强制进入无限循环(而不是忽略这些错误)。您可能还需要调整超时值,例如 ISOTP_TX_TIMEOUT_MS 或 UDS_SESSION_TIMEOUT_MS,以匹配实际条件或使 ECU 更易于交互。例如,UDS_SESSION_TIMEOUT_MS 被设置为 5000,这会强制 ECU 在扩展诊断会话期间超过 5 秒未收到请求时恢复到 UDS 默认会话。如果您增大此值,用户将更容易进行 UDS 实验,但这将不再代表真实 ECU 的行为(真实 ECU 需要定期发送“测试器存在”请求)。
RAMN 通常使用两种类型的 CAN 报文:“命令”和“控制”。命令报文由外部 ECU/计算机发送,用于请求 ECU 将其执行器设置为特定值(例如,CARLA 可利用此方式请求 ECU C 加速)。控制报文则报告所应用控制的实际值(例如,ECU C 实际施加的油门位置)。
例如,如果 CARLA 希望车辆施加 100%的油门值,它可以使用油门命令报文向 ECU C 发送请求。ECU C 可能会根据该报文决定施加 100%的油门。然而,如果 ECU C 检测到当前车速超过某一阈值或制动踏板被踩下,它可以选择忽略该命令,转而施加 0%的油门。这种方法可用于实现各种闭环控制算法(例如,实现 PID 和 bang-bang 控制器)。
您可以修改 ramn_vehicle_specific.h 以更新 RAMN 的 CAN 流量的基本属性。例如,如果您希望使用 ID 0x25 而不是 0x24 来表示制动消息,请将 CAN_SIM_CONTROL_BRAKE_CANID 从 0x24 更改为 0x25。
您可以通过修改 ramn_vehicle_specific.h 从 CAN 升级到 CAN-FD:
CAN_MAX_PAYLOAD_SIZE_DEFAULT 更改为 64(以启用 64 字节的有效载荷)。CAN_SIM_FORMAT_DEFAULT 更改为 FDCAN_FD_CAN。CAN_SIM_BRS_DEFAULT 更改为 FDCAN_BRS_ON(如果您希望启用比特率切换)。更改 CAN_MAX_PAYLOAD_SIZE_DEFAULT 可能会在编译时导致 RAM 溢出错误。请阅读了解 RAMN 的内存部分,以了解如何为您的应用程序释放更多内存。
当您将默认流量更新为 CAN-FD 时,将失去与大多数 slcan 工具的兼容性。建议使用外部 CAN-FD 适配器。在 Linux 上,您可以使用 scripts/vcand 中的脚本,从 RAMN 的 slcan 接口生成一个虚拟的 CAN-FD 接口。
ramn_dbc.c 是一个用于维护传入 CAN/CAN-FD 消息最新值数据库的模块。这确保了所有 ECU 都能访问所有的 RAMN 控制,即使某个控制属于其他 ECU。例如,如果你想从 ECU A 获取 ECU C 操纵杆的状态,只需读取 RAMN_DBC_Handle.joystick 的值即可。
RAMN_ACTUATORS_ApplyControls 位于 ramn_actuators.c 中,负责确定要设置到传出周期性 CAN/CAN-FD 消息中的有效载荷。RAMN_DBC_FormatDefaultPeriodicMessage 位于 ramn_dbc.c 中,通过添加计数器和 CRC32 校验和来格式化消息。RAMN_DBC_Send 实际用于发送 CAN 消息。RAMN_DBC_ProcessCANMessage 在 RAMN_DBC 模块中解析并记录传入的 CAN 消息。如果希望某个 ECU 停止发送消息,只需注释掉对 RAMN_DBC_Send 的调用即可。有关自定义示例,请参阅实现安全的 CAN 总线(SecOC 等)。
ECU A 还使用 RAMN_DBC_ProcessUSBBuffer 将从 CARLA 接收到的 USB 数据转换为 CAN 消息。
如果您希望在流量规范中添加新的 CAN 消息(而不仅仅是修改现有消息),则必须确保新 CAN 标识符未被过滤。您可以通过以下方式实现:
ramn_config.h 中注释掉 USE_HARDWARE_CAN_FILTERS。这将禁用硬件滤波器,从而略微增加 CPU 负载。ramn_canfd.c 中的 recvStdCANIDList 和 recvExtCANIDList 中分别添加您的新标识符,以分别处理标准和扩展标识符。如果您希望修改默认的波特率,应按照修改 .ioc 文件一节中的说明,在 RAMNV1.ioc 文件中修改 CAN/CAN-FD 外设设置。如果您不熟悉位定时,可参考位定时一节。此外,您也可以通过修改 ramn_canfd.c 中的 FDCAN_Config,在初始化外设之前调用 RAMN_FDCAN_UpdateBaudrate,并传入您所需的新的波特率,从而覆盖默认的标称波特率。
如果您希望自定义 UDS 接口(无论是修改现有服务还是添加新服务),则需要更新 ramn_uds.c 文件。您应根据需要修改用于物理寻址的 RAMN_UDS_ProcessDiagPayload 和/或用于功能寻址的 RAMN_UDS_ProcessDiagPayloadFunctional 。请注意,根据标准规范,功能寻址仅适用于单帧消息。
如果需要在发送响应后执行您的代码(例如,因为 ECU 将复位或更改波特率,需先进行响应),请在处理函数中发送一个肯定响应,并在 RAMN_UDS_PerformPostAnswerActions 中执行实际操作。
ramn_customize.c 是一个模块,旨在更轻松地向 RAMN 添加自定义内容。如果您希望在不改变 RAMN 默认行为的前提下对其进行扩展,应使用此模块。在 ramn_customize.c 中提供了多种函数,允许您在不同任务中添加自己的代码。例如:
RAMN_CUSTOM_Update 由 RAMN 的主周期性任务每 10 毫秒调用一次(默认设置)。它与处理其他周期性操作的任务相同,例如发送 CAN 消息、更新屏幕或 LED。RAMN_CUSTOM_CustomTaskX 函数由未使用的任务调用,与主周期性任务并行执行。它们可用于在主周期性任务的同时并行执行某些操作。RAMN_CUSTOM_ProcessRxCANMessage 由 RAMN 的 CAN 接收任务在接收到新 CAN 消息时调用。RAMN_CUSTOM_ProcessCDCLine 由 RAMN 的 USB 接收任务在通过 USB 串行 (CDC) 接收到新行时调用。RAMN_CUSTOM_TIM6ISR 由一个周期性定时器调用(默认情况下,每秒一次)。这可用于执行某些需要精确计时的操作,且与 FreeRTOS 无关。RAMN_CUSTOM_ReceiveUART 在接收到 UART 命令行时被调用。RAMN_CUSTOM_ReceiveI2C 和 RAMN_CUSTOM_PrepareTransmitDataI2C 在接收到 I2C RX 或 TX 命令时被调用(RAMN ECU 处于 设备模式)。您还可以使用 TIM16 来访问一个高精度的自由运行定时器,该定时器未被其他模块使用(参见 ramn_customize.c 中的注释)。您可以修改 TIM6 和 TIM16,而不会影响 RAMN 的功能。
阅读 ramn_customize.c 以获取示例,例如如何发送 CAN 消息。
SPI 函数(用于更新 ECU A 的屏幕)只能从同一任务中调用 ,默认情况下,该任务是调用 RAMN_CUSTOM_Update 的主周期性任务。这是因为该任务会等待 SPI 模块发出传输完成的通知后再继续执行,但如果从其他任务调用,则该任务将无法收到通知。
如果希望从其他任务使用 SPI,需要更新对 RAMN_SPI_Init 或 RAMN_SCREENMANAGER_Init 的调用,使 SPI 模块改为向你的任务发送通知。
为了让 ECU B 每秒发送一条标准 ID 为 0x123、负载为八个 0x77 的经典 CAN 报文,您可以在 RAMN_CUSTOM_Update 中,在 “此处代码每 1 秒执行一次” 注释之后添加以下代码:
为了让 ECU C 在接收到上述 CAN 报文时执行某些操作,您可以在 RAMN_CUSTOM_ProcessRxCANMessage 中添加以下代码:
独立地,您必须确保 ECU C 不会过滤 CAN 消息。请在 ramn_config.h 中取消定义 USE_HARDWARE_CAN_FILTERS,或者将该标识符添加到 ramn_canfd.c 中的 recvStdCANIDList:
您可以使用以下方式读取当前 RAMN 控件的值:
ramn_sensors.h 中的变量。ramn_dbc.h 中的变量。例如,如果您希望与换挡操纵杆物理连接的 ECU C 仅在操纵杆释放时执行代码,您可以添加以下条件:
如果希望将相同的条件应用于其他 ECU(ECU A、B 或 D),您可以添加以下条件:
ramn_screen_manager.c 是一个用于处理 ECU A 显示的模块,允许用户通过按下操纵杆上的左右键在不同屏幕之间切换。
如果需要修改可用屏幕以及启动后加载的默认屏幕,请修改 screens 和 DEFAULT_SCREEN 在 ramn_screen_manager.c 和 ramn_screen_manager.h 中的内容。
如果您想添加一个新的自定义屏幕,需要在代码中创建一个包含函数指针(例如:Init、DeInit、Update 等)的 RAMNScreen_t 结构。建议您创建一个新的模块(.c 和 .h 文件),并模仿 ramn_screen_saver.c 和 ramn_screen_saver.h 的内容,这两个文件展示了一个能够读取输入并更新屏幕的简单屏幕示例。只需复制粘贴这些文件的内容,并将其中的“screensaver”和“screen_saver”字符串替换为您新屏幕的名称。然后,修改这些文件以实现您期望的行为,并将您的结构添加到 screens 数组中,该数组位于 ramn_screen_manager.c 文件中。
如果要显示图像,可以使用 misc 文件夹中的 image_to_C.py 脚本,将图像文件转换为可添加到.c 文件中的源代码(RGB565 数组)。然后,使用 RAMN_SPI_DrawImage 函数并传入您的图像以进行显示(建议在仅调用一次的 Init 函数中执行,而不是在周期性调用的 Update 函数中执行)。ECU A 的显示屏尺寸为 240x240。“内部屏幕”的尺寸为 236x195(起始偏移量为 x=2,y=2)。
您可以使用 RAMN_SPI_SetScroll 或 RAMN_SPI_ScrollUp 来滚动显示屏(包括图像)。根据您希望滚动的显示区域大小,您可能需要在 Init 阶段调用 RAMN_SCREENUTILS_PrepareScrollScreen() ,并在 DeInit 阶段调用 RAMN_SPI_SetScroll(SCREEN_HEADER_SIZE) 。尽管显示屏的实际显示区域为 240x240,但其显示缓冲区大小为 240x320。这意味着,如果您希望滚动屏幕,需要绘制高度为 320 的屏幕画面(但每次仅显示其中的 240 行)。
您可以使用 ramn_eeprom.c 在模拟的 EEPROM 中保存数据(使用 STM32 的闪存)。该模块允许对 16 位索引(“地址”)读写 32 位数值。由于其中一些索引用于存储 DTC 和 VIN,因此您应使用高于 DTC_LAST_VALID_ADDRESS 的索引(或者通过搜索对 RAMN_EEPROM_Write32 的引用来禁用使用这些索引的功能)。
EEPROM 模拟层可能与 FreeRTOS 存在兼容性问题,因此在使用时务必始终检查错误,并在各种条件下进行测试。如果您在使用 ramn_eeprom.c 模块时频繁遇到错误,请随时与我们联系,以便我们进行调查。
如果需要的内存超过 EEPROM 模拟所能提供的容量,您可以使用 stm32l5xx_hal_flash.h 中的函数(文件开头的注释说明了如何使用)。请注意,在从闪存执行代码时,对其写入存在显著的限制。
RAMNV1.ioc 是 STM32CubeIDE 代码生成的配置文件。它定义了微控制器的引脚配置、中断、外设(CAN/CAN-FD、SPI 等)以及 FreeRTOS 设置。
您可以在 STM32CubeIDE 中编辑 RAMNV1.ioc,该工具提供了一个图形化界面,用于修改设置(例如,添加新的 GPIO 引脚或调整外设的波特率)。尽管也可以直接在源代码中修改这些设置(例如,通过编辑 main.c 中的 hlpuart1.Init.baudrate = 115200; ),但 STM32CubeIDE 能够自动检测无效配置,从而节省调试时间。
在项目资源管理器中双击 RAMNV1.ioc,即可打开默认的“引脚分配与配置”选项卡。如果保存时未自动提示,可选择“项目”>“生成代码”,以根据您的更改重新生成代码。
大部分设置均可在 “引脚分配与配置” 选项卡中进行修改。
您可以在 “连接” 类别中修改外设设置。例如,选择 “LPUART1” 以修改默认 UART 波特率。选择 “FDCAN1” 以修改默认的 CAN/CAN-FD 设置。
STM32CubeIDE 在添加新外设时不会自动启用所需的中断。请务必检查 NVIC 部分是否已启用必要的中断。

FreeRTOS 设置位于 “中间件与软件包” > “FreeRTOS” 菜单(“配置参数”)中。在此,您可以特别修改“最小堆栈大小”,以防止堆栈溢出问题;如果需要更多 FreeRTOS 堆内存,则可修改 TOTAL_HEAP_SIZE。请注意,这些设置与 链接器设置中描述的主堆栈和堆大小不同。如果您不确定应修改哪一项,可尝试同时修改两者。
在“配置参数”菜单中,您还可以更改“内存管理方案”,默认为“heap_4”。其他方案可能不支持 free() 函数,因此建议您不要更改此设置。
如果您不需要 FreeRTOS 运行时统计信息,也可以禁用“GENERATE_RUN_TIME_STATS”、“USE_TRACE_FACILITY”和“USE_STATS_FORMATTING”,以优化您的项目。如果这样做,还应在 ramn_config.h 中禁用 GENERATE_RUNTIME_STATS。
打开 “任务与队列” 选项卡,以修改/添加/删除 FreeRTOS 任务。双击某个任务可修改其设置(其中最重要的设置是优先级和堆栈大小 )。请注意,如果您重命名某个任务,STM32CubeIDE 实际上会删除该任务内的代码并生成一个新任务,因此您应先复制其内容,然后在代码生成后将其粘贴到新任务中。
如果您希望使用内部时钟而非外部晶振,请阅读 ramn_config.h 文件底部的注释。
要修改 CPU 时钟(SYSCLK),请选择顶部菜单中的 “时钟配置” 选项卡,并修改 PLLCLK 的 N 和 R 参数。默认情况下,RAMN 仅使用 80MHz,但您可以将其提升至 110MHz。在修改此时钟后,请务必同时调整 Q 值,以确保 PLLQ 仍保持 40MHz。
由于定时器依赖于 SYSCLK,如果您使用了 TIM6 和 TIM16,也需要相应地修改其设置(默认 RAMN 并不需要这些定时器;它们仅为方便您而预先配置)。
要增加主堆栈和主堆大小(这与 FreeRTOS 设置中描述的 FreeRTOS 堆和堆栈大小不同),请选择顶部菜单中的 “项目管理器” 选项卡,并更新“最小堆大小”和“最小堆栈大小”。
对于其他设置,您需要直接修改 STM32L552CETX_FLASH.ld。
您可以使用 “工具” 选项卡来使用其他 STM32CubeIDE 工具,例如将您的项目与另一个项目进行比较,或者在您进行更改后概览微控制器的功耗情况。
STM32CubeIDE 在您使用代码生成功能时可能会删除代码。如果您对自动生成的文件(主要是 main.c 和 main.h)进行修改,请务必将其放置在 USER CODE BEGIN ... 和 USER CODE END ... 注释之间,否则这些修改将被删除。建议您在使用代码生成功能时使用版本控制系统并检查差异,以确保您的代码不会被意外删除。
您可以使用 FreeRTOS 和 STM32CubeIDE 工具来优化您的应用程序。
您可以通过依次选择 “项目” > “属性”,然后 “C/C++ 构建” > “设置”,再选择 “MCU GCC 编译器” > “优化”,来启用编译器优化。在那里,您可以选择一个优化级别以启用优化,并根据需求选择优先考虑速度或大小。
在 ECU A 上,您可以使用 slcan ‘X’命令(参见与 USB 交互)来显示 FreeRTOS 运行时统计信息。您也可以在调试时通过 STM32CubeIDE 查看相同的信息(选择 “窗口” > “显示视图” > “FreeRTOS” > “任务列表”):

您可以查看每个任务的 CPU 使用率和可用堆栈的下限 。 统计信息从启动时开始计算,这意味着“使用率”显示的是自启动以来的平均使用情况,而非峰值使用率。 如果您想观察高负载下的统计信息,请重启并立即启动高负载处理任务。
如果 CPU 使用率较高(例如由于复杂的软件算法),您可能需要提高 CPU 时钟速度(参见时钟设置),或者对代码进行重构。
“堆栈”显示任务溢出前剩余的内存量。如果该值接近零,您需要增加任务的堆栈大小(参见 FreeRTOS 设置)。
您可以使用 “窗口” > “显示视图” > “静态堆栈分析器” 来更好地了解堆栈的使用情况。 (您可能需要先选择“文件” > “刷新”,然后在堆栈分析器窗口中单击刷新图标,才能看到正确的数值。) 请注意,某些视图以字(32 位)为单位显示大小,而另一些视图则以字节(8 位)为单位显示大小。

如果您使用了代码生成功能,可能需要在 STM32CubeIDE 中为 ulTotalRunTime(位于 tasks.c 的第 396 行)添加 volatile,以确保统计信息能够正确读取。如果在 STM32CubeIDE 中遇到运行时统计问题,请按照此处的步骤操作。
FreeRTOS 统计信息是通过 TIM7 计算的。如果您需要更高的精度,可以修改 TIM7 的计数器周期值(例如,从 7999 改为 799),但这会增加 CPU 负载。
当您完成应用程序优化后,可以禁用运行时统计信息(请参阅 FreeRTOS 设置)。
RAMNV1.ioc 是为 STM32L552 微控制器创建的。如果您拥有 STM32L562 微控制器并希望使用其加密硬件外设,则必须手动将“STM32L552”的引用更新为“STM32L562”。
请阅读 内存布局,尤其是当您需要内存保护时。
如果内存不足且不需要内存保护,请尝试将 STM32L552CETX_FLASH.ld 的内容替换为 STM32L552CETX_FLASH_INSECURE.ld 的内容。同时,也请尝试启用 编译器优化。
如果在 INSECURE_RAM 区域中出现内存不足的情况,请尝试在 ramn_config.h 中降低以下定义的值(某些定义在 ECU A 和 ECU B/C/D 中可能有所不同):
USB_RX_BUFFER_SIZEUSB_TX_BUFFER_SIZECAN_RX_BUFFER_SIZECAN_TX_BUFFER_SIZEUSB_COMMAND_BUFFER_SIZE如果您在 RAM 区域中耗尽了内存,应尝试减小堆和栈的大小,如修改 .ioc 文件一节所述。如果您不确定该减小哪个大小,可先从“最小堆大小”开始(在链接器设置中)。
如果您使用默认的 STM32L552CETX_FLASH.ld 链接器脚本,可通过在其定义中添加 __attribute__ ((section (".buffers"))) ,将变量从 RAM 移动到 INSECURE_RAM。如果存在一个较大的变量,您认为其无需保护(例如,非关键的 FreeRTOS 任务栈),可以将其移至 INSECURE_RAM,并将释放的空间用于您自己的应用程序。
如果您想了解如何修改布局以实现内存漏洞,请参阅实现漏洞一节。
您可以在 ramn_config.h 中使用 HARDENING 标志,以禁用那些容易危及设备安全的功能。启用此标志后,您将遇到各种编译错误,提示您还应启用或禁用哪些其他标志。请根据提示建议进行处理,或者删除 #error 指令。
如果您不需要某些剩余功能,可通过直接编辑源代码将其移除。值得注意的是,我们建议您检查可用的 UDS 服务,并编辑 RAMN_UDS_ProcessDiagPayload 和 RAMN_UDS_ProcessDiagPayloadFunctional 。请务必重新构建索引,以确保 STM32CubeIDE 能够正确高亮显示哪些函数仍然可用(选择 “项目” > “C/C++索引” > “重新构建”)。
您可以在 ramn_config.h 中使用 MEMORY_AUTOLOCK 标志来保护内存。当启用此标志时,STM32 RDP 选项字节将在启动期间设置为临时启用内存保护。 在您移除保护之前,将无法再对固件进行调试,因此应在完成调试后再执行此操作。
为确保内存保护已启用,您需要确认固件至少已被执行过一次(具体是否满足此条件取决于您用于编程固件的工具)。因此,建议您移除任何 JTAG 调试器,并对 RAMN 进行一次断电重启。
使用默认保护机制(RDP 级别 1), 内存保护可随时解除,但内存会自动擦除 。您可以通过更新 ramn_config.h 中的 RDP_OPTIONBYTE 来启用 RDP 级别 2,从而对设备进行永久锁定 ,但相应地您将失去重新刷写和调试的能力。
要解除 ECU A 的保护,您可以使用 ‘D’ slcan 命令。默认情况下,此命令需要一个由 DFU_COMMAND_STRING 定义的“密码”。然而,该“密码”仅用于防止意外的内存擦除(例如由于模糊测试);通过 JTAG 仍然可以无需任何密码直接解除保护。
ECU A 的保护会由 ECUA_OptionBytes_Reset.bat 和 ProgramECU_A.bat 自动解除。如果您更改了密码,则需要相应地更新 ECUA_goToDFU.py 脚本。对于 ECU B/C/D,您可以使用 Unlock_BCD.bat 脚本来解除内存保护。
此外,您也可以通过 STM32 引导加载程序接口来启用和禁用内存保护。STM32 引导加载程序能够独立地启用或禁用读写保护。对于 ECU A,您可以使用意法半导体提供的 DFU 工具(或 STM32CubeProgrammer)。对于 ECU B/C/D,您可以使用位于 scripts/STBootloader 文件夹中的 canboot.py Python 脚本:
如果您直接使用 STM32 引导加载程序接口,请务必确保内存保护设置一致,否则可能会遇到问题(请参阅内存保护不一致)。
请阅读内存布局,以了解如何通过 MEMORY_AUTOLOCK 标志对内存进行(或无法进行)保护。请注意,SRAM1(INSECURE_RAM 区域)无法被保护,除非您永久锁定设备(请参阅 RAM)。
当 RDP 级别 1 内存保护处于激活状态时,预期的 JTAG 行为如下:
如果您需要为密钥派生函数提供种子,以生成每个 ECU 的唯一密钥,可以使用位于 HARDWARE_UNIQUE_ID_ADDRESS 处的 8 个字节。
STM32L5 微控制器配备了 MPU,如果您需要启用内存保护功能,可以使用它。
尽管这些功能默认处于禁用状态,但您仍可为您的应用程序启用 TrustZone 功能。有关 STM32L552 安全功能的更多信息,请参阅 STM32L552 数据手册。
如果您的 RAMN 配备了 STM32L562 微控制器,则还可使用加密引擎(例如用于 AES 和公钥操作)。有关详细信息,请参阅 STM32L562 数据手册。
如果您希望在 Ghidra 中分析 RAMN 固件,应使用构建固件时在 RAMNV1/Debug 或 RAMNV1/Release 文件夹中生成的 .elf 文件。RAMN 所使用的 STM32L552/STM32L562 微控制器基于 ARM Cortex M-33 内核,该内核仅支持 Thumb 指令。
请注意,更改编译器优化设置通常会导致生成非常不同的二进制代码。
与.hex 文件不同,.elf 文件包含调试符号,这大大简化了分析过程。如果需要移除调试符号,必须使用 arm 工具链,而不是系统中可能已有的默认工具链。例如,应使用 arm-none-eabi-strip,而不能仅使用 strip。
当您在 Ghidra 中加载.hex 文件时,可能会因为缺少内存映射信息而导致分析困难。固件的默认起始地址(Flash)为 0x08000000。您应打开“ 工具 ”>“ 内存映射 ”,并按照 STM32L552CETX_FLASH.ld 中定义的内容添加区域。此外,您还可以根据 STM32L5 参考手册添加特殊寄存器(例如外设)的相关信息。请搜索“ 内存映射和寄存器边界地址 ”以及“ 外设寄存器边界 ”。您也可以找到在线资源来帮助您实现这一过程的自动化。
您可以使用常见的 Shellcode 编写工具,编写可在 RAMN 上执行的 ARM Shellcode(二进制代码)。您必须将目标选择为“ARM (thumb)”(适用于 ARM Cortex M-33 的 16 位指令)。默认情况下未启用 TrustZone,也无需进行权限提升。默认情况下已启用 RAM 执行功能,以便用户能够轻松测试 Shellcode。
您可以使用 UDS 例程 0x209(参见例程控制 (0x31))通过 CAN 测试有效载荷。诊断任务将跳转到您的有效载荷地址。此例程要求您首先执行安全访问 (0x27) 中所述的简单安全访问方法,以防止在模糊测试过程中发生意外执行。由于 UDS 数据直接复制到内存中,因此您必须以小端格式提供指令(例如,在您的 CAN 有效载荷中,nop 应显示为 00BF,而不是 BF00)。
该函数不会自动返回,因此您应保存上下文并自行安全地返回,例如在末尾执行 bx lr(7047)。只有当您的代码成功返回时,UDS 服务才会作出响应(响应是在有效载荷执行之后,而非之前)。
例如,有效载荷 00BF7047 将执行一条 NOP 指令并返回。
为 RAMN 编写 ARM shellcode 并不特别困难,但请注意,网络上的大多数资源都是针对 Linux 嵌入式系统,而非 FreeRTOS 嵌入式系统。虽然大部分技术仍然适用,但在 RAMN 上无法通过系统调用“弹出一个 shell”,因为根本不存在 shell。同样地,需要注意的是,大多数任务会处于休眠状态,等待通知后才继续执行。如果你利用某个任务并调用一个函数,而该函数却在等待本应属于另一个任务的通知,那么它将永远挂起,除非你先覆盖该通知处理程序,或者找到其他方式来触发通知。类似地,某个任务可能不会检查资源是否可用,因为它假定自己是唯一使用该资源的任务,因此你可能会无意中影响到其他任务。
你可以将 OpenOCD 调试器连接到 RAMN 的 ECU。有关连接方式,请参阅 JTAG 硬件接口。
你应该使用 stm32l5x.cfg 配置文件(在 Linux 系统中,该文件通常位于 /usr/share/openocd 下的某个位置)。
启动一个 openocd 服务器,并通过以下方式连接到它:
然后,您可以执行调试命令,例如:
您可以按照本页面的说明来创建 CTF 挑战。例如,您可以根据 CAN 流量的简单修改(标识符、周期等) 一节中的说明,修改 CAN 标识符,并让参与者猜测您新定义的标识符。您可以在 CTF 解题报告一节中找到简单和高级 CTF 挑战的示例。
如果您想将 RAMN 用于CTF或类似活动,需要确保用户无法轻易读取固件或执行任意代码。 如果您的 CTF 面向初学者,可以跳过本节中的说明 。
为确保用户无法轻易转储固件,您应修改 ramn_config.h,以实现以下目标:
HARDENING,以移除潜在的危险功能(如用于读写内存的 UDS 服务等)。MEMORY_AUTOLOCK,以便在启动时自动启用 STM32 内存保护(RDP),从而防止通过 JTAG 或引导加载程序模式进行内存转储。此外,建议开启编译器优化,以移除可能残留在内存中的未使用代码。当定义了 MEMORY_AUTOLOCK 标志时,您将无法调试固件,因此应在开发完成后才启用该选项。
请阅读理解 RAMN 的安全特性部分,以了解更多关于内存保护的信息。请记住,MEMORY_AUTOLOCK 仅会保护 Flash 和 SRAM2(“RAM”)区域的内存。SRAM1(“INSECURE_RAM”)仍可通过调试接口进行读取。因此,您应确保没有敏感数据(标志等)存储在 SRAM1 中。默认情况下,源代码不会将任何内容放入 SRAM1,除非您主动在那里声明变量(参见 RAM)。RAMN 仅将 SRAM1 用于通信缓冲区(USB、SPI、CAN 等),因为这些缓冲区无论如何都会始终暴露在外。
默认情况下,RAM 是可执行的。如果您禁止 RAM 代码执行,将无法通过 USB 移除内存保护(使用 slcan ‘D’命令),并且只能通过 JTAG 移除 ECU A 的保护。
由于 ECU A 的功能最为复杂,因此最有可能存在未预料到的漏洞,这些漏洞最终可能比你实际设置的挑战更容易被利用。如果你希望为高级用户设计一场 CTF 比赛,建议将最具难度的挑战部署在 ECU B/C/D 上。
在启用 HARDENING 时,许多 USB 和 UDS 服务仍处于活动状态,这可能会让参与者误以为这些服务是需要分析的目标。您应明确告知参与者哪些服务不在本次范围之内(以避免他们浪费时间)。
完成 CTF 后,如果您启用了 MEMORY_AUTOLOCK,可以通过执行 scripts/STBootloader/windows 文件夹中的 Unlock_BCD.bat 和 ECUA_OptionBytes_Reset.bat 来移除内存保护。 ECU A 需要使用有效的固件进行编程才能执行 Unlock_BCD.bat,因此应最后擦除 ECU A。 然后,您可以执行 ProgramECU_A.bat 和 ProgramECU_BCD.bat(来自原始 Github 仓库)以恢复原始固件。
要在 CAN(或 CAN-FD)上实现您自己的消息认证或加密机制,请编辑以下 ramn_dbc.c 函数:
RAMN_DBC_FormatDefaultPeriodicMessage 以实现您的机制(例如,加密有效载荷、计算 MAC 而非 CRC32 等)。RAMN_DBC_ProcessCANMessage 以实现相关消息处理(例如,解密有效载荷、验证 MAC 等)。如果您希望使用 CAN-FD 而非 CAN,请阅读升级到 CAN-FD 部分。要深入了解 ramn_dbc.c 模块,请阅读 CAN 流量的高级修改(有效载荷格式等) 部分。
要仅修改特定的 CAN/CAN-FD 报文(例如制动控制报文),而不是所有报文,请将 RAMN_DBC_Send 更新为调用您的函数,而非 RAMN_DBC_FormatDefaultPeriodicMessage 。例如:
对于加密操作,您可以使用众多嵌入式软件加密库之一(例如:Tiny AES 或 CMOX)。如果您的 RAMN 配备了 STM32L562 微控制器,您还可以访问用于私钥和公钥操作的硬件加密引擎。
在为所有 ECU 构建并刷写固件后,RAMN 应该能够照常运行,但会采用您更新后的流量规范。
参考:
首先,请阅读内存布局部分,以了解默认的内存布局。
您应假设在从内部闪存执行代码时,无法向其写入数据。您可以选择以下任一方式:
如果您想使用双存储区模式实现内部存储器的重新刷写,请务必确保您使用的微控制器具有 512KB 内存 (STM32 微控制器参考型号以 CET6 结尾)。否则,您的微控制器仅配备一个 256kB 的闪存存储区。
您可以通过使用 __attribute__((__section__(".RamFunc"))) 将函数放入 RAM 中。有关此类示例,请参阅 ramn_memory.c。如果您希望对闪存执行写操作,很可能还需要使用 __disable_irq(); 来禁用中断,因为默认情况下,中断服务例程位于闪存中。
UDS 重新编程依赖于双 bank 模式,它可能会切换 bank 并覆盖内存的任何区域。因此,您可能需要禁用 UDS 重新编程(在 ramn_config.h 中取消定义 ENABLE_UDS_REPROGRAMMING),或者更新 ramn_uds.c 中的 UDS 重新编程接口,以使其与您的引导程序兼容。
RAMN 没有自定义引导程序,而是直接运行您刷入的固件。然而,STM32L5 微控制器已经内置了一个硬件引导程序。该引导程序通常仅在启动时 BOOT0 引脚为高电平时才会执行。
该 STM32 引导加载程序位于只读存储器中,无法进行修改。虽然您可以通过修改 STM32 引导加载程序的地址(NSBOOTADD1 选项字节,默认值为 0x0BF90000)来完全替换它,但并不推荐这样做,因为这将导致您无法再使用 scripts/STBootloader 文件夹中的脚本。同样地,您也可以通过修改 STM32 的 NSBOOTADD0 选项字节(默认值为 0x08000000)来更改应用程序启动地址(当 BOOT0 为低电平时启动)。
如果你的目标是为研究或教育目的实现一个概念验证(例如 OTA、安全启动等),我们建议你:
您可以通过修改 STM32L552CETX_FLASH.ld 中 FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 248K 行来调整应用程序所使用的内存布局。如果您修改了项目的起始地址,还需确保中断向量表指向新的起始地址。这需要通过修改 main.c 中 main() 的最开始几行来实现:
这些代码行用于确保即使前一阶段(之前的引导加载程序)修改了中断向量表,代码仍能正确启动。 如果您为引导程序创建一个新的 STM32CubeIDE 项目,还需要添加以下几行代码,以将中断表向量指向您的引导程序起始地址(即使该地址默认为 0x08000000)。这是因为 STM32CubeIDE 模板项目不会重置中断表向量,但该向量可能已被 STM32 嵌入式引导程序修改。
一旦您完成引导程序的开发,如果确实需要,您可以禁用 STM32 嵌入式引导程序,例如通过将 NSBOOTADD0 和 NSBOOTADD1 设置为您引导程序的地址,并启用(永久性)内存保护(参见内存保护)。有关更多详细信息,请参阅启动配置。
如果你想在 RAMN 上实现内存漏洞(例如用于安全培训),很可能需要让某些变量在内存中彼此相邻。确保变量相邻的简单方法是使用 C 语言中的结构体 ,但如果这些变量属于不同的模块,这种方法并不实用。
请注意,使用 GCC 时,您无法采用某些可能在网上找到的建议(例如,使用 at 属性)。您可以通过使用 section 属性,确保某些变量(即使它们属于不同的模块)在特定内存区域中彼此相邻。例如,如果您希望确保 variable1 和 variable2 相邻放置,可以按如下方式定义它们:
然后,您可以修改链接器脚本 STM32L552CETX_FLASH.ld,并使用 KEEP 指令,以使这些变量位于特定区域。例如,如果您希望 variable2 位于 variable1 之前,在 .bss 区域的末尾,只需在 *(.bss*) 之后添加以下几行:
或者,您也可以通过在 STM32L552CETX_FLASH.ld 中定义自定义区域,为这些变量创建新的区域:
例如,如果您希望确保它们被放置在 RAM 的最末端,请在 HEAP 段的定义(以 ._user_heap_stack : 开头)之后立即添加上述定义。
有关扩展硬件的详细信息,请参阅扩展端口页面。
设计扩展时最易使用的接口是 UART,但您也可以使用 I2C(主设备或从设备)、SPI(主设备或从设备)、基于定时器的接口(例如用于 WS2812B LED 和伺服电机的 PWM 控制)、模数转换器(ADC)以及 GPIO(例如用于位操作或中断)。
UART(LPUART1,速率为 115200bps)和 I2C(I2C2,设备模式,地址为 0x77)仅为方便您使用而预先配置;它们并未被主动使用。 SPI(SPI2)已配置,并由 ECU A 和 D 主动用于控制显示屏和 LED。 如果您希望在 ECU A 和 D 上使用 SPI 进行自定义扩展,则需要对通信进行复用,并且将无法以设备模式使用 SPI(除非您移除原有的扩展模块)。
ECU A 上配备了一个 SD 卡读卡器,可通过 SPI 模式访问 SD 卡。RAMN 并未主动使用该功能,但其本身是可用的。 PB10 引脚同时用于预配置的 I2C SCL 引脚和 ECU A 上 SD 卡读卡器的 CS 引脚,因此除非您清楚自己的操作,否则应假设无法同时使用 SD 卡读卡器和 I2C。
如果您计划使 RAMN 的总功耗超过 500 mA,则应更新 USB 描述符中的“MaxPower”字段(位于 usbd_composite.c 中)。
在制作扩展板之前,我们强烈建议您打开 ioc 文件(参见修改 .ioc 文件),并充分配置微控制器,以确保您所需的所有功能均在所用引脚上可用,并自动检查潜在的冲突。
您需要连接 TX 和 RX 引脚,以实现与扩展设备上的通信(如果仅单向通信,则只需连接其中一个引脚)。
换句话说,您可能需要在 RAMN 与您的扩展设备之间交叉 RX 和 TX 引脚 。此外,您还可以选择使用 PA6 作为“CTS”,PB1 作为“RTS”。
要在 RAMN 固件中启用 UART,您需要在 ramn_config.h 中定义 ENABLE_UART 标志。之后,您可以在 ramn_customize.c 中使用钩子来发送和接收 UART 数据(主要可以使用 RAMN_CUSTOM_ReceiveUART 以及 RAMN_UART_SendFromTask 或 RAMN_UART_SendStringFromTask)。
如果需要更改波特率或其他 UART 设置,请参阅修改 .ioc 文件部分。ECU A 还需要执行一些额外的步骤(详见 ramn_config.h)。
你需要连接:
默认情况下,RAMN 配置为 I2C 设备模式 。无需对 I2C 进行“交叉接线”,因此可直接将 SCL 连接到 SCL,SDA 连接到 SDA。如果希望将 RAMN ECU 用作 I2C 主设备,只需更改 I2C2 外设配置(参见修改 .ioc 文件),连接保持不变。
默认情况下会启用内部上拉电阻,如果您的扩展设备上已存在上拉电阻,您可能需要将其禁用。
要在 RAMN 的固件中启用 I2C,请在 ramn_config.h 中启用 ENABLE_I2C 标志。之后,您可以在 ramn_customize.c 中使用钩子(在接收数据时为 RAMN_CUSTOM_ReceiveI2C,在接收到传输请求时为 RAMN_CUSTOM_PrepareTransmitDataI2C )。
如果您希望将 RAMN 用作主设备,可以直接使用 HAL 库(例如使用 HAL_I2C_Master_Transmit 和 HAL_I2C_Master_Receive 等函数,并参考网上众多的 STM32 教程)。有关使用示例,请参阅 SPI 部分(I2C 与 SPI 类似)。
如果要将 DMA 功能与 I2C 一起使用,还需要在 RAMNV1.ioc 文件中更新 DMA 配置(以配置 I2C DMA),并重新生成代码(请参阅 修改 .ioc 文件)。
您需要连接 SCK、MISO、MOSI 和 CS 引脚。如果不存在从设备到主设备的通信,则无需使用 MISO 引脚。
默认情况下,RAMN 配置为 SPI 主模式 ,您应连接以下引脚:
PB2 (LCD_nCS) 用作 ECU A 和 ECU D 扩展的“片选”(CS)引脚。请勿将其用于您自己的扩展。
无需将线路交叉连接 SPI(将 MISO 连接到 MISO,将 MOSI 连接到 MOSI)。默认情况下,PB14(MISO)具有一个启用的内部上拉电阻 (以兼容 ECU A 扩展上的 SD 卡读卡器)。如果您的扩展已自带或不需要上拉电阻,则应将其禁用。
如果要使用 SPI 访问 ECU A 显示屏上的 SD 卡读卡器,必须修改 RAMNV1.ioc 文件以禁用 I2C,并将 PB10 配置为输出(请参阅修改.ioc 文件),然后将其用作 SD 卡的片选引脚。
要在固件中启用 SPI,请在 ramn_config.h 中启用 ENABLE_SPI 标志。SPI 的 DMA 已经配置完毕。您可以参考网上众多 STM32 教程,使用 HAL SPI 接口(例如 HAL_SPI_Transmit_DMA 等)。
RAMN 的 SPI 功能通常通过启动传输并进入休眠状态,直到接收到传输完成的通知。 ramn_spi.c 假定在调用函数时 SPI 外设已准备就绪,并且仅在传输完成后通知主周期性任务,因此您应仅从主周期性任务中调用 SPI 函数,例如在 RAMN_CUSTOM_Update 中。或者,您也可以修改 SPI 模块以改变这一行为。
如果您在主周期性任务中编写代码(例如在 ramn_customize.c 中的 RAMN_CUSTOM_Update 中),则当您的代码被调用时,SPI 外设将始终处于就绪状态,并且您应当仅在外设再次就绪时才返回。
例如,你可以通过以下伪代码发送和接收 SPI 消息:
上述代码在等待时会占用 100%的 CPU,从而阻止低优先级任务的执行。因此,建议你使用等效的 DMA 函数(并在等待期间允许其他任务执行)。要调用 DMA 函数并等待通知,你可以使用如下示例:
然后,你需要确保在传输完成后,通过覆盖与你所调用函数相关联的回调函数,来通知并恢复任务的执行。
如果遇到问题,请确保已启用 SPI 中断(参见修改.ioc 文件)。
PA6、PA7、PB0 和 PB1 可用作 ADC,且不会与其他接口发生冲突。如果需要更多 ADC,还可以禁用 UART 接口,这样 PA2 和 PA3 将被释放出来,同样可配置为 ADC。
默认情况下,RAMN 使用 DMA 定期从 ADC 读取数据,并自动将最新的 12 位值存储到一个由 16 位变量组成的数组中。
如果这种行为正是您所需要的,只需将新的 ADC 引脚配置为 “ADC1_INx”,并在 .ioc 文件中更新 ADC1 设置以包含您的新 ADC(参见 修改 .ioc 文件)。请务必更新所有相关字段,包括“转换次数”字段。然后,在 ramn_sensor.h 中更新 NUMBER_OF_ADC,以包含您的新 ADC。对于 ECU A,还应在 ramn_config.h 中定义 ENABLE_ADC 标志位。
如果你需要另一种行为,应将你的 ADC 引脚配置为 “ADC2_Inx”(使用 2 而非 1),然后按照网上众多 STM32 教程中的说明,对 ADC2 进行配置以满足你的需求。
PA6、PA7、PB0 和 PB1 未被分配功能,可配置为定时器(输入或输出)。它们通常可用于读取和写入 PWM 信号, 但不同定时器的功能有所差异,因此你应确认这些引脚具备你所需的精确功能 。
RAMN 默认情况下不需要 UART 和 I2C。如果你不使用 UART,也可以将 PA2 和 PA3 用作定时器引脚。如果你不使用 I2C,也可以将 PB10 和 PB11 用作定时器引脚。对于不使用 SPI 的 ECU B 和 C,你还可以考虑将所有 SPI 引脚用作定时器引脚。
PA6、PA7、PB0 和 PB1 未分配功能,均可配置为 GPIO 或外部中断。
通常,大多数 STM32 引脚可用作 GPIO 或外部中断。如果需要大量引脚,请考虑禁用未使用的接口并复用其引脚,具体说明请参见定时器(PWM)。
有关 FreeRTOS 如何用于实现 RAMN 功能的概述,请参阅 固件架构。以下是一些针对不熟悉 RTOS 用户的 FreeRTOS 使用技巧:
UDS_LoadTest.py 脚本来测试此类问题。您还可以使用 vTaskPrioritySet() 动态调整优先级。taskYIELD()(或 portYIELD_FROM_ISR())可立即允许高优先级任务执行。但是,如果存在已就绪的更高优先级任务,这将不会允许低优先级任务运行。RAMN_CUSTOM_TIM6ISR,它应仅用于通过 vTaskNotifyGiveFromISR() 通知另一个任务(请参阅 HAL_UART_TxCpltCallback 以获取示例)。xStreamBufferCreateStatic())。RAMN_CUSTOM_CustomTask 函数,请确保其中包含无限循环,或者以 vTaskDelete(NULL) 结束。osDelay() 使任务进入睡眠状态,以便其他任务得以执行。如果你的任务执行时间可变,请使用 vTaskDelayUntil() 来计算相对于任务开始执行时刻的延迟时间(而非调用延迟函数时的时刻)。RAMN 中没有内置方法来判断特定已排队待发送的 CAN 消息是否成功发送。默认情况下,CAN 外设的自动重传功能是启用的,这意味着 ECU 将持续重试发送,直到成功或进入总线关闭模式。
如果您禁用自动重传功能,请考虑修改 RAMN_SendCANFunc()、 HAL_FDCAN_TxBufferCompleteCallback() 以及 HAL_FDCAN_ErrorCallback(),以跟踪哪些报文已发送,哪些未发送。
请注意,当自动重传功能被禁用时,观察到的行为是: 如果 ECU 在仲裁过程中失败,将不会重新尝试传输 (这是一种正常现象,并非真正的 CAN 错误)。您可能还需要修改源代码以改变这一行为。