現在一些小型系統中也往往有多任務處理的需求,這就為實時操作系統提供了用武之地。事實上國內外各種各樣的RTOS有很多,而且基本都在走開源的路線,ThreadX也不例外,在這一篇中我們就來學習ThreadX初步應用並將其移植到STM32平台中。
1、前期准備
在開始將ThreadX一直到STM32平台之間我們需要做一些前期准備。首先我們需要准備一個硬件平台,這次我們采用STM32F407IG控制單元來作為目標平台。其次我們需要准備一個該硬件平台下可以正常運行的裸機項目。這兩點其實我們都已經具備了。
最主要的我們需要獲得ThreadX的源碼,這是我們移植它的基礎。ThreadX的源碼已經開源到Github上,其地址為:https://github.com/azure-rtos/threadx,直接下載源碼就可以了。目前發布的最新版本是6.0.1,在我們的移植中我們使用6.0.0的版本來實現。
2、系統移植
首先我們先來了解一下獲得的ThreadX源碼。解壓下載下來的壓縮包,其包含有以下文件及文件夾,我們先來具體看一看都有哪些文件,如下圖:
上圖中一目了然,無需做太多解釋。我們需要用到的文件主要存放在common文件夾和ports文件夾。其中common文件夾存放的是內核源碼,ports文件夾存放的是不同平台的接口文件。我們的硬件采用的是STM32F407IG,軟件開發環境用的是IAR EWARM,所以我們選擇ports文件夾下cortex_m4下的IAR文件夾中的接口文件。
接下來我們需要在項目中添加ThreadX的相關源碼文件。所以我們在項目下添加ThreadX組、並在ThreadX組下添加Source和Ports兩個組用於添加文件。並將common文件夾和ports文件夾中的文件添加到對應的分組。如下所示:
然后要在項目屬性中為編譯器指定頭文件的引用路徑,主要是內核函數的頭文件以及接口文件的頭文件兩個路徑,在我們這個項目中配置如下:
$PROJ_DIR$\..\..\ThreadX\common\inc
$PROJ_DIR$\..\..\ThreadX\ports\cortex_m4\iar\inc
事實上到這了,我們已經完成了對ThreadX內核文件以及接口文件的移植,但現在ThreadX不會運行,我們還需要做一些工作。我們要將內核與主函數聯系起來,首先我們要在調用內核的地方添加頭文件“tx_api.h”,我們這里將其添加到主函數文件中。
然后有兩個函數我們需要處理,分別是:tx_kernel_enter和tx_application_define,這兩個函數在頭文件“tx_api.h”中被聲明。tx_kernel_enter實際是一個宏,真正的函數是_tx_initialize_kernel_enter,用於啟動內核,這個函數需要我們在主函數中調用。而tx_application_define函數只有聲明沒有實現,在_tx_initialize_kernel_enter函數中被調用,用於任務的創建。這個函數的實現是我們的主要工作,后續將詳細說明。
3、任務實現
我們已經說過了tx_application_define用於任務的創建,它的具體內容需要我們來實現,接下來我們就來實現tx_application_define這個函數。
我們先來規划一下我們將要實現的內容。我們計划實現5個任務,包括啟動任務、紅燈閃爍任務、綠燈閃爍任務、空閑任務及統計任務。其中為啟動任務用於初始化一些配置並執行一些如系統心跳、看門狗之類的工作;用於紅燈閃爍任務和綠燈閃爍任務用於實現我們要操作的指示燈控制;空閑任務在其他任務不運行時其運行,優先級最低。統計任務再次我們實現系統空閑率的統計。接下來我們就按此思路來實現之。
1 /*tx_application_define函數實現*/ 2 void tx_application_define(void *first_unused_memory) 3 { 4 /**************創建啟動任務*********************/ 5 tx_thread_create(&AppTaskStartTCB, /* 任務控制塊地址 */ 6 "App Task Start", /* 任務名 */ 7 AppTaskStart, /* 啟動任務函數地址 */ 8 0, /* 傳遞給任務的參數 */ 9 &AppTaskStartStk[0], /* 堆棧基地址 */ 10 APP_CFG_TASK_START_STK_SIZE, /* 堆棧空間大小 */ 11 APP_CFG_TASK_START_PRIO, /* 任務優先級*/ 12 APP_CFG_TASK_START_PRIO, /* 任務搶占閥值 */ 13 TX_NO_TIME_SLICE, /* 不開啟時間片 */ 14 TX_AUTO_START); /* 創建后立即啟動 */ 15 16 /**************創建紅燈閃爍任務*********************/ 17 tx_thread_create(&AppTaskRedLedTCB, /* 任務控制塊地址 */ 18 "App Msp Pro", /* 任務名 */ 19 AppTaskRedLED, /* 啟動任務函數地址 */ 20 0, /* 傳遞給任務的參數 */ 21 &AppTaskMsgProStk[0], /* 堆棧基地址 */ 22 APP_CFG_TASK_RedLED_STK_SIZE, /* 堆棧空間大小 */ 23 APP_CFG_TASK_REDLED_PRIO, /* 任務優先級*/ 24 APP_CFG_TASK_REDLED_PRIO, /* 任務搶占閥值 */ 25 TX_NO_TIME_SLICE, /* 不開啟時間片 */ 26 TX_AUTO_START); /* 創建后立即啟動 */ 27 28 /**************創建綠燈閃爍任務*********************/ 29 tx_thread_create(&AppTaskGreenLEDTCB, /* 任務控制塊地址 */ 30 "App Task UserIF", /* 任務名 */ 31 AppTaskGreenLED, /* 啟動任務函數地址 */ 32 0, /* 傳遞給任務的參數 */ 33 &AppTaskUserIFStk[0], /* 堆棧基地址 */ 34 APP_CFG_TASK_GreenLED_STK_SIZE, /* 堆棧空間大小 */ 35 APP_CFG_TASK_GREENLED_PRIO, /* 任務優先級*/ 36 APP_CFG_TASK_GREENLED_PRIO, /* 任務搶占閥值 */ 37 TX_NO_TIME_SLICE, /* 不開啟時間片 */ 38 TX_AUTO_START); /* 創建后立即啟動 */ 39 40 /**************創建統計任務*********************/ 41 tx_thread_create(&AppTaskStatTCB, /* 任務控制塊地址 */ 42 "App Task STAT", /* 任務名 */ 43 AppTaskStat, /* 啟動任務函數地址 */ 44 0, /* 傳遞給任務的參數 */ 45 &AppTaskStatStk[0], /* 堆棧基地址 */ 46 APP_CFG_TASK_STAT_STK_SIZE, /* 堆棧空間大小 */ 47 APP_CFG_TASK_STAT_PRIO, /* 任務優先級*/ 48 APP_CFG_TASK_STAT_PRIO, /* 任務搶占閥值 */ 49 TX_NO_TIME_SLICE, /* 不開啟時間片 */ 50 TX_AUTO_START); /* 創建后立即啟動 */ 51 52 /**************創建空閑任務*********************/ 53 tx_thread_create(&AppTaskIdleTCB, /* 任務控制塊地址 */ 54 "App Task IDLE", /* 任務名 */ 55 AppTaskIDLE, /* 啟動任務函數地址 */ 56 0, /* 傳遞給任務的參數 */ 57 &AppTaskIdleStk[0], /* 堆棧基地址 */ 58 APP_CFG_TASK_IDLE_STK_SIZE, /* 堆棧空間大小 */ 59 APP_CFG_TASK_IDLE_PRIO, /* 任務優先級*/ 60 APP_CFG_TASK_IDLE_PRIO, /* 任務搶占閥值 */ 61 TX_NO_TIME_SLICE, /* 不開啟時間片 */ 62 TX_AUTO_START); /* 創建后立即啟動 */ 63 }
我們實現了tx_application_define函數,在其中創建了任務,理所當然我們還需要實現相應的任務函數。
1 /*系統啟動任務*/ 2 static void AppTaskStart (ULONG thread_input) 3 { 4 (void)thread_input; 5 6 /* 任務統計前先掛起其它任務 */ 7 tx_thread_suspend(&AppTaskRedLedTCB); 8 tx_thread_suspend(&AppTaskGreenLEDTCB); 9 10 OSStatInit(); 11 12 /* 任務統計完畢后,恢復其它任務 */ 13 tx_thread_resume(&AppTaskRedLedTCB); 14 tx_thread_resume(&AppTaskGreenLEDTCB); 15 16 /* 內核開啟后,恢復HAL里的時間基准 */ 17 HAL_ResumeTick(); 18 19 while (1) 20 { 21 sysHeartBeat++; 22 tx_thread_sleep(1000); 23 } 24 } 25 26 /*紅燈閃爍控制*/ 27 static void AppTaskRedLED(ULONG thread_input) 28 { 29 (void)thread_input; 30 31 while(1) 32 { 33 HAL_GPIO_TogglePin(GPIOI,GPIO_PIN_8); 34 tx_thread_sleep(500); 35 } 36 } 37 38 /*綠燈閃爍控制*/ 39 static void AppTaskGreenLED(ULONG thread_input) 40 { 41 (void)thread_input; 42 43 while(1) 44 { 45 46 HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13); 47 tx_thread_sleep(2000); 48 } 49 } 50 51 /*統計任務函數*/ 52 static void AppTaskStat(ULONG thread_input) 53 { 54 (void)thread_input; 55 56 while (OSStatRdy == false) 57 { 58 tx_thread_sleep(200); /* 等待統計任務就緒 */ 59 } 60 61 OSIdleCtrMax /= 100uL; 62 if (OSIdleCtrMax == 0uL) 63 { 64 OSCPUUsage = 0u; 65 } 66 67 OSIdleCtr = OSIdleCtrMax * 100uL; /* 設置初始CPU利用率 0% */ 68 69 for (;;) 70 { 71 OSIdleCtrRun = OSIdleCtr; /* 獲得100ms內空閑計數 */ 72 OSIdleCtr = 0uL; /* 復位空閑計數 */ 73 OSCPUUsage = (100uL - (float)OSIdleCtrRun / OSIdleCtrMax); 74 tx_thread_sleep(100); /* 每100ms統計一次 */ 75 } 76 } 77 78 /*空閑任務函數*/ 79 static void AppTaskIDLE(ULONG thread_input) 80 { 81 TX_INTERRUPT_SAVE_AREA 82 83 (void)thread_input; 84 85 while(1) 86 { 87 TX_DISABLE 88 OSIdleCtr++; 89 TX_RESTORE 90 } 91 }
實現了上面這些函數后,我們一個基於ThreadX的最基礎的系統就建立起來了,對於更復雜的系統也沒有問題,其實現的基本思路也是與此相同的。
4、最后測試
完成前述的全部內容后,我們編譯下載到目標平台,兩個指示燈按照我們的預期正常閃爍,說明的們的移植是成功的。
事實上ThreadX的移植相對簡單,接下來我們總結一下移植ThreadX的步驟。我們覺得大體可分為如下過程進行:
首先,將ThreadX的文件及引用,包括內核文件和接口文件,添加到我們的項目中,並做好相關的項目配置。
其次,將 tx_api.h 文件包含於所有使用 ThreadX 服務和數據結構的應用程序。如前面我們將其包含在主函數文件中。
然后,在主函數中調用 tx_kernel_enter函數以達到啟動ThreadX內核的目的。如果沒有經過ThreadX特定的初始化,可以通過增加其優先權而進入到內核中。
再其次,建立 tx_application_define 函數。這是初始系統資源創建的地方。這些資源包括線程、隊列、內存緩沖池、事件標志組以及信號。
最后,編譯下載到目標平台測試。