一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

韋東山freeRTOS系列教程之隊列(queue)(5)

嵌入式Linux那些事 ? 2021-12-13 14:33 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

文章目錄

  • 系列教程總目錄
  • 概述
  • 5.1 隊列的特性
    • 5.1.1 常規(guī)操作
    • 5.1.2 傳輸數(shù)據(jù)的兩種方法
    • 5.1.3 隊列的阻塞訪問
  • 5.2 隊列函數(shù)
    • 5.2.1 創(chuàng)建
    • 5.2.2 復位
    • 5.2.3 刪除
    • 5.2.4 寫隊列
    • 5.2.5 讀隊列
    • 5.2.6 查詢
    • 5.2.7 覆蓋/偷看
  • 5.3 示例8: 隊列的基本使用
  • 5.4 示例9: 分辨數(shù)據(jù)源
  • 5.5 示例10: 傳輸大塊數(shù)據(jù)
  • 5.6 示例11: 郵箱(Mailbox)

需要獲取更好閱讀體驗的同學,請訪問我專門設立的站點查看,地址:http://rtos.100ask.net/

系列教程總目錄

本教程連載中,篇章會比較多,為方便同學們閱讀,點擊這里可以查看文章的 目錄列表,目錄列表頁面地址:https://blog.csdn.net/thisway_diy/article/details/121399484

概述

隊列(queue)可以用于"任務到任務"、“任務到中斷”、"中斷到任務"直接傳輸信息。

本章涉及如下內(nèi)容:

  • 怎么創(chuàng)建、清除、刪除隊列
  • 隊列中消息如何保存
  • 怎么向隊列發(fā)送數(shù)據(jù)、怎么從隊列讀取數(shù)據(jù)、怎么覆蓋隊列的數(shù)據(jù)
  • 在隊列上阻塞是什么意思
  • 怎么在多個隊列上阻塞
  • 讀寫隊列時如何影響任務的優(yōu)先級

5.1 隊列的特性

5.1.1 常規(guī)操作

隊列的簡化操如入下圖所示,從此圖可知:

  • 隊列可以包含若干個數(shù)據(jù):隊列中有若干項,這被稱為"長度"(length)
  • 每個數(shù)據(jù)大小固定
  • 創(chuàng)建隊列時就要指定長度、數(shù)據(jù)大小
  • 數(shù)據(jù)的操作采用先進先出的方法(FIFO,F(xiàn)irst In First Out):寫數(shù)據(jù)時放到尾部,讀數(shù)據(jù)時從頭部讀
  • 也可以強制寫隊列頭部:覆蓋頭部數(shù)據(jù)
在這里插入圖片描述

更詳細的操作入下圖所示:

在這里插入圖片描述

5.1.2 傳輸數(shù)據(jù)的兩種方法

使用隊列傳輸數(shù)據(jù)時有兩種方法:

  • 拷貝:把數(shù)據(jù)、把變量的值復制進隊列里
  • 引用:把數(shù)據(jù)、把變量的地址復制進隊列里

FreeRTOS使用拷貝值的方法,這更簡單:

局部變量的值可以發(fā)送到隊列中,后續(xù)即使函數(shù)退出、局部變量被回收,也不會影響隊列中的數(shù)據(jù)

無需分配buffer來保存數(shù)據(jù),隊列中有buffer

局部變量可以馬上再次使用

發(fā)送任務、接收任務解耦:接收任務不需要知道這數(shù)據(jù)是誰的、也不需要發(fā)送任務來釋放數(shù)據(jù)

如果數(shù)據(jù)實在太大,你還是可以使用隊列傳輸它的地址

隊列的空間有FreeRTOS內(nèi)核分配,無需任務操心

對于有內(nèi)存保護功能的系統(tǒng),如果隊列使用引用方法,也就是使用地址,必須確保雙方任務對這個地址都有訪問權(quán)限。使用拷貝方法時,則無此限制:內(nèi)核有足夠的權(quán)限,把數(shù)據(jù)復制進隊列、再把數(shù)據(jù)復制出隊列。

5.1.3 隊列的阻塞訪問

只要知道隊列的句柄,誰都可以讀、寫該隊列。任務、ISR都可讀、寫隊列??梢远鄠€任務讀寫隊列。

任務讀寫隊列時,簡單地說:如果讀寫不成功,則阻塞;可以指定超時時間??谡Z化地說,就是可以定個鬧鐘:如果能讀寫了就馬上進入就緒態(tài),否則就阻塞直到超時。

某個任務讀隊列時,如果隊列沒有數(shù)據(jù),則該任務可以進入阻塞狀態(tài):還可以指定阻塞的時間。如果隊列有數(shù)據(jù)了,則該阻塞的任務會變?yōu)榫途w態(tài)。如果一直都沒有數(shù)據(jù),則時間到之后它也會進入就緒態(tài)。

既然讀取隊列的任務個數(shù)沒有限制,那么當多個任務讀取空隊列時,這些任務都會進入阻塞狀態(tài):有多個任務在等待同一個隊列的數(shù)據(jù)。當隊列中有數(shù)據(jù)時,哪個任務會進入就緒態(tài)?

  • 優(yōu)先級最高的任務
  • 如果大家的優(yōu)先級相同,那等待時間最久的任務會進入就緒態(tài)

