3. 移植RT-Thread Nano到STM32

任务目标

将RT-Thread Nano内核源码集成到STM32裸机工程中,完成基础时钟与SysTick中断配置,创建两个不同优先级的线程循环打印日志,通过串口输出验证RTOS调度器已正常启动并执行线程切换。

环境与依赖

  • 硬件:STM32F103C8T6最小系统板(蓝丸),USART1(PA9/PA10)接USB转串口模块
  • 软件:Keil MDK 5.x 或 STM32CubeIDE,STM32标准外设库或HAL库均可
  • RT-Thread Nano源码包(rt-thread-4.0.3\nano目录),包含rtthread.h、board.c、rtconfig.h等核心文件

移植步骤与关键配置

将Nano源码中的src、libcpu/arm/cortex-m3、include目录加入工程,并添加board.c与rtconfig.h。核心配置如下:

  • 时钟:SysTick中断频率设为1000Hz(即RT_TICK_PER_SECOND = 1000),作为RTOS系统节拍
  • 中断:在board.c中实现SysTick_Handler,调用rt_tick_increase();PendSV与SVCall中断由Nano自动接管
  • 内存:在rtconfig.h中定义RT_HEAP_SIZE为2KB,用于动态内存管理

完整实现代码

// rtconfig.h 关键配置
#define RT_THREAD_PRIORITY_MAX  8
#define RT_TICK_PER_SECOND      1000
#define RT_ALIGN_SIZE           4
#define RT_HEAP_SIZE            2048
#define RT_USING_COMPONENTS_INIT
#define RT_USING_CONSOLE
#define RT_CONSOLEBUF_SIZE      128

// board.c 关键实现(时钟与中断绑定)
#include "stm32f10x.h"
#include "rtthread.h"

void SysTick_Handler(void)
{
    rt_tick_increase();
}

void rt_hw_board_init(void)
{
    // 系统时钟72MHz,SysTick 1ms中断
    SystemInit();
    SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);

    // 串口1初始化(PA9=TX, PA10=RX, 115200-8-N-1)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
    GPIO_InitTypeDef gpio = {GPIO_Pin_9, GPIO_Speed_50MHz, GPIO_Mode_AF_PP};
    GPIO_Init(GPIOA, &gpio);
    gpio.GPIO_Pin = GPIO_Pin_10;
    gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &gpio);

    USART_InitTypeDef usart = {115200, USART_WordLength_8b, USART_StopBits_1,
                               USART_Parity_No, USART_Mode_TX | USART_Mode_RX};
    USART_Init(USART1, &usart);
    USART_Cmd(USART1, ENABLE);

    rt_console_set_device("uart1");  // 注册控制台设备
}

// 串口输出底层函数(rt_console 需要)
void rt_hw_console_output(const char *str)
{
    while (*str) {
        while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
        USART_SendData(USART1, *str++);
    }
}

// main.c 应用入口
#include "rtthread.h"

static void thread1_entry(void *param)
{
    while (1) {
        rt_kprintf("[Thread1] running, count: %d\n", (int)param);
        rt_thread_mdelay(500);   // 延时500ms
    }
}

static void thread2_entry(void *param)
{
    while (1) {
        rt_kprintf("[Thread2] running, count: %d\n", (int)param);
        rt_thread_mdelay(1000);  // 延时1000ms
    }
}

int main(void)
{
    rt_thread_t t1 = rt_thread_create("t1", thread1_entry, (void*)1,
                                      512, 5, 20);
    rt_thread_t t2 = rt_thread_create("t2", thread2_entry, (void*)2,
                                      512, 3, 20);
    if (t1) rt_thread_startup(t1);
    if (t2) rt_thread_startup(t2);

    return 0;
}

运行与验证

  1. 将上述文件加入Keil工程,勾选微库(MicroLIB)以避免半主机模式
  2. 编译下载后,打开串口助手(115200, 8N1),复位开发板
  3. 通过标准:串口交替输出[Thread1] running, count: 1与[Thread2] running, count: 2,且Thread1输出频率约为Thread2的两倍(500ms与1000ms间隔),说明两个线程独立运行且优先级调度正常
  4. 若仅输出一次或卡死,检查SysTick中断是否触发(在SysTick_Handler设断点)、rt_hw_console_output是否被正确调用

常见问题

  • 编译报错Undefined symbol SysTick_Handler:确认启动文件(startup_stm32f10x_md.s)中未定义同名中断,或删除启动文件中的弱定义
  • 串口无输出:检查GPIO复用时钟是否开启,以及rt_console_set_device是否在输出前调用
  • 线程不切换:确认RT_TICK_PER_SECOND与SysTick配置一致,且PendSV_Handler未在启动文件中被占用