当你在嵌入式开发中提起 RTOS,绝大多数人会脱口而出“FreeRTOS”。
但 2024 年 Eclipse 基金会接手 ThreadX 后,这个曾经微软旗下的工业级 RTOS 正以全新的姿态回到开源社区。
本文将简要介绍 ThreadX 在 STM32F407VET6 上的移植和创建第一个多线程任务。
一、为什么选 ThreadX?
1.2 选 ThreadX 的三个理由
1. 安全认证预置:如果你的产品需要过功能安全(汽车 ASIL-D、工业 SIL4),ThreadX自带认证包,能省去认证费用和时间。
2. 确定性调度:Priority Threshold 机制允许线程屏蔽同优先级及以下的抢占,减少不必要的上下文切换——这在电机控制、实时信号处理场景中尤为关键。
一句话总结:FreeRTOS 是“够用”的选择,ThreadX 是“专业”的选择。当项目对实时性、安全性、可扩展性有更高要求时,ThreadX 值得你投入学习成本。
二、环境搭建与目标平台
2.1 硬件平台
2.2 软件环境
2.3 时钟树配置
25MHz HSE --+ PLL (M=25, N=336, P=2) --+ 168MHz SYSCLK
+-- 168MHz AHB (HCLK)
+-- 42MHz APB1 (PCLK1)
+-- 84MHz APB2 (PCLK2)
注意:ThreadX 的系统 Tick 默认使用 SysTick,由 HAL 的 HAL_Init() 初始化为 1ms 周期(1000Hz),与 FreeRTOS 的 configTICK_RATE_HZ 类似。
三、ThreadX 仓库结构与移植过程
3.1 GitHub 仓库
Eclipse ThreadX 的官方仓库:
https://github.com/eclipse-threadx/threadx
仓库核心目录结构:
3.2 移植四步走
第一步:复制源码到工程
Threadx-stm32/
+-- Middlewares/
+-- ThreadX/
+-- common/
| +-- inc/ <-- 从 threadx/common/inc 复制
| +-- src/ <-- 从 threadx/common/src 复制
+-- ports/
+-- cortex_m4/gnu/
+-- inc/ <-- 从 threadx/ports/cortex_m4/gnu/inc 复制
+-- src/ <-- 从 threadx/ports/cortex_m4/gnu/src 复制
第二步:配置工程 Include Path
在 .cproject 中添加以下Include 路径:
Middlewares/ThreadX/common/inc
Middlewares/ThreadX/ports/cortex_m4/gnu/inc
预处理器宏定义添加:
USE_HAL_DRIVER
STM32F407xx
第三步:配置 Source Entries
确保 .cproject 的sourceEntries 包含:
kind="sourcePath" name="Middlewares"/>
这样 .c 内核文件和 .S 汇编文件都会被编译。
第四步:对接中断(关键!)
ThreadX 需要两个Cortex-M 异常:
1.SysTick → 驱动 ThreadX 时钟节拍
2.PendSV → 线程上下文切换
在 stm32f4xx_it.c 中,必须注释掉 HAL 默认的 SysTick_Handler 和 PendSV_Handler,因为 ThreadX 的汇编代码已经提供了自己的实现:
/* !! 必须注释!ThreadX 汇编代码已接管这些中断 */
/*
void PendSV_Handler(void)
{
}
void SysTick_Handler(void)
{
}
*/
ThreadX 的 tx_timer_interrupt.S 中会自动调用 _tx_timer_interrupt(),该函数处理所有定时器相关逻辑,包括 tx_thread_sleep() 的唤醒。
四、实战:创建第一个 ThreadX 任务
4.1 主程序框架
main.c 的核心流程:
4.2 任务创建回调
ThreadX 通过tx_application_define() 回调来创建初始任务,这个函数由内核在启动时自动调用:
oid tx_application_define(void *first_unused_memory)
{
(void)first_unused_memory;
tx_thread_create(&led_thread, /* 线程控制块 */
"LED Thread", /* 线程名称 */
LED_Thread_Entry, /* 入口函数 */
0, /* 入口参数 */
led_thread_stack, /* 栈基地址 */
LED_THREAD_STACK_SIZE, /* 栈大小 */
LED_THREAD_PRIORITY, /* 优先级 */
LED_THREAD_PRIORITY, /* 抢占阈值(同优先级)*/
TX_NO_TIME_SLICE, /* 无时间片 */
TX_AUTO_START); /* 创建后自动启动 */
}
重要区别:ThreadX 优先级数值越大优先级越高(0 最低),这与 FreeRTOS 相反!
4.3 LED 任务入口函数
void LED_Thread_Entry(ULONG thread_input)
{
(void)thread_input;
while(1)
{
HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin); /* 翻转 PA5 */
tx_thread_sleep(500); /* 睡眠 500 ticks = 500ms */
}
}
tx_thread_sleep() 对应 FreeRTOS 的 vTaskDelay(),参数是系统 Tick 数。SysTick 配置为1ms,所以 sleep(500) = 500ms。
4.4 GPIO 初始化
目标板的LED输出PIN脚:PA5
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
GPIO_InitStruct.Pin = LED_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LED_GPIO_Port, &GPIO_InitStruct);
}
4.5 编译验证
1. 在STM32CubeIDE 中 编译
2. 应看到 ThreadX 内核文件 +移植汇编文件被编译
3. 总Flash 占用约 ~30KB(内核 + HAL + 应用)
4. 连接调试器 ,点击 Debug 烧录运行
5. 观察PA5 LED 以 500ms 周期闪烁 → 移植成功!
五、总结
5.1 移植要点回顾
5.2 ThreadX 与FreeRTOS 的 API 速查
5.3 下一步进阶计划(后续更新)
• 多线程抢占实验:创建 5 个不同优先级的线程(Auto/Manual/Event/Sleep/Timer),观察抢占时序
• 中断集成:在 EXTI ISR 中调用 tx_semaphore_put() /tx_event_flags_set() 唤醒线程
• CAN通讯:基于 ThreadX 实现符合AutoSar层级的CAN 通讯协议