FreeRTOS复习
1.FreeRTOS介绍
是一个实时操作系统,windows是通用操作系统
优点:
-
处理多任务
-
专注实时性
2.FreeRTOS基础知识
2.1.任务调度策略
抢占式调度:优先级高的,抢占优先级高的;
时间片轮询:优先级相同的,每个任务执行一个时间片;时间片就是时钟节拍tick,也就是一次系统时钟中断;(如果一个任务,时间片执行了一半任务结束了,那么会立马切换到下一个任务)
协程式:有高优先级的任务就绪时,需要等待这一个正在执行的低优先级任务执行结束才执行A;
任务调度器:任务切换;
默认任务调度策略:默认使用固定优先级的抢占式调度,对同等优先级执行时间片轮询;
FreeRTOS任务优先级:优先级数值越大,优先级越高;
高优先级会不会一直执行?:高优先级任务阻塞(延时或者等待),会执行就绪态的其他任务;
2.2.任务状态
2.2.1四种任务状态介绍
运行态:正在运行,同一时间只有一个任务处于运行态;
就绪态:已经准备就绪,随时可以执行;
阻塞态:正在等待延时或者外部事件;
挂起态:暂停,调用vTaskSuspend()
挂起,调用vTaskResume()
就绪;
2.2.2.任务状态保存方式
列表,内部其实就是双向链表。
就绪列表:pxReadyTasksLists[x],这个有32个,x代表任务优先级
阻塞列表:pxDelayedTaskList
挂起列表:xSuspendedTaskList
有一个32位变量,存标志位,如果对应的就绪列表有任务存在,就会把对应的位置1,调度器从最高位开始找,选择优先级最高的任务来执行;
2.3.FreeRTOS滴答
默认使用的systick系统滴答定时器;
每次滴答数增加时,实时内核必须检查是否现在是解除阻塞或唤醒任务的时间。
2.4.上下文切换
上下文:程序执行时的某一个状态的所有程序信息,数据、执行状态等等;保存了上下文就可以暂停任务,然后恢复到暂停的那个时刻;
要从任务a切换到任务b:1.保存a任务的上下文;2.恢复b任务的上下文
任务堆栈:每一个任务的上下文,会存在自己的任务堆栈中。(这是一种数据结构,实际上存在堆空间,FreeRTOS自己申请和释放)
上下文切换:每个栈有栈顶指针,根据栈顶指针来寻找任务堆栈;
上下问切换在PendSV的ISR中断来处理,默认用的最低优先级,避免任务切换抢占其他中断;
触发PendSV异常产生切换:1.RTOS滴答中断,处理就绪列表,判断是否要切换任务(包括抢占和时间片)2.任务执行完毕:主动调用任务切换函数强制切换;
2.5.空闲任务
RTOS任务调度器启动时,自动创建空闲任务,始终运行,优先级最低;
空闲任务的作用:1.保证系统时钟存在能运行的任务;2.释放删除任务的内存;3.低功耗支持;
3FreeRTOS移植
3.1.源码结构介绍
1)下载文件目录
名称 | 描述 |
---|---|
FreeRTOS | FreeRTOS内核 |
FreeRTOS-Plus | FreeRTOS组件,一般我们会选择使用第三方的组件 |
tools | 工具 |
GitHub-FreeRTOS-Home | FreeRTOS的GitHub仓库链接 |
Quick_Start_Guide | 快速入门指南官方文档链接 |
Upgrading-to-FreeRTOS-xxx | 升级到指定FreeRTOS版本官方文档链接 |
History.txt | FreeRTOS历史更新记录 |
其他 | 其他 |
2)FreeRTOS文件夹结构
名称 | 描述 |
---|---|
Demo | FreeRTOS演示例程,支持多种芯片架构、多种型号芯片 |
License | FreeRTOS相关许可 |
Source | FreeRTOS源码,最重要的文件夹 |
Test | 公用以及移植层测试代码 |
3)Source文件夹结构如下
名称 | 描述 |
---|---|
include | 内包含了FreeRTOS的头文件 |
portable | 包含FreeRTOS移植文件:与编译器相关、keil编译环境 |
croutine.c | 协程相关文件 |
event_groups.c | 事件相关文件 |
list.c | 列表相关文件 |
queue.c | 队列相关文件 |
stream_buffer.c | 流式缓冲区相关文件 |
tasks.c | 任务相关文件 |
timers.c | 软件定时器相关文件 |
-
include文件夹和.c文件是通用的头文件和 C 文件,这两部分的文件适用于各种编译器和处理器,是通用的。
-
加粗的是移植必需的,其他.c文件根据需要选取。
-
portable文件夹里根据编译器、内核等实际环境对应选取。
4)portable文件夹结构
FreeRTOS操作系统归根到底是一个软件层面的东西,需要跟硬件联系在一起,portable文件夹里面的东西就是和硬件的连接桥梁。由于我们使用MDK开发,因此这里只重点介绍其中的部分移植文件。
名称 | 描述 |
---|---|
Keil | 指向RVDS文件夹 |
RVDS | 不同内核芯片的移植文件 |
MemMang | 内存管理相关文件 |
Keil文件夹里只有一个See-also-the-RVDS-directory.txt,意思是让我们看RVDS文件夹。
3.2.移植步骤
3.2.1.移植7个FreeRTOS的.c文件和头文件
3.2.2.移植硬件关联的portable和内存管理文件
我使用的是stm32F407ZGT6
,所以选ARM_CM4F
3.2.3.FreeRTOS配置文件
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
// #define xPortSysTickHandler SysTick_Handler 这样不用注释掉it.c里面的SysTick_Handler
#define INCLUDE_xTaskGetSchedulerState 1
配置系统中断
void SysTick_Handler(void)
{
if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
xPortSysTickHandler();
}
}
4.FreeRTOS中任务的创建和删除
4.1.动态创建
- 要开启宏
#define configSUPPORT_DYNAMIC_ALLOCATION 1 // 开启动态分配
- 任务句柄,指向任务控制块TCB
void start_task(void *pvParameters);
TaskHandle_t start_task_handle;
#define START_TASK_STACK_SIZE 128
#define START_TASK_PRIORITY 1
void freertos_start(void)
{
// 1.创建一个启动任务
xTaskCreate((TaskFunction_t)start_task, // 任务函数地址
(char *)"start_task", // 任务名称
(configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE, // 任务堆栈大小
(void *)NULL, // 传递给任务函数的参数
(UBaseType_t)START_TASK_PRIORITY, // 任务优先级
(TaskHandle_t *)&start_task_handle); // 任务句柄
// 2.启动调度器:会自动创建空闲任务和软件定时器(如果开启)
vTaskStartScheduler();
}
1.申请堆栈内存,任务控制块内存;
2.TCB结构体成员赋值;
3.添加新任务到就绪列表;
4.2.静态创建
- 要开启一下宏
#define configSUPPORT_STATIC_ALLOCATION 1 // 开启静态分配
- 没有TCB,堆栈自己管理
void start_task(void *pvParameters);
TaskHandle_t start_task_handle;
#define START_TASK_STACK_SIZE 128
#define START_TASK_PRIORITY 1
StackType_t start_task_stack[START_TASK_STACK_SIZE]; // 静态任务的任务栈,以数组形式存储
StaticTask_t start_task_tcb; // 声明静态任务控制块TCB
void freertos_start(void)
{
// 1.创建一个启动任务
start_task_handle = xTaskCreateStatic( (TaskFunction_t) start_task, // 任务函数
(char *) "start_task", // 任务名称
(uint32_t) START_TASK_STACK_SIZE, // 任务堆栈大小
(void *) NULL, // 任务参数
(UBaseType_t) START_TASK_PRIORITY, // 任务优先级
(StackType_t *) start_task_stack, // 任务堆栈地址
(StaticTask_t *) &start_task_tcb); // 静态任务的控制块TCB结构体
// 2.启动调度器:会自动创建空闲任务和软件定时器(如果开启),静态创建的方式需要去实现2个分配资源的接口函数
vTaskStartScheduler();
}
- 静态函数还需要手动创建空闲任务和软件定时器
- 要开启以下宏
/* Software timer definitions. */
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY ( 2 )
#define configTIMER_QUEUE_LENGTH 10
#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 )
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
StackType_t ** ppxIdleTaskStackBuffer,
uint32_t * pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &idle_task_tcb;
*ppxIdleTaskStackBuffer = idle_task;
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
void vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer,
StackType_t ** ppxTimerTaskStackBuffer,
uint32_t * pulTimerTaskStackSize )
{
*ppxTimerTaskTCBBuffer = &timer_task_tcb;
*ppxTimerTaskStackBuffer = timer_task;
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}
5.任务删除
这个函数只是任务标记删除,从就绪、阻塞、挂起列表中移除,并没有释放内存,空闲函数释放内存;
vTaskDelete(NULL);
6.任务/调度器 挂起
只有ISR的才能在中断里面调用;
- 开启以下宏
#define INCLUDE_vTaskSuspend 1 // 开启任务挂起/恢复
#define INCLUDE_xResumeFromISR 1 // 开启中断里任务恢复
/* 开启跟踪task信息 */
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
char task_infop[500];
void task3(void *pvParameters)
{
static uint32_t count = 0;
while (1)
{
count++;
if(count == 10)
{
usart_debug("count = 10\r\n");
vTaskSuspend(task1_handle); // 挂起任务1
}else if (count == 20)
{
usart_debug("count = 20\r\n");
vTaskResume(task1_handle); // 恢复任务1
}else if (count == 30)
{
usart_debug("count = 30\r\n");
vTaskSuspendAll(); // 挂起任务调度器,会一直执行这个任务,出不去了
}else if (count == 40)
{
usart_debug("count = 40\r\n");
xTaskResumeAll(); // 恢复任务调度器
}
usart_debug("%d",count);
usart_debug("task3 正在运行\r\n");
LED3_Toggle();
vTaskList(task_infop);
debug_usart_send_string(task_infop);
// 如果开启了任务调度器
if( xTaskGetSchedulerState())
{
vTaskDelay(500);
}
}
}
7.查看任务状态
char task_infop[500];
vTaskList(task_infop);
debug_usart_send_string(task_infop);
- 这里是剩余堆栈,不是使用堆栈
8.中断管理
**BASEPRI:**中断管理,屏蔽中断的,是STM32里面的,这个寄存器只看高4位;例:0x50,高4位是0101,代表屏蔽5-15的中断优先级,只保留0-4的正常执行;
在FreeRTOS中,是configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
管理可不可以调用xxxFromISR
**中断处理流程:**发起中断请求 —> 响应中断请求 —> 保存现场 —> 执行ISR —> 恢复现场
- 开关中断相关宏
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI() // 关中断,关闭要屏蔽的优先级的中断
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 ) // 开中断,打开所有中断
/*3. 中断嵌套行为相关配置 cm3内核:我们要求4个优先级位全部为抢占优先级位
最高优先级是 0
最低优先级是 15
*/
/* 设置 RTOS 内核自身使用的中断优先级。 一般设置为最低优先级, 不至于屏蔽其他优先级程序*/
#define configKERNEL_INTERRUPT_PRIORITY (15 << 4)
/* 设置了 调用中断安全的 FreeRTOS API 函数的最高中断优先级。 FreeRTOS 的管理的最高优先级 */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY (5 << 4)
/* 同上. 仅用于新版移植。 这两者是等效的。 */
#define configMAX_API_CALL_INTERRUPT_PRIORITY configMAX_SYSCALL_INTERRUPT_PRIORITY
8.1.临界区
-
临界区内部也是关闭中断
-
临界区需要同时出现,进去了多次,也要退出多次
-
挂起任务调度器,不会关闭中断,只是不切换任务
9.时间片调度
- 开启相关宏
#define configUSE_TIME_SLICING 1 // 开启时间片调度
#define configUSE_PREEMPTION 1 // 起用抢占式调度
#define configTICK_RATE_HZ ( ( TickType_t ) 20 ) // 时间片大小
10.任务相关API函数
10.1.任务状态查询类
任务相关的API主要如下:
函数 | 描述 |
---|---|
uxTaskPriorityGet() | 获取任务优先级 |
vTaskPrioritySet() | 设置任务优先级 |
uxTaskGetNumberOfTasks() | 获取系统中任务的数量 |
uxTaskGetSystemState() | 获取所有任务状态信息 |
vTaskGetInfo() | 获取指定单个的任务信息 |
xTaskGetCurrentTaskHandle() | 获取当前任务的任务句柄 |
xTaskGetHandle() | 根据任务名获取该任务的任务句柄 |
uxTaskGetStackHighWaterMark() | 获取任务的任务栈历史剩余最小值 |
eTaskGetState() | 获取任务状态 |
vTaskList() | 以“表格”形式获取所有任务的信息 |
vTaskGetRunTimeStats() | 获取任务的运行时间 |
官网:https://www.freertos.org/zh-cn-cmn-s/Documentation/02-Kernel/04-API-references/03-Task-utilities/00-Task-utilities
- 开启宏定义
#define INCLUDE_xTaskGetSchedulerState 1
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
#define INCLUDE_xTaskGetHandle 1
#define INCLUDE_uxTaskGetStackHighWaterMark 1
#define INCLUDE_eTaskGetState 1
10.2.任务时间统计类
函数 | 描述 |
---|---|
vTaskGetRunTimeStats() | 获取任务的运行时间 |
- 需要一个精度比systick精度更高的
- 声明ulHighFrequencyTimerTicks变量,在定时器中断里++
/* 运行时间和任务状态统计相关定义 */
#define configGENERATE_RUN_TIME_STATS 1 /* 1: 使能任务运行时间统计功能, 默认: 0 */
#if configGENERATE_RUN_TIME_STATS
extern volatile unsigned long ulHighFrequencyTimerTicks;
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() ( ulHighFrequencyTimerTicks = 0UL )
#define portGET_RUN_TIME_COUNTER_VALUE() ulHighFrequencyTimerTicks
#endif
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
-
portCONFIGURE_TIMER_FOR_RUNTIME_STATE():用于初始化用于配置任务运行时间统计的时基定时器。它的时间精度需要比 tick 中断具有更高的精度,建议10到100倍。
-
portGET_RUN_TIME_COUNTER_VALUE():返回该定时器的计数值,即当前已运行的时间。
参数说明:https://freertos.org/zh-cn-cmn-s/rtos-run-time-stats.html
11.delay
vTaskDelay(n):相对延时,从这里开始延时n个滴答节拍;(只管delay自己延时这么久)
xTaskDelayUntil(n);绝对延时,从函数开始到结束,一共n个滴答节拍;(整个任务执行这么久)
12.消息队列queue
任务 中断,相互之间发送消息;
一个任务把消息放到queue里面,另一个任务可以从里面读出来;
- 创建队列
函数 | 描述 |
---|---|
xQueueCreate() | 动态方式创建队列 |
xQueueCreateStatic() | 静态方式创建队列 |
- 写队列
xQueueSend() | 往队列的尾部写入消息 |
---|---|
xQueueSendToBack() | 同 xQueueSend() |
xQueueSendToFront() | 往队列的头部写入消息 |
xQueueOverwrite() | 覆写队列消息(只用于队列长度为 1 的情况) |
xQueueSendFromISR() | 在中断中往队列的尾部写入消息 |
xQueueSendToBackFromISR() | 同 xQueueSendFromISR() |
xQueueSendToFrontFromISR() | 在中断中往队列的头部写入消息 |
xQueueOverwriteFromISR() | 在中断中覆写队列消息(只用于队列长度为 1 的情况) |
- 读队列
函数 | 描述 |
---|---|
xQueueReceive() | 从队列头部读取消息,并删除消息 |
xQueuePeek() | 从队列头部读取消息 |
xQueueReceiveFromISR() | 在中断中从队列头部读取消息,并删除消息 |
xQueuePeekFromISR() | 在中断中从队列头部读取消息 |
13.信号量semphore
信号量与队列的区别如下:
信号量 | 队列 |
---|---|
主要用于管理对共享资源的访问,确保在同一时刻只有一个任务可以访问共享资源 | 用于任务之间的数据通信,通过在任务之间传递消息,实现信息的传递和同步。 |
可以是二进制信号量(Binary Semaphore)或计数信号量(Counting Semaphore) | 存储和传递消息的数据结构,任务可以发送消息到队列,也可以从队列接收消息。 |
适用于对资源的互斥访问,控制任务的执行顺序,或者限制同时访问某一资源的任务数量。 | 适用于在任务之间传递数据,实现解耦和通信。 |
13.1.二值信号量Binary
介绍:
-
二值信号量只有0和1;
-
底层实现就是队列,不过这里只判断队列满了没有;释放了就满了,获取了就不满了;
用途:
-
任务同步:任务1执行完了,释放信号量,任务2收到信号量,开始执行。1和2同步;
-
互斥访问(对共享资源管理):一个资源,只能被一个任务使用;eg:任务1和任务2都要对a++,由于a++不是原子操作,所以可能会加的不够,所以任务执行的时候获取信号量,执行结束释放信号量,保证a++同时只有1个任务在操作;
二值信号量相关函数:
函数 | 描述 |
---|---|
xSemaphoreCreateBinary() | 使用动态方式创建二值信号量 |
xSemaphoreCreateBinaryStatic() | 使用静态方式创建二值信号量 |
xSemaphoreGive() | 释放信号量 |
xSemaphoreGiveFromISR() | 在中断中释放信号量 |
xSemaphoreTake() | 获取信号量 |
xSemaphoreTakeFromISR() | 在中断中获取信号量 |
13.2.计数型信号量
介绍
- 计数型信号量,有多个,可以计数
- 释放多少个,就+n,获取了多少个,就-n
用途
- 事件计数:事件A执行多次释放多次,事件B执行多次获取多次;
- 资源管理:计数值表示可用的资源数量;
开启宏
#define configUSE_COUNTING_SEMAPHORES 1
函数 | 描述 |
---|---|
xSemaphoreCreateCounting() | 使用动态方法创建计数型信号量。 |
xSemaphoreCreateCountingStatic() | 使用静态方法创建计数型信号量 |
uxSemaphoreGetCount() | 获取信号量的计数值 |
13.3.优先级翻转问题
三个任务,优先级为1、2、3,1和3同步执行,公用一个二值信号量;
1.任务1获取了信号量,还没释放被3打断了;
2.3要获取信号量,发现没有,被阻塞了,又回到1;
3.1还没释放信号量,任务2就绪了,被任务2抢占了;
4.任务2执行完,才执行任务1;
5.任务1执行完,释放了信号量,任务3才获取信号量继续执行;
这里发现,任务执行顺序为2,1,3,并不是3,2,1;
13.4.互斥信号量
介绍
- 比二值信号量多了优先级继承机制;
- 当一个高优先级任务需要等待一个低优先级任务的资源时,会提升低任务的优先级;这样避免了高优先级任务长时间等待;
- 高优先级任务获取到资源后,会立马把刚刚低优先级的任务的优先级放回去;
注意!!!!!!!!!!!!
- 不能完全解决优先级翻转问题;
- 不能在中断中使用互斥信号量;1.中断没有任务优先级,优先级继承机制无法生效;2.中断无法阻塞等待一个互斥信号量保护的资源;
互斥信号量相关函数:
函数 | 描述 |
---|---|
xSemaphoreCreateMutex() | 使用动态方法创建互斥信号量。 |
xSemaphoreCreateMutexStatic() | 使用静态方法创建互斥信号量。 |
- 要配置的宏
#define configUSE_MUTEXES 1
14.队列集
**介绍:**一种数据结构,用于管理多个队列
**作用:**一个任务从a、b两个队列里面接收,如果代码a写在前面,b写在后面,那么如果b队列有数据,a阻塞了,并不会收到;
- 开启一下宏
#define configUSE_QUEUE_SETS
函数 | 描述 |
---|---|
xQueueCreateSet() | 创建队列集 |
xQueueAddToSet() | 队列添加到队列集中 |
xQueueRemoveFromSet() | 从队列集中移除队列 |
xQueueSelectFromSet() | 获取队列集中有有效消息的队列 |
xQueueSelectFromSetFromISR() | 在中断中获取队列集中有有效消息的队列 |
15.事件标志组
介绍:相当于一组信号量,有多个位,可以等待一个位,也可以等待多个位
事件标志组是多少位,怎么设置:
Ø 如果 configUSE_16_BIT_TICKS 设置为 1,则事件组内实现的位数(或标志数)为 8; 如果 configUSE_16_BIT_TICKS 设置为 0,则为 24。
Ø 如果 configTICK_TYPE_WIDTH_IN_BITS 设为 TICK_TYPE_WIDTH_16_BITS,则事件组内实现的位数(或标志数)为 8。
Ø 如果 configTICK_TYPE_WIDTH_IN_BITS 设为 TICK_TYPE_WIDTH_32_BITS,则为 24 。
Ø 如果 configTICK_TYPE_WIDTH_IN_BITS 设为 TICK_TYPE_WIDTH_64_BITS,则为 56。
configTICK_TYPE_WIDTH_IN_BITS 只在新版本中有,老版本只有configUSE_16_BIT_TICKS 配置
事件标志组相关函数:
函数 | 描述 |
---|---|
xEventGroupCreate() | 使用动态方式创建事件标志组 |
xEventGroupCreateStatic() | 使用静态方式创建事件标志组 |
xEventGroupClearBits() | 清零事件标志位 |
xEventGroupClearBitsFromISR() | 在中断中清零事件标志位 |
xEventGroupSetBits() | 设置事件标志位 |
xEventGroupSetBitsFromISR() | 在中断中设置事件标志位 |
xEventGroupWaitBits() | 等待事件标志位 |
xEventGroupSync() | 设置事件标志位,并等待事件标志位 |
16.任务通知
介绍:
任务与任务直接发送数据,减少了中间量(消息队列,信号量,事件通知组)
通知接收方法:
Ø 覆盖原值,无论接收任务是否已读取被覆盖的值。
Ø 覆盖原值(仅当接收任务已读取被覆盖的值时)。
Ø 在值中设置一个或多个位。
Ø 对值进行增量(添加 1)。
10.4.0版本之前,是任务通知变量(只能接收一个任务的通知)
10.4.0版本之后,是任务通知组(可以接收多个任务的通知)
- 要开启的宏
configUSE_TASK_NOTIFICATIONS 设为0可以禁用。
任务通知相关函数如下:
函数 | 描述 |
---|---|
xTaskNotify() | 发送通知,带有通知值 |
xTaskNotifyAndQuery() | 发送通知,带有通知值并且保留接收任务的原通知值 |
xTaskNotifyGive() | 发送通知,不带通知值 |
xTaskNotifyFromISR() | 在中断中发送任务通知 |
xTaskNotifyAndQueryFromISR() | |
vTaskNotifyGiveFromISR() | |
ulTaskNotifyTake() | 获取任务通知,可选退出函数时对通知值清零或减1 |
xTaskNotifyWait() | 获取任务通知,可获取通知值和清除通知值的指定位 |
注意:发送通知有相关ISR函数,接收通知没有ISR函数,不能在ISR中接收任务通知。
16.1.任务通知模拟信号量
- 发送通知,不带通知值,xTaskNotifyGive每调用一次,给任务通知组的值+1;
- 获取通知,可以清除通知值,也可以-1;
16.2.任务通知模拟队列
- 发送通知,带通知值 xTaskNotify(task2_handle,num,eSetValueWithOverwrite);
- 获取通知,退出时清除通知值
16.3.任务通知模拟事件标志组
- 发送通知:不带通知值 xTaskNotify(task2_handle,1<<n,eSetBits);
- 获取通知,清除指定位
16.4.任务通知数组新用法
任务通知组,新版本是数组,老版本的index是0,新版本的index可以自己给,给0就第一个,给1就第二个……;并且数组的长度可以修改;
17.软件定时器
**介绍:**软件利用心跳实现的定时器;
**状态:**未创建、已创建、已运行;
**原理:**有一个存放命令的消息队列,把创建、开始等等命令放在消息队列中,软件定时器从消息队列中读命令来执行对应操作;
- 开启以下宏
/* 软件定时器相关定义 */
#define configUSE_TIMERS 1 /* 1: 使能软件定时器, 默认: 0。使能后需指定下面3个 */
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1) /* 定义软件定时器任务的优先级 */
#define configTIMER_QUEUE_LENGTH 5 /* 定义软件定时器命令队列的长度*/
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 2) /* 定义软件定时器任务的栈空间大小*/
软件定时器相关函数如下:
函数 | 描述 |
---|---|
xTimerCreate() | 动态方式创建软件定时器 |
xTimerCreateStatic() | 静态方式创建软件定时器 |
xTimerStart() | 开启软件定时器定时 |
xTimerStartFromISR() | 在中断中开启软件定时器定时 |
xTimerStop() | 停止软件定时器定时 |
xTimerStopFromISR() | 在中断中停止软件定时器定时 |
xTimerReset() | 复位软件定时器定时 |
xTimerResetFromISR() | 在中断中复位软件定时器定时 |
xTimerChangePeriod() | 更改软件定时器的定时超时时间 |
xTimerChangePeriodFromISR() | 在中断中更改定时超时时间 |
回调函数:把一个函数当成另一个函数的参数来用;
18.Tickless低功耗模式
在执行空闲任务的时候,会对下一次任务切换的时间进行计算,然后关闭systick,等到需要任务切换的时候,再打开systick,为了避免tick值发生紊乱,会把这段时间的tick应该+的值计算出来加上;即在Tickless模式下,时钟中断频率明显降低,之后会把时钟节拍数补上;
-
利用了STM32的睡眠模式,关CPU时钟;
-
Tickless模式相关配置项(掌握)
配置项 | 说明 |
---|---|
configUSE_TICKLESS_IDLE | 使能低功耗 Tickless 模式,默认0 |
configEXPECTED_IDLE_TIME_BEFORE_SLEEP | 系统进入相应低功耗模式的最短时长,默认2 |
configPRE_SLEEP_PROCESSING(x) | 在系统进入低功耗模式前执行的事务,比如关闭外设时钟 |
configPOST_SLEEP_PROCESSING(x) | 系统退出低功耗模式后执行的事务,比如开启之前关闭的外设时钟 |
- FreeRTOSConfig.h代码清单
#define configUSE_TICKLESS_IDLE 1
#include "freertos_demo.h"
#define configPRE_SLEEP_PROCESSING( x ) PRE_SLEEP_PROCESSING() // 这两个函数可以在进入低功耗和退出低功耗的时候做一些事情,名字可以自己写
#define configPOST_SLEEP_PROCESSING( x ) POST_SLEEP_PROCESSING()
19.内存管理
Ø heap_1:最简单,不允许释放内存。
Ø heap_2:允许释放内存,但不会合并相邻的空闲块。最佳适应算法:选择能满足自己最接近的;
Ø heap_3:简单包装了标准 malloc() 和 free(),以保证线程安全。把所有的内存分成固定大小的块;
Ø heap_4:合并相邻的空闲块以避免碎片化。包含绝对地址放置选项。第一适应算法:选择可用的内存块中第一个;
Ø heap_5:如同 heap_4,能够跨越多个不相邻内存区域的堆。(操作太多)
-
使用动态创建的方法,就是采用内存管理算法自动处理;
-
手动的内存管理相关函数
函数 | 描述 |
---|---|
void * pvPortMalloc( size_t xWantedSize ); | 申请内存 |
void vPortFree( void * pv ); | 释放内存 |
size_t xPortGetFreeHeapSize( void ); | 获取当前空闲内存的大小 |
LEEP_PROCESSING( x ) PRE_SLEEP_PROCESSING() // 这两个函数可以在进入低功耗和退出低功耗的时候做一些事情,名字可以自己写 | |
#define configPOST_SLEEP_PROCESSING( x ) POST_SLEEP_PROCESSING() |
# 20.内存管理
Ø heap_1:最简单,不允许释放内存。
Ø heap_2:允许释放内存,但不会合并相邻的空闲块。**最佳适应算法**:选择能满足自己最接近的;
Ø heap_3:简单包装了标准 malloc() 和 free(),以保证线程安全。把所有的内存分成固定大小的块;
Ø heap_4:合并相邻的空闲块以避免碎片化。包含绝对地址放置选项。**第一适应算法**:选择可用的内存块中第一个;
Ø heap_5:如同 heap_4,能够跨越多个不相邻内存区域的堆。(操作太多)
- 使用动态创建的方法,就是采用内存管理算法自动处理;
- 手动的内存管理相关函数
| **函数** | **描述** |
| ------------------------------------------ | ---------------------- |
| void * pvPortMalloc( size_t xWantedSize ); | 申请内存 |
| void vPortFree( void * pv ); | 释放内存 |
| size_t xPortGetFreeHeapSize( void ); | 获取当前空闲内存的大小 |