国产精品久久久aaaa,日日干夜夜操天天插,亚洲乱熟女香蕉一区二区三区少妇,99精品国产高清一区二区三区,国产成人精品一区二区色戒,久久久国产精品成人免费,亚洲精品毛片久久久久,99久久婷婷国产综合精品电影,国产一区二区三区任你鲁

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

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

3天內不再提示

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

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

掃碼添加小助手

加入工程師交流群

文章目錄

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

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

系列教程總目錄

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

概述

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

本章涉及如下內容:

  • 怎么創建、清除、刪除隊列
  • 隊列中消息如何保存
  • 怎么向隊列發送數據、怎么從隊列讀取數據、怎么覆蓋隊列的數據
  • 在隊列上阻塞是什么意思
  • 怎么在多個隊列上阻塞
  • 讀寫隊列時如何影響任務的優先級

5.1 隊列的特性

5.1.1 常規操作

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

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

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

在這里插入圖片描述

5.1.2 傳輸數據的兩種方法

使用隊列傳輸數據時有兩種方法:

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

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

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

無需分配buffer來保存數據,隊列中有buffer

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

發送任務、接收任務解耦:接收任務不需要知道這數據是誰的、也不需要發送任務來釋放數據

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

隊列的空間有FreeRTOS內核分配,無需任務操心

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

5.1.3 隊列的阻塞訪問

只要知道隊列的句柄,誰都可以讀、寫該隊列。任務、ISR都可讀、寫隊列。可以多個任務讀寫隊列。

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

某個任務讀隊列時,如果隊列沒有數據,則該任務可以進入阻塞狀態:還可以指定阻塞的時間。如果隊列有數據了,則該阻塞的任務會變為就緒態。如果一直都沒有數據,則時間到之后它也會進入就緒態。

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

  • 優先級最高的任務
  • 如果大家的優先級相同,那等待時間最久的任務會進入就緒態

跟讀隊列類似,一個任務要寫隊列時,如果隊列滿了,該任務也可以進入阻塞狀態:還可以指定阻塞的時間。如果隊列有空間了,則該阻塞的任務會變為就緒態。如果一直都沒有空間,則時間到之后它也會進入就緒態。

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

  • 優先級最高的任務
  • 如果大家的優先級相同,那等待時間最久的任務會進入就緒態

5.2 隊列函數

使用隊列的流程:創建隊列、寫隊列、讀隊列、刪除隊列。

5.2.1 創建

隊列的創建有兩種方法:動態分配內存、靜態分配內存,

  • 動態分配內存:xQueueCreate,隊列的內存在函數內部動態分配

函數原型如下:

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

函數原型如下:

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

示例代碼:

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

5.2.2 復位

隊列剛被創建時,里面沒有數據;使用過程中可以調用xQueueReset()把隊列恢復為初始狀態,此函數原型為:

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

5.2.3 刪除

刪除隊列的函數為vQueueDelete(),只能刪除使用動態方法創建的隊列,它會釋放內存。原型如下:

void vQueueDelete( QueueHandle_t xQueue );

5.2.4 寫隊列

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

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

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


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

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

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

這些函數用到的參數是類似的,統一說明如下:

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

5.2.5 讀隊列

使用xQueueReceive()函數讀隊列,讀到一個數據后,隊列中該數據會被移除。這個函數有兩個版本:在任務中使用、在ISR中使用。函數原型如下:

BaseType_t xQueueReceive( QueueHandle_t xQueue,
                          void * const pvBuffer,
                          TickType_t xTicksToWait );

BaseType_t xQueueReceiveFromISR(
                                    QueueHandle_t    xQueue,
                                    void             *pvBuffer,
                                    BaseType_t       *pxTaskWoken
                                );

參數說明如下:

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

5.2.6 查詢

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

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

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

5.2.7 覆蓋/偷看

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

/* 覆蓋隊列
 * xQueue: 寫哪個隊列
 * pvItemToQueue: 數據地址
 * 返回值: pdTRUE表示成功, pdFALSE表示失敗
 */
BaseType_t xQueueOverwrite(
                           QueueHandle_t xQueue,
                           const void * pvItemToQueue
                      );

BaseType_t xQueueOverwriteFromISR(
                           QueueHandle_t xQueue,
                           const void * pvItemToQueue,
                           BaseType_t *pxHigherPriorityTaskWoken
                      );

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

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