跟讀隊列類似,一個任務要寫隊列時,如果隊列滿了,該任務也可以進入阻塞狀態(tài):還可以指定阻塞的時間。如果隊列有空間了,則該阻塞的任務會變?yōu)榫途w態(tài)。如果一直都沒有空間,則時間到之后它也會進入就緒態(tài)。

既然寫隊列的任務個數(shù)沒有限制,那么當多個任務寫"滿隊列"時,這些任務都會進入阻塞狀態(tài):有多個任務在等待同一個隊列的空間。當隊列中有空間時,哪個任務會進入就緒態(tài)?

  • 優(yōu)先級最高的任務
  • 如果大家的優(yōu)先級相同,那等待時間最久的任務會進入就緒態(tài)

5.2 隊列函數(shù)

使用隊列的流程:創(chuàng)建隊列、寫隊列、讀隊列、刪除隊列。

5.2.1 創(chuàng)建

隊列的創(chuàng)建有兩種方法:動態(tài)分配內(nèi)存、靜態(tài)分配內(nèi)存,

  • 動態(tài)分配內(nèi)存:xQueueCreate,隊列的內(nèi)存在函數(shù)內(nèi)部動態(tài)分配

函數(shù)原型如下:

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
參數(shù) 說明
uxQueueLength 隊列長度,最多能存放多少個數(shù)據(jù)(item)
uxItemSize 每個數(shù)據(jù)(item)的大小:以字節(jié)為單位
返回值 非0:成功,返回句柄,以后使用句柄來操作隊列
NULL:失敗,因為內(nèi)存不足
  • 靜態(tài)分配內(nèi)存:xQueueCreateStatic,隊列的內(nèi)存要事先分配好

函數(shù)原型如下:

QueueHandle_t xQueueCreateStatic(
                           UBaseType_t uxQueueLength,
                           UBaseType_t uxItemSize,
                           uint8_t *pucQueueStorageBuffer,
                           StaticQueue_t *pxQueueBuffer
                       );
參數(shù) 說明
uxQueueLength 隊列長度,最多能存放多少個數(shù)據(jù)(item)
uxItemSize 每個數(shù)據(jù)(item)的大?。阂宰止?jié)為單位
pucQueueStorageBuffer 如果uxItemSize非0,pucQueueStorageBuffer必須指向一個uint8_t數(shù)組,
此數(shù)組大小至少為"uxQueueLength * uxItemSize"
pxQueueBuffer 必須執(zhí)行一個StaticQueue_t結(jié)構(gòu)體,用來保存隊列的數(shù)據(jù)結(jié)構(gòu)
返回值 非0:成功,返回句柄,以后使用句柄來操作隊列
NULL:失敗,因為pxQueueBuffer為NULL

示例代碼:

// 示例代碼
 #define QUEUE_LENGTH 10
 #define ITEM_SIZE sizeof( uint32_t )
 
 // xQueueBuffer用來保存隊列結(jié)構(gòu)體
 StaticQueue_t xQueueBuffer;
 
 // ucQueueStorage 用來保存隊列的數(shù)據(jù)
 // 大小為:隊列長度 * 數(shù)據(jù)大小
 uint8_t ucQueueStorage[ QUEUE_LENGTH * ITEM_SIZE ];
 
 void vATask( void *pvParameters )
 {
	QueueHandle_t xQueue1;
 
	// 創(chuàng)建隊列: 可以容納QUEUE_LENGTH個數(shù)據(jù),每個數(shù)據(jù)大小是ITEM_SIZE
	xQueue1 = xQueueCreateStatic( QUEUE_LENGTH,
						  ITEM_SIZE,
						  ucQueueStorage,
						  &xQueueBuffer ); 
 }

5.2.2 復位

隊列剛被創(chuàng)建時,里面沒有數(shù)據(jù);使用過程中可以調(diào)用xQueueReset()把隊列恢復為初始狀態(tài),此函數(shù)原型為:

/* pxQueue : 復位哪個隊列;
 * 返回值: pdPASS(必定成功)
 */
BaseType_t xQueueReset( QueueHandle_t pxQueue);

5.2.3 刪除

刪除隊列的函數(shù)為vQueueDelete(),只能刪除使用動態(tài)方法創(chuàng)建的隊列,它會釋放內(nèi)存。原型如下:

void vQueueDelete( QueueHandle_t xQueue );

5.2.4 寫隊列

可以把數(shù)據(jù)寫到隊列頭部,也可以寫到尾部,這些函數(shù)有兩個版本:在任務中使用、在ISR中使用。函數(shù)原型如下:

/* 等同于xQueueSendToBack
 * 往隊列尾部寫入數(shù)據(jù),如果沒有空間,阻塞時間為xTicksToWait
 */
BaseType_t xQueueSend(
                                QueueHandle_t    xQueue,
                                const void       *pvItemToQueue,
                                TickType_t       xTicksToWait
                            );

/* 
 * 往隊列尾部寫入數(shù)據(jù),如果沒有空間,阻塞時間為xTicksToWait
 */
BaseType_t xQueueSendToBack(
                                QueueHandle_t    xQueue,
                                const void       *pvItemToQueue,
                                TickType_t       xTicksToWait
                            );


