首先从FreeRTOS官网下载源码,FreeRTOS官网
下载的文件解压后查看FreeRTOS-Kernel文件夹,这是FreeRTOS的内核。
内核文件结构
FreeRTOS-Kernel文件夹下的.c文件
| 文件名 | 必选 | 作用 | 关联的内核对象或功能 |
|---|---|---|---|
| task.c | 是 | 与任务调度相关的内容存储在此文件中,该文件必须添加到工程中 | 任务、任务通知、调度器 |
| list.c | 是 | 用于实现 “列表” 数据类型,仅 FreeRTOS 内部使用,但它是支撑 FreeRTOS 运行的关键。该文件必须添加到工程中 | — |
| queue.c | 与队列、各种信号量相关的源文件。如果项目中用到队列或信号量,则需要添加此文件 | 队列、各种信号量(二进制信号量、计数信号量、互斥锁、递归互斥锁) | |
| timer.c | 与软件定时器相关的源文件。如果项目中用到了定时器,则需要添加此文件 | 软件定时器 | |
| event_group.c | 与事件组相关的源文件。如果项目中使用到了事件组,则需添加此文件 | 事件组 | |
| croutine.c | 与协程相关的源文件。协程是一种比较早期的技术,用于资源极度匮乏的嵌入式硬件系统,现在已经废弃。 | 协程 | |
| stream_buffer.c | 与流缓冲区和消息缓冲区有关的源文件。如果项目中使用到了流缓冲区或者消息缓冲区,则需要添加此文件。注意,本课程暂不介绍与流缓冲区和消息缓冲区相关的内容。 | 流缓冲区、消息缓冲区 |
通常情况下,上述的.c文件全部放在工程中,这只会增加一点代码体积,是可接受的。
一般在工程目录下新建FreeRTOS文件夹,放在/FreeRTOS/Src/目录下。
FreeRTOS-Kernel文件夹下的.h文件
| 文件名 | 必选 | 作用 | 关联的内核对象或功能 |
|---|---|---|---|
| FreeRTOS.h | 是 | FreeRTOS 内核的头文件,该头文件必须被引用。 | - |
| task.h | 是 | 如果使用到与任务、任务通知相关的功能,则该头文件要被引用 | 任务、任务通知、调度器 |
| queue.h | 队列相关的头文件,如果使用到与队列相关的功能,则该头文件要被引用 | 队列 | |
| semphr.h | 信号量相关的头文件,如果使用到与信号量相关的功能,则该头文件要被引用 | 二进制信号量、计数信号量、互斥锁、递归互斥锁 | |
| timer.h | 软件定时器相关的头文件,如果使用到与软件定时器相关的功能,则该头文件要被引用 | 软件定时器 | |
| event_group.h | 事件组相关的头文件,如果使用到与事件组相关的功能,则该头文件要被引用 | 事件组 | |
| message_buffer.h | 消息缓冲区相关的头文件,如果使用到与消息缓冲区相关的功能,则该头文件要被引用。注意,本课程暂不介绍与流缓冲区和消息缓冲区相关的内容。 | 消息缓冲区 | |
| stream_buffer.h | 流缓冲区相关的头文件,如果使用到与流缓冲区相关的功能,则该头文件要被引用。注意,本课程暂不介绍与流缓冲区和消息缓冲区相关的内容。 | 流缓冲区 |
一般情况下,所以的头文件全部放在工程中,放在/FreeRTOS/Inc/目录下。
全部的头文件放在Inc目录下之后,还要在IDE中引用这些头文件:右键Inc文件夹,点击Add/remove include path…,再点击OK即可。
portable移植层
移植层就是为不同的工具链和单片机内核提供的一组代码。
移植层的文件夹结构:portable\开发工具链\内核类型
本文使用开发工具为STM32CubeIDE(GCC),单片机内核类型为ARM_CM3,因此打开portable\GCC\ARM_CM3目录。
把目录下的port.c和portmacro.h文件添加到工程中,分别放在Src和Inc目录下。
MenMang内存管理
该文件夹位于portable目录下。
文件夹内有5个文件heap1~heap5,分别对应5种堆内存管理方案,一般只把heap_4.c添加到工程中,其他无需添加。放到Src文件夹。
FreeRTOSConfig.h文件
该文件是配置文件,位于\examples\template_configuration目录下。
这是配置文件的模板,添加到工程\Core\Inc目录下。
配置文件
官方说明文档:https://www.freertos.org/zh-cn-cmn-s/Documentation/02-Kernel/03-Supported-devices/02-Customization
内核频率:configCPU_CLOCK_HZ
在CubeMX时钟数配置界面配置的HCLK to APB bus, core, memory and DMA(MHz)频率。
例如配置的频率为72MHz,此处填写#define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000 )
TickType_t位宽:configTICK_TYPE_WIDTH_IN_BITS
位宽就是用多少个比特位表示一个整数:
| uint8_t | uint16_t | uint32_t | uint64_t |
|---|---|---|---|
| 无符号8位整数 | 无符号16位整数 | 无符号32位整数 | 无符号64位整数 |
| 位宽为8比特 | 位宽为16比特 | 位宽为32比特 | 位宽为64比特 |
| 0~255 | 0~65535 | 0~约43亿 | 0~约1800亿亿 |
| - | 8位、16位单片机使用 | 32位单片机使用 | 64位单片机使用 |
本项配置为:#define configTICK_TYPE_WIDTH_IN_BITS TICK_TYPE_WIDTH_32_BITS
pendSV和Systick中断优先级:configKERNEL_INTERRUPT_PRIORITY
内核中断优先级。
pendSV和Systick的优先级设置为最低。
因此本项设置为:#define configKERNEL_INTERRUPT_PRIORITY (15 << 4)
能调用FreeRTOS API的最高优先级:configMAX_SYSCALL_INTERRUPT_PRIORITY

