文章目錄
- 系列教程總目錄
- 概述
-
5.1 隊(duì)列的特性
- 5.1.1 常規(guī)操作
- 5.1.2 傳輸數(shù)據(jù)的兩種方法
- 5.1.3 隊(duì)列的阻塞訪問(wèn)
-
5.2 隊(duì)列函數(shù)
- 5.2.1 創(chuàng)建
- 5.2.2 復(fù)位
- 5.2.3 刪除
- 5.2.4 寫(xiě)隊(duì)列
- 5.2.5 讀隊(duì)列
- 5.2.6 查詢
- 5.2.7 覆蓋/偷看
- 5.3 示例8: 隊(duì)列的基本使用
- 5.4 示例9: 分辨數(shù)據(jù)源
- 5.5 示例10: 傳輸大塊數(shù)據(jù)
- 5.6 示例11: 郵箱(Mailbox)
需要獲取更好閱讀體驗(yàn)的同學(xué),請(qǐng)?jiān)L問(wèn)我專門(mén)設(shè)立的站點(diǎn)查看,地址:http://rtos.100ask.net/
系列教程總目錄
本教程連載中,篇章會(huì)比較多,為方便同學(xué)們閱讀,點(diǎn)擊這里可以查看文章的 目錄列表,目錄列表頁(yè)面地址:https://blog.csdn.net/thisway_diy/article/details/121399484
概述
隊(duì)列(queue)可以用于"任務(wù)到任務(wù)"、“任務(wù)到中斷”、"中斷到任務(wù)"直接傳輸信息。
本章涉及如下內(nèi)容:
- 怎么創(chuàng)建、清除、刪除隊(duì)列
- 隊(duì)列中消息如何保存
- 怎么向隊(duì)列發(fā)送數(shù)據(jù)、怎么從隊(duì)列讀取數(shù)據(jù)、怎么覆蓋隊(duì)列的數(shù)據(jù)
- 在隊(duì)列上阻塞是什么意思
- 怎么在多個(gè)隊(duì)列上阻塞
- 讀寫(xiě)隊(duì)列時(shí)如何影響任務(wù)的優(yōu)先級(jí)
5.1 隊(duì)列的特性
5.1.1 常規(guī)操作
隊(duì)列的簡(jiǎn)化操如入下圖所示,從此圖可知:
- 隊(duì)列可以包含若干個(gè)數(shù)據(jù):隊(duì)列中有若干項(xiàng),這被稱為"長(zhǎng)度"(length)
- 每個(gè)數(shù)據(jù)大小固定
- 創(chuàng)建隊(duì)列時(shí)就要指定長(zhǎng)度、數(shù)據(jù)大小
- 數(shù)據(jù)的操作采用先進(jìn)先出的方法(FIFO,F(xiàn)irst In First Out):寫(xiě)數(shù)據(jù)時(shí)放到尾部,讀數(shù)據(jù)時(shí)從頭部讀
- 也可以強(qiáng)制寫(xiě)隊(duì)列頭部:覆蓋頭部數(shù)據(jù)

更詳細(xì)的操作入下圖所示:

5.1.2 傳輸數(shù)據(jù)的兩種方法
使用隊(duì)列傳輸數(shù)據(jù)時(shí)有兩種方法:
- 拷貝:把數(shù)據(jù)、把變量的值復(fù)制進(jìn)隊(duì)列里
- 引用:把數(shù)據(jù)、把變量的地址復(fù)制進(jìn)隊(duì)列里
FreeRTOS使用拷貝值的方法,這更簡(jiǎn)單:
局部變量的值可以發(fā)送到隊(duì)列中,后續(xù)即使函數(shù)退出、局部變量被回收,也不會(huì)影響隊(duì)列中的數(shù)據(jù)
無(wú)需分配buffer來(lái)保存數(shù)據(jù),隊(duì)列中有buffer
局部變量可以馬上再次使用
發(fā)送任務(wù)、接收任務(wù)解耦:接收任務(wù)不需要知道這數(shù)據(jù)是誰(shuí)的、也不需要發(fā)送任務(wù)來(lái)釋放數(shù)據(jù)
如果數(shù)據(jù)實(shí)在太大,你還是可以使用隊(duì)列傳輸它的地址
隊(duì)列的空間有FreeRTOS內(nèi)核分配,無(wú)需任務(wù)操心
對(duì)于有內(nèi)存保護(hù)功能的系統(tǒng),如果隊(duì)列使用引用方法,也就是使用地址,必須確保雙方任務(wù)對(duì)這個(gè)地址都有訪問(wèn)權(quán)限。使用拷貝方法時(shí),則無(wú)此限制:內(nèi)核有足夠的權(quán)限,把數(shù)據(jù)復(fù)制進(jìn)隊(duì)列、再把數(shù)據(jù)復(fù)制出隊(duì)列。
5.1.3 隊(duì)列的阻塞訪問(wèn)
只要知道隊(duì)列的句柄,誰(shuí)都可以讀、寫(xiě)該隊(duì)列。任務(wù)、ISR都可讀、寫(xiě)隊(duì)列??梢远鄠€(gè)任務(wù)讀寫(xiě)隊(duì)列。
任務(wù)讀寫(xiě)隊(duì)列時(shí),簡(jiǎn)單地說(shuō):如果讀寫(xiě)不成功,則阻塞;可以指定超時(shí)時(shí)間??谡Z(yǔ)化地說(shuō),就是可以定個(gè)鬧鐘:如果能讀寫(xiě)了就馬上進(jìn)入就緒態(tài),否則就阻塞直到超時(shí)。
某個(gè)任務(wù)讀隊(duì)列時(shí),如果隊(duì)列沒(méi)有數(shù)據(jù),則該任務(wù)可以進(jìn)入阻塞狀態(tài):還可以指定阻塞的時(shí)間。如果隊(duì)列有數(shù)據(jù)了,則該阻塞的任務(wù)會(huì)變?yōu)榫途w態(tài)。如果一直都沒(méi)有數(shù)據(jù),則時(shí)間到之后它也會(huì)進(jìn)入就緒態(tài)。
既然讀取隊(duì)列的任務(wù)個(gè)數(shù)沒(méi)有限制,那么當(dāng)多個(gè)任務(wù)讀取空隊(duì)列時(shí),這些任務(wù)都會(huì)進(jìn)入阻塞狀態(tài):有多個(gè)任務(wù)在等待同一個(gè)隊(duì)列的數(shù)據(jù)。當(dāng)隊(duì)列中有數(shù)據(jù)時(shí),哪個(gè)任務(wù)會(huì)進(jìn)入就緒態(tài)?
- 優(yōu)先級(jí)最高的任務(wù)
- 如果大家的優(yōu)先級(jí)相同,那等待時(shí)間最久的任務(wù)會(huì)進(jìn)入就緒態(tài)
跟讀隊(duì)列類(lèi)似,一個(gè)任務(wù)要寫(xiě)隊(duì)列時(shí),如果隊(duì)列滿了,該任務(wù)也可以進(jìn)入阻塞狀態(tài):還可以指定阻塞的時(shí)間。如果隊(duì)列有空間了,則該阻塞的任務(wù)會(huì)變?yōu)榫途w態(tài)。如果一直都沒(méi)有空間,則時(shí)間到之后它也會(huì)進(jìn)入就緒態(tài)。
既然寫(xiě)隊(duì)列的任務(wù)個(gè)數(shù)沒(méi)有限制,那么當(dāng)多個(gè)任務(wù)寫(xiě)"滿隊(duì)列"時(shí),這些任務(wù)都會(huì)進(jìn)入阻塞狀態(tài):有多個(gè)任務(wù)在等待同一個(gè)隊(duì)列的空間。當(dāng)隊(duì)列中有空間時(shí),哪個(gè)任務(wù)會(huì)進(jìn)入就緒態(tài)?
- 優(yōu)先級(jí)最高的任務(wù)
- 如果大家的優(yōu)先級(jí)相同,那等待時(shí)間最久的任務(wù)會(huì)進(jìn)入就緒態(tài)
5.2 隊(duì)列函數(shù)
使用隊(duì)列的流程:創(chuàng)建隊(duì)列、寫(xiě)隊(duì)列、讀隊(duì)列、刪除隊(duì)列。
5.2.1 創(chuàng)建
隊(duì)列的創(chuàng)建有兩種方法:動(dòng)態(tài)分配內(nèi)存、靜態(tài)分配內(nèi)存,
- 動(dòng)態(tài)分配內(nèi)存:xQueueCreate,隊(duì)列的內(nèi)存在函數(shù)內(nèi)部動(dòng)態(tài)分配
函數(shù)原型如下:
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
參數(shù) | 說(shuō)明 |
---|---|
uxQueueLength | 隊(duì)列長(zhǎng)度,最多能存放多少個(gè)數(shù)據(jù)(item) |
uxItemSize | 每個(gè)數(shù)據(jù)(item)的大?。阂宰止?jié)為單位 |
返回值 |
非0:成功,返回句柄,以后使用句柄來(lái)操作隊(duì)列 NULL:失敗,因?yàn)閮?nèi)存不足 |
- 靜態(tài)分配內(nèi)存:xQueueCreateStatic,隊(duì)列的內(nèi)存要事先分配好
函數(shù)原型如下:
QueueHandle_t xQueueCreateStatic(
UBaseType_t uxQueueLength,
UBaseType_t uxItemSize,
uint8_t *pucQueueStorageBuffer,
StaticQueue_t *pxQueueBuffer
);
參數(shù) | 說(shuō)明 |
---|---|
uxQueueLength | 隊(duì)列長(zhǎng)度,最多能存放多少個(gè)數(shù)據(jù)(item) |
uxItemSize | 每個(gè)數(shù)據(jù)(item)的大?。阂宰止?jié)為單位 |
pucQueueStorageBuffer |
如果uxItemSize非0,pucQueueStorageBuffer必須指向一個(gè)uint8_t數(shù)組, 此數(shù)組大小至少為"uxQueueLength * uxItemSize" |
pxQueueBuffer | 必須執(zhí)行一個(gè)StaticQueue_t結(jié)構(gòu)體,用來(lái)保存隊(duì)列的數(shù)據(jù)結(jié)構(gòu) |
返回值 |
非0:成功,返回句柄,以后使用句柄來(lái)操作隊(duì)列 NULL:失敗,因?yàn)閜xQueueBuffer為NULL |
示例代碼:
// 示例代碼
#define QUEUE_LENGTH 10
#define ITEM_SIZE sizeof( uint32_t )
// xQueueBuffer用來(lái)保存隊(duì)列結(jié)構(gòu)體
StaticQueue_t xQueueBuffer;
// ucQueueStorage 用來(lái)保存隊(duì)列的數(shù)據(jù)
// 大小為:隊(duì)列長(zhǎng)度 * 數(shù)據(jù)大小
uint8_t ucQueueStorage[ QUEUE_LENGTH * ITEM_SIZE ];
void vATask( void *pvParameters )
{
QueueHandle_t xQueue1;
// 創(chuàng)建隊(duì)列: 可以容納QUEUE_LENGTH個(gè)數(shù)據(jù),每個(gè)數(shù)據(jù)大小是ITEM_SIZE
xQueue1 = xQueueCreateStatic( QUEUE_LENGTH,
ITEM_SIZE,
ucQueueStorage,
&xQueueBuffer );
}
5.2.2 復(fù)位
隊(duì)列剛被創(chuàng)建時(shí),里面沒(méi)有數(shù)據(jù);使用過(guò)程中可以調(diào)用xQueueReset()
把隊(duì)列恢復(fù)為初始狀態(tài),此函數(shù)原型為:
/* pxQueue : 復(fù)位哪個(gè)隊(duì)列;
* 返回值: pdPASS(必定成功)
*/
BaseType_t xQueueReset( QueueHandle_t pxQueue);
5.2.3 刪除
刪除隊(duì)列的函數(shù)為vQueueDelete()
,只能刪除使用動(dòng)態(tài)方法創(chuàng)建的隊(duì)列,它會(huì)釋放內(nèi)存。原型如下:
void vQueueDelete( QueueHandle_t xQueue );
5.2.4 寫(xiě)隊(duì)列
可以把數(shù)據(jù)寫(xiě)到隊(duì)列頭部,也可以寫(xiě)到尾部,這些函數(shù)有兩個(gè)版本:在任務(wù)中使用、在ISR中使用。函數(shù)原型如下:
/* 等同于xQueueSendToBack
* 往隊(duì)列尾部寫(xiě)入數(shù)據(jù),如果沒(méi)有空間,阻塞時(shí)間為xTicksToWait
*/
BaseType_t xQueueSend(
QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait
);
/*
* 往隊(duì)列尾部寫(xiě)入數(shù)據(jù),如果沒(méi)有空間,阻塞時(shí)間為xTicksToWait
*/
BaseType_t xQueueSendToBack(
QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait
);
/*
* 往隊(duì)列尾部寫(xiě)入數(shù)據(jù),此函數(shù)可以在中斷函數(shù)中使用,不可阻塞
*/
BaseType_t xQueueSendToBackFromISR(
QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken
);
/*
* 往隊(duì)列頭部寫(xiě)入數(shù)據(jù),如果沒(méi)有空間,阻塞時(shí)間為xTicksToWait
*/
BaseType_t xQueueSendToFront(
QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait
);
/*
* 往隊(duì)列頭部寫(xiě)入數(shù)據(jù),此函數(shù)可以在中斷函數(shù)中使用,不可阻塞
*/
BaseType_t xQueueSendToFrontFromISR(
QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken
);
這些函數(shù)用到的參數(shù)是類(lèi)似的,統(tǒng)一說(shuō)明如下:
參數(shù) | 說(shuō)明 |
---|---|
xQueue | 隊(duì)列句柄,要寫(xiě)哪個(gè)隊(duì)列 |
pvItemToQueue |
數(shù)據(jù)指針,這個(gè)數(shù)據(jù)的值會(huì)被復(fù)制進(jìn)隊(duì)列, 復(fù)制多大的數(shù)據(jù)?在創(chuàng)建隊(duì)列時(shí)已經(jīng)指定了數(shù)據(jù)大小 |
xTicksToWait |
如果隊(duì)列滿則無(wú)法寫(xiě)入新數(shù)據(jù),可以讓任務(wù)進(jìn)入阻塞狀態(tài), xTicksToWait表示阻塞的最大時(shí)間(Tick Count)。 如果被設(shè)為0,無(wú)法寫(xiě)入數(shù)據(jù)時(shí)函數(shù)會(huì)立刻返回; 如果被設(shè)為portMAX_DELAY,則會(huì)一直阻塞直到有空間可寫(xiě) |
返回值 |
pdPASS:數(shù)據(jù)成功寫(xiě)入了隊(duì)列 errQUEUE_FULL:寫(xiě)入失敗,因?yàn)殛?duì)列滿了。 |
5.2.5 讀隊(duì)列
使用xQueueReceive()
函數(shù)讀隊(duì)列,讀到一個(gè)數(shù)據(jù)后,隊(duì)列中該數(shù)據(jù)會(huì)被移除。這個(gè)函數(shù)有兩個(gè)版本:在任務(wù)中使用、在ISR中使用。函數(shù)原型如下:
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );
BaseType_t xQueueReceiveFromISR(
QueueHandle_t xQueue,
void *pvBuffer,
BaseType_t *pxTaskWoken
);
參數(shù)說(shuō)明如下:
參數(shù) | 說(shuō)明 |
---|---|
xQueue | 隊(duì)列句柄,要讀哪個(gè)隊(duì)列 |
pvBuffer |
bufer指針,隊(duì)列的數(shù)據(jù)會(huì)被復(fù)制到這個(gè)buffer 復(fù)制多大的數(shù)據(jù)?在創(chuàng)建隊(duì)列時(shí)已經(jīng)指定了數(shù)據(jù)大小 |
xTicksToWait |
果隊(duì)列空則無(wú)法讀出數(shù)據(jù),可以讓任務(wù)進(jìn)入阻塞狀態(tài), xTicksToWait表示阻塞的最大時(shí)間(Tick Count)。 如果被設(shè)為0,無(wú)法讀出數(shù)據(jù)時(shí)函數(shù)會(huì)立刻返回; 如果被設(shè)為portMAX_DELAY,則會(huì)一直阻塞直到有數(shù)據(jù)可寫(xiě) |
返回值 |
pdPASS:從隊(duì)列讀出數(shù)據(jù)入 errQUEUE_EMPTY:讀取失敗,因?yàn)殛?duì)列空了。 |
5.2.6 查詢
可以查詢隊(duì)列中有多少個(gè)數(shù)據(jù)、有多少空余空間。函數(shù)原型如下:
/*
* 返回隊(duì)列中可用數(shù)據(jù)的個(gè)數(shù)
*/
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );
/*
* 返回隊(duì)列中可用空間的個(gè)數(shù)
*/
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );
5.2.7 覆蓋/偷看
當(dāng)隊(duì)列長(zhǎng)度為1時(shí),可以使用xQueueOverwrite()
或xQueueOverwriteFromISR()
來(lái)覆蓋數(shù)據(jù)。
注意,隊(duì)列長(zhǎng)度必須為1。當(dāng)隊(duì)列滿時(shí),這些函數(shù)會(huì)覆蓋里面的數(shù)據(jù),這也以為著這些函數(shù)不會(huì)被阻塞。
函數(shù)原型如下:
/* 覆蓋隊(duì)列
* xQueue: 寫(xiě)哪個(gè)隊(duì)列
* pvItemToQueue: 數(shù)據(jù)地址
* 返回值: pdTRUE表示成功, pdFALSE表示失敗
*/
BaseType_t xQueueOverwrite(
QueueHandle_t xQueue,
const void * pvItemToQueue
);
BaseType_t xQueueOverwriteFromISR(
QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken
);
如果想讓隊(duì)列中的數(shù)據(jù)供多方讀取,也就是說(shuō)讀取時(shí)不要移除數(shù)據(jù),要留給后來(lái)人。那么可以使用"窺視",也就是xQueuePeek()
或xQueuePeekFromISR()
。這些函數(shù)會(huì)從隊(duì)列中復(fù)制出數(shù)據(jù),但是不移除數(shù)據(jù)。這也意味著,如果隊(duì)列中沒(méi)有數(shù)據(jù),那么"偷看"時(shí)會(huì)導(dǎo)致阻塞;一旦隊(duì)列中有數(shù)據(jù),以后每次"偷看"都會(huì)成功。
函數(shù)原型如下:
/* 偷看隊(duì)列
* xQueue: 偷看哪個(gè)隊(duì)列
* pvItemToQueue: 數(shù)據(jù)地址, 用來(lái)保存復(fù)制出來(lái)的數(shù)據(jù)
* xTicksToWait: 沒(méi)有數(shù)據(jù)的話阻塞一會(huì)
* 返回值: pdTRUE表示成功, pdFALSE表示失敗
*/
BaseType_t xQueuePeek(
QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait
);
BaseType_t xQueuePeekFromISR(
QueueHandle_t xQueue,
void *pvBuffer,
);
5.3 示例8: 隊(duì)列的基本使用
本節(jié)代碼為:FreeRTOS_08_queue
。
本程序會(huì)創(chuàng)建一個(gè)隊(duì)列,然后創(chuàng)建2個(gè)發(fā)送任務(wù)、1個(gè)接收任務(wù):
- 發(fā)送任務(wù)優(yōu)先級(jí)為1,分別往隊(duì)列中寫(xiě)入100、200
- 接收任務(wù)優(yōu)先級(jí)為2,讀隊(duì)列、打印數(shù)值
main函數(shù)中創(chuàng)建的隊(duì)列、創(chuàng)建了發(fā)送任務(wù)、接收任務(wù),代碼如下:
/* 隊(duì)列句柄, 創(chuàng)建隊(duì)列時(shí)會(huì)設(shè)置這個(gè)變量 */
QueueHandle_t xQueue;
int main( void )
{
prvSetupHardware();
/* 創(chuàng)建隊(duì)列: 長(zhǎng)度為5,數(shù)據(jù)大小為4字節(jié)(存放一個(gè)整數(shù)) */
xQueue = xQueueCreate( 5, sizeof( int32_t ) );
if( xQueue != NULL )
{
/* 創(chuàng)建2個(gè)任務(wù)用于寫(xiě)隊(duì)列, 傳入的參數(shù)分別是100、200
* 任務(wù)函數(shù)會(huì)連續(xù)執(zhí)行,向隊(duì)列發(fā)送數(shù)值100、200
* 優(yōu)先級(jí)為1
*/
xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );
xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );
/* 創(chuàng)建1個(gè)任務(wù)用于讀隊(duì)列
* 優(yōu)先級(jí)為2, 高于上面的兩個(gè)任務(wù)
* 這意味著隊(duì)列一有數(shù)據(jù)就會(huì)被讀走
*/
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
/* 啟動(dòng)調(diào)度器 */
vTaskStartScheduler();
}
else
{
/* 無(wú)法創(chuàng)建隊(duì)列 */
}
/* 如果程序運(yùn)行到了這里就表示出錯(cuò)了, 一般是內(nèi)存不足 */
return 0;
}
發(fā)送任務(wù)的函數(shù)中,不斷往隊(duì)列中寫(xiě)入數(shù)值,代碼如下:
static void vSenderTask( void *pvParameters )
{
int32_t lValueToSend;
BaseType_t xStatus;
/* 我們會(huì)使用這個(gè)函數(shù)創(chuàng)建2個(gè)任務(wù)
* 這些任務(wù)的pvParameters不一樣
*/
lValueToSend = ( int32_t ) pvParameters;
/* 無(wú)限循環(huán) */
for( ;; )
{
/* 寫(xiě)隊(duì)列
* xQueue: 寫(xiě)哪個(gè)隊(duì)列
* &lValueToSend: 寫(xiě)什么數(shù)據(jù)? 傳入數(shù)據(jù)的地址, 會(huì)從這個(gè)地址把數(shù)據(jù)復(fù)制進(jìn)隊(duì)列
* 0: 不阻塞, 如果隊(duì)列滿的話, 寫(xiě)入失敗, 立刻返回
*/
xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );
if( xStatus != pdPASS )
{
printf( "Could not send to the queue.\r\n" );
}
}
}
接收任務(wù)的函數(shù)中,讀取隊(duì)列、判斷返回值、打印,代碼如下:
static void vReceiverTask( void *pvParameters )
{
/* 讀取隊(duì)列時(shí), 用這個(gè)變量來(lái)存放數(shù)據(jù) */
int32_t lReceivedValue;
BaseType_t xStatus;
const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
/* 無(wú)限循環(huán) */
for( ;; )
{
/* 讀隊(duì)列
* xQueue: 讀哪個(gè)隊(duì)列
* &lReceivedValue: 讀到的數(shù)據(jù)復(fù)制到這個(gè)地址
* xTicksToWait: 如果隊(duì)列為空, 阻塞一會(huì)
*/
xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );
if( xStatus == pdPASS )
{
/* 讀到了數(shù)據(jù) */
printf( "Received = %d\r\n", lReceivedValue );
}
else
{
/* 沒(méi)讀到數(shù)據(jù) */
printf( "Could not receive from the queue.\r\n" );
}
}
}
程序運(yùn)行結(jié)果如下:

任務(wù)調(diào)度情況如下圖所示:

5.4 示例9: 分辨數(shù)據(jù)源
本節(jié)代碼為:FreeRTOS_09_queue_datasource
。
當(dāng)有多個(gè)發(fā)送任務(wù),通過(guò)同一個(gè)隊(duì)列發(fā)出數(shù)據(jù),接收任務(wù)如何分辨數(shù)據(jù)來(lái)源?數(shù)據(jù)本身帶有"來(lái)源"信息,比如寫(xiě)入隊(duì)列的數(shù)據(jù)是一個(gè)結(jié)構(gòu)體,結(jié)構(gòu)體中的lDataSouceID用來(lái)表示數(shù)據(jù)來(lái)源:
typedef struct {
ID_t eDataID;
int32_t lDataValue;
}Data_t;
不同的發(fā)送任務(wù),先構(gòu)造好結(jié)構(gòu)體,填入自己的eDataID
,再寫(xiě)隊(duì)列;接收任務(wù)讀出數(shù)據(jù)后,根據(jù)eDataID
就可以知道數(shù)據(jù)來(lái)源了,如下圖所示:
- CAN任務(wù)發(fā)送的數(shù)據(jù):eDataID=eMotorSpeed
-
HMI任務(wù)發(fā)送的數(shù)據(jù):eDataID=eSpeedSetPoint

