移植第一步:ThreadX内核移植到STM32H743平台

好,咱们正式开始动手。这一章我带你走完ThreadX内核在STM32H743上的移植全流程。说白了,就是把ThreadX的调度器、中断管理、时钟节拍这些核心部件,跟你的硬件平台对接起来。

我在项目里移植过不下五个RTOS,ThreadX算是文档最清晰的一个。但清晰归清晰,该踩的坑一个不少。咱们一步步来。

移植前的准备工作

先确认你手头的东西齐了没有:

  • STM32H743开发板(我习惯用Nucleo-H743ZI2)
  • ThreadX源码包(从Azure RTOS仓库拉下来)
  • ARM GCC工具链或IAR/Keil
  • STM32CubeMX生成的HAL库基础工程

我的习惯:先用CubeMX生成一个能点灯的基础工程,确认硬件没问题,再往上叠ThreadX。别一上来就搞移植,万一板子本身有问题,你排查起来会疯掉。

ThreadX内核文件结构

打开ThreadX源码包,你真正需要关心的文件其实不多。我列个表给你看:

文件 作用 是否需要修改
tx_initialize_low_level.S 底层初始化汇编代码 是
tx_thread_schedule.S 任务调度器入口 否(直接使用)
tx_thread_context_restore.S 上下文恢复 否
tx_thread_context_save.S 上下文保存 否
tx_timer_interrupt.c 时钟节拍中断处理 是
tx_port.h 平台相关的宏定义 是

嗯,核心就这几个。其他文件你暂时不用管。

启动文件与链接脚本修改

这是移植的第一步,也是最容易出问题的地方。

启动文件修改

STM32H743的启动文件(startup_stm32h743xx.s)里,你需要做两件事:

  1. 增加栈空间——ThreadX本身需要一块独立的系统栈,我建议至少分配4KB。
  2. 修改复位向量——让系统启动后先进入ThreadX的初始化函数,再跳转到main。

具体改法:

; 在启动文件中找到栈定义
Stack_Size      EQU     0x00002000  ; 从默认的1KB改为8KB

; 修改复位向量
; 原来:IMPORT __main
;       LDR     R0, =__main
;       BX      R0
; 改为:
IMPORT  tx_application_define  ; ThreadX应用定义函数
IMPORT  __main
LDR     R0, =tx_application_define
BLX     R0
LDR     R0, =__main
BX      R0

我曾经踩过的坑:第一次移植时没改栈大小,结果系统跑起来后任务一多就莫名其妙死机。查了两天才发现是栈溢出。记住,ThreadX的系统栈和任务栈是分开的,系统栈给中断和调度器用,小了绝对不行。

链接脚本修改

链接脚本(STM32H743_FLASH.ld)主要改两个地方:

  • 内存区域定义——确认Flash和RAM的起始地址和大小跟你的板子一致
  • 增加ThreadX专用段——有些版本需要放置tx_application_define的入口

H743的Flash是2MB,RAM是1MB(分DTCM、AXI SRAM等区域)。我一般这么配:

MEMORY
{
    FLASH (rx)     : ORIGIN = 0x08000000, LENGTH = 2M
    DTCMRAM (xrw)  : ORIGIN = 0x20000000, LENGTH = 128K
    RAM_D1 (xrw)   : ORIGIN = 0x24000000, LENGTH = 512K
    RAM_D2 (xrw)   : ORIGIN = 0x30000000, LENGTH = 288K
    RAM_D3 (xrw)   : ORIGIN = 0x38000000, LENGTH = 64K
}

你想想看,H743的内存架构其实挺复杂的,分了好几个域。我建议把ThreadX的堆和任务栈放在RAM_D1(0x24000000),这块区域访问速度最快,而且不会被DMA干扰。

系统时钟配置

时钟配置是移植的灵魂。ThreadX需要一个周期性的定时器中断来驱动任务调度,这个中断源通常用SysTick或者TIM。

选择时钟源

我个人偏好用TIM2而不是SysTick。为什么?因为SysTick在H743上只有24位,如果你系统主频跑400MHz,SysTick的溢出时间太短,频繁进中断会影响实时性。TIM2是32位的,可以设置更长的周期。

