FreeRTOS源码
下载 -> FreeRTOS文件夹 -> Source文件夹 :
我们需要include文件夹,里面包含了FreeRTOS的头文件。
我们需要portable文件夹,里面包含了FreeRTOS的移植文件。
我们需要list.c、queue.c、tasks.c这三个C文件,分别是列表、队列和任务相关的文件。
portable文件夹
FreeRTOS操作系统归根到底是一个软件层面的东西,那FreeRTOS是如何跟硬件联系在一起的呢?
和硬件联系在一起,说白了就是软件可以操作寄存器,portable文件夹里面的东西就是连接桥梁 。
我们使用Keil开发,因此这里只需要选用Keil的移植文件,我们只需要portable文件夹中的三个:

FreeRTOS移植

工程添加源码
我们将FreeRTOS中的上述源码,先添加至我们的Keil5的工程中(别忘了头文件路径):

增加配置文件
我们之前说了,FreeRTOS是可以被裁剪的,有些用不到的功能,我们可以通过一些宏定义,给他去掉。
在这一步,我们可以直接用官方Demo文件中的FreeRTOSConfig.h文件,你是什么型号的芯片,就在Demo里找对应的文件夹,然后找到这个C文件,直接复制粘贴,直接拿来用。
因为是初学者,我们现在肯定看不懂这个C文件里是什么,而且这个文件是我们自己拿Demo里面的文件,报错了也不知道会怎么改,所以当你不知道怎么办的时候,就要编译你的程序,看你的报错内容,然后针对性的修改配置文件。
钩子函数是FreeRTOS提供的用户自定义函数,用于在特定事件发生时执行特定操作。它们允许用户在不修改内核代码的情况下扩展FreeRTOS的功能。以下是常用的钩子函数:
1. **vApplicationTickHook**:
– 每个系统节拍(tick)发生时调用。
– 用于执行周期性任务。
2. **vApplicationIdleHook**:
– 系统空闲时调用。
– 常用于进入低功耗模式。
3. **vApplicationStackOverflowHook**:
– 当检测到任务堆栈溢出时调用。
– 用于错误处理和调试。
4. **vApplicationMallocFailedHook**:
– 当内存分配失败时调用。
– 用于处理内存不足的情况。
这些钩子函数帮助开发者在关键事件上插入自定义逻辑,提高系统的可调试性和稳定性。
下面是最常见的错误案例:
Undefined symbol vApplicationTickHook (referred from tasks.o)
Undefined symbol vApplicationStackOverflowHook (referred from tasks.o)
Undefined symbol vApplicationIdleHook (referred from tasks.o)
Undefined symbol vApplicationMallocFailedHook (referred from heap_4.o)
这是因为缺少钩子函数的定义,解决方法很简单,只需要在FreeRTOSConfig.h中找到对应的宏定义,然后将其置0。
修改SYSTEM文件
这里所谓修改system文件(sys.c、usart.c、delay.c),无非就是在自己写的文件里进行增删查改,如果你的系统之中没有这些文件,就不必更改。如果你的代码是拷贝别人的代码,我推荐你删除,现在也用不到,等用到了自己再写,这样更能锻炼自身的技术。
同时,在我后续的文章中,会逐一讲解要怎么写这些文件,现在这里我们用不到,我们只是移植一个最基础的工程。
修改中断相关文件
在stm324xx_it.c(你是什么芯片,就找自己对应的芯片的中断文件),注释掉下面这三个函数,为什么要注释掉这三个函数?
因为在我们刚才移植的FreeRTOS中,已经定义了这三个中断函数,老的就不再用了,不删除会导致重定义。