FreeRTOS_09_queue_datasource
程序會(huì)創(chuàng)建一個(gè)隊(duì)列,然后創(chuàng)建2個(gè)發(fā)送任務(wù)、1個(gè)接收任務(wù):
- 創(chuàng)建的隊(duì)列,用來(lái)發(fā)送結(jié)構(gòu)體:數(shù)據(jù)大小是結(jié)構(gòu)體的大小
- 發(fā)送任務(wù)優(yōu)先級(jí)為2,分別往隊(duì)列中寫(xiě)入自己的結(jié)構(gòu)體,結(jié)構(gòu)體中會(huì)標(biāo)明數(shù)據(jù)來(lái)源
- 接收任務(wù)優(yōu)先級(jí)為1,讀隊(duì)列、根據(jù)數(shù)據(jù)來(lái)源打印信息
main函數(shù)中創(chuàng)建了隊(duì)列、創(chuàng)建了發(fā)送任務(wù)、接收任務(wù),代碼如下:
/* 定義2種數(shù)據(jù)來(lái)源(ID) */
typedef enum
{
eMotorSpeed,
eSpeedSetPoint
} ID_t;
/* 定義在隊(duì)列中傳輸?shù)臄?shù)據(jù)的格式 */
typedef struct {
ID_t eDataID;
int32_t lDataValue;
}Data_t;
/* 定義2個(gè)結(jié)構(gòu)體 */
static const Data_t xStructsToSend[ 2 ] =
{
{ eMotorSpeed, 10 }, /* CAN任務(wù)發(fā)送的數(shù)據(jù) */
{ eSpeedSetPoint, 5 } /* HMI任務(wù)發(fā)送的數(shù)據(jù) */
};
/* vSenderTask被用來(lái)創(chuàng)建2個(gè)任務(wù),用于寫(xiě)隊(duì)列
* vReceiverTask被用來(lái)創(chuàng)建1個(gè)任務(wù),用于讀隊(duì)列
*/
static void vSenderTask( void *pvParameters );
static void vReceiverTask( void *pvParameters );
/*-----------------------------------------------------------*/
/* 隊(duì)列句柄, 創(chuàng)建隊(duì)列時(shí)會(huì)設(shè)置這個(gè)變量 */
QueueHandle_t xQueue;
int main( void )
{
prvSetupHardware();
/* 創(chuàng)建隊(duì)列: 長(zhǎng)度為5,數(shù)據(jù)大小為4字節(jié)(存放一個(gè)整數(shù)) */
xQueue = xQueueCreate( 5, sizeof( Data_t ) );
if( xQueue != NULL )
{
/* 創(chuàng)建2個(gè)任務(wù)用于寫(xiě)隊(duì)列, 傳入的參數(shù)是不同的結(jié)構(gòu)體地址
* 任務(wù)函數(shù)會(huì)連續(xù)執(zhí)行,向隊(duì)列發(fā)送結(jié)構(gòu)體
* 優(yōu)先級(jí)為2
*/
xTaskCreate(vSenderTask, "CAN Task", 1000, (void *) &(xStructsToSend[0]), 2, NULL);
xTaskCreate(vSenderTask, "HMI Task", 1000, (void *) &( xStructsToSend[1]), 2, NULL);
/* 創(chuàng)建1個(gè)任務(wù)用于讀隊(duì)列
* 優(yōu)先級(jí)為1, 低于上面的兩個(gè)任務(wù)
* 這意味著發(fā)送任務(wù)優(yōu)先寫(xiě)隊(duì)列,隊(duì)列常常是滿的狀態(tài)
*/
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
/* 啟動(dòng)調(diào)度器 */
vTaskStartScheduler();
}
else
{
/* 無(wú)法創(chuàng)建隊(duì)列 */
}
/* 如果程序運(yùn)行到了這里就表示出錯(cuò)了, 一般是內(nèi)存不足 */
return 0;
}
發(fā)送任務(wù)的函數(shù)中,不斷往隊(duì)列中寫(xiě)入數(shù)值,代碼如下:
static void vSenderTask( void *pvParameters )
{
BaseType_t xStatus;
const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
/* 無(wú)限循環(huán) */
for( ;; )
{
/* 寫(xiě)隊(duì)列
* xQueue: 寫(xiě)哪個(gè)隊(duì)列
* pvParameters: 寫(xiě)什么數(shù)據(jù)? 傳入數(shù)據(jù)的地址, 會(huì)從這個(gè)地址把數(shù)據(jù)復(fù)制進(jìn)隊(duì)列
* xTicksToWait: 如果隊(duì)列滿的話, 阻塞一會(huì)
*/
xStatus = xQueueSendToBack( xQueue, pvParameters, xTicksToWait );
if( xStatus != pdPASS )
{
printf( "Could not send to the queue.\r\n" );
}
}
}
接收任務(wù)的函數(shù)中,讀取隊(duì)列、判斷返回值、打印,代碼如下:
static void vReceiverTask( void *pvParameters )
{
/* 讀取隊(duì)列時(shí), 用這個(gè)變量來(lái)存放數(shù)據(jù) */
Data_t xReceivedStructure;
BaseType_t xStatus;
/* 無(wú)限循環(huán) */
for( ;; )
{
/* 讀隊(duì)列
* xQueue: 讀哪個(gè)隊(duì)列
* &xReceivedStructure: 讀到的數(shù)據(jù)復(fù)制到這個(gè)地址
* 0: 沒(méi)有數(shù)據(jù)就即刻返回,不阻塞
*/
xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 );
if( xStatus == pdPASS )
{
/* 讀到了數(shù)據(jù) */
if( xReceivedStructure.eDataID == eMotorSpeed )
{
printf( "From CAN, MotorSpeed = %d\r\n", xReceivedStructure.lDataValue );
}
else if( xReceivedStructure.eDataID == eSpeedSetPoint )
{
printf( "From HMI, SpeedSetPoint = %d\r\n", xReceivedStructure.lDataValue );
}
}
else
{
/* 沒(méi)讀到數(shù)據(jù) */
printf( "Could not receive from the queue.\r\n" );
}
}
}
運(yùn)行結(jié)果如下:

任務(wù)調(diào)度情況如下圖所示:
- t1:HMI是最后創(chuàng)建的最高優(yōu)先級(jí)任務(wù),它先執(zhí)行,一下子向隊(duì)列寫(xiě)入5個(gè)數(shù)據(jù),把隊(duì)列都寫(xiě)滿了
- t2:隊(duì)列已經(jīng)滿了,HMI任務(wù)再發(fā)起第6次寫(xiě)操作時(shí),進(jìn)入阻塞狀態(tài)。這時(shí)CAN任務(wù)是最高優(yōu)先級(jí)的就緒態(tài)任務(wù),它開(kāi)始執(zhí)行
- t3:CAN任務(wù)發(fā)現(xiàn)隊(duì)列已經(jīng)滿了,進(jìn)入阻塞狀態(tài);接收任務(wù)變?yōu)樽罡邇?yōu)先級(jí)的就緒態(tài)任務(wù),它開(kāi)始運(yùn)行
- t4:現(xiàn)在,HMI任務(wù)、CAN任務(wù)的優(yōu)先級(jí)都比接收任務(wù)高,它們都在等待隊(duì)列有空閑的空間;一旦接收任務(wù)讀出1個(gè)數(shù)據(jù),會(huì)馬上被搶占。被誰(shuí)搶占?誰(shuí)等待最久?HMI任務(wù)!所以在t4時(shí)刻,切換到HMI任務(wù)。
- t5:HMI任務(wù)向隊(duì)列寫(xiě)入第6個(gè)數(shù)據(jù),然后再次阻塞,這是CAN任務(wù)已經(jīng)阻塞很久了。接收任務(wù)變?yōu)樽罡邇?yōu)先級(jí)的就緒態(tài)任務(wù),開(kāi)始執(zhí)行。
- t6:現(xiàn)在,HMI任務(wù)、CAN任務(wù)的優(yōu)先級(jí)都比接收任務(wù)高,它們都在等待隊(duì)列有空閑的空間;一旦接收任務(wù)讀出1個(gè)數(shù)據(jù),會(huì)馬上被搶占。被誰(shuí)搶占?誰(shuí)等待最久?CAN任務(wù)!所以在t6時(shí)刻,切換到CAN任務(wù)。
- t7:CAN任務(wù)向隊(duì)列寫(xiě)入數(shù)據(jù),因?yàn)閮H僅有一個(gè)空間供寫(xiě)入,所以它馬上再次進(jìn)入阻塞狀態(tài)。這時(shí)HMI任務(wù)、CAN任務(wù)都在等待空閑空間,只有接收任務(wù)可以繼續(xù)執(zhí)行。

