基于正点原子视瓶的FreeRTOS的学习

文章深入介绍了FreeRTOS实时操作系统的特性、与裸机的区别、任务调度机制、挂起与恢复API、任务状态管理以及移植过程。重点讲解了抢占式、时间片调度及任务状态切换原理,同时提供了任务挂起与恢复的实践方法,并概述了FreeRTOS的系统初始化和任务调度器启动流程。
摘要由CSDN通过智能技术生成

一、FreeRTOS

1.简介
  • 免费的嵌入式实时操作系统

  • 使用多,2019你年占有18%

  • 工作上必不可少

  • 无商业风险

  • 可裁剪、轻量级,核心代码9000+行

  • 简单,移植性好

  • 任务优先级软件上,分配没有限制

  • 硬件上STM32只有32位,0~31优先级

  • 任务堆栈大小,适应硬件

  • 资料

    • FreeRTOS官网
      • 英文
      • 文档较小
    • 正点原子学习资料
2.与裸机的区别
  • 裸机称为前后台系统,前台系统指的是中断服务函数
  • 后台系统指大循环,应用程序
  • 打完游戏才能回复消息,回完信息才能打游戏
  • 实时性差 应用程序轮流执行
  • 实时系统
    • 任务带有堆栈
      • 用于保存局部变量
      • 任务的上下文信息
    • 中断可以打断人以人物
    • 任务可以同等优先级
    • 高优先级任务一直运行
    • 低优先级任务无法运行

二、基础知识

1.任务调度器
  • 使用调度算法决定执行哪个任务

  • 三种调度方式

    • 抢占式
      • 针对优先级不同的任务
      • 数字越大优先级越高

在这里插入图片描述

  • 时间片调度

    • 代码中设置1ms的周期

    • 针对优先级相同的任务
      在这里插入图片描述

    • 任务调度器会在每一次系统时钟节拍到的时候切换任务

    • 时间片大小,取决为滴答定时器中断周期

    • 没有用完的时间片不会再使用,下次任务Task3还是按照一个时间片的时钟节拍运行

  • 协程式调度

    • 当前任务会一直执行,不会被高优先级任务打断
    • 少用
2.任务状态

在这里插入图片描述

  • 除了运行态,其他任务状态的任务都有其对应的任务状态列表
1.运行态

正在执行的任务

  • 从就绪态列表中挑选最高优先级任务去执行
2.就绪态

任务准备好能够执行,还没执行

  • 就绪列表
  • pxReadTaskLists
  • 相同优先级任务会连接在同一个就绪列表上
3.阻塞态

任务因延时或等待外部时间发生

  • 阻塞列表

  • pxDelayedTaskList

4.挂起态

类似暂停调用函数()进入挂起 ,解挂后才能进入就绪态

  • 挂机列表
  • xSuspendedTaskList

三、FreeRTOS移植

1.获取源码
  • 官网

  • 正点原子

    • 解压相关文件
  • 源码内容

    • FreeRTOS 内核

    • Demo 演示例程

      • 支持不同芯片型号
      • 支持多种芯片架构
    • license 相关许可

    • Source 源码
      在这里插入图片描述

      • include 包含头文件
      • portable 包含移植文件
      • croutine 协程相关文件
      • event_group 事件相关文件
      • list.c 列表相关文件
      • queue.c 队列相关文件
      • stream_buffer 流式缓存区
      • task.c 任务相关文件
      • timers.c 软件定时器相关文件
    • Test 测试代码

    • portable

      • 连接硬件的桥梁
      • 必用
        • Keil (内容指向RVDS文件夹)
        • MemMang (不同内核芯片的移植文件)
        • RVDS (内存管理文件)
    • FreeRTOS-plus 组件

    2.移植步骤

    添加源码到基础工程

    FreeRTOSConfig.h 添加配置文件

    修改SYSTEM文件

    sys.c

    delay.c

    usart.c

    修改中断相关文件

    systick中断

    SVC中断

    PendSV中断

    添加应用程序

    验证移植是否正确

四、任务挂机与恢复

1.API函数
  • 挂起任务vTaskSuspend()挂起任务的任务句柄

    • 使用此函数将 INCLUDE_vTaskSuspend 配置为1
    • 无论优先级如何,被挂起的任务都不再执行,直至恢复
    • vTaskSuspend(NULL)代表挂起任务自身(当前正在运行的)
  • 恢复被挂起的任务vTaskResume()在任务中恢复

    • 传入任务句柄恢复挂起的任务
    • 使用此函数将 INCLUDE_vTaskSuspend 配置为1
    • 无论任务被vTaskSuspend()挂起多少次,只要在任务中调用了vTaskResume()一次,就可以连续运行。且被恢复的任务进入就绪态
  • 在中断中恢复被挂起的任务xTaskResumeFromISR()

    • 使用此函数将 INCLUDE_vTaskSuspend 配置为1
    • 使用此函数将 INCLUDE_xTaskResumeFromISR 配置为1
    • 以上都要配置
    • 中断服务程序中使用RTOS的函数,则中断优先级不能高于RTOS所管理的范围**(5~15)**

在这里插入图片描述

  • 中断优先级数值越小,越优先(0~4)不在范围内
  • 任务优先级数字越大,越优先
    在这里插入图片描述

判断返回值,是否执行任务切换

1.1挂起
  • 挂起任务类似暂停,可恢复;删除任务,无法恢复
  • 恢复:恢复被挂起的任务
  • FromISR:带有FromISR后缀专用于中断函数中的API函数
2.挂起实验

在这里插入图片描述

3.注意
  • 设置好抢占优先级分组,组4,同时 抢占优先级>=5
  • 不然会报错
  • 外部中断初始化需要,使能时钟并且端口复用
if(BEPP_TASK_Handle!=NULL)
{
    vTaskSuspend(BEPP_TASK_Handle);
    vTaskSuspend(led_task_Handle);
    printf("\r\n任务已挂起\r\n");
}
if(flag1)//key2按下删除任务
{
    if(BEPP_TASK_Handle!=NULL)
    {
        vTaskResume(BEPP_TASK_Handle);
        vTaskResume(led_task_Handle);
        printf("\r\n任务已解挂\r\n");
    }

在这里插入图片描述

3.1中断函数
void KEY2_IRQHandler(void)
{
	BaseType_t YieldRequired;
  //确保是否产生了EXTI Line中断

	if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE) != RESET) 
	{
			YieldRequired=xTaskResumeFromISR(BEPP_TASK_Handle);
		printf("\r\n恢复BEEP的运行!\r\n");
		if(YieldRequired==pdTRUE)
		{
		/*如果函数 xTaskResumeFromISR()返回值为 pdTRUE,那么说明要恢复的这个
		任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务),所以在
		退出中断的时候一定要进行上下文切换!*/
			portYIELD_FROM_ISR(YieldRequired);
		}
		EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE);     
	}  
}
4函数解析
4.1任务挂起函数
  • 结合函数源码进行学习查看

在这里插入图片描述

4.2任务恢复函数

在这里插入图片描述

4.3中断任务恢复

在这里插入图片描述

4.4实验任务

在这里插入图片描述

五、中断管理

1.什么是中断
  • 让CPU打断正常运行的程序,转而去处理紧急的(程序),是中断。

在这里插入图片描述

  • 中断步骤
    在这里插入图片描述
2.中断优先级分组
  • ARM Cortex-M 使用了8位宽的寄存器来配置中断的优先等级。

    • ​ 即支持0~255个分级
    • STM32只用了中断优先级配置寄存器的高四位**[7:4**],所以STM32提供了最大16级的中断
      • 数值越小越优先
      • 抢占优先级,高优先级的中断可以打断级别低的
      • 子优先级:
        • 同时发生具有相同抢占优先级的两个中断时,子优先级数值小的优先执行
        • 若1级正在执行,则1执行完后,级别0再执行
  • 分组方式

    • 官网文档有说明中断
    • hal库
    • 标准库
      在这里插入图片描述
  • 优先级分组设置 特点

    • freeRTOS管理的最高优先级是5,所以抢占优先级设置不能大于5,应设置位5~15

在这里插入图片描述

3.中断相关寄存器
  • 每个寄存32位大小
  • 通过地址偏移设置相关寄存器

在这里插入图片描述

  • 中断屏蔽寄存器

  • 优先级设置高四位有效,要设置5,则要左移四位–>0x50

在这里插入图片描述

3.1 FreeRTOS 如何设置PendSV和Systick中断优先级
  • PendSV和SysTick设置最低优先级,保证系统任务切换不会阻塞系统其他中断的响应

在这里插入图片描述

3.2关中断程序实现

在这里插入图片描述

3.3开中断函数

在这里插入图片描述

4.中断服务函数使用注意
  • 中断服务函数的优先级需在FreeRTOS所管理的范围内
  • 中断服务函数里边调用FreeRTOS的API函数,必须带**“FromISR”**后缀的函数
  • 优先级分组为Group4,抢占优先级大于4
5.中断管理实验
5.1实验简介

在这里插入图片描述

六、临界段代码保护

​ 临界段的代码也叫临界区,必须完整运行,不能被打断。

​ 使用场合:

  1. IIC,SPI,严格时序外设,单总线
  2. 系统要求
  3. 用户自身需求
  4. 中断,任务调度可以打断当前程序
1.临界段函数介绍
函数描述
taskENTER_CRITICAL()任务级进入临界段
taskEXIT_CRITICAL()任务级退出临界段
taskENTER_CRITICAL_FROM_ISR中断级进入临界段
taskEXIT_CRITICAL_FROM_ISR中断级退出临界段
1.1示例:
  • 成对使用支持嵌套
  • 总中断的开关
  • 任务调度开关
/*临界区代码*/
taskENTER_CRITICAL();进入
/*
	临界区代码
*/
taskEXIT_CRITICAL();退出
/*中断级临界区代码*/
uint32_t save_status;
save_status = taskENTER_CRITICAL_FROM_ISR();
/*
	临界区代码
*/
taskEXIT_CRITICAL_FROM_ISR(save_status);

2.任务调度器的挂起和恢复
  • 调用此函数不需要关闭中断
2.1函数介绍
函数描述
vTaskSuspendAll()挂起任务调度器
xTskResumeAll()恢复任务调度器
2.2使用示例
vTaskSuspendAll();
xTaskResumeAll();
/*
	.....
	.....
	.....
*/
xTaskResumeAll();
/*
	1.与临界区不一样的是,挂起任务调度器,未关闭中断
	2.防止任务之间的资源争夺,中断可以直接响应
	3.挂起调度器的方式,适用于临界区位于任务与任务之间,不用延迟中断		的响应
*/

七、列表和列表项

1.简介
  • 是一种数据结构,追踪任务,类似链表(列表),节点(列表项)。
    在这里插入图片描述
2.列表结构体定义
  • 文件参考list.c/.h

  • 15用来检查列表完整性

  • 设置宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 1

  • 默认不开启

  • 2:uxNumberOfItems记录列表中的列表项数量

  • 3:pxIndex用来记录当前列表项索引,用于遍历列表

  • 4:列表中最后一个列表项,用来表示列表结束

typedef struct xLIST
{
	listFIRST_LIST_INTEGRITY_CHECK_VALUE    //  1 
	configLIST_VOLATILE UBaseType_t uxNumberOfItems; //2 列表项数量
	ListItem_t * configLIST_VOLATILE pxIndex; //3 遍历列表项的指针
	MiniListItem_t xListEnd; //4末尾列表项
	listSECOND_LIST_INTEGRITY_CHECK_VALUE //5
} List_t;

在这里插入图片描述

3.列表项定义
3.1 列表项

在这里插入图片描述

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

3.2迷你列表项
  • 仅用于标记列表的末尾和挂在其他插入列表中的列表项

在这里插入图片描述

4.列表和列表项的关系

在这里插入图片描述

5.API函数介绍

在这里插入图片描述

5.1列表初始化

在这里插入图片描述

5.2列表项初始化

在这里插入图片描述

5.3列表项插入(升序)
  • 手册详解

在这里插入图片描述

5.4末尾插入

在这里插入图片描述

5.5删除列表项

在这里插入图片描述

6.插入和删除函数实验
6.1实验简介

在这里插入图片描述

6.2实验代码
List_t testList;	//定义列表
ListItem_t 			ListItem1;//定义列表项1
ListItem_t 			ListItem2;//定义列表项2
ListItem_t 			ListItem3;//定义列表项3
//在任务中初始化列表和列表项
void TASK2(void* pvParameters)
{
    vListInitialise(&testList);//初始化列表
    vListInitialiseItem(&ListItem1);//初始化列表项
    vListInitialiseItem(&ListItem2);//初始化列表项
    vListInitialiseItem(&ListItem2);//初始化列表项
    ListItem1.xItemValue=40;
    ListItem2.xItemValue=60;
    ListItem3.xItemValue=50;
    //第一步  串口显示地址

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rn3zVycm-1690029448126)(image/1690014151556.png)]

	vListInsert(&testList,&ListItem1);//插入列表项1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PI2ng3aK-1690029448126)(image/1690014205428.png)]

	vListInsert(&testList,&ListItem2);//插入列表项2

在这里插入图片描述

	vListInsert(&testList,&ListItem3);//插入列表项3

在这里插入图片描述

	uxListRemove(&ListItem2);//删除列表项2

在这里插入图片描述

	testList.pxIndex=testList.pxIndex->pxNext;			//pxIndex向后移一项,这样pxIndex就会指向ListItem1。
		vListInsertEnd(&testList,&ListItem2);				//列表末尾添加列表项ListItem2
}

在这里插入图片描述

八、任务调度器

1.开启任务调度器
  • 参考CortexM3权威指南 与内核
1.1 vTaskStartSchedule()
  • 作用启动任务调度器
  • 不开启任务不调度不执行
  • 内部实现
    1. 创建空闲任务
    2. 如果使能软件定时器,则创建软件定时器任务
    3. 关闭中断,防止调度器开启之前或过程中,受中断干扰,会在运行第一个任务时打开中断
    4. 初始化全局变量,并将任务调度器的运行标志设置为已运行
    5. 初始化任务运行时间统计功能的时基定时器
    6. 调用函数xPortStartScheduler
1.2 xPortStartScheduler
  • 用于完成启动任务调度器中与硬件架构相关的配置部分(滴答定时器的配置等),以及启动第一个任务
  • 函数实现

在这里插入图片描述

1.3 启动第一个任务

  • prvStartFirstTask();

,&ListItem2); //列表末尾添加列表项ListItem2
}

八、任务调度器

1.开启任务调度器
  • 参考CortexM3权威指南 与内核
1.1 vTaskStartSchedule()
  • 作用启动任务调度器
  • 不开启任务不调度不执行
  • 内部实现
    1. 创建空闲任务
    2. 如果使能软件定时器,则创建软件定时器任务
    3. 关闭中断,防止调度器开启之前或过程中,受中断干扰,会在运行第一个任务时打开中断
    4. 初始化全局变量,并将任务调度器的运行标志设置为已运行
    5. 初始化任务运行时间统计功能的时基定时器
    6. 调用函数xPortStartScheduler
1.2 xPortStartScheduler
  • 用于完成启动任务调度器中与硬件架构相关的配置部分(滴答定时器的配置等),以及启动第一个任务
  • 函数实现

1.3 启动第一个任务

  • prvStartFirstTask();
    在这里插入图片描述
  • MSP指针在这里插入图片描述
  • vPortSVCHandler() 启动第一次任务时会调用一次,以后均不调用

在这里插入图片描述

  • 出栈、压栈汇编指令

在这里插入图片描述

2.任务切换
  • 本质是CPU寄存器的切换。
  • 任务的寄存器值加载到CPU寄存器
  • 任务1被更高优先级的任务2打断,先记录打断现场(好恢复),再将任务2加载到CPU寄存器,执行完回到任务1.
2.1 切换步骤

在这里插入图片描述

2.2 示意图
  • 任务切换过程在PendSV中断服务函数里实现

