天天看點

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

1)實驗平台:正點原子MPSoC開發闆

2)平台購買位址:https://detail.tmall.com/item.htm?id=692450874670

3)全套實驗源碼+手冊+視訊下載下傳位址: http://www.openedv.com/thread-340252-1-1.html

第十九章FreeRtos Hello World實驗

我們在使用Vitis建立工程時,在軟體配置界面中有對作業系統的選擇,這個選擇有兩個選項,一個選項是單機操作(standalone,即無作業系統),一個選項是實時作業系統(freertos10_xilinx)。Vitis開發指南的工程在開發時選擇的都是單機操作,本章實驗的目的是使用實時作業系統進行簡單的基礎實驗開發,例如使用實時作業系統運作“Hello World”實驗。

本章包括以下幾個部分:

1919.1簡介

19.2實驗任務

19.3硬體設計

19.4軟體設計

19.5下載下傳驗證

19.1簡介

什麼是FreeRtos?

Free即免費的,RTOS全稱是Real Time Operating System,中文就是實時作業系統。注意,RTOS不是指某一個确定的系統,而是指一類系統,比如uC/OS,FreeRTOS,RTX,RT-Thread等這些都是RTOS類作業系統。

作業系統允許多個任務同時運作,這個叫做多任務。實際上,一個處理器核心在某一時刻隻能運作一個任務。作業系統中任務排程器的責任就是決定在某一時刻究竟運作哪個任務。任務排程在各個任務之間的切換非常快,就給人們造成了同一時刻有多個任務同時運作的錯覺。

某些作業系統給每個任務配置設定同樣的運作時間,時間到了就輪到下一個任務,比如Unix作業系統。FreeRTOS作業系統則是由使用者給每個任務配置設定一個任務優先級,任務排程器就可以根據此優先級來決定下一刻應該運作哪個任務。

FreeRTOS是RTOS系統的一種,FreeRTOS十分的小巧,可以在資源有限的微控制器中運作,當然,FreeRTOS不僅局限于在微控制器中使用。但從檔案數量上來看FreeRTOS要比uC/OSII和uC/OSIII小的多。

在嵌入式領域中,嵌入式實時作業系統正得到越來越廣泛的應用。采用嵌入式實時作業系統(RTOS)可以更合理、更有效地利用CPU的資源,簡化應用軟體的設計,縮短系統開發時間,更好地保證系統的實時性和可靠性。

FreeRTOS是一個迷你的實時作業系統核心。作為一個輕量級的作業系統,功能包括:任務管理、時間管理、信号量、消息隊列、記憶體管理、記錄功能、軟體定時器、協程等,可基本滿足較小系統的需要。

由于RTOS需占用一定的系統資源(尤其是RAM資源),隻有μC/OS-II、embOS、salvo、FreeRTOS等少數實時作業系統能在小RAM單片機上運作。相對μC/OS-II、embOS等商業作業系統,FreeRTOS作業系統是完全免費的作業系統,具有源碼公開、可移植、可裁減、排程政策靈活的特點,可以友善地移植到各種單片機上運作。

FreeRtos的特點

FreeRTOS是一個可裁剪的小型RTOS系統,其特點包括:

● FreeRTOS的核心支援搶占式,合作式和時間片排程。

● 提供了一個用于低功耗的Tickless模式。

● 系統的元件在建立時可以選擇動态或者靜态的RAM,比如任務、消息隊列、信号量、軟體定時器等等。

● 已經在超過30種架構的晶片上進行了移植。

● FreeRTOS系統簡單、小巧、易用,通常情況下核心占用4k-9k位元組的空間。

● 高可移植性,代碼主要C語言編寫。

● 支援實時任務和協程(co-routines也有稱為合作式、協同程式)。

● 任務與任務、任務與中斷之間可以使用任務通知、消息隊列、二值信号量、數值型、信号量、遞歸互斥信号量和互斥信号量進行通信和同步。

● 創新的事件組(或者事件标志)。

● 具有優先級繼承特性的互斥信号量。

● 高效的軟體定時器。

● 強大的跟蹤執行功能。

● 堆棧溢出檢測功能。

● 任務數量不限。

● 任務優先級不限。

19.2實驗任務

本章的實驗任務是在MPSOC開發闆上建立實時作業系統,在使用序列槽列印“Hello World”資訊的同時分别使ps端與pl端的LED燈以不同的頻率閃爍。

