1. 最小系统启动
任务目标
在STM32F103(以STM32F103C8T6蓝丸板为例)上完成ThreadX内核的移植与最小系统搭建:初始化内核、创建并启动一个控制LED闪烁的线程。通过观察板载LED以约1Hz频率翻转,验证ThreadX调度器正常运行。
环境与依赖
- 硬件:STM32F103C8T6最小系统板(板载LED接PC13,低电平点亮)
- IDE:STM32CubeIDE 1.15+ 或 Keil MDK-ARM 5.30+
- ThreadX源码:Azure RTOS 6.x(本案例使用6.2.0,从GitHub获取后放入工程)
- 时钟配置:使用HSE 8MHz外部晶振,经PLL倍频至72MHz系统主频
工程配置要点
在STM32CubeMX中完成以下配置后生成工程:
- RCC:HSE使能,时钟树配置为72MHz主频
- PC13设为GPIO输出,初始电平为高(LED灭)
- 调试接口:SWD(保持默认即可)
ThreadX源码需添加至工程:将tx_api.h、tx_port.h所在目录加入头文件路径,将tx_initialize_low_level.s(汇编启动文件)及tx_thread_schedule.s加入工程源文件。若使用CubeIDE,还需在链接器选项中增加--specs=nano.specs以减小体积。
关键完整代码
/* main.c - 最小系统启动与LED线程 */
#include "tx_api.h"
#include "main.h"
/* 线程栈与控制块 */
static TX_THREAD led_thread;
static uint8_t led_thread_stack[512];
/* 线程入口函数 */
static void led_thread_entry(ULONG arg)
{
(void)arg;
while (1)
{
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
tx_thread_sleep(500); /* 延时500个时钟节拍(1秒) */
}
}
/* 用户初始化入口(CubeMX生成的main函数中调用) */
void user_threadx_init(void)
{
/* 初始化ThreadX内核 */
tx_kernel_enter();
}
/* ThreadX内核初始化完成后回调 */
void tx_application_define(void *first_unused_memory)
{
(void)first_unused_memory;
/* 创建LED控制线程 */
UINT status = tx_thread_create(
&led_thread, /* 线程控制块 */
"led_thread", /* 线程名称 */
led_thread_entry, /* 入口函数 */
0, /* 入口参数 */
led_thread_stack, /* 栈起始地址 */
sizeof(led_thread_stack), /* 栈大小(字节) */
1, /* 优先级:0最高,31最低 */
1, /* 时间片(仅同优先级轮询时有效) */
TX_AUTO_START); /* 创建后自动启动 */
if (status != TX_SUCCESS)
{
/* 创建失败处理:可在此处加入错误陷阱 */
while (1) { }
}
}
/* main函数(CubeMX生成,此处仅保留关键调用) */
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
/* 进入ThreadX内核(不会返回) */
user_threadx_init();
/* 不会执行到这里 */
while (1) { }
}
运行与验证
- 编译工程并烧录至STM32F103C8T6。
- 复位开发板,观察板载LED(PC13)。
- 通过标准:LED以约1Hz频率持续闪烁(亮500ms,灭500ms)。若LED常亮或常灭,检查PC13电平逻辑(低电平点亮)及GPIO初始化;若系统无反应,检查时钟配置与ThreadX汇编启动文件是否正确加入工程。
常见问题
- 编译报错找不到tx_api.h:确认ThreadX源码路径已加入头文件搜索目录。
- 链接错误Undefined symbol tx_thread_schedule:未将
tx_thread_schedule.s加入工程。 - 进入HardFault:检查系统时钟是否确实为72MHz,ThreadX节拍定时器(SysTick)配置依赖正确的时钟频率;同时确认线程栈大小足够(512字节在本案例中足够)。
以上流程展示了从系统上电到LED线程被调度的完整路径。核心要点在于tx_kernel_enter()不会返回,所有用户线程的创建必须在tx_application_define()回调中完成,该回调由内核在初始化完成后自动调用。