/* 
 * 往隊列尾部寫入數(shù)據(jù),此函數(shù)可以在中斷函數(shù)中使用,不可阻塞
 */
BaseType_t xQueueSendToBackFromISR(
                                      QueueHandle_t xQueue,
                                      const void *pvItemToQueue,
                                      BaseType_t *pxHigherPriorityTaskWoken
                                   );

/* 
 * 往隊列頭部寫入數(shù)據(jù),如果沒有空間,阻塞時間為xTicksToWait
 */
BaseType_t xQueueSendToFront(
                                QueueHandle_t    xQueue,
                                const void       *pvItemToQueue,
                                TickType_t       xTicksToWait
                            );

/* 
 * 往隊列頭部寫入數(shù)據(jù),此函數(shù)可以在中斷函數(shù)中使用,不可阻塞
 */
BaseType_t xQueueSendToFrontFromISR(
                                      QueueHandle_t xQueue,
                                      const void *pvItemToQueue,
                                      BaseType_t *pxHigherPriorityTaskWoken
                                   );

這些函數(shù)用到的參數(shù)是類似的,統(tǒng)一說明如下:

參數(shù) 說明
xQueue 隊列句柄,要寫哪個隊列
pvItemToQueue 數(shù)據(jù)指針,這個數(shù)據(jù)的值會被復制進隊列,
復制多大的數(shù)據(jù)?在創(chuàng)建隊列時已經(jīng)指定了數(shù)據(jù)大小
xTicksToWait 如果隊列滿則無法寫入新數(shù)據(jù),可以讓任務進入阻塞狀態(tài),
xTicksToWait表示阻塞的最大時間(Tick Count)。
如果被設為0,無法寫入數(shù)據(jù)時函數(shù)會立刻返回;
如果被設為portMAX_DELAY,則會一直阻塞直到有空間可寫
返回值 pdPASS:數(shù)據(jù)成功寫入了隊列
errQUEUE_FULL:寫入失敗,因為隊列滿了。

5.2.5 讀隊列

使用xQueueReceive()函數(shù)讀隊列,讀到一個數(shù)據(jù)后,隊列中該數(shù)據(jù)會被移除。這個函數(shù)有兩個版本:在任務中使用、在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ù)說明如下:

參數(shù) 說明
xQueue 隊列句柄,要讀哪個隊列
pvBuffer bufer指針,隊列的數(shù)據(jù)會被復制到這個buffer
復制多大的數(shù)據(jù)?在創(chuàng)建隊列時已經(jīng)指定了數(shù)據(jù)大小
xTicksToWait 果隊列空則無法讀出數(shù)據(jù),可以讓任務進入阻塞狀態(tài),
xTicksToWait表示阻塞的最大時間(Tick Count)。
如果被設為0,無法讀出數(shù)據(jù)時函數(shù)會立刻返回;
如果被設為portMAX_DELAY,則會一直阻塞直到有數(shù)據(jù)可寫
返回值 pdPASS:從隊列讀出數(shù)據(jù)入
errQUEUE_EMPTY:讀取失敗,因為隊列空了。

5.2.6 查詢

可以查詢隊列中有多少個數(shù)據(jù)、有多少空余空間。函數(shù)原型如下:

/*
 * 返回隊列中可用數(shù)據(jù)的個數(shù)
 */
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );

/*
 * 返回隊列中可用空間的個數(shù)
 */
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );

5.2.7 覆蓋/偷看

當隊列長度為1時,可以使用xQueueOverwrite()xQueueOverwriteFromISR()來覆蓋數(shù)據(jù)。
注意,隊列長度必須為1。當隊列滿時,這些函數(shù)會覆蓋里面的數(shù)據(jù),這也以為著這些函數(shù)不會被阻塞。
函數(shù)原型如下:

/* 覆蓋隊列
 * xQueue: 寫哪個隊列
 * 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
                      );

如果想讓隊列中的數(shù)據(jù)供多方讀取,也就是說讀取時不要移除數(shù)據(jù),要留給后來人。那么可以使用"窺視",也就是xQueuePeek()xQueuePeekFromISR()。這些函數(shù)會從隊列中復制出數(shù)據(jù),但是不移除數(shù)據(jù)。這也意味著,如果隊列中沒有數(shù)據(jù),那么"偷看"時會導致阻塞;一旦隊列中有數(shù)據(jù),以后每次"偷看"都會成功。
函數(shù)原型如下:

/* 偷看隊列
 * xQueue: 偷看哪個隊列
 * pvItemToQueue: 數(shù)據(jù)地址, 用來保存復制出來的數(shù)據(jù)
 * xTicksToWait: 沒有數(shù)據(jù)的話阻塞一會
 * 返回值: 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: 隊列的基本使用

本節(jié)代碼為:FreeRTOS_08_queue。

本程序會創(chuàng)建一個隊列,然后創(chuàng)建2個發(fā)送任務、1個接收任務:

  • 發(fā)送任務優(yōu)先級為1,分別往隊列中寫入100、200
  • 接收任務優(yōu)先級為2,讀隊列、打印數(shù)值

main函數(shù)中創(chuàng)建的隊列、創(chuàng)建了發(fā)送任務、接收任務,代碼如下:

/* 隊列句柄, 創(chuàng)建隊列時會設置這個變量 */
QueueHandle_t xQueue;