19.3硬體設計

根據實驗任務我們可以畫出本次實驗的系統框圖,如下圖所示:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.3.1系統框圖

本實驗的硬體搭建是在AXI_GPIO的實驗基礎上進行設計的,首先是将AXI_GPIO實驗的工程另存為FreeRtos工程,如下圖所示:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.3.2 建立“FreeRtos”工程

“FreeRtos”工程建立完成後如下圖所示,然後點選Flow Navigator—IP INTEGRATOR—Open Block Design:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.3.3 打開硬體設計

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.3.4硬體設計如上圖

從前面的實驗任務介紹可知,本實驗的vitis – FreeRtos工程設計是要使用官方提供的Hello World模闆,該模闆中進行的是一個定時器控制的列印資訊的例程,是以在建立vitis – FreeRtos工程前需要在硬體設計中添加定時器選項。

在硬體設計中勾標明時器,如下圖所示:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.3.5輕按兩下“ZYNQ UltraSCALE+”

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.3.6打開 “Re-customize IP”界面

在I/O Configuration—Low Speed—Processing Unit—TTC路徑下勾標明時器設定:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.3.7 勾標明時器

上面勾標明時器後點選“OK”。

根據實驗任務本實驗還要在硬體設計中添加一個PL端的LED燈,因為本實驗中不需要使用PL_KEY,是以将該GPIO接口修改成一個PL_LED輸出接口,輕按兩下“axi_gpio_0”子產品打開後設定該子產品參數,進行如下所示設定:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.3.8 PL_LED GPIO配置

點選上圖“OK”後,接下來是對下圖中紅框訓示的名稱進行修改,修改如下圖所示:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.3.9 修改引腳名稱

将PL_KEY的GPIO引腳AXI_GPIO_KEY在上圖左側的紅框中修改為AXI_GPIO_LED,名稱修改完成後硬體搭建完成,進行設計檢查。

右擊“Diagram”空白處:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.3.10檢查設計

檢查完成後沒有錯誤,點選OK,按Ctrl+S儲存,如下圖所示:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.3.11檢查完成

儲存設計,然後右鍵點選design_1_wrapper選擇Generate Output Products。

因為設計中使用了PL的資源,則需要添加引腳限制并對該設計進行綜合、實作并生成Bitstream檔案。

如下圖所示的添加pl端引腳限制檔案:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.3.12 pl引腳限制

#LED

set_property PACKAGE_PIN AE10 [get_ports {AXI_GPIO_LED_tri_o[0]}]

set_property IOSTANDARD LVCMOS33 [get_ports {AXI_GPIO_LED_tri_o[0]}]

接下來生成bit檔案,如下圖所示:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.3.13生成bit檔案

bit檔案成功生成後進行導出硬體操作:

導出Hardware,在彈出的對話框中,因為生成了bitstream檔案,是以需要勾選“Include bitstream”,直接點選“OK”按鈕。

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.3.14 勾選“Include bitstream”

上圖中,XSA file name一欄是産生的硬體資訊檔案的檔案名,這裡我們保持預設。Export to後面的路徑是生成的包含硬體資訊檔案的路徑,生成的檔案如下所示:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.3.15生成的xsa檔案

将導出的design_1_wrapper.xsa放到vitis檔案夾,啟動Vitis。

19.4軟體設計

在硬體設計的最後,我們啟動了軟體開發環境(Vitis),如下圖所示:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.4.1 Vitis開發環境界面

在菜單欄選擇File > New > Application Project, 建立一個vitis應用工程,如下圖所示:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.4.2 建立應用工程

在彈出的對話框中,輸入工程名“FreeRtos_hello_world”,其它選項保持預設即可,點選“Next”,如下圖所示:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.4.3 配置工程

打開Create a new platform from hardware(XSA)标簽頁,點選“+”添加xsa檔案,如下圖所示:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖19.4.4 添加xsa檔案

在彈出的視窗中選擇design_1_wrapper.xsa檔案,如下圖所示:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.4.5 選擇design_1_wrapper.xsa檔案

添加xsa檔案後的頁面如下圖所示,點選next:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖19.4.6 添加完成xsa檔案

在彈出的頁面中有一個Generate boot components選項,如果勾選,軟體會自動生成fsbl工程,這裡我們選擇預設勾選,然後點選next,如下圖所示:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖19.4.7 預設生成fsbl工程

