FreeRTOS的任务间通信

文章目录

  • 4 FreeRTOS任务间通信
    • 4.1 队列
      • 4.1.1 队列的使用
      • 4.1.2 队列的创建,删除,复位
      • 4.1.3 队列的发送,接收,查询
    • 4.2 邮箱(mailbox)
      • 4.2.1 任务中读写邮箱
      • 4.2.2 中断中读写邮箱
    • 4.3 队列集
      • 4.3.1 队列集的创建
      • 4.3.2 队列集读写使用
    • 4.4 信号量
      • 4.4.1 信号量基础
      • 4.4.2 give,take操作
      • 4.4.3 信号量的使用
    • 4.5 互斥量
      • 4.5.1 互斥量与信号量实现互斥异同点
      • 4.5.2 优先级反转
      • 4.5.3 优先级继承
      • 4.5.4 互斥量的创建,删除
    • 4.6 事件组(事件标志组)
      • 4.6.1 事件位
      • 4.6.2 事件组
      • 4.6.3 事件组的工作流程
      • 4.6.5 事件组控制块
      • 4.6.6 事件组实验

4 FreeRTOS任务间通信

​ 为了实现FreeRTOS任务之间的同步和临界资源互斥的问题,我们需要进行任务之间的通信,针对于任务之间通信,FreeRTOS一般有如下方法:

  • 队列:先进先出的一种结构(队列集)
  • 邮箱:特殊的一种队列
  • 信号量(semaphoe)
  • 互斥量(mutex):类似于锁的一种机制。
  • 事件组(event group):事件的组合
  • 任务通知(task notification)
  • StreamBuffer流媒体存储

4.1 队列

4.1.1 队列的使用

​ 队列是一种先进先出的数据结构,一般来说,写数据一般写在尾部,读数据一般读头部的数据。类似于Linux中的消息队列。

在这里插入图片描述

  • 队列中可以包含若干项的数据,数据的个数称为队列的长度。
  • 每个数据的大小是固定的,在建立队列的时候就已经确定了队列的长度和每个数据的大小。
  • 将新的数据放到头部时,并不会覆盖掉原来的数据,队列的本质是循环buffer。
  • 在队列中没有数据的时候,消费者(接收方)是处于阻塞态的。
  • 队列的长度N时,元素的角标是0~N-1。

​ 队列传输数据可以选择两种方法:(但是要注意单片机的地址都是物理地址)

  • 拷贝:就是值传递,传递一个值过去,另一个task也会生成一个该值的局部变量的副本,两个task之间的这个值是不会互相干扰的。
  • 引用:就是地址传递,需要注意的是,由于没有MMU的缘故,单片机传递的是物理地址而不是虚拟地址,所以也就是说两个任务此时此刻用的是同一个地址里面的值,传地址的时候一定要注意临界资源的冲突。

4.1.2 队列的创建,删除,复位

​ 队列的创建分为动态创建内存和静态创建内存两个方式:

/*@brief  动态分配内存创建队列函数@retval 返回创建成功的队列句柄,如果返回NULL则表示因内存不足创建失败@param  uxQueueLength:队列深度uxItemSize:队列中数据单元的长度,以字节为单位
*/
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);/*@brief  静态分配内存创建队列函数@retval 返回创建成功的队列句柄,如果返回NULL则表示因内存不足创建失败@param  uxQueueLength:队列深度uxItemSize:队列中数据单元的长度,以字节为单位pucQueueStorageBuffer:队列栈空间数组pxQueueBuffer:指向StaticQueue_t类型的用于保存队列数据结构的变量*/
QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,UBaseType_t uxItemSize,uint8_t *pucQueueStorageBuffer,StaticQueue_t *pxQueueBuffer);/*example:创建一个深度为5,队列单元占uint16_t大小队列*/
QueueHandle_t QueueHandleTest;
QueueHandleTest = xQueueCreate(5, sizeof(uint16_t));
/*** @brief  删除队列* @retval None* @param  pxQueueToDelete:要删除的队列句柄
*/
void vQueueDelete(QueueHandle_t pxQueueToDelete);/*** @brief  将队列重置为其原始空状态* @retval pdPASS(从FreeRTOS V7.2.0之后)* @param  xQueue:要复位的队列句柄
*/
BaseType_t xQueueReset(QueueHandle_t xQueue);

4.1.3 队列的发送,接收,查询

​ 队列发送(写队列)

/*@brief  向队列后方发送数据(FIFO先入先出)@retval pdPASS:数据发送成功,errQUEUE_FULL:队列满无法写入@param  xQueue:要写入数据的队列句柄pvItemToQueue:要写入的数据xTicksToWait:阻塞超时时间,单位为节拍数,portMAXDELAY表示无限等待
*/
BaseType_t xQueueSend(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait);/*@brief  向队列后方发送数据(FIFO先入先出),与xQueueSend()函数一致
*/
BaseType_t xQueueSendToBack(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait);/*@brief  向队列前方发送数据(LIFO后入先出)
*/
BaseType_t xQueueSendToFront(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait);/*@brief  以下三个函数为上述三个函数的中断安全版本@param  pxHigherPriorityTaskWoken:用于通知应用程序编写者是否应该执行上下文切换
*/
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);

