論壇原始地址(持續更新):http://www.armbbs.cn/forum.php?mod=viewthread&tid=99514
第4章 ThreadX操作系統移植(MDK AC5)
本章節將為大家介紹ThreadX內核的MDK AC5方式移植和設計框架,理論上不建議初學者直接學習,因為本章節涉及到的知識點很多,建議對ThreadX的應用有一些了解后再來看,這樣將事半功倍。但是本章的工程模板框架一定要學習。
雖然本章節是以我們開發板為例進行移植的,但是教會大家如何移植到自己的板子上以及移植過程中的注意事項是本章節的重點。
4.1初學者重要提示
4.2移植前的准備工作以及移植ThreadX的流程
4.3第1步:了解ThreadX內核模板框架設計
4.4第2步:添加ThreadX庫所有相關文件到裸機工程模板
4.5第3步:修改驅動初始化文件bsp.c(含MPU配置)
4.6 第4步:更新bsp_timer.c和bsp.h文件
4.7第5步:修改文件stm32f4xx_it.c
4.8第6步:修改文件tx_initalize_low_level.s
4.9第7步:ThreadX配置文件tx_user.h
4.10第8步:添加應用程序
4.11實驗例程
4.12總結
4.1 初學者重要提示。
- 注意ThreadX MDK AC5工程提供的Port文件問題,移植必讀:http://www.armbbs.cn/forum.php?mod=viewthread&tid=99306 。
- 為了方便大家移植,推薦直接添加我們的工程文件到自己的工程或者直接使用我們的工程模板,按照本章的修改說明移植即可。
4.2 移植前的准備工作以及移植ThreadX的流程
移植前注意以下兩個問題:
- 本章節的IDE開發環境務必是MDK5.30及其以上版本,鏡像下載地址:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=96992 。
- 准備一個簡單的裸機工程,越簡單越好,我們就在這個簡單的工程上面移植即可:
配套模板名稱:V6-3001_Base Template
GUIX的移植通過以下8步完成,下面各個小節詳細講解每一步:
4.3 第1步:了解ThreadX內核模板框架設計
移植ThreadX前,我們優先了解下ThreadX內核模板程序的框圖。
4.3.1 准備一個ThreadX內核工程模板
首先准備好一個簡單的ThreadX工程模板,工程模板的制作在ThreadX內核教程里面有詳細說明,這里的重點是教大家移植ThreadX,對應的例子名稱:V6-3002_ThreadX Kernal Template。准備好的工程模板如下圖所示。
4.3.2 內核框架整體把控(重要)
為了幫助大家更好的理解ThreadX內核例子模板,專門制作了一個框圖,可以讓大家整體把控模板設計:
下面把幾個關鍵點逐一為大家做個說明。
4.3.3 各種頭文件匯總includes.h
這個文件主要實現工程中各種頭文件的匯總,大家用到的都可以將其放到這個頭文件里面。其它應用源文件有調用到的,直接調用這個頭文件includes.h即可。
使用這個頭文件主要是方便各種頭文件的管理。
/* ********************************************************************************************************* * 標准庫 ********************************************************************************************************* */ #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <math.h> /* ********************************************************************************************************* * OS ********************************************************************************************************* */ #include "tx_api.h" #include "tx_timer.h" /* ********************************************************************************************************* * APP / BSP ********************************************************************************************************* */ #include <bsp.h> /* ********************************************************************************************************* * 變量和函數 ********************************************************************************************************* */ /* 方便RTOS里面使用 */ extern void SysTick_ISR(void); #define bsp_ProPer1ms SysTick_ISR
4.3.4 TheadX配置文件tx_user.h
此文件主要用於ThreadX內核的配置,內核相關的幾個宏配置基本都已經整理到這個文件里面。
/* ********************************************************************************************************* * 宏定義 ********************************************************************************************************* */ /* 最快速度優化需要開啟的選項 : TX_MAX_PRIORITIES 32 TX_DISABLE_PREEMPTION_THRESHOLD TX_DISABLE_REDUNDANT_CLEARING TX_DISABLE_NOTIFY_CALLBACKS TX_NOT_INTERRUPTABLE TX_TIMER_PROCESS_IN_ISR TX_REACTIVATE_INLINE TX_DISABLE_STACK_FILLING TX_INLINE_THREAD_RESUME_SUSPEND 最小代碼優化需要開啟的選項: TX_MAX_PRIORITIES 32 TX_DISABLE_PREEMPTION_THRESHOLD TX_DISABLE_REDUNDANT_CLEARING TX_DISABLE_NOTIFY_CALLBACKS TX_NOT_INTERRUPTABLE TX_TIMER_PROCESS_IN_ISR */ /* 覆蓋tx_port.h 里面的宏定義 */ /* #define TX_MAX_PRIORITIES 32 #define TX_MINIMUM_STACK ???? #define TX_THREAD_USER_EXTENSION ???? #define TX_TIMER_THREAD_STACK_SIZE ???? #define TX_TIMER_THREAD_PRIORITY ???? */ /* 確定定時器是否到期的處理,比如應用定時器,溢出時間和函數tx_thread_sleep調用等,是在系統定時器任務里面還是在定時器中斷里面調用。 默認是在定時任務里面,當定義了下面函數后,將直接在定時器中斷里面處理,可以去掉定時器任務所消耗資源。 */ //#define TX_TIMER_PROCESS_IN_ISR /* 用於設置定時器激活是否采用內聯方式,默認此功能是關閉的。如果使能后,內聯方式的執行速度快,但增加代碼量 */ //#define TX_REACTIVATE_INLINE /* 用於設置是否關閉棧填充,默認情況下是使能的,所有任務的棧空間全部填充為0xEF, * 帶有ThreadX調試組件或者運行時棧檢測會用到。 */ //#define TX_DISABLE_STACK_FILLING /* 用於使能棧檢測,默認是關閉的。此選項使能后,而TX_DISABLE_STACK_FILLING沒使能時,棧填充將開啟,方便棧檢測 */ //#define TX_ENABLE_STACK_CHECKING /* 用於設置是否關閉搶占閥值,默認是開啟的。如果應用程序不需要此功能,關閉后可以降低代碼需求,提升性能 */ //#define TX_DISABLE_PREEMPTION_THRESHOLD /* 用於設置是否清零ThreadX全局變量,如果編譯器啟動代碼在ThreadX運行前清除了.bss段,那么可以關閉不必要的清零 */ //#define TX_DISABLE_REDUNDANT_CLEARING /* 確定是否不需要定時器組,禁止后需要用戶注釋掉tx_initialize_low_level文件里面tx_timer_interrupt的調用。 另外,禁止后,必須使能TX_TIMER_PROCESS_IN_ISR */ /* #define TX_NO_TIMER #ifndef TX_TIMER_PROCESS_IN_ISR #define TX_TIMER_PROCESS_IN_ISR #endif */ /* 用於設置是否關閉通知回調,默認是使能的。如果應用程序沒有用到消息回調,關閉掉后可以減小代碼,並且可以提升性能。 */ //#define TX_DISABLE_NOTIFY_CALLBACKS /* 使能tx_thread_resume和tx_thread_suspend使用內聯代碼,優勢是提升這兩個函數的執行性能,劣勢是增加代碼量 */ //#define TX_INLINE_THREAD_RESUME_SUSPEND /* 設置TreadX內核不可中斷,好處是降低處理負擔,並且產生的代碼小。但增加鎖時間 */ //#define TX_NOT_INTERRUPTABLE /* 使能事件Trace,會稍微增加點代碼 */ //#define TX_ENABLE_EVENT_TRACE /* 使能BLOCK_POOL信息獲取 */ //#define TX_BLOCK_POOL_ENABLE_PERFORMANCE_INFO /* 使能BYTE_POOL信息獲取 */ //#define TX_BYTE_POOL_ENABLE_PERFORMANCE_INFO /* 使能事件標志信息獲取 */ //#define TX_EVENT_FLAGS_ENABLE_PERFORMANCE_INFO /* 使能互斥信號量信息獲取 */ //#define TX_MUTEX_ENABLE_PERFORMANCE_INFO /* 使能消息對象信息獲取 */ //#define TX_QUEUE_ENABLE_PERFORMANCE_INFO /* 使能信號量信息獲取 */ //#define TX_SEMAPHORE_ENABLE_PERFORMANCE_INFO /* 使能任務信息獲取 */ //#define TX_THREAD_ENABLE_PERFORMANCE_INFO /* 使能定時器信息獲取 */ //#define TX_TIMER_ENABLE_PERFORMANCE_INFO
4.3.5 系統時鍾節拍配置tx_initialize_low_level.s
這個匯編文件里面有個重要參數需要大家配置,即芯片主頻和系統時鍾節拍。
SYSTEM_CLOCK EQU 400000000
SYSTICK_CYCLES EQU ((SYSTEM_CLOCK / 1000) -1)
400000000是系統時鍾主頻,1000對應的就是系統時鍾節拍,這里1000就表示1000Hz。
4.3.6 TheadX任務管理main.c
ThreadX所有任務基本都在main.c里面創建,方便統一管理。如果有GUIX,FileX等組件的任務需要運行,實際運行函數會在其它源文件里面,並將這個函數extern到main.C文件里面,放到相應的任務里面執行。
另外,任務優先級,任務棧大小,任務控制塊等也都放到main.C文件里面,方便管理:
/* ********************************************************************************************************* * 任務優先級,數值越小優先級越高 ********************************************************************************************************* */ #define APP_CFG_TASK_START_PRIO 2u #define APP_CFG_TASK_MsgPro_PRIO 3u #define APP_CFG_TASK_USER_IF_PRIO 4u #define APP_CFG_TASK_COM_PRIO 5u #define APP_CFG_TASK_STAT_PRIO 30u #define APP_CFG_TASK_IDLE_PRIO 31u /* ********************************************************************************************************* * 任務棧大小,單位字節 ********************************************************************************************************* */ #define APP_CFG_TASK_START_STK_SIZE 4096u #define APP_CFG_TASK_MsgPro_STK_SIZE 4096u #define APP_CFG_TASK_COM_STK_SIZE 4096u #define APP_CFG_TASK_USER_IF_STK_SIZE 4096u #define APP_CFG_TASK_IDLE_STK_SIZE 1024u #define APP_CFG_TASK_STAT_STK_SIZE 1024u /* ********************************************************************************************************* * 靜態全局變量 ********************************************************************************************************* */ static TX_THREAD AppTaskStartTCB; static uint64_t AppTaskStartStk[APP_CFG_TASK_START_STK_SIZE/8]; static TX_THREAD AppTaskMsgProTCB; static uint64_t AppTaskMsgProStk[APP_CFG_TASK_MsgPro_STK_SIZE/8]; static TX_THREAD AppTaskCOMTCB; static uint64_t AppTaskCOMStk[APP_CFG_TASK_COM_STK_SIZE/8]; static TX_THREAD AppTaskUserIFTCB; static uint64_t AppTaskUserIFStk[APP_CFG_TASK_USER_IF_STK_SIZE/8]; static TX_THREAD AppTaskIdleTCB; static uint64_t AppTaskIdleStk[APP_CFG_TASK_IDLE_STK_SIZE/8]; static TX_THREAD AppTaskStatTCB; static uint64_t AppTaskStatStk[APP_CFG_TASK_STAT_STK_SIZE/8];
4.3.7 TheadX啟動任務
啟動任務里面主要做了四個工作:
- 優先執行一次任務統計OSStatInit。
- 外設初始化bsp_Init。
- 任務創建AppTaskCreate和通信組件創建AppObjCreate。
- 需要周期性處理的程序bsp_ProPer1ms,對應裸機工程調用的SysTick_ISR。這個的實現非常重要,這樣之前裸機里面使用的API,就可以直接在ThreadX里面直接調用。
代碼如下:
/* ********************************************************************************************************* * 函 數 名: AppTaskStart * 功能說明: 啟動任務。 * 形 參: thread_input 是在創建該任務時傳遞的形參 * 返 回 值: 無 優 先 級: 2 ********************************************************************************************************* */ static void AppTaskStart (ULONG thread_input) { (void)thread_input; /* 先掛起定時器組 */ #ifndef TX_NO_TIMER tx_thread_suspend(&_tx_timer_thread); #endif /* 優先執行任務統計 */ OSStatInit(); /* 恢復定時器組 */ #ifndef TX_NO_TIMER tx_thread_resume(&_tx_timer_thread); #endif /* 內核開啟后,恢復HAL里的時間基准 */ HAL_ResumeTick(); /* 外設初始化 */ bsp_Init(); /* 創建任務 */ AppTaskCreate(); /* 創建任務間通信機制 */ AppObjCreate(); while (1) { /* 需要周期性處理的程序,對應裸機工程調用的SysTick_ISR */ bsp_ProPer1ms(); tx_thread_sleep(1); } }
4.3.8 HAL庫時間基准stm32f4xx_hal_timebase_tim.c
ThreadX系統時鍾節拍默認是用的滴答定時器,STM32的HAL庫時間基准也是用的滴答定時器。對於這種情況,我們一般的情況下是使用其他的通用定時器替代,不過要額外的占用一點系統性能。簡單的處理辦法是重新下面兩個函數即可,讓HAL庫和ThreadX都使用滴答定時器:
/* ********************************************************************************************************* * 函 數 名: HAL_Delay * 功能說明: 重定向毫秒延遲函數。替換HAL中的函數。因為HAL中的缺省函數依賴於Systick中斷,如果在USB、SD * 卡中斷中有延遲函數,則會鎖死。也可以通過函數HAL_NVIC_SetPriority提升Systick中斷 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void HAL_Delay(uint32_t Delay) { bsp_DelayMS(Delay); } HAL_StatusTypeDef HAL_InitTick (uint32_t TickPriority) { return HAL_OK; } uint32_t HAL_GetTick (void) { static uint32_t ticks = 0U; uint32_t i; if (_tx_thread_system_state == TX_INITIALIZE_IS_FINISHED) { return ((uint32_t)_tx_time_get()); } /* 如果ThreadX還沒有運行,采用下面方式 */ for (i = (SystemCoreClock >> 14U); i > 0U; i--) { __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); } return ++ticks; }
4.3.9 ThreadX使能硬件浮點
這個是移植的坑王,大家移植后,可以測試下多任務的FPU計算是否有異常。比如兩個任務運行相同的浮點運算和刷新速度,看看兩個任務的輸出是否同步變化,這個測試非常重要:
那么問題來了,正確的使能姿勢是什么?務必保證C和匯編的預定義宏里面都使能。
C里面對應的使能:
匯編里面對應的使能:
4.4 第2步:添加ThreadX庫所有相關文件到裸機工程模板
了解了ThreadX內核框架后,介紹下如何將移植到裸機工程模板里面。我們這里一步到位,直接把所有相關的文件都加上,然后再介紹如何修改,方便大家移植到自己的板子上。
4.4.1 第2.1步,下載ThreadX源碼包
按照第2章2.3.1小節講解的方法下載軟件包threadx-6.0.2_rel(如果軟件包升級了,數字6.0.2略有不同),下面是ThreadX軟件包內容:
主要用到兩個文件夾:
common文件夾里面是源碼文件。
ports文件夾里面是移植文件。
4.4.2 第2.2步,創建ThreadX文件夾到工程模板
在工程模板創建文件夾
將2.1步的ThreadX軟件包里面的所有內容復制進來,整體效果就是下面這樣:
4.4.3 第2.3步,添加Port文件和源碼文件到工程
將源碼文件和ports文件添加到MDK的工程項目中,添加后的效果如下:
- ThreadX/Ports分組文件位置
- 文件tx_initialize_low_level.s在路徑ThreadX\ports\cortex_m4\ac5\example_build。
- 其它文件在路徑ThreadX\ports\cortex_m4\ac5\src。
- ThreadX/Source分組文件位置
- 全部在路徑ThreadX\common\src,所有文件全部添加進來
推薦使用下面的方法添加,有效防止MDK大批量添加源文件造成的卡頓問題:
4.4.4 第2.4步,添加配置文件tx_user.h
在User文件夾下添加文件tx_user.h,直接從本章節教程配套例子的User文件夾復制即可。此文件主要用於ThreadX配置。
為了方便管理,我們這里將路徑ThreadX\ports\cortex_m4\ac5\inc里面的tx_port.h文件也添加進來了。
4.4.5 第2.5步,添加頭文件的繪制文件includes.h
在User文件夾下添加文件incudes.h,直接從本章節教程配套例子的User文件夾復制即可。此文件主要用於ThreadX的各種頭文件匯總。
4.4.6 第2.6步,加HAL基准文件stm32f4xx_hal_timbase_tim.c
在User\bsp文件夾下添加文件stm32f4xx_hal_timebase_tim.c,直接從本章節教程配套例子的User\bsp文件夾復制即可。此文件主要用於為HAL庫重新安排一個時間基准:
4.4.7 第2.7步,添加BSP驅動文件bsp_dwt.c
添加bsp_dwt.c文件和bsp_dwt.h文件主要是因為2.6步中的stm32f4xx_hal_timebase_tim.c文件里面的函數bsp_DelayMS要使用,此函數是基於DWT系統時鍾周期計數器實現。
4.4.8 第2.8步,添加HAL庫文件
相關BSP驅動關聯到的HAL庫文件都添加了進來,簡單省事些,大家也可以把HAL庫所有文件都添加進來:
4.4.9 第2.9步,添加預定義宏
C/C++文件中添加的預定義宏如下:
- USE_HAL_DRIVER
- STM32F429xx
- USE_FULL_LL_DRIVER
- TX_ENABLE_FPU_SUPPORT 用於支持硬件FPU
- TX_ENABLE_STACK_CHECKING 用於棧檢測
- TX_INCLUDE_USER_DEFINE_FILE 用於包含tx_user.h
ASM匯編文件里面添加的宏定義:
- TX_ENABLE_FPU_SUPPORT
4.4.10 第2.10步,添加頭文件路徑
需要添加的路徑如下:
至此,我們需要的GUIX文件都已經添加完畢。下面為大家介紹如何修改用於自己的板子。
4.5 第3步,修改驅動初始化文件bsp.c
這個bsp.c文件比較重要,移植階段,直接將我們移植好的模板內容復制過去即可,這里把相關的內容為大家做個說明。
4.5.1 函數System_Init
系統初始化,需要在ThreadX初始化之前調用。
/* ********************************************************************************************************* * 函 數 名: System_Init * 功能說明: 系統初始化,主要是MPU,Cache和系統時鍾配置 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void System_Init(void) { /* STM32H429 HAL 庫初始化,此時系統用的還是F429自帶的16MHz,HSI時鍾: - 調用函數HAL_InitTick,初始化滴答時鍾中斷1ms。 - 設置NVIV優先級分組為4。 */ HAL_Init(); /* 配置系統時鍾到168MHz - 切換使用HSE。 - 此函數會更新全局變量SystemCoreClock,並重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用於代碼執行時間測量,MDK5.25及其以上版本才支持,IAR不支持。 - 默認不開啟,如果要使能此選項,務必看V7開發板用戶手冊第8章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder並開啟 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif }
4.5.2 函數bsp_Init
硬件外設的初始化,這個函數在ThreadX啟動任務里面調用。
/* ********************************************************************************************************* * 函 數 名: bsp_Init * 功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_Init(void) { bsp_InitDWT(); /* 初始化DWT時鍾周期計數器 */ bsp_InitKey(); /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */ bsp_InitUart(); /* 初始化串口 */ bsp_InitExtIO(); /* 初始化FMC總線74HC574擴展IO. 必須在 bsp_InitLed()前執行 */ bsp_InitLed(); /* 初始化LED */ bsp_InitTimer(); /* 初始化滴答定時器 */ }
4.5.3 函數SystemClock_Config
這個函數主要是完成系統時鍾配置。
/* ********************************************************************************************************* * 函 數 名: SystemClock_Config * 功能說明: 初始化系統時鍾 * System Clock source = PLL (HSE) * SYSCLK(Hz) = 168000000 (CPU Clock) * HCLK = SYSCLK / 1 = 168000000 (AHB1Periph) * PCLK2 = HCLK / 2 = 84000000 (APB2Periph) * PCLK1 = HCLK / 4 = 42000000 (APB1Periph) * HSE Frequency(Hz) = 25000000 * PLL_M = 25 * PLL_N = 336 * PLL_P = 2 * PLL_Q = 4 * VDD(V) = 3.3 * Flash Latency(WS) = 5 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void SystemClock_Config(void) { 省略未寫 /* 使能SYS時鍾和IO補償 */ __HAL_RCC_SYSCFG_CLK_ENABLE() ; HAL_EnableCompensationCell(); }
4.5.4 函數bsp_RunPer10ms
這個函數里面默認有個按鍵掃描,如果大家移植的程序里面沒有按鍵初始化,務必要把這個按鍵掃描函數注釋掉。
/* ********************************************************************************************************* * 函 數 名: bsp_RunPer10ms * 功能說明: 該函數每隔10ms被Systick中斷調用1次。詳見 bsp_timer.c的定時中斷服務程序。一些處理時間要求 * 不嚴格的任務可以放在此函數。比如:按鍵掃描、蜂鳴器鳴叫控制等。 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_RunPer10ms(void) { bsp_KeyScan10ms(); }
4.6 第4步,更新bsp_timer.c和bsp.h文件
更新bsp_timer.c文件是因為此文件跟ThreadX都要使用滴答定時器,有沖突。所以大家直接將我們工程模板里面此文件覆蓋移植的這個文件即可。
bsp.h文件里面要添加一個宏定義,因為bsp_timer.c文件里面做了些條件編譯:
#define USE_THREADX 1
另外,bsp.h文件將大部分頭文件都添加進來了,大家可以根據需要,用到那些頭文件,使能那些,用不到的,可以注釋掉。當然,不注釋也是沒問題的:
/* 通過取消注釋或者添加注釋的方式控制是否包含底層驅動模塊 */ //#include "bsp_msg.h" #include "bsp_user_lib.h" #include "bsp_timer.h" #include "bsp_led.h" #include "bsp_key.h" #include "bsp_dwt.h" //#include "bsp_cpu_rtc.h" //#include "bsp_cpu_adc.h" //#include "bsp_cpu_dac.h" #include "bsp_uart_fifo.h" //#include "bsp_uart_gps.h" //#include "bsp_uart_esp8266.h" //#include "bsp_uart_sim800.h" //#include "bsp_spi_bus.h" //#include "bsp_spi_ad9833.h" //#include "bsp_spi_ads1256.h" //#include "bsp_spi_dac8501.h" //#include "bsp_spi_dac8562.h" //#include "bsp_spi_flash.h" //#include "bsp_spi_tm7705.h" //#include "bsp_spi_vs1053b.h" //#include "bsp_fmc_sdram.h" //#include "bsp_fmc_nand_flash.h" //#include "bsp_fmc_ad7606.h" //#include "bsp_fmcdma_ad7606.h" //#include "bsp_fmc_oled.h" #include "bsp_fmc_io.h" //#include "bsp_i2c_gpio.h" //#include "bsp_i2c_bh1750.h" //#include "bsp_i2c_bmp085.h" //#include "bsp_i2c_eeprom_24xx.h" //#include "bsp_i2c_hmc5883l.h" //#include "bsp_i2c_mpu6050.h" //#include "bsp_i2c_si4730.h" //#include "bsp_i2c_wm8978.h" //#include "bsp_tft_429.h" //#include "bsp_tft_lcd.h" //#include "bsp_ts_touch.h" //#include "bsp_ts_ft5x06.h" //#include "bsp_ts_gt811.h" //#include "bsp_ts_gt911.h" //#include "bsp_ts_stmpe811.h" #include "bsp_beep.h" #include "bsp_tim_pwm.h" //#include "bsp_sdio_sd.h" //#include "bsp_dht11.h" //#include "bsp_ds18b20.h" //#include "bsp_ps2.h" //#include "bsp_ir_decode.h" //#include "bsp_camera.h" //#include "bsp_rs485_led.h" //#include "bsp_can.h"
4.7 第5步,修改文件stm3h7xx_it.c
刪除此文件里面帶的如下函數,ThreadX要使用,沖突了。
/** * @brief This function handles PendSVC exception. * @param None * @retval None */ void PendSV_Handler(void) { }
4.8 第6步,修改文件tx_initalize_low_level.s
ThreadX為MDK AC5提供的這個文件有點欠妥,因為ThreadX通過這個文件直接接管了我們一直使用的xxxx.S啟動文件。從ThreadX的角度來說,他們保證了自己發布新軟件包的獨立性,直接面向內核出,不需要再面向各個廠家單獨出一個系列。那樣工作量就超級大,各大廠家加起來有幾千個上萬的啟動文件。要他們專門去做適配,基本做不到的。
所以他們打算接管xxxx.S啟動文件,但接管的文件僅寫了幾個重要的中斷向量表入口。這就給讓移植的人非常不方便。所以我們做的例子不接管XXX.S啟動文件了,方便大家移植。本周教程配套例子使用的這個文件已經做了修改,推薦大家直接復制粘貼到自己的工程即可,內容如下:
注意:下面代碼中置紅部分的參數需要大家配置,即芯片主頻和系統時鍾節拍。
SYSTEM_CLOCK EQU 168000000
SYSTICK_CYCLES EQU ((SYSTEM_CLOCK / 1000) -1)
168000000是系統時鍾主頻,1000對應的就是系統時鍾節拍,這里1000就表示1000Hz。
;/**************************************************************************/ ;/* */ ;/* Copyright (c) Microsoft Corporation. All rights reserved. */ ;/* */ ;/* This software is licensed under the Microsoft Software License */ ;/* Terms for Microsoft Azure RTOS. Full text of the license can be */ ;/* found in the LICENSE file at https://aka.ms/AzureRTOS_EULA */ ;/* and in the root directory of this software. */ ;/* */ ;/**************************************************************************/ ; ; ;/**************************************************************************/ ;/**************************************************************************/ ;/** */ ;/** ThreadX Component */ ;/** */ ;/** Initialize */ ;/** */ ;/**************************************************************************/ ;/**************************************************************************/ ; ;#define TX_SOURCE_CODE ; ; ;/* Include necessary system files. */ ; ;#include "tx_api.h" ;#include "tx_initialize.h" ;#include "tx_thread.h" ;#include "tx_timer.h" ; ; IMPORT _tx_thread_system_stack_ptr IMPORT _tx_initialize_unused_memory IMPORT _tx_thread_context_save IMPORT _tx_thread_context_restore IMPORT _tx_timer_interrupt IMPORT __main IMPORT __Vectors IMPORT __initial_sp IF :DEF:TX_ENABLE_EXECUTION_CHANGE_NOTIFY IMPORT _tx_execution_isr_exit IMPORT _tx_execution_isr_enter ENDIF ; ; SYSTEM_CLOCK EQU 168000000 SYSTICK_CYCLES EQU ((SYSTEM_CLOCK / 1000) -1) ; ; ; ; AREA ||.text||, CODE, READONLY PRESERVE8 ;/**************************************************************************/ ;/* */ ;/* FUNCTION RELEASE */ ;/* */ ;/* _tx_initialize_low_level Cortex-M7/AC5 */ ;/* 6.0.1 */ ;/* AUTHOR */ ;/* */ ;/* William E. Lamie, Microsoft Corporation */ ;/* */ ;/* DESCRIPTION */ ;/* */ ;/* This function is responsible for any low-level processor */ ;/* initialization, including setting up interrupt vectors, setting */ ;/* up a periodic timer interrupt source, saving the system stack */ ;/* pointer for use in ISR processing later, and finding the first */ ;/* available RAM memory address for tx_application_define. */ ;/* */ ;/* INPUT */ ;/* */ ;/* None */ ;/* */ ;/* OUTPUT */ ;/* */ ;/* None */ ;/* */ ;/* CALLS */ ;/* */ ;/* None */ ;/* */ ;/* CALLED BY */ ;/* */ ;/* _tx_initialize_kernel_enter ThreadX entry function */ ;/* */ ;/* RELEASE HISTORY */ ;/* */ ;/* DATE NAME DESCRIPTION */ ;/* */ ;/* 06-30-2020 William E. Lamie Initial Version 6.0.1 */ ;/* */ ;/**************************************************************************/ ;VOID _tx_initialize_low_level(VOID) ;{ EXPORT _tx_initialize_low_level _tx_initialize_low_level ; ; /* Disable interrupts during ThreadX initialization. */ ; CPSID i ; ; /* Set base of available memory to end of non-initialised RAM area. */ ; LDR r0, =_tx_initialize_unused_memory ; Build address of unused memory pointer LDR r1, =__initial_sp ; Build first free address ADD r1, r1, #4 ; STR r1, [r0] ; Setup first unused memory pointer ; ; /* Setup Vector Table Offset Register. */ ; MOV r0, #0xE000E000 ; Build address of NVIC registers LDR r1, =__Vectors ; Pickup address of vector table STR r1, [r0, #0xD08] ; Set vector table address ; ; /* Enable the cycle count register. */ ; ; LDR r0, =0xE0001000 ; Build address of DWT register ; LDR r1, [r0] ; Pickup the current value ; ORR r1, r1, #1 ; Set the CYCCNTENA bit ; STR r1, [r0] ; Enable the cycle count register ; ; /* Set system stack pointer from vector value. */ ; LDR r0, =_tx_thread_system_stack_ptr ; Build address of system stack pointer LDR r1, =__Vectors ; Pickup address of vector table LDR r1, [r1] ; Pickup reset stack pointer STR r1, [r0] ; Save system stack pointer ; ; /* Configure SysTick. */ ; MOV r0, #0xE000E000 ; Build address of NVIC registers LDR r1, =SYSTICK_CYCLES STR r1, [r0, #0x14] ; Setup SysTick Reload Value MOV r1, #0x7 ; Build SysTick Control Enable Value STR r1, [r0, #0x10] ; Setup SysTick Control ; ; /* Configure handler priorities. */ ; LDR r1, =0x00000000 ; Rsrv, UsgF, BusF, MemM STR r1, [r0, #0xD18] ; Setup System Handlers 4-7 Priority Registers LDR r1, =0xFF000000 ; SVCl, Rsrv, Rsrv, Rsrv STR r1, [r0, #0xD1C] ; Setup System Handlers 8-11 Priority Registers ; Note: SVC must be lowest priority, which is 0xFF LDR r1, =0x40FF0000 ; SysT, PnSV, Rsrv, DbgM STR r1, [r0, #0xD20] ; Setup System Handlers 12-15 Priority Registers ; Note: PnSV must be lowest priority, which is 0xFF ; ; /* Return to caller. */ ; BX lr ;} ; ; ;/* Define shells for each of the unused vectors. */ ; EXPORT __tx_BadHandler __tx_BadHandler B __tx_BadHandler EXPORT __tx_SVCallHandler __tx_SVCallHandler B __tx_SVCallHandler EXPORT __tx_IntHandler __tx_IntHandler ; VOID InterruptHandler (VOID) ; { PUSH {r0, lr} ; /* Do interrupt handler work here */ ; /* .... */ POP {r0, lr} BX LR ; } EXPORT __tx_SysTickHandler EXPORT SysTick_Handler __tx_SysTickHandler SysTick_Handler ; VOID TimerInterruptHandler (VOID) ; { ; PUSH {r0, lr} IF :DEF:TX_ENABLE_EXECUTION_CHANGE_NOTIFY BL _tx_execution_isr_enter ENDIF BL _tx_timer_interrupt IF :DEF:TX_ENABLE_EXECUTION_CHANGE_NOTIFY BL _tx_execution_isr_exit ENDIF POP {r0, lr} BX LR ; } EXPORT __tx_NMIHandler __tx_NMIHandler B __tx_NMIHandler EXPORT __tx_DBGHandler __tx_DBGHandler B __tx_DBGHandler ALIGN LTORG END
4.9 第7步,ThreadX配置文件tx_user.h
ThreadX內核相關的配置,已經全部整理到了這個文件中,並且做中文注釋,大家可以更新需要使能宏定義。
4.10 第8步,添加應用程序
應用程序比較簡單,大家可以直接復制本章教程配置例子的main.c文件中的內容到自己工程里面測試。主要創建了如下幾個任務:
App Task Start任務 :啟動任務,這里用作BSP驅動包處理。
App Task MspPro任務 :消息處理,這里用作浮點數串口打印。
App Task UserIF任務 :按鍵消息處理。
App Task COM任務 :浮點數串口打印。
App Task STAT任務 :統計任務
App Task IDLE任務 :空閑任務
System Timer Thread任務:系統定時器任務
任務棧大小和任務控制塊定義如下:
/* ********************************************************************************************************* * 任務優先級,數值越小優先級越高 ********************************************************************************************************* */ #define APP_CFG_TASK_START_PRIO 2u #define APP_CFG_TASK_MsgPro_PRIO 3u #define APP_CFG_TASK_USER_IF_PRIO 4u #define APP_CFG_TASK_COM_PRIO 5u #define APP_CFG_TASK_STAT_PRIO 30u #define APP_CFG_TASK_IDLE_PRIO 31u /* ********************************************************************************************************* * 任務棧大小,單位字節 ********************************************************************************************************* */ #define APP_CFG_TASK_START_STK_SIZE 4096u #define APP_CFG_TASK_MsgPro_STK_SIZE 4096u #define APP_CFG_TASK_COM_STK_SIZE 4096u #define APP_CFG_TASK_USER_IF_STK_SIZE 4096u #define APP_CFG_TASK_IDLE_STK_SIZE 1024u #define APP_CFG_TASK_STAT_STK_SIZE 1024u /* ********************************************************************************************************* * 靜態全局變量 ********************************************************************************************************* */ static TX_THREAD AppTaskStartTCB; static uint64_t AppTaskStartStk[APP_CFG_TASK_START_STK_SIZE/8]; static TX_THREAD AppTaskMsgProTCB; static uint64_t AppTaskMsgProStk[APP_CFG_TASK_MsgPro_STK_SIZE/8]; static TX_THREAD AppTaskCOMTCB; static uint64_t AppTaskCOMStk[APP_CFG_TASK_COM_STK_SIZE/8]; static TX_THREAD AppTaskUserIFTCB; static uint64_t AppTaskUserIFStk[APP_CFG_TASK_USER_IF_STK_SIZE/8]; static TX_THREAD AppTaskIdleTCB; static uint64_t AppTaskIdleStk[APP_CFG_TASK_IDLE_STK_SIZE/8]; static TX_THREAD AppTaskStatTCB; static uint64_t AppTaskStatStk[APP_CFG_TASK_STAT_STK_SIZE/8];
優先創建的啟動任務,統計任務和空閑任務:
/* ********************************************************************************************************* * 函 數 名: tx_application_define * 功能說明: ThreadX專用的任務創建,通信組件創建函數 * 形 參: first_unused_memory 未使用的地址空間 * 返 回 值: 無 ********************************************************************************************************* */ void tx_application_define(void *first_unused_memory) { /* 如果實現任務CPU利用率統計的話,此函數僅用於實現啟動任務,統計任務和空閑任務,其它任務在函數 AppTaskCreate里面創建。 */ /**************創建啟動任務*********************/ tx_thread_create(&AppTaskStartTCB, /* 任務控制塊地址 */ "App Task Start", /* 任務名 */ AppTaskStart, /* 啟動任務函數地址 */ 0, /* 傳遞給任務的參數 */ &AppTaskStartStk[0], /* 堆棧基地址 */ APP_CFG_TASK_START_STK_SIZE, /* 堆棧空間大小 */ APP_CFG_TASK_START_PRIO, /* 任務優先級*/ APP_CFG_TASK_START_PRIO, /* 任務搶占閥值 */ TX_NO_TIME_SLICE, /* 不開啟時間片 */ TX_AUTO_START); /* 創建后立即啟動 */ /**************創建統計任務*********************/ tx_thread_create(&AppTaskStatTCB, /* 任務控制塊地址 */ "App Task STAT", /* 任務名 */ AppTaskStat, /* 啟動任務函數地址 */ 0, /* 傳遞給任務的參數 */ &AppTaskStatStk[0], /* 堆棧基地址 */ APP_CFG_TASK_STAT_STK_SIZE, /* 堆棧空間大小 */ APP_CFG_TASK_STAT_PRIO, /* 任務優先級*/ APP_CFG_TASK_STAT_PRIO, /* 任務搶占閥值 */ TX_NO_TIME_SLICE, /* 不開啟時間片 */ TX_AUTO_START); /* 創建后立即啟動 */ /**************創建空閑任務*********************/ tx_thread_create(&AppTaskIdleTCB, /* 任務控制塊地址 */ "App Task IDLE", /* 任務名 */ AppTaskIDLE, /* 啟動任務函數地址 */ 0, /* 傳遞給任務的參數 */ &AppTaskIdleStk[0], /* 堆棧基地址 */ APP_CFG_TASK_IDLE_STK_SIZE, /* 堆棧空間大小 */ APP_CFG_TASK_IDLE_PRIO, /* 任務優先級*/ APP_CFG_TASK_IDLE_PRIO, /* 任務搶占閥值 */ TX_NO_TIME_SLICE, /* 不開啟時間片 */ TX_AUTO_START); /* 創建后立即啟動 */ }
在啟動任務中優先執行一次任務統計,然后創建其它任務:
/* ********************************************************************************************************* * 函 數 名: AppTaskStart * 功能說明: 啟動任務。 * 形 參: thread_input 是在創建該任務時傳遞的形參 * 返 回 值: 無 優 先 級: 2 ********************************************************************************************************* */ static void AppTaskStart (ULONG thread_input) { (void)thread_input; /* 先掛起定時器組 */ #ifndef TX_NO_TIMER tx_thread_suspend(&_tx_timer_thread); #endif /* 優先執行任務統計 */ OSStatInit(); /* 恢復定時器組 */ #ifndef TX_NO_TIMER tx_thread_resume(&_tx_timer_thread); #endif /* 內核開啟后,恢復HAL里的時間基准 */ HAL_ResumeTick(); /* 外設初始化 */ bsp_Init(); /* 創建任務 */ AppTaskCreate(); /* 創建任務間通信機制 */ AppObjCreate(); while (1) { /* 需要周期性處理的程序,對應裸機工程調用的SysTick_ISR */ bsp_ProPer1ms(); tx_thread_sleep(1); } }
4.11 實驗例程
本章節配套了如下幾個例子供大家移植參考:
- V6-3001_Base Template
裸機模板,方便大家添加ThreadX內核源碼,含有GCC,IAR,MDK AC5和AC6四個版本工程。
- V6-3002_Threadx Kernel Template
ThreadX內核模板,用於大家移植GUIX的參考Demo,含有GCC,IAR,MDK AC5和AC6四個版本工程。
IAR,MDK AC5和AC6工程可以串口打印任務執行情況:按開發板的按鍵K1可以打印,波特率 115200,數據位 8,奇偶校驗位無,停止位 1:
Embedded Studio(GCC)平台的串口打印是通過其調試組件SEGGER RTT做的串口打印,速度也非常快,打印效果如下:
展示里面有亂碼是因為Embedded Studio不支持中文。
4.12 總結
本章節為大家講解的內容涉及到的知識較多,信息量較大,部分知識點沒有弄明白是沒有關系的,但是一定要掌握ThreadX內核框架設計,掌握后再分析細節,事半功倍。