色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

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"
在這里插入圖片描述

運行結果如下圖所示:

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

    關注

    5083

    文章

    19131

    瀏覽量

    305492
  • Linux
    +關注

    關注

    87

    文章

    11310

    瀏覽量

    209598
  • 函數
    +關注

    關注

    3

    文章

    4332

    瀏覽量

    62656
  • RTOS
    +關注

    關注

    22

    文章

    814

    瀏覽量

    119669
  • FreeRTOS
    +關注

    關注

    12

    文章

    484

    瀏覽量

    62199
收藏 人收藏

    評論

    相關推薦

    東山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 ?5085次閱讀
    <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發送消息隊列失敗的解決辦法?

    剛入門FreeRTOS,正在學習發送消息隊列。程序參考原子哥的例子寫的。在執行 err = xQueueSend(SI24R1Rx_Queue,&RxBuff,100);這一句的時候。總是會
    發表于 06-14 09:01

    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 ?2272次閱讀
    <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 ?2039次閱讀
    <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 ?1036次閱讀
    <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 ?11次下載
    <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 ?5776次閱讀
    <b class='flag-5'>隊列</b><b class='flag-5'>Queue</b>的常用方法有哪些

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

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

    FreeRTOS消息隊列結構體

    有一個結構體用于描述隊列,叫做 Queue_t,這個結構體在文件 queue.c 中定義。 3、隊列創建 在使用隊列之前必須先創建
    的頭像 發表于 07-06 17:03 ?1111次閱讀
    <b class='flag-5'>FreeRTOS</b>消息<b class='flag-5'>隊列</b>結構體
    主站蜘蛛池模板: 国产乱妇乱子在线播视频播放网站 | 高h肉辣文黄蓉| 亚洲精品国偷拍电影自产在线| 男人J桶进男人屁股过程| 国产亚洲精品久久久久久久| 扒开女生尿口| 中文亚洲大香伊蕉不卡一区| 午夜宅宅伦电影网中文字幕| 全身无赤裸裸美女网站| 久久九九精品国产自在现线拍| 国产精品7777人妻精品冫| japanesen女同| 在线亚洲免费| 亚洲免费无码av线观看| 少女free大陆| 日本另类z0zxhd| 欧美性爱-第1页| 美女被抽插到哭内射视频免费| 红杏俱乐部| 国产午夜精品片一区二区三区| 钉钉女老师| 不良网站进入窗口软件下载免费| 26uuu老色哥| 真人裸交有声性动态图| 亚洲一区在线观看无码欧美| 性xxxx18公交车| 翁公吮她的花蒂和奶水| 日本午夜视频在线| 青柠在线视频| 青草影院内射中出高潮-百度| 男人J进入女人P免费狂躁| 美女gif趴跪式动态图| 久久热在线视频精品店| 久久se视频精品视频在线| 黑色丝袜美腿美女被躁翻了| 国拍在线精品视频免费观看| 国产日韩欧美另类| 国产亚洲精品网站在线视频| 国产精品人妻久久无码不卡| 国产成久久免费精品AV片天堂| 动漫美女被羞羞动漫怪物|