公司动态
FreeRTOS——消息队列
引入在裸机中若写入以下程序若函数A一直没有将flag置1则函数B需要一直判断非常占用CPU资源void Fun_A() { if(条件成立) flag 1; } void Fun_B() { if(flag 1) 进行操作; } int flag 0; int main() { while(1) { Fun_A(); Fun_B(); } }若在实时操作系统中使用消息队列传递信息当消息队列非空任务B读取数据否则任务阻塞直到队列中有数据任务B才可以运行极大提高CPU的利用率void task_A() { while(1) { if(条件成立) 写队列; } } void task_B() { while(1) { 读队列; 进行操作; } } int main() { 创建消息队列; 创建task_A; 创建task_B; 开启任务调度器; }一、队列简介队列任务与任务、任务与中断之间进行数据交流的一种机制消息传递FreeRTOS基于队列 实现了多种功能其中包括队列集、互斥信号量、计数型信号量、二值信号量、 递归互斥信号量队列的核心关中断实现互斥、链表实现休眠和唤醒、环形缓冲区保存数据Q队列作用与全局变量作用的区别答功能类似于全局变量但又比全局变量更安全假设全局变量a 0现有两个任务都在操作全局变量a正常情况下a 2然而在操作系统中对于简单的变量自增其中也包含许多步骤。当a的值读到CPU中的 r0 寄存器中正在修改数据时此时任务2处于就绪态任务2的优先级 任务1的优先级就会打断任务1任务对变量a操作完成后此时a1。当任务2执行完成后任务1继续执行a 0恢复出来后进行修改则a 1但实际值a应该等于2全局变量的弊端数据无保护导致数据不安全当多个任务同时对同一变量操作时数据易受损对于队列而言在写队列和读队列中都会进行关中断、开中断的操作 可以防止多任务同时访问冲突在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做“队列项目”队列能够存储“队列项目”的最大数量称为队列的长度 在创建队列时队列长度以及队列项目的大小由编程者指定FreeRTOS队列的特点数据入队出队方式队列通常采用“先进先出”(FIFO)的数据存储缓冲机制即先入队的数据会先从队列中被读取FreeRTOS中也可以配置为“后进先出”LIFO方式数据传递方式FreeRTOS中队列采用实际值传递即将数据拷贝到队列中进行传递 FreeRTOS采用拷贝数据传递也可以传递指针所以传递较大的数据的时候采用指针传递(1)值拷贝的方式入队uint8_t value 0; xQueueSend(Queue1,value,portMAX_DELAY);2传递指针的方式入队uint8_t value 0; uint8_t* p value; xQueueSend(Queue1,p,portMAX_DELAY);多任务访问队列不属于某个任务任何任务和中断都可以向队列发送/读取消息出队、入队阻塞当任务向一个队列发送消息时可以指定一个阻塞时间假设此时当队列已满无法入队或者队列已空无法出队①若阻塞时间为0直接返回不会等待②若阻塞时间为0~port_MAX_DELAY等待设定的阻塞时间若在该时间内还无法入队超时后直接返回不再等待③若阻塞时间为port_MAX_DELAY死等一直等到可以入队或出队为止出/入队阻塞当多个任务写入消息给一个“满队列”时或者在一个空队列中接收数据这些任务都会进入阻塞状态 当队列有空间或者有队列项目时1、优先级最高的任务2、如果大家的优先级相同那等待时间最久的任务会进入就绪态二、队列结构体typedef struct QueueDefinition { int8_t * pcHead; /* 存储区域的起始地址 */ int8_t * pcWriteTo; /* 下一个写入的位置 */ union { QueuePointers_t xQueue; SemaphoreData_t xSemaphore; } u ; List_t xTasksWaitingToSend; /* 等待发送列表 */ /*队列满而入队失败进入阻塞状态的任务则挂在此列表上 */ List_t xTasksWaitingToReceive; /* 等待接收列表 */ /*队列空而出队失败进入阻塞状态的任务则挂在此列表上 */ volatile UBaseType_t uxMessagesWaiting; /* 非空闲队列项目的数量 */ UBaseType_t uxLength /* 队列长度 */ UBaseType_t uxItemSize; /* 队列项目的大小 */ volatile int8_t cRxLock; /* 读取上锁计数器 queueUNLOCKED未上锁*/ /*上锁后统计出队的队列项数量 */ volatile int8_t cTxLock /* 写入上锁计数器 queueUNLOCKED未上锁*/ /*上锁后统计入队的队列项数量 */ /* 其他的一些条件编译 */ } xQUEUE;队列锁当上锁后无法操作队列等待发送列表、等待接收列表但可以正常进行对队列的读写操作默认初始化不上锁共用体队列的基本操作示意图三、队列相关API函数使用队列的主要流程创建队列 —— 写队列—— 读队列3.1 动态创建队列xQueueCreate()头文件queue.h使用条件configSUPPORT_DYNAMIC_ALLOCATION 必须在 FreeRTOSConfig.h 中被设置为 1或保留未定义状态此时它默认为 1)函数原型QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );参数说明参数描述uxQueueLength队列可同时容纳的最大项目数uxItemSize存储队列中的每个数据项所需的大小以字节为单位返回值成功返回所创建的队列句柄失败NULL用法示例struct AMessage { char ucMessageID; char ucData[ 20 ]; }; void vATask( void *pvParameters ) { QueueHandle_t xQueue1, xQueue2; /* Create a queue capable of containing 10 unsigned long values. */ xQueue1 xQueueCreate( 10, sizeof( unsigned long ) ); if( xQueue1 NULL ) { /* Queue was not created and must not be used. */ } /* Create a queue capable of containing 10 pointers to AMessage structures. These are to be queued by pointers as they are relatively large structures. */ xQueue2 xQueueCreate( 10, sizeof( struct AMessage * ) ); if( xQueue2 NULL ) { /* Queue was not created and must not be used. */ } /* ... Rest of task code. */ }注#define xQueueCreate ( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), (queueQUEUE_TYPE_BASE ))动态创建队列函数实际是通过一个宏实现的被定义的函数有三个参数最后一个参数指定了参数的不同类型动态创建队列函数详解QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType ) { Queue_t * pxNewQueue NULL; size_t xQueueSizeInBytes; uint8_t * pucQueueStorage; /* 判断队列长度是否大于0以及是否溢出 if( ( uxQueueLength ( UBaseType_t ) 0 ) ( ( SIZE_MAX / uxQueueLength ) uxItemSize ) /* Check for addition overflow. */ ( ( SIZE_MAX - sizeof( Queue_t ) ) ( uxQueueLength * uxItemSize ) ) ) { /* 计算队列项的总大小 */ xQueueSizeInBytes ( size_t ) ( uxQueueLength * uxItemSize ); /* 申请队列结构体以及环形缓冲区的内存空间 */ pxNewQueue ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) xQueueSizeInBytes ); /* 申请成功 */ if( pxNewQueue ! NULL ) { pucQueueStorage ( uint8_t * ) pxNewQueue; //队列结构体的首地址 pucQueueStorage sizeof( Queue_t ); //队列环形缓冲区的首地址 #if ( configSUPPORT_STATIC_ALLOCATION 1 ) { pxNewQueue-ucStaticallyAllocated pdFALSE; } #endif /* configSUPPORT_STATIC_ALLOCATION */ /* 初始化队列 */ prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue ); } else { traceQUEUE_CREATE_FAILED( ucQueueType ); mtCOVERAGE_TEST_MARKER(); } } else { configASSERT( pxNewQueue ); mtCOVERAGE_TEST_MARKER(); } return pxNewQueue; }动态创建队列流程图各个成员的初值如图所示3.2 写入消息入队函数描述xQueueSend()往队列的尾部写入消息xQueueSendToBack()同xQueueSend()xQueueSendToFront()往队列的头部写入消息xQueueOverwrite()覆写队列消息只用于队列长度为 1的情况xQueueSendFromISR()在中断中往队列的尾部写入消息xQueueSendToBackFromISR()同xQueueSendFromISR()xQueueSendToFrontFromISR()在中断中往队列的头部写入消息xQueueOverwriteFromISR()在中断中覆写队列消息只用于队列长度为 1的情况可以看出写入函数调用的是同一个函数xQueueGenericSend( )只是指定了不同的写入位置以及阻塞时间队列写入位置一共有三种注意覆写方式写入队列只有在队列的队列长度为 1时才能够使用不论队列中是否有数据都可以写进去并且不会阻塞总结写入队列尾部从pcWriteTo位置开始写入之后地址即向上增长如果超过既定区域返回头尾部pcWriteTo指向初始位置即pcHead指向的位置写入队列头部从pcReadFrom位置开始写入之后地址--即向下增长如果超过既定区域返回头部 pcReadFrom指向初始位置即pcTail - 一个队列项的大小写入队列流程图3.3 读取消息出队函数描述xQueueReceive()从队列头部读取消息并删除消息xQueuePeek()从队列头部读取消息xQueueReceiveFromISR()在中断中从队列头部读取消息并删除消息xQueuePeekFromISR()在中断中从队列头部读取消息1xQueueReceive()BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )此函数用于在任务中从队列中读取消息并且消息读取成功后会将消息从队列中移除。2xQueuePeek()BaseType_t xQueuePeek( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )此函数用于在任务中从队列中读取消息 但与函数 xQueueReceive()不同此函数在成功读取消息后并不会移除已读取的消息读消息将pcReadFrom指向位置的数据拷贝出来如果读取的位置等于尾部则将头部位置赋给读取的位置进行读取消息读队列流程图