int main( void )
{
	prvSetupHardware();
	
    /* 創(chuàng)建隊列: 長度為5,數(shù)據(jù)大小為4字節(jié)(存放一個整數(shù)) */
    xQueue = xQueueCreate( 5, sizeof( int32_t ) );

	if( xQueue != NULL )
	{
		/* 創(chuàng)建2個任務用于寫隊列, 傳入的參數(shù)分別是100、200
		 * 任務函數(shù)會連續(xù)執(zhí)行,向隊列發(fā)送數(shù)值100、200
		 * 優(yōu)先級為1
		 */
		xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );
		xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );

		/* 創(chuàng)建1個任務用于讀隊列
		 * 優(yōu)先級為2, 高于上面的兩個任務
		 * 這意味著隊列一有數(shù)據(jù)就會被讀走
		 */
		xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );

		/* 啟動調(diào)度器 */
		vTaskStartScheduler();
	}
	else
	{
		/* 無法創(chuàng)建隊列 */
	}

	/* 如果程序運行到了這里就表示出錯了, 一般是內(nèi)存不足 */
	return 0;
}

發(fā)送任務的函數(shù)中,不斷往隊列中寫入數(shù)值,代碼如下:

static void vSenderTask( void *pvParameters )
{
	int32_t lValueToSend;
	BaseType_t xStatus;

	/* 我們會使用這個函數(shù)創(chuàng)建2個任務
	 * 這些任務的pvParameters不一樣
 	 */
	lValueToSend = ( int32_t ) pvParameters;

	/* 無限循環(huán) */
	for( ;; )
	{
		/* 寫隊列
		 * xQueue: 寫哪個隊列
		 * &lValueToSend: 寫什么數(shù)據(jù)? 傳入數(shù)據(jù)的地址, 會從這個地址把數(shù)據(jù)復制進隊列
		 * 0: 不阻塞, 如果隊列滿的話, 寫入失敗, 立刻返回
		 */
		xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );

		if( xStatus != pdPASS )
		{
			printf( "Could not send to the queue.\r\n" );
		}
	}
}

接收任務的函數(shù)中,讀取隊列、判斷返回值、打印,代碼如下:

static void vReceiverTask( void *pvParameters )
{
	/* 讀取隊列時, 用這個變量來存放數(shù)據(jù) */
	int32_t lReceivedValue;
	BaseType_t xStatus;
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );

	/* 無限循環(huán) */
	for( ;; )
	{
		/* 讀隊列
		 * xQueue: 讀哪個隊列
		 * &lReceivedValue: 讀到的數(shù)據(jù)復制到這個地址
		 * xTicksToWait: 如果隊列為空, 阻塞一會
		 */
		xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );

		if( xStatus == pdPASS )
		{
			/* 讀到了數(shù)據(jù) */
			printf( "Received = %d\r\n", lReceivedValue );
		}
		else
		{
			/* 沒讀到數(shù)據(jù) */
			printf( "Could not receive from the queue.\r\n" );
		}
	}
}

程序運行結(jié)果如下:

在這里插入圖片描述


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

在這里插入圖片描述

5.4 示例9: 分辨數(shù)據(jù)源

本節(jié)代碼為:FreeRTOS_09_queue_datasource

當有多個發(fā)送任務,通過同一個隊列發(fā)出數(shù)據(jù),接收任務如何分辨數(shù)據(jù)來源?數(shù)據(jù)本身帶有"來源"信息,比如寫入隊列的數(shù)據(jù)是一個結(jié)構(gòu)體,結(jié)構(gòu)體中的lDataSouceID用來表示數(shù)據(jù)來源:

typedef struct {
    ID_t eDataID;
    int32_t lDataValue;
}Data_t;

不同的發(fā)送任務,先構(gòu)造好結(jié)構(gòu)體,填入自己的eDataID,再寫隊列;接收任務讀出數(shù)據(jù)后,根據(jù)eDataID就可以知道數(shù)據(jù)來源了,如下圖所示:

  • CAN任務發(fā)送的數(shù)據(jù):eDataID=eMotorSpeed
  • HMI任務發(fā)送的數(shù)據(jù):eDataID=eSpeedSetPoint
在這里插入圖片描述

FreeRTOS_09_queue_datasource程序會創(chuàng)建一個隊列,然后創(chuàng)建2個發(fā)送任務、1個接收任務:

  • 創(chuàng)建的隊列,用來發(fā)送結(jié)構(gòu)體:數(shù)據(jù)大小是結(jié)構(gòu)體的大小
  • 發(fā)送任務優(yōu)先級為2,分別往隊列中寫入自己的結(jié)構(gòu)體,結(jié)構(gòu)體中會標明數(shù)據(jù)來源
  • 接收任務優(yōu)先級為1,讀隊列、根據(jù)數(shù)據(jù)來源打印信息

main函數(shù)中創(chuàng)建了隊列、創(chuàng)建了發(fā)送任務、接收任務,代碼如下:

/* 定義2種數(shù)據(jù)來源(ID) */
typedef enum
{
	eMotorSpeed,
	eSpeedSetPoint
} ID_t;

/* 定義在隊列中傳輸?shù)臄?shù)據(jù)的格式 */
typedef struct {
    ID_t eDataID;
    int32_t lDataValue;
}Data_t;