本節代碼為:FreeRTOS_08_queue

本程序會創建一個隊列,然后創建2個發送任務、1個接收任務:

  • 發送任務優先級為1,分別往隊列中寫入100、200
  • 接收任務優先級為2,讀隊列、打印數值

main函數中創建的隊列、創建了發送任務、接收任務,代碼如下:

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

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

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

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

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

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

發送任務的函數中,不斷往隊列中寫入數值,代碼如下:

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

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

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

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

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

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

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

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

程序運行結果如下:

在這里插入圖片描述


任務調度情況如下圖所示:

在這里插入圖片描述

5.4 示例9: 分辨數據源

本節代碼為:FreeRTOS_09_queue_datasource

當有多個發送任務,通過同一個隊列發出數據,接收任務如何分辨數據來源?數據本身帶有"來源"信息,比如寫入隊列的數據是一個結構體,結構體中的lDataSouceID用來表示數據來源:

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

不同的發送任務,先構造好結構體,填入自己的eDataID,再寫隊列;接收任務讀出數據后,根據eDataID就可以知道數據來源了,如下圖所示:

  • CAN任務發送的數據:eDataID=eMotorSpeed
  • HMI任務發送的數據:eDataID=eSpeedSetPoint
在這里插入圖片描述

FreeRTOS_09_queue_datasource程序會創建一個隊列,然后創建2個發送任務、1個接收任務:

  • 創建的隊列,用來發送結構體:數據大小是結構體的大小
  • 發送任務優先級為2,分別往隊列中寫入自己的結構體,結構體中會標明數據來源
  • 接收任務優先級為1,讀隊列、根據數據來源打印信息

main函數中創建了隊列、創建了發送任務、接收任務,代碼如下:

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

/* 定義在隊列中傳輸的數據的格式 */
typedef struct {
    ID_t eDataID;
    int32_t lDataValue;
}Data_t;

/* 定義2個結構體 */
static const Data_t xStructsToSend[ 2 ] =
{
	{ eMotorSpeed,    10 }, /* CAN任務發送的數據 */
	{ eSpeedSetPoint, 5 }   /* HMI任務發送的數據 */
};

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

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

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

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

	if( xQueue != NULL )
	{
		/* 創建2個任務用于寫隊列, 傳入的參數是不同的結構體地址
		 * 任務函數會連續執行,向隊列發送結構體
		 * 優先級為2
		 */
		xTaskCreate(vSenderTask, "CAN Task", 1000, (void *) &(xStructsToSend[0]), 2, NULL);
		xTaskCreate(vSenderTask, "HMI Task", 1000, (void *) &( xStructsToSend[1]), 2, NULL);

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

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

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

發送任務的函數中,不斷往隊列中寫入數值,代碼如下:

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

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

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

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

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

	/* 無限循環 */
	for( ;; )
	{
		/* 讀隊列
		 * xQueue: 讀哪個隊列
		 * &xReceivedStructure: 讀到的數據復制到這個地址
		 * 0: 沒有數據就即刻返回,不阻塞
		 */
		xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 );

		if( xStatus == pdPASS )
		{
			/* 讀到了數據 */
			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
		{
			/* 沒讀到數據 */
			printf( "Could not receive from the queue.\r\n" );
		}
	}
}

運行結果如下:

在這里插入圖片描述

任務調度情況如下圖所示:

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

5.5 示例10: 傳輸大塊數據

本節代碼為:FreeRTOS_10_queue_bigtransfer

FreeRTOS的隊列使用拷貝傳輸,也就是要傳輸uint32_t時,把4字節的數據拷貝進隊列;要傳輸一個8字節的結構體時,把8字節的數據拷貝進隊列。

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

這時候,我們要傳輸的是這個巨大結構體的地址:把它的地址寫入隊列,對方從隊列得到這個地址,使用地址去訪問那1000字節的數據。

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

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

FreeRTOS_10_queue_bigtransfer程序會創建一個隊列,然后創建1個發送任務、1個接收任務:

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

這個程序故意設置接收任務的優先級更高,在它訪問數組的過程中,接收任務無法執行、無法寫這個數組。