在彈出的工程模闆選擇頁面裡,我們選擇已有的FreeRTOS Hello World模闆,然後點選Finish,如下圖所示:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.4.8 選擇Hello World模闆

工程建立完成後的頁面如下圖所示,我看可以看到生成了兩個工程,一個是硬體平台工程,即platform工程,一個是應用工程。

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖19.4.9 工程建立完成

輕按兩下打開FreeRtos_hello_world/src工程目錄下freertos_hello_world.c檔案,可以看到官方提供的源代碼,本實驗代碼是在官方源代碼上添加了ps與pl端led燈閃爍的任務,本實驗代碼如下:

1   /* FreeRTOS includes. */
2   #include "FreeRTOS.h"
3   #include "task.h"
4   #include "queue.h"
5   #include "timers.h"
6   /* Xilinx includes. */
7   #include "xil_printf.h"
8   #include "xparameters.h"
9   #include "xgpiops.h"
10  #include "xgpio.h"
11  
12  #define TIMER_ID    1
13  #define DELAY_60_SECONDS    60000UL   //定義1分鐘連續中斷時間
14  #define DELAY_1_SECOND      1000UL    //定義1s的發送時間
15  #define TIMER_CHECK_THRESHOLD   59    //連續中斷的次數
16  
17  #define DELAY_100_MSECOND   100UL     //PL端閃爍間隔為100ms
18  #define DELAY_500_MSECOND   500UL     //PS端閃爍間隔為500ms
19  
20  //PS端  GPIO 器件(LED)      ID
21  #define MIO_0_ID XPAR_PSU_GPIO_0_DEVICE_ID
22  //PL端  AXI GPIO 器件(LED)  ID
23  #define LED_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID
24  
25  #define GPIO_OUTPUT     1
26  XGpioPs GPIO_PTR ;
27  
28  #define LED_CHANNEL 1
29  XGpio Gpio_led ;
30  
31  unsigned int PsLedVal = 0 ;    //ps_led顯示狀态0:滅 1:亮
32  unsigned int PlLedVal = 0x0 ;  //pl_led顯示狀态
33  /*----------------------------------------------------------*/
34  
35  /*  Tx 和 Rx 任務。 */
36  static void prvTxTask( void *pvParameters );
37  static void prvRxTask( void *pvParameters );
38  static void vTimerCallback( TimerHandle_t pxTimer );
39  
40  /* PS/PL端LED燈閃爍任務 */
41  static void prvPsLedTask( void *pvParameters );
42  static void prvPlLedTask( void *pvParameters );
43  /*-----------------------------------------------------------*/
44  
45  /*-----------------------------------------------------------*/
46  void PsGpioSetup() ;
47  void PlGpioSetup() ;
48  
49  /* Tx 和 Rx 任務使用的隊列 */
50  static TaskHandle_t xTxTask;
51  static TaskHandle_t xRxTask;
52  static QueueHandle_t xQueue = NULL;
53  static TimerHandle_t xTimer = NULL;
54  char HWstring[15] = "Hello World";
55  long RxtaskCntr = 0;
           

在沒有作業系統的時候兩個應用程式進行消息傳遞一般使用全局變量的方式,但是如果在使用作業系統的應用中用全局變量來傳遞消息就會涉及到“資源管理”的問題。FreeRTOS對此提供了一個叫做“隊列”的機制來完成任務與任務、任務與中斷之間的消息傳遞。50行到55行代碼是定義Tx和Rx任務使用的隊列。

代碼的81行到83行使用xQueueCreate函數建立了隊列,該對列中隻有一個空間,隊列中的每個空間都足以容納一個uint32_t。xQueueCreate本質上是一個宏,用來動态建立隊列,此宏最終調用的是函數xQueueGenericCreate(),函數原型如下:#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )

#endif

由該函數的功能可知,該函數是通過隊列空間的個數與隊列的空間大小來創造隊列。