在这里插入图片描述

3.PendSV如何触发
  1. 滴答定时器中断调用
  2. 执行系统相关API函数:portYIELD()
  3. 本质通过向中断控制和状态寄存器ICSR的bit28写入1挂起PendSV来启东PendSV来启动中断
  4. P131 权威指南

在这里插入图片描述

九、时间片调度

  • 针对任务优先级不同的任务,有两种调度方式
    • 时间片调度 针对相同优先级
    • 抢占式调度 针对不同优先级
1.简介

同等优先级任务轮流地享有相同的CPU时间(可设置),叫时间片,一个时间片就是Systick的中断周期

1.1 图示

在这里插入图片描述

1.2 流程解释

1、任务 3 正在运行。
2、这时一个时钟节拍中断(滴答定时器中断)发生,任务 3 的时间片用完,但是任务 3 还
没有执行完。
3、FreeRTOS 将任务切换到任务 1,任务 1 是优先级 N 下的下一个就绪任务。
4、任务 1 连续运行至时间片用完。
5、任务 3 再次获取到 CPU 使用权,接着运行。
6、任务 3 运行完成,调用任务切换函数 portYIELD()强行进行任务切换放弃剩余的时间片,
从而使优先级 N 下的下一个就绪的任务运行。
7、FreeRTOS 切换到任务 1。
8、任务 1 执行完其时间片。

1.3注意

使用时间片调度

  • configUSE_PREEMPTION 和宏 configUSE_TIME_SLICING 必须
    1
  • 时间片的长度由宏 configTICK_RATE_HZ 确定
  • configTICK_RATE_HZ1000,那么一个时间片的长度就是 1ms
2.实验设计
  1. start_task:用来创建其他 2 个任务。
  2. task1_task :控制 LED0 灯闪烁,并且通过串口打印 task1_task 的运行次数。
  3. task2_task :控制 LED1 灯闪烁,并且通过串口打印 task2_task 的运行次数。
/*
	方便观察将宏 configTICK_RATE_HZ 设置为 20
	以后滴答定时器的中断周期就是 50ms 
*/
#define configTICK_RATE_HZ   (20) 

#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 128 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄

void start_task(void *pvParameters); //任务函数
#define TASK1_TASK_PRIO 2 //任务优先级 (1)
#define TASK1_STK_SIZE 128 //任务堆栈大小
TaskHandle_t Task1Task_Handler; //任务句柄

void task1_task(void *pvParameters); //任务函数
#define TASK2_TASK_PRIO 2 //任务优先级 (2)
#define TASK2_STK_SIZE 128 //任务堆栈大小
TaskHandle_t Task2Task_Handler; //任务句柄
void task2_task(void *pvParameters); //任务函数

void task1_task(void *pvParameters)
{
    u8 task1_num=0;
	while(1)
    {
        task1_num++; //任务 1 执行次数加 1 注意 task1_num1 加到 255 的时候会清零!!
        LED0=!LED0;
        taskENTER_CRITICAL(); //进入临界区
        printf("任务 1 已经执行:%d 次\r\n",task1_num);
        taskEXIT_CRITICAL(); //退出临界区
        //延时 10ms,模拟任务运行 10ms,此函数不会引起任务调度
        delay_xms(10); (1)
    }
}
//task2 任务函数
void task2_task(void *pvParameters)
{
	u8 task2_num=0;
	while(1)
	{
    task2_num++; //任务 2 执行次数加 1 注意 task2_num1 加到 255 的时候会清零!!
    LED1=!LED1;
    taskENTER_CRITICAL(); //进入临界区
    printf("任务 2 已经执行:%d 次\r\n",task2_num);
    taskEXIT_CRITICAL(); //退出临界区
    //延时 10ms,模拟任务运行 10ms,此函数不会引起任务调度
    delay_xms(10); (2)
    }
}


2.1 注意事项
  • 任务2的优先级比任务1的优先级高时,使用的是死延时delay_xms(),此时没有没有系统的阻塞延时,所以就不会进入任务调度,因此导致任务1不会被执行
    • 使用delay_xms()时,任务2不会放弃CPU的使用权,因此任务1得不到执行
  • 打印函数需要进行临界保护,否则有可能,任务执行到一半(打印到一半),去到另一个任务打印去了,从而导致打印错误,
    • 临界保护内的代码量要少,不要影响其他任务和中断。
  • 野火好像没有提供这样的死延时,有待查找。

十、任务相关API函数

1.函数相关描述
  • 在官网也可以查询其用法
函数描述
uxTaskPriorityGet()查询某个任务的优先级
vTaskPrioritySet()改变某个任务的任务优先级
uxTaskGetSystemState()获取系统中任务状态
vTaskGetInfo()获取某个任务信息
xTaskGetApplicationTaskTag()获取某个任务的标签(Tag)值
xTaskGetCurrentTaskHandle()获取当前正在运行的任务的任务句柄
xTaskGetHandle()根据任务名字查找某个任务的句柄
xTaskGetIdleTaskHandle()获取空闲任务的任务句柄
uxTaskGetStackHighWaterMark()获取任务的堆栈的历史剩余最小值,FreeRTOS中叫做“高水位线”
eTaskGetState()获取某个任务的壮态,这个壮态是 eTaskState 类型。
pcTaskGetName()获取某个任务的任务名字。
xTaskGetTickCount()获取系统时间计数器值。
xTaskGetTickCountFromISR()在中断服务函数中获取时间计数器值
xTaskGetSchedulerState()获取任务调度器的壮态,开启或未开启。
uxTaskGetNumberOfTasks()获取当前系统中存在的任务数量。
vTaskList()以一种表格的形式输出当前系统中所有任务的详细信息。
vTaskGetRunTimeStats()获取每个任务的运行时间。
vTaskSetApplicationTaskTag()设置任务标签(Tag)值。
SetThreadLocalStoragePointer()设置线程本地存储指针
GetThreadLocalStoragePointer()获取线程本地存储指针
2.函数详解
2.1、函数 uxTaskPriorityGet()

函数作用

​ 用来获取任务的优先级,使用此函数时 宏 INCLUDE_uxTaskPriorityGet定义为1

函数原型

UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask )
// xTask 任务句柄
// 返回值:获取相应任务的优先级
2.2、函数 vTaskPrioritySet()

函数作用

​ 用于改变某一个任务的任务优先级,使用它时,INCLUDE_vTaskPrioritySet 定义为 1

函数原型

void vTaskPrioritySet( TaskHandle_t xTask,
						UBaseType_t uxNewPriority )
/*
	参数:
	xTask: 要查找的任务的任务句柄。
	uxNewPriority: 任务要使用的新的优先级,可以是 0~ configMAX_PRIORITIES – 1
	返回值:无
*/   
2.3、函数 uxTaskGetSystemState()

函数作用

​ 用于获取系统中所有任务的任务状态,每个任务的壮态信息保存在一个 TaskStatus_t类型的结构体里面,这个结构体里面包含了任务的任务句柄任务名字堆栈优先级等信息

函数原型

UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray,
                                    const UBaseType_t uxArraySize,
                                    uint32_t * const pulTotalRunTime)
 /*
	参数:
	pxTaskStatusArray: 指向 TaskStatus_t 结构体类型的数组首地址,每个任务至少需要一个
		TaskStatus_t 结 构 体 , 任 务 的 数 量 可 以 使 用 函 数
		uxTaskGetNumberOfTasks()。结构体 TaskStatus_t 在文件 task.h 中有如下
        uxArraySize 太小的话返回值可能为 0。
         uxArraySize: 保存任务壮态数组的数组的大小。
        pulTotalRunTime: 如果 configGENERATE_RUN_TIME_STATS 为 1 的话此参数用来保存系
        统总的运行时间。
        返回值: 统计到的任务壮态的个数,也就是填写到数组 pxTaskStatusArray 中的个
        数,此值应该等于函数 uxTaskGetNumberOfTasks()的返回值。如果参数
		
    定义:
    
*/  
    typedef struct xTASK_STATUS
    {
        TaskHandle_t xHandle; //任务句柄
        const char * pcTaskName; //任务名字
        UBaseType_t xTaskNumber; //任务编号
        eTaskState eCurrentState; //当前任务壮态,eTaskState 是一个枚举类型
        UBaseType_t uxCurrentPriority; //任务当前的优先级
        UBaseType_t uxBasePriority; //任务基础优先级
        uint32_t ulRunTimeCounter;//任务运行的总时间
        StackType_t * pxStackBase; //堆栈基地址
        uint16_t usStackHighWaterMark; //从任务创建以来任务堆栈剩余的最小大小,此
        //值如果太小的话说明堆栈有溢出的风险。
        

} TaskStatus_t;
如何使用
\\经测试taskNum1与taskNum的数量相等
void LED_TASK(void* pvParameters)
{
    uint8_t uXianJi=0,i=0;
    //记录任务个数 用来分配内存
	UBaseType_t  taskNum=0; 
	UBaseType_t  taskNum1=0;
	TaskStatus_t * status_array = 0;
	//获取任务数量
	taskNum=uxTaskGetNumberOfTasks();
    //分配内存   可以参考官网例程
    status_array = pvPortMalloc(  sizeof( TaskStatus_t )*taskNum  );
    //查找所有任务信息
    //得到任务个数
	taskNum1=uxTaskGetSystemState(  										status_array, 
									taskNum, 
									NULL);
printf("\r\n任务名\t\t任务优先级\t任务\r\n");
    //打印显示
    //每一个数组成员都是结构体
	for(i=0;i<taskNum1;i++)
	{
                   printf("%s\t\t%ld\t%ld\r\n",
       status_array[i].pcTaskName,							   status_array[i].uxCurrentPriority,				status_array[i].xTaskNumber);
	}
}

2.4 、函数 vTaskGetInfo()

函数作用

​ 获取单个任务状态 宏 configUSE_TRACE_FACILITY 定义为1

函数原型

void vTaskGetInfo( TaskHandle_t xTask,
                    TaskStatus_t * pxTaskStatus,
                    BaseType_t xGetFreeStackSpace,
                    eTaskState eState )
    
/*
	xTask: 要查找的任务的任务句柄。
    pxTaskStatus: 指向类型为 TaskStatus_t 的结构体变量。
    xGetFreeStackSpace: 在结构体 TaskStatus_t 中有个字段 usStackHighWaterMark 来保存自任务
    运行以来任务堆栈剩余的历史最小大小,这个值越小说明越接近堆栈溢
    出,但是计算这个值需要花费一点时间,所以我们可以通过将
    xGetFreeStackSpace设置为pdFALSE来跳过这个步骤,当设置为pdTRUE
    的时候就会检查堆栈的历史剩余最小值。
    eState: 结构体 TaskStatus_t 中有个字段 eCurrentState 用来保存任务运行壮态,
    这个字段是 eTaskState 类型的,这是个枚举类型,在 task.h 中有如下定
义:
typedef enum
{
    eRunning = 0, //运行壮态
    eReady, //就绪态
    eBlocked, //阻塞态
    eSuspended, //挂起态
    eDeleted, //任务被删除
    eInvalid //无效
} eTaskState;
获取任务运行壮态会耗费不少时间,所以为了加快函数 vTaskGetInfo()的执行
速度结构体 TaskStatus_t 中的字段 eCurrentState 就可以由用户直接赋值,
参数 eState 就是要赋的值。如果不在乎这点时间,那么可以将 eState 设置为
eInvalid,这样任务的壮态信息就由函数 vTaskGetInfo()去想办法获取。
返回值: 无。
*/
函数使用
与上同理
TaskStatus_t * status_array_single = 0;
status_array_single= pvPortMalloc(  sizeof( TaskStatus_t ) );//分配内存
	vTaskGetInfo(	led_task_Handle,
					status_array_single,
					pdTRUE,
					eInvalid);
	printf("\r\n任务名:%s",status_array_single->pcTaskName);
	printf("\r\n任务优先级:%ld",status_array_single->uxCurrentPriority);
	printf("\r\n任务编号:%ld",status_array_single->xTaskNumber);
2.5、 函数 xTaskGetApplicationTaskTag()

函数作用

​ 获取任务的 Tag(标签)值 宏 configUSE_APPLICATION_TASK_TAG 必须为1

函数原型

TaskHookFunction_t xTaskGetApplicationTaskTag( TaskHandle_t xTask )
/*
	参数:
    xTask: 要获取标签值的任务对应的任务句柄,如果为 NULL 的话就获取当前正在运
    行的任务标签值。
    返回值: 任务的标签值。
*/
2.6 、数 函数 xTaskGetCurrentTaskHandle()

函数作用

获取当前任务的任务句柄 宏INCLUDE_xTaskGetCurrentTaskHandle 应该为 1

函数原型

TaskHandle_t xTaskGetCurrentTaskHandle( void )
   /*
   	参数: 无
   	返回值: 当前任务的任务句柄。
   */
2.7、函数 xTaskGetHandle()

函数作用

​ 根据任务名字获取任务的任务句柄 INCLUDE_xTaskGetHandle 应该设置为 1

函数原型

TaskHandle_t xTaskGetHandle( const char * pcNameToQuery )
   
 /*
   	参数:
   	pcNameToQuery : 任务名,C 语言字符串
   	返回值:
       NULL : 没有任务名 pcNameToQuery 所对应的任务
       其他值: 任务名 pcNameToQuery 所对应的任务句柄
   */
函数使用
TaskHandle_t  task_handle=0;//定义	
task_handle=xTaskGetHandle( "LED_TASK" );//任务名字,返回句柄
printf("\r\n任务句柄%#x\r\n",task_handle);//打印地址 16进制 %#x  #带前缀

2.8、函数 xTaskGetIdleTaskHandle()

函数作用

​ 用返回空闲任务的任务句柄 INCLUDE_xTaskGetIdleTaskHandle 应该设置为 1

函数原型

TaskHandle_t xTaskGetIdleTaskHandle( void )
   
 /*
   	参数: 无
   	返回值: 空闲任务的任务句柄。
   */
2.9、函数 uxTaskGetStackHighWaterMark()

函数作用

​ 用返回空闲任务的任务句柄 INCLUDE_xTaskGetIdleTaskHandle 应该设置为 1

函数原型

TaskHandle_t xTaskGetIdleTaskHandle( void )
   
 /*
   	参数: 无
   	返回值: 空闲任务的任务句柄。
   */
2.10、函数 eTaskGetState()

​ 用于查询某个任务的运行壮态,比如:运行态、阻塞态、挂起态、就绪态等,返回

值是个枚举类型。**INCLUDE_eTaskGetState ** 必须为 1

函数原型

eTaskState eTaskGetState( TaskHandle_t xTask )
   
 /*
		 xTask : 要查询的任务的任务句柄。
   	返回值: 返回值为 eTaskState 类型,这是个枚举类型,在文件 task.h 中有定义,前面讲解
   	 函数 vTaskGetInfo()的时候已经讲过了
   */
2.11、函数 pcTaskGetName()

函数作用

​ 根据某个任务的任务句柄来查询这个任务对应的任务名**INCLUDE_uxTaskGetStackHighWaterMark ** 必须为 1

函数原型

char *pcTaskGetName( TaskHandle_t xTaskToQuery )
   
 /*
		 参数:
xTaskToQuery : 要查询的任务的任务句柄,此参数为 NULL 的话表示查询自身任务(调
用函数 pcTaskGetName())的任务名字
返回值: 返回任务所对应的任务名。
   */
2.12、函数 xTaskGetTickCount()

函数作用

​ 用于查询任务调度器从启动到现在时间计数器 xTickCount 的值。xTickCount 是系统的时钟节拍值,并不是真实的时间值。每个滴答定时器中断 xTickCount 就会加 1,一秒钟滴答定时器中断多少次取决于宏 configTICK_RATE_HZ。理论上 xTickCount 存在溢出的问题,但是这个溢出FreeRTOS 的内核没有影响,但是如果用户的应用程序有使用到的话就要考虑溢出了。什么时候溢出取决于宏 configUSE_16_BIT_TICKS,当此宏为 1 的时候 xTixkCount 就是个 16 位的变量,当为 0 的时候就是个 32 位的变量。