​ 队列接收(读队列)

/* @brief  从队列头部接收数据单元,接收的数据同时会从队列中删除@retval pdPASS:数据接收成功,errQUEUE_FULL:队列空无读取到任何数据@param  xQueue:被读队列句柄pvBuffer:接收缓存指针xTicksToWait:阻塞超时时间,单位为节拍数
*/
BaseType_t xQueueReceive(QueueHandle_t xQueue,void *pvBuffer,TickType_t xTicksToWait);/*@brief  从队列头部接收数据单元,不从队列中删除接收的单元
*/
BaseType_t xQueuePeek(QueueHandle_t xQueue,void *pvBuffer,TickType_t xTicksToWait);/*** @brief  以下两个函数为上述两个函数的中断安全版本* @param  pxHigherPriorityTaskWoken:用于通知应用程序编写者是否应该执行上下文切换*/
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,void *pvBuffer,BaseType_t *pxHigherPriorityTaskWoken);BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue, void *pvBuffer);

​ 查询队列信息

/*** @brief  查询队列剩余可用空间数* @param  xQueue:被查询的队列句柄* @retval 返回队列中可用的空间数*/
UBaseType_t uxQueueSpacesAvailable(QueueHandle_t xQueue);/*** @brief  查询队列有效数据单元个数* @param  xQueue:被查询的队列句柄* @retval 当前队列中保存的数据单元个数*/
UBaseType_t uxQueueMessagesWaiting(const QueueHandle_t xQueue);/*** @brief  查询队列有效数据单元个数函数的中断安全版本*/
UBaseType_t uxQueueMessagesWaitingFromISR(const QueueHandle_t xQueue);

4.2 邮箱(mailbox)

​ 邮箱的本质就是队列,只不过是长度为1的对列,并且对于邮箱来说,写操作都是覆盖的,所以该队列满了也没事。照样可以进行写操作。而相应的读取数据时,数据并不会被移除。需要注意的是,这里讲到的函数并不只适用于邮箱,也适用于队列,只是由于覆盖和不移除的特质,常常如此使用,所以专门拎出来叫做“邮箱”

4.2.1 任务中读写邮箱

//内容写入邮箱,(用覆盖的方式写入)
BaseType_t xQueueOverwrite(QueueHandle_t xQueue, const void * pvItemToQueue);QueueHandle_t xQueue:队列句柄
const void * pvItemToQueue:数据指针,指向要复制的指针的开头,大小在创立队列时就已经确定了。//读取,但是不移除已经读取的数据
BaseType_t xQueuePeek( QueueHandle_t xQueue,void *pvBuffer, TickType_t xTicksToWait );QueueHandle_t xQueue:要读取的队列
void *pvBuffer:buffer指针,队列中头部的数据会被复制到这个指针之中,复制多少呢?在创建队列的时候已经决定了。
TickType_t xTicksToWait:读取的超时时间,与发送的超时时间一样,设置为0表示非阻塞,>0表示阻塞最大时间,portMAX_DELAY表示等待数据,直到有数据为止。

4.2.2 中断中读写邮箱

//写入,带中断保护
BaseType_t xQueueOverwriteFromISR (QueueHandle_t xQueue, const void * pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);//检测队列是否已满,(只适用于ISR)
BaseType_t xQueueIsQueueFullFromISR( const QueueHandle_t xQueue );

4.3 队列集

​ 队列集的本质也是一个队列,只不过之前存储的是一个个数据,而现在队列集存储的是一个个队列。使用队列集时需我们在FreeRTOSConfig.h中配置宏,队列集的长度等于队列集中所有队列长度之和。

#define configUSE_QUEUE_SETS 1 /*Queue Set 的函数开关*/

4.3.1 队列集的创建

/*队列集的长度应该是 队列A的长度+队列B的长度*/
QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength );const UBaseType_t uxEventQueueLength:队列集的长度
BaseType_t xQueueAddToSet(QueueSetMemberHandle_t xQueueOrSemaphore,    QueueSetHandle_t xQueueSet );QueueSetMemberHandle_t xQueueOrSemaphore:队列成员
QueueSetHandle_t xQueueSet:要加入到哪个队列集中

4.3.2 队列集读写使用

​ 队列集的读写和队列是相似的,他很像是select,从很多队列中找出是谁有数据,然后进行读写工作。(仍待学习)

4.4 信号量

4.4.1 信号量基础