57  int main( void )
58  {
59      const TickType_t x60seconds 
60      = pdMS_TO_TICKS( DELAY_60_SECONDS );
61      xil_printf( "Hello from Freertos example main\r\n" );
62      PsGpioSetup() ;
63      PlGpioSetup() ;
64  
65      xTaskCreate(    prvTxTask,                    /* 實作任務的函數。*/
66                      /* 任務的文本名稱,僅用于協助調試。 */
67                      ( const char * ) "Tx",        
68                      configMINIMAL_STACK_SIZE,   /* 配置設定給任務的堆棧。*/
69                      /* 未使用任務參數,是以設定為 NULL。 */
70                      NULL,                         
71                      tskIDLE_PRIORITY,       /* 任務以空閑優先級運作。*/
72                      &xTxTask );
73  
74      xTaskCreate(    prvRxTask,
75                      ( const char * ) "GB",
76                      configMINIMAL_STACK_SIZE,
77                      NULL,
78                      tskIDLE_PRIORITY + 1,
79                      &xRxTask );
80  
81      xQueue = xQueueCreate(1,         /* 隊列中隻有一個空間。*/
82      /* 隊列中的每個空間都足以容納一個 uint32_t。*/
83                            sizeof( HWstring ) );    
84  
85      /* 檢查隊列是否已建立。 */
86      configASSERT( xQueue );
87  
88      xTimer = xTimerCreate( (const char *) "Timer",
89                              x60seconds,
90                              pdFALSE,
91                              (void *) TIMER_ID,
92                              vTimerCallback);
93  
94      xTaskCreate( prvPsLedTask,
95                  ( const char * ) "Ps Led",
96                  configMINIMAL_STACK_SIZE,
97                  NULL,
98                  tskIDLE_PRIORITY + 1,
99                  NULL);
100 
101     xTaskCreate( prvPlLedTask,
102                 ( const char * ) "PL Led",
103                 configMINIMAL_STACK_SIZE,
104                 NULL,
105                 tskIDLE_PRIORITY + 1,
106                 NULL);
107
108     /* 檢查計時器是否已建立。 */
109     configASSERT( xTimer );
110 
111     /* 以 0 滴答的塊時間啟動計時器。這意味着開發闆一旦開始上電運作,
112     計時器将開始運作并在60秒後到期 */
113     xTimerStart( xTimer, 0 );
114 
115     /* 啟動任務并運作計時器。 */
116     vTaskStartScheduler();
117 
118     for( ;; );
119 }
           

59行到60行代碼,延時60s,但是函數xTimerCreate ()的89行代碼參數(x60seconds)需要設定的是延時的節拍數,不能直接設定延時時間,是以使用函數pdMS_TO_TICKS将時間轉換為節拍數。88行到92行代碼是建立了建立一個計時器到期時間為60秒的計時器,計時器将在60秒後到期,并且将調用計時器回調。在計時器回調中進行檢查以確定任務在此之前一直正常運作,任務在計時器回調中被删除,并列印一條消息以傳達示例已成功運作。計時器到期時間設定為60秒,計時器設定為不自動重新加載。

在使用FreeRTOS的過程中,我們要使用函數xTaskCreate()來建立任務。FreeRTOS官方給出的任務函數模闆如下:

BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,
							const char * const pcName,		
							const configSTACK_DEPTH_TYPE usStackDepth,
							void * const pvParameters,
							UBaseType_t uxPriority,
							TaskHandle_t * const pxCreatedTask )
           

(1)、這個函數的第一個參數pxTaskCode,就是這個任務的任務函數。什麼是任務函數?任務函數就是完成本任務工作的函數。我這個任務要幹嘛?要做什麼?要完成什麼樣的功能都是在這個任務函數中實作的。任務函數本質也是函數,是以肯定有任務名什麼的,不過這裡我們要注意:任務函數的傳回類型一定要為void類型,也就是無傳回值,而且任務的參數也是void指針類型的!任務函數名可以根據實際情況定義。

(2)、這個函數的第二個參數是pcName,任務的文本名稱,僅用于協助調試。

(3)、這個函數的第三個參數是configSTACK_DEPTH_TYPE usStackDepth,配置設定給任務的堆棧。

(4)、這個函數的第四個參數是pvParameters,我們本實驗裡未使用任務參數,是以設定為NULL。

(5)、這個函數的第五個參數是uxPriority,該參數是定義任務運作優先級。

(6)、這個函數的第六個參數是pxCreatedTask,是任務使用的隊列。

65行到72行代碼使用函數xTaskCreate()來建立一個發送任務(prvTxTask())的函數,74行到79行代碼使用函數xTaskCreate()來建立一個接收任務(prvRxTask())的函數,94行到99行代碼使用函數xTaskCreate()來建立一個使ps端的LED閃爍任務的函數,閃爍間隔為500ms,101行到106行代碼使用函數xTaskCreate()來建立一個使pl端的LED閃爍任務的函數,閃爍間隔為100ms。

