文章目錄
寫在前面
準備移植
開始移植
驗證
最后一點
點擊下方閱讀原文可訪問文中超鏈接
寫在前面
ThreadX 是由 Express Logic 公司開發的實時操作系統。目前已被微軟收購,并且前不久開源了,當開源的時候很多論壇都第一時間發布了相關文章,可見其影響力還是很不錯的,剛好最近有一個新項目,需要用到網絡協議棧,而threadx有自己的網絡協議棧組件,之前打算用freeRTOS加LWIP的方式,現在直接用threadx加netx的方式。
首先到github上下載threadx的源碼,建議使用git,下載zip的話非常慢,而且容易失敗,點擊鏈接threadx下載。
準備移植
下載好源碼后就準備開始移植了,我以stm32f407為例,keil版本5.31,threax版本6.0.1,其它系列都差不多,最近的一次更新增加了非常多的器件支持。看下源碼目錄:
這次是移植,主要關心ports文件夾就行了,里面是針對各種內核寫的移植文件,我這里對應的是cortex_m4這個文件夾,打開后里面有四個目錄:
我用的是keil,但是我選擇的是ac5這個文件夾,因為我用的也是ac5編譯器,我對比了一下keil文件夾和ac5文件夾里面的內容不一樣的地方只有一個,這個后面再說。
開始移植
首先搭建一個簡單的裸機工程,然后添加threadx的源碼及移植文件,添加完后如下:
我們主要修改tx_initialize_low_level.s文件,其它幾個文件都是可以不用動的,這個文件和st提供的啟動文件沖突了,但是這個文件內容又不全。采用的方法類似freeRTOS的移植方法,不動st的啟動文件。
首先更改時鐘頻率及給滴答定時器設置的重裝載值
SYSTEM_CLOCK EQU 168000000
SYSTICK_CYCLES EQU ((SYSTEM_CLOCK / 1000) -1)
刪掉堆棧大小定義代碼段(沖突)
刪掉向量表的定義(沖突)
刪掉Reset_Handler代碼段(沖突)
刪掉__user_initial_stackheap代碼段(沖突)
刪掉__tx_BadHandler代碼段(沒用)
刪掉__tx_SVCallHandler代碼段(沒用)
刪掉__tx_IntHandler代碼段(沒用)
刪掉__tx_NMIHandler代碼段(沒用)
刪掉__tx_DBGHandler代碼段(沒用)
編譯一下,報了兩個錯說是__tx_vectors未定義
這個是向量表的基地址,在st提供的啟動文件中已經定義了,改成啟動文件中的向量表__Vectors,注意要先在前面導入這個符號。
IMPORT __Vectors
再次編譯提示PendSV_Handler重復定義,但是卻未提示SysTick_Handler重復定義,從以往移植其它rtos的經驗來看,基本上都是使用滴答定時器作為系統心跳,我們暫且繼續往下看
屏蔽掉stm32f4xx_it.c文件中的PendSV_Handler函數,threadx需要用來進行上下文切換。
再次編譯提示tx_application_define未定義,這個是在threadx系統啟動的時候會自動調用,我們如果需要創建任務或者信號量等資源的話都是在這個函數里面完成,參考實現可仿照sample_threadx.c文件。
在main函數中添加頭文件并且添加此函數的實體
#include "tx_api.h"
VOID tx_application_define(VOID *first_unused_memory)
{
/*暫時留空*/
}
這個問題解決后再次編譯又報了幾個錯,也是未定義,猜測這幾個符號是編譯完代碼后在某個中間文件生成的,在鏈接的時候這里再獲取其值,因為keil編譯完程序生成的map文件有類似的符號。
經過搜索,發現只有ImageZILimit有用(這里$顯示不出來),其它都沒有使用,從官方提供的其它移植的例程得知這里是用戶可用內存(運行環境需要的除外,比如C運行環境的堆棧)的起始地址,設置為__initial_sp,同樣的需要在前面IMPORT這個符號,__initial_sp有點特殊,需要勾選keil選項中的Use MicroLIB選項,不然的話提示找不到這個符號的定義,這是從啟動文件得知的。
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
...
現在已經編譯成功了,但是你會發現系統跑不起來,這就是前面為什么說SysTick_Handler這個函數沒有報重復定義的錯,因為系統的心跳還沒有提供,看下PendSV_Handler是怎么實現的。
; /* Generic context switching PendSV handler. */
;
EXPORT __tx_PendSVHandler
EXPORT PendSV_Handler
__tx_PendSVHandler
PendSV_Handler
這里將兩個標號寫在一起,也就是相當于一個函數的兩個名字,實際上是同一個東西,模仿一下,添加SysTick_Handler,然后屏蔽掉stm32f4xx_it.c文件中的SysTick_Handler函數。
EXPORT __tx_SysTickHandler
EXPORT SysTick_Handler
__tx_SysTickHandler
SysTick_Handler
編譯成功,現在移植已經就大功告成了,添加兩個線程測試下(參考sample_threadx.c文件)
void thread_0_entry(ULONG thread_input)
{
/* This thread simply sits in while-forever-sleep loop. */
while(1)
{
PRINTF("thread 0 is running..\r\n");
tx_thread_sleep(1000);
}
}
void thread_1_entry(ULONG thread_input)
{
/* This thread simply sends messages to a queue shared by thread 2. */
while(1)
{
PRINTF("thread 1 is running..\r\n");
tx_thread_sleep(500);
}
}
測試結果,目前運行良好,暫時還不知道有沒有其它問題
驗證
雖然系統移植好了,但是怎么才能驗證呢,比如我這里延時的時間是準確的嗎?我們可以將HAL_GetTick這個函數移植進來,然后測試下時間是否準確,當然也有其它方式。
將HAL庫里面stm32f4xx_hal_timebase_tim_template.c文件拷貝出來,重命名為stm32f4xx_hal_timebase_tim.c,然后添加到工程中,本來裸機的時候使用滴答定時器來實現HAL_GetTick函數的功能,但是現在滴答定時器被用了,st提供了這個文件使用timer6來替代。注意HAL庫F4的庫版本1.25.0有個BUG在這里,我們還需要修改下stm32f4xx_hal_timebase_tim.c文件HAL_InitTick函數,否則會出現斷言失敗(如果開啟了斷言檢測),因為這個函數在HAL_Init被調用了一次,傳入的參數是TICK_INT_PRIORITY(0x0F),然后在SystemClock_Config函數又調用了一下,這次調用的時候入參是一個全局變量,而在老版本的庫中,這里入參還是TICK_INT_PRIORITY,就是因為這里入參現在傳了個全局變量才會導致斷言失敗,并且優先級設置的是個錯誤的值,修改如下:
/*略*/
HAL_NVIC_SetPriority(TIM6_DAC_IRQn, TickPriority ,0U);
/*新版BUG,這里參考stm32f4xx_hal.c文件的HAL_InitTick函數*/
uwTickPrio = TickPriority;
/* Enable the TIM6 global Interrupt */
HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
/*略*/
但是測試結果卻出乎意料
我明明延時的1秒,但是出來的兩個值間隔時間根本就不是1秒,最開始懷疑系統移植哪里有錯誤,無奈只好看ports里面那幾個匯編文件,看來看去也沒有發現問題,各種測試都是一樣的結果,最后嘗試更改stm32f4xx_hal_timebase_tim.c文件的函數HAL_InitTick傳入的優先級參數,試了一下,時間準確了,多次測試發現,只要優先級設置的不是0x0F就正確,又在tx_initialize_low_level.s文件的_tx_initialize_low_level代碼段看到里面將PnSV和SVC設置的0x0F優先級,所以猜測這個中斷優先級被threadx用了后,其它地方就不能用了,這個和以前用的uCOS和freeRTOS還有所不同。再次修改代碼。
/*略*/
HAL_NVIC_SetPriority(TIM6_DAC_IRQn, TickPriority ,0U);
/*0x0f優先級已被threadx系統使用*/
TickPriority -= 1;
/*新版BUG,這里參考stm32f4xx_hal.c文件的HAL_InitTick函數*/
uwTickPrio = TickPriority;
/* Enable the TIM6 global Interrupt */
HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
/*略*/
這次修改后,正確了
最后一點
前面有一段說了ports文件夾下面的ac5和keil移植文件就只有一個區別,如下
這里是出棧操作,按理說正確的匯編代碼應該是右邊的寫法,但是左邊的寫法編譯時居然沒報錯,而且運行也是正常的,兩種寫法我都測試了,都可以正常穩定運行,暫時不知道具體原因,只有看官方之后的更新到底是怎樣的,可能這里本身就是個BUG,也可能左邊寫法有其它含義。
文章的整個測試源碼可以在我的碼云倉庫獲取。源碼點這兒
歡迎掃碼關注我的微信公眾號