​ 与Linux中的信号量十分相似,他不能够传输数据,只能够用来表示资源的个数,占用情况,任务可以对信号量进行give和take操作,即类似于Linux中的pv操作。常见的信号量有四种:计数信号量,二值信号量,互斥信号量(就是互斥量),递归信号量。这一节中我们主要了解计数信号量和二值信号量。这几种信号量除了取值不一样以外,其实操作都是一样的。

  • 信号量只传递状态,即(信号),而不能传递数据。所以我们一般用来做任务间的同步。同时相比于队列,他更加节省内存。
  • 计数型信号量的值:0~整数,用来表示资源池中还有多少资源可用。
  • 二进制信号量:取值返回为0或者1,但是二进制信号量的初始值都为0。

​ 其实我们使用队列也可以实现这样的功能,那么为什么我们需要信号量呢?主要有以下几点:

  • 使用队列可以传递数据,数据的保存需要空间。
  • 使用信号量时不需要传递数据,更加节省空间。
  • 使用信号量时不需要复制数据,效率更高。

ps:使用信号量时,需要配置FreeRTOSConfig.h中的宏定义。

//信号量使用时,需要将此宏定义的值设置为1 
#define configUSE_COUNTING_SEMAPHORES  1 //另外,若在keil编译时出现如下错误,也可能是由于宏定义configUSE_COUNTING_SEMAPHORES的缺失导致的。
Undefined symbol xQueueCreateCountingS

4.4.2 give,take操作

  • give给所用信号量+1,进行解锁操作。
  • take给所用信号量 - 1,进行加锁操作。
  • 对于计数信号量来说:give可以无限的往上加,直到达到了信号量的取值范围。
  • 对于二值信号量来说,若值为1,再进行give操作是无效的。
//对指定的信号量进行give操作
BaseType_t xSemaphoreGive( xSemaphoreHandle_t xSemaphore ); xSemaphoreHandle_t xSemaphore:信号量对应的句柄
//对指定的信号量进行take操作xSemaphoreTakeRecursive( SemaphoreHandle_t xSemaphore, TickType_t xBlockTime );xSemaphoreHandle_t xSemaphore:信号量对应的句柄
TickType_t xBlockTime:超时时间选项> 0 即超时的滴答数,可以用pdMS_TO_TICKS宏来规定时间。不阻塞,即阻塞时间为0, take不成功时,返回errportMAX_DELAY 一直阻塞take资源,直到成功take。

4.4.3 信号量的使用

(1)创建信号量

/*** @brief  动态分配内存创建二值信号量函数* @retval None* @param  xSemaphore:创建的二值信号量句柄*/
void vSemaphoreCreateBinary(SemaphoreHandle_t xSemaphore);/*** @brief  静态分配内存创建二值信号量函数* @retval 返回创建成功的信号量句柄,如果返回NULL则表示因为pxSemaphoreBuffer为空无法创建* @param  pxSemaphoreBuffer:指向一个StaticSemaphore_t类型的变量,该变量将用于保存信号量的状态*/
SemaphoreHandle_t xSemaphoreCreateBinaryStatic(StaticSemaphore_t *pxSemaphoreBuffer);/*** @brief  动态分配内存创建计数信号量函数* @retval 返回创建成功的信号量句柄,如果返回NULL则表示内存不足无法创建* @param  uxMaxCount:可以达到的最大计数值* @param  uxInitialCount:创建信号量时分配给信号量的计数值 */
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);/*** @brief  静态分配内存创建计数信号量函数* @retval 返回创建成功的信号量句柄,如果返回NULL则表示因为pxSemaphoreBuffer为空无法创建* @param  uxMaxCount:可以达到的最大计数值* @param  uxInitialCount:创建信号量时分配给信号量的计数值* @param  pxSempahoreBuffer:指向StaticSemaphore_t类型的变量,该变量然后用于保存信号量的数据结构体*/
SemaphoreHandle_t xSemaphoreCreateCountingStatic(UBaseType_t uxMaxCount,UBaseType_t uxInitialCount,StaticSemaphore_t pxSempahoreBuffer);//例如:
SemaphoreHandle_t xSemaphore //创建一个信号量句柄
xSemaphore = xSemaphoreCreateCounting( 10, 0 );//创立一个初始值为0,最大值为10的信号量

(2)(3)进行take,give操作(Linux中的pv操作)

(4)删除信号量

/*** @brief  释放信号量函数* @retval 如果信号量释放成功,则返回pdTRUE;如果发生错误,则返回pdFALSE* @param  xSemaphore:要释放的信号量的句柄*/
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);/*** @brief  释放信号量的中断安全版本函数* @retval 如果成功给出信号量,则返回pdTRUE,否则errQUEUE_FULL* @param  pxHigherPriorityTaskWoken:用于通知应用程序编写者是否应该执行上下文切换*/
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken);

4.5 互斥量

4.5.1 互斥量与信号量实现互斥异同点