配置方法:

// 在tx_initialize_low_level.S或tx_timer_interrupt.c中
// 假设HCLK = 400MHz,我们需要1ms的节拍

// 使用TIM2
#define TIM2_BASE_ADDR      0x40000000UL
#define TIM2_PSC            399     // 预分频:400MHz / (399+1) = 1MHz
#define TIM2_ARR            999     // 自动重装:1MHz / (999+1) = 1KHz = 1ms

void tx_timer_interrupt_setup(void)
{
    // 使能TIM2时钟
    RCC->APB1ENR1 |= RCC_APB1ENR1_TIM2EN;
    
    // 配置定时器
    TIM2->PSC = TIM2_PSC;
    TIM2->ARR = TIM2_ARR;
    TIM2->DIER |= TIM_DIER_UIE;    // 使能更新中断
    TIM2->CR1 |= TIM_CR1_CEN;     // 启动定时器
    
    // 配置NVIC
    NVIC_SetPriority(TIM2_IRQn, 0);  // 最高优先级
    NVIC_EnableIRQ(TIM2_IRQn);
}

关键点:定时器中断优先级必须设为最高(0),否则高优先级中断会阻塞节拍中断,导致系统时间漂移。我在一个工业控制项目里吃过这个亏,当时电机控制中断优先级比节拍高,结果系统跑了半小时后任务调度完全乱套。

中断处理函数

在中断服务函数里,你只需要调用ThreadX的节拍处理函数:

void TIM2_IRQHandler(void)
{
    if (TIM2->SR & TIM_SR_UIF)
    {
        TIM2->SR = ~TIM_SR_UIF;  // 清除中断标志
        _tx_timer_interrupt();    // ThreadX节拍处理
    }
}

就这么简单。别在中断里干别的事,节拍中断要快进快出。

整体移植流程

我把整个流程画了张图,你对照着走:

ThreadX移植到STM32H743流程图 准备ThreadX源码 修改启动文件 startup.s 修改链接脚本 .ld 配置系统时钟节拍 实现tx_application_define 编译验证点灯 关键文件清单 • tx_initialize_low_level.S • tx_port.h • tx_timer_interrupt.c • startup_stm32h743xx.s 常见错误 • 栈溢出(系统栈太小) • 中断优先级配置错误 • 时钟节拍频率不匹配 • 链接脚本内存地址错误

验证移植是否成功

移植完了怎么知道对不对?写个最简单的测试:

// tx_application_define.c
#include "tx_api.h"

TX_THREAD test_thread;
UCHAR test_stack[1024];

void test_entry(ULONG input)
{
    while(1)
    {
        HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);  // 点灯
        tx_thread_sleep(500);                     // 延时500ms
    }
}

void tx_application_define(void *first_unused_memory)
{
    tx_thread_create(&test_thread, "test", test_entry, 0,
                     test_stack, sizeof(test_stack),
                     1, 1, TX_NO_TIME_SLICE, TX_AUTO_START);
}

编译下载,如果LED以1秒周期闪烁(500ms亮+500ms灭),恭喜你,移植成功了。

一个小技巧:第一次跑的时候把节拍频率设慢一点,比如10ms。这样即使调度有问题,你也能从LED闪烁节奏上看出来。等确认移植没问题了,再改回1ms。

移植检查清单

我每次移植完都会过一遍这个清单,你也照着来:

  • 启动文件:栈大小是否足够?复位向量是否正确跳转到tx_application_define?
  • 链接脚本:内存区域地址是否匹配芯片型号?ThreadX堆空间是否分配?
  • 时钟配置:定时器时钟源是否使能?预分频和重装值计算是否正确?
  • 中断优先级:节拍中断优先级是否设为最高(0)?
  • 中断处理:中断标志是否清除?是否调用了_tx_timer_interrupt?
  • 任务创建:栈对齐是否4字节?优先级是否在有效范围内?

嗯,这一章的内容就到这儿。移植这件事,说白了就是让ThreadX的代码跟你的硬件对上话。只要启动、时钟、中断这三个环节不出错,后面的事情就顺了。