回到主页

从 FreeRTOS 到 ThreadX

STM32F407 上手 Eclipse ThreadX 实战指南

· AI
Section image

当你在嵌入式开发中提起 RTOS,绝大多数人会脱口而出“FreeRTOS”。

但 2024 年 Eclipse 基金会接手 ThreadX 后,这个曾经微软旗下的工业级 RTOS 正以全新的姿态回到开源社区。

本文将简要介绍 ThreadX 在 STM32F407VET6 上的移植和创建第一个多线程任务。

一、为什么选 ThreadX?

Section image


1.2 选 ThreadX 的三个理由

1. 安全认证预置:如果你的产品需要过功能安全(汽车 ASIL-D、工业 SIL4),ThreadX自带认证包,能省去认证费用和时间。

2. 确定性调度:Priority Threshold 机制允许线程屏蔽同优先级及以下的抢占,减少不必要的上下文切换——这在电机控制、实时信号处理场景中尤为关键。
一句话总结:FreeRTOS 是“够用”的选择,ThreadX 是“专业”的选择。当项目对实时性、安全性、可扩展性有更高要求时,ThreadX 值得你投入学习成本。
二、环境搭建与目标平台

2.1 硬件平台

Section image


2.2 软件环境

Section image


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
仓库核心目录结构:

Section image


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 的核心流程:

Section image


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 移植要点回顾

Section image


5.2 ThreadX 与FreeRTOS 的 API 速查

Section image


5.3 下一步进阶计划(后续更新)

多线程抢占实验:创建 5 个不同优先级的线程(Auto/Manual/Event/Sleep/Timer),观察抢占时序

中断集成:在 EXTI ISR 中调用 tx_semaphore_put() /tx_event_flags_set() 唤醒线程

CAN通讯:基于 ThreadX 实现符合AutoSar层级的CAN 通讯协议