FreeRTOS实战指南 — 2 移植 FreeRTOS 到 STM32F429

目录

1 准备裸机工程文件

2 创建FreeRTOS文件夹结构

3 修改Keil工程文件

3.1 添加工程文件

3.2 指定 FreeRTOS 头文件的路径

4 移植FreeRTOSConfig.h配置文件

4.1 移植FreeRTOSConfig.h

4.2 详解FreeRTOSConfig.h

4.3 修改FreeRTOSConfig.h

5 修改main.c


1 准备裸机工程文件

裸机工程模板使用任意的工程模板,这里我们选取比较简单的例程—点亮 LED作为我们本次的实验工程,移植好后,使用FreeRTOS实现简单的LED点灯实验。

2 创建FreeRTOS文件夹结构

(1) 在裸机工程模板的根目录下,创建“FreeRTOS”文件夹,并在此文件夹内分别建立“src”和“port”两个子文件夹。

  • src文件夹:存放FreeRTOS的核心源文件,通常是.c格式,包含了FreeRTOS运行所必需的基础代码。
  • port文件夹:前面1.4节有介绍到,该文件夹用于放置与特定处理器架构和内存管理相关的代码,是连接软件(FreeRTOS)与硬件的关键桥梁。

(2) 打开1.3节获取的FreeRTOS V9.0.0 源码,在“FreeRTOSv9.0.0\FreeRTOS\Source”目录下找到所有的‘.c 文件’,将它们拷贝到我们新建的 src 文件夹中,具体见图 13-12。

 

(3) 打开源码,在“.\FreeRTOS\Source\portable”目录下找到“ MemMang”文件夹(内存管理文件)与“ RVDS”文件夹,将它们拷贝到我们新建的port 文件夹中,如下。

 

(4) 打开 FreeRTOS源码,在“.\ FreeRTOS\Source”目录下找到“include”文件夹,会用到 里面的一些头文件,将它直接拷贝到我们新建的 FreeRTOS 文件夹中。

 

完成这一步之后就可以看到我们新建的FreeRTOS 文件夹已经有 3 个文件夹,这 3个文件夹就包含 FreeRTOS 的核心文件,至此, FreeRTOS 的源码初步移植完成。

为了简便,我们可以直接将整个 FreeRTOS 文件夹复制到我们的 STM32 裸机项目中,FreeRTOS 占用空间较小,多余的代码一般并不会影响项目运行。

3 修改Keil工程文件

3.1 添加工程文件

接下来在Keil开发环境里面新建 FreeRTOS/src 和 FreeRTOS/port 两个组文件夹,其中FreeRTOS/src 用于存放 src 文件夹的内容, FreeRTOS/port 用于存放 port\MemMang 文件夹与 port\RVDS\ARM_CM4文件夹的内容(具体看你开发板型号)。

heap_4.c 是 MemMang 文件夹中跟内存管理相关的文件,MemMang 文件夹中共有 5 个 c 文件:heap_1.c、heap_2.c、heap_3.c、heap_4.c 和 heap_5.c,heap_4 实现是首选,区别如下:

  • heap_1:最简单,不允许释放内存
  • heap_2:允许释放内存,但不会合并相邻的空闲块
  • heap_3:简单包装了标准malloc()和free(),以保证线程安全
  • heap_4:合并相邻的空闲块以避免碎片化,包含绝对地址放置选项
  • heap_5:如同heap_4,能够跨越多个不相邻的堆。

port.c 是 RVDS 文件夹下的 ARM_CM4F 中的文件,因为 STM32F429 是 Cortex-M4 内核并且带有 FPU,因此要选择 ARM_CM4F 中的 port.c 文件。也就是说,需要根据不同的MCU选择不同的硬件接口文件。

3.2 指定 FreeRTOS 头文件的路径

我们再工程文件中添加了一些必要的头文件,Keil必须明确设置这些源代码文件所依赖的头文件搜索路径,否则将导致编译错误。FreeRTOS的头文件存放于FreeRTOS\ include和FreeRTOS\port\RVDS\ARM_CM两个目录中,将这两个目录作为头文件搜索路径进行添加。