/* 定義2個結(jié)構(gòu)體 */
static const Data_t xStructsToSend[ 2 ] =
{
	{ eMotorSpeed,    10 }, /* CAN任務發(fā)送的數(shù)據(jù) */
	{ eSpeedSetPoint, 5 }   /* HMI任務發(fā)送的數(shù)據(jù) */
};

/* vSenderTask被用來創(chuàng)建2個任務,用于寫隊列
 * vReceiverTask被用來創(chuàng)建1個任務,用于讀隊列
 */
static void vSenderTask( void *pvParameters );
static void vReceiverTask( void *pvParameters );

/*-----------------------------------------------------------*/

/* 隊列句柄, 創(chuàng)建隊列時會設置這個變量 */
QueueHandle_t xQueue;

int main( void )
{
	prvSetupHardware();
	
    /* 創(chuàng)建隊列: 長度為5,數(shù)據(jù)大小為4字節(jié)(存放一個整數(shù)) */
    xQueue = xQueueCreate( 5, sizeof( Data_t ) );

	if( xQueue != NULL )
	{
		/* 創(chuàng)建2個任務用于寫隊列, 傳入的參數(shù)是不同的結(jié)構(gòu)體地址
		 * 任務函數(shù)會連續(xù)執(zhí)行,向隊列發(fā)送結(jié)構(gòu)體
		 * 優(yōu)先級為2
		 */
		xTaskCreate(vSenderTask, "CAN Task", 1000, (void *) &(xStructsToSend[0]), 2, NULL);
		xTaskCreate(vSenderTask, "HMI Task", 1000, (void *) &( xStructsToSend[1]), 2, NULL);

		/* 創(chuàng)建1個任務用于讀隊列
		 * 優(yōu)先級為1, 低于上面的兩個任務
		 * 這意味著發(fā)送任務優(yōu)先寫隊列,隊列常常是滿的狀態(tài)
		 */
		xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );

		/* 啟動調(diào)度器 */
		vTaskStartScheduler();
	}
	else
	{
		/* 無法創(chuàng)建隊列 */
	}

	/* 如果程序運行到了這里就表示出錯了, 一般是內(nèi)存不足 */
	return 0;
}

發(fā)送任務的函數(shù)中,不斷往隊列中寫入數(shù)值,代碼如下:

static void vSenderTask( void *pvParameters )
{
	BaseType_t xStatus;
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );

	/* 無限循環(huán) */
	for( ;; )
	{
		/* 寫隊列
		 * xQueue: 寫哪個隊列
		 * pvParameters: 寫什么數(shù)據(jù)? 傳入數(shù)據(jù)的地址, 會從這個地址把數(shù)據(jù)復制進隊列
		 * xTicksToWait: 如果隊列滿的話, 阻塞一會
		 */
		xStatus = xQueueSendToBack( xQueue, pvParameters, xTicksToWait );

		if( xStatus != pdPASS )
		{
			printf( "Could not send to the queue.\r\n" );
		}
	}
}

接收任務的函數(shù)中,讀取隊列、判斷返回值、打印,代碼如下:

static void vReceiverTask( void *pvParameters )
{
	/* 讀取隊列時, 用這個變量來存放數(shù)據(jù) */
	Data_t xReceivedStructure;
	BaseType_t xStatus;

	/* 無限循環(huán) */
	for( ;; )
	{
		/* 讀隊列
		 * xQueue: 讀哪個隊列
		 * &xReceivedStructure: 讀到的數(shù)據(jù)復制到這個地址
		 * 0: 沒有數(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
		{
			/* 沒讀到數(shù)據(jù) */
			printf( "Could not receive from the queue.\r\n" );
		}
	}
}

運行結(jié)果如下:

在這里插入圖片描述

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

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

5.5 示例10: 傳輸大塊數(shù)據(jù)

本節(jié)代碼為:FreeRTOS_10_queue_bigtransfer。

FreeRTOS的隊列使用拷貝傳輸,也就是要傳輸uint32_t時,把4字節(jié)的數(shù)據(jù)拷貝進隊列;要傳輸一個8字節(jié)的結(jié)構(gòu)體時,把8字節(jié)的數(shù)據(jù)拷貝進隊列。

如果要傳輸1000字節(jié)的結(jié)構(gòu)體呢?寫隊列時拷貝1000字節(jié),讀隊列時再拷貝1000字節(jié)?不建議這么做,影響效率!

這時候,我們要傳輸?shù)氖沁@個巨大結(jié)構(gòu)體的地址:把它的地址寫入隊列,對方從隊列得到這個地址,使用地址去訪問那1000字節(jié)的數(shù)據(jù)。

使用地址來間接傳輸數(shù)據(jù)時,這些數(shù)據(jù)放在RAM里,對于這塊RAM,要保證這幾點:

  • RAM的所有者、操作者,必須清晰明了
    這塊內(nèi)存,就被稱為"共享內(nèi)存"。要確保不能同時修改RAM。比如,在寫隊列之前只有由發(fā)送者修改這塊RAM,在讀隊列之后只能由接收者訪問這塊RAM。
  • RAM要保持可用
    這塊RAM應該是全局變量,或者是動態(tài)分配的內(nèi)存。對于動然分配的內(nèi)存,要確保它不能提前釋放:要等到接收者用完后再釋放。另外,不能是局部變量。

