01. FreeRTOS的简介
一、FreeRTOS的简介
1.1、什么是RTOS
操作系统是允许多个任务 “同时运行” 的,操作系统的这个特性被称为多任务。然而实际上,一个 CPU 核心在某一时刻只能运行一个任务,而操作系统中任务调度器的责任就是决定在某一时刻 CPU 究竟要运行哪一个任务,任务调度器使得 CPU 在各个任务之间来回切换并处理任务,由于切换处理任务的速度非常快,因此就给人造成了一种同一时刻有多个任务同时运行的错觉。
操作系统的分类方式可以由任务调度器的工作方式决定,比如有的操作系统给每个任务分配同样的运行时间,时间到了就切换到下一个任务,Unix 操作系统就是这样的。RTOS 的任务调度器被设计为可预测的,而这正是嵌入式实时操作系统所需要的。在实时环境中,要求操作系统必须实时地对某一个事件做出响应,因此任务调度器的行为必须是可预测的。像 FreeRTOS 这种传统的 RTOS 类操作系统是由用户给每个任务分配一个任务优先级,任务调度器就可以根据此优先级来决定下一刻应该运行哪个任务。
RTOS 全称为 Real Time OS,就是实时操作系统,它将功能分而治之,划分为多个任务,并且通过延时函数进行任务调度。其中,高优先级任务抢占低优先级任务,并且每个任务都有自己的栈空间。

中断可以打断任意任务。
任务可以同等优先级。
1.2、什么是FreeRTOS
FreeRTOS 的名字,可以分为两部分:“Free”和“RTOS”,“Free”就是免费的、自由的、不受约束的意思,“RTOS”全称是 Real Time Operating System。FreeRTOS 采用了 MIT 开源许可,这允许将 FreeRTOS 操作系统用于商业应用,并且不需要公开源代码。此外,FreeRTOS 还衍生出了另外两个操作系统:OpenRTOS 和 SafeRTOS,其中 OpenRTOS 使用了和 FreeRTOS 相同的代码,只是 OpenRTOS 受商业授权保护。
二、任务调度简介
调度器就是使用相关的调度算法来决定当前需要执行哪个任务,FreeRTOS 支持以下任务调度方式:抢占式调度 和 时间片调度 。
- 抢占式调度:主要针对优先级不同的任务,每个任务都有一个优先级,优先级高的任务可以抢占优先级低的任务。
- 时间片调度:主要针对优先级相同的任务,当多个任务的优先级相同且就绪时,任务调度器会根据用户所设置的时间片轮流的运行这些任务。
2.1、抢占式调度
在 FreeRTOS 中,高优先级任务,优先执行,并且高优先级任务不停止,低优先级任务无法执行。被抢占的任务将会进入就绪态。

首先,这里创建三个任务:Task1、Task2、Task3。Task1、Task2、Task3 的优先级分别为 1、2、3;在 FreeRTOS 中任务设置的数值越大,优先级越高,所以 Task3 的优先级最高。
该程序在运行时,首先 Task1 在运行中,在这个过程中 Task2 就绪了,在抢占式调度器的作用下 Task2 会抢占 Task1 的运行。然后,在 Task2 运行过程中,Task3 就绪了,在抢占式调度器的作用下 Task3 会抢占 Task2 的运行。接着,Task3 运行过程中,Task3 阻塞了(系统延时或等待信号量等),此时就绪态中,优先级最高的任务 Task2 执行。随后,Task3 阻塞解除了(延时到了或者接收到信号量),此时 Task3 恢复到就绪态中,抢占 TasK2 的运行。
2.2、时间片调度
同等优先级任务轮流地享有相同的 CPU 时间(可设置), 叫时间片,在 FreeRTOS 中,一个时间片就等于 SysTick 中断周期。没有用完的时间片不会再使用,下次任务得到执行还是按照一个时间片的时钟节拍运行。

