FreeRTOS(MDK、STM32CUBEIDE)学习笔记(合集),超详细!!!

FreeRTOS(MDK、STM32CUBEIDE)学习笔记(合集),超详细!!!

文章目录



FreeRTOS学习笔记系列

【FreeRTOS(MDK、STM32CUBEIDE)学习笔记(合集),超详细!!!】
【FreeRTOS学习笔记(一)—— 裸机和RTOS,Freertos移植(MDK),stm32cubeIDE使用Freertos】
【FreeRTOS学习笔记(二)—— 任务,挂起,临界区中断】
【FreeRTOS学习笔记(三)—— 消息队列,信号量,事件标志组,任务通知】
【FreeRTOS学习笔记(四)—— 延时函数,列表,软件定时器,低功耗模式,内存管理】



开胃小菜(前缀后缀)

前缀x和v

FreeRTOS 的一些函数前缀x,而有一些前缀v,到底是什么意思呢
就比如任务的创建和删除

API函数描述
xTaskCreate()动态方式创建任务
xTaskCreateStatic()静态方式创建任务
vTaskDelete()删除任务

x代表函数返回值为一个BaseType_t类型,用于辨别是否创建成功
v的含义是void,即没有返回值

后缀FromISR

“FromISR” 后缀是在 中断函数 中专用的API函数
在中断服务函数里边需调用FreeRTOS的API函数,必须使用带“FromISR”后缀的函数


1、裸机与RTOS

裸机与RTOS的区别

用两个事情作比喻:打游戏和跟对象聊天
裸机:游戏和对象只能选一个,打游戏怎么能分心呢

在这里插入图片描述

RTOS:小孩子才做选择,我两个都要,边打游戏边聊天
在这里插入图片描述

前后台系统(裸机)

裸机又称为前后台系统,前台系统指的中断服务函数,后台系统指的while()大循环,即应用程序
1、实时性差:(应用程序)轮流执行
2、delay空等待:CPU不执行其他代码
3、结构臃肿:实现功能都放在一个无限循环里面
在这里插入图片描述


实时操作系统(RTOS)

RTOS全称为:Real Time OS,就是实时操作系统,强调的是:实时性
1、分而治之:实现功能划分为多个任务
2、延时函数:任务调度,delay的时候去干别的事,不空等
3、抢占式:高优先级任务抢占低优先级任务
4、任务堆栈:每个任务都有自己的栈空间
在这里插入图片描述


2、FreeRTOS简介

更加详细的介绍可以看往期文章【嵌入式实时操作系统(RT-Thread、FreeRTOS、UCOSIII)】

FreeRTOS 是一个免费的嵌入式实时操作系统

  1. 免费开源:FreeRTOS完全免费且开源,无潜在商业风险,无须担心
  2. 高度可裁剪:其核心代码精炼至约9000行,集中分布在3个.c文件中,方便定制和优化
  3. 使用简便:FreeRTOS的用户界面友好,易于理解和操作。
  4. 优先级无限制:任务优先级分配自由,多个任务共享同一优先级
  5. 无限任务数量:理论上,FreeRTOS支持创建无限数量的实时任务,没有软件限制(但受硬件限制)
  6. 多样化的任务调度:支持抢占式、协程式和时间片轮转等多种任务调度方式

3、Freertos调度方式

抢占式——优先级不同的任务

抢占式调度主要是针对优先级不同的任务
每个任务都有一个优先级,优先级高的任务可以抢占优先级低的任务。
在这里插入图片描述

时间片轮转——优先级相同的任务

时间片调度主要针对优先级相同的任务
同等优先级任务轮流地享有相同的 CPU 时间, 叫时间片
当多个任务的优先级相同时,任务调度器会在每一次系统时钟节拍到的时候切换任务。

在FreeRTOS中,一个时间片(Freertos不可设置)就等于 SysTick 中断周期(可设置)
一个时间片大小,取决为滴答定时器中断周期
SysTick 中断周期是可以设置的,可以间接更改时间片

在这里插入图片描述
如果Task运行过程中(还不到一个时间片)阻塞了(系统延时或等待信号量等),直接切换到下个Task
注意没有用完的时间片不会再使用

协程式——不更新

协程式调度当前执行任务将会一直运行,同时高优先级的任务不会抢占低优先级任务
FreeRTOS部分版本还支持,但是官方已经表示不再更新协程式调度

PS:Freertos的四个任务状态

在STM32中,同一时间仅一个任务处于运行态
仅就绪态可转变成运行态,其他状态的任务想运行,必须先转变成就绪态

在这里插入图片描述
就绪态、阻塞态、挂起态的任务都有其对应的任务状态列表
就绪列表pxReadyTasksLists[x]
阻塞列表pxDelayedTaskList[x]
挂起列表xSuspendedTaskList[x]
x:表示优先级


4、Freertos移植(MDK)

官网直接下载就行:https://www.freertos.org/


源码文件夹介绍

解压压缩包,可以看到一堆文件,文件如下表所示

文件/文件夹描述
FreeRTOSFreeRTOS内核
FreeRTOS-PlusFreeRTOS组件
tools工具
GitHub-FreeRTOS-HomeFreeRTOS的GitHub仓库链接
Quick_Start_Guide快速入门指南官方文档链接
Upgrading-to-FreeRTOS-xxx升级到指定FreeRTOS版本官方文档链接
History.txtFreeRTOS历史更新记录

FreeRTOS-Plus包含了TCP,UDP,MQTT等一些组件,但是这些组件一般用三方
最核心的文件夹内就是FreeRTOS——FreeRTOS内核

文件/文件夹描述
DemoFreeRTOS演示例程
LicenseFreeRTOS相关许可
SourceFreeRTOS源码
Test公用以及移植层测试代码

其中最重要的是Source——FreeRTOS源码

文件/文件夹描述
include内包含了FreeRTOS的头文件
portable内包含了FreeRTOS的移植文件
croutine.c协程相关文件
event_groups.c事件相关文件
list.c列表相关文件
queue.c队列相关文件
stream_buffer.c流式缓冲区相关文件
tasks.c任务相关文件
timers.c软件定时器相关文件

其中必要的有include,portable,list.c,queue.c,tasks.c

FreeRTOS操作系统归根到底是软件层面的
portable文件夹里面的东西就是连接桥梁

portable文件夹里面文件很多
在这里插入图片描述

用MDK编辑器的重要文件如下

名称描述
Keil指向RVDS文件夹
RVDS不同内核芯片的移植文件
MemMang内存管理文件

动手移植

这些文件从哪来?
当然还有其他的文档整理方式
在这里插入图片描述


Source文件夹

Source文件夹——FreeRTOS源码就是要移植的文件
前面所说的9个文件/文件夹,文件就不多说,文件夹再仔细看

文件/文件夹描述
include内包含了FreeRTOS的头文件
portable内包含了FreeRTOS的移植文件
croutine.c协程相关文件
event_groups.c事件相关文件
list.c列表相关文件
queue.c队列相关文件
stream_buffer.c流式缓冲区相关文件
tasks.c任务相关文件
timers.c软件定时器相关文件

portable文件夹

接着,就是我们的链接桥梁 portable——内包含了FreeRTOS的移植文件
前文也讲到了用MDK编辑器的重要文件如下

名称描述
Keil指向RVDS文件夹
RVDS不同内核芯片的移植文件
MemMang内存管理文件

Keil文件夹

里面就只有一个 “See-also-the-RVDS-directory.txt”,所以也没什么用


RVDS文件夹

不同内核芯片的移植文件(链接桥梁)