FreeRTOS_10_queue_bigtransfer程序會創(chuàng)建一個隊列,然后創(chuàng)建1個發(fā)送任務、1個接收任務:

  • 創(chuàng)建的隊列:長度為1,用來傳輸"char *"指針
  • 發(fā)送任務優(yōu)先級為1,在字符數(shù)組中寫好數(shù)據(jù)后,把它的地址寫入隊列
  • 接收任務優(yōu)先級為2,讀隊列得到"char *"值,把它打印出來

這個程序故意設置接收任務的優(yōu)先級更高,在它訪問數(shù)組的過程中,接收任務無法執(zhí)行、無法寫這個數(shù)組。

main函數(shù)中創(chuàng)建了隊列、創(chuàng)建了發(fā)送任務、接收任務,代碼如下:

/* 定義一個字符數(shù)組 */
static char pcBuffer[100];


/* vSenderTask被用來創(chuàng)建2個任務,用于寫隊列
 * vReceiverTask被用來創(chuàng)建1個任務,用于讀隊列
 */
static void vSenderTask( void *pvParameters );
static void vReceiverTask( void *pvParameters );

/*-----------------------------------------------------------*/

/* 隊列句柄, 創(chuàng)建隊列時會設置這個變量 */
QueueHandle_t xQueue;

int main( void )
{
	prvSetupHardware();
	
    /* 創(chuàng)建隊列: 長度為1,數(shù)據(jù)大小為4字節(jié)(存放一個char指針) */
    xQueue = xQueueCreate( 1, sizeof(char *) );

	if( xQueue != NULL )
	{
		/* 創(chuàng)建1個任務用于寫隊列
		 * 任務函數(shù)會連續(xù)執(zhí)行,構(gòu)造buffer數(shù)據(jù),把buffer地址寫入隊列
		 * 優(yōu)先級為1
		 */
		xTaskCreate( vSenderTask, "Sender", 1000, NULL, 1, NULL );

		/* 創(chuàng)建1個任務用于讀隊列
		 * 優(yōu)先級為2, 高于上面的兩個任務
		 * 這意味著讀隊列得到buffer地址后,本任務使用buffer時不會被打斷
		 */
		xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );

		/* 啟動調(diào)度器 */
		vTaskStartScheduler();
	}
	else
	{
		/* 無法創(chuàng)建隊列 */
	}

	/* 如果程序運行到了這里就表示出錯了, 一般是內(nèi)存不足 */
	return 0;
}

發(fā)送任務的函數(shù)中,現(xiàn)在全局大數(shù)組pcBuffer中構(gòu)造數(shù)據(jù),然后把它的地址寫入隊列,代碼如下:

static void vSenderTask( void *pvParameters )
{
	BaseType_t xStatus;
	static int cnt = 0;
	
	char *buffer;

	/* 無限循環(huán) */
	for( ;; )
	{
		sprintf(pcBuffer, "www.100ask.net Msg %d\r\n", cnt++);
		buffer = pcBuffer; // buffer變量等于數(shù)組的地址, 下面要把這個地址寫入隊列
		
		/* 寫隊列
		 * xQueue: 寫哪個隊列
		 * pvParameters: 寫什么數(shù)據(jù)? 傳入數(shù)據(jù)的地址, 會從這個地址把數(shù)據(jù)復制進隊列
		 * 0: 如果隊列滿的話, 即刻返回
		 */
		xStatus = xQueueSendToBack( xQueue, &buffer, 0 ); /* 只需要寫入4字節(jié), 無需寫入整個buffer */

		if( xStatus != pdPASS )
		{
			printf( "Could not send to the queue.\r\n" );
		}
	}
}

接收任務的函數(shù)中,讀取隊列、得到buffer的地址、打印,代碼如下:

static void vReceiverTask( void *pvParameters )
{
	/* 讀取隊列時, 用這個變量來存放數(shù)據(jù) */
	char *buffer;
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );	
	BaseType_t xStatus;

	/* 無限循環(huán) */
	for( ;; )
	{
		/* 讀隊列
		 * xQueue: 讀哪個隊列
		 * &xReceivedStructure: 讀到的數(shù)據(jù)復制到這個地址
		 * xTicksToWait: 沒有數(shù)據(jù)就阻塞一會
		 */
		xStatus = xQueueReceive( xQueue, &buffer, xTicksToWait); /* 得到buffer地址,只是4字節(jié) */

		if( xStatus == pdPASS )
		{
			/* 讀到了數(shù)據(jù) */
			printf("Get: %s", buffer);
		}
		else
		{
			/* 沒讀到數(shù)據(jù) */
			printf( "Could not receive from the queue.\r\n" );
		}
	}
}

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

在這里插入圖片描述

5.6 示例11: 郵箱(Mailbox)

本節(jié)代碼為:FreeRTOS_11_queue_mailbox