首先,这里创建三个任务:Task1、Task2、Task3。Task1、Task2、Task3 的优先级均为 1,即 3 个任务同等优先级。
该程序在运行时,首先 Task1 运行完 1 个时间片后,切换至 Task2 运行。然后,Task2 运行完 1 个时间片后,切换至 Task3 运行。如果在 Task3 运行过程中(还不到 1 个时间片),Task3 阻塞了(系统延时或等待信号量等),此时直接切换到下一个任务 Task1。接着,Task1 运行完 1 个时间片后,切换至 Task2 运行,依次循环运行下去。
注意没有用完的时间片不会再使用,下次任务 Task3 得到执行还是按照 1 个时间片的时钟节拍运行。
三、任务状态
FreeRTOS 中任务共存在 4 种任务状态:运行态、就绪态、挂起态 和 阻塞态。
- 运行态:正在执行的任务,该任务就处于运行态,注意在 STM32 中,同一时间仅一个任务处于运行态。
- 就绪态:如果该任务已经能够被执行,但当前还未被执行,那么该任务处于就绪态。
- 挂起态:如果一个运行态的任务因延迟或等待某一事件发生时被挂起,那么这个任务就处于挂起态。
- 阻塞态:调用函数 vTaskSuspend() 进入挂起态,需要调用解挂函数 vTaskResume() 才能进入就绪态。

被创建的任务,初始状态均为就绪态。
被删除的任务,会转换为休眠态。
仅就绪态可以转变为运行态。
这四种状态中,除了运行态,其他三种任务状态的任务都有其对应的任务状态列表。
- 就绪列表:准备运行的任务将放在就绪列表:pxReadyTaskLists[x],其中 x 代表任务优先级数值。
- 阻塞列表:正在等待延迟超时或挂起的对象超时的任务,将放在 pxDelayedTaskList。
- 挂起列表:当任务等待信号量、事件时,任务将放置在挂起列表 xSuspendedTaskList。
调度器总是在所有处于就绪列表的任务中,选择具有最高优先级的任务来执行。
相同优先级的任务会连接在同一个就绪列表上。
四、FreeRTOS的移植
4.1、FreeRTOS源码的移植
我们可以从 FreeRTOS 的官网上下载源码:https://www.freertos.org。下载后,我们解压压缩包。进入【FreeRTOS】子文件夹中的【FreeRTOS-Kernel】子文件夹就可以查看 FreeRTOS 内核相关的文件。然后,我们将需要的内核文件添加到自己的工程中。

接着,我们要根据自己工程使用的编译器类型添加【portable】文件夹中移植文件。

然后,我们还需要将 FreeRTOS 内存管理的算法也加入到自己的工程中。这里,只添加 heap_4.c 文件即可。

接下来添加 FreeRTOS 源码的头文件路径,需要添加两个头文件路径,其中一个头文件路径就是 FreeRTOS\FreeRTOS-Kernel\include,另外一个头文件路径为 port 文件的路径:FreeRTOS\FreeRTOS-Kernel\portable\GCC\ARM_CM4F(选择对应单片机对应内核的 port 文件添加即可)。
然后,添加 FreeRTOSConfig.h 头文件。FreeRTOSConfig.h 是 FreeRTOS 操作系统的配置文件,FreeRTOS 操作系统是可裁剪的,用户可以根据需求对 FreeRTOS 进行裁剪,裁剪掉不需要用到的 FreeRTOS 功能,以此来节约 MCU中的内存资源。
FreeRTOSConfig.h 头文件获取的第一种途径就是用户自行编写,用户可以根据自己的需求编写 FreeRTOSConfig.h 对FreeRTOS 操作系统进行裁剪。FreeRTOS 官网的在线文档中就详细地对 FreeRTOSConfig.h 中各个配置项进行了描述,网页链接:https://www.freertos.org/a00110.html。
第二种途径就是 FreeRTOS 内核的示例文件。它位于【FreeRTOS】子文件夹中的【FreeRTOS-Kernel】子文件夹中的【examples】子文件夹中【template_configuration】子文件夹中。
4.2、修改SysTick相关的文件
FreeRTOS 系统使用 SysTick 作为任务切换的时基单元,因此,我们需要修改有关 SysTick 的代码。
修改 stm32f4xx_it.c 文件中关于 SysTick 的中断服务函数:

#include "FreeRTOS.h"
#include "task.h"
extern void xPortSysTickHandler(void);
/**
* @brief Systick中断服务函数
*
*/
void SysTick_Handler(void)
{
HAL_IncTick();
// OS开始跑了,才执行正常的调度处理
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
xPortSysTickHandler();
}
}
修改基础工程上的关于延迟初始化函数:
#include "stm32f4xx_hal.h"
#include "FreeRTOS.h"
uint16_t g_frequency_us = 0; // us延时倍乘数
/**
* @brief 延迟初始化函数
*
* @param clock 系统时钟频率,单位为MHz
*/
void Delay_Init(uint16_t clock)
{
uint32_t reload = clock;
SysTick->CTRL = 0; // SysTick控制寄存器清零
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK); // 设置SysTick时钟源为HCLK
g_frequency_us = clock; // 1us定时的计数频率
// 根据delay_ostickspersec设定溢出时间,reload为24位寄存器,最大值:16777216,在168M下,约合0.09986s左右
reload *= 1000000 / configTICK_RATE_HZ;
SysTick->CTRL |= 1 << 1; // 开启SysTick中断
SysTick->LOAD = reload; // 设置计数器的值
SysTick->CTRL |= 1 << 0; // 开启计数器
}
修改微秒级延迟函数:
/**
* @brief 微秒级延迟函数
*
* @param time 要延迟的微秒数
*/
void Delay_us(uint32_t time)
{
uint32_t tick = 0;
uint32_t t_old = 0, t_now = 0, t_count = 0;
uint32_t reload = SysTick->LOAD; // LOAD的值
tick = time * g_frequency_us; // 延迟时间加载
t_old = SysTick->VAL;
while (1)
{
t_now = SysTick->VAL; // tnow用于记录当前的SysTick->VAL 值
if (t_now < t_old)
{
t_count += t_old - t_now; // 在一轮内,t_count加等于t_old到t_now的差值
}
else // SysTick是向下计数的定时器,当VAL值大于t_old时,表示已经到一轮了
{
t_count += reload - t_now + t_old; // 超过一轮内,t_count加等于重装值减t_now到t_old的差值,即VAL-(t_now-t_old)
}
t_old = t_now; // t_old用于记录最近一次的SysTick->VAL值
if (t_count >= tick) // 时间超过或等于要延迟的时间,则定时时间到,退出
{
break;
}
}
}
修改毫秒级延迟函数:
/**
* @brief 毫秒级延迟函数
*
* @param time 要延迟的毫秒数
*/
void Delay_ms(uint32_t time)
{
for (uint32_t i = 0; i < time; i++)
{
Delay_us(1000);
}
}
4.3、修改有关任务切换的文件
在 FreeRTOS 有关任务切换的中断是 SVC 中断、PendSV 中断。这两个中断的中断服务函数在 HAL 库提供的文件中都有定义(STM32F4 对应的文件是 stm32f4xx_it.c 文件)。FreeRTOS 也提供了 SVC 和 PendSV 的中断服务函数,因此需要将 HAL 库提供的这两个中断服务函数注释掉。