函数原型

TickType_t xTaskGetTickCount( void )
   
 /*
		 参数: 无。
返回值: 时间计数器 xTickCount 的值。
   */
2.13、函数 xTaskGetTickCountFromISR()

函数作用

​ **xTaskGetTickCount()**的中断级版本,用于在中断服务函数中获取时间计数器

xTickCount 的值,

函数原型

TickType_t xTaskGetTickCountFromISR( void )
   
 /*
		 参数: 无。
   	返回值: 时间计数器 xTickCount 的值。
   */
2.14、函数 函数 xTaskGetSchedulerState()

函数作用

​ 用于获取 FreeRTOS 的任务调度器运行情况:运行关闭?还是挂起!要使用此函

数的话宏 INCLUDE_xTaskGetSchedulerState 必须为 1

函数原型

BaseType_t xTaskGetSchedulerState( void )
   
 /*
		 参数: 无。
       返回值:
       taskSCHEDULER_NOT_STARTED: 调 度 器 未 启 动 , 调 度 器 的 启 动 是 通 过 函 数
       vTaskStartScheduler() 来 完 成 , 所 以 在 函 数
       vTaskStartScheduler() 未 调 用 之 前 调 用 函 数
       xTaskGetSchedulerState()的话就会返回此值。
       taskSCHEDULER_RUNNING : 调度器正在运行。
       taskSCHEDULER_SUSPENDED: 调度器挂起。
   */
2.15、函数 uxTaskGetNumberOfTasks()

函数作用

​ 此函数用于查询系统当前存在的任务数量

函数原型

UBaseType_t uxTaskGetNumberOfTasks( void )
   
 /*
		参数: 无。
   	返回值: 当前系统中存在的任务数量, 此值=挂起态的任务+阻塞态的任务+就绪态的任务
+空闲任务+运行态的任务。
   */
2.16、函数 vTaskList()

函数作用

​ 此函数会创建一个表格来描述每个任务的详细信息,如图:

configUSE_TRACE_FACILITYconfigUSE_STATS_FORMATTING_FUNCTION置1

  • Name: 创建任务的时候给任务分配的名字。
  • State: 任务的壮态信息,B 是阻塞态,R 是就绪态,S 是挂起态,D 是删除态。
  • Priority:任务优先级。
  • Stack: 任务堆栈的“高水位线”,就是堆栈历史最小剩余大小。
  • Num: 任务编号,这个编号是唯一的,当多个任务使用同一个任务名的时候可以通过此
    编号来做区分。

在这里插入图片描述

函数原型


void vTaskList( char * pcWriteBuffer )
   
 /*
		参数:
   	pcWriteBuffer : 保存任务壮态信息表的存储区。存储区要足够大来保存任务状态信息表。
   	返回值: 无
   */
   
char task_buff[500];
vTaskList(task_buff);
printf("\r\n%s",task_buff);
2.17、 函数 vTaskGetRunTimeStats()

函数作用

​ 函数 vTaskGetRunTimeStats()会将统计到的信息填充到一个表里面,表里面提供了每个任务的运行时间和其所占总时间的百分比

configGENERATE_RUN_TIME_STATSconfigUSE_STATS_FORMATTING_FUNCTIONS必须

都为 1

  • 如果宏 configGENERATE_RUN_TIME_STATS1 的话还需要实现一下几个宏定义:
    portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(),此宏用来初始化一个外设来
    提供时间统计功能所需的时基,一般是定时器/计数器。这个时基的分辨率一定要比 FreeRTOS
    的系统时钟高,一般这个时基的时钟精度比系统时钟的高 10~20 倍就可以了。
  • portGET_RUN_TIME_COUNTER_VALUE() 或者
    portALT_GET_RUN_TIME_COUNTER_VALUE(Time), ,这两个宏实现其中一个就行
    了,这两个宏用于提供当前的时基的时间值。

在这里插入图片描述

函数原型

void vTaskGetRunTimeStats( char *pcWriteBuffer )
   
 /*
		 参数:
   	pcWriteBuffer : 保存任务时间信息的存储区。存储区要足够大来保存任务时间信息。
   	返回值: 无 
   */

函数使用

//该函数一般用于调试,正式代码需要删除
//统计时间占比,(空闲任务)占比大,说明应用程序压力越小
//宏定义好上面两个宏后,还需实现
//任务执行时间,计数值乘以中断周期 10us
extern uint32_t  FreeRTOSRunTimeTicks;
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()  ConfigureTimeForRunTimeStats()                                                                   
#define portGET_RUN_TIME_COUNTER_VALUE()        FreeRTOSRunTimeTicks 

//定时器文件中 
#define            BASIC_TIM6_Period            (10-1)        //10us
#define            BASIC_TIM6_Prescaler         71
//函数实现

void ConfigureTimeForRunTimeStats(void)
{
    //初始化分频系数,技术值,中断
	BASIC_TIM6_NVIC_Config();
	BASIC_TIM6_Mode_Config();
	FreeRTOSRunTimeTicks=0;
}
//中断服务函数
void  BASIC_TIM6_IRQHandler (void)
{
	if ( TIM_GetITStatus( BASIC_TIM6, TIM_IT_Update) != RESET ) 
	{	
		FreeRTOSRunTimeTicks++;
		TIM_ClearITPendingBit(BASIC_TIM6 , TIM_FLAG_Update);  		 
	}		 	
}
2.18、函数 vTaskSetApplicationTaskTag()

函数作用

	为高级用户准备的,此函数用于设置某个任务的标签值 ,这个标签值的具体函数和用法由用户自行决定,FreeRTOS 内核不会使用这个标签值,如果要使用此函数的话宏configUSE_APPLICATION_TASK_TAG 必须为 1 

函数原型

void vTaskSetApplicationTaskTag( TaskHandle_t xTask,
   							TaskHookFunction_t pxHookFunction )
   
 /*
		 xTask : 要设置标签值的任务,此值为 NULL 的话表示设置自身任务的标签值。
   	pxHookFunction: 要设置的标签值,这是一个 TaskHookFunction_t 类型的函数指针,		但是也可以设置为其他值。
   	返回值: 无
   */
2.19、函数SetThreadLocalStoragePointer()

函数作用

​ 设置线程本地存储指针的值,每个任务都有它自己的指针数组来作为线程本地
存储,使用这些线程本地存储可以用来在任务控制块中存储一些应用信息,这些信息只属于任
务 自 己 的 。 线 程 本 地 存 储 指 针 数 组 的 大 小 由 宏
configNUM_THREAD_LOCAL_STORAGE_POINTERS 来决定的。如果要使用此函数的话宏
configNUM_THREAD_LOCAL_STORAGE_POINTERS 不能为 0,宏的具体值是本地存储指针
数组的大小

函数原型

void vTaskSetThreadLocalStoragePointer( TaskHandle_t xTaskToSet,
   									BaseType_t xIndex,
   									void * pvValue )
   
 /*
		 xTaskToSet : 要设置线程本地存储指针的任务的任务句柄,如果是 NULL 的话表示设置任务自身的线程本地存储指针。
   		xIndex: 要设置的线程本地存储指针数组的索引。
   		pvValue: 要存储的值。
   		返回值: 无
   */
2.20、函数 pvTaskGetThreadLocalStoragePointer()

函数作用

此 函 数 用 于 获 取 线 程 本 地 存 储 指 针 的 值 , 如 果 要 使 用 此 函 数 的 话 宏
configNUM_THREAD_LOCAL_STORAGE_POINTERS 不能为 0

函数原型

void *pvTaskGetThreadLocalStoragePointer( TaskHandle_t xTaskToQuery,
   										BaseType_t xIndex )
   
 /*
		 参数:
   	xTaskToSet : 要获取的线程本地存储指针的任务句柄,如果是 NULL 的话表示获取任务自
身的线程本地存储指针。
   	xIndex: 要获取的线程本地存储指针数组的索引。
   	返回值: 获取到的线程本地存储指针的值。
   */

十一、时间管理

1.延时函数介绍
函数描述
vTaskDelay()相对延时
vTaskDelayUnti()绝对延时
1.1、相对延时 vTaskDelay()

指每次延时都是从执行vTaskDelay()开始,直到延时指定的时间结束

  • INCLUDE_vTaskDelay 必须为 1

函数代码

/*
(1)、延时时间由参数 xTicksToDelay 来确定,为要延时的时间节拍数,延时时间肯定要大
于 0。否则的话相当于直接调用函数 portYIELD()进行任务切换。
(2)、调用函数 vTaskSuspendAll()挂起任务调度器。
(3)、调用函数 prvAddCurrentTaskToDelayedList() 将要延时的任务添加到延时列表
pxDelayedTaskList 或 者 pxOverflowDelayedTaskList() 中 。 后 面 会 具 体 分 析 函 数
prvAddCurrentTaskToDelayedList()。
(4)、调用函数 xTaskResumeAll()恢复任务调度器。
(5)、如果函数 xTaskResumeAll()没有进行任务调度的话那么在这里就得进行任务调度。
(6)、调用函数 portYIELD_WITHIN_API()进行一次任务调度。
*/
void vTaskDelay( const TickType_t xTicksToDelay )
{
    BaseType_t xAlreadyYielded = pdFALSE;
    //延时时间要大于 0。
    if( xTicksToDelay > ( TickType_t ) 0U ) (1)
    {
        configASSERT( uxSchedulerSuspended == 0 );
        vTaskSuspendAll(); (2)
    {
        traceTASK_DELAY();
        prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE ); (3)
    }
    	xAlreadyYielded = xTaskResumeAll(); (4)
    }
    else
    {
   	 mtCOVERAGE_TEST_MARKER();
    }
    	if( xAlreadyYielded == pdFALSE ) (5)
    {
    	portYIELD_WITHIN_API(); (6)
    }
    else
    {
    	mtCOVERAGE_TEST_MARKER();
    }
}
1.2、绝对延时 vTaskDelayUnti()
  • 使用时 必须将INCLUDE_vTaskDelayUntil定义为1

指将整个任务的运行周期看成一个整体,适用于需要按照一定频率运行的任务

在这里插入图片描述

  • 注意当同时两个任务分别使用绝对延时和相对延时时,且他们都使用了死延时(死等),高优先级任务会占用时间,导致任务1的延时时间不准确

十二、队列

1.简介

队列简介

  1. 队列是任务到任务,任务到中断,中断到任务数据交流的一种机制(消息传递)

  2. 队列中可以存储数量有限、大小固定的数据,其中的每一个数据叫做队列项目

  3. 队列能够存储队列项目的最大数量称为队列的长度

    在这里插入图片描述

队列特点

  • 数据入队出队方式
    • 读出数据即出队
    • 写入数据即入队
  • FIFO 先进先出的数据存储缓冲机制
    • 先入队的数据会先从队列中被读取
    • 也支持后进先出
  • 数据传递方式
    • 采取实际值传递
      • 将数据拷贝到队列中进行传递
      • 不适合数据量大的,耗时
    • 采取指针传递
      • 传地址时(变量不能更改)
      • 传递大数据,给个地址找到大数据
  • 多任务访问
    • 队列不属于某个任务,任何任务和中断都可以向队列发送、读取消息
  • 出队、入队阻塞
    • 当任务向一个队列发送消息时,可以指定一个阻塞时间,假设当时队列已满无法入队
      • 阻塞时间为0,:返回不等待
      • 阻塞时间为0~port_MAX_DELAY :等待设定的阻塞时间,超过返回不等待
      • 阻塞时间为port_MAX_DELAY : 死等,等到可以入队为止。出队阻塞和入队阻塞类似。
      • 入队阻塞,队列中没数据,要读数据,会阻塞,等到有数据为止
  • 阻塞 图示

在这里插入图片描述

问题

  • 当多个任务写入消息给一个,满队列时,这些任务都会进入阻塞状态,也就是说有多个任务在等待同一个队列空间。队列有空间时,哪个任务会进入就绪态?
    • 优先级最高的任务
    • 如果大家的优先级相同,那等待时间最久的任务会进入就绪态

使用场景

​ 当多个任务操作到全局变量时,由于任务打断机制,会进行多次反复操作,会影响变量的数据,这个时候数据是不安全的
1

2、队列操作过程

在这里插入图片描述

3.队列结构体介绍
typedef struct QueueDefinition
{
    int8_t *pcHead; //指向队列存储区开始地址。
    int8_t *pcTail; //指向队列存储区最后一个字节。
    int8_t *pcWriteTo; //指向存储区中下一个空闲区域。
union
{
    int8_t *pcReadFrom; //当用作队列的时候指向最后一个出队的队列项首地址
    UBaseType_t uxRecursiveCallCount;//当用作递归互斥量的时候用来记录递归互斥量被
    //调用的次数。
} u;
    List_t xTasksWaitingToSend; //等待发送任务列表,那些因为队列满导致入队失败而进
    //入阻塞态的任务就会挂到此列表上。
    List_t xTasksWaitingToReceive; //等待接收任务列表,那些因为队列空导致出队失败而进
    //入阻塞态的任务就会挂到此列表上。
    volatile UBaseType_t uxMessagesWaiting; //队列中当前队列项数量,也就是消息数
    UBaseType_t uxLength; //创建队列时指定的队列长度,也就是队列中最大允许的
    //队列项(消息)数量
    UBaseType_t uxItemSize; //创建队列时指定的每个队列项(消息)最大长度,单位字节
    volatile int8_t cRxLock; //当队列上锁以后用来统计从队列中接收到的队列项数
    //量,也就是出队的队列项数量,当队列没有上锁的话此字
    //段为 queueUNLOCKED
    volatile int8_t cTxLock; //当队列上锁以后用来统计发送到队列中的队列项数量,
    //也就是入队的队列项数量,当队列没有上锁的话此字
    //段为 queueUNLOCKED
    #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) &&\
    ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
    uint8_t ucStaticallyAllocated; //如果使用静态存储的话此字段设置为 pdTURE。
    #endif
    #if ( configUSE_QUEUE_SETS == 1 ) //队列集相关宏
    struct QueueDefinition *pxQueueSetContainer;
    #endif
    #if ( configUSE_TRACE_FACILITY == 1 ) //跟踪调试相关宏
    UBaseType_t uxQueueNumber;
    uint8_t ucQueueType;
	#endif
} xQUEUE;
typedef xQUEUE Queue_t;
3.1 使用队列时
typedef struct QueuePointers
{
    int8_t *pcTail;// 存储区的结束地址
    int8_t *pcReadFrom;//最后一个读取队列的地址
    
}QueuePointers_t;
3.2 用于互斥信号量和递归互斥信号量时
typedef struct SemaphoreData
{
    TaskHandle_t xMutexHolder;// 存储区的结束地址
    UBaseType_t  uxRecursiveCallCount;//最后一个读取队列的地址
    
}SemaphoreData_t;
3.3示意图

在这里插入图片描述

4.创建队列API函数介绍
4.1 函数 xQueueCreate()

动态创建队列

​ 此函数本质上是一个宏,用来动态创建队列,此宏最终调用的是函数 xQueueGenericCreate()

函数原型

QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,
							UBaseType_t uxItemSize)
    
  /*
  	参数:
    uxQueueLength: 要创建的队列的队列长度,这里是队列的项目数。
    uxItemSize: 队列中每个项目(消息)的长度,单位为字节
    返回值:
    其他值: 队列创捷成功以后返回的队列句柄!
    NULL: 队列创建失败。	
  */
4.2 函数 xQueueCreateStatic()

静态方法创建队列

​ 函数本质上也是一个宏,此宏最终调用的是函数 xQueueGenericCreateStatic()

函数原型

QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,
                                 UBaseType_t uxItemSize,
								 uint8_t * pucQueueStorageBuffer,
								 StaticQueue_t * pxQueueBuffer)
    /*
    	参数:
        uxQueueLength: 要创建的队列的队列长度,这里是队列的项目数。
        uxItemSize: 队列中每个项目(消息)的长度,单位为字节
        pucQueueStorage: 指向队列项目的存储区,也就是消息的存储区,这个存储区需要用户自
        行分配。此参数必须指向一个 uint8_t 类型的数组。这个存储区要大于等
        于(uxQueueLength * uxItemsSize)字节。
        pxQueueBuffer: 此参数指向一个 StaticQueue_t 类型的变量,用来保存队列结构体。
        返回值:
        其他值: 队列创捷成功以后的队列句柄!
        NULL: 队列创建失败。
    */
4.3 、函数 xQueueGenericCreate()

函数介绍

​ 函数 **xQueueGenericCreate()**用于动态创建队列,创建队列过程中需要的内存均通过
FreeRTOS 中的动态内存管理函数 **pvPortMalloc()**分配

函数原型

QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
                                 const UBaseType_t uxItemSize,
                                 const uint8_t ucQueueType )
    
    /*
    	参数:
        uxQueueLength: 要创建的队列的队列长度,这里是队列的项目数。
        uxItemSize: 队列中每个项目(消息)的长度,单位为字节。
        ucQueueType : 队列类型,由于 FreeRTOS 中的信号量等也是通过队列来实现的,创建信号
        量的函数最终也是使用此函数的,因此在创建的时候需要指定此队列的用途,
        也就是队列类型,一共有六种类型:
        queueQUEUE_TYPE_BASE 普通的消息队列
        queueQUEUE_TYPE_SET 队列集
        queueQUEUE_TYPE_MUTEX 互斥信号量
        queueQUEUE_TYPE_COUNTING_SEMAPHORE 计数型信号量
        queueQUEUE_TYPE_BINARY_SEMAPHORE 二值信号量
        queueQUEUE_TYPE_RECURSIVE_MUTEX 递归互斥信号量
        函 数 xQueueCreate() 创 建 队 列 的 时 候 此 参 数 默 认 选 择 的 就 是
        queueQUEUE_TYPE_BASE。
        返回值:
        其他值: 队列创捷成功以后的队列句柄!
        NULL: 队列创建失败。
    */
4.4 、函数 xQueueGenericCreateStatic()

函数介绍

​ 此函数用于动态创建队列,创建队列过程中需要的内存需要由用户自行分配好

函数原型

QueueHandle_t xQueueGenericCreateStatic( const UBaseType_t uxQueueLength,
                                        const UBaseType_t uxItemSize,
                                        uint8_t * pucQueueStorage,
                                        StaticQueue_t * pxStaticQueue,
                                        const uint8_t ucQueueType )
     /*
    	参数:
        uxQueueLength: 要创建的队列的队列长度,这里是队列的项目数。
        uxItemSize: 队列中每个项目(消息)的长度,单位为字节
        pucQueueStorage: 指向队列项目的存储区,也就是消息的存储区,这个存储区需要用户自
        行分配。此参数必须指向一个 uint8_t 类型的数组。这个存储区要大于等
        于(uxQueueLength * uxItemsSize)字节。
        pxStaticQueue: 此参数指向一个 StaticQueue_t 类型的变量,用来保存队列结构体。
        ucQueueType : 队列类型。
        返回值:
        其他值: 队列创捷成功以后队列句柄!
        NULL: 队列创建失败。
    */
5.向队列发送消息函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ErUIuX4V-1690684099669)(image/1690117094236.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wfQxZfd9-1690684099670)(image/1690117272788.png)]

5.1 函数介绍表格
分类函数描述
任务级入队函数xQueueSend()发送消息到队列尾部(后向入队),这两个函数是一样的。
xQueueSendToBack()
xQueueSendToFront()发送消息到队列头(前向入队)。
xQueueOverwrite()发送消息到队列,带覆写功能,当队列满了以后自动覆盖掉旧的消息。
中断级入队函数xQueueSendFromISR()发送消息到队列尾(后向入队),这两个函数是一样的,用于中断服务函数
xQueueSendToBackFromISR()
xQueueSendToFrontFromISR()发送消息到队列头(前向入队),用于中断服务函数
xQueueOverwriteFromISR()发送消息到队列,带覆写功能,当队列满了以后自动覆盖掉旧的消息,用于中断服务函数。
5.2、函数 xQueueSend() 、xQueueSendToBack()和 和 xQueueSendToFront()

函数介绍

​ 这三个函数都是用于向队列中发送消息的,这三个函数本质都是宏,其中函数 xQueueSend()
xQueueSendToBack()是一样的,都是后向入队,即将新的消息插入到队列的后面。函数
xQueueSendToToFront()是前向入队,即将新消息插入到队列的前面。然而!这三个函数最后都
是调用的同一个函数:xQueueGenericSend()。这三个函数只能用于任务函数中,不能用于中断
服务函数,中断服务函数有专用的函数,它们以“FromISR”结尾

函数原型

BaseType_t xQueueSend( QueueHandle_t xQueue,
                        const void * pvItemToQueue,
                        TickType_t xTicksToWait);
BaseType_t xQueueSendToBack(QueueHandle_t xQueue,
                            const void* pvItemToQueue,
                            TickType_t xTicksToWait);
BaseType_t xQueueSendToToFront(QueueHandle_t xQueue,
                            const void *pvItemToQueue,
                            TickType_t xTicksToWait);
/*
	参数:
    xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的
    队列句柄。
    pvItemToQueue:指向要发送的消息,发送时候会将这个消息拷贝到队列中。
    xTicksToWait: 阻塞时间,此参数指示当队列满的时候任务进入阻塞态等待队列空闲的最大
    时间。如果为 0 的话当队列满的时候就立即返回;当为 portMAX_DELAY 的
    话就会一直等待,直到队列有空闲的队列项,也就是死等,但是宏
    INCLUDE_vTaskSuspend 必须为 1。
    返回值:
	pdPASS: 向队列发送消息成功!
	errQUEUE_FULL: 队列已经满了,消息发送失败。
*/
5.3、函数 xQueueOverwrite()

函数介绍

此函数也是用于向队列发送数据的 当队列满了以后会覆写掉旧的数据,不管这个旧数据
有没有被其他任务或中断取走。这个函数常用于向那些长度为 1 的队列发送消息,此函数也是
一个宏,最终调用的也是函数 xQueueGenericSend()

函数原型

BaseType_t xQueueOverwrite(QueueHandle_t xQueue,
					const void * pvItemToQueue);
/*
	参数:
    xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的
    队列句柄。
    pvItemToQueue:指向要发送的消息,发送的时候会将这个消息拷贝到队列中。
    返回值:
    pdPASS: 向队列发送消息成功,此函数也只会返回 pdPASS!因为此函数执行过程中不
	在乎队列满不满,满了的话我就覆写掉旧的数据,总之肯定能成功。
*/
5.4 函数 xQueueGenericSend()

函数介绍

​ 此函数才是真正干活的,上面讲的所有的任务级入队函数最终都是调用的此函数

函数原型

BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
                       const void * const pvItemToQueue,
                       TickType_t xTicksToWait,
                       const BaseType_t xCopyPosition )
 /*
   参数:
       xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的
       队列句柄。
       pvItemToQueue:指向要发送的消息,发送的过程中会将这个消息拷贝到队列中。
       xTicksToWait : 阻塞时间。
       xCopyPosition: 入队方式,有三种入队方式:
       queueSEND_TO_BACK: 后向入队
       queueSEND_TO_FRONT: 前向入队
       queueOVERWRITE: 覆写入队。
       上面讲解的入队 API 函数就是通过此参数来决定采用哪种入队方式的。
       返回值:
       pdTRUE: 向队列发送消息成功!
       errQUEUE_FULL: 队列已经满了,消息发送失败。
*/
5.5 函数 xQueueSendFromISR()、 xQueueSendToBackFromISR() 、xQueueSendToFrontFromISR()

函数介绍

​ 这三个函数也是向队列中发送消息的,这三个函数用于中断服务函数中。其中函数 xQueueSendFromISR ()和 xQueueSendToBackFromISR ()是一样的,都是后向入
队,即将新的消息插入到队列的后面。函数 xQueueSendToFrontFromISR ()是前向入队,即将新
消息插入到队列的前面。这三个函数同样调用同一个函数 xQueueGenericSendFromISR ()

函数原型

BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,
                       const void * pvItemToQueue,
                       BaseType_t * pxHigherPriorityTaskWoken);
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue,
                       const void * pvItemToQueue,
                       BaseType_t * pxHigherPriorityTaskWoken);
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,
                       const void * pvItemToQueue,
                       BaseType_t * pxHigherPriorityTaskWoken);
/*
   参数:
   xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的
   队列句柄。
   pvItemToQueue:指向要发送的消息,发送的时候会将这个消息拷贝到队列中。
   pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这
   三个函数来设置的,用户不用进行设置,用户只需要提供一
   个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退
   出中断服务函数之前一定要进行一次任务切换。
   返回值:
   pdTRUE: 向队列中发送消息成功!
   errQUEUE_FULL: 队列已经满了,消息发送失败。
   
   
   可以看出这些函数都没有设置阻塞时间值。原因很简单,这些函数都是在
   中断服务函数中调用的,并不是在任务中,所以也就没有阻塞这一说了!
*/
5.6 函数 xQueueOverwriteFromISR()

函数介绍

​ 此函数是 xQueueOverwrite()的中断级版本,用在中断服务函数中,在队列满的时候自动覆

写掉旧的数据,此函数也是一个宏,实际调用的也是函数 xQueueGenericSendFromISR()

函数原型

BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue,
   							 const void * pvItemToQueue,
                                 BaseType_t * pxHigherPriorityTaskWoken);
 /*
   此函数的参数和返回值同上面三个函数相同。
*/
5.7 函数 xQueueGenericSendFromISR()

函数介绍

​ 上面说了 4 个中断级入队函数最终都是调用的函数 xQueueGenericSendFromISR(),这是真

正干活的主啊

函数原型

BaseType_t xQueueGenericSendFromISR(QueueHandle_t xQueue,
                                   const void* pvItemToQueue,
                                   BaseType_t* pxHigherPriorityTaskWoken,
                                   BaseType_t xCopyPosition);
 /*
   参数:
   xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的
   队列句柄。
   pvItemToQueue:指向要发送的消息,发送的过程中会将这个消息拷贝到队列中。
   pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这
   三个函数来设置的,用户不用进行设置,用户只需要提供一
   个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退
   出中断服务函数之前一定要进行一次任务切换。
   xCopyPosition: 入队方式,有三种入队方式:
   queueSEND_TO_BACK: 后向入队
   queueSEND_TO_FRONT: 前向入队
   queueOVERWRITE: 覆写入队。
   返回值:
   pdTRUE: 向队列发送消息成功!
   errQUEUE_FULL: 队列已经满了,消息发送失败。
*/
5.8 任务级通用入队函数、中断级通用入队函数

不管是后向入队 、前向入队还是覆写入队 、最终调用的都是通用入队函数xQueueGenericSend(),这个函数在文件 queue.c 文件中由定义

5.9 队列上锁和解锁

prvLockQueue()和 prvUnlockQueue()

函数原型

#define prvLockQueue( pxQueue ) \
taskENTER_CRITICAL(); \
{ \
	if( ( pxQueue )->cRxLock == queueUNLOCKED ) \
    { \
        ( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED; \
    } \
	if( ( pxQueue )->cTxLock == queueUNLOCKED ) \
    { \
        ( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED; \
    } \
} \
taskEXIT_CRITICAL()
//
static void prvUnlockQueue( Queue_t * const pxQueue )
{
//上锁计数器(cTxLock 和 cRxLock)记录了在队列上锁期间,入队或出队的数量,当队列
    ...
6.从队列读消息

函数描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1uGYvoV9-1690684099671)(image/1690116419656.png)]

6.1 、函数 xQueueReceive()

函数介绍

​ 此函数用于在任务中从队列中读取一条(请求)消息,读取成功以后就会将队列中的这条数据删除,此函数的本质是一个,真正执行的函数是 xQueueGenericReceive()。此函数在读取消
息的时候是采用拷贝方式的,所以用户需要提供一个数组或缓冲区来保存读取到的数据,所读
取的数据长度是创建队列的时候所设定的每个队列项目的长度

函数原型

BaseType_t xQueueReceive(QueueHandle_t xQueue,
                        void * pvBuffer,
                        TickType_t xTicksToWait);

/*
		参数:
        xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的
        队列句柄。
        pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区
        中。
        xTicksToWait: 阻塞时间,此参数指示当队列空的时候任务进入阻塞态等待队列有数据的最
        大时间。如果为 0 的话当队列空的时候就立即返回;当为 portMAX_DELAY
        的 话 就 会 一 直 等 待 , 直 到 队 列 有 数 据 , 也 就 是 死 等 , 但 是 宏
        INCLUDE_vTaskSuspend 必须为 1。
        返回值:
        pdTRUE: 从队列中读取数据成功。
        pdFALSE: 从队列中读取数据失败。
*/
6.2、函数 xQueuePeek()

函数介绍

​ 此函数用于从队列读取一条(请求)消息,只能用在任务中!此函数在读取成功以后不会将消息删除,此函数是一个宏,真正执行的函数是 xQueueGenericReceive()。此函数在读取消息的
时候是采用拷贝方式的,所以用户需要提供一个数组或缓冲区来保存读取到的数据,所读取的
数据长度是创建队列的时候所设定的每个队列项目的长度

函数原型

BaseType_t xQueuePeek(QueueHandle_t xQueue,
                      void * pvBuffer,
					TickType_t xTicksToWait);
/*
		参数:
        xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的
        队列句柄。
        pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区
        中。
        xTicksToWait: 阻塞时间,此参数指示当队列空的时候任务进入阻塞态等待队列有数据的最
        大时间。如果为 0 的话当队列空的时候就立即返回;当为 portMAX_DELAY
        的 话 就 会 一 直 等 待 , 直 到 队 列 有 数 据 , 也 就 是 死 等 , 但 是 宏
        INCLUDE_vTaskSuspend 必须为 1。
        返回值:
        pdTRUE: 从队列中读取数据成功。
        pdFALSE: 从队列中读取数据失败。
*/
6.3 、函数 xQueueGenericReceive()
  • xQueueReceive() 还 是 xQueuePeek() , 最 终 都 是 调 用 的 函 数
    xQueueGenericReceive(),此函数是真正干事的,函数原型如下
BaseType_t xQueueGenericReceive(QueueHandle_t xQueue,
                                void* pvBuffer,
                                TickType_t xTicksToWait
                                BaseType_t xJustPeek)
    
    /*
        参数:
        xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的
        队列句柄。
        pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区
        中。
        xTicksToWait: 阻塞时间,此参数指示当队列空的时候任务进入阻塞态等待队列有数据的最
        大时间。如果为 0 的话当队列空的时候就立即返回;当为 portMAX_DELAY
        的 话 就 会 一 直 等 待 , 直 到 队 列 有 数 据 , 也 就 是 死 等 , 但 是 宏
        INCLUDE_vTaskSuspend 必须为 1。
        xJustPeek : 标记当读取成功以后是否删除掉队列项,当为 pdTRUE 的时候就不用删除,
        也就是说你后面再调用函数 xQueueReceive()获取到的队列项是一样的。当为
        pdFALSE 的时候就会删除掉这个队列项。
        返回值:
        pdTRUE: 从队列中读取数据成功。
        pdFALSE: 从队列中读取数据失败。
    */
6.4 、函数 xQueueReceiveFromISR()

​ 此函数是 xQueueReceive()的中断版本,用于在中断服务函数中从队列中读取(请求)一条消息,读取成功以后就会将队列中的这条数据删除。此函数在读取消息的时候是采用拷贝方式的,
所以需要用户提供一个数组或缓冲区来保存读取到的数据,所读取的数据长度是创建队列的时
候所设定的每个队列项目的长度

函数原型

BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,
                                void* pvBuffer,
                                BaseType_t * pxTaskWoken);