此外,我们将FreeRTOSConfig.h这一关键配置文件移动至项目根目录下的user文件夹内。同样也要将user文件夹的路径加入到开发环境的头文件搜索路径列表中。完成上述路径设置后,开发环境将能够正确识别并引用FreeRTOS的头文件,添加步骤如下。

4 移植FreeRTOSConfig.h配置文件

4.1 移植FreeRTOSConfig.h

FreeRTOSConfig.h 文件是 FreeRTOS操作系统的核心配置文件,因为 FreeRTOS 是可以裁剪的实时操作内核,应用于不同的处理器平台,用户可以根据目标硬件平台的特性和应用程序的具体需求来调整操作系统的配置,所以我们把它拷贝一份放在 user 这个文件夹下面。

打开FreeRTOS源码,在“.\FreeRTOS\Demo”文件夹下面找到“CORTEX_M4F_STM32F407ZG-SK”这个文件夹(这里我们使用的是STM32F429,可以根据硬件平台选取相应的文件),双击打开,在其根目录下找到这个“FreeRTOSConfig.h”文件,然后拷贝到我们工程的user文件夹下即可。

4.2 详解FreeRTOSConfig.h

FreeRTOSConfig.h文件通过一系列宏来进行,包括启用抢占式调度、设置CPU时钟频率、定义任务优先级数量、最小堆栈大小、系统堆大小、任务名称长度等。此外,还指定了是否包含特定的API函数以及中断优先级设置,以确保系统的实时性能和资源管理符合特定硬件和应用需求。

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

/* Ensure stdint is only used by the compiler, and not the assembler. */
#if 
    defined __ICCARM__  
    #include <stdint.h>  
    extern uint32_t SystemCoreClock;
#endif

#define configUSE_PREEMPTION    1     // 定义为1启用抢占式调度,0为协作式调度
#define configUSE_IDLE_HOOK     0     // 定义为1启用空闲钩子,允许在空闲任务中添加用户代码
#define configUSE_TICK_HOOK     0     // 定义为1启用滴答钩子,允许在每次滴答中断时添加用户代码
#define configCPU_CLOCK_HZ      (SystemCoreClock)    // 定义CPU时钟频率为72MHz
#define configTICK_RATE_HZ      ( ( TickType_t ) 1000 )           // 定义RTOS滴答频率为1000Hz
#define configMAX_PRIORITIES    ( 5 )     // 定义可用的最大任务优先级数量
#define configMINIMAL_STACK_SIZE  ( ( unsigned short ) 128 )      // 定义最小任务堆栈大小为128个栈单元
#define configTOTAL_HEAP_SIZE   ( ( size_t ) ( 32 * 1024 ) )      // 定义RTOS堆的总大小为17KB
#define configMAX_TASK_NAME_LEN   ( 16 )    // 定义任务名称的最大长度
#define configUSE_TRACE_FACILITY  0         // 定义为1启用跟踪设施,用于任务切换跟踪等
#define configUSE_16_BIT_TICKS    0         // 定义为1使用16位时钟节拍计数,0使用标准的32位计数
#define configIDLE_SHOULD_YIELD   1         // 定义为1时,空闲任务将让出CPU(即使没有其他任务运行)

代码第11行: 选择占式调度器还是协作式调度器,置1使用抢占式调度器,置0使用协作式调度器,这里我们选择抢占式调度。

在抢占式调度中,任务根据它们的优先级进行调度。当一个高优先级任务变为就绪状态时,系统会中断当前运行的较低优先级任务,并将CPU控制权交给高优先级任务。这种调度方式确保了紧急任务能够得到快速响应,因为高优先级任务可以随时抢占低优先级任务的CPU使用权。

在协作式调度中,一旦一个任务开始执行,它将持续运行,直到它自己放弃CPU控制权为止。

代码第12行:这个宏定义表示是否启用空闲任务钩子函数(Idle Hook Function),空闲钩子函数允许开发者在空闲任务执行时插入自定义的代码,空闲任务是在没有任何其他任务就绪时自动运行的任务,其优先级最低(通常为0)。通过空闲钩子函数,开发者可以实现诸如低功耗模式、系统监控、资源清理等后台功能,而无需创建额外的任务来占用系统资源。FreeRTOS规定了函数的名字和参数:voidvApplicationIdleHook(void),具体的函数由用户来实现。