FreeRTOS的郵箱概念跟別的RTOS不一樣,這里的郵箱稱為"櫥窗"也許更恰當:

  • 它是一個隊列,隊列長度只有1
  • 寫郵箱:新數(shù)據(jù)覆蓋舊數(shù)據(jù),在任務中使用xQueueOverwrite(),在中斷中使用xQueueOverwriteFromISR()
    既然是覆蓋,那么無論郵箱中是否有數(shù)據(jù),這些函數(shù)總能成功寫入數(shù)據(jù)。
  • 讀郵箱:讀數(shù)據(jù)時,數(shù)據(jù)不會被移除;在任務中使用xQueuePeek(),在中斷中使用xQueuePeekFromISR()。
    這意味著,第一次調(diào)用時會因為無數(shù)據(jù)而阻塞,一旦曾經(jīng)寫入數(shù)據(jù),以后讀郵箱時總能成功。

main函數(shù)中創(chuàng)建了隊列(隊列長度為1)、創(chuàng)建了發(fā)送任務、接收任務:

  • 發(fā)送任務的優(yōu)先級為2,它先執(zhí)行
  • 接收任務的優(yōu)先級為1

代碼如下:

/* 隊列句柄, 創(chuàng)建隊列時會設置這個變量 */
QueueHandle_t xQueue;

int main( void )
{
	prvSetupHardware();
	
    /* 創(chuàng)建隊列: 長度為1,數(shù)據(jù)大小為4字節(jié)(存放一個char指針) */
    xQueue = xQueueCreate( 1, sizeof(uint32_t) );

	if( xQueue != NULL )
	{
		/* 創(chuàng)建1個任務用于寫隊列
		 * 任務函數(shù)會連續(xù)執(zhí)行,構(gòu)造buffer數(shù)據(jù),把buffer地址寫入隊列
		 * 優(yōu)先級為2
		 */
		xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );

		/* 創(chuàng)建1個任務用于讀隊列
		 * 優(yōu)先級為1
		 */
		xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );

		/* 啟動調(diào)度器 */
		vTaskStartScheduler();
	}
	else
	{
		/* 無法創(chuàng)建隊列 */
	}

	/* 如果程序運行到了這里就表示出錯了, 一般是內(nèi)存不足 */
	return 0;
}

發(fā)送任務、接收任務的代碼和執(zhí)行流程如下:

  • A:發(fā)送任務先執(zhí)行,馬上阻塞
  • BC:接收任務執(zhí)行,這是郵箱無數(shù)據(jù),打印"Could not …"。在發(fā)送任務阻塞過程中,接收任務多次執(zhí)行、多次打印。
  • D:發(fā)送任務從阻塞狀態(tài)退出,立刻執(zhí)行、寫隊列
  • E:發(fā)送任務再次阻塞
  • FG、HI、……:接收任務不斷"偷看"郵箱,得到同一個數(shù)據(jù),打印出多個"Get: 0"
  • J:發(fā)送任務從阻塞狀態(tài)退出,立刻執(zhí)行、覆蓋隊列,寫入1
  • K:發(fā)送任務再次阻塞
  • LM、……:接收任務不斷"偷看"郵箱,得到同一個數(shù)據(jù),打印出多個"Get: 1"
在這里插入圖片描述

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

