网站开发技术的比较,手机网络不稳定,自己如何做网站优化,产品网站推广下面将详细介绍如何在 Keil MDK 环境下将 FreeRTOS 手动移植到 STM32G473VET6 微控制器上。内容涵盖工程创建、获取源码、文件组织、移植层适配、测试任务编写以及编译调试等步骤。
1. 工程搭建#xff08;Keil 项目创建#xff09;
创建基础工程#xff1a;首先准备一个基…下面将详细介绍如何在 Keil MDK 环境下将 FreeRTOS 手动移植到 STM32G473VET6 微控制器上。内容涵盖工程创建、获取源码、文件组织、移植层适配、测试任务编写以及编译调试等步骤。
1. 工程搭建Keil 项目创建
创建基础工程首先准备一个基础的 STM32G473VET6 工程例如一个点灯工程。可以使用 STM32CubeMX 生成 Keil uVision 工程勾选需要的外设驱动和初始化代码如 HAL 库以简化后续开发。确保工程包含 STM32G4的启动文件如startup_stm32g473xx.s和系统时钟配置代码SystemInit() 或 HAL 的 SystemClock_Config()。配置 Keil 工程选项在 Keil 中选择正确的器件和编译器。对于 Cortex-M4F 内核启用硬件 FPU 支持。使用 ARM Compiler 5 可以直接使用 FreeRTOS 提供的 RVDS 移植代码如果使用 ARM Compiler 6需要确保使用对应的移植代码或兼容设置。包含 HAL 库可选如果使用 STM32CubeMX 生成的工程并选择了 HAL 驱动则工程已包含 HAL 库和CMSIS设备支持。HAL 库的使用是可选的但如果使用HAL需要注意其与 FreeRTOS 的时间基准SysTick的配合。
完成上述基础工程搭建后确保一个简单的裸机程序如LED闪烁可以编译运行以此作为移植 FreeRTOS 的起点。
2. 获取 FreeRTOS 源码
下载最新 FreeRTOS 内核从 FreeRTOS 官方网站获取最新版本的内核源码压缩包。解压后会看到包含 FreeRTOS/Source 源码目录以及各类 Demo 示例等。我们主要关注 FreeRTOS/Source 下的文件。整理所需源码文件FreeRTOS 提供了很多可选组件我们在移植时只保留必要的内核源文件。将以下文件从 FreeRTOS/Source 复制到工程目录建议放入 Middlewares/FreeRTOS/Source 内核核心tasks.c任务调度与管理、queue.c队列和信号量实现、list.c内核链表、timers.c软件定时器可选、event_groups.c事件标志组可选、croutine.c协程可选根据需要等。这些文件实现了任务管理、调度器、队列、软件定时器、事件组等核心功能。头文件复制 FreeRTOS/Source/include 目录下的所有头文件到工程的 FreeRTOS/Source/include或指定一个包含路径。这些头文件定义了FreeRTOS API和配置项。 移植层与内存管理在 FreeRTOS/Source/portable 目录下根据使用的内核架构和编译器选择正确的移植层代码 针对 STM32G473 (Cortex-M4F 内核) 和 Keil MDK 编译器使用路径 portable/RVDS/ARM_CM4F 下的文件。这通常包含 port.c 和 portmacro.h实现与 Cortex-M4F 内核相关的上下文切换和中断处理代码。将 ARM_CM4F 文件夹复制到工程的 FreeRTOS/Source/portable 下。内存管理方面FreeRTOS 提供多种堆管理实现。在 portable/MemMang 目录下选择一款堆实现源码文件例如常用的 heap_4.c最佳适应算法支持分配和释放或 heap_5.c支持多内存区。复制所选的 heap_x.c 文件到 FreeRTOS/Source/portable/MemMang 并加入工程。其余未用的 portable 子目录和堆实现文件可不添加以减少干扰。 添加到 Keil 工程在 Keil 中为 FreeRTOS 创建分组并添加上述源文件。例如新建 “FreeRTOS_CORE” 分组添加内核 .c 文件tasks.c、queue.c 等新建 “FreeRTOS_PORT” 分组添加移植层和堆文件如 port.c、heap_4.c 等。同时在工程的 C/C Include 路径中增加 FreeRTOS 的头文件目录例如 Middlewares\FreeRTOS\Source\include 以及 Middlewares\FreeRTOS\Source\portable\RVDS\ARM_CM4F以便编译器能够找到 FreeRTOS.h、portmacro.h 等头文件。获取或创建 FreeRTOSConfig.hFreeRTOSConfig.h 是用于配置 FreeRTOS 内核的头文件不包含在上述源码中。我们需要为 STM32G4 创建该文件并根据需求进行配置。可以参考 FreeRTOS 提供的示例配置例如在 FreeRTOS/Demo 或 STM32 的示例中查找类似 STM32 的配置复制并修改后加入工程的 Inc 或 FreeRTOS/include 目录。确保在编译选项的包含路径中能找到该文件。下面将在下一节详细说明关键配置项。
3. 文件结构整理与 FreeRTOSConfig 配置
工程目录组织按照惯例可将 FreeRTOS 源码放置在工程目录下的 Middlewares/FreeRTOS 文件夹中并划分子目录 Middlewares/FreeRTOS/Source放置 FreeRTOS内核 .c 源文件和 include 头文件。Middlewares/FreeRTOS/Source/portable放置移植层相关文件。其中 portable/RVDS/ARM_CM4F 存放 Cortex-M4F Keil 移植代码portable/MemMang 存放所选择的堆管理实现。如果使用 CubeMX 自动生成中间件结构可直接将文件对号入座到对应文件夹。 FreeRTOSConfig.h 关键配置打开新建的 FreeRTOSConfig.h根据 STM32G473VET6 的硬件参数和应用需求设置各项宏定义。以下是常用配置项 系统频率configCPU_CLOCK_HZ 定义CPU时钟频率Hz。可设置为系统时钟频率数值或使用 SystemCoreClock 变量。例如 #define configCPU_CLOCK_HZ (SystemCoreClock)确保这个值与系统实际运行频率匹配。
Tick 定时频率configTICK_RATE_HZ 定义RTOS滴答时钟频率即系统节拍中断频率。常用设为1000Hz1ms周期。需与 SysTick 配置匹配。最大优先级数configMAX_PRIORITIES 定义系统可用的任务优先级数量优先级从0到configMAX_PRIORITIES-1。根据应用需要设置一个合适值比如5或以上。注意至少要大于等于使用的优先级数Idle任务优先级为0。最小空闲任务栈configMINIMAL_STACK_SIZE 定义空闲任务的栈深度以字为单位。Cortex-M4上一般设置为128即512字节或根据需求调整。总堆大小configTOTAL_HEAP_SIZE 定义FreeRTOS可用的堆内存总字节数仅对heap_1.c, heap_2.c, heap_4.c, heap_5.c等有效。根据创建的任务、队列等数量估算所需内存并设置。例如设为(10*1024)表示10KB用于容纳所有动态分配对象。内核特性开关根据需要启用或禁用内核功能宏 configUSE_PREEMPTION 设置为1启用抢占式调度常用为0则为协作式调度。configUSE_TIME_SLICING 为1则同优先级任务时间片轮转。configUSE_IDLE_HOOK/configUSE_TICK_HOOK 设置是否使用空闲任务和Tick中断的钩子函数如不需要可设0。configUSE_MUTEXES、configUSE_COUNTING_SEMAPHORES 等设为1启用互斥信号量和计数信号量。configUSE_TRACE_FACILITY 和 configUSE_STATS_FORMATTING_FUNCTIONS 可用于启用运行时统计如 uxTaskGetSystemState。configGENERATE_RUN_TIME_STATS 如需启用运行时间统计需要提供时钟源。其他比如 configCHECK_FOR_STACK_OVERFLOW (栈溢出检查)configUSE_MALLOC_FAILED_HOOK (内存分配失败钩子)可按需设置。 软件定时器和事件组如果使用软件定时器和事件标志组 设置 configUSE_TIMERS 为1并配置 configTIMER_TASK_PRIORITY定时器服务任务优先级一般高于普通任务configTIMER_QUEUE_LENGTH定时器命令队列长度configTIMER_TASK_STACK_DEPTH定时器任务栈深度。设置 configUSE_EVENT_GROUPS 为1 以启用事件组机制。 中断优先级配置针对 Cortex-M 内核以下配置 极为重要必须正确设置中断优先级相关宏以确保 FreeRTOS 安全运行 configPRIO_BITSNVIC 可用优先级位数。STM32G4 系列有 4 位优先级0-15级因此 configPRIO_BITS 应定义为4如果CMSIS的__NVIC_PRIO_BITS已定义则可用它。configLIBRARY_LOWEST_INTERRUPT_PRIORITY应用可设置的最低中断优先级数值。STM32优先级数值越大优先级越低一般设为15表示最低优先级。configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY使用FreeRTOS系统调用的最高中断优先级数值。建议选择一个较高的优先级等级例如5表示任何优先级数值5的中断服务例程 不应调用FreeRTOS API。configKERNEL_INTERRUPT_PRIORITY内核所使用的中断优先级用于PendSV和SysTick。通常定义为 configLIBRARY_LOWEST_INTERRUPT_PRIORITY 左移适当位数使其成为最低优先级。例如
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY (8 - configPRIO_BITS) )若优先级位4位上式计算结果为 15 4 240 (0xF0)对应 NVIC 优先级15。这确保RTOS内核的中断PendSV/SysTick设为最低优先级。
configMAX_SYSCALL_INTERRUPT_PRIORITY可调用系统API的最高中断优先级临界值。定义为 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 左移 (8 - configPRIO_BITS) 位。例如优先级5则计算为 5 4 80 (0x50)对应 NVIC 优先级5设置上述优先级配置后务必在应用中确保所有使用 FreeRTOS API的中断的NVIC优先级数值大于等于configMAX_SYSCALL_INTERRUPT_PRIORITY例如设为5或更大即优先级不高于5级否则会触发优先级嵌套错误。。这意味着优先级数值小于5的中断更高优先级的中断不应调用FreeRTOS安全API。
配置完成后保存 FreeRTOSConfig.h。经过以上设置FreeRTOS内核行为就根据目标硬件和应用需求进行了调整。 4. 移植层适配中断向量与启动文件调整
在将 FreeRTOS 集成到 STM32G4 时需要确保 Cortex-M 内核的几个特殊中断SVC、PendSV、SysTick正确地连接到 FreeRTOS 的调度机制。这涉及移植层代码和启动文件的适配 重定向内核中断处理函数FreeRTOS 的 Cortex-M 移植在 port.c 中实现了 SVC、PendSV 和 SysTick 中断处理逻辑。但我们需要把这些处理例程挂接到实际的中断向量。常用方法有两种 宏定义映射法在 FreeRTOSConfig.h 中添加宏将 FreeRTOS 移植层的中断处理函数名映射为标准中断名。例如 #define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler 这样编译器在编译移植层代码时会将实现函数名替换为对应的中断名从而覆盖启动文件中的默认中断处理。STM32CubeMX 生成的FreeRTOS配置常用此方法其中将 SysTick 的映射宏默认注释是因为 HAL 自带 SysTick handler如果我们希望RTOS接管SysTick需要启用该宏。通过这种方式FreeRTOS 的 vPortSVCHandler() 实际编译为 SVC_HandlerxPortPendSVHandler() 编译为 PendSV_HandlerxPortSysTickHandler() 编译为 SysTick_Handler从而自动替换掉弱定义的默认 handlers。 启动文件重定向法直接修改启动文件 startup_stm32g473xx.s将向量表中相应中断的入口指向 FreeRTOS 提供的函数。具体做法是在启动文件中声明移植层函数为外部符号并将中断入口替换为跳转。例如在启动文件的中断向量表区域找到 PendSV_Handler、SysTick_Handler 和 SVC_Handler 的默认实现替换为 EXTERN vPortSVCHandler
EXTERN xPortPendSVHandler
EXTERN xPortSysTickHandler SVC_Handler B vPortSVCHandler ; 跳转到FreeRTOS的SVC处理
PendSV_Handler B xPortPendSVHandler ; 跳转到FreeRTOS的PendSV处理
SysTick_Handler B xPortSysTickHandler ; 跳转到FreeRTOS的SysTick处理 如此修改后这三个中断会触发 FreeRTOS 对应的服务例程实现上下文切换和心跳节拍。 注意采用上述两种方法之一即可通常推荐使用第一种映射宏的方法修改少且清晰。如果使用HAL库且其SysTick作为时基默认HAL会定义自己的SysTick_Handler这时更需要使用映射或修改启动文件确保RTOS的SysTick处理生效而HAL的时基不中断系统滴答可选择在FreeRTOS的滴答钩子中调用HAL_IncTick()以保持HAL时基。 SysTick 定时器设置FreeRTOS利用 SysTick 产生固定频率的节拍中断用于任务调度。需要保证 SysTick 定时中断按照 configTICK_RATE_HZ 配置的频率触发 如果基础工程使用了 HALHAL_Init() 默认将 SysTick 配置为1ms中断1000Hz。若 configTICK_RATE_HZ 也是1000则默认配置可用但要确保 SysTick 中断由 FreeRTOS接管。如上所述可以在 FreeRTOS的 SysTick_Handler 中调用 xPortSysTickHandler() 实现RTOS心跳同时也可调用 HAL_IncTick() 保持HAL的时基计数。如果未使用 HAL 或需手动配置可以在系统初始化时配置 SysTick 寄存器。计算加载值reload SystemCoreClock / configTICK_RATE_HZ然后设置 SysTick SysTick-LOAD reload - 1; // 装载值
SysTick-VAL 0; // 清零计数器
SysTick-CTRL | SysTick_CTRL_TICKINT_Msk; // 使能SysTick中断
SysTick-CTRL | SysTick_CTRL_ENABLE_Msk; // 启动SysTick 同时选择合适的时钟源如使用外部HCLK。这样SysTick每1/configTICK_RATE_HZ秒产生一次中断。在 SysTick 中断服务函数中应调用 FreeRTOS 的心跳处理函数。例如 void SysTick_Handler(void) { if(xTaskGetSchedulerState() ! taskSCHEDULER_NOT_STARTED) { xPortSysTickHandler(); }
} 述实现确保只有在调度器启动后才调用 RTOS 的滴答处理在调度器启动前 SysTick 中断也许已经开启此时不应调用RTOS API。 PendSV 和 SVC 中断PendSV 用于触发上下文切换应设置为最低优先级以免打断高优先级中断。SVCSupervisor Call在 FreeRTOS 中用于启动第一个任务之后不再使用。通常无需手动配置其优先级默认即可PendSV 和 SysTick 优先级会在 FreeRTOS启动时根据 configKERNEL_INTERRUPT_PRIORITY 设置。在移植过程中只需确保上述映射正确FreeRTOS 会将 PendSV/SysTick 设置为最低优先级。NVIC 中断优先级分组STM32 默认优先级分组通常将全部4位用于抢占优先级无子优先级。确保保持这种配置一般无需额外修改SCB-AIRCR。这保证了configMAX_SYSCALL_INTERRUPT_PRIORITY规定的优先级阈值正确生效。
完成移植层适配后FreeRTOS 核心应能正确接管 SVC、PendSV、SysTick 中断实现其调度功能。可以编译工程确保链接阶段没有未定义引用尤其是 vPortSVCHandler 等应已映射或定义。
5. 编写测试任务
移植完成后在 main.c 中创建示例任务以验证 RTOS 调度运行是否正常 初始化在创建任务之前执行必要的硬件初始化。例如调用 HAL_Init() 和 SystemClock_Config()若使用HAL初始化用于测试的外设如配置GPIO用于控制LED初始化UART用于串口打印等。确认此时不要调用会引起延时阻塞的函数如 HAL_Delay()以免在RTOS启动前产生不确定延时。 创建任务使用 FreeRTOS 提供的 API 创建至少两个任务进行演示 LED 闪烁任务例如创建一个周期性闪烁板上 LED 的任务。任务函数中反复切换 LED 引脚状态并调用 vTaskDelay() 延时一定 Tick 数如500ms以测试定时调度功能。串口打印任务创建另一个任务周期性地通过串口打印消息比如每1秒打印一行文本或计数值。这可以测试多个任务并行运行以及任务间的独立性。如使用HAL UART发送可在任务中调用 HAL_UART_Transmit() 发送字符串注意需确保串口初始化在RTOS启动前完成或使用互斥确保线程安全。 使用 xTaskCreate() 创建任务时需要提供任务入口函数、任务名、栈大小、任务参数、优先级和任务句柄等参数。例如 xTaskCreate(LED_Task, LEDTask, 128, NULL, 2, NULL);
xTaskCreate(UART_Task, UARTTask, 256, NULL, 2, NULL);上述示例创建了LED_Task和UART_Task分别指定了栈深度128和256字具体值视功能需求而定和优先级这里都为2同优先级将时间片轮转执行。可以根据需要调整优先级以验证优先级调度效果优先级数值越大优先级越高。 启动调度器所有任务创建完成后调用 vTaskStartScheduler() 启动FreeRTOS调度。该函数调用后RTOS 接管CPU控制权开始根据优先级和时间片调度任务。注意vTaskStartScheduler() 若返回则意味着调度启动失败通常是因为堆内存不足无法创建Idle任务这种情况下可以在main函数中添加错误处理例如进入死循环或触发断言以便调试发现问题。正常情况下该调用不会返回。 任务函数示例下面给出简要的任务函数代码片段示例 void LED_Task(void *argument) {for(;;) {HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // 切换LED引脚状态vTaskDelay(pdMS_TO_TICKS(500)); // 延时500ms (pdMS_TO_TICKS宏将毫秒转换为节拍数)}
}void UART_Task(void *argument) {const char *msg Hello from FreeRTOS!\r\n;for(;;) {HAL_UART_Transmit(huart2, (uint8_t*)msg, strlen(msg), 100);vTaskDelay(1000); // 延时1000ms}
}在上述代码中LED任务每0.5秒翻转一次LED状态UART任务每1秒通过串口发送一条消息。两个任务的死循环中都使用了vTaskDelay或等效宏进行阻塞延时让出CPU给其他任务。 注意 HAL 与 RTOS 的配合如果使用了HAL库在RTOS启动后不要使用HAL_Delay()进行延时因为HAL_Delay依赖于SysTick全局变量的中断更新。在FreeRTOS接管SysTick后默认HAL的滴答不再增长除非在SysTick_Handler中继续调用HAL_IncTick。应改用vTaskDelay等RTOS延时机制来替代阻塞延时。此外如果串口中断等外设中断需要调用FreeRTOS API如xQueueSendFromISR务必确保这些中断优先级符合前述configMAX_SYSCALL_INTERRUPT_PRIORITY限制。 完成上述移植和任务创建后进行编译、下载并调试重点关注以下方面 系统滴答验证调试时可以在 SysTick_Handler 中打断点或读取 xTaskGetTickCount() 返回值确认滴答计数在持续增长频率符合预期的 configTICK_RATE_HZ。例如可在UART打印任务中定期打印 xTaskGetTickCount()值来观察。 任务调度检查观察板上 LED 是否按设计频率闪烁串口打印是否按周期输出。如果只有一个任务运行、另一个任务饿死可能是优先级配置不当或某任务陷入死循环未调用阻塞API。确保每个任务在适当位置会阻塞或延时以让出CPU。还可以使用Keil的调试器查看FreeRTOS线程列表如果安装了FreeRTOS调试插件以确认多个任务都处于就绪/阻塞状态并被调度。 中断优先级问题如果程序跑一段时间后出现HardFault或异常常见原因是中断优先级配置不正确导致违反了FreeRTOS的中断安全策略。检查 FreeRTOSConfig.h 中 configMAX_SYSCALL_INTERRUPT_PRIORITY 的设置以及有无中断使用了过高的优先级调用了RTOS API。如有需要可在 FreeRTOSConfig.h 中定义 configASSERT() 钩子以捕获运行时的优先级违规等错误。 堆内存使用使用 xPortGetFreeHeapSize() 查询剩余堆内存。该函数返回当前未被分配的堆空间大小有助于判断 configTOTAL_HEAP_SIZE 设置是否合理。示例在所有任务创建后调用 xPortGetFreeHeapSize()如果返回值很小接近0说明堆几乎耗尽需要增大 configTOTAL_HEAP_SIZE如果返回值远大于实际需要可以优化减小 configTOTAL_HEAP_SIZE 以节省RAM。FreeRTOS还提供 xPortGetMinimumEverFreeHeapSize() 可查询历史最低剩余堆空间用于评估最糟情况内存占用。 其他调试技巧可开启 configCHECK_FOR_STACK_OVERFLOW并实现vApplicationStackOverflowHook来捕获任务栈溢出开启 configUSE_MALLOC_FAILED_HOOK并实现vApplicationMallocFailedHook来捕获内存分配失败。这些钩子在调试阶段很有帮助。一旦系统运行正常可以选择关闭或保留这些检查。
经过以上步骤如果 LED 按预期闪烁且串口输出正常就表明 FreeRTOS 在 STM32G473VET6 上已成功移植并运行。由此可以进一步开发应用比如创建更多任务利用队列、信号量等进行任务间通信。在实际项目中按照上述指南配置 FreeRTOS可确保系统稳定运行于 Keil MDK 环境下。