​ 在前面学习的信号量中,我们似乎已经可以实现互斥的功能了,那么接下来学习的互斥量和二值信号量用来做互斥,有什么区别呢?

在这里我们解决三个问题,分别是上锁解锁任务不同,优先级反转,递归上锁/解锁问题。

  1. 上锁/解锁任务不同:即可以在任务1中上锁后,在任务2中解锁。这个问题无论是互斥量还是信号量都解决不了。
  2. 由于上锁阻塞,导致的优先级发生翻转,低优先级任务比高优先任务先执行的情况。使用互斥量而不是二值信号量可以解决这个问题。
  3. 在递归中上锁,一直申请,但不释放资源。会造成递归死锁。可以通过使用递归锁来解决。

4.5.2 优先级反转

​ 使用二值信号量作为互斥量的时候会导致优先级反转的问题。而互斥量带有优先级继承的功能,可以解决这个问题(优先级继承我们后面解释)。

优先级反转实验:

#include <...若干>void led_task(void *param);
void usart_task1(void *param);
void usart_task2(void *param);
void usart_task3(void *param);void SystemClock_Config(void);SemaphoreHandle_t xSemaphore;//二值信号量的全局变量int main()
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();xSemaphore = xSemaphoreCreateCounting( 1, 0 );xSemaphoreGive(xSemaphore ); xTaskCreate(usart_task1, "task1", 100, NULL, 1, NULL);xTaskCreate(usart_task2, "task2", 100, NULL, 2, NULL);xTaskCreate(usart_task3, "task3", 100, NULL, 3, NULL);vTaskStartScheduler();//三个任务,都是打印串口,但是优先级不一样,这里为了方便分析,我们只打印一次。
void usart_task1(void *param)
{printf("task1 taking\n");xSemaphoreTake(xSemaphore, portMAX_DELAY); printf("task 1:priority [1]\n");vTaskDelay( pdMS_TO_TICKS(1000));printf("task1 finish\n");printf("1 give mutex\n");xSemaphoreGive(xSemaphore);vTaskDelete( NULL );
}//任务2和任务3的开始我们都vTaskDelay延迟了10ms,用来模拟任务1先执行,之后再任务1进入阻塞态后任务2和3同时抢占的情况
void usart_task2(void *param)
{vTaskDelay( pdMS_TO_TICKS(10)); for(int i=0; i<=10; i++){//xSemaphoreTake(xSemaphore, portMAX_DELAY);printf("task 2:priority [2]\n");//xSemaphoreGive(xSemaphore); }vTaskDelete( NULL );
}void usart_task3(void *param)
{vTaskDelay( pdMS_TO_TICKS(10));printf("task3 takeing\n");xSemaphoreTake(xSemaphore, portMAX_DELAY);printf("task 3:priority [3]\n");;vTaskDelay( pdMS_TO_TICKS(1000));printf("task3 finish\n");printf("3 give mutex\n");xSemaphoreGive(xSemaphore); vTaskDelete( NULL );
}

实验现象及分析:

在这里插入图片描述

再上述实验中,我们使用vTaskDelay()来让task1先执行,之后task2和task3进行抢占。接下来我们分析执行过程,为什么会发生优先级反转情况:

  1. task1执行,进行take操作上锁后开始执行程序,task1程序进入到vTaskDelay()时,task1进入阻塞态
  2. task2和task3开始抢占运行态,但是task3的优先级高,所以task3抢占成功。
  3. task3抢占成功后,要进行take操作,但是这时候,资源是被task1占用的,所以task3再调用了take操作后进入阻塞态。
  4. task2优先级高,所以先执行task2,(此时task3因为锁的关系进入阻塞态无法运行)。
  5. task2运行结束,由于task3一直阻塞,所以此时是task3抢占后发现自己还是阻塞状态,没有办法,只能等task1什么时候把锁解开,进行give操作后,才能够抢占。

以上,才会出现本应该按照顺序3->2->1优先级运行的任务,变成了2->1->3的运行顺序。

4.5.3 优先级继承

​ 前面的实验中,task1上的锁在task3中被使用了,所以导致了翻转问题以及使得task1的优先级提高。**优先级继承(priority inheritance)是指当高优先级进程(t3)请求一个已经被被低优先级(t1)占有的临界资源时,将低优先级进程(t1)的优先级临时提升到与高优先级进程一样的级别,使得低优先级进程能更快地运行,从而更快地释放临界资源。低优先级进程离开临界区后,其优先级恢复至原本的值。**当我们使用互斥锁的时候,就会有优先级继承的功能。

4.5.4 互斥量的创建,删除

​ 在创建时,需要配置宏定义#define configUSE_MUTEXES 1

/*** @brief  动态分配内存创建互斥信号量函数* @retval 创建互斥信号量的句柄*/
SemaphoreHandle_t xSemaphoreCreateMutex(void);/*** @brief  静态分配内存创建互斥信号量函数* @param  pxMutexBuffer:指向StaticSemaphore_t类型的变量,该变量将用于保存互斥锁型信号量的状态* @retval 返回成功创建后的互斥锁的句柄,如果返回NULL则表示内存不足创建失败*/
SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t *pxMutexBuffer);/*** @brief  动态分配内存创建递归互斥信号量函数* @retval 创建递归互斥信号量的句柄,如果返回NULL则表示内存不足创建失败*/
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(void);/*** @brief  动态分配内存创建二值信号量函数* @param  pxMutexBuffer:指向StaticSemaphore_t类型的变量,该变量将用于保存互斥锁型信号量的状态*/
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(StaticSemaphore_t pxMutexBuffer);/*** @brief  删除信号量函数* @param  xSemaphore:要删除的信号量的句柄* @retval None*/
void vSemaphoreDelete(SemaphoreHandle_t xSemaphore);