在這里插入圖片描述
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 嵌入式
    +關(guān)注

    關(guān)注

    5149

    文章

    19651

    瀏覽量

    317169
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11508

    瀏覽量

    213626
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4379

    瀏覽量

    64771
  • RTOS
    +關(guān)注

    關(guān)注

    24

    文章

    849

    瀏覽量

    121112
  • FreeRTOS
    +關(guān)注

    關(guān)注

    12

    文章

    492

    瀏覽量

    64257
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關(guān)推薦
    熱點推薦

    東山freeRTOS系列程之信號量(6)

    文章目錄 系列教程總目錄 概述 6.1 信號量的特性 6.1.1 信號量的常規(guī)操作 6.1.2 信號量跟隊列的對比 6.1.3 兩種信號量的對比 6.2 信號量函數(shù) 6.2.1 創(chuàng)建 6.2.2
    的頭像 發(fā)表于 12-13 14:35 ?5608次閱讀
    <b class='flag-5'>韋</b><b class='flag-5'>東山</b><b class='flag-5'>freeRTOS</b><b class='flag-5'>系列</b>教<b class='flag-5'>程之</b>信號量(6)

    FreeRTOS發(fā)送消息隊列失敗的解決辦法?

    剛?cè)腴TFreeRTOS,正在學習發(fā)送消息隊列。程序參考原子哥的例子寫的。在執(zhí)行 err = xQueueSend(SI24R1Rx_Queue,&RxBuff,100);這一句的時候??偸菚?/div>
    發(fā)表于 06-14 09:01

    Queue隊列的作用是什么

    文章目錄前言Queue 隊列semaphore 信號量Mutex 互斥量微信公眾號前言FreeRTOS STM32CubeMX配置 內(nèi)存管理 任務管理上節(jié)介紹了用STM32CubeMX生成帶
    發(fā)表于 02-14 06:57

    消息隊列Queue相關(guān)資料推薦

    消息隊列QueueAPItx_queue_createtx_queue_deletex_queue_flushtx_queue_front_sendtx_queue_receivetx_queue_send_notifyAPItx_queue_createtx_queue_del
    發(fā)表于 02-22 06:53

    東山freeRTOS系列教程:入門文檔教程+進階視頻教程

    文章目錄 學前知識普及 初級文檔教程 進階視頻教程 進階一:FreeRTOS的內(nèi)部機制 進階二:深入理解FreeRTOS隊列隊列實戰(zhàn) 進階三:RTOS商業(yè)產(chǎn)品案例源碼講解 學前知識普
    發(fā)表于 11-29 16:36 ?2596次閱讀
    <b class='flag-5'>韋</b><b class='flag-5'>東山</b><b class='flag-5'>freeRTOS</b><b class='flag-5'>系列</b>教程:入門文檔教程+進階視頻教程

    東山freeRTOS程之FreeRTOS概述與體驗(1)

    文章目錄 教程目錄 1.1 FreeRTOS目錄結(jié)構(gòu) 1.1 FreeRTOS目錄結(jié)構(gòu) 1.2 核心文件 1.3 移植時涉及的文件 1.4 頭文件相關(guān) 1.4.1 頭文件目錄 1.4.2 頭文件
    發(fā)表于 11-29 16:56 ?2437次閱讀
    <b class='flag-5'>韋</b><b class='flag-5'>東山</b><b class='flag-5'>freeRTOS</b>教<b class='flag-5'>程之</b><b class='flag-5'>FreeRTOS</b>概述與體驗(1)

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

    文章目錄 教程目錄 2.1 為什么要自己實現(xiàn)內(nèi)存管理 2.2 FreeRTOS5種內(nèi)存管理方法 2.2.1 Heap_1 2.2.2 Heap_2 2.2.3 Heap_3 2.2.4
    發(fā)表于 11-29 16:58 ?1236次閱讀
    <b class='flag-5'>韋</b><b class='flag-5'>東山</b><b class='flag-5'>freeRTOS</b><b class='flag-5'>系列</b>教<b class='flag-5'>程之</b>內(nèi)存管理(2)

    FreeRTOS消息隊列 & ESP32使用

    FreeRTOS消息隊列 & ESP32實戰(zhàn)FreeRTOS消息隊列FreeRTOS的消息隊列
    發(fā)表于 12-03 17:51 ?1次下載
    <b class='flag-5'>FreeRTOS</b>消息<b class='flag-5'>隊列</b> & ESP32使用

    FreeRTOS學習(五)消息隊列和二值信號量 xQueue / xSemaphore

    消息隊列可以和中斷 人物間發(fā)送和接受不定長的消息,在消息隊列中會使任務進入阻塞。 可以在調(diào)度器開始前,創(chuàng)建消息隊列。#include "FreeRTOS.h"#include
    發(fā)表于 12-04 20:06 ?7次下載
    <b class='flag-5'>FreeRTOS</b>學習(五)消息<b class='flag-5'>隊列</b>和二值信號量 xQueue / xSemaphore

    FreeRTOS 隊列 信號量 互斥量

    文章目錄前言Queue 隊列semaphore 信號量Mutex 互斥量微信公眾號前言FreeRTOS STM32CubeMX配置 內(nèi)存管理 任務管理上節(jié)介紹了用STM32CubeMX生成帶
    發(fā)表于 12-09 09:51 ?0次下載
    <b class='flag-5'>FreeRTOS</b> <b class='flag-5'>隊列</b> 信號量 互斥量

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

    消息隊列QueueAPItx_queue_createtx_queue_deletex_queue_flushtx_queue_front_sendtx_queue_receivetx_queue_send_notifyAPItx_queue_createtx_queue_del
    發(fā)表于 12-28 19:35 ?2次下載
    ThreadX(九)------消息<b class='flag-5'>隊列</b><b class='flag-5'>Queue</b>

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

    FreeRTOS為操作隊列提供了非常豐富的API函數(shù),包括隊列的創(chuàng)建、刪除,靈活的入隊和出隊方式、帶中斷保護的入隊和出隊等等。下面就來詳細...
    發(fā)表于 01-26 17:44 ?12次下載
    <b class='flag-5'>FreeRTOS</b><b class='flag-5'>系列</b>第18篇---<b class='flag-5'>FreeRTOS</b><b class='flag-5'>隊列</b>API函數(shù)

    隊列Queue的常用方法有哪些

    FIFO(先入先出)隊列Queue,LIFO(后入先出)隊列LifoQueue,和優(yōu)先級隊列PriorityQueue。
    的頭像 發(fā)表于 08-19 10:24 ?6254次閱讀
    <b class='flag-5'>隊列</b><b class='flag-5'>Queue</b>的常用方法有哪些

    STM32G0開發(fā)筆記:使用FreeRTOS系統(tǒng)的隊列Queue

    使用Platformio平臺的libopencm3開發(fā)框架來開發(fā)STM32G0,下面為使用FreeRTOS系統(tǒng)的隊列Queue。
    的頭像 發(fā)表于 01-16 14:50 ?1841次閱讀

    FreeRTOS消息隊列結(jié)構(gòu)體

    有一個結(jié)構(gòu)體用于描述隊列,叫做 Queue_t,這個結(jié)構(gòu)體在文件 queue.c 中定義。 3、隊列創(chuàng)建 在使用隊列之前必須先創(chuàng)建
    的頭像 發(fā)表于 07-06 17:03 ?1451次閱讀
    <b class='flag-5'>FreeRTOS</b>消息<b class='flag-5'>隊列</b>結(jié)構(gòu)體