代码第13行:配置时间片钩子函数,与空闲任务钩子函数一样,该函数允许开发者在每个系统时间节拍(tick)中断时执行自定义的代码。与空闲任务钩子函数相同,FreeRTOS规定了函数的名字和参数为void vApplicationTickHook(void),具体函数由用户来实现。

代码第14行:配置 CPU 内核时钟频率,也就是 CPU 指令执行频率,对于STM32F429开发板,其CPU时钟频率可以通过外部晶振、PLL等多种方式进行配置,因此你需要根据你的具体硬件配置来设置configCPU_CLOCK_HZ。STM32F429的CPU时钟被配置为最高168MHz,因此数值应该修改为168000000。

代码第15行:configTICK_RATE_HZ 指定了FreeRTOS的“时钟节拍”(tick)的频率,即每秒钟产生的时钟节拍数。当定义 configTICK_RATE_HZ 为 ( ( TickType_t ) 1000 ) 时,系统时钟每ms(1/1000秒)产生一次中断,即每秒产生1000个时钟节拍。

代码第16行:configMAX_PRIORITIES 指定了FreeRTOS能够支持的任务优先级数量。每个任务在FreeRTOS中都可以被分配一个优先级,优先级决定了任务之间的调度顺序。高优先级的任务会先于低优先级的任务运行,当多个任务同时就绪时,FreeRTOS会根据它们的优先级来决定先运行哪个任务。当定义 configMAX_PRIORITIES 为 5时,意味着你可以将任务分为5个不同的优先级级别,从0(最低优先级)到4(最高优先级)。

代码第17行:configMINIMAL_STACK_SIZE定义了任务的最小堆栈大小。这里的单位是“栈单元”,具体栈单元大小取决于具体硬件配置,多数情况下一个栈单元可能对应于处理器的一个字(word)大小。对于configMINIMAL_STACK_SIZE的值,128个栈单元可能对于非常简单的任务来说是足够的,但对于需要执行复杂操作或大量局部变量存储的任务来说,这个值可能需要修改为更大的值。

代码第18行:configTOTAL_HEAP_SIZE定义了FreeRTOS堆的总大小,堆是用于动态内存分配的内存区域,单位为字节,代码中堆被配置为17KB(17 * 1024字节)。

代码第19行:configMAX_TASK_NAME_LEN定义了FreeRTOS中任务名称的最大长度。代码中最大长度被设置为16个字符,它限制了每个任务名称所占用的内存空间。如果任务名称过长,可能会浪费宝贵的内存资源。

代码第20行:configUSE_TRACE_FACILITY用于控制FreeRTOS是否启用跟踪设施,跟踪设施是FreeRTOS提供的一种高级功能,它允许开发者跟踪任务的状态变化,如任务的创建、删除、挂起、恢复以及任务之间的切换等。启用跟踪设施需要额外的代码和可能的内存开销,因为它需要维护一个跟踪缓冲区来记录这些事件。如非必要,该定义为0,意味着跟踪设施被禁用。

代码第21行:configUSE_16_BIT_TICKS用于选择FreeRTOS内部时钟节拍(tick)的计数位数。设置为1时,FreeRTOS将使用16位的计数器来跟踪时钟节拍,设置为0时,FreeRTOS将使用标准的32位计数器。对于需要长时间运行且对时钟节拍计数范围有较高要求的项目,建议使用32位计数。

代码第22行:configIDLE_SHOULD_YIELD定义FreeRTOS的空闲任务在执行时是否应该让出CPU。这里的值设置为1,空闲任务在执行完其必要的操作后,会调用taskYIELD()或taskYIELD_FROM_ISR()函数,从而让出CPU的控制权。一般不建议使用这个功能,能避免尽量避免。