4.6 事件组(事件标志组)

4.6.1 事件位

在这里插入图片描述

​ 事件的"标志"(即事件位)是一个布尔值(即0和1),用于指示某个事件是否发生了。而事件“组”就是事件位的集合。集合中有多少个事件位是由configUSE_16_BIT_TICKS宏所决定的,若配置为1,那么事件组长度为16个位,每个事件组包含8个可用的事件位。若配置为0,那么事件组的长度为32个位,每个事件组包含24个可用的事件位。(无论是哪种模式,事件组的高8位永远是保留位)。

​ 任何知道事件组存在的任务或ISR都可以访问它。任意数量的任务可以在同一事件组中设置位,并且任意数量的任务可以从同一事件组中读取位。所以我们可以利用事件组来进行任务间的通知,同步。

事件组可以用来标识事件的状态,从而针对不同的状态做出对应的动作,比如下面的几个例子:

  • 当收到一条消息并且需要把这条消息处理时,将某个位(标志)置1表示这个事件,当队列中没有消息需要处理的时候就可以将这个位(标志)置 0。
  • 当把队列中的某个消息需要通过网络发送时,将某个位(标志)置1表示这个事件,当没有数据需要从网络发送出去的时候,就将这个位(标志)置 0。
  • 现在需要向网络中发送一个心跳信息,将某个位(标志)置 1。现在不需要向网络中发送心跳信息,这个位(标志)置 0。
  • 等等等等,事件组的作用十分广泛。

4.6.2 事件组

​ 之前我们就已经提到过了,事件组就是一组事件位的集合,事件组中的不同的事件位通过其位编号来访问,按照之前的来举例子,那么可以有如下的定义:

  • 事件标志组的 bit0 表示队列中的消息是否处理掉。
  • 事件标志组的 bit1 表示是否有消息需要从网络中发送出去。
  • 事件标志组的 bit2 表示现在是否需要向网络发送心跳信息。
  • 。。。

在使用事件组之前,我们需要设置宏定义configUSE_16_BIT_TICKS,这里我们再次强调

#define configUSE_16_BIT_TICKS 1	// 时,事件标志组可以存储 8 个事件位
#define configUSE_16_BIT_TICKS 0	// 时,事件标志组存储 24 个事件位。
//ps:高八位永远是保留位不使用!!!//而时间组存储事件位依靠的是EventBits_t的数据类型,这个数据类型可以是16位的也可以是32位的,主要取决于上面的宏定义。

4.6.3 事件组的工作流程

(1)创建,删除事件组

​ 创建事件组有动态和静态两种方式,我们常使用的都是静态创建方式。

/*			@brief:动态创建事件组。@retval:成功返回事件组的句柄,失败返回NULL,常见的失败原因是内存空间不足导致创建失败。
*/
EventGroupHandle_t xEventGroupCreate(void);/*			@brief:静态创建事件组。@retval:成功返回事件组的句柄,失败返回NULL,常见的失败原因是内存空间不足导致创建失败。@param:pxEventGroupBuffer:指向StaticEventGroup_t类型的变量,该变量用于存储事件组数据结构体。这个结构体需要我们提前配置好。
*/
EventGroupHandle_t xEventGroupCreateStatic(StaticEventGroup_t *pxEventGroupBuffer);/*---------------------------------------------------------------------------------------------------------------*/
/*			@brief:删除已经创建事件组。@retval:None@param:EventGroupHandle_t xEventGroup:要删除的事件组的句柄。
*/
void vEventGroupDelete(EventGroupHandle_t xEventGroup);

(2)设置,清零事件组的某个位