代碼的86行代碼中的函數是斷言(configASSERT),86行代碼斷言函數是檢測隊列是否已建立,109行代碼的斷言函數是檢查計時器是否已建立。斷言(configASSERT)類似C标準庫中的assert()函數,調試代碼的時候可以檢查傳入的參數是否合理,FreeRTOS核心中的關鍵點都會調用configASSERT(x),當x為0的時候說明有錯誤發生,使用斷言的話會導緻開銷加大,一般在調試階段使用。configASSERT()需要在FreeRTOSConfig.h檔案中定義,如下執行個體:

#define configASSERT( x ) if( ( x ) == 0 ) vApplicationAssert( FILE, LINE )

當參數x錯誤的時候就通過序列槽列印出發生錯誤的檔案名和錯誤所在的行号,調試代碼的可以使用斷言,當調試完成以後盡量去掉斷言,防止增加開銷!

113行代碼使用的函數xTimerStart開啟軟體定時器。如果軟體定時器停止運作的話可以使用FreeRTOS提供的兩個開啟函數來重新啟動軟體定時器,xTimerStart()開啟軟體定時器,用于任務中,xTimerStartFromISR()開啟軟體定時器,用于中斷中。

116行代碼使用開啟任務排程函數(vTaskStartScheduler),這個函數的功能就是開啟任務排程器的,這個函數在檔案tasks.c中有定義。調用函數xPortStartScheduler()來初始化跟排程器啟動有關的硬體,比如滴答定時器、FPU單元和PendSV中斷等等。

121 /*-----------------------------------------------------------*/
122 static void prvTxTask( void *pvParameters )
123 {
124 const TickType_t x1second = pdMS_TO_TICKS( DELAY_1_SECOND );
125 
126     for( ;; )
127     {
128         /* 延遲 1 秒。*/
129         vTaskDelay( x1second );
130 
131         /*發送隊列中的下一個值。
132         此時隊列應始終為空,是以使用的阻塞時間為0。*/
133         xQueueSend( xQueue,         /* 正在寫入的隊列。 */
134                     HWstring,       /* 正在發送的資料的位址。 */
135                     0UL );          /* 阻塞時間。 */
136     }
137 }
           

122行到137行是建立的發送任務函數,本章的發送任務每隔一秒發送一條資訊,是以在第14行代碼定義了該延遲時間,延時1s,但是函數vTaskDelay()的129行代碼參數(x1second)需要設定的是延時的節拍數,不能直接設定延時時間,是以124行代碼使用函數pdMS_TO_TICKS将時間轉換為節拍數。

129行代碼調用的函數vTaskDelay是FreeRTOS的延時函數,此處不一定要用延時函數,其他隻要能讓FreeRTOS發生任務切換的API函數都可以,比如請求信号量、隊列等,甚至直接調用任務排程器。隻不過最常用的就是FreeRTOS的延時函數。在FreeRTOS中延時函數有相對模式和絕對模式,在FreeRTOS中不同的模式用的函數不同,其中函數vTaskDelay()是相對模式(相對延時函數),函數vTaskDelayUntil()是絕對模式(絕對延時函數)。函數vTaskDelay()在檔案tasks.c中有定義,要使用此函數的話宏INCLUDE_vTaskDelay必須為1。需要注意的是延時時間函數的延時時間是時間的節拍數,延時時間肯定要大于0。

133行到135行代碼使用的函數xQueueSend用于向隊列中發送消息的,即将“Hello World”字元串寫入隊列。這個函數的本質是宏,函數xQueueSend()是後向入隊,即将新的消息插入到隊列的後面。這個函數最後調用的是函數:xQueueGenericSend()。這三個函數隻能用于任務函數中,不能用于中斷服務函數,中斷服務函數有專用的函數,它們以“FromISR”結尾,這個函數的原型如下:

#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )

參數:

xQueue:隊列句柄,指明要向哪個隊列發送資料,建立隊列成功以後會傳回此隊列的隊列句柄。

pvItemToQueue:指向要發送的消息,發送時候會将這個消息拷貝到隊列中。xTicksToWait:阻塞時間,此參數訓示當隊列滿的時候任務進入阻塞态等待隊列空閑的最大時間。如果為0的話當隊列滿的時候就立即傳回;當為portMAX_DELAY的話就會一直等待,直到隊列有空閑的隊列項,也就是死等,但是宏INCLUDE_vTaskSuspend必須為1。