STM32系列Port文件所在文件夹
STM32F1(ARMCC)RVDS/ARM_CM3
STM32F4/STM32G4(ARMCC)RVDS/ARM_CM4F
STM32F7(ARMCC)RVDS/ARM_CM7/r0p1
STM32H7(ARMCC)RVDS/ARM_CM7/r0p1
STM32H5(ARMClang)GCC/ARM_CM33_NTZ

里面都是这两个文件
在这里插入图片描述


MemMang文件夹

内存管理算法
在这里插入图片描述
一般使用的是 “heap_4.c”

这就是我们常见的这些文件从哪里来
在这里插入图片描述


添加 FreeRTOSConfig.h 文件

FreeRTOSConfig.h 配置文件作用:对FreeRTOS的功能进行配置和裁剪,以及API函数的使能等

FreeRTOSConfig.h 是 FreeRTOS 操作系统的配置文件
FreeRTOS 操作系统是可裁剪的,用户可以根据需求对 FreeRTOS 进行裁剪
裁剪掉不需要用到的 FreeRTOS 功能,节约 MCU中内存资源。

INCLUDE前缀:使能FreeRTOS中所需的API函数
在这里插入图片描述
config前缀:FreeRTOS的功能配置和裁剪
在这里插入图片描述


FreeRTOSConfig.h获取途径

途径1:官方历程里拿来用,在文件FreeRTOS——Demo里(可能不靠谱)
途径2:找个靠谱公司的嵌入式历程移植,比如说正点原子
途径3:自己写,官网内核 > 开发者文档 > FreeRTOSConfig.h > 定制 里面介绍了各个功能

看过 “FreeRTOSConfig.h” 就会知道,其实也并没有多难理解
文章下面有解释每个宏的意义
在这里插入图片描述
在这里插入图片描述


移植好了别忘了加路径啊!!!!!!!!

在这里插入图片描述


5、stm32cubeide创建Freertos工程

RCC模式和配置(根据实际情况)

在这里插入图片描述

系统模式与配置

在这里插入图片描述

烧录/调试选择所需的烧录/调试方式
时钟则采用一个定时器作为时钟信号,如果使用Systick,会报如下警告

在这里插入图片描述


FREERTOS模式和配置


Freertos接口(CMSIS)

没有特殊需求的选择接口2
在这里插入图片描述
PS:CMSIS(Cortex Microcontroller Software Interface Standard)
CMSIS_V1和CMSIS_V2版本主要影响的是底层硬件抽象层的标准接口。
分别被称为CMSIS_CORE和CMSIS 5。

  • CMSIS V1 (CMSIS_CORE)
    这是早期的版本,为Cortex-M处理器提供了一个标准的软件接口
    包括了内核外设访问层(CPAL)、调试和DWT(Data Watchpoint and Trace)等功能。
    它是FreeRTOS和其他RTOS的基础,确保了不同厂商的Cortex-M微控制器能够使用统一的API来访问硬件资源。

  • CMSIS V2 (CMSIS 5)
    这是CMSIS的较新版本,引入了一些新的特性和改进
    针对特定系列的STM32增加了额外组件,提供更多的功能和更好的性能。比如DFPs
    CMSIS 5还改进了调试和跟踪机制,增强了与开发工具的兼容性。

参数配置(Config parameters)

在这里插入图片描述
下面是 参数配置(Config parameters) 各项的意义

配置参数意义默认选项
APIFreeRTOS APIAP接口CMSIS V2
VersionsFreeRTOS versionFreeRTOS版本10.0.1
(版本)CMSIS-RTOS versionCMSIS-RTOS版本2
Kernel settingsUSE_PREEMPTION使能抢占式调度器(Disable: 协程式调度器)Enabled
(内核设置)CPU CLOCK HZCPU主频SystemCoreClock
TICK RATE_HZ系统时钟节拍频率1000
MAX PRIORITIES定义最大优先级数56
MINIMAL STACK SIZE定义空闲任务的栈空间大小128 Words
MAX TASK NAME LEN定义任务名最大字符数16
USE_16_BIT_TICKS定义系统时钟节拍计数器的数据类型为16位无符号数Disabled
IDLE SHOULD YIELD使能在抢占式调度下,同优先级的任务能抢占空闲任务Enabled
USE MUTEXES使能互斥信号量Enabled
USE RECURSIVE MUTEXES使能递归互斥信号量Enabled
USE COUNTING SEMAPHORES使能计数信号量Enabled
QUEUE REGISTRY SIZE定义可以注册的消息量和消息队列的个数8
USE APPLICATION TASK TAG使能启用应用程序任务堆栈监视Disabled
ENABLE BACKWARD COMPATIBILITY使能兼容老版本Enabled
USE PORT OPTIMISED TASK SELECTION使用硬件计算下一个要运行的任务Disabled
USE TICKLESS IDLE使能idleless低功耗模式Disabled
USE TASK NOTIFICATIONS使能任务间直接的消息传递包括信号量、事件标志组和消息邮箱Enabled
RECORD STACK HIGH ADDRESS记录任务堆栈的高地址Disabled
Memory management settingsMemory Allocation内存分配方式(默认动态/静态)Dynamic / Static
(内存管理设置)TOTAL HEAP SIZEFreeRTOS堆中可用的RAM总量3072 Bytes
Memory Management scheme内存管理方案(第4章算法)heap_4
Hook function related definitionsUSE IDLE HOOK使能空闲任务钩子函数Disabled
(钩子函数相关定义)USE TICK HOOK使能系统时钟节拍中断钩子函数Disabled
USE MALLOC FAILED_HOOK使能动态内存申请失败钩子函数Disabled
USE DAEMON TASK STARTUP_HOOK使能定时器服务任务首次执行前的钩子函数Disabled
CHECK FOR STACK OVERFLOW使能栈溢出检测方法Disabled
Run time and task stats gathering related definitionsGENERATE RUN TIME STATS使能任务运行时间统计功能Disabled
(收集相关定义的运行时和任务统计信息)USE TRACE FACILITY使能可视化跟踪调试Enabled
USE STATS FORMATTING FUNCTIONS使能编译统计格式化函数Disabled
Co-routine related definitionsUSE CO ROUTINES启用协程Disabled
(协例程相关定义)MAX CO ROUTINE PRIORITIES定义协程的最大优先级2
Software timer definitionsUSE TIMERS使能软件定时器Enabled
(软件定时器定义)TIMER TASK PRIORITY定义软件定时器任务的优先级2
TIMER QUEUE LENGTH定义软件定时器命令队列的长度10
TIMER TASK STACK DEPTH定义软件定时器任务的栈空间大小10
Interrupt nesting behaviour configurationLIBRARY LOWER INTERRUPT PRIORITY中断最低优先级15
(中断嵌套行为配置)LIBRARY MAX SYSCALL INTERRUPT PRIORITYFreeRTOS可管理的最高中断优先级5

任务创建(Tasks) 和 消息队列(Queues)

任务 和 消息队列的创建,在同一个页面
在这里插入图片描述
队列又称消息队列,是一种常用于任务间通信的数据结构
队列可以在任务与任务间、 中断和任务间传递信息
实现了任务接收来自其他任务或中断的不固定长度的消息,任务能够从队列里面读取消息
当队列中的消息是空时,读取消息的任务将被阻塞,用户还可以指定阻塞的任务时间 xTicksToWait
在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。
在这里插入图片描述

代码生成选项

Default:就是生成正常的函数
在这里插入图片描述

As a external:作为一个外部函数
在这里插入图片描述

As weak:生成弱函数,函数可以被重写
在这里插入图片描述


互斥锁(Mutexes) 和 递归互斥锁(Recursive Mutexes)