/* 设置以下定义为1以包含API函数,或0以排除API函数。 */
#define INCLUDE_vTaskPrioritySet    1     // 包含用于设置任务优先级的API函数
#define INCLUDE_uxTaskPriorityGet   1     // 包含用于获取任务优先级的API函数
#define INCLUDE_vTaskDelete     1         // 包含用于删除任务的API函数
#define INCLUDE_vTaskCleanUpResources 0   // 不包含用于清理任务资源的API函数
#define INCLUDE_vTaskSuspend      1       // 包含用于挂起任务的API函数
#define INCLUDE_vTaskDelayUntil   1       // 包含用于设置任务延迟直到特定时间的API函数
#define INCLUDE_vTaskDelay      1         // 包含用于延迟任务执行的API函数


/* 这是按照Cortex-M3 NVIC的原始值。值可以是255(最低)到0(最高)。 */
#define configKERNEL_INTERRUPT_PRIORITY     255    // 定义内核中断优先级(数值越小优先级越高)
#define configMAX_SYSCALL_INTERRUPT_PRIORITY  191  // 定义最高系统调用中断优先级,等同于0xb0,或优先级11


/* 这是根据ST库使用的值,它允许16个优先级值,0到15。这必须与configKERNEL_INTERRUPT_PRIORITY
设置相对应。这里15对应于最低的NVIC值255。 */
#define configLIBRARY_KERNEL_INTERRUPT_PRIORITY 15  // 定义库使用的内核中断优先级,对应于最低的NVIC值255


#endif /* FREERTOS_CONFIG_H */

代码第2行:INCLUDE_vTaskPrioritySet 定义vTaskPrioritySet函数是否被包含在项目中。vTaskPrioritySet函数用于动态地改变一个任务的优先级,当任务的工作负载变化时,可能需要调整其优先级以优化系统的性能。

代码第3行:INCLUDE_uxTaskPriorityGet定义uxTaskPriorityGet函数是否被包含。这个函数用于获取当前任务的优先级。

代码第4行:INCLUDE_vTaskDelete定义 vTaskDelete函数是否被包含。函数用于删除一个任务,并释放其占用的所有资源(除了栈空间,如果栈空间是动态分配的,则需要单独处理)。这对于那些一旦完成就不需要再次执行的任务非常有用。

代码第5行:INCLUDE_vTaskCleanUpResources定义vTaskCleanUpResources函数是否被包含。函数通常用于在任务调度器不再需要时释放所有由FreeRTOS占用的资源。

代码第6行:INCLUDE_vTaskSuspend定义vTaskSuspend函数是否被包含。函数用于挂起一个任务,即让任务进入挂起状态,从而不再参与调度。

代码第7行:INCLUDE_vTaskDelayUntil定义是否启用vTaskDelayUntil函数。函数用于让任务延迟执行,直到指定的绝对时间到达。这对于需要周期性执行的任务(如定时任务)非常有用。

代码第8行:INCLUDE_vTaskDelay定义是否启用vTaskDelay函数。函数vTaskDelay是基于相对时间的延迟,即任务将延迟指定的时间周期后继续执行。

代码第12行:configKERNEL_INTERRUPT_PRIORITY定义了内核使用的中断优先级。设置为255,意味着FreeRTOS内核的中断处理函数将使用最低的优先级,任何更高优先级的中断都可以打断它。

代码第13行:configMAX_SYSCALL_INTERRUPT_PRIORITY定义了系统调用可以安全执行的最大中断优先级。源码被设置为191,确保了系统调用的执行不会被大多数中断打断,但允许一些更高优先级的中断打断它。

代码第18行:configLIBRARY_KERNEL_INTERRUPT_PRIORITY用于将FreeRTOS的内核中断优先级映射到该库或HAL使用的优先级系统上。

4.3 修改FreeRTOSConfig.h

(1) 编译时,发现SystemCoreClock 未定义错误。因为在 FreeRTOSConfig.h 中使用到了SystemCoreClock 来标记 MCU 的频率,而这里的定义是有条件的。可以包含宏定义SystemCoreClock的头文件#include "stm32f4xx.h",也可以修改这个条件编译,修改前:

#ifdef __ICCARM__

  #include <stdint.h>

  extern uint32_t SystemCoreClock;

#endif

修改后如下:

#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)

  #include <stdint.h>

  extern uint32_t SystemCoreClock;

#endif

(2) 修改#define configCPU_CLOCK_HZ 系统式中频率,将其设置为SystemCoreClock宏,在system_stm32f4xx.c中已经设置系统时钟频率为180MHz。