/*			@brief:用来设置一个事件组中某个位。@retval:如果uxBitsToSet中的一个或多个位在事件组被设置之后为1,那么xEventGroupSetBits将返回pdPASS。                     				如果uxBitsToSet中的所有位在事件组被设置之后都为0(即事件组已经在调用之前就处于这种状态),那么													xEventGroupSetBits将返回errQUEUE_FULL。@param:EventGroupHandle_t xEventGroup:要设置的事件组的句柄const EventBits_t uxBitsToSet:指定要在事件组中设置的一个或多个位的位值,一般来说设置单个位时常采用位操作进行																			 设置,例如设置0位时,用(1<<0)来设置,而设置多个位时,例如设置为0x09																							(即=0000 1001)表示置位3和位0。注意位是从0开始算的,而不是1哦。
*/
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet );/*---------------------------------------------------------------------------------------------------------------*/
/*@brief:将某个事件组中的某个位清零。@retval:返回清除指定位之前的事件组的值。@param:xEventGroup:要清除的事件位所在的事件组的句柄。uxBitsToSet:表示要在事件组中清除一个或多个位的按位值。
*/
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear);/*@brief:下面的两个函数是上述两个函数的中断安全版本。@retval:消息已发送到RTOS守护进程任务,则返回pdPASS,否则将返回pdFAIL@param:pxHigherPriorityTaskWoken:用于通知应用程序编写者是否应该执行上下文切换
*/
BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet,BaseType_t *pxHigherPriorityTaskWoken);BaseType_t xEventGroupClearBitsFromISR(EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToClear);

事件组使用例子:

/*example1: 将事件组 EventGroup_Test 的位 1 和 3 置位*/
EventBits_t val;
val = xEventGroupSetBits(EventGroup_Test, 0x0A);/*example2: 将事件组 EventGroup_Test 的位 0 和 2 清零*/
EventBits_t val;
val = xEventGroupClearBits(EventGroup_Test, 0x05);

(3)等待事件组的某个位

​ FreeRTOS 关于事件组提出了等待事件组和事件组同步两个比较重要的 API 函数,分别对应两种不同的使用场景,等待事件组主要用于使用事件组进行事件的管理,而另外一主要用于使用事件组进行任务间的同步。首先我们来了解一下等待事件组和事件组同步的概念

/*@brief:允许任务读取事件组的值,并且可以选择在阻塞状态下等待事件组中的一个或多个事件位被设置(如果事件位尚未设置)@retval:返回事件位,等待完成设置/或阻塞时间过期时的事件组值。@param:EventGroupHandle_t xEventGroup:所操作事件组的句柄const EventBits_t uxBitsToWaitFor:所等待事件位的掩码,例如设置为0x05表示等待第0位和/或第2位const BaseType_t xClearOnExit:pdTRUE表示事件组条件成立退出阻塞状态时将掩码指定的所有位清零;																									 pdFALSE表示事件组条件成立退出阻塞状态时不将掩码指定的所有位清零;const BaseType_t xWaitForAllBits:pdTRUE表示等待掩码中所有事件位都置1,条件才算成立(逻辑与);																										  pdFALSE表示等待掩码中所有事件位中一个置1,条件就成立(逻辑或);通俗来说的意思就是,是否等待所有事件都成立。TickType_t xTicksToWait:任务进入阻塞状态等待时间成立的超时节拍数。portMAX_DELAY为永不超时。
*/
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToWaitFor,const BaseType_t xClearOnExit,const BaseType_t xWaitForAllBits,TickType_t xTicksToWait );/*@brief:事件组同步@retval:返回函数退出时事件组的值@param:EventGroupHandle_t xEventGroup:所操作事件组的句柄uxBitsToSet:设置和测试位的事件组uxBitsToWaitFor:指定事件组中要测试的一个或多个事件位的按位值xTicksToWait:任务进入阻塞状态等待时间成立的超时节拍数
*/
//关于返回值:举个简单的例子就容易理解:假设目前有两个任务,分别为 TASK1 和 TASK2 ,如果 TASK1 被执行过程中因为延时等原因先于 TASK2 调用了 xEventGroupSync() 函数,参数 uxBitsToSet 被设置为 0x01(0000 0001),参数 uxBitsToWaitFor 被设置为 0x05(0000 0101),则 TASK1 执行到该函数时会将事件组中位 0 的值置 1 ,然后进入阻塞状态,等待位 2 和位 0 同时被置 1 ;如果 TASK2 与 TASK1 一样,只不过落后于 TASK1 执行 xEventGroupSync() 函数,并且参数 uxBitsToSet 被设置为 0x04(0000 0100),当 TASK2 执行该函数时会将事件组中位 2 的值置 1 ,此时满足解锁条件,所以 TASK2 不会进入阻塞状态,同时 TASK1 也满足解锁条件,从阻塞状态中退出,这时候假设任务优先级一致,则 TASK1 和 TASK2 会同时从同步点开始运行后续的程序代码,从而达到同步的目的。
EventBits_t xEventGroupSync(EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet,const EventBits_t uxBitsToWaitFor,TickType_t xTicksToWait);

PS:事件组只起到通知的作用,如果想把数据保存起来,就要另外使用其他方法保存数据。使用事件组的时候,需要#define configSUPPORT_STATIC_ALLOCATION 。