递归互斥锁,也称为可重入互斥锁,是一种特殊的互斥锁,允许多次尝试以递归的方式锁定同一资源
普通的互斥锁在同一时间只能被一个线程锁定,且必须在同一线程中解锁相同次数才能实际释放锁
相比之下,递归互斥锁可以被同一个线程多次锁定,只要该线程解锁相同的次数就可以释放锁
这种类型的锁通常用于递归函数中,或者在多个线程可能以未知的顺序锁定和解锁资源的情况下

在这里插入图片描述


定时器(Timer) 和 信号量(Semaphore)

定时器是一种硬件或软件机制,可以生成周期性的中断,用于在特定的时间间隔后触发事件
定时器可以设置为一次性触发模式,也可以设置为周期性重复触发模式。
在多任务操作系统中,定时器还用于确定任务的最大执行时间,从而实现任务的实时调度。
在这里插入图片描述
信号量是一个重要的同步工具,用于控制多个进程对共享资源的访问,以防止数据的损坏。
信号量维护一个计数器,该计数器在进程申请资源时递减,在进程释放资源时递增。
当计数器的值大于零时,意味着有可用资源,进程可以直接使用;
当计数器的值为零时,进程必须等待,直到有其他进程释放资源。


事件标志组(Events)

在这里插入图片描述


高级设置(Advanced settings)

在这里插入图片描述

要是不使能的话,就会报如下警告

在这里插入图片描述


其他设置(others)

FreeRTOS Heap Usage——FreeRTOS堆使用情况——改不了,看得懂的,可以看这些数据

在这里插入图片描述

Include parameters——包括参数——看不懂的不要乱改

在这里插入图片描述


生成位置/效果

参数配置(Config parameters)的一系列定义和使能都在 freertos.h文件
创建的任务,消息队列,互斥锁,定时器,信号量,事件标志组等都在 freertos.c文件
这些线程同步方法的具体用法,官网 FreeRTOS 内核开发者文档 有介绍

在这里插入图片描述
在这里插入图片描述


6、任务


动态创建任务函数

在实际的应用中,动态方式创建任务是比较常用的,创建相对简单
对应的宏:configSUPPORT_DYNAMIC_ALLOCATION

BaseType_t xTaskCreate(	
	TaskFunction_t 						pxTaskCode,		/* 指向任务函数的指针 */				
	const char * const 					pcName, 		/* 任务名字,最大长度configMAX_TASK_NAME_LEN */
	const 	configSTACK_DEPTH_TYPE 		usStackDepth, 	/* 任务堆栈大小,注意字为单位 */
	void * const 						pvParameters,	/* 传递给任务函数的参数 */
	UBaseType_t 						uxPriority,		/* 任务优先级,范围:0 ~ configMAX_PRIORITIES - 1 */
	TaskHandle_t * const 				pxCreatedTask 	/* 任务句柄,就是任务的任务控制块 */
)

返回值(BaseType_t):
任务创建成功:pdPASS
任务创建失败:errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY

此函数创建的任务会立刻进入就绪态,由任务调度器调度运行


静态创建任务函数

对应的宏:configSUPPORT_STATIC_ALLOCATION

TaskHandle_t xTaskCreateStatic
(
	TaskFunction_t			pxTaskCode,				/* 指向任务函数的指针 */
	const char * const		pcName,					/* 任务函数名 */
	const uint32_t			ulStackDepth, 			/* 任务堆栈大小注意字为单位 */
	void * const			pvParameters, 			/* 传递的任务函数参数 */
	UBaseType_t				uxPriority, 			/* 任务优先级 */
	StackType_t * const		puxStackBuffer, 		/* 任务堆栈,一般为数组,由用户分配 */
	StaticTask_t * const	pxTaskBuffer			/* 任务控制块指针,由用户分配 */
); 		

返回值(TaskHandle_t ):
任务创建成功:其他值,任务句柄,任务创建成功
任务创建失败:NULL,用户没有提供相应的内存,任务创建失败

此函数创建的任务会立刻进入就绪态,由任务调度器调度运行


任务删除函数

静态创建:可将任务堆栈放置在特定的内存位置,并且无需关心对内存分配失败的处理
对应的宏:INCLUDE_vTaskDelete

/**
  * @brief  任务删除函数
  * @param  xTaskToDelete:待删除任务的任务句柄
  */
void vTaskDelete(TaskHandle_t xTaskToDelete);

被删除的任务将从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除
当传入的参数为NULL,则代表删除任务自身(当前正在运行的任务)
空闲任务会负责释放被删除任务中由系统分配的内存
但是用户在任务删除前申请的内存, 需要由用户在任务被删除前释放,否则导致内存泄露


PS:任务控制块

具体看本文【任务通知】板块

typedef struct tskTaskControlBlock       
{
	volatile StackType_t 	* pxTopOfStack; 		/* 任务栈栈顶,必须为TCB的第一个成员 */
   	ListItem_t 				xStateListItem;         /* 任务状态列表项 */      
	ListItem_t 				xEventListItem;			/* 任务事件列表项 */     
    UBaseType_t 			uxPriority;             /* 任务优先级,数值越大,优先级越大 */
    StackType_t 			* pxStack;				/* 任务栈起始地址 */
    char 					pcTaskName[ configMAX_TASK_NAME_LEN ]; 	/* 任务名字 */		
	…………
} tskTCB;

任务栈栈顶,在任务切换时的任务上下文保存、任务恢复息息相关
每个任务都有属于自己的任务控制块,类似身份证


参数:任务堆栈大小

任务堆栈大小注意字为单位
创建任务的时候,怎么确定 “任务堆栈大小” 设置多少?

Freertos提供了一个API函数:获取任务堆栈历史 剩余 最小值

对应的宏:INCLUDE_uxTaskGetStackHighWaterMark
函数:uxTaskGetStackHighWaterMark()
形参:要查看的任务
返回值:历史剩余最小值


任务相关API函数

每个函数都有对应的宏需要置一,使用前别忘了设置
每个函数都有对应的宏需要置一,使用前别忘了设置
每个函数都有对应的宏需要置一,使用前别忘了设置

函数描述
uxTaskPriorityGet()获取任务优先级
vTaskPrioritySet()设置任务优先级
uxTaskGetNumberOfTasks()获取系统中任务的数量
uxTaskGetSystemState()获取所有任务状态信息
vTaskList()以“表格”形式获取所有任务的信息
vTaskGetInfo()获取指定单个的任务信息
xTaskGetCurrentTaskHandle()获取当前任务的任劳句柄
xTaskGetHandle()根据任务名获取该任务的任务句柄
uxTaskGetStackHighWaterMark()获取任务的栈历史剩余最小值
eTaskGetState()获取任务状态
vTaskGetRunTimeStats()获取任务的运行时间

7、挂起恢复 和 临界区


任务级——单任务

对应的宏: INCLUDE_vTaskSuspend
中断中恢复对应的宏:INCLUDE_xTaskResumeFromISR(两个宏都置一)

API函数描述
vTaskSuspend()挂起任务
vTaskResume()恢复被挂起的任务
xTaskResumeFromISR()在中断中恢复被挂起的任务

TaskSuspend()
vTaskSuspend() 无论优先级如何,被挂起的任务都将不再被执行,直到任务被恢复
vTaskSuspend() 当传入的参数为NULL,则代表挂起任务自身(当前正在运行的任务)

vTaskResume()
任务无论被 vTaskSuspend() 挂起多少次
只需在任务中调用 vTakResume() 恢复一次
就可以继续运行,且被恢复的任务会进入就绪态!

xTaskResumeFromISR()
返回值
pdTRUE:任务恢复后需要进行任务切换
pdFALSE:任务恢复后不需要进行任务切换

/* 任务级挂起 */
vTaskSuspendAll() ;
{
        … …	/* 代码 */
}
xTaskResumeAll()	;	

任务调度器——所有任务

挂起任务调度器,其他任务不可以打断,但是中断可以打断