(3) 未定义的Hook函数,在FreeRTOSConfig.h中开启了这些钩子函数,但是却没有定义这些钩子函数而导致的。

..\..\Output\LED.axf: Error: L6218E: Undefined symbol vApplicationIdleHook (referred from tasks.o).

..\..\Output\LED.axf: Error: L6218E: Undefined symbol vApplicationStackOverflowHook (referred from tasks.o).

..\..\Output\LED.axf: Error: L6218E: Undefined symbol vApplicationTickHook (referred from tasks.o).

..\..\Output\LED.axf: Error: L6218E: Undefined symbol vApplicationMallocFailedHook (referred from heap_4.o).

在FreeRTOSConfig.h 中关闭这些钩子函数,将 宏 configUSE_IDLE_HOOK 、 configUSE_TICK_HOOK 、configUSE_MALLOC_FAILED_ HOOK  和 configCHECK_ FOR_ STACK_OVERFLOW 定义为 0。

(4) 没有使能硬件浮点,会报如下错误:

..\..\FreeRTOS\port\RVDS\ARM_CM4_MPU\port.c(43): error:  #35: #error directive: This port can only be used when the project options are configured to enable hardware floating point support.

      #error This port can only be used when the project options are configured to enable hardware floating point support.

解决方法如下:打开魔术棒,在Floating Point Hardware一栏中从之前的Not Used 切换到Use FPU,点击OK 保存配置。

(5) 重复定义中断服务函数错误。通过全局搜索查找SVC_Handler,我们发现在FreeRTOS源代码中已经实现了SVC_Handler函数,而之前在stm32f4xx_it.c文件中也实现了该函数,我们需要注释掉stm32f4xx_it.c中SVC_Handler中断服务函数的实现。

..\..\Output\多彩流水灯.axf: Error: L6200E: Symbol SVC_Handler multiply defined (by port.o and stm32f4xx_it.o).

..\..\Output\多彩流水灯.axf: Error: L6200E: Symbol PendSV_Handler multiply defined (by port.o and stm32f4xx_it.o).

..\..\Output\多彩流水灯.axf: Error: L6200E: Symbol SysTick_Handler multiply defined (by port.o and stm32f4xx_it.o).

注释掉有SVC_Handler、PendSV_Handler、SysTick_Handler三个中断服务函数。

/**
  * @brief  This function handles SVCall exception.
  * @param  None
  * @retval None
  */
//void SVC_Handler(void)
//{}

/**
  * @brief  This function handles PendSV_Handler exception.
  * @param  None
  * @retval None
  */
//void PendSV_Handler(void)
//{}


/**
  * @brief  This function handles SysTick Handler.
  * @param  None
  * @retval None
  */
 //systick 中断服务函数
// void SysTick_Handler(void)
// {}

5 修改main.c

Main函数定义了两个任务TaskLED1和TaskLED2,分别用来控制两个LED灯,每个任务在无限循环中点亮对应的LED,然后通过vTaskDelay函数延迟500毫秒关闭。

实验现象应该是两个LED灯都以500毫秒的间隔闪烁,由于两个任务的延迟相同,如果调度器将时间片分配给这两个任务,会同步闪烁。

#include "stm32f4xx.h"
#include "FreeRTOS.h"
#include "task.h"
#include "./led/bsp_led.h"

void TaskLED1(void *pvParameters)
{
    while (1)
    {
        LED1( ON );
        vTaskDelay(pdMS_TO_TICKS(500));
        LED1( OFF );
    }
}

void TaskLED2(void *pvParameters)
{
    while (1)
    {
        LED2( ON );
        vTaskDelay(pdMS_TO_TICKS(500));
        LED2( OFF );
    }
}

int main(void)
{
  /* LED 端口初始化 */
  LED_GPIO_Config();

  /* 创建任务 */
  xTaskCreate(TaskLED1, "LED1", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
  xTaskCreate(TaskLED2, "LED2", configMINIMAL_STACK_SIZE, NULL, 1, NULL);

  /* 启动调度器 */
  vTaskStartScheduler();
  /* 如果调度器启动失败,程序将停在这里 */

  while (1)
  {
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

几度春风里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值