1. 简介
1.1 RTOS简介
RTOS(实时操作系统)是指一类系统,如 FreeRTOS,uC/OS,RTX,RT-Thread 等,都是 RTOS 类操作系统。
FreeRTOS 由美国的 Richard Barry 于 2003 年发布。
FreeRTOS 于 2017 年被亚马逊收购,改名为 AWS FreeRTOS。
1.2 FreeRTOS优势
FreeRTOS 通过 MIT 开源许可免费分发,包括一个内核和一组不断丰富的 IoT 库,适用于所有行业领域。FreeRTOS 的构建突出可靠性和易用性。
-
开源和免费:FreeRTOS是一款开源的RTOS,采用MIT许可证发布,可以免费使用、修改和分发。
-
轻量级设计:FreeRTOS注重轻量级设计,适用于资源受限的嵌入式系统,不占用过多内存和处理器资源。
-
广泛应用:FreeRTOS在嵌入式领域得到广泛应用,包括工业自动化、医疗设备、消费电子产品、汽车电子等。
-
多平台支持:FreeRTOS的设计注重可移植性,可以轻松地移植到不同的硬件平台,支持多种处理器架构。
-
丰富的功能:提供了多任务调度、任务通信、同步等功能,适用于复杂的嵌入式应用场景。
简单来说:使用人数多,轻量级,开源免费,兼容性强,功能丰富。
2. FreeRTOS基础知识
2.1 多任务处理
使用多任务操作系统可以简化原本复杂的软件应用程序的设计:
-
操作系统的多任务处理和任务间通信功能允许将复杂的应用程序 划分为一组更小且更易于管理的任务。
-
这种划分可以简化软件测试,确保团队分工明确,并促进代码复用。
-
复杂的时序和排序细节将由 RTOS 内核负责,从而减轻了应用程序代码的负担。
简单来说:FreeRTOS可以帮助我们把复杂的项目细分为一小块一小块。
2.2 多任务处理与并发
常规单核处理器一次只能执行一个任务,但多任务操作系统可以快速切换任务, 使所有任务看起来像是同时在执行。下图展示了 三个任务相对于时间的执行模式。任务名称用不同颜色标示,并写在左侧。时间从左向右移动, 彩色线条显示在特定时间执行的任务。上方展示了所感知的并发执行模式, 下方展示了实际的多任务执行模式。
简单理解:单核就是一个厕所,多核就是多个厕所。如果只有一个厕所,同一个时间只能一个人上厕所。但是想要同时上厕所,通过调度多人快速轮循上厕所看起来像是多人一起上厕所。
2.3 任务调度
由上可知想要看起来像多人上厕所,就需要一个管理人员负责调度,所以任务调度器就出现了。
调度器负责决定在任何特定时间哪个人去上厕所。
FreeRTOS 默认使用固定优先级的抢占式调度策略,对同等优先级的任务执行时间片轮询调度:
-
抢占式调度:FreeRTOS采用抢占式调度方式,允许更高优先级的任务在任何时刻抢占正在执行的低优先级任务。这确保了高优先级任务能够及时响应,并提高了系统的实时性。
-
时间片轮询:在相同优先级的任务之间,FreeRTOS采用时间片轮转策略。每个任务执行一个时间片,如果有其他同优先级的任务等待执行,则切换到下一个任务。这有助于公平地分配CPU时间。
但是并不是说高优先级的任务会一直执行,导致低优先级的任务无法得到执行。如果高优先级任务等待某个资源(延时或等待信号量等)而无法执行,调度器会选择执行其他就绪的高优先级的任务。
简单理解:坐飞机的话分为vip通道和普通通道,你是vip说明你优先级高,你可以先上飞机。这就是抢占式调度。时间片轮训类似A,B,C三个人同时想上厕所(优先级相同),A先去拉一会(拉一个时间片)然后出出来,B再去拉一会(拉一个时间片)然后出出来,C再去拉一会(拉一个时间片)然后出出来,然后一直循环
2.4 任务状态
FreeRTOS中任务共存在4种状态:
-
运行态:当任务实际执行时,它被称为处于运行状态。如果运行 RTOS 的处理器只有一个内核, 那么在任何给定时间内都只能有一个任务处于运行状态。注意在STM32中,同一时间仅一个任务处于运行态。
-
就绪态:准备就绪任务指那些能够执行(它们不处于阻塞或挂起状态), 但目前没有执行的任务, 因为同等或更高优先级的不同任务已经处于运行状态。
-
阻塞态:如果任务当前正在等待延时或外部事件,则该任务被认为处于阻塞状态。
-
挂起态:类似暂停,调用函数 vTaskSuspend() 进入挂起态,需要调用解挂函数vTaskResume()才可以进入就绪态。
只有就绪态可转变成运行态,其他状态的任务想运行,必须先转变成就绪态。转换关系如下:
这四种状态中,除了运行态,其他三种任务状态的任务都有其对应的任务状态列表:
-
就绪列表:pxReadyTasksLists[x],其中x代表任务优先级数值。
-
阻塞列表:pxDelayedTaskList。
-
挂起列表:xSuspendedTaskList。
每种状态都维护了一个列表List(其实用的链表):
就绪态是特殊的:
① 维护一个32bit的变量,对应优先级有任务,对应的bit位置1,方便查找;
② 每一个优先级,都单独维护了一个列表(链表),相同优先级的任务在同一个列表中
代码实现:如果想使用状态机实现一个音乐播放器
char status = 'C';
switch (status)
case 'A' :
播放处理;
if(暂停按键) status = 'B';
if(停止按键) status = 'C';
case 'B' :
暂停处理;
if(停止按键) status='C';
if(播放按键) status='A';
case 'C' :
停止处理;
if(播放按键) status = 'A';
2.5 上下文切换
当一个任务执行时,它会利用处理器/微控制器寄存器,并像其他程序一样访问 RAM 和 ROM。这些资源(处理器寄存器,堆栈等)一起组成了任务执行上下文
一个任务在即将执行将两个处理器寄存器内包含的数值相加之前被挂起。 当该任务被挂起时,其他任务会执行,还可能会修改处理器寄存器的数值。恢复时, 该任务不会知道处理器寄存器已经被修改过了——如果它使用经修改过的数值, 那么求和会得到一个错误的数值。
为了防止这种类型的错误,任务在恢复时必须有一个与挂起之前相同的上下文 。通过在任务挂起时保存任务的上下文,操作系统内核负责确保上下文保持不变。任务恢复时,其保存的上下文在执行之前由操作系统内核恢复。
保存被挂起的任务的上下文和恢复被恢复的任务的上下文的过程被称为 上下文切换。
- 将 TaskA在相应的处理器寄存器中的上下文保存到其任务堆栈中。
- 将 TaskB 的上下文从其任务堆栈中恢复到相应的处理器寄存器中
在需要切换任务的时候进行上下文切换,真正执行上下文切换是在PendSV的ISR中处理的。
在FreeRTOS中有以下几个情况会触发PendSV异常产生切换:
- RTOS 滴答中断:会处理就绪列表,判断是否要切换任务(包括抢占式、时间片轮转)。
- 任务执行完毕:主动调用任务切换函数进行强制切换。
简单来说:切换上下文就是切换任务,任务之间相互转换到CPU的过程中难免会遇到变量名相同的情况,为保证数据的唯一性,采用上下文切换的方式进行更稳定保存现场。
3. FreeRTOS移植
3.1 简单介绍源码结构
3.1.1 源码下载地址
官网地址:FreeRTOS™ - FreeRTOS™
3.1.2 结构介绍
所用FreeRTOS版本为202212.01版本
名称 | 描述 |
FreeRTOS | FreeRTOS内核 |
FreeRTOS-Plus | FreeRTOS组件,一般我们会选择使用第三方的组件 |
tools | 工具 |
GitHub-FreeRTOS-Home | FreeRTOS的GitHub仓库链接 |
Quick_Start_Guide | 快速入门指南官方文档链接 |
Upgrading-to-FreeRTOS-xxx | 升级到指定FreeRTOS版本官方文档链接 |
History.txt | FreeRTOS历史更新记录 |
其他 | 其他 |
FreeRTOS文件夹结构
名称 | 描述 |
Demo | FreeRTOS演示例程,支持多种芯片架构、多种型号芯片 |
License | FreeRTOS相关许可 |
Source | FreeRTOS源码,最重要的文件夹 |
Test | 公用以及移植层测试代码 |
Source文件夹结构如下
名称 | 描述 |
include | 内包含了FreeRTOS的头文件 |
portable | 包含FreeRTOS移植文件:与编译器相关、编译环境 |
croutine.c | 协程相关文件 |
event_groups.c | 事件相关文件 |
list.c | 列表相关文件 |
queue.c | 队列相关文件 |
stream_buffer.c | 流式缓冲区相关文件 |
tasks.c | 任务相关文件 |
timers.c | 软件定时器相关文件 |
portable文件夹结构
FreeRTOS操作系统归根到底是一个软件层面的东西,需要跟硬件联系在一起,portable文件夹里面的东西就是连接桥梁。
3.2 FreeRTOS在基于HAL库项目中移植步骤
Demo的芯片型号为: STM32F103ZET6
开发环境为:Clion+STMcubeMX
创建案例只添加了以下功能:按钮与led灯
3.2.1 目录添加源码文件
我们移植需要以下内核文件
接着我们点进portable 根据我们所用的开发环境进行选择
最后一个就是FreeRTOSConfig.H文件我这里是复制了一个dome里面的栗子
完整就是这样
另外我们还需要在CMakeLists.txt里把我们加进来的文件配置进去
3.2.2 系统配置文件修改
FreeRTOSConfig.h中添加如下3个配置:
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler
#define INCLUDE_xTaskGetSchedulerState 1
3.2.3 修改stm32f1xx_it.c
引入头文件
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "FreeRTOS.h"
#include "task.h"
/* USER CODE END Includes */
注释掉2个函数
// void SVC_Handler(void)
// {
// }
// void PendSV_Handler(void)
// {
// }
添加SysTick时钟中断服务函数
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
extern void xPortSysTickHandler(void);
/* USER CODE END PV */
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
/* USER CODE BEGIN SysTick_IRQn 1 */
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
xPortSysTickHandler();
}
/* USER CODE END SysTick_IRQn 1 */
}
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
extern void xPortSysTickHandler(void);
/* USER CODE END PV */
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
/* USER CODE BEGIN SysTick_IRQn 1 */
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
xPortSysTickHandler();
}
/* USER CODE END SysTick_IRQn 1 */
}
注意:HAL本身和FreeRTOS都默认依赖SysTick,可能出现卡死的问题。
为了保险起见,可以考虑在SYS选择HAL时钟源的时候换成其他的,并且中断优先级设为较高,比如1。(详情请看3.2 在cube里的配置)
3.3 系统配置文件说明
FreeRTOSConfig.h 配置文件作用:对FreeRTOS的功能进行配置和裁剪,以及API函数的使能等。
官网中文说明:定制 - FreeRTOS™
整体的配置项可以分为三类:
-
INCLUDE开头:一般是“INCLUDE_函数名”,函数的使能,1表示可用,0表示禁用。
-
config开头:FreeRTOS的一些功能配置,比如基本配置、内存配置、钩子配置、中断配置等。
-
其他配置:PendSV宏定义、SVC宏定义。
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/* 头文件 */
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include <stdint.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
#include "./BSP/TIMER/btim.h"
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() ConfigureTimeForRunTimeStats()
extern uint32_t FreeRTOSRunTimeTicks;
#define portGET_RUN_TIME_COUNTER_VALUE() FreeRTOSRunTimeTicks
#endif
#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 */
3.4 FreeRTOS数据类型
不多说直接看源码