5.5 示例10: 傳輸大塊數(shù)據(jù)
本節(jié)代碼為:FreeRTOS_10_queue_bigtransfer
。
FreeRTOS的隊(duì)列使用拷貝傳輸,也就是要傳輸uint32_t時(shí),把4字節(jié)的數(shù)據(jù)拷貝進(jìn)隊(duì)列;要傳輸一個(gè)8字節(jié)的結(jié)構(gòu)體時(shí),把8字節(jié)的數(shù)據(jù)拷貝進(jìn)隊(duì)列。
如果要傳輸1000字節(jié)的結(jié)構(gòu)體呢?寫(xiě)隊(duì)列時(shí)拷貝1000字節(jié),讀隊(duì)列時(shí)再拷貝1000字節(jié)?不建議這么做,影響效率!
這時(shí)候,我們要傳輸?shù)氖沁@個(gè)巨大結(jié)構(gòu)體的地址:把它的地址寫(xiě)入隊(duì)列,對(duì)方從隊(duì)列得到這個(gè)地址,使用地址去訪問(wèn)那1000字節(jié)的數(shù)據(jù)。
使用地址來(lái)間接傳輸數(shù)據(jù)時(shí),這些數(shù)據(jù)放在RAM里,對(duì)于這塊RAM,要保證這幾點(diǎn):
-
RAM的所有者、操作者,必須清晰明了
這塊內(nèi)存,就被稱為"共享內(nèi)存"。要確保不能同時(shí)修改RAM。比如,在寫(xiě)隊(duì)列之前只有由發(fā)送者修改這塊RAM,在讀隊(duì)列之后只能由接收者訪問(wèn)這塊RAM。 -
RAM要保持可用
這塊RAM應(yīng)該是全局變量,或者是動(dòng)態(tài)分配的內(nèi)存。對(duì)于動(dòng)然分配的內(nèi)存,要確保它不能提前釋放:要等到接收者用完后再釋放。另外,不能是局部變量。
FreeRTOS_10_queue_bigtransfer
程序會(huì)創(chuàng)建一個(gè)隊(duì)列,然后創(chuàng)建1個(gè)發(fā)送任務(wù)、1個(gè)接收任務(wù):
- 創(chuàng)建的隊(duì)列:長(zhǎng)度為1,用來(lái)傳輸"char *"指針
- 發(fā)送任務(wù)優(yōu)先級(jí)為1,在字符數(shù)組中寫(xiě)好數(shù)據(jù)后,把它的地址寫(xiě)入隊(duì)列
- 接收任務(wù)優(yōu)先級(jí)為2,讀隊(duì)列得到"char *"值,把它打印出來(lái)
這個(gè)程序故意設(shè)置接收任務(wù)的優(yōu)先級(jí)更高,在它訪問(wèn)數(shù)組的過(guò)程中,接收任務(wù)無(wú)法執(zhí)行、無(wú)法寫(xiě)這個(gè)數(shù)組。
main函數(shù)中創(chuàng)建了隊(duì)列、創(chuàng)建了發(fā)送任務(wù)、接收任務(wù),代碼如下:
/* 定義一個(gè)字符數(shù)組 */
static char pcBuffer[100];
/* vSenderTask被用來(lái)創(chuàng)建2個(gè)任務(wù),用于寫(xiě)隊(duì)列
* vReceiverTask被用來(lái)創(chuàng)建1個(gè)任務(wù),用于讀隊(duì)列
*/
static void vSenderTask( void *pvParameters );
static void vReceiverTask( void *pvParameters );
/*-----------------------------------------------------------*/
/* 隊(duì)列句柄, 創(chuàng)建隊(duì)列時(shí)會(huì)設(shè)置這個(gè)變量 */
QueueHandle_t xQueue;
int main( void )
{
prvSetupHardware();
/* 創(chuàng)建隊(duì)列: 長(zhǎng)度為1,數(shù)據(jù)大小為4字節(jié)(存放一個(gè)char指針) */
xQueue = xQueueCreate( 1, sizeof(char *) );
if( xQueue != NULL )
{
/* 創(chuàng)建1個(gè)任務(wù)用于寫(xiě)隊(duì)列
* 任務(wù)函數(shù)會(huì)連續(xù)執(zhí)行,構(gòu)造buffer數(shù)據(jù),把buffer地址寫(xiě)入隊(duì)列
* 優(yōu)先級(jí)為1
*/
xTaskCreate( vSenderTask, "Sender", 1000, NULL, 1, NULL );
/* 創(chuàng)建1個(gè)任務(wù)用于讀隊(duì)列
* 優(yōu)先級(jí)為2, 高于上面的兩個(gè)任務(wù)
* 這意味著讀隊(duì)列得到buffer地址后,本任務(wù)使用buffer時(shí)不會(huì)被打斷
*/
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
/* 啟動(dòng)調(diào)度器 */
vTaskStartScheduler();
}
else
{
/* 無(wú)法創(chuàng)建隊(duì)列 */
}
/* 如果程序運(yùn)行到了這里就表示出錯(cuò)了, 一般是內(nèi)存不足 */
return 0;
}
發(fā)送任務(wù)的函數(shù)中,現(xiàn)在全局大數(shù)組pcBuffer中構(gòu)造數(shù)據(jù),然后把它的地址寫(xiě)入隊(duì)列,代碼如下:
static void vSenderTask( void *pvParameters )
{
BaseType_t xStatus;
static int cnt = 0;
char *buffer;
/* 無(wú)限循環(huán) */
for( ;; )
{
sprintf(pcBuffer, "www.100ask.net Msg %d\r\n", cnt++);
buffer = pcBuffer; // buffer變量等于數(shù)組的地址, 下面要把這個(gè)地址寫(xiě)入隊(duì)列
/* 寫(xiě)隊(duì)列
* xQueue: 寫(xiě)哪個(gè)隊(duì)列
* pvParameters: 寫(xiě)什么數(shù)據(jù)? 傳入數(shù)據(jù)的地址, 會(huì)從這個(gè)地址把數(shù)據(jù)復(fù)制進(jìn)隊(duì)列
* 0: 如果隊(duì)列滿的話, 即刻返回
*/
xStatus = xQueueSendToBack( xQueue, &buffer, 0 ); /* 只需要寫(xiě)入4字節(jié), 無(wú)需寫(xiě)入整個(gè)buffer */
if( xStatus != pdPASS )
{
printf( "Could not send to the queue.\r\n" );
}
}
}
接收任務(wù)的函數(shù)中,讀取隊(duì)列、得到buffer的地址、打印,代碼如下:
static void vReceiverTask( void *pvParameters )
{
/* 讀取隊(duì)列時(shí), 用這個(gè)變量來(lái)存放數(shù)據(jù) */
char *buffer;
const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
BaseType_t xStatus;
/* 無(wú)限循環(huán) */
for( ;; )
{
/* 讀隊(duì)列
* xQueue: 讀哪個(gè)隊(duì)列
* &xReceivedStructure: 讀到的數(shù)據(jù)復(fù)制到這個(gè)地址
* xTicksToWait: 沒(méi)有數(shù)據(jù)就阻塞一會(huì)
*/
xStatus = xQueueReceive( xQueue, &buffer, xTicksToWait); /* 得到buffer地址,只是4字節(jié) */
if( xStatus == pdPASS )
{
/* 讀到了數(shù)據(jù) */
printf("Get: %s", buffer);
}
else
{
/* 沒(méi)讀到數(shù)據(jù) */
printf( "Could not receive from the queue.\r\n" );
}
}
}
運(yùn)行結(jié)果如下圖所示:

5.6 示例11: 郵箱(Mailbox)
本節(jié)代碼為:FreeRTOS_11_queue_mailbox
。
FreeRTOS的郵箱概念跟別的RTOS不一樣,這里的郵箱稱為"櫥窗"也許更恰當(dāng):
- 它是一個(gè)隊(duì)列,隊(duì)列長(zhǎng)度只有1
-
寫(xiě)郵箱:新數(shù)據(jù)覆蓋舊數(shù)據(jù),在任務(wù)中使用
xQueueOverwrite()
,在中斷中使用xQueueOverwriteFromISR()
。
既然是覆蓋,那么無(wú)論郵箱中是否有數(shù)據(jù),這些函數(shù)總能成功寫(xiě)入數(shù)據(jù)。 -
讀郵箱:讀數(shù)據(jù)時(shí),數(shù)據(jù)不會(huì)被移除;在任務(wù)中使用
xQueuePeek()
,在中斷中使用xQueuePeekFromISR()
。
這意味著,第一次調(diào)用時(shí)會(huì)因?yàn)闊o(wú)數(shù)據(jù)而阻塞,一旦曾經(jīng)寫(xiě)入數(shù)據(jù),以后讀郵箱時(shí)總能成功。
main函數(shù)中創(chuàng)建了隊(duì)列(隊(duì)列長(zhǎng)度為1)、創(chuàng)建了發(fā)送任務(wù)、接收任務(wù):
- 發(fā)送任務(wù)的優(yōu)先級(jí)為2,它先執(zhí)行
- 接收任務(wù)的優(yōu)先級(jí)為1
代碼如下:
/* 隊(duì)列句柄, 創(chuàng)建隊(duì)列時(shí)會(huì)設(shè)置這個(gè)變量 */
QueueHandle_t xQueue;
int main( void )
{
prvSetupHardware();
/* 創(chuàng)建隊(duì)列: 長(zhǎng)度為1,數(shù)據(jù)大小為4字節(jié)(存放一個(gè)char指針) */
xQueue = xQueueCreate( 1, sizeof(uint32_t) );
if( xQueue != NULL )
{
/* 創(chuàng)建1個(gè)任務(wù)用于寫(xiě)隊(duì)列
* 任務(wù)函數(shù)會(huì)連續(xù)執(zhí)行,構(gòu)造buffer數(shù)據(jù),把buffer地址寫(xiě)入隊(duì)列
* 優(yōu)先級(jí)為2
*/
xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
/* 創(chuàng)建1個(gè)任務(wù)用于讀隊(duì)列
* 優(yōu)先級(jí)為1
*/
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
/* 啟動(dòng)調(diào)度器 */
vTaskStartScheduler();
}
else
{
/* 無(wú)法創(chuàng)建隊(duì)列 */
}
/* 如果程序運(yùn)行到了這里就表示出錯(cuò)了, 一般是內(nèi)存不足 */
return 0;
}
發(fā)送任務(wù)、接收任務(wù)的代碼和執(zhí)行流程如下:
- A:發(fā)送任務(wù)先執(zhí)行,馬上阻塞
- BC:接收任務(wù)執(zhí)行,這是郵箱無(wú)數(shù)據(jù),打印"Could not …"。在發(fā)送任務(wù)阻塞過(guò)程中,接收任務(wù)多次執(zhí)行、多次打印。
- D:發(fā)送任務(wù)從阻塞狀態(tài)退出,立刻執(zhí)行、寫(xiě)隊(duì)列
- E:發(fā)送任務(wù)再次阻塞
- FG、HI、……:接收任務(wù)不斷"偷看"郵箱,得到同一個(gè)數(shù)據(jù),打印出多個(gè)"Get: 0"
- J:發(fā)送任務(wù)從阻塞狀態(tài)退出,立刻執(zhí)行、覆蓋隊(duì)列,寫(xiě)入1
- K:發(fā)送任務(wù)再次阻塞
- LM、……:接收任務(wù)不斷"偷看"郵箱,得到同一個(gè)數(shù)據(jù),打印出多個(gè)"Get: 1"