然后,我们可以在在 FreeRTOSConfig.h 配置文件中对 FreeRTOS 提供的 SVC 和 PendSV 的中断服务函数进行宏定义替换。
/* FreeRTOS中断服务函数相关定义 */
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler
4.4、FreeRTOS的配置文件
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/* 头文件 */
#include <stdio.h>
#include "stm32f4xx_hal.h"
extern uint32_t SystemCoreClock;
/* 基础配置项 */
#define configUSE_PREEMPTION 1 // 1: 抢占式调度器; 0: 协程式调度器; 无默认需定义
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1 // 1: 使用硬件计算下一个要运行的任务; 0: 使用软件算法计算下一个要运行的任务; 默认: 0
#define configUSE_TICKLESS_IDLE 0 // 1: 使能tickless低功耗模式, 默认: 0
#define configCPU_CLOCK_HZ SystemCoreClock // 定义CPU主频,单位: Hz,无默认需定义
// #define configSYSTICK_CLOCK_HZ (configCPU_CLOCK_HZ / 8)// 定义SysTick时钟频率,当SysTick时钟频率与内核时钟频率不同时才可以定义,单位: Hz,默认: 不定义
#define configTICK_RATE_HZ 1000 // 定义系统时钟节拍频率,单位: Hz,无默认需定义
#define configMAX_PRIORITIES 32 // 定义最大优先级数,最大优先级=configMAX_PRIORITIES-1,无默认需定义
#define configMINIMAL_STACK_SIZE 128 // 定义空闲任务的栈空间大小,单位: Word,无默认需定义
#define configMAX_TASK_NAME_LEN 16 // 定义任务名最大字符数,默认: 16
#define configUSE_16_BIT_TICKS 0 // 1: 定义系统时钟节拍计数器的数据类型为16位无符号数,无默认需定义
#define configIDLE_SHOULD_YIELD 1 // 1: 使能在抢占式调度下,同优先级的任务能抢占空闲任务,默认: 1
#define configUSE_TASK_NOTIFICATIONS 1 // 1: 使能任务间直接的消息传递,包括信号量、事件标志组和消息邮箱,默认: 1
#define configTASK_NOTIFICATION_ARRAY_ENTRIES 1 // 定义任务通知数组的大小,默认: 1
#define configUSE_MUTEXES 1 // 1: 使能互斥信号量,默认: 0
#define configUSE_RECURSIVE_MUTEXES 1 // 1: 使能递归互斥信号量,默认: 0
#define configUSE_COUNTING_SEMAPHORES 1 // 1: 使能计数信号量,默认: 0
#define configUSE_ALTERNATIVE_API 0 // 已弃用!!!
#define configQUEUE_REGISTRY_SIZE 8 // 定义可以注册的信号量和消息队列的个数,默认: 0
#define configUSE_QUEUE_SETS 1 // 1: 使能队列集,默认: 0
#define configUSE_TIME_SLICING 1 // 1: 使能时间片调度,默认: 1
#define configUSE_NEWLIB_REENTRANT 0 // 1: 任务创建时分配Newlib的重入结构体,默认: 0
#define configENABLE_BACKWARD_COMPATIBILITY 0 // 1: 使能兼容老版本,默认: 1
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 0 // 定义线程本地存储指针的个数,默认: 0
#define configSTACK_DEPTH_TYPE uint16_t // 定义任务堆栈深度的数据类型,默认: uint16_t
#define configMESSAGE_BUFFER_LENGTH_TYPE size_t // 定义消息缓冲区中消息长度的数据类型,默认: size_t
/* 内存分配相关定义 */
#define configSUPPORT_STATIC_ALLOCATION 0 // 1: 支持静态申请内存,默认: 0
#define configSUPPORT_DYNAMIC_ALLOCATION 1 // 1: 支持动态申请内存,默认: 1
#define configTOTAL_HEAP_SIZE ((size_t)(10 * 1024)) // FreeRTOS堆中可用的RAM总量,单位: Byte,无默认需定义
#define configAPPLICATION_ALLOCATED_HEAP 0 // 1: 用户手动分配FreeRTOS内存堆(ucHeap),默认: 0
#define configSTACK_ALLOCATION_FROM_SEPARATE_HEAP 0 // 1: 用户自行实现任务创建时使用的内存申请与释放函数,默认: 0
/* 钩子函数相关定义 */
#define configUSE_IDLE_HOOK 0 // 1: 使能空闲任务钩子函数,无默认需定义
#define configUSE_TICK_HOOK 0 // 1: 使能系统时钟节拍中断钩子函数,无默认需定义
#define configCHECK_FOR_STACK_OVERFLOW 0 // 1: 使能栈溢出检测方法1,2: 使能栈溢出检测方法2,默认: 0
#define configUSE_MALLOC_FAILED_HOOK 0 // 1: 使能动态内存申请失败钩子函数,默认: 0
#define configUSE_DAEMON_TASK_STARTUP_HOOK 0 // 1: 使能定时器服务任务首次执行前的钩子函数,默认: 0
/* 运行时间和任务状态统计相关定义 */
#define configGENERATE_RUN_TIME_STATS 0 // 1: 使能任务运行时间统计功能,默认: 0
#if configGENERATE_RUN_TIME_STATS
extern void ConfigureTimeForRunTimeStats(void);
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() ConfigureTimeForRunTimeStats()
extern uint32_t FreeRTOSRunTimeTicks;
#define portGET_RUN_TIME_COUNTER_VALUE() FreeRTOSRunTimeTicks
#endif /* configGENERATE_RUN_TIME_STATS */
#define configUSE_TRACE_FACILITY 1 // 1: 使能可视化跟踪调试,默认: 0
#define configUSE_STATS_FORMATTING_FUNCTIONS 1 // 1: configUSE_TRACE_FACILITY为1时,会编译vTaskList()和vTaskGetRunTimeStats()函数,默认: 0
/* 协程相关定义 */
#define configUSE_CO_ROUTINES 0 // 1: 启用协程,默认: 0
#define configMAX_CO_ROUTINE_PRIORITIES 2 // 定义协程的最大优先级,最大优先级=configMAX_CO_ROUTINE_PRIORITIES-1,无默认configUSE_CO_ROUTINES为1时需定义
/* 软件定时器相关定义 */
#define configUSE_TIMERS 1 // 1: 使能软件定时器,默认: 0
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1) // 定义软件定时器任务的优先级,无默认configUSE_TIMERS为1时需定义
#define configTIMER_QUEUE_LENGTH 5 // 定义软件定时器命令队列的长度,无默认configUSE_TIMERS为1时需定义
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 2) // 定义软件定时器任务的栈空间大小,无默认configUSE_TIMERS为1时需定义
/* 可选函数, 1: 使能 */
#define INCLUDE_vTaskPrioritySet 1 // 设置任务优先级
#define INCLUDE_uxTaskPriorityGet 1 // 获取任务优先级
#define INCLUDE_vTaskDelete 1 // 删除任务
#define INCLUDE_vTaskSuspend 1 // 挂起任务
#define INCLUDE_xResumeFromISR 1 // 恢复在中断中挂起的任务
#define INCLUDE_vTaskDelayUntil 1 // 任务绝对延时
#define INCLUDE_vTaskDelay 1 // 任务延时
#define INCLUDE_xTaskGetSchedulerState 1 // 获取任务调度器状态
#define INCLUDE_xTaskGetCurrentTaskHandle 1 // 获取当前任务的任务句柄
#define INCLUDE_uxTaskGetStackHighWaterMark 1 // 获取任务堆栈历史剩余最小值
#define INCLUDE_xTaskGetIdleTaskHandle 1 // 获取空闲任务的任务句柄
#define INCLUDE_eTaskGetState 1 // 获取任务状态
#define INCLUDE_xEventGroupSetBitFromISR 1 // 在中断中设置事件标志位
#define INCLUDE_xTimerPendFunctionCall 1 // 将函数的执行挂到定时器服务任务
#define INCLUDE_xTaskAbortDelay 1 // 中断任务延时
#define INCLUDE_xTaskGetHandle 1 // 通过任务名获取任务句柄
#define INCLUDE_xTaskResumeFromISR 1 // 恢复在中断中挂起的任务
/* 中断嵌套行为配置 */
#ifdef __NVIC_PRIO_BITS
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4
#endif
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 // 中断最低优先级
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 // FreeRTOS可管理的最高中断优先级
#define configKERNEL_INTERRUPT_PRIORITY (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
#define configMAX_SYSCALL_INTERRUPT_PRIORITY (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
#define configMAX_API_CALL_INTERRUPT_PRIORITY configMAX_SYSCALL_INTERRUPT_PRIORITY
/* FreeRTOS中断服务函数相关定义 */
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler
/* 断言 */
#define vAssertCalled(char, int) printf("Error: %s, %d\r\n", char, int)
#define configASSERT( x ) if( ( x ) == 0 ) vAssertCalled( __FILE__, __LINE__ )
/* FreeRTOS MPU 特殊定义 */
//#define configINCLUDE_APPLICATION_DEFINED_PRIVILEGED_FUNCTIONS 0
//#define configTOTAL_MPU_REGIONS 8
//#define configTEX_S_C_B_FLASH 0x07UL
//#define configTEX_S_C_B_SRAM 0x07UL
//#define configENFORCE_SYSTEM_CALLS_FROM_KERNEL_ONLY 1
//#define configALLOW_UNPRIVILEGED_CRITICAL_SECTIONS 1
/* ARMv8-M 安全侧端口相关定义。 */
//#define secureconfigMAX_SECURE_CONTEXTS 5
#endif /* FREERTOS_CONFIG_H */

浙公网安备 33010602011771号