/*
		参数:
        xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的
        队列句柄。
        pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区
        中。
        pxTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值是由函数来设置的,
        用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值
        为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
        返回值:
        pdTRUE: 从队列中读取数据成功。
        pdFALSE: 从队列中读取数据失败。
*/
6.5、函数xQueuePeekFromISR()

​ 此函数是 xQueuePeek()的中断版本,此函数在读取成功以后不会将消息删除

函数原型

	BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,
								void * pvBuffer)
/*
	参数:
    xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的
    队列句柄。
    pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区
    中。
    返回值:
    pdTRUE: 从队列中读取数据成功。
    pdFALSE: 从队列中读取数据失败。
*/
7.队列操作实验
7.1 实验简介

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yr3tIqeB-1690684099671)(image/1690117569827.png)]

7.2 实验代码
//定义 
QueueHandle_t  little_queue;//小数据句柄
QueueHandle_t  big_queue;//大数据句柄
char buff[100] = "我是一个大数组大大的数组124214 dsafadfdsfdafddfsdaf";
//创建队列
little_queue=xQueueCreate(2,sizeof(uint8_t));
if(little_queue!=NULL)
{
    printf("\r\n创建成功\r\n");
}
big_queue=xQueueCreate(1,sizeof(char *));
if(little_queue!=NULL)
{
    printf("\r\n创建成功\r\n");
}
//入队
void task1(void * parameter)
{
    BaseType_t err=0;
	char * buf; //
	buf=buff;//buf=&buff[0];
    //小数据句柄  发送num地址 发里面的值
	err = xQueueSend(little_queue,&num,portMAX_DELAY);
    if(err!=pdTRUE)
    {
        printf("\r\n发送失败\r\n");	
    }
    //选择大数据句柄   buf发送  数据地址 
	err = xQueueSend(big_queue,&buf,portMAX_DELAY);
    if(err!=pdTRUE)
    {
        printf("\r\n发送失败\r\n");	
    }
}
//出队  当没有东西可以出时,该任务阻塞挂起,执行其他的
void task2(void * parameter)
{
        BaseType_t err1=0;
        uint8_t key = 0;
  	  BaseType_t err2=0;
		char *buff;
    	err1=xQueueReceive(little_queue,
					 &key,
					 portMAX_DELAY);//没有读取,变阻塞态,等待
		if(err1!=pdTRUE)
		{
			printf("\r\n读取失败\r\n");	

		}
		else
		{
			taskENTER_CRITICAL();
			printf("\r\nkey:%d\r\n",key);
			taskEXIT_CRITICAL();	
		}
    	err2=xQueueReceive(big_queue,
					 &buff,
					 portMAX_DELAY);//没有读取,变阻塞态
		if(err2!=pdTRUE)
		{
			printf("\r\n读取失败\r\n");	

		}
		else
		{
			taskENTER_CRITICAL();
			printf("\r\nkey:%s\r\n",buff);
			taskEXIT_CRITICAL();		

		}
}

十三、信号量

1.什么是信号量?
  • 队列发送数据
  • 信号发送状态,所需内存小,事情到底做没做完
    在这里插入图片描述
1.1信号量与队列的对比

在这里插入图片描述

2.二值信号量

在这里插入图片描述

2.1简介
  • 本质为一个队列长度为1的队列,该队列只有空和满两种情况,这就是二值
  • 二值信号量通常用于互斥访问或任务同步,与互斥信号量比较类似,但是二值信号量有可能会导致优先级翻转的问题,所以二值信号量更适合用于同步
2.2 API函数简介

在这里插入图片描述

2.3、函数 vSemaphoreCreateBinary ()

函数功能

​ 使用此函数创建二值信号量的话信号量所需要的 RAM 是由 FreeRTOS 的内存管
理部分来动态分配的。此函数创建好的二值信号量默认是空的,也就是说刚创建好的二值信号量使用函数 xSemaphoreTake()是获取不到的,此函数也是个宏,具体创建过程是由函数
xQueueGenericCreate()来完成的

函数原型

SemaphoreHandle_t xSemaphoreCreateBinary( void)
   /*
   	参数:无。
   	返回值:
	NULL: 二值信号量创建失败。
	其他值: 创建成功的二值信号量的句柄。
	参数:

   */ 
2.4 、函数 xSemaphoreCreateBinaryStatic()

函数功能

此函数也是创建二值信号量的,只不过使用此函数创建二值信号量的话信号量所需要的RAM 需要由用户来分配,此函数是个宏,具体创建过程是通过函数 xQueueGenericCreateStatic()来完成的,

函数原型

SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer )
     /*
 
    pxSemaphoreBuffer: :此参数指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。
    返回值:
    NULL: 二值信号量创建失败。
    其他值: 创建成功的二值信号量句柄。
   */ 
2.5、函数 xSemaphoreGive()

函数功能

​ 此函数用于释放二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正释放信号量的过程是由函数 xQueueGenericSend()来完成的

函数原型

BaseType_t xSemaphoreGive( xSemaphore )
     /*
 
    参数:
    xSemaphore :要释放的信号量句柄。
    返回值:
    pdPASS: 释放信号量成功。
    errQUEUE_FULL: 释放信号量失败。
   */ 
    #define xSemaphoreGive( xSemaphore ) \
	xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), \
		NULL, \
	semGIVE_BLOCK_TIME, \
	queueSEND_TO_BACK ) \
2.6 、函数xSemaphoreGiveFromISR()

函数功能

​ 此函数用于在中断中释放信号量,此函数只能用来释放二值信号量和计数型信号量,绝对不能用来在中断服务函数中释放互斥信号量!此函数是一个宏,真正执行的是函数xQueueGiveFromISR()

函数原型

BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
							BaseType_t * pxHigherPriorityTaskWoken)
     /*
 
    参数:
    xSemaphore: 要释放的信号量句柄。
    pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这
    三个函数来设置的,用户不用进行设置,用户只需要提供一
    个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退
    出中断服务函数之前一定要进行一次任务切换。
    返回值:
    pdPASS: 释放信号量成功。
    errQUEUE_FULL: 释放信号量失败。
    
    在中断中释放信号量真正使用的是函数 xQueueGiveFromISR(),此函数和中断级通用入队
函数 xQueueGenericSendFromISR()极其类似!只是针对信号量做了微小的改动。函数
xSemaphoreGiveFromISR()不能用于在中断中释放互斥信号量,因为互斥信号量涉及到优先级继
承的问题,而中断不属于任务,没法处理中断优先级继承。大家可以参考第十三章分析函数
xQueueGenericSendFromISR()的过程来分析 xQueueGiveFromISR()
   */ 
    #define xSemaphoreGive( xSemaphore ) \
	xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), \
		NULL, \
	semGIVE_BLOCK_TIME, \
	queueSEND_TO_BACK ) \

2.7、函数 xSemaphoreTake()

函数功能

​ 此函数用于获取二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正获取信号量的过程是由函数 xQueueGenericReceive ()来完成的

函数原型

BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,
							TickType_t xBlockTime)
    /*
    参数:
    xSemaphore :要获取的信号量句柄。
	xBlockTime: 阻塞时间。
	返回值:
	pdTRUE: 获取信号量成功。
	pdFALSE: 超时,获取信号量失败。
    */
2.8、函数 xSemaphoreTakeFromISR()

函数功能

​ 此函数用于在中断服务函数中获取信号量,此函数用于获取二值信号量和计数型信号量,绝 对 不 能 使 用 此 函 数 来 获 取 互 斥 信 号 量 ! 此 函 数 是 一 个 宏 , 真 正 执 行 的 是 函 数xQueueReceiveFromISR ()

函数原型

BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,
								BaseType_t * pxHigherPriorityTaskWoken)
    /*
   参数:
    xSemaphore: 要获取的信号量句柄。
    pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这
    三个函数来设置的,用户不用进行设置,用户只需要提供一
    个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退
    出中断服务函数之前一定要进行一次任务切换。
    返回值:
    pdPASS: 获取信号量成功。
    pdFALSE: 获取信号量失败。
    */
3.实验设计
3.1简介

实验目的

  • 学习FreeRTOS的二值信号量相关API函数的使用

实验设计

  • 设计3个任务: start_task,task1,task2

任务功能

  1. strat_task: 创建task1和task2任务
  2. task1:用于按键扫描,检测到按键按下,释放二值信号量
  3. task2:获取二值信号量,当成功获取后打印提示信息
3.2实验代码
#include "semphr.h"   //包含这个文件  在include文件夹里有
4.计数型信号量

​ 相当于队列长度大于1的队列,因此计数型信号量能够容纳多个资源,

  • 计数值大于0,有资源
  • 计数值等于0,没有资源
4.1 适用场合
  • 事件技术: 每次事件发生后,在事件处理函数中释放计数型信号量 (计数值+1),其他任务会获取计数型信号量(计数值-1),创建时初始计数值设置为0

  • 资源管理:信号量表示有效资源数目。任务必须先获取信号量(信号量计数值-1),才能获取资源控制权,计数值减为0时表示没有的资源,任务使用完资源后,必须释放信号量(信号量计数值加1)。信号量创建时计数值应等于最大资源数目

4.2 相关函数

在这里插入图片描述

4.3 函数 xSemaphoreCreateCounting()

函数功能

​ 用于创建一个计数型信号量,所需要的内存通过动态内存管理方法分配 本质是一个宏,真正完成信号量创建的是函数 xQueueCreateCountingSemaphore()

//为1时使用计数信号量
#define   configUSE_COUNTING_SEMAPHORES		 1

函数原型

SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount,
											UBaseType_t uxInitialCount )
    
    /*
    	参数:
        uxMaxCount: 计数信号量最大计数值,当信号量值等于此值的时候释放信号量就会失败。
        uxInitialCount: 计数信号量初始值。
        返回值:
        NULL: 计数型信号量创建失败。
        其他值: 计数型信号量创建成功,返回计数型信号量句柄。
    */
4.4 函数 xSemaphoreCreateCountingStatic()

函数功能

​ 此函数也是用来创建计数型信号量的,使用此函数创建计数型信号量的时候所需要的内存
需要由用户分配。此函数也是一个宏,真正执行的是函数xQueueCreateCountingSemaphoreStatic()

函数原型

SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount,
                                    UBaseType_t uxInitialCount,
                                    StaticSemaphore_t * pxSemaphoreBuffer )
    
    /*
    	参数:
        uxMaxCount: 计数信号量最大计数值,当信号量值等于此值的时候释放信号量就会失败。
        uxInitialCount: 计数信号量初始值。
        pxSemaphoreBuffer :指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。
        返回值:
        NULL: 计数型信号量创建失败。
        其他值: 计数型号量创建成功,返回计数型信号量句柄。
    */
5.优先级翻转
  • 高优先级的任务反而慢执行,低优先级的任务反而优先执行

  • 优先级翻转会破坏任务的预期顺序。

  • 使用二值信号量经常会遇到

  • 示意图

    在这里插入图片描述

    /*
    (1) 任务 H 和任务 M 处于挂起状态,等待某一事件的发生,任务 L 正在运行。
    (2) 某一时刻任务 L 想要访问共享资源,在此之前它必须先获得对应该资源的信号量。
    (3) 任务 L 获得信号量并开始使用该共享资源。
    (4) 由于任务 H 优先级高,它等待的事件发生后便剥夺了任务 L 的 CPU 使用权。
    (5) 任务 H 开始运行。
    (6) 任务 H 运行过程中也要使用任务 L 正在使用着的资源,由于该资源的信号量还被任务L 占用着,任务 H 只能进入挂起状态,等待任务 L 释放该信号量。
    (7) 任务 L 继续运行。
    (8) 由于任务 M 的优先级高于任务 L,当任务 M 等待的事件发生后,任务 M 剥夺了任务L 的 CPU 使用权。
    (9) 任务 M 处理该处理的事。
    (10) 任务 M 执行完毕后,将 CPU 使用权归还给任务 L。
    (11) 任务 L 继续运行。
    (12) 最终任务 L 完成所有的工作并释放了信号量,到此为止,由于实时内核知道有个高优先级的任务在等待这个信	号量,故内核做任务切换。
    (13) 任务 H 得到该信号量并接着运行。
    在这种情况下,任务 H 的优先级实际上降到了任务 L 的优先级水平。因为任务 H 要一直
    等待直到任务 L 释放其占用的那个共享资源。由于任务 M 剥夺了任务 L 的 CPU 使用权,使
    得任务 H 的情况更加恶化,这样就相当于任务 M 的优先级高于任务 H,导致优先级翻转。
    */
    
    5.1实验现象
    开始任务任务函数
    void start_task(void *pvParameters)
    {
        taskENTER_CRITICAL(); //进入临界区
        //创建二值信号量
        BinarySemaphore=xSemaphoreCreateBinary(); (1)
        //二值信号量创建成功以后要先释放一下
        if(BinarySemaphore!=NULL)xSemaphoreGive(BinarySemaphore); (2)
        //创建高优先级任务
        xTaskCreate((TaskFunction_t )high_task,
                                    (const char* )"high_task",
                                    (uint16_t )HIGH_STK_SIZE,
                                    (void* )NULL,
                                    (UBaseType_t )HIGH_TASK_PRIO,
                                    (TaskHandle_t* )&HighTask_Handler);
            //创建中等优先级任务
        xTaskCreate((TaskFunction_t )middle_task,
                                    (const char* )"middle_task",
                                    (uint16_t )MIDDLE_STK_SIZE,
                                    (void* )NULL,
                                    (UBaseType_t )MIDDLE_TASK_PRIO,
                                    (TaskHandle_t* )&MiddleTask_Handler);
        //创建低优先级任务
        xTaskCreate((TaskFunction_t )low_task,
                                (const char* )"low_task",
                                (uint16_t )LOW_STK_SIZE,
                                (void* )NULL,
                                (UBaseType_t )LOW_TASK_PRIO,
                                (TaskHandle_t* )&LowTask_Handler);
                                vTaskDelete(StartTask_Handler); //删除开始任务
        taskEXIT_CRITICAL(); //退出临界区
    }
    //其他任务函数见正点原子新版  视频讲解
    
    
6.互斥信号量
  • 拥有优先级继承的二值信号量,在同步的应用中二值信号量最合适
  • 优先级继承
    • 当互斥信号量被一个低优先级的任务持有时,如果此时有高优先级任务尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。

注意

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

​ 宏 #define configUSE_MUTEXES 1

6.1 函数 xSemaphoreCreateMutex()

​ 此函数用于创建一个互斥信号量,所需要的内存通过动态内存管理方法分配,本质是一个宏,真正完成信号量创建的是函数 xQueueCreateMutex()

SemaphoreHandle_t xSemaphoreCreateMutex( void )
    /*
    	参数:
        无。
        返回值:
        NULL: 互斥信号量创建失败。
        其他值: 创建成功的互斥信号量的句柄。
    */
6.2 函数 xSemaphoreCreateMutexStatic()

此函数也是创建互斥信号量的,只不过使用此函数创建互斥信号量的话信号量所需要的RAM 需要由用户来分配

SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer )
    
    /*
    	参数:
        pxMutexBuffer :此参数指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。
        返回值:
        NULL: 互斥信号量创建失败。
        其他值: 创建成功的互斥信号量的句柄。
    */
    
  #define configUSE_MUTEXES		1
6.3 具体使用
   //创建句柄  创建互斥 
// 创建时,默认执行一次 释放信号量
Semaphore_Handle=xSemaphoreCreateMutex();

//与上一个实验类似,  创建三个任务 低中高优先级
7.队列集
7.1简介

​ 一个队列只允许同一种数据类型,如果任务间传递不同数据类型的消息时,可以使用队列集。

作用

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

假设

​ 一个接收任务,使用到队列接收和信号量接收的获取, 当使用到队列集时,不管哪个消息到来都会使其解除阻塞状态

7.2 函数简介

