STM32 HAL库 FreeRTOS内核移植

首先从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.hFreeRTOS 内核的头文件,该头文件必须被引用。-
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_tuint16_tuint32_tuint64_t
无符号8位整数无符号16位整数无符号32位整数无符号64位整数
位宽为8比特位宽为16比特位宽为32比特位宽为64比特
0~2550~655350~约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

能调用FreeRTOS API的最高优先级
因此本项设置为#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定时器。
使用其他定时器作为HAL库时间基准

案例:使用移植的FreeRTOS实现2个小灯不同频率闪烁

首先,引用头文件

1234
/* USER CODE BEGIN Includes */#include "FreeRTOS.h"#include "task.h"/* 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 */
🔍 ×