添加应用程序
上述步骤我们移植完成之后,就需要运行一下面的Demo,来看一下是否正常。
#include "FreeRTOS.h"
#include "task.h"
#include "stm32f4xx.h" // Device header
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define LED0_TASK_PRIO 2
//任务堆栈大小
#define LED0_STK_SIZE 50
//任务句柄
TaskHandle_t LED0Task_Handler;
//任务函数
void led0_task(void *pvParameters);
//任务优先级
#define LED1_TASK_PRIO 3
//任务堆栈大小
#define LED1_STK_SIZE 50
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);
void GPIO_INIT()
{
// 使能GPIOF时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
// 初始化结构体
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; // 替换为实际引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; // 输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 速度
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // 推挽输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; // 无上拉下拉
GPIO_Init(GPIOF, &GPIO_InitStructure);
}
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建LED0任务
xTaskCreate((TaskFunction_t )led0_task,
(const char* )"led0_task",
(uint16_t )LED0_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED0_TASK_PRIO,
(TaskHandle_t* )&LED0Task_Handler);
//创建LED1任务
xTaskCreate((TaskFunction_t )led1_task,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//LED0 任务函数
void led0_task(void *pvParameters)
{
while(1)
{
GPIO_SetBits(GPIOF,GPIO_Pin_9);
vTaskDelay(500);
GPIO_ResetBits(GPIOF,GPIO_Pin_9);
vTaskDelay(500);
}
}
//LED1 任务函数
void led1_task(void *pvParameters)
{
while(1)
{
GPIO_ResetBits(GPIOF,GPIO_Pin_10);
vTaskDelay(300);
GPIO_SetBits(GPIOF,GPIO_Pin_10);
vTaskDelay(300);
}
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组 4
GPIO_INIT();
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
问题与解决
在我移植FreeRTOS成功后,运行了Demo程序,LED闪烁,但是出现了一个问题,那就是闪烁频率过慢,不是FreeRTOS设置的1s,下面我给出两个解决方法:
闪烁问题,就直接定位到了是时钟的问题,当确保在FreeRTOSConfig.h文件中:
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
确保这个宏定义是1000,不建议通过修改 configTICK_RATE_HZ 来调整任务的延时,这是极其不规范的做法!
configTICK_RATE_HZ 是 FreeRTOS 中的一个配置项,表示系统的时钟节拍频率,即每秒产生的时钟节拍(Tick)的次数。 单位是Hz(赫兹),即每秒的次数。
如果这里我们确实是1000,那么可以肯定,是系统时钟的问题了,查看系统时钟的代码:
查看SystemInit()函数,找到SetSysClock(),对SetSysClock()函数进行替换或者修改,下面是两种解决方法:
方法1:对SetSysClock()函数进行更改:
将宏定义更改为下方的参数,即可修改正确的系统时钟。
#define PLL_M 8
#define PLL_N 336
#define PLL_P 2
#define PLL_Q 7
方法2:对SetSysClock()函数进行替换:
直接清空SetSysClock()函数中的内容,置换成以下函数(我的是STM32F40X系列,不是同一系列的,注意修改参数):
void SetSysClock(void)
{
/* 1. 启用 HSE */
RCC_HSEConfig(RCC_HSE_ON);
if (RCC_WaitForHSEStartUp() == SUCCESS)
{
/* 2. 设置电源调节器电压输出级别,以适应更高的系统频率 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
PWR_MainRegulatorModeConfig(PWR_Regulator_Voltage_Scale1);
/* 3. 配置 Flash 所需的等待状态 */
FLASH_SetLatency(FLASH_Latency_5);
FLASH_PrefetchBufferCmd(ENABLE);
FLASH_InstructionCacheCmd(ENABLE);
FLASH_DataCacheCmd(ENABLE);
/* 4. 配置 PLL */
RCC_PLLConfig(RCC_PLLSource_HSE, 8, 336, 2, 7); // 8MHz 外部晶振
/* 5. 启用 PLL */
RCC_PLLCmd(ENABLE);
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
/* 6. 选择 PLL 作为系统时钟源 */
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while (RCC_GetSYSCLKSource() != 0x08);
/* 7. 配置 AHB、APB 时钟分频器 */
RCC_HCLKConfig(RCC_SYSCLK_Div1); // AHB 时钟 = 系统时钟 / 1 = 168MHz
RCC_PCLK1Config(RCC_HCLK_Div4); // APB1 时钟 = AHB 时钟 / 4 = 42MHz
RCC_PCLK2Config(RCC_HCLK_Div2); // APB2 时钟 = AHB 时钟 / 2 = 84MHz
/* 8. 更新 SystemCoreClock 变量 */
SystemCoreClockUpdate();
}
else
{
/* HSE 启动失败,处理错误 */
while (1);
}
}
系统配置文件
FreeRTOS的系统配置文件是FreeRTOSConfig.h,我们来详细介绍一下这个文件。
这个文件是对FreeRTOS的功能进行配置和裁剪,以及API函数的使能等。
我们在这个文件里,可以通过宏定义,来裁剪掉我们不使用的功能,这就是裁剪。
之前也说过,我们的调度器算法有三种:抢占式、协作式、时间片这三种,我们可以通过该文件来配置,看用哪种算法,这就是配置。
API函数的使能和失能,有些函数用得到,就使能,用不到,就失能;这样可以大大节省我们的空间。
这些宏定义可以分为三大类:
“INCLUDE”开头的 : 配置FreeRTOS中可选的API函数。
“config”开头的: 完成FreeRTOS的功能配置和裁剪。
其他配置项 : PendSV宏定义、SVC宏定义。
configUSE_PREEMPTION :1是抢占式调度,0是协程式调度。
configUSE_PORT_OPTIMISED_TASK_SELECTION:计算下一个任务是哪个,一种是硬件计算方式(特殊方式),另一种是软件计算方式(通用方式)。但是软件方式效率比较低,硬件方式效率高。(STM32支持硬件计算方式)
configUSE_TICKLESS_IDLE : 1就是使能tickless低功耗模式, 默认是0,不使能。
configCPU_CLOCK_HZ : SystemCoreClock 是系统时钟频率,我的是168MHz。
configSYSTICK_CLOCK_HZ : (configCPU_CLOCK_HZ / 8) ,这个是只有F1才会使用这个宏,F4、F7、H7这个是不用的,这是不同系列的唯一区别。SysTick时钟频率(时钟源),当SysTick时钟频率与内核时钟频率不同时才可以定义。
configTICK_RATE_HZ :定义系统时钟节拍频率, 单位: Hz, 无默认需定义 ,我们这里设置1000,就是1000Hz,计数一次就是1ms。
configMAX_PRIORITIES :最大优先级数量,硬件的话就是32。
configMINIMAL_STACK_SIZE : 空闲任务的堆栈大小,单位是字(Word)。我们CPU是不能闲起来的,所以会提前创建一个空闲任务。
configMAX_TASK_NAME_LEN :任务名字的最大字符。一般我们选择16。
configUSE_16_BIT_TICKS:用于定义系统节拍计数器的数据类型,为1的时候就是16位无符号整型;为0的时候,就是32位无符号整型。STM32是32位的,所以我们定义为0,32位无符号整型。
configIDLE_SHOULD_YIELD: 当为1时,使能在抢占式调度下,同优先级的任务能抢占空闲任务。比如我们空闲任务时最低优先级的,也就是0,那么如果我们也创建了一个优先级为0的任务,我们可以抢占空闲任务。
configUSE_TASK_NOTIFICATIONS:使能任务间直接的消息传递,包括信号量、事件标志组和消息邮箱, 默认: 1。
configTASK_NOTIFICATION_ARRAY_ENTRIES:使能任务间直接的消息传递,包括信号量、事件标志组和消息邮箱, 默认: 1。
configUSE_MUTEXES:使能互斥信号量, 默认: 0 。
configUSE_RECURSIVE_MUTEXES:使能递归互斥信号量, 默认: 0 。
configUSE_COUNTING_SEMAPHORES :1: 使能计数信号量, 默认: 0 。
configQUEUE_REGISTRY_SIZE :定义可以注册的信号量和消息队列的个数, 默认: 0 。
configUSE_QUEUE_SETS :1: 使能队列集, 默认: 0 。
configUSE_TIME_SLICING : 1: 使能时间片调度, 默认: 1 。
#define configUSE_NEWLIB_REENTRANT 0 /* 1: 任务创建时分配Newlib的重入结构体, 默认: 0 */
#define configENABLE_BACKWARD_COMPATIBILITY 0 /* 1: 使能兼容老版本, 默认: 1 ,如果设置1,就说明你兼容一些老版本的API函数*/
钩子函数,所谓钩子函数其实就是回调函数,钩子函数里面的内容,就是用户自己实现了。