4.6.5 事件组控制块

//事件的标志组存储在EventBits_t 类型的变量中,该变量在事件组结构体中定义。除了这个,FreeRTOS还使用了一个链表来记录等待事件的任务,所有等待某个事件的任务都会被挂载到这个等待事件列表xTasksWaitingForBits下。

4.6.6 事件组实验

​ 配合中断,打印xEventGroupWaitBits的返回值。方便我们理解事件组的返回值。

#define REDLEDEVENT (1<<0)
#define GREENLEDEVENT (1<<1)
#define BLUELEDEVENT (1<<2)void SystemClock_Config(void);
void task_led(void *param);
EventGroupHandle_t event_handle1;int mian()
{event_handle1 = xEventGroupCreate();xTaskCreate(task_led, "task1",100,NULL,3,NULL);vTaskStartScheduler();
}
void task_led(void *param)
{EventBits_t val;while(1){val = xEventGroupWaitBits( event_handle1,REDLEDEVENT|GREENLEDEVENT|BLUELEDEVENT,pdTRUE,pdFALSE,portMAX_DELAY);printf("return val:%lu\n", val);}
}void HAL_GPIO_EXTI_Callback(uint16_t GPIO_PIN)
{   switch (GPIO_PIN){case GPIO_PIN_12:xEventGroupSetBits(event_handle1, REDLEDEVENT);break;case GPIO_PIN_13:xEventGroupSetBits(event_handle1, GREENLEDEVENT);break;case GPIO_PIN_14:xEventGroupSetBits(event_handle1, BLUELEDEVENT);break;}
}

实验现象:按顺序按下KEY1,KEY2,KEY3。观察printf的输出值(即wait的返回值)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/866483.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue打包文件dist放在SpringBoot项目下运行(正确实现全过程)

项目开发中&#xff0c;一般我们都会使用SpringBootVue进行前后端开发。 在部署时&#xff0c;会后端启动一个服务&#xff0c;再启动一个nginx&#xff0c;nginx中配置前端打包文件dist进行项目访问。 实际上&#xff0c;我们也可以把打包好的dist目录放在SpringBoot项目下进…

Os-hackNos

下载地址 https://download.vulnhub.com/hacknos/Os-hackNos-1.ova 环境配置如果出现&#xff0c;扫描不到IP的情况&#xff0c;可以尝试vulnhub靶机检测不到IP地址解决办法_vulnhub靶机扫描不到ip-CSDN博客 信息收集 确定靶机地址&#xff1a; 探测到存活主机192.168.111.…

如何利用AI撰写短文案获客?分享6大平台和3大步骤!

从去年开始&#xff0c;很多大厂都在裁员&#xff0c;原因就是因为AI的火爆&#xff0c;替代了很多机械式的劳动力。以前很多人可以通过机械式的工作来摸鱼&#xff0c;现在AI完成的效率比人工的要高很多倍。 国内好用的AI平台非常多&#xff0c;有时候也可以使用几个AI平台结合…

软件设计之Java入门视频(11)

软件设计之Java入门视频(11) 视频教程来自B站尚硅谷&#xff1a; 尚硅谷Java入门视频教程&#xff0c;宋红康java基础视频 相关文件资料&#xff08;百度网盘&#xff09; 提取密码&#xff1a;8op3 idea 下载可以关注 软件管家 公众号 学习内容&#xff1a; 该视频共分为1-7…

STM32学习历程(day2)

GPIO解释 GPIO(General-purpose input/output) 可以配置为八种输入输出模式 引脚电平 0V-3.3V 部分引脚可容忍5v 输出模式可控制端口输出高低电平 用以驱动LED、控制蜂鸣器、模拟通信协议输出时序 输入模式可读取端口的高低电平或电压&#xff0c;用于读取按键输入、外界…

uniapp自定义富文本现实组件(支持查看和收起)

废话不多说上代码 CollapseText.vue <template><view v-if"descr"><scroll-view class"collapse-text" :style"{maxHeight: computedMaxHeight}"><!-- <slot></slot> --><rich-text :nodes"descr&q…

2-网页请求的原理

网页请求的原理 ​ 网络爬虫请求网页的过程可以理解为用户使用浏览器加载网页的过程&#xff0c;这个过程其实是向Web服务器发送请求的过程&#xff0c;即浏览器向Web服务器发送请求&#xff0c;Web服务器会将响应内容以网页形式返回给浏览器。因此&#xff0c;了解浏览器与We…

Android Studio下载Gradle特别慢,甚至超时,失败。。。解决方法

使用Android studio下载或更新gradle时超级慢怎么办&#xff1f; 切换服务器&#xff0c;立马解决。打开gradle配置文件 修改服务器路径 distributionUrlhttps\://mirrors.cloud.tencent.com/gradle/gradle-7.3.3-bin.zip 最后&#xff0c;同步&#xff0c;下载&#xff0c;速…