在这里插入图片描述

7.3 函数 BaseType_t QueueAddToSet()

函数功能

​ 用于往队列集中添加队列,要注意的是,队列在被添加到队列集之前,队列中不能有有效消息

函数原型

 BaseType_t QueueAddToSet(QueueSetMemberHandle_t xQueueOrSemaphone
                         QueueSetHandle_t        xQueueSet)
    // 参数:
	//xQueueOrSemaphone 待添加的 句柄
     //xQueueSet		队列集
     //返回值 pdTURE 有效
     
7.4 函数 xQueueRemoveFromSet()

函数功能

​ 从队列集中移除队列

函数原型

BaseType_t xQueueRemoveFromSet
                      (
                          QueueSetMemberHandle_t xQueueOrSemaphore,
                          QueueSetHandle_t xQueueSet
                      );//句柄   队列集
7.5 函数 xQueueSelectFromSet()

函数功能

​ 用于在任务中获取队列集中有效消息的队列

函数原型

 QueueSetMemberHandle_t xQueueSelectFromSet
                       (
                             QueueSetHandle_t xQueueSet,
                             const TickType_t xTicksToWait
                        );// 队列集  阻塞超时时间
						//返回值 NULL 失败 其他 成功  返回哪一个队列的句柄
7.6 函数 xQueueCreateSet()

函数功能

​ 创建一个队列集 宏 configUSE_QUEUE_SETS 设置为 1

函数原型

QueueSetHandle_t xQueueCreateSet
               (
                   const UBaseType_t uxEventQueueLength
               );//长度 可容纳队列的数量  返回句柄
8.队列集操作实验
8.1 使用流程

在这里插入图片描述

8.2 代码 展现
//句柄定义
QueueSetHandle_t QueueSet_Handle;
QueueHandle_t 	Queue_Handle;
QueueHandle_t  Semphore_Queue_Handle;
//start 任务中创建 
//队列集
	QueueSet_Handle=xQueueCreateSet(2);//2个数目的队列集
	if(QueueSet_Handle!=NULL)
	{
		printf("\r\n队列集创建成功\r\n");
	}
	//队列
	Queue_Handle=xQueueCreate(1,sizeof(uint8_t));
	if(Queue_Handle!=NULL)
	{
		printf("\r\n队列创建成功\r\n");

	}
	//信号量
	Semphore_Queue_Handle=xSemaphoreCreateBinary();
	if(Semphore_Queue_Handle!=NULL)
	{
			printf("\r\n信号量创建成功\r\n");

	}
	//添加队列 到队列集

//任务2比任务1 优先级高

//实现队列发送  以及信号量释放
void TASK1(void* pvParameters)
{
	uint8_t key=0,key1=0;
	BaseType_t err=0;
	while(1)
	{
		key=Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN);
		key1=Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN);
		if(key)
		{
			err=xQueueSend(Queue_Handle,&key,portMAX_DELAY);
			if(err==pdTRUE)
			{
				printf("\r\n向队列发送成功\r\n");
			}
		}
		if(key1)
		{
			err=xSemaphoreGive(Queue_Handle);
			if(err==pdTRUE)
			{
				printf("\r\n释放信号量成功\r\n");
			}
		}
		vTaskDelay(10);

	}
}
//获取队列集消息
void TASK2(void* pvParameters)
{
	QueueSetMemberHandle_t member_handle;
	uint8_t key;//缓存
	while(1)
	{
		//读取哪个队列集
		member_handle=xQueueSelectFromSet(QueueSet_Handle,portMAX_DELAY);
		//读取到队列的句柄
		if(member_handle==Queue_Handle)
		{
			xQueueReceive(member_handle,&key,portMAX_DELAY);
			printf("\r\n key:%d\r\n",key);
		}
		//信号量句柄
		else if(member_handle==Semphore_Queue_Handle)
		{
			xSemaphoreTake(member_handle,portMAX_DELAY);
			printf("\r\n 获取信号量成功\r\n");
		}
	}
}

十四、事件标志组

1.简介

事件标志位

​ 用一个位,表示事件是否发生,按键按下,flag置1,通过判断flag判断按键是否按下

事件标志组

​ 一组事件标志位的集合,可以简单理解为1个整数

特点

  • 每一个位,表示一个事件,高8位不算
  • 事件含义由用户自己决定,如bit0表示按键是否按下,bit1表示是否接收到消息
    • 1表示发生
    • 0表示未发生
  • 任意任务或中断都可以读写这些位
  • 可以等待某一位成立,或者等待多位同时成立

数据类型

	事件标志组的数据类型为 EventGroupHandle_t,当 configUSE_16_BIT_TICKS 为 1 的时候
事件标志组可以存储 8 个事件位,当 configUSE_16_BIT_TICKS 为 0 的时候事件标志组存储 24
个事件位
事件标志组中的所有事件位都存储在一个无符号的 EventBits_t 类型的变量中,EventBits_t在 event_groups.h 中有如下定义
typedef TickType_t EventBits_t;
2.事件 函数
2.1 函数 xEventGroupCreate()
//#include "event_groups.h"
/*
此函数用于创建一个事件标志组,所需要的内存通过动态内存管理方法分配。由于内部处
理 的 原 因 , 事 件 标 志 组 可 用 的 bit 数 取 决 于
*/ 
configUSE_16_BIT_TICKS , 当
configUSE_16_BIT_TICKS1 为 1 的 时 候 事 件 标 志 组 有 8 个 可 用 的 位 (bit0~bit7) , 当
configUSE_16_BIT_TICKS 为 0 的时候事件标志组有 24 个可用的位(bit0~bit23)。EventBits_t 类
型的变量用来存储事件标志组中的各个事件位
  //函数原型
   EventGroupHandle_t xEventGroupCreate( void )
   /*
   	参数:无。
    返回值:
    NULL: 事件标志组创建失败。
    其他值: 创建成功的事件标志组句柄。
   */
2.2函数 xEventGroupCreateStatic()
/*
	此函数用于创建一个事件标志组定时器,所需要的内存需要用户自行分配
*/ 
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t *pxEventGroupBuffer )
    
    /*
    参数:
    pxEventGroupBuffer : 参数指向一个 StaticEventGroup_t 类型的变量,用来保存事件标志组结
    构体。
    返回值:
    NULL: 事件标志组创建失败。
    其他值: 创建成功的事件标志组句柄。
*/
2.3 设置事件位

在这里插入图片描述

2.4 函数 xEventGroupClearBits()
/*
	将事件标志组中的指定事件位清零,此函数只能用在任务中,不能用在中断服务函数中!
中断服务函数有其他的 API 函数
*/ 
EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear );
    
    /*
   参数:
    xEventGroup : 要操作的事件标志组的句柄。
    uxBitsToClear : 要清零的事件位,比如要清除 bit3 的话就设置为 0X08。可以同时清除多个
    bit,如设置为 0X09 的话就是同时清除 bit3 和 bit0。
    返回值:
    任何值: 将指定事件位清零之前的事件组值。
*/
2.5 函数 xEventGroupClearBitsFromISR()
/*
	此函数为函数 xEventGroupClearBits()的中断级版本,也是将指定的事件位(标志)清零。此函数用在中断服务函数中
*/ 
BaseType_t xEventGroupClearBitsFromISR( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );
    
    /*		
   参数:
    xEventGroup : 要操作的事件标志组的句柄。
    uxBitsToClear : 要清零的事件位,比如要清除 bit3 的话就设置为 0X08。可以同时清除多个
    bit,如设置为 0X09 的话就是同时清除 bit3 和 bit0。
    返回值:
    pdPASS : 事件位清零成功。
    pdFALSE: 事件位清零失败。
*/
2.6 函数 xEventGroupSetBits()
/*
	设置指定的事件位为 1,此函数只能用在任务中,不能用于中断服务函数
*/ 
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );
    
    /*		
   参数:
    xEventGroup : 要操作的事件标志组的句柄。
    uxBitsToClear : 指定要置 1 的事件位,比如要将 bit3 值 1 的话就设置为 0X08。可以同时将多个 bit 置 1,如设置为 0X09 的话就是同时将 bit3 和 bit0 置 1。
    返回值:
	任何值: 在将指定事件位置 1 后的事件组值。
*/
2.7 函数 xEventGroupSetBitsFromISR()
/*
	此函数也用于将指定的事件位置 1,此函数是 xEventGroupSetBits()的中断版本,用在中断
服务函数中
*/ 
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t * pxHigherPriorityTaskWoken );
    
    /*		
   参数:
    xEventGroup : 要操作的事件标志组的句柄。
    uxBitsToClear : 指定要置 1 的事件位,比如要将 bit3 值 1 的话就设置为 0X08。可以同时将
    多个 bit 置 1,如设置为 0X09 的话就是同时将 bit3 和 bit0 置 1。
    pxHigherPriorityTaskWoken: :标记退出此函数以后是否进行任务切换,这个变量的值函数会自
    动设置的,用户不用进行设置,用户只需要提供一个变量来保存
    这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之
    前一定要进行一次任务切换。
    返回值:
    pdPASS : 事件位置 1 成功。
    pdFALSE: 事件位置 1 失败。
*/
2.8 获取事件标志组值

在这里插入图片描述

2.9函数 xEventGroupGetBits()
/*
	   此函数用于获取当前事件标志组的值,也就是各个事件位的值。此函数用在任务中,不能
用在中断服务函数中。此函数是个宏,真正执行的是函数 xEventGroupClearBits()

*/ 
EventBits_t xEventGroupGetBits( EventGroupHandle_t xEventGroup )
    
    /*		
   参数:
xEventGroup : 要获取的事件标志组的句柄。
返回值:
任何值:当前事件标志组的值。
*/

2.10 函数 xEventGroupGetBitsFromISR()
/*
	   获取当前事件标志组的值,此函数是 xEventGroupGetBits()的中断版本

*/ 
EventBits_t xEventGroupGetBitsFromISR( EventGroupHandle_t xEventGroup )
    
    /*		
   参数:
    xEventGroup : 要获取的事件标志组的句柄。
    返回值:
    任何值: 当前事件标志组的值。
*/

2.11 xEventGroupWaitBits()等待指定的事件位
/*
	  某个任务可能需要与多个事件进行同步,那么这个任务就需要等待并判断多个事件位(标志),使用函数 xEventGroupWaitBits()可以完成这个功能。调用函数以后如果任务要等待的事件位还没有准备好(置 1 或清零)的话任务就会进入阻塞态,直到阻塞时间到达或者所等待的事件位准备好

*/ 
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
const TickType_t xTicksToWait );
    
    /*		
   参数:
    xEventGroup : 指定要等待的事件标志组。
    uxBitsToWaitFord: 指定要等待的事件位,比如要等待bit0和(或)bit2的时候此参数就是0X05,
    如果要等待 bit0 和(或)bit1 和(或)bit2 的时候此参数就是 0X07,以此类推。
    xClearOnExit: 此参数要是为pdTRUE的话,那么在退出此函数之前由参数uxBitsToWaitFor
    所设置的这些事件位就会清零。如果设置位 pdFALSE 的话这些事件位就
    不会改变。
    xWaitForAllBits: 此参数如果设置为 pdTRUE 的话,当 uxBitsToWaitFor 所设置的这些事件
    位都置 1,或者指定的阻塞时间到的时候函数 xEventGroupWaitBits()才会
    返回。当此函数为 pdFALSE 的话,只要 uxBitsToWaitFor 所设置的这些事
    件 位 其 中 的 任 意 一 个 置 1 , 或 者 指 定 的 阻 塞 时 间 到 的 话 函 数
    xEventGroupWaitBits()就会返回。
    xTicksToWait : 设置阻塞时间,单位为节拍数。
    返回值:
    任何值: 返回当所等待的事件位置 1 以后的事件标志组的值,或者阻塞时间到。根
    据这个值我们就知道哪些事件位置 1 了。如果函数因为阻塞时间到而返回的话那么这个返回值就不代表任何的含义。
*/
3.事件标志组与队列、信号量的区别
功能唤醒对象事件清除
队列、信号量事件发生时,只会唤醒一个任务消耗型的资源,队列的数据读走了就没了,信号量被获取就减少了
事件标志组事件发生时,会唤醒所有符合条件的任务,理解为广播被唤醒的任务有两个选择,可以让事件保留不动,也可以清除事件
4.实验

实验目的

​ 学习相关函数的使用

实验设计

​ start_task:创建task1、task2

​ task1:读取按键按下键值,根据不同键值将事件标志组响应事件位置1,模拟事件发生

​ task2: 同时等待事件标志组中的多个事件位,当这些事件位都置1的话就执行相应的处理

void  start_task(void* pvParameters)
{
	
	
	taskENTER_CRITICAL();//临界保护不被打断  要不然会出错  因为开始任务调度后  创建任务后成就绪态的任务立马运行
	//创建事件 
	EventGroup_Handle=xEventGroupCreate();
	if(EventGroup_Handle!=NULL)
	{
		printf("\r\n事件创建成功\r\n");
	}
	
	xTaskCreate((TaskFunction_t) TASK1,//任务函数
			   (const char*	   ) "TASK1",//任务名字
			   (uint16_t		) 64,//任务栈大小
			   (void*			)NULL,//任务入口函数参数
			    (UBaseType_t	)4, //任务优先级
			   (TaskHandle_t*   )&TASK1_Handle);//控制块指针	   
	xTaskCreate((TaskFunction_t) TASK2,//任务函数
			   (const char*	   ) "TASK2",//任务名字
			   (uint16_t		) 64,//任务栈大小
			   (void*			)NULL,//任务入口函数参数
			    (UBaseType_t	)5, //任务优先级  高优先
			   (TaskHandle_t*   )&TASK2_Handle);//控制块指针
			     

	taskEXIT_CRITICAL();
			   
	vTaskDelete(start_task_Handle);//任务创建完毕,删除创建任务的任务
}

void TASK1(void* pvParameters)
{
	uint8_t key=0,key1=0;
	BaseType_t err=0;
	while(1)
	{
		key=Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN);
		key1=Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN);
		if(key)
		{
			//按键1按下第0位标志置1
			 xEventGroupSetBits(EventGroup_Handle,1<<0);
//			err=xQueueSend(Queue_Handle,&key,portMAX_DELAY);
			if(err==pdTRUE)
			{
				printf("\r\n向队列发送成功\r\n");
			}
		}
		if(key1)
		{
			//bit1置1
			xEventGroupSetBits(EventGroup_Handle,1<<1);
//			err=xSemaphoreGive(Queue_Handle);
			if(err==pdTRUE)
			{
				printf("\r\n释放信号量成功\r\n");
			}
		}
		vTaskDelay(10);

	}
}
void TASK2(void* pvParameters)
{
	uint8_t key;//缓存
	EventBits_t event_bit=0;
	while(1)
	{
		event_bit=xEventGroupWaitBits(EventGroup_Handle,   //句柄
							EVENT_BIT0|EVENT_BIT1, // 等待的位
							pdTRUE,  //清除
							pdTRUE,// 等待全为1
							portMAX_DELAY  //一直等
											);
		printf("\r\n等待的事件标志位值为:%#x\r\n",event_bit);//0x03
	}
}

十四、任务通知

1.简介

任务通知

用来通知任务的,任务控制块的结构体成员变量ulNotifiedValue就是这个通知值

注意

使用队列、信号量、事件标志组、时都需要另外创建一个结构体,通过中间的结构体间接通信

任务通知值的更新方式

  • 不覆盖接受任务的通知值
  • 覆盖接受任务的通知值
  • 更新接受任务通知值的一个或多个bit
  • 增加接受任务的通知值
  • 使用合理,可以在一些场合中替代队列,信号量,事件标志组

任务通知的优势和劣势

优势

  • 效率更高
  • 使用内存更小 无需创建额外的结构体