傳回值:

pdPASS:向隊列發送消息成功!

errQUEUE_FULL:隊列已經滿了,消息發送失敗。

126行到135行代碼是說明任務的具體執行過程是一個大循環,for(; ; )就代表一個循環,作用和while(1)一樣。循環裡面就是真正的任務代碼了,主要說明此任務具體要幹的活就在這裡實作!任務函數一般不允許跳出循環,如果一定要跳出循環的話在跳出循環以後一定要調用函數vTaskDelete(NULL)删除此任務!

139 /*---------------------------------------------------------------*/
140 static void prvRxTask( void *pvParameters )
141 {
142 char Recdstring[15] = "";
143     for( ;; )
144     {
145         /* 阻塞等待資料到達隊列。 */
146         xQueueReceive(  xQueue,         /*正在讀取的隊列。  */
147                         Recdstring,     /*資料被讀入該位址。*/
148                         portMAX_DELAY );/*等待資料沒有逾時。*/
149 
150         /* 列印接收到的資料。 */
151     xil_printf( "Rx task received string 
152     from Tx task: %s\r\n", Recdstring );
153     xil_printf( "The number of RX task
154     executions is %d\r\n", RxtaskCntr );
155         RxtaskCntr++;
156     }
157 }
           

140行到157行是建立的接收任務函數,本章的接收任務是從隊列中讀取一條(請求)消息。從142行代碼定義的輸入接收任務函數具體執行過程個也是一個大循環,for(; ; )就代表一個循環。

146行到156行代碼使用的是隊列接收函數(xQueueReceive)讀取隊列中的“Hello World”字元串,此函數用于在任務中從隊列中讀取一條(請求)消息,讀取成功以後就會将隊列中的這條資料删除,此函數的本質是一個宏,真正執行的函數是xQueueGenericReceive()。此函數在讀取消息的時候是采用拷貝方式的,是以使用者需要提供一個數組或緩沖區來儲存讀取到的資料,所讀取的資料長度是建立隊列的時候所設定的每個隊列項目的長度,函數原型如下:

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

參數:

xQueue:隊列句柄,指明要讀取哪個隊列的資料,建立隊列成功以後會傳回此隊列的隊列句柄。

pvBuffer:儲存資料的緩沖區,讀取隊列的過程中會将讀取到的資料拷貝到這個緩沖區中。

xTicksToWait:阻塞時間,此參數訓示當隊列空的時候任務進入阻塞态等待隊列有資料的最大時間。如果為0的話當隊列空的時候就立即傳回;當為portMAX_DELAY的話就會一直等待,直到隊列有資料,也就是死等,但是宏INCLUDE_vTaskSuspend必須為1。

傳回值:

pdTRUE:從隊列中讀取資料成功。

pdFALSE:從隊列中讀取資料失敗。

151行到155行代碼是列印從隊列裡讀取的“Hello World”字元串與每次列印的編号(RxtaskCntr)。

159 /*-----------------------------------------------------------*/
160 static void vTimerCallback( TimerHandle_t pxTimer )
161 {
162     long lTimerId;
163     configASSERT( pxTimer );
164 
165     lTimerId = ( long ) pvTimerGetTimerID( pxTimer );
166 
167     if (lTimerId != TIMER_ID) {
168         xil_printf("FreeRTOS Hello World Example FAILED");
169     }
170 
171     if (RxtaskCntr >= TIMER_CHECK_THRESHOLD) {
172         xil_printf("FreeRTOS Hello World Example PASSED");
173     } else {
174         xil_printf("FreeRTOS Hello World Example FAILED");
175     }
176     vTaskDelete( xRxTask );
177     vTaskDelete( xTxTask );
178 }
           

vTimerCallback任務是一個計時60s的定時器,33行代碼是定義的定時器的ID(TIMER_ID),163行代碼檢測定時器是否已經被建立,165行代碼通過pvTimerGetTimerID()函數擷取建立定時器的ID(lTimerId),167行到169行代碼将擷取的建立的定時器的ID(lTimerId)與33定義的定時器的ID(TIMER_ID)進行對比,如果兩者不一緻則會列印"FreeRTOS Hello World Example FAILED"語句,結束接收與發送任務,本次實驗失敗,結束本此次實驗。如果兩個ID一緻則不列印"FreeRTOS Hello World Example FAILED"語句,定時器将正常工作,開始計時,函數會進行171行到175行代碼描述的判斷語句,171行代碼是将發送任務發送次數的計數值(RxtaskCntr)與定時器到期時的值(TIMER_CHECK_THRESHOLD)進行比較,發送任務發送次數的計數值(RxtaskCntr)超過定時器到期時的值(TIMER_CHECK_THRESHOLD)後,會列印"FreeRTOS Hello World Example PASSED"語句,并結束接收與發送任務,本次實驗成功,結束本此次實驗,否則列印"FreeRTOS Hello World Example FAILED",本次實驗失敗。

vTaskDelete()函數可以删除一個用函數xTaskCreate()或者xTaskCreateStatic()建立的任務,176行代碼與177行代碼就是删除了xTaskCreate()建立的接收任務(xRxTask)與發送任務(xTxTask),被删除了的任務不再存在,也就是說再也不會進入運作态。任務被删除以後就不能再使用此任務的句柄!如果此任務是使用動态方法建立的,也就是使用函數xTaskCreate()建立的,那麼在此任務被删除以後此任務之前申請的堆棧和控制塊記憶體會在空閑任務中被釋放掉,是以當調用函數vTaskDelete()删除任務以後必須給空閑任務一定的運作時間。隻有那些由核心配置設定給任務的記憶體才會在任務被删除以後自動的釋放掉,使用者配置設定給任務的記憶體需要使用者自行釋放掉,比如某個任務中使用者調用函數pvPortMalloc()配置設定了500位元組的記憶體,那麼在此任務被删除以後使用者也必須調用函數vPortFree()将這500位元組的記憶體釋放掉,否則會導緻記憶體洩露。此函數原型如下:vTaskDelete( TaskHandle_t xTaskToDelete )。如果要使用函數vTaskDelete()的話需要将宏INCLUDE_vTaskDelete定義為1。

180 void PsGpioSetup()
181 {
182     int Status ;
183     XGpioPs_Config *GpioCfg ;
184     GpioCfg = XGpioPs_LookupConfig(MIO_0_ID) ;
185     Status = XGpioPs_CfgInitialize(&GPIO_PTR, 
186     GpioCfg, GpioCfg->BaseAddr) ;
187     if (Status != XST_SUCCESS)
188     {
189         xil_printf("PS GPIO Configuration failed!\r\n") ;
190     }
191     /* 設定 MIO 38 作為輸出 */
192     XGpioPs_SetDirectionPin(&GPIO_PTR, 38, GPIO_OUTPUT) ;
193     /* 啟用 MIO 38 輸出 */
194     XGpioPs_SetOutputEnablePin(&GPIO_PTR, 38, GPIO_OUTPUT) ;
195 }
           

PsGpioSetup()函數的作用是設定初始化ps端的GPIO端口,184行代碼配置PS端GPIO端口器件(ps端LED)ID,185行代碼對PS端GPIO端口進行初始化,187行到190行代碼是判斷PS端GPIO端口初始化是否成功,如果沒有成功,會列印"PS GPIO Configuration failed!\r\n"語句。192行代碼使用XGpioPs_SetDirectionPin()函數設定PS端LED的GPIO端口号為38,并進行初始指派。194行代碼是使用XGpioPs_SetOutputEnablePin()函數使能PS端LED的GPIO端口輸出。

197 /*-----------------------------------------------------------*/
198 static void prvPsLedTask( void *pvParameters )
199 {
200     const TickType_t x1second 
201     = pdMS_TO_TICKS( DELAY_500_MSECOND );
202         for( ;; )
203         {
204             XGpioPs_WritePin(&GPIO_PTR, 38, PsLedVal) ;
205             PsLedVal = ~PsLedVal ;
206             /* 延遲 1秒。 */
207             vTaskDelay( x1second );
208         }
209 }
           

prvPsLedTask()任務函數的功能是使ps端LED燈以500ms的間隔閃爍。200行到201行代碼是使用函數pdMS_TO_TICKS将時間延時500ms轉換為節拍數。202到208行的for( ;; )裡就是實作任務函數的功能代碼,204行代碼通過XGpioPs_WritePin()函數将PS端LED燈的值寫入配置好的GPIO端口。205行代碼的功能是PS端LED燈的值每隔500ms進行一次翻轉。

211 void PlGpioSetup()
212 {
213     int Status ;
214     /* 初始 GPIO 引導  */
215     Status = XGpio_Initialize(&Gpio_led, LED_DEVICE_ID) ;
216     if (Status != XST_SUCCESS)
217         xil_printf("PL GPIO Configuration failed!\r\n") ;
218     /* 将 led 設定為輸出  */
219     XGpio_SetDataDirection(&Gpio_led, LED_CHANNEL, 0x0);
220 
221 }
           

PlGpioSetup()函數的作用是設定初始化pl端的GPIO端口。215行代碼是使用XGpio_Initialize()函數對pl端的GPIO_LED端口進行初始化配置,216到217行代碼的功能是對初始化設定結果的判斷,初始化結果失敗會列印"PL GPIO Configuration failed! "語句。219行代碼是使用XGpio_SetDataDirection()函數配置pl端LED燈使用的GPIO通道與初始值。

223 static void prvPlLedTask( void *pvParameters )
224 {
225     const TickType_t x1second 
226     = pdMS_TO_TICKS( DELAY_100_MSECOND );
227         for( ;; )
228         {
229             XGpio_DiscreteWrite(&Gpio_led, LED_CHANNEL, PlLedVal);
230             PlLedVal = ~PlLedVal ;
231             /* 延遲 1 秒。*/
232             vTaskDelay( x1second );
233         }
234 }
           

prvPlLedTask()任務函數的功能是使pl端LED燈以100ms的間隔閃爍。225行到226行代碼是使用函數pdMS_TO_TICKS将時間延時100ms轉換為節拍數。227到233行的for( ;; )裡就是實作任務函數的功能代碼,229行代碼通過XGpio_DiscreteWrite ()函數将PL端LED燈的值(PlLedVal)寫入配置好的GPIO端口。230行代碼的功能是PL端LED燈的值每隔500ms進行一次翻轉。至此,建立的任務函數基本運作完成,代碼編寫完成,接下是要對代碼進行編譯。本章實驗隻是簡單介紹了工程中使用到的一些函數,如果需要靈活使用FreeRtos實時作業系統的可以系統學習原子官方編寫的FreeRtos開發手冊,在開源電子網資料下載下傳可以獲得。

選中應用工程,右鍵Build Project對工程進行編譯。

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖19.4.10 編譯工程

編譯進度可以在工具下方的控制台面闆(Console)中進行檢視,編譯完成後顯示“Finished building:FreeRtos_hello_world.elf”,如下圖所示:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖19.4.11 編譯完成

到這裡我們已經完成了本次實驗的軟體設計部分。

19.5下載下傳驗證

首先我們将下載下傳器與MPSOC開發闆上的JTAG接口連接配接,下載下傳器另外一端與電腦連接配接。然後使用USB連接配接線将開發闆USB_UART (PS_PORT)接口與電腦連接配接,用于序列槽通信。接下來将開發闆上四個啟動模式開關均置為ON,即設定為JTAG模式。最後連接配接開發闆電源給開發闆上電。如下圖所示:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.5.1 MPSOC開發闆實物圖

使用UART序列槽列印資訊對于序列槽的設定可以直接參考“Hello Word”中對序列槽的設定。

下載下傳程式,右鍵點選FreeRtos_hello_world工程,選擇“Run As”,然後選擇最後一項“Run Confagurations…”,如下圖所示:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.5.2打開下載下傳頁面

在打開的下載下傳頁面中,沒有出現下載下傳選項,這時需要輕按兩下左側清單中Single Application Debug一項,輕按兩下後,該項下面出現新的項Debugger_FreeRtos_hello_world-Default,同時在右側出現的頁面中選擇Target Setup标簽頁,勾選複位,然後點選run下載下傳程式,如下圖所示:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.5.3下載下傳程式

下載下傳完成後,MPSOC PS端的序列槽會列印應用工程中列印函數裡面的字元串。在Terminal視窗可以看到上位機接收到的字元串,如下圖所示:

【正點原子FPGA連載】第十九章FreeRtos Hello World實驗 摘自【正點原子】DFZU2EG_4EV MPSoC之嵌入式Vitis開發指南第十九章FreeRtos Hello World實驗

圖 19.5.4程式運作結果

從序列槽列印資訊可知,定時60s,成功列印59次“Hello World”。最後程式成功列印出了“FreeRtos Hello World Example PASSED”字元串,說明本次實驗在MPSOC開發闆上面下載下傳驗證成功。

繼續閱讀