数据融合工具(1)指定路径下同名图层合并

情景再现&#xff0c;呼叫小编 ————数据合并时&#xff0c;你是否也经常碰到这些情况&#xff1f; 数据存在几何错误&#xff0c;合并失败&#xff01; 数据字段类型不一致&#xff0c;合并失败&#xff01; 合并工具运行有警告信息&#xff0c;不知道是否合并成功&…

2024年中国网络安全市场全景图 -百度下载

是自2018年开始&#xff0c;数说安全发布的第七版全景图。 企业数智化转型加速已经促使网络安全成为全社会关注的焦点&#xff0c;在网络安全边界不断扩大&#xff0c;新理念、新产品、新技术不断融合发展的进程中&#xff0c;数说安全始终秉承科学的方法论&#xff0c;以遵循…

航模插头篇

一、常见的电池插头&#xff08;电调端 是公头 电池端 是母头&#xff09; 电池总是被插的 1.XT60头 过流大 安全系数高 难插拔 2.T插 插拔轻松 过流比较小 容易发烫 电调端 是公头 电池端 是母头 3.香蕉头插孔 过流够 插拔轻松 但 容易插反 爆炸 4.TX90(和XT60差…

11-阿里云服务器 ECS-FileZilla的文件传输

FileZilla的下载与安装以及简单使用(有图解超简单)-CSDN博客 Windows下 FileZilla客户端下载与安装 官方下载地址:https://www.filezilla.cn/download 绿色版我们可以理解为免安装版本,而安装版本则是需要运行Setup的引导程序,最大的区别就是会不会生成注册表。 filezi…

Running cmake version 2.8.12.2解决方案

Centos7安装mysql8.0&#xff0c;编译环节出现如下报错&#xff1a; Running cmake version 2.8.12.2 CMake Warning at CMakeLists.txt:82 (MESSAGE):Please use cmake3 rather than cmake on this platform-- Please install cmake3 (yum install cmake3) CMake Error at CMa…

通过一个单相逆变器仿真深度学习PR控制器

目录 前言 ​编辑 PR控制器的理论 PR控制器不同表达式及其建模 PR控制器连续积分组合及模型 PR控制器连续传递函数及模型 PR控制器离散积分及模型 PR控制器离散传递函数及模型 PR控制器差分方程及模型 系统仿真效果 总结 前言 在项目开发中常用PI控制器&#xff0c;这次在…

深入探索PHP中的多维数组:构建复杂数据结构的艺术

深入探索PHP中的多维数组&#xff1a;构建复杂数据结构的艺术 引言 在PHP开发中&#xff0c;数组&#xff08;Array&#xff09;是一种非常重要的数据类型&#xff0c;它允许我们存储多个值&#xff0c;并且这些值可以是不同类型的。而多维数组&#xff08;Multidimensional …

BeanUtils拷贝List数据

工具类&#xff1a; package com.ssdl.baize.pub;import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; import org.springframework.beans.BeanUtils;public class BeanConvertUti…

【BUUCTF-PWN】10-bjdctf_2020_babystack

简单的栈溢出&#xff0c;ret2text 64位&#xff0c;开启了NX保护 执行效果&#xff1a; main函数&#xff1a; 因为读入的字符长度可以由用户输入的第一个参数值决定&#xff0c;因此read函数存在栈溢出 覆盖距离为0x108 存在后门函数&#xff1a; 后门函数地址0x4…

AIGC | 在机器学习工作站安装NVIDIA cuDNN 深度学习库

[ 知识是人生的灯塔&#xff0c;只有不断学习&#xff0c;才能照亮前行的道路 ] 0x03.初识与安装 cuDNN 深度学习库 什么是cuDNN? cuDNN&#xff08;CUDA Deep Neural Network library&#xff09;是由英伟达&#xff08;NVIDIA&#xff09;开发的深度学习库&#xff0c;专门用…

昇思学习打卡-5-基于Mindspore实现BERT对话情绪识别

本章节学习一个基本实践–基于Mindspore实现BERT对话情绪识别 自然语言处理任务的应用很广泛&#xff0c;如预训练语言模型例如问答、自然语言推理、命名实体识别与文本分类、搜索引擎优化、机器翻译、语音识别与合成、情感分析、聊天机器人与虚拟助手、文本摘要与生成、信息抽…

LLMs之gpt_academic:gpt_academic的简介、安装和使用方法、案例应用之详细攻略

LLMs之gpt_academic&#xff1a;gpt_academic的简介、安装和使用方法、案例应用之详细攻略 目录 gpt_academic的简介 1、版本更新历史 版本: 1、新增功能及其描述 新界面&#xff08;修改config.py中的LAYOUT选项即可实现“左右布局”和“上下布局”的切换&#xff09; 所…