因此本项设置为#define configMAX_SYSCALL_INTERRUPT_PRIORITY (5 << 4)
时间片:configUSE_TIME_SLICING
一般为带时间片,此项配置为:#define configUSE_TIME_SLICING 1
栈溢出检测:configCHECK_FOR_STACK_OVERFLOW
设置为:#define configCHECK_FOR_STACK_OVERFLOW 0,禁用栈溢出检测。
port.c的配置
安装中断服务程序
123 | void xPortPendSVHandler( void ) __attribute__( ( naked ) );void xPortSysTickHandler( void );void vPortSVCHandler( void ) __attribute__( ( naked ) ); |
在port.c中找到3个中断服务程序的函数名。
需要把这3个中断服务程序安装到单片机的中断向量表里。
打开/Core/Startup文件夹,中断向量表在启动文件中。
中断向量表从131行开始:
| 原内容 | 替换为 |
|---|---|
| .word SVC_Handler | .word vPortSVCHandler |
| .word PendSV_Handler | .word xPortPendSVHandler |
| .word SysTick_Handler | .word xPortSysTickHandler |
使用其他定时器作为HAL库时间基准
在CubeMX中修改SYS->Timebase Source的来源,不再使用SysTick,更换为其他TIM定时器。
案例:使用移植的FreeRTOS实现2个小灯不同频率闪烁
首先,引用头文件
1234 | /* USER CODE BEGIN Includes *//* USER CODE END Includes */ |
分别编写2个小灯闪烁代码:
12345678910111213141516171819202122232425 | /* USER CODE BEGIN 0 */void vLED1Task(void *pvParameters){ while(1) { HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET); vTaskDelay(pdMS_TO_TICKS(1000)); HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET); vTaskDelay(pdMS_TO_TICKS(1000)); } vTaskDelete(NULL);} void vLED2Task(void *pvParameters){ while(1) { HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET); vTaskDelay(pdMS_TO_TICKS(300)); HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET); vTaskDelay(pdMS_TO_TICKS(300)); } vTaskDelete(NULL);}/* USER CODE END 0 */ |
调用这2个闪烁任务,并启动任务调度器
12345 | /* USER CODE BEGIN 2 */xTaskCreate(vLED1Task, "LED1", 128, NULL, 1, NULL);xTaskCreate(vLED2Task, "LED2", 128, NULL, 1, NULL);vTaskStartScheduler();//启用调度器/* USER CODE END 2 */ |