劣势

  • 无法发送数据给ISR,
  • IS没有任务结构体,但ISR可以使用任务通知的功能,发数据给任务
  • 无法广播给多个任务,任务通知只能是被指定的一个任务接收并处理
  • 无法缓存多个数据,结构体中只有一个任务通知值,只能保持一个数据
  • 发送方无法进入阻塞状态等待

任务通知值和状态

任务都有一个结构体:任务控制块TCB,它里边有两个结构体成员变量

任务通知值的更新方式有多种类型

计数值,数值累加 类似信号量

相应位置1 类似事件标志组

任意数值

通知状态

任务未等待通知 任务通知默认的初始化状态

等待通知: 接收方已经准备好了(调用了接收任务通知函数),等待发送方给个通知

等待接收:发送方已经发送出去了(调用了发送任务通知函数),等待接收方接收

2.函数介绍
  • //开启任务通知功能,默认开启
    #define configUSE_TASK_NOTIFICATIONS 1

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

2.1 函数 xTaskNotify()
/*
	此函数用于发送任务通知,此函数发送任务通知的时候带有通知值,此函数是个宏,真正执行的函数 xTaskGenericNotify()
*/
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction )
   /*
   	参数:
    xTaskToNotify : 任务句柄,指定任务通知是发送给哪个任务的。
    ulValue : 任务通知值。
    eAction : 任务通知更新的方法,eNotifyAction 是个枚举类型,在文件 task.h 
    返回值:
    pdFAIL: 当参数 eAction 设置为 eSetValueWithoutOverwrite 的时候,如果任务通知值没有
    更新成功就返回 pdFAIL。
    pdPASS: eAction 设置为其他选项的时候统一返回 pdPASS。中有如下
定义:
typedef enum
{
    eNoAction = 0,
    eSetBits, //更新指定的 bit
    eIncrement, //通知值加一
    eSetValueWithOverwrite, //覆写的方式更新通知值
    eSetValueWithoutOverwrite //不覆写通知值
} eNotifyAction;

   */
2.2 函数 xTaskNotifyFromISR()
/*
	此函数用于发送任务通知,是函数 xTaskNotify()的中断版本,此函数是个宏,真正执行的是函数 xTaskGenericNotifyFromISR()
*/
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t * pxHigherPriorityTaskWoken );
   /*
   	参数:
xTaskToNotify : 任务句柄,指定任务通知是发送给哪个任务的。
ulValue : 任务通知值。
eAction : 任务通知更新的方法。
    pxHigherPriorityTaskWoken: 记退出此函数以后是否进行任务切换,这个变量的值函数会自动
    设置的,用户不用进行设置,用户只需要提供一个变量来保存这
    个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之
    前一定要进行一次任务切换。
返回值:
pdFAIL: 当参数 eAction 设置为 eSetValueWithoutOverwrite 的时候,如果任务通知值没有
更新成功就返回 pdFAIL。
pdPASS: eAction 设置为其他选项的时候统一返回 pdPASS。
   */
2.3 函数 xTaskNotifyGive()
/*
	发送任务通知,相对于函数 xTaskNotify(),此函数发送任务通知的时候不带有通知值。此函数只是将任务通知值简单的加一,此函数是个宏,真正执行的是函数 xTaskGenericNotify()
*/
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
   /*
   参数:
xTaskToNotify : 任务句柄,指定任务通知是发送给哪个任务的。
返回值:
pdPASS: 此函数只会返回 pdPASS。
   */
2.4函数 vTaskNotifyGiveFromISR()
/*
	此函数为 xTaskNotifyGive()的中断版本
*/
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskHandle,
BaseType_t * pxHigherPriorityTaskWoken );
   /*
   参数:
xTaskToNotify : 任务句柄,指定任务通知是发送给哪个任务的。
pxHigherPriorityTaskWoken: 记退出此函数以后是否进行任务切换,这个变量的值函数会自动
设置的,用户不用进行设置,用户只需要提供一个变量来保存这
个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之
前一定要进行一次任务切换。
返回值:无。
   */
2.5 函数xTaskNotifyAndQuery()
/*
	此函数和 xTaskNotify()很类似,此函数比 xTaskNotify()多一个参数,此参数用来保存更新
前的通知值。此函数是个宏,真正执行的是函数 xTaskGenericNotify()
*/
BaseType_t xTaskNotifyAndQuery ( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction
uint32_t * pulPreviousNotificationValue);
   /*
   参数:
      ulValue : 任务通知值。
    eAction : 任务通知更新的方法。
    pulPreviousNotificationValue:用来保存更新前的任务通知值。
    返回值:
    pdFAIL: 当参数 eAction 设置为 eSetValueWithoutOverwrite 的时候,如果任务通知值没有
    更新成功就返回 pdFAIL。
    pdPASS: eAction 设置为其他选项的时候统一返回 pdPASS。
   */
2.6 函数 xTaskNotifyAndQueryFromISR()
/*
	此函数为 xTaskNorityAndQuery()的中断版本,用在中断服务函数中。此函数同样为宏,真正执行的是函数 xTaskGenericNotifyFromISR()
*/
BaseType_t xTaskNotifyAndQueryFromISR ( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t * pulPreviousNotificationValue
BaseType_t * pxHigherPriorityTaskWoken );
   /*
   参数:
    xTaskToNotify : 任务句柄,指定任务通知是发送给哪个任务的。
    ulValue : 任务通知值。
    eAction : 任务通知更新的方法。
    pulPreviousNotificationValue:用来保存更新前的任务通知值。
    pxHigherPriorityTaskWoken: 记退出此函数以后是否进行任务切换,这个变量的值函数会自动
    设置的,用户不用进行设置,用户只需要提供一个变量来保存这
    个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之
    前一定要进行一次任务切换。
    返回值:
    pdFAIL: 当参数 eAction 设置为 eSetValueWithoutOverwrite 的时候,如果任务通知值没有
    更新成功就返回 pdFAIL。
    pdPASS: eAction 设置为其他选项的时候统一返回 pdPASS。
   */
2.7 通用发送 xTaskGenericNotify()和 xTaskGenericNotifyFromISR()
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, //任务句柄
uint32_t ulValue, //任务通知值
eNotifyAction eAction, //任务通知更新方式
                              
uint32_t * pulPreviousNotificationValue )//保存更新前的
//任务通知值
    
    
BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t * pulPreviousNotificationValue,
BaseType_t * pxHigherPriorityTaskWoken )
    /*
    	参数:
    xTaskToNotify : 任务句柄,指定任务通知是发送给哪个任务的。
    ulValue : 任务通知值。
    eAction : 任务通知更新的方法。
    pulPreviousNotificationValue:用来保存更新前的任务通知值。
    pxHigherPriorityTaskWoken: 记退出此函数以后是否进行任务切换,这个变量的值函数会自动
    设置的,用户不用进行设置,用户只需要提供一个变量来保存这
    个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之
    前一定要进行一次任务切换。
    返回值:
    pdFAIL: 当参数 eAction 设置为 eSetValueWithoutOverwrite 的时候,如果任务通知值没有
    更新成功就返回 pdFAIL。
    pdPASS: eAction 设置为其他选项的时候统一返回 pdPASS
    */
2.8 获取任务通知 介绍

在这里插入图片描述

2.9 函数 ulTaskNotifyTake()
/*
	此函数为获取任务通知函数,当任务通知用作二值信号量或者计数型信号量的时候可以使用此函数来获取信号量
*/
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
TickType_t xTicksToWait );
    /*
    	参数:
    xClearCountOnExit : 
    参数为 pdFALSE 的话在退出函数 ulTaskNotifyTake()的时候任务通知值
    减一,类似计数型信号量。
    当此参数为 pdTRUE 的话在退出函数的时候任务任务通知值清零,类似二值信号量。
    xTickToWait: 阻塞时间。
    返回值:
    任何值 : 任务通知值减少或者清零之前的值。
    此函数在文件 tasks.c 中有定义
    */
2.10 函数 xTaskNotifyWait()
/*
	此函数也是用来获取任务通知的,不过此函数比 ulTaskNotifyTake()更为强大,不管任务通知用作二值信号量、计数型信号量、队列和事件标志组中的哪一种,都可以使用此函数来获取任务通知。但是当任务通知用作位置信号量和计数型信号量的时候推荐使用函数
ulTaskNotifyTake()
*/
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t * pulNotificationValue,
TickType_t xTicksToWait );
    /*
    	参数:
    ulBitsToClearOnEntry: :当没有接收到任务通知的时候将任务通知值与此参数的取反值进行按位与运算,当此参数为 0xffffffff 或者 ULONG_MAX 的时候就会将任务通知值清零。
    ulBitsToClearOnExit: :如果接收到了任务通知,在做完相应的处理退出函数之前将任务通知值与此参数的取反值进行按位与运算,当此参数为 0xffffffff 或者
    ULONG_MAX 的时候就会将任务通知值清零。
    pulNotificationValue :此参数用来保存任务通知值。
    xTickToWait: 阻塞时间。
    返回值:
    pdTRUE : 获取到了任务通知。
    pdFALSE : 任务通知获取失败。
    */
3.模拟信号量实验设计
//发送任务通知
void TASK1(void* pvParameters)
{
	uint8_t key=0,key1=0;
	BaseType_t err=0;
	while(1)
	{
		key=Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN);
		if(key)
		{
			//按键1按下第0位标志置1
//			 xEventGroupSetBits(EventGroup_Handle,1<<0);
//			err=xQueueSend(Queue_Handle,&key,portMAX_DELAY);
			xTaskNotifyGive(TASK2_Handle);
			printf("\r\n\r\n释放信号量成功\r\n\r\n");
		}
		vTaskDelay(10);

	}
}
//接收任务通知
void TASK2(void* pvParameters)
{
	uint8_t key;//缓存
	EventBits_t event_bit=0;
	while(1)
	{
//		key=ulTaskNotifyTake(pdTRUE,portMAX_DELAY);//二值信号量
		key=ulTaskNotifyTake(pdFALSE,portMAX_DELAY);//计数型
//		if(key)
//		{
//			printf("\r\n接收任务通知成功,模拟二值信号量:%#x\r\n",key);//0x03
//		}
		if(key)
		{
			printf("\r\n接收任务通知成功,模拟计数信号量:%#x\r\n",key);//0x03
		}
		vTaskDelay(1000);
	}
}

4.模拟事件标志组
//发送任务通知值
void TASK1(void* pvParameters)
{
	uint8_t key=0,key1=0;
	BaseType_t err=0;
	while(1)
	{
		key=Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN);
		key1=Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN);
		if(key&&TASK2_Handle)
		{
			printf("\r\n更新bit0\r\n");
			xTaskNotify(TASK2_Handle,1<<0,eSetBits);
		}
		if(key1&&TASK2_Handle)
		{
			printf("\r\n更新bit1\r\n");
			xTaskNotify(TASK2_Handle,1<<1,eSetBits);
		}
		vTaskDelay(10);

	}
}
//接收任务通知值
void TASK2(void* pvParameters)
{
	uint32_t key,event_bit=0;//缓存
//	EventBits_t event_bit=0;
	while(1)
	{
		xTaskNotifyWait(0,0xFFFFFFFF,&key,portMAX_DELAY);
		if(key&1<<0)
		{
			event_bit|=1<<0;
		}
		if(key&1<<1)
		{
			event_bit|=1<<1;
//			printf("\r\n接收任务通知成功key:%#x\r\n",key);//0x03
//			LED1_TOGGLE;
		}
		if(event_bit==(1<<1|1<<0))
		{
			printf("\r\n事件标志组成功\r\n");
		}
		vTaskDelay(1000);
	}
}

5.模拟消息邮箱
//发送任务通知
void TASK1(void* pvParameters)
{
	uint8_t key=0,key1=0;
	BaseType_t err=0;
	while(1)
	{
		key=Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN);
		key1=Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN);
		if(key&&TASK2_Handle)
		{
			//按键1按下第0位标志置1
//			 xEventGroupSetBits(EventGroup_Handle,1<<0);
//			err=xQueueSend(Queue_Handle,&key,portMAX_DELAY);
			printf("\r\n\r\n消息邮箱发送键值为:%d\r\n\r\n",key);
			xTaskNotify(TASK2_Handle,key,eSetValueWithoutOverwrite);
//			xTaskNotifyGive(TASK2_Handle);
			if(err==pdTRUE)
			{
				printf("\r\n\r\n释放信号量成功\r\n\r\n");
			}
		}
		if(key1&&TASK2_Handle)
		{
			//bit1置1
//			xEventGroupSetBits(EventGroup_Handle,1<<1);
//			err=xSemaphoreGive(Queue_Handle);
			printf("\r\n\r\n消息邮箱发送键值为:%d\r\n\r\n",key);
			xTaskNotify(TASK2_Handle,key1,eSetValueWithoutOverwrite);

			if(err==pdTRUE)
			{
				printf("\r\n释放信号量成功\r\n");
			}
		}
		vTaskDelay(10);

	}
}
//接收任务通知
void TASK2(void* pvParameters)
{
	uint32_t key;//缓存
	EventBits_t event_bit=0;
	while(1)
	{
//		key=ulTaskNotifyTake(pdTRUE,portMAX_DELAY);//二值信号量
		key=ulTaskNotifyTake(pdFALSE,portMAX_DELAY);//计数型
		xTaskNotifyWait(0,0xFFFFFFFF,&key,portMAX_DELAY);
//		if(key)
//		{
//			printf("\r\n接收任务通知成功,模拟二值信号量:%#x\r\n",key);//0x03
//		}
		if(key)
		{
			printf("\r\n接收任务通知成功key:%#x\r\n",key);//0x03
			LED1_TOGGLE;
		}
		vTaskDelay(1000);
	}
}

十五、软件定时器

1.简介

在这里插入图片描述

//软件定时器MCU的一个外设	可提供PWM、输入捕获等功能  最常用的还是定时
//MCU 自带硬件定时  FreeRTOS提供了软件定时器 精度没硬件那么高  

概述

软件定时器需要设定时间,时间到达后就执行指定的功能函数

使用注意

功能函数是在定时器服务任务中执行的,所以一定不能在函数中调用任何会阻塞任务的API函数

  • 禁止调用vTaskDelay()等延时函数
  • 访问队列,信号量非零阻塞时间API函数也不能用

单次定时器

调用一次,时间到就执行一次功能函数,不会自动重启

周期定时器

开启后,定时器时间到执行,能够自动重启,循环往复执行

在这里插入图片描述

2.定时器相关配置 函数

在这里插入图片描述

/*
1 、configUSE_TIMERS
如果要使用软件定时器的话宏 configUSE_TIMERS 一定要设置为 1,当设置为 1 的话定时
器服务任务就会在启动 FreeRTOS 调度器的时候自动创建。
2 、configTIMER_TASK_PRIORITY
设置软件定时器服务任务的任务优先级,可以为 0~( configMAX_PRIORITIES-1)。优先级
一定要根据实际的应用要求来设置。如果定时器服务任务的优先级设置的高的话,定时器命令
队列中的命令和定时器回调函数就会及时的得到处理。
3 、configTIMER_QUEUE_LENGTH
此宏用来设置定时器命令队列的队列长度。
4 、configTIMER_TASK_STACK_DEPTH
此宏用来设置定时器服务任务的任务堆栈大小,单位为字,不是字节!,对于 STM32 来说
一个字是 4 字节。由于定时器服务任务中会执行定时器的回调函数,因此任务堆栈的大小一定
要根据定时器的回调函数来设置。

*/

在这里插入图片描述

2.1复位软件定时器

在这里插入图片描述

