论坛原始地址(持续更新):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在路径ThreadXportscortex_m4ac5example_build。
- 其它文件在路径ThreadXportscortex_m4ac5src。
- ThreadX/Source分组文件位置
- 全部在路径ThreadXcommonsrc,所有文件全部添加进来
推荐使用下面的方法添加,有效防止MDK大批量添加源文件造成的卡顿问题:
4.4.4 第2.4步,添加配置文件tx_user.h
在User文件夹下添加文件tx_user.h,直接从本章节教程配套例子的User文件夹复制即可。此文件主要用于ThreadX配置。
为了方便管理,我们这里将路径ThreadXportscortex_m4ac5inc里面的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
在Usersp文件夹下添加文件stm32f4xx_hal_timebase_tim.c,直接从本章节教程配套例子的Usersp文件夹复制即可。此文件主要用于为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内核框架设计,掌握后再分析细节,事半功倍。