日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

韦东山freeRTOS系列教程之【第五章】队列(queue)

發(fā)布時間:2023/12/14 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 韦东山freeRTOS系列教程之【第五章】队列(queue) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

  • 系列教程總目錄
  • 概述
  • 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,First 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都可讀、寫隊列。可以多個任務讀寫隊列。

任務讀寫隊列時,簡單地說:如果讀寫不成功,則阻塞;可以指定超時時間。口語化地說,就是可以定個鬧鐘:如果能讀寫了就馬上進入就緒態(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_SIZExQueue1 = 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隊列句柄,要讀哪個隊列
pvBufferbufer指針,隊列的數(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é)果如下圖所示:

總結(jié)

以上是生活随笔為你收集整理的韦东山freeRTOS系列教程之【第五章】队列(queue)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。