main函數中創建了隊列、創建了發送任務、接收任務,代碼如下:

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


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

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

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

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

	if( xQueue != NULL )
	{
		/* 創建1個任務用于寫隊列
		 * 任務函數會連續執行,構造buffer數據,把buffer地址寫入隊列
		 * 優先級為1
		 */
		xTaskCreate( vSenderTask, "Sender", 1000, NULL, 1, NULL );

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

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

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

發送任務的函數中,現在全局大數組pcBuffer中構造數據,然后把它的地址寫入隊列,代碼如下:

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

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

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

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

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

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

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

運行結果如下圖所示:

在這里插入圖片描述

5.6 示例11: 郵箱(Mailbox)

本節代碼為:FreeRTOS_11_queue_mailbox

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

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

main函數中創建了隊列(隊列長度為1)、創建了發送任務、接收任務:

  • 發送任務的優先級為2,它先執行
  • 接收任務的優先級為1

代碼如下:

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

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

	if( xQueue != NULL )
	{
		/* 創建1個任務用于寫隊列
		 * 任務函數會連續執行,構造buffer數據,把buffer地址寫入隊列
		 * 優先級為2
		 */
		xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );

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

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

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

發送任務、接收任務的代碼和執行流程如下:

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

運行結果如下圖所示:

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

    關注

    5198

    文章

    20445

    瀏覽量

    333994
  • Linux
    +關注

    關注

    88

    文章

    11758

    瀏覽量

    219009
  • 函數
    +關注

    關注

    3

    文章

    4417

    瀏覽量

    67502
  • RTOS
    +關注

    關注

    25

    文章

    866

    瀏覽量

    122977
  • FreeRTOS
    +關注

    關注

    14

    文章

    499

    瀏覽量

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

掃碼添加小助手

加入工程師交流群

    評論

    相關推薦
    熱點推薦

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

    文章目錄 系列教程總目錄 概述 6.1 信號量的特性 6.1.1 信號量的常規操作 6.1.2 信號量跟隊列的對比 6.1.3 兩種信號量的對比 6.2 信號量函數 6.2.1 創建 6.2.2
    的頭像 發表于 12-13 14:35 ?6163次閱讀
    <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)

    Queue隊列的作用是什么

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

    消息隊列Queue相關資料推薦

    消息隊列QueueAPItx_queue_createtx_queue_deletex_queue_flushtx_queue_front_sendtx_queue_receivetx_queue_send_notifyAPItx_queue_createtx_queue_del
    發表于 02-22 06:53

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

    文章目錄 學前知識普及 初級文檔教程 進階視頻教程 進階一:FreeRTOS的內部機制 進階二:深入理解FreeRTOS隊列隊列實戰 進階三:RTOS商業產品案例源碼講解 學前知識普
    發表于 11-29 16:36 ?3231次閱讀
    <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目錄結構 1.1 FreeRTOS目錄結構 1.2 核心文件 1.3 移植時涉及的文件 1.4 頭文件相關 1.4.1 頭文件目錄 1.4.2 頭文件
    發表于 11-29 16:56 ?2852次閱讀
    <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系列程之內存管理(2)

    文章目錄 教程目錄 2.1 為什么要自己實現內存管理 2.2 FreeRTOS5種內存管理方法 2.2.1 Heap_1 2.2.2 Heap_2 2.2.3 Heap_3 2.2.4
    發表于 11-29 16:58 ?1500次閱讀
    <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>內存管理(2)

    FreeRTOS消息隊列 & ESP32使用

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

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

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

    FreeRTOS 隊列 信號量 互斥量

    文章目錄前言Queue 隊列semaphore 信號量Mutex 互斥量微信公眾號前言FreeRTOS STM32CubeMX配置 內存管理 任務管理上節介紹了用STM32CubeMX生成帶
    發表于 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
    發表于 12-28 19:35 ?2次下載
    ThreadX(九)------消息<b class='flag-5'>隊列</b><b class='flag-5'>Queue</b>

    FreeRTOS系列第18篇---FreeRTOS隊列API函數

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

    隊列Queue的常用方法有哪些

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

    STM32G0開發筆記:使用FreeRTOS系統的隊列Queue

    使用Platformio平臺的libopencm3開發框架來開發STM32G0,下面為使用FreeRTOS系統的隊列Queue
    的頭像 發表于 01-16 14:50 ?2392次閱讀

    什么是queue

    queue 容器,又稱隊列容器,是簡單地裝飾deque容器而成為另外的一種容器。
    的頭像 發表于 02-27 15:43 ?3358次閱讀

    FreeRTOS消息隊列結構體

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