API函数描述
vTaskSuspendAll()挂起任务调度器
xTaskResumeAll()恢复任务调度器
/* 任务调度器挂起 */
vTaskSuspendAll();
{
	… …	/* 代码 */
}
xTaskResumeAll();	

挂起任务调度器,未关闭中断,仅仅是防止了任务之间的资源争夺,中断照样可以直接响应
挂起任务调度器,适用于任务与任务之间,既不用去延时中断,又可以做到任务块的安全

这里也可以称作临界区,但实际为了避免跟下面的误导,用任务块描述


临界区——所有任务和中断

临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段
系统任务调度靠中断,ISR也靠中断,临界区是直接屏蔽了中断
FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断

函数描述
taskENTER_CRITICAL()任务级进入临界段
taskEXIT_CRITICAL()任务级退出临界段
taskENTER_CRITICAL_FROM_ISR()中断级进入临界段
taskEXIT_CRITICAL_FROM_ISR()中断级退出临界段

成对使用,支持嵌套,尽量保持临界段耗时短
进入多少次临界段,就要退出多少临界段
没进入临界段,不能退出临界段 ,会报错

/* 任务级临界区 */
taskENTER_CRITICAL() ;
{
	/* 临界区代码 */
}
taskEXIT_CRITICAL()	;	

/* 中断级临界区 */
uint32_t  save_status;
save_status  = taskENTER_CRITICAL_FROM_ISR(){
	/* 临界区代码 */
}
taskEXIT_CRITICAL_FROM_ISR(save_status );	

实际上,临界区函数 嵌套了 开中断函数 和 关中断函数,以及一个计数值
关中断函数:portDISABLE_INTERRUPTS();
开中断函数:portENABLE_INTERRUPTS();


8、中断

什么是中断

让CPU打断正常运行的程序,转而去处理紧急的事件(程序),就叫中断
在这里插入图片描述
关中断函数:portDISABLE_INTERRUPTS();
开中断函数:portENABLE_INTERRUPTS();

中断执行机制步骤

1,中断请求: 外设产生中断请求(GPIO外部中断、定时器中断等)
2,响应中断: CPU停止执行当前程序,转而去执行中断处理程序(ISR)
3,退出中断: 执行完毕,返回被打断的程序处,继续往下执行


中断优先级

ARM Cortex-M 使用了 8 位宽的寄存器来配置中断的优先等级,最大256级的中断优先等级
STM32只用了中断优先级配置寄存器的高4位 [7 : 4],最大16级的中断优先等级
配置寄存器:中断优先级配置寄存器
在这里插入图片描述

中断优先级数值越小越优先,任务优先级数值越大越优先

在这里插入图片描述

中断优先级分组
STM32 的中断优先级可以分为抢占优先级和子优先级
抢占优先级: 抢占优先级高的中断可以打断抢占优先级低的中断
子优先级:相同抢占优先级的两个中断,子优先级数值小的优先执行

配置函数:HAL_NVIC_SetPriorityGrouping(优先级分组);

优先级分组抢占优先级子优先级优先级配置寄存器高4位
NVIC_PriorityGroup_00级抢占优先级0-15级子优先级0bit用于抢占优先级,4bit用于子优先级
NVIC_PriorityGroup_10-1级抢占优先级0-7级子优先级1bit用于抢占优先级,3bit用于子优先级
NVIC_PriorityGroup_20-3级抢占优先级0-3级子优先级2bit用于抢占优先级,2bit用于子优先级
NVIC_PriorityGroup_30-7级抢占优先级0-1级子优先级3bit用于抢占优先级,1bit用于子优先级
NVIC_PriorityGroup_40-15级抢占优先级0级子优先级4bit用于抢占优先级,0bit用于子优先级

建议将所有优先级位指定为抢占优先级位,方便FreeRTOS管理
抢占优先级高的可以打断低的,子优先级不能打断

中断BASEPRI寄存器

BASEPRI:屏蔽优先级低于某一个阈值的中断,当设置为0时,则不关闭任何中断
例如:BASEPRI设置为0x50,代表中断优先级在5 ~ 15内的均被屏蔽,0 ~ 4的中断优先级正常执行
在这里插入图片描述

配置最大系统调用中断优先级的宏:configMAX_SYSCALL_INTERRUPT_PRIORITY默认设置为5
如果要调用freeRTOS的API函数,中断服务程序的中断优先级不能高于FreeRTOS所管理的最高优先级
即优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断,才能调用FreeRTOS 的API函数
所以 FreeRTOS所管理的最高优先级默认是5 ~ 15


9、任务调度(了解)

开启任务

函数:vTaskStartScheduler()
作用:用于启动任务调度器,任务调度器启动后, FreeRTOS 便会开始进行任务调度
流程:
1、创建空闲任务
2、如果使能软件定时器,则创建定时器任务
3、关闭中断,防止调度器开启之前或过程中,受中断干扰,会在运行第一个任务时打开中断
4、初始化全局变量,并将任务调度器的运行标志设置为已运行
5、初始化任务运行时间统计功能的时基定时器
6、调用函数 xPortStartScheduler()

函数:xPortStartScheduler()
作用:该函数用于完成启动任务调度器中与硬件架构相关的配置部分,以及启动第一个任务该函数内部实现
流程:
1、检测用户在FreeRTOSConfig.h文件中对中断的相关配置是否有误
2、配置PendSV和SysTick的中断优先级为最低优先级
3、调用函数vPortSetupTimerlnterrupt()配置SysTick
4、初始化临界区嵌套计数器为0
5、调用函数prvEnableVFP()使能FPU(M3没有)
6、调用函数prvStartFirstTask()启动第一个任务,开启中断

函数:prvStartFirstTask ()
用于初始化启动第一个任务前的环境,主要是重新设置MSP 指针,并使能全局中断