運(yùn)行結(jié)果如下圖所示:

-
嵌入式
+關(guān)注
關(guān)注
5150文章
19659瀏覽量
317369 -
Linux
+關(guān)注
關(guān)注
87文章
11509瀏覽量
213715 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4379瀏覽量
64832 -
RTOS
+關(guān)注
關(guān)注
24文章
851瀏覽量
121148 -
FreeRTOS
+關(guān)注
關(guān)注
12文章
493瀏覽量
64300
發(fā)布評(píng)論請(qǐng)先 登錄
韋東山freeRTOS系列教程之信號(hào)量(6)

FreeRTOS發(fā)送消息隊(duì)列失敗的解決辦法?
Queue隊(duì)列的作用是什么
消息隊(duì)列Queue相關(guān)資料推薦
韋東山freeRTOS系列教程:入門(mén)文檔教程+進(jìn)階視頻教程

韋東山freeRTOS教程之FreeRTOS概述與體驗(yàn)(1)

韋東山freeRTOS系列教程之內(nèi)存管理(2)

FreeRTOS消息隊(duì)列 & ESP32使用

FreeRTOS學(xué)習(xí)(五)消息隊(duì)列和二值信號(hào)量 xQueue / xSemaphore

FreeRTOS 隊(duì)列 信號(hào)量 互斥量

ThreadX(九)------消息隊(duì)列Queue

FreeRTOS系列第18篇---FreeRTOS隊(duì)列API函數(shù)

隊(duì)列Queue的常用方法有哪些

STM32G0開(kāi)發(fā)筆記:使用FreeRTOS系統(tǒng)的隊(duì)列Queue
FreeRTOS消息隊(duì)列結(jié)構(gòu)體

評(píng)論