2.2 函数 xTimerReset()
/*
	复位一个软件定时器,此函数只能用在任务中,不能用于中断服务函数!此函数是一个宏,
	真正执行的是函数 xTimerGenericCommand()
*/
BaseType_t xTimerReset( TimerHandle_t xTimer,
TickType_t xTicksToWait )
/*
	参数:
    xTimer : 要复位的软件定时器的句柄。
    xTicksToWait : 设置阻塞时间,调用函数 xTimerReset ()开启软件定时器其实就是向定时器命
    令队列发送一条 tmrCOMMAND_RESET 命令,既然是向队列发送消息,那
    肯定会涉及到入队阻塞时间的设置。
    返回值:
    pdPASS: 软件定时器复位成功,其实就是命令发送成功。
    pdFAIL: 软件定时器复位失败,命令发送失败。
*/
2.3 函数xTimerResetFromISR()
/*
	此函数是 xTimerReset()的中断版本,此函数用于中断服务函数中!此函数是一个宏,真正执行的是函数 xTimerGenericCommand())
*/
BaseType_t xTimerResetFromISR( TimerHandle_t xTimer,
BaseType_t * pxHigherPriorityTaskWoken );
/*
	参数:
    xTimer : 要复位的软件定时器的句柄。
    pxHigherPriorityTaskWoken : 记退出此函数以后是否进行任务切换,这个变量的值函数会自
    动设置的,用户不用进行设置,用户只需要提供一个变量来保
    存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函
    数之前一定要进行一次任务切换。
    返回值:
    pdPASS: 软件定时器复位成功,其实就是命令发送成功。
    pdFAIL: 软件定时器复位失败,命令发送失败。
*/
2.4创建软件定时器

在这里插入图片描述

2.5 函数 xTiemrCreate()
/*此函数用于创建一个软件定时器,所需要的内存通过动态内存管理方法分配。新创建的软件 定 时 器 处 于 休 眠 状 态 , 也 就 是 未 运 行 的 。 函 数 xTimerStart() 、 xTimerReset() 、
xTimerStartFromISR() 、 xTimerResetFromISR() 、 xTimerChangePeriod() 和
xTimerChangePeriodFromISR()可以使新创建的定时器进入活动状态*/
TimerHandle_t xTimerCreate( const char * const pcTimerName,
                TickType_t xTimerPeriodInTicks,
                UBaseType_t uxAutoReload,
                void * pvTimerID,
                TimerCallbackFunction_t pxCallbackFunction )
   /* 参数:
    pcTimerName : 软件定时器名字,名字是一串字符串,用于调试使用。
    xTimerPeriodInTicks : : 软 件 定 时 器 的 定 时 器 周 期 , 单 位 是 时 钟 节 拍 数 。 可 以 借 助portTICK_PERIOD_MS 将 ms 单位转换为时钟节拍数。举个例子,定
    时器的周期为 100 个时钟节拍的话,那么 xTimerPeriodInTicks 就为
    100,当定时器周期为 500ms 的时候 xTimerPeriodInTicks 就可以设置
    为(500/ portTICK_PERIOD_MS)。
    uxAutoReload : 设置定时器模式,单次定时器还是周期定时器?当此参数为 pdTRUE
    的时候表示创建的是周期定时器。如果为 pdFALSE 的话表示创建的
    是单次定时器。
    pvTimerID : 定时器 ID 号,一般情况下每个定时器都有一个回调函数,当定时器定
    时周期到了以后就会执行这个回调函数。但是 FreeRTOS 也支持多个
    定时器共用同一个回调函数,在回调函数中根据定时器的 ID 号来处
    理不同的定时器。
    pxCallbackFunction : 定时器回调函数,当定时器定时周期到了以后就会调用这个函数。
    返回值:
    NULL: 软件定时器创建失败。
    其他值: 创建成功的软件定时器句柄。*/
2.6 函数xTimerCreateStatic()
/*此函数用于创建一个软件定时器,所需要的内存需要用户自行分配。新创建的软件定时器
处于休眠状态,也就是未运行的。函数 xTimerStart()、xTimerReset()、xTimerStartFromISR()、
xTimerResetFromISR()、xTimerChangePeriod()和 xTimerChangePeriodFromISR()可以使新创建的
定时器进入活动状态*/
TimerHandle_t xTimerCreateStatic(const char * const pcTimerName,
                    TickType_t xTimerPeriodInTicks,
                    UBaseType_t uxAutoReload,
                    void * pvTimerID,
                    TimerCallbackFunction_t pxCallbackFunction,
                    StaticTimer_t * pxTimerBuffer )
   /* 
   参数:
pcTimerName : 软件定时器名字,名字是一串字符串,用于调试使用。
xTimerPeriodInTicks : : 软 件 定 时 器 的 定 时 器 周 期 , 单 位 是 时 钟 节 拍 数 。 可 以 借 助
portTICK_PERIOD_MS 将 ms 单位转换为时钟节拍数。举个例子,定
时器的周期为 100 个时钟节拍的话,那么 xTimerPeriodInTicks 就为
100,当定时器周期为 500ms 的时候 xTimerPeriodInTicks 就可以设置
为(500/ portTICK_PERIOD_MS)。
uxAutoReload : 设置定时器模式,单次定时器还是周期定时器?当此参数为 pdTRUE
的时候表示创建的是周期定时器。如果为 pdFALSE 的话表示创建的
是单次定时器。
pvTimerID : 定时器 ID 号,一般情况下每个定时器都有一个回调函数,当定时器定
时周期到了以后就会执行这个回调函数。当时 FreeRTOS 也支持多个
定时器共用同一个回调函数,在回调函数中根据定时器的 ID 号来处
理不同的定时器。
pxCallbackFunction : 定时器回调函数,当定时器定时周期到了以后就会调用这个函数。
pxTimerBuffer : 参数指向一个 StaticTimer_t 类型的变量,用来保存定时器结构体。
返回值:
NULL: 软件定时器创建失败。
其他值: 创建成功的软件定时器句柄。
*/
2.7开启软件定时器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AodvYsEy-1690684099683)(image/1690269030325.png)]

2.8 函数xTimerStart()
/*
启动软件定时器,函数 xTimerStartFromISR()是这个函数的中断版本,可以用在中断服务函
数中。如果软件定时器没有运行的话调用函数 xTimerStart()就会计算定时器到期时间,如果软
件定时器正在运行的话调用函数 xTimerStart()的结果和 xTimerReset()一样。此函数是个宏,真
正执行的是函数 xTimerGenericCommand
*/
BaseType_t xTimerStart( TimerHandle_t xTimer,
TickType_t xTicksToWait )
    
/*
	参数:
xTimer : 要开启的软件定时器的句柄。
xTicksToWait : 设置阻塞时间,调用函数 xTimerStart()开启软件定时器其实就是向定时器命令
队列发送一条 tmrCOMMAND_START 命令,既然是向队列发送消息,那肯
定会涉及到入队阻塞时间的设置。
返回值:
pdPASS: 软件定时器开启成功,其实就是命令发送成功。
pdFAIL: 软件定时器开启失败,命令发送失败。
*/
2.9 函数 xTimerStartFromISR()
/*
此函数是函数 xTimerStart()的中断版本,用在中断服务函数中,此函数是一个宏,真正执行
的是函数 xTimerGenericCommand()
*/
BaseType_t xTimerStartFromISR( TimerHandle_t xTimer,
BaseType_t * pxHigherPriorityTaskWoken );
    
/*
	参数:
    xTimer : 要开启的软件定时器的句柄。
    pxHigherPriorityTaskWoken : 标记退出此函数以后是否进行任务切换,这个变量的值函数会自动设置的,用户不用进行设置,用户只需要提供一个变量来
    保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务
    函数之前一定要进行一次任务切换。
    返回值:
    pdPASS: 软件定时器开启成功,其实就是命令发送成功。
    pdFAIL: 软件定时器开启失败,命令发送失败。
*/
2.10停止软件定时器

在这里插入图片描述

2.11 函数xTimerStop()
/*
此函数用于停止一个软件定时器,此函数用于任务中,不能用在中断服务函数中!此函数
是一个宏,真正调用的是函数 xTimerGenericCommand()
*/
BaseType_t xTimerStop ( TimerHandle_t xTimer,
TickType_t xTicksToWait )
    
/*
	参数:
xTimer : 要停止的软件定时器的句柄。
xTicksToWait : 设置阻塞时间,调用函数 xTimerStop()停止软件定时器其实就是向定时器命令
队列发送一条 tmrCOMMAND_STOP 命令,既然是向队列发送消息,那肯定
会涉及到入队阻塞时间的设置。
返回值:
pdPASS: 软件定时器停止成功,其实就是命令发送成功。
pdFAIL: 软件定时器停止失败,命令发送失败。
*/
2.12 函数xTimerStopFromISR()
/*
此函数是 xTimerStop()的中断版本,此函数用于中断服务函数中!此函数是一个宏,真正执
行的是函数 xTimerGenericCommand()
*/
BaseType_t xTimerStopFromISR( TimerHandle_t xTimer,
BaseType_t * pxHigherPriorityTaskWoken );
    
/*
	参数:
xTimer : 要停止的软件定时器句柄。
pxHigherPriorityTaskWoken : 标记退出此函数以后是否进行任务切换,这个变量的值函数会
自动设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务
函数之前一定要进行一次任务切换。
返回值:
pdPASS: 软件定时器停止成功,其实就是命令发送成功。
pdFAIL: 软件定时器停止失败,命令发送失败。
*/
3.实验设计
  • start_task: 创建task1任务,并创建两个定时器(单次和周期)

  • task1:用于按键扫描,并对软件定时器进行开启、停止操作

 //启用软件定时器
#define configUSE_TIMERS				            1                              
//软件定时器优先级
#define configTIMER_TASK_PRIORITY		        (configMAX_PRIORITIES-1)        
//软件定时器队列长度
#define configTIMER_QUEUE_LENGTH		        10                               
//软件定时器任务堆栈大小
#define configTIMER_TASK_STACK_DEPTH	      (configMINIMAL_STACK_SIZE*2)    

// 1. 定义句柄 2.创建任务 3.编写回调函数 4.实现任务1,key1 按下开启定时器,key2 关闭
//创建 单次
TimerHandle_t Timer_Handle=0;//单次
TimerHandle_t Timer_Handle1=0;//周期
	
Timer_Handle=xTimerCreate("timer1", //任务名
								1000,  //周期1000ms
								pdFALSE,  //单次设置
								(void *)1, //ID
								Timer1Callback);
//创建 周期
	Timer_Handle1=xTimerCreate("timer2", //任务名
								1000,  //周期1000ms
								pdTRUE,  //单次设置
								(void *)2,//ID
								Timer2Callback);

void Timer1Callback(TimerHandle_t pxTimer)
{
	static uint32_t timer1=0;
	timer1++;
	printf("\r\ntimer1的运行次数:%d\r\n",timer1);
}

void Timer2Callback(TimerHandle_t pxTimer)
{
	static uint32_t timer2=0;
	timer2++;

	printf("\r\ntimer2的运行次数:%d\r\n",timer2);
}
void TASK1(void* pvParameters)
{
	uint8_t key=0,key1=0;
	while(1)
	{
		key=Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN);
		key1=Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN);
		if(key&&TASK2_Handle)
		{
			//开启单次
			xTimerStart(Timer_Handle,portMAX_DELAY);
		//开启周期
			xTimerStart(Timer_Handle1,portMAX_DELAY);
		}
		if(key1&&TASK2_Handle)
		{
			//关闭单次
			xTimerStart(Timer_Handle,portMAX_DELAY);
		//关闭周期
			xTimerStart(Timer_Handle1,portMAX_DELAY);
		}
		vTaskDelay(100);

	}
}

十六、低功耗模式

1.简介
  • 裸机 低功耗模式

在这里插入图片描述

低功耗模式简介

很多应用场合对于功耗的要求很严格

  • 如可穿戴设备低功耗产品 智能手表
  • 物联网低功耗产品

MCU低功耗模式

睡眠模式

停止模式

待机模式

FreeRTOS 提供的低功耗模式

Tickless低功耗模式

如何降低功耗?

本质调用指令WFI实现睡眠 模式

降低功耗,不影响系统运行

MCU大部分时间执行空闲任务,可以在本该空闲任务执行的期间,让MCU进入相应的低功耗模式,其他任务准备运行的时候唤醒MCU退出低功耗模式

难点

进入低功耗之后,多久唤醒,下一个运行的任务如何被准确唤醒

任何中断均可唤醒MCU,若滴答定时器频繁中断则会影响低功耗的效果?

  • 将滴答定时器的中断周期修改为低功耗运行时间
  • 退出低功耗后,需补上系统时钟节拍数

Tickless模式 解决了上述难点

2.Tickless模式,相关配置项

在这里插入图片描述

#define configUSE_TICKLESS_IDLE 1 //1 启用低功耗 tickless 模式
/*
	系统处于低功耗模式的时间至少大于 configEXPECTED_IDLE_TIME_BEFORE_SLEEP
个时钟节拍,宏 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 默认在文件 FreeRTOS.h 中定
义为 2,我们可以在 FreeRTOSConfig.h 中重新定义,此宏必须大于 2!

 、宏 configPRE_SLEEP_PROCESSING ()和 和 configPOST_SLEEP_PROCESSING()
 用户编写
 在真正的低功耗设计中不仅仅是将处理器设置到低功耗模式就行了,还需要做一些其他的
处理,比如:
● 将处理器降低到合适的频率,因为频率越低功耗越小,甚至可以在进入低功耗模式以后
关闭系统时钟。
● 修改时钟源,晶振的功耗肯定比处理器内部的时钟源高,进入低功耗模式以后可以切换
到内部时钟源,比如 STM32 的内部 RC 振荡器。
● 关闭其他外设时钟,比如 IO 口的时钟。
● 关闭板子上其他功能模块电源,这个需要在产品硬件设计的时候就要处理好,比如可以
通过MOS管来控制某个模块电源的开关,在处理器进入低功耗模式之前关闭这些模块的电源
*/

十七、内存管理

1.简介

在这里插入图片描述

2.字节对齐

struct a{
    int a;// 地址 0
    char i;// 地址 4
    int b;//地址5
}
//4字节对齐后
struct a{
    int a;// 地址 0
    char i;// 地址 4
    char gap[3];//补充3个字节
    int b;//地址8
}
//4字节对齐后
3.内存申请函数heap.4
//内存堆的容量由宏 configTOTAL_HEAP_SIZE 来确定的
3.1 pvPortMalloc( )
  • 内存申请函数
void *pvPortMalloc( size_t xWantedSize )
char *buf;
buf=pvPortMalloc(30);
3.2vPortFree( )
  • 内存释放函数
void vPortFree()( void *pv )
if(buf!=NULL)
{
  vPortFree(buf);
	buf=NULL;    
}

3.3 xPortGetFreeHeapSize();
  • 获取剩余内存大小
//内存堆的容量由宏 configTOTAL_HEAP_SIZE 
uint32_t free;
free=xPortGetFreeHeapSize();
//打印
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
正点原子(DFRobot)是一个提供电子开发平台和教育资源的公司,而FreeRTOS是一个开源的实时操作系统。正点原子FreeRTOS是基于FreeRTOS内核进行封装和优化的版本。它提供了一系列内核控制函数,用于管理任务、调度器、时间片等核心功能。这些内核控制函数由FreeRTOS内核所使用,一般情况下应用层程序不直接使用这些函数。在FreeRTOS官网上可以找到这些内核控制函数的详细说明和用法。其中,delay_init()函数用于根据FreeRTOS的系统时钟节拍来初始化滴答定时器,以设置滴答定时器的中断周期。滴答定时器是FreeRTOS提供的用于刷新系统时间和进行任务调度的定时器。需要注意的是,滴答定时器的时钟频率在正点原子FreeRTOS中被设置为AHB的频率,一般为72MHz。而系统时钟节拍由宏configTICK_RATE_HZ来设置,用户可以根据需求自由设置其值。值得提醒的是,FreeRTOS还提供了一些仅供系统内核使用的函数,用户应用程序一般不应直接调用这些API函数。这些API函数被称为系统内核控制函数,可以用于实现内核的管理和控制功能。在学习FreeRTOS中的内核控制函数时,可以参考相关的文档和教程,深入了解每个函数的功能和使用方法。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [正点原子FreeRTOS(上)](https://blog.csdn.net/zhuguanlin121/article/details/124237033)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值