函数:vPortSVCHandler ()
SVC中断只在启动第一次任务时会调用一次,以后均不调用
当使能了全局中断,并且手动触发SVC 中断后,就会进入到SVC的中断服务函数中
流程:
1、通过 pxCurrentTCB 获取优先级最高的就绪态任务的任务栈地址
2、通过栈顶指针,将任务栈的内容出栈到CPU寄存器
(任务栈中的内容在调用任务创建函数的时候,已初始化,
4、设置 PSP 指针。
3、往 BASEPRI 寄存器中写O,开启中断。

进入中断后硬件会强制使用MSP指针 ,此时LR(R14)的值将会被自动被更新为特殊的EXC_RETURN

PS:MSP指针(R13)

程序在运行过程中需要一定的栈空间来保存局部变量等一些信息。
当有信息保存到栈中时,MCU 会自动更新 SP 指针

主堆栈指针(MSP):用于OS内核、异常服务以及所有需要特权访问的应用程序
进程堆栈指针(PSP):用于常规的应用程序代码 (不处于异常服务例程中时)
在FreeRTOS中,中断使用MSP(主堆栈),中断以外使用PSP(进程堆栈)


任务切换

本质(转移和更新)

CPU寄存器的数据的转移和更新

步骤(上下文切换)

任务A切换到任务B分为两步:
1、保存现场:暂停任务A,并将此时CPU的寄存器(任务A的寄存器值) 保存到 A的任务堆栈
2、恢复现场:将 B的任务堆栈(任务B的寄存器值) 恢复到 CPU寄存器

上下文切换:对任务A保存现场,对任务B恢复现场 的这个整体的过程
在这里插入图片描述
恢复现场(出栈):从下往上(低地址往高地址),R0—>R12
保存现场(压栈):从上往下(高地址往低地址), R12—>R0

xPSR,PC(R15),LR(R14),R12,R3-R0硬件自动出/入栈;
R4~R11 需要手动出/入栈

扩展疑问
  1. 任务切换过程在哪执行?
    PendSV中断服务函数
  2. 在哪触发PendSV中断?
    滴答定时器中断服务函数内,执行portYIELD()
  3. portYIELD() 怎么触发PendSV中断?
    将中断控制和状态寄存器 ICSR 的 bit28 置一

10、延时函数

相对延时:vTaskDelay()
绝对延时:xTaskDelayUntil()

注意
只由Freertos提供的这两个延时函数,才有任务调度的功能
其他自己写的延时就直接是干等着,什么都不干

相对延时
指每次延时都是从函数vTaskDelay()开始计数,直到延时指定的时间结束

绝对延时
指每次延时都是从任务开始时计数,直到延时指定的时间结束
将整个任务的运行周期看成一个整体,即整个任务运行周期就是这点时间
适用于需要按照一定频率运行的任务,比如:看门狗

相对延时&绝对延时比较
同样的功能,黄色用的是相对延时,绿色用的是绝对延时
内部在插入1个字节写的10ms延时,最后都是延时500ms
在这里插入图片描述

假如,任务所需时间比绝对延时设置的时间要短,则任务会被拦腰斩断


11、列表(相当于链表)

列表: FreeRTOS 中的一个数据结构,用来跟踪 FreeRTOS中的任务
列表项:存放在列表中的项目


跟数组相比

数组的特点:数组成 地址是连续 的,数组在最初确定了 成员数量后期无法改变
列表的特点:列表项间的 地址非连续的 ,列表项的数目随时可以改变

在这里插入图片描述


列表结构体

在移植的文件 l i s t . c list.c list.c l i s t . h list.h list.h

/* 列表项结构体 */
struct xLIST_ITEM
{
   	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE			/* 用于检测列表项的数据完整性 */
   	configLIST_VOLATILE TickType_t xItemValue			/* 列表项的值,多用于按升序对列表中的列表项进行排序 */
    struct xLIST_ITEM * configLIST_VOLATILE pxNext		/* 下一个列表项 */
 	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious	/* 上一个列表项 */
   	void * pvOwner										/* 列表项的拥有者(通常是任务控制块) */
   	struct xLIST * configLIST_VOLATILE pxContainer; 	/* 列表项所在列表 */
  	listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE			/* 用于检测列表项的数据完整性 */
};
typedef struct xLIST_ITEM ListItem_t; 	
/* 迷你列表项 */
/* 迷你列表项也是列表项,但迷你列表项仅用于标记列表的末尾和挂载其他插入列表中的列表项  */
struct xMINI_LIST_ITEM
{
   	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE 				/* 用于检测数据完整性 */
	configLIST_VOLATILE TickType_t xItemValue;				/* 列表项的值 */
   	struct xLIST_ITEM * configLIST_VOLATILE pxNext;			/* 上一个列表项 */
  	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; 	/* 下一个列表项 */
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;
/* 列表结构体 */
typedef struct xLIST
{
  	listFIRST_LIST_INTEGRITY_CHECK_VALUE			/* 校验值 */
  	volatile UBaseType_t uxNumberOfItems;			/* 列表中的列表项数量(不包含 xListEnd) */
 	ListItem_t * configLIST_VOLATILE pxIndex		/* 指向列表中的某个列表项,用于遍历列表项的指针 */
  	MiniListItem_t xListEnd							/* 末尾列表项(迷你列表项) */
  	listSECOND_LIST_INTEGRITY_CHECK_VALUE			/* 校验值 */
} List_t;

/* 校验值: 是两个宏,这两个宏是确定的已知常量
 FreeRTOS通过检查这两个常量的值,来判断列表的数据在程序运行过程中,是否遭到破坏 
 该功能一般用于调试, 默认是不开启的 
 */

在这里插入图片描述


API函数

API函数描述
vListInitialise()初始化列表
vListInitialiseItem()初始化列表项
vListInsertEnd()列表末尾插入列表项
vListInsert()列表插入列表项
uxListRemove()列表移除列表项

具体原理,先去学学C语言的链表(双向循环),本文只作介绍,告知有这这样一个工具可使用


12、消息队列(集)

全局变量的弊端:数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损


队列

概念

队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递)
在队列中可以存储数量有限大小固定的数据。
在创建队列时,就要指定 队列长度 以及 队列项目的大小
队列项目:队列中的每一个数据
队列长度 :队列能够存储 “队列项目” 的最大数量
在这里插入图片描述


FreeRTOS队列特点
  • 1、数据入队出队方式
    队列通常采用“先进先出”(FIFO)的数据存储缓冲机制
    也可以配置为“后进先出”LIFO方式
  • 2、数据传递方式
    FreeRTOS中队列采用实际值传递,即将数据拷贝到队列中进行传递
    可以传递指针,在传递较大的数据的时候采用指针传递
  • 3、多任务访问
    队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息
  • 4、出队、入队阻塞
    当任务向一个队列发送消息时,可以指定一个阻塞时间
    假设此时当队列已满无法入队
    ①若阻塞时间为0:直接返回不会等待;
    ②若阻塞时间为O~port_MAX_DELAY:等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;
    ③若阻塞时间为port_MAX_DELAY:死等,一直等到可以入队为止。出队阻塞与入队阻塞类似;
    阻塞的时候多个任务等待同一个队列的空间
    当队列中有空间时,优先级最高的任务 先进入就绪态
    如果优先级相同,等待时间最久的任务 先进入就绪态

结构体
/* 消息队列结构体 */
typedef struct QueueDefinition 
{
    int8_t * pcHead							/* 存储区域的起始地址 */
    int8_t * pcWriteTo;        				/* 下一个写入的位置 */
    union
    {
        QueuePointers_t     xQueue; 
		SemaphoreData_t  xSemaphore; 
    } u ;
    List_t xTasksWaitingToSend; 			/* 等待发送列表 */
    List_t xTasksWaitingToReceive;			/* 等待接收列表 */
    volatile UBaseType_t uxMessagesWaiting; /* 非空闲队列项目的数量 */
    UBaseType_t uxLength;					/* 队列长度 */
    UBaseType_t uxItemSize;                 /* 队列项目的大小 */
    volatile int8_t cRxLock; 				/* 读取上锁计数器 */
    volatile int8_t cTxLock;				/* 写入上锁计数器 */
   	…………/* 其他的一些条件编译 */
} xQUEUE;

/* 当用于队列使用时 */
typedef struct QueuePointers
{
     int8_t * pcTail; 		/* 存储区的结束地址 */
     int8_t * pcReadFrom;	/* 最后一个读取队列的地址 */
} QueuePointers_t;

/* 当用于互斥信号量和递归互斥信号量时 */
typedef struct SemaphoreData
{
    TaskHandle_t xMutexHolder;			/* 互斥信号量持有者 */
    UBaseType_t uxRecursiveCallCount;	/* 递归互斥信号量的获取计数器 */
} SemaphoreData_t;

API函数
函数描述
xQueueCreate()动态方式创建队列
xQueueCreateStatic()静态方式创建队列
xQueueSend()往队列的尾部写入消息
xQueueSendToBack()同 xQueueSend()
xQueueSendToFront()往队列的头部写入消息
xQueueOverwrite()覆写队列消息(只用于队列长度为1的情况)
xQueueSendFromISR()在中断中往队列的尾部写入消息
xQueueSendToBackFromISR()同 xQueueSendFromISR()
xQueueSendToFrontFromISR()在中断中往队列的头部写入消息
xQueueOverwriteFromISR()在中断中覆写队列消息(只用于队列长度为1的情况)
xQueueReceive()从队列头部读取消息,并删除消息
xQueuePeek()从队列头部读取消息
xQueueReceiveFromISR()在中断中从队列头部读取消息,并删除消息
xQueuePeekFromISR()在中断中从队列头部读取消息

在这里插入图片描述

一个队列只允许传递同一种数据类型的消息
一个队列只允许传递同一种数据类型的消息
一个队列只允许传递同一种数据类型的消息
如果需要传递不同数据类型的消息,就需要使用队列集 !


队列集

对多个队列或信号量进行“监听”,其中不管哪一个消息到来,都可让任务退出阻塞状态

对应的宏:configUSE_QUEUE_SETS

API函数
函数描述
xQueueCreateSet()创建队列集
xQueueAddToSet()队列添加到队列集中
xQueueRemoveFromSet()从队列集中移除队列
xQueueSelectFromSet()获取队列集中有有效消息的队列
xQueueSelectFromSetFromISR()在中断中获取队列集中有有效消息的队列

注意!!!,添加到队列集的队列,必须没有有效的消息
注意!!!,添加到队列集的队列,必须没有有效的消息
注意!!!,添加到队列集的队列,必须没有有效的消息

注意!!!,从队列集移除的队列,必须没有有效的消息
注意!!!,从队列集移除的队列,必须没有有效的消息
注意!!!,从队列集移除的队列,必须没有有效的消息


13、信号量

信号量是一种解决同步问题的机制,可以实现对共享资源的有序访问
信号量:用于传递状态

当计数值大于0,代表有信号量
当释放信号量,信号量计数值(资源数)加一
当获取信号量,信号量计数值(资源数)减一

在这里插入图片描述

资源信号量的计数值都有限定最大值
二值信号量:限定最大值被限定为1
计数型信号量:限定最大值不是1


二值信号量

二值信号量的本质是一个队列长度为 1 的队列 ,该队列就只有空和满两种情况

函数描述
xSemaphoreCreateBinary()使用动态方式创建二值信号量
xSemaphoreCreateBinaryStatic()使用静态方式创建二值信号量
xSemaphoreGive()释放信号量
xSemaphoreTake()获取信号量
xSemaphoreGiveFromISR()在中断中释放信号量
xSemaphoreTakeFromISR()在中断中获取信号量

计数型信号量

计数型信号量相当于队列长度大于1 的队列

函数描述
xSemaphoreCreateCounting()使用动态方法创建计数型信号量。
xSemaphoreCreateCountingStatic()使用静态方法创建计数型信号量
uxSemaphoreGetCount()获取信号量的计数值

互斥信号量

对应的宏:configUSE_MUTEXES

互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中二值信号量最适合。
优先级继承并不能完全的消除优先级翻转的问题,只是尽可能的降低优先级翻转带来的影响

函数描述
xSemaphoreCreateMutex()使用动态方法创建互斥信号量。
xSemaphoreCreateMutexStatic()使用静态方法创建互斥信号量。

互斥信号量不能用于中断服务函数
互斥信号量有任务优先级继承的机制, 但是中断不是任务,没有任务优先级
中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态


PS:优先级继承

当一个互斥信号量正在被一个低优先级的任务持有时
如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞
不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。
一定程度上降到了优先级翻转的危害


PS:优先级翻转

什么是优先级反转?

在这里插入图片描述

优先级翻转是当一个高优先级任务通过信号量机制访问共享资源时
该信号量已被一低优先级任务占有
造成高优先级任务被许多中优先级任务阻塞,实时性难以保证
而且中优先任务比高优先级任务先执行。

通俗来讲:一个高优先级任务(H任务)正在执行,但是执行过程中高优先级要使用的资源的信号量被一个低优先级(L任务)占用,这时候高优先级任务只能进入阻塞,将cpu使用权给低优先级,等低优先级任务级释放信号量,低优先级任务继续执行任务,不巧的是,低优先级任务还没有释放信号量,中优先级任务(M任务)进入就绪态,然后抢占了低优先级任务的cpu使用权,此时必须先执行完中优先级任务。此时没有任何瓜葛的中优先级任务和高优先级任务执行先后顺序就翻转了

操作系统如何解救优先级反转?

在这里插入图片描述

优先级天花板(priority ceilings)
指将申请某资源的任务的优先级提升到可能访问该资源的所有任务中最高优先级任务的优先级。
(这个优先级称为该资源的优先级天花板) 。
这种方法简单易行, 不必进行复杂的判断
不管任务是否阻塞了高优先级任务的运行, 只要任务访问共享资源都会提升任务的优先级。

通俗来讲:就是将低优先级任务(L任务)的优先级暂时提升,优先级会提升至定义的天花板优先级(大于或等于H任务的优先级)。这个天花板优先级通常高于所有在该信号量上可能阻塞的任务优先级,如此中优先级任务就没有机会抢占cpu使用权了


14、事件标志组

事件标志位:用一个位,来表示事件是否发生
事件标志组是一组事件标志位的集合, 可以简单的理解事件标志组,就是一个整数。
在这里插入图片描述
使用了 32 位无符号的数据类型变量来存储事件标志
高8位:用作存储事件标志组的控制信息
低24位:用作存储事件标志

一个事件组最多可以存储 24 个事件标志

函数描述
xEventGroupCreate()使用动态方式创建事件标志组
xEventGroupCreateStatic()使用静态方式创建事件标志组
xEventGroupClearBits()清零事件标志位
xEventGroupClearBitsFromISR()在中断中清零事件标志位
xEventGroupSetBits()设置事件标志位
xEventGroupSetBitsFromISR()在中断中设置事件标志位
xEventGroupWaitBits()等待事件标志位
xEventGroupSync()设置事件标志位,并等待事件标志位

15、任务通知

任务通知:用来通知任务的

任务结构体的成员变量——任务控制块TCB
任务控制块结构体两个重要成员变量:通知值,通知状态

#define  configTASK_NOTIFICATION_ARRAY_ENTRIES	1  	/* 定义任务通知数组的大小, 默认: 1 */

typedef  struct  tskTaskControlBlock 
{
	… …
#if ( configUSE_TASK_NOTIFICATIONS  ==  1 )
   	volatile  uint32_t    ulNotifiedValue [ configTASK_NOTIFICATION_ARRAY_ENTRIES ];	/* 通知值 */
   	volatile  uint8_t      ucNotifyState [ configTASK_NOTIFICATION_ARRAY_ENTRIES ];		/* 通知状态 */
#endif
	… …
} tskTCB;

跟队列、信号量、事件标志组相比

相似

合理灵活的利用任务通知,部分情况可以替代队列、信号量、事件标志组
任务通知值的更新方式如下:

  • 不覆盖接受任务的通知值(队列)
  • 覆盖接受任务的通知值(队列)
  • 更新接受任务通知值的一个或多个bit(事件标志组)
  • 增加接受任务的通知值(信号量)
区别

队列、信号量、事件标志组通讯需要中间媒介(对应的结构体)
在这里插入图片描述
任务通知不需要中间媒介,任务结构体TCB包含了内部对象,可以直接接收别人发过来的"通知"
在这里插入图片描述


优劣

效率更高:使用任务通知向任务发送事件或数据比使用队列、事件标志组或信号量快得多
使用内存更小:使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体

无法发送数据给ISR:ISR没有任务结构体,无法接受数据。但是ISR可以发数据给任务。
无法广播给多个任务:任务通知只能指定的一个任务来接收
无法缓存多个数据:任务通知通过更新任务通知值来发送数据,任务结构体中只有一个任务通知值,只能保持一个数据。
发送受阻不支持阻塞:发送方无法进入阻塞状态等待


API函数

函数描述
xTaskNotify()发送通知,带有通知值 (队列、信号量、事件标志组)
xTaskNotifyAndQuery()发送通知,带有通知值,保留接收任务的原通知值(队列、信号量、事件标志组)
xTaskNotifyGive()发送通知,不带通知值(信号量)
xTaskNotifyFromISR()在中断中发送通知,带有通知值
xTaskNotifyAndQueryFromISR()在中断中发送通知,带有通知值,保留接收任务的原通知值
vTaskNotifyGiveFromISR()在中断中发送通知,不带通知值
uITaskNotifyTake()获取任务通知,可以设置在退出此函数的时候,将任务通知值清零或者减一。(信号量)
xTaskNotifyWait()获取任务通知,可获取 通知值清除通知值的指定位(队列、事件标志组)

16、软件定时器

对应的宏:
使能:configUSE_TIMERS
优先级: configTIMER_TASK_PRIORITY = 31
命令队列长度:configTIMER_QUEUE_LENGTH = 5

简介

  • 定时器
    从指定时刻开始,经过指定时间,触发指定事件
  • 硬件定时器
    芯片自带的定时器模块,
    在定时时间到达之后就会自动触发中断,在中断服务函数中处理信息。
    硬件定时器的精度较高,硬件定时器数量有限
  • 软件定时器
    指具有定时功能的软件,可设置定时周期
    软件定时器精度相对较低,定时器理论上只要内存足够,就可以创建无数个
    软件定时器以系统时钟为基准,但系统时钟中断优先级是最低,容易被打断
    在时间到达后要调用回调函数(也称超时函数),在回调函数中处理信息

状态 和 命令队列

新创建的软件定时器处于休眠状态,需手动开启

  • 休眠态
    休眠态的软件定时器可以通过其句柄被引用
    但因为是休眠态,所以其定时超时回调函数不会被执行

  • 运行态
    运行态的定时器,当指定时间到达之后,它的超时回调函数会被调用

  • 软件定时器命令队列
    提供给 FreeRTOS 中的软件定时器使用的,用户是不能直接访问的。
    API 函数大多都是往定时器的队列中写入消息(发送命令)
    在这里插入图片描述


单次/周期定时器

  • 单次定时器

在这里插入图片描述
单次定时器的一旦定时超时,只会执行一次其软件定时器超时回调函数
不会自动重新开启定时,不过可以被手动重新开启。

  • 周期定时器

在这里插入图片描述
周期定时器的一旦启动,就会在执行完回调函数以后自动的重新启动
从而周期地执行其软件定时器回调函数。


注意事项

在调用函数 vTaskStartScheduler()开启任务调度器的时候
会创建一个用于管理软件定时器的【软件定时器服务任务】,叫prvTimerTask( )
【软件定时器的超时回调函数】是由 【软件定时器服务任务】调用的
【服务任务】不是专为某个定时器服务的,还要处理其他定时器
【超时回调函数】本身不是任务,要尽快实行,不能进入阻塞状态
不能调用那些会阻塞任务的 API 函数,如:vTaskDelay()
访问队列或者信号量的非零阻塞时间的 API 函数也不能调用。


结构体

typedef	struct
{
     const char * 						pcTimerName				/* 软件定时器名字(无大用) */
     ListItem_t 						xTimerListItem			/* 软件定时器列表项(软件定时器列表,溢出列表) */
     TickType_t 						xTimerPeriodInTicks;	/* 软件定时器的周期(超时时间) */     
     void * 							pvTimerID				/* 软件定时器的ID(同一个回调函数通过ID判断定时器) */
     TimerCallbackFunction_t	 		pxCallbackFunction; 	/* 软件定时器的回调函数 */
#if ( configUSE_TRACE_FACILITY == 1 )
     UBaseType_t 						uxTimerNumber			/*  软件定时器的编号,调试用  */
#endif
     uint8_t 							ucStatus;  				/*  软件定时器的状态(单次/周期)  */
} xTIMER;

API函数

函数描述
xTimerCreate()动态方式创建软件定时器
xTimerCreateStatic()静态方式创建软件定时器
xTimerStart()开启软件定时器定时
xTimerStop()停止软件定时器定时
xTimerReset()复位软件定时器定时
xTimerChangePeriod()更改软件定时器的定时超时时间
xTimerStartFromISR()在中断中开启软件定时器定时
xTimerStopFromISR()在中断中停止软件定时器定时
xTimerResetFromISR()在中断中复位软件定时器定时
xTimerChangePeriodFromISR()在中断中更改定时超时时间

17、低功耗模式

一般MCU都低功耗模式,裸机也可以使用MCU的低功耗模式。
FreeRTOS也提供了低功耗模式,叫Tickless

对应的宏:

  • configUSE_TICKLESS_IDLE
    使能低功耗Tickless模式
  • configEXPECTED_IDLE_TIME_BEFORE_SLEEP
    定义系统进入相应低功耗模式的最短时长
  • configPRE_SLEEP_PROCESSING(x)
    定义需要在系统进入低功耗模式前执行的事务,如:关闭外设时钟,以达到降低功耗的目的
  • configPOSR_SLEEP_PROCESSING(x)
    定义需要在系统退出低功耗模式后执行的事务,如:开启外设时钟,以使系统能够正常运行

原理

在空闲任务执行的期间,让MCU进入低功耗模式
当其他任务准备运行时,唤醒MCU退出低功耗模式
在这里插入图片描述
在整个系统的运行过程中,其实大部分时间是在执行空闲任务的
Tickless低功耗模式的本质:通过调用指令 WFI 实现睡眠模式


STM32低功耗模式类型

睡眠模式、停止模式、待机模式

模式名称进入唤醒对1.2V域时钟的影响对VDD域时钟的影响调压器
睡眠WFI任意中断CPU CLK关闭对其他时钟或模拟时钟源无影响开启
(立即休眠或退出是休眠)WFE唤醒事件CPU CLK关闭对其他时钟或模拟时钟源无影响开启
停止PDDS位+SLEEPDEEP位+WFI或WFE任意EXTI线(在EXTI寄存器中配置,内部线和外部线)所有1.2V域时钟都关闭HSI和HSE振荡器关闭开启或处于低功耗模式
待机PDDS位+SLEEPDEEP位+WFI或WFEWKUP引脚上升沿、RTC闹钟(闹钟A或闹钟B)RTC唤醒事件、RTC入侵事件、RTC时间戳事件、NRST引脚外部复位、IWDG复位所有1.2V域时钟都关闭HSI和HSE振荡器关闭关闭

Tickless使用的是睡眠模式

  • 进入睡眠模式
    WFI 指令:__WFI
    WFE 指令:__WFE
  • 退出睡眠模式
    任何中断 / 事件都可以唤醒睡眠模式

应用场景

很多应用场合对于功耗的要求很严格,比如可穿戴低功耗产品、物联网低功耗产品等
减少耗电,增强续航,减少发热

PS:空闲任务

所有其它任务都阻塞或被挂起时运行


18、内存管理

FreeRTOS 创建对象的方法

动态方法创建
自动地从 FreeRTOS 管理的内存堆中申请创建对象所需的内存
并且在对象删除后,可将这块内存释放回FreeRTOS管理的内存堆

静态方法创建
需用户提供各种内存空间,并且使用静态方式占用的内存空间一般固定下来了
即使任务、队列等被删除后,这些被占用的内存空间一般没有其他用途

动态方式管理内存相比与静态方式,更加灵活。


标准C库的内存管理的缺点

标准的 C 库也提供了函数 malloc()和函数 free()来实现动态地申请和释放内存 。
1、占用大量的代码空间,不适合用在资源紧缺的嵌入式系统中
2、没有线程安全的相关机制
3、运行有不确定性,每次调用这些函数时花费的时间可能都不相同
4、内存碎片化
…………(总之就是,毛病一大堆)


FreeRTOS内存管理算法

FreeRTOS提供了5种动态内存管理算法,分别为: heap_1、heap_2、heap_3、heap_4、heap_5 。
具体可见本文【Freertos移植(MDK)】板块

算法优点缺点
heap_1分配简单,时间确定只允许申请内存,不允许释放内存
heap_2允许申请和释放内存不能合并相邻的空闲内存块会产生碎片、花费时间不定
heap_3直接调用C库函数malloc()和 free(),简单速度慢、花费时间不定
heap_4相邻空闲内存可合并,减少内存碎片的产生花费时间不定
heap_5能够管理多个非连续内存区域的heap_4花费时间不定
heap_1

只实现了pvPortMalloc(申请内存),没有实现vPortFree(释放内存)
最为简单,管理的内存堆是一个数组
在申请内存的时候, heap_1 内存管理算法只是简单地从数组中分出合适大小的内存

/* 定义一个大数组作为 FreeRTOS 管理的内存堆 */
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
heap_2

heap_2 内存管理算法使用最适应算法,并且支持申请和释放内存
但不能将相邻的空闲内存块合并成一个大的空闲内存块
heap_2 内存管理算法不可避免地会产生内存碎片
在这里插入图片描述

heap_3

标准C库
简单地包装了 C 语言的 malloc 和 free 函数,确保线程安全。

heap_4

heap_4 内存管理算法使用了首次适应算法
支持内存的申请与释放,能够将空闲且相邻的内存进行合并,减少内存碎片的现象
在这里插入图片描述

heap_5

heap_5 内存管理算法是在 heap_4 内存管理算法的基础上实现的
heap_5 内存管理算法实现了 管理多个非连续内存区域的能力
heap_5 内存管理算法默认并 没有定义内存堆
需要用户手动指定内存区域的信息,对其进行初始化。

/* 内存块结构体 */
typedef struct HeapRegion
{
	uint8_t* 	pucStartAddress; 	/* 内存区域的起始地址 */
	size_t 		xSizeInBytes; 		/* 内存区域的大小,单位:字节 */
} HeapRegion_t;


/* 内存块数组 */
Const  HeapRegion_t  xHeapRegions[] =
{
	{ (uint8_t *)0x80000000, 0x10000 }, 	/* 内存区域 1 */
	{ (uint8_t *)0x90000000, 0xA0000 }, 	/* 内存区域 2 */
	{ NULL, 0 } 							/* 数组终止标志 */
};
vPortDefineHeapRegions(xHeapRegions); 		/* 初始化定义堆内存区域的大小和数量 */

PS:最适应算法 & 首次适应算法

假设有3块已释放的内存,地址由低到高排序:5字节、50字节、25字节
现在新创建一个任务需要申请20字节的内存

第一步:
最适应算法 :找出 最小的且满足 pvPortMalloc的内存:25字节(第3块)
首次适应算法 :找出 第一个能满足 pvPortMalloc的内存:50字节(第2块)

第二步:
将内存分割出20字节内存,并返回返回这20字节的地址
剩下的字节仍然是空闲状态,留给后续的pvPortMalloc使用


API函数

函数描述
void* pvPortMalloc(size_t xWantedSize);申请内存
void vPortFree(void* pv);释放内存
size_t xPortGetFreeHeapSize(void);获取当前空闲内存的大小
/**
  * @brief  	申请内存
  * @param  	xWantedSize:申请的内存大小,以字节为单位;
  * @retval 	成功返回一个指向已分配大小的内存的指针 。失败则返回 NULL。
  */
void * pvPortMalloc( size_t  xWantedSize );

/**
  * @brief  	释放内存
  * @param  	pv:指针指向一个要释放内存的内存块;
  * @retval 	无
  */
void vPortFree(void* pv); 

/**
  * @brief  	释放内存
  * @param		无
  * @retval		返回当前剩余的空闲内存大小
  */
size_t  xPortGetFreeHeapSize( void );

0、API函数查询表

API 函数作用描述
xTaskCreate创建一个新的任务
xTaskCreateStatic创建一个静态任务
vTaskDelete删除指定的任务
vTaskDelay相对延时
vTaskDelayUntil绝对延时
vTaskPrioritySet设置指定任务的优先级。
uxTaskPriorityGet获取指定任务的当前优先级。
vTaskSuspend挂起任务
vTaskResume恢复被挂起任务
xTaskGetTickCount获取系统运行的滴答计数
xTaskGetCurrentTaskHandle获取当前正在执行的任务的句柄。
vTaskList生成当前所有任务的状态列表
vTaskGetRunTimeStats获取任务运行时间的统计信息
xTaskGetIdleTaskHandle获取空闲任务的句柄
vTaskSetThreadLocalStoragePointer设置线程本地存储指针
pvTaskGetThreadLocalStoragePointer获取线程本地存储指针
xTaskGetSchedulerState获取调度器的当前状态(运行中、挂起或停止)
vTaskSwitchContext进行任务切换,通常由调度器自动调用。
xTaskGetNumberOfTasks获取当前系统中任务的数量。
xTaskGetIdleTaskHandle获取空闲任务的句柄。
vTaskSetTaskName设置指定任务的名称。
pcTaskGetTaskName获取指定任务的名称。
xTaskGetStackHighWaterMark获取指定任务的堆栈高水位标记
vTaskPrioritySet设置指定任务的优先级。
xTaskCreateRestricted创建一个受限任务
xTaskGetTaskNumber获取指定任务的任务编号。
vTaskSetTaskNumber设置指定任务的任务编号。
xTaskGetTaskHandle获取指定任务的句柄。
vTaskSetTaskHandle设置指定任务的句柄。
xTaskGetTaskState获取指定任务的状态
vTaskSetTaskState设置指定任务的状态。
xTaskGetTaskInfo获取指定任务的详细信息
vTaskGetTaskInfo获取指定任务的详细信息
xTaskGetTaskHandle获取指定任务的句柄。
vTaskSetTaskHandle设置指定任务的句柄。
uxTaskGetNumberOfTasks()获取系统中任务的数量
uxTaskGetSystemState()获取所有任务状态信息
xTaskGetHandle()获取该任务的任务句柄
eTaskGetState()获取任务状态



┈┈┈┈▕▔╲┈┈┈┈┈┈┈ ┈┈┈┈▕▔╲┈┈┈┈┈┈┈ ┈┈┈┈▕▔╲┈┈┈┈┈┈┈┈
┈┈┈┈┈▏▕┈┈┈┈┈┈┈ ┈┈┈┈┈▏▕┈┈┈┈┈┈┈ ┈┈┈┈┈▏▕┈┈┈┈┈┈┈ ┈
┈┈┈┈┈▏ ▕▂▂▂▂▂┈┈┈┈┈┈┈▏ ▕▂▂▂▂▂┈┈┈┈┈┈┈▏ ▕▂▂▂▂▂┈┈┈
▂▂▂▂╱┈┈▕▂▂▂▂▏┈ ▂▂▂▂╱┈┈▕▂▂▂▂▏┈ ▂▂▂▂╱┈┈▕▂▂▂▂▏┈┈
▉▉▉┈┈┈┈▕▂▂▂▂▏ ┈ ▉▉▉┈┈┈┈▕▂▂▂▂▏ ┈ ▉▉▉┈┈┈┈▕▂▂▂▂▏ ┈
▉▉▉┈┈┈┈▕▂▂▂▂▏ ┈ ▉▉▉┈┈┈┈▕▂▂▂▂▏ ┈ ▉▉▉┈┈┈┈▕▂▂▂▂▏ ┈
▔▔▔▔╲▂▂▕▂▂▂▂▏┈ ▔▔▔▔╲▂▂▕▂▂▂▂▏┈ ▔▔▔▔╲▂▂▕▂▂▂▂▏┈┈

如果对你有帮助,就点赞收藏吧!(。・ω・。)ノ♡

  • 15
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值