任务和ISR统称为进程,有时候任务与任务之间、任务与ISR之间需要进行通讯或者同步,这被称之为进程间的通信。FreeRTOS提供了完善的进程间通信技术,包括队列、信号量、互斥量等。
队列是一个缓冲区,用于在进程间传递少量数据,所以也称为消息队列。队列可以储存多个数据项,一般采用先进先出的存储方式,也可以采用后进先出的方式。
##创建一个队列
队列是FreeRTOS的一种操作对象,可以使用创建函数来创建队列:
// 动态创建一个消息队列 QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize ); // 静态创建一个消息队列 QueueHandle_t xQueueCreateStatic( UBaseType_t uxQueueLength, UBaseType_t uxItemSize, uint8_t *pucQueueStorage, StaticQueue_t *pxQueueBuffer);动态创建队列的内存由FreeRTOS从堆空间中自动分配,参数uxQueueLength是队列能够存储的最大项目数,也即是存储单元数,uxItemSize是每个队列项目的大小(字节),若创建消息队列成功则成功返回QueueHandle_t句柄,失败返回NULL。注意:队列的存储单元可以设置任意大小、存储任意数据类型,是以数据复制的方式来存储数据。如果数据太大那么就传入数据的指针,然后通过指针来读取原始数据。
静态创建所需的内存则是由用户在编译期时提供而不使用动态内存分配,pucQueueStorage指向一块预先分配的静态内存区域,用于存放队列的数据内容,这块内存必须足够大,用以容纳 uxQueueLength * uxItemSize 字节的数据,该指针不能为NULL,且必须指向一块已经正确分配的、连续的内存块;pxQueueBuffer指向一个StaticQueue_t类型的静态缓冲区结构体(控制块内存),用于保存队列的控制信息。因此在实际开发过程中,静态船舰队列要时刻注意内存的分配情况。
Part1:向队列写入数据
一个任务或ISR向队列写入数据被称为发送消息,队列是一个共享的存储区域,可以被多个进程写入数据,也可以被多个进程读取(这和前面提到的队列是一个缓冲区就联系上了);写入数据的方式有两种:FIFO和LIFO,这里会用到两个函数
##xQueueSendToBack()向队列后端写入数据,可以做到让数据先进先出(FIFO模式);
BaseType_t xQueueSendToBack( QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait );xQueue:指向数据发送目标队列句柄的指针
pvItemToQueue:指向要发送的数据的指针。该指针数据项的大小必须与队列创建时指定的项的大小一致,函数会从该指针指向的地址拷贝相应字节数的数据到队列的存储区中
xTicksToWait:在队列满且无法立即发送数据时,任务等待队列有空间可用的最大时间
如果数据项成功发送到队列尾部则返回pdPASS;反之则返回pdFAIL(一般是因为无法在指定时间内无法成功将数据放入队列)
##xQueueSendToFront()向队列前端写入数据,可以做到让数据先进后出(LIFO模式);
BaseType_t xQueueSendToBack( QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait );这个函数的参数与前一个函数基本一致,但是返回值有所区别,如果队列已满且在指定时间内未能发送数据会返回errrQueue_FULL。
在队列已满的情况下,我们可以使用xQueueOverWrite()函数写入,但是这个函数只能用于队列长度为1的队列,在队列已满时会覆盖原来的数据。所需参数与两个函数基本一致,只是有等待时间,返回值为pdPASS和pdFAIL。
BaseType_t xQueueOverWrite( QueueHandle_t xQueue, const void * pvItemToQueue );Part2:从队列读取数据
在任务和ISR里面读取数据,被称为接收消息;总是从队列的头部开始读取数据,如果读取数据成功则会删除这个单元的数据,后面的数据则往前移,所用到的函数为xQueueReceive():
BaseType_t xQueueReceive( QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait );xQueue是指向目标队列的句柄;pvBuffer是用于接收数据的缓冲区指针,实际使用时需要根据队列中存储的数据类型进行强制转换;xTicksToWait时等待队列中有数据可读的时间,如果设置为0,则立即返回,如果设置为portMAX_DELAY,就无限等待直到有数据可用;成功从队列中接收到数据返回pdTRUE,如果未在指定时间内接收到数据 且xTicksToWait不为0就返回pdFALSE。
如果我们不想在读取完数据后删除队列里面的数据可以使用另一个函数xQueuePeek(),参数基本一致
BaseType_t xQueuePeek( QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait );xQueueReset()可以丢弃队列里面的所有数据让队列恢复为空的状态。xQueue为指向待重置的队列的句柄;若成功则返回pdTRUE,若失败则返回pdFALSE,一般是由于传入的队列句柄无效或为空引起的。
BaseType_t xQueueReset( QueueHandle_t xQueue );ISR 中不能阻塞,也不能调用可能引起任务切换的普通发送/接收函数,所以如果你想在ISR里面操作任务你就需要用带有FromISR后缀的函数;在FreeRTOS中,执行上下文(Context)决定了可调用的API集合。任务上下文可以使用所有标准的FreeRTOS API,但严禁调用FromISR后缀的函数;而中断服务程序(ISR)只能调用FromISR版本,并且该中断的优先级在逻辑上必须不高于configMAX_SYSCALL_INTERRUPT_PRIORITY。对于中断中耗时或复杂的处理,应通过FromISRAPI将工作“延迟”到一个普通任务(即延迟中断处理任务)中完成。