【超详细】STM32F407ZGT6 + HAL库 手把手移植RT-Thread(附源码)

前言

在嵌入式开发中,裸机开发仅能实现简单的前后台循环逻辑,面对多设备协同、多任务并行、定时处理等复杂场景,存在任务阻塞、实时性差、代码耦合度高、维护难度大等诸多问题。而RT-Thread作为国内开源、轻量化、高实时性的嵌入式RTOS,兼具小巧精简、稳定可靠、生态完善的优势,是单片机裸机开发进阶实时操作系统开发的最优选择之一。

本文采用STM32CubeMX原生HAL库裸机工程 + 纯手动移植RT-Thread的方式,从零完成内核适配、时钟节拍配置、串口控制调试、多任务测试,摒弃一键生成框架,拆解每一步移植核心逻辑,规避各类适配冲突问题。教程适配新手、步骤完整、落地性强,看完即可独立完成RT-Thread HAL库版本移植,彻底掌握Cortex-M内核RTOS底层适配核心思路。

开发环境

  • 主控芯片:STM32F407ZGT6

  • 编译工具:Keil MDK5

  • 配置工具:STM32CubeMX 6.17.0

  • 操作系统:RT-Thread 3.1.5(轻量稳定,适配F4内核)

  • 驱动库:STM32F4xx HAL库 最新版


目录

前言

一、下载 RT-Thread 源码

二、RT-Thread 源码概述

2.1 RT-Thread 目录结构

2.2 RT-Thread 文件介绍

2.2.1 bsp

2.2.2 libcpu

2.2.3 src

2.2.4 RT-Thread 核心文件

三、CubeMX生成HAL裸机工程

3.1 芯片选型

3.2 配置系统时钟

3.3 设置SYS

3.3 配置GPIO

3.4 配置UART

3.5 生成最终工程

四、移植RT-Thread

4.1 移植时涉及的主要文件

4.1.1 CPU 部分

4.1.2 BSP 部分

4.2 移植操作

4.3 RT-Thread 源码改写

五、上机测试

六、源码分享


一、下载 RT-Thread 源码

获取地址:https://www.rt-thread.org/download.html

这里我们选择rt-thread-3.1.5.zip。

选择 3.1.5 版本的主要原因是:

  • 经典稳定:经过长期工业验证,内核稳定,Bug 极少。

  • 体积极小:压缩包仅 122MB(相比 5.x 的近 500MB),去除了冗余的板级支持包,下载解压快。

  • 适配成熟:对 STM32F1/F4 等经典 MCU 的底层支持极其完善,开箱即用。

  • 工具链友好:高度兼容老版本 MDK/IAR,无需强迫迁入新版 RT-Thread Studio 开发环境。

(注:如果是新品开发,日后需要联网、LVGL等复杂组件,建议尽量升级到 5.x 版本;如果只是学习或老项目延续,3.1.5 足够。)


二、RT-Thread 源码概述

2.1 RT-Thread 目录结构

2.2 RT-Thread 文件介绍

2.2.1 bsp

bsp目录下是各单板的板级支持包(Board support package),即针对某个特定单板兼容适配的工程。比如ST官方公板:stm32\stm32f407-st-discovery。

移植时,需要针对特定单板适配串口端口、GPIO等。

2.2.2 libcpu

为了在不同CPU架构芯片上运行,RT-Thread提供 libcpu 抽象层对内核提供统一接口,包括全局中断开关、线程栈初始化等。

移植时,需要针对特地CPU架构实现全局中断开关、线程上下文切换等。

2.2.3 src

src文件夹的内容是RT-Thread 的核心文件。

2.2.4 RT-Thread 核心文件

以理解、使用RT-Thread 为目标的话,最核心的文件只有5个:

  • src\ipc.c
  • src\scheduler.c
  • src\thread.c
  • src\timer.c
  • include\rtservice.h

这些文件的作用列表如下:

重要的文件作用
src\thread.c线程核心文件,实现线程创建、启动、挂起、删除等全套线程管理功能
src\timer.c软件定时器相关实现,依托系统节拍完成定时唤醒、周期调度
src\scheduler.c任务调度器核心:维护线程就绪链表、实现优先级抢占调度,提供临界区进出保护函数
src\ipc.c线程间通信组件实现,包含消息队列、信号量、互斥量、事件集等同步互斥对象
include\rtservice.h封装通用双向链表操作接口,为线程、定时器、IPC 对象提供底层链表支撑

三、CubeMX生成HAL裸机工程

3.1 芯片选型

打开STM32CubeMX,选择芯片STM32F407ZGT6,新建工程。

3.2 配置系统时钟

在RCC中,配置外部高速晶振(HSE)。

时钟树:倍频至168MHz(F407最大主频)。

注意:HSE(外部高速晶振)的频率需要根据实际开发板上的晶振规格进行配置。

3.3 设置SYS

开启SYS时钟、调试接口(SWD模式)。

3.3 配置GPIO

开发板上,PF9、PF10为LED1、LED2,低电平点亮。

3.4 配置UART

配置USART1(PA9/PA10)为异步串口,这里不用作FinSH控制台,只作任务输出信息用。另外这里不用中断和DMA。

3.5 生成最终工程

选择MDK-ARM,以及配置工程,如下图:


四、移植RT-Thread

4.1 移植时涉及的主要文件

移植RT-Thread,包含CPU 部分移植和BSP 部分移植。

4.1.1 CPU 部分

CPU 部分移植就是移植libcpu 目录,主要涉及cpuport.c 和context_xx.S。

比如:libcpu/arm/cortex-m4/cpuport.c 和 context_rvds.S,这表示 Cortex-M4 架构在 RVDS 或Keil 工具上的移植文件。

  • cpuport.c:主要用来初始化线程的栈。
  • context_rvds.S:主要线程切换、全局中断开关。

如果RT-Thread 还没支持你使用的CPU,你才需要实现这两个文件。 一般来说,对于大部分的CPU, RT-Thread都已经支持,不需要我们移植。

4.1.2 BSP 部分

BSP 就是Board Support Packet,板级支持包,就是开发板的相关文件。

BSP 部分主要涉及main.c、board.c 和rtconfig.h

比如:bsp/stm32/stm32f407-atk-explorer 目录下,包含 applications/main.c 、 board/board.c 和 rtconfig.h

  • main.c:工程的主函数入口 。
  • board.c:该单板的系统时钟相关配置 。
  • rtconfig.h:内核配置文件,配置裁剪系统功能。

4.2 移植操作

(1)将解压得到的 rt-thread-3.1.5 源码文件夹,复制到裸机工程目录下,并在该目录中新建一个名为“RT-Thread”的文件夹。

(2)在新建的 RT-Thread 文件夹中,分别创建 config、board、libcpu 三个子文件夹。接着,在 libcpu 文件夹内创建 arm 文件夹,并在 arm 文件夹中继续创建 cortex-m4 文件夹。

(3)将 \F407_RTT_HAL\rt-thread-3.1.5\rt-thread-3.1.5\bsp\stm32\stm32f407-atk-explorer 目录下的 rtconfig.h 文件,复制到新建的 RT_Thread\config 文件夹中。

(4)将 \F407_RTT_HAL\rt-thread-3.1.5\rt-thread-3.1.5\bsp\stm32\stm32f407-atk-explorer\board 目录下的 board.cboard.h 文件,复制到新建的 RT_Thread\board 文件夹中。

(5)将 \F407_RTT_HAL\rt-thread-3.1.5\rt-thread-3.1.5\libcpu\arm\cortex-m4 目录下的 context_rvds.Scpuport.c 文件,复制到 \F407_RTT_HAL\RT-Thread\libcpu\arm\cortex-m4 目录中。

(6)将 \F407_RTT_HAL\rt-thread-3.1.5\rt-thread-3.1.5 目录下的 includesrccomponents 文件夹,完整复制到 \F407_RTT_HAL\RT_Thread 目录下。

(7)删除 \F407_RTT_HAL\RT-Thread\components 目录下的 finsh 文件夹。

(8)在 Keil5 中新建四个组别:RT_Thread/configRT_Thread/boardRT_Thread/libcpuRT_Thread/src

(9)将对应的文件分别添加到这四个新建的组中。

(10)在 keil5 中添加对应路径

4.3 RT-Thread 源码改写

(1)在 rtconfig.h 文件中,注释掉 RT_USING_FINSH 宏定义。由于本工程不需要命令行交互调试功能,因此需要在组件配置中关闭 FinSH。

(2)为避免函数重定义冲突,需在 stm32f4xx_it.cstm32f1xx_it.h 文件中注释掉 PendSV_HandlerSysTick_HandlerHardFault_Handler 这三个中断服务函数。

(3)根据目标板卡需求,重新编写 board.c 文件。

/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2018-11-06     SummerGift   first version
 */
#include "board.h"
#include <rthw.h>
#include <rtthread.h>
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
/* 定义内存堆的大小与空间 /
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
/
Please modify RT_HEAP_SIZE if you enable RT_USING_HEAP
the RT_HEAP_SIZE max value = (sram size - ZI size), 1024 means 1024 bytes
/
#define RT_HEAP_SIZE (151024)
static rt_uint8_t rt_heap[RT_HEAP_SIZE];
RT_WEAK void *rt_heap_begin_get(void)
{
return rt_heap;
}
RT_WEAK void *rt_heap_end_get(void)
{
return rt_heap + RT_HEAP_SIZE;
}
#endif
void rt_os_tick_callback(void)
{
rt_interrupt_enter();
rt_tick_increase();

rt_interrupt_leave();
}
/**
This function will initial your board.
/
void rt_hw_board_init(void)
{
/ 初始化 SysTick */
extern void SystemClock_Config(void);
SystemClock_Config();
//下面放硬件初始化
MX_GPIO_Init();
MX_USART1_UART_Init();
#ifdef RT_USING_COMPONENTS_INIT
rt_components_board_init();
#endif
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
#endif
}
#ifdef RT_USING_CONSOLE
/**
@brief 串口控制台初始化(此处未使用,所以保留空函数即可)
*/
static int uart_init(void)
{
//#error "TODO 2: Enable the hardware uart and config baudrate."
return 0;
}
INIT_BOARD_EXPORT(uart_init);
/**
@brief 重定向 RT-Thread 默认打印输出(未使用 rt_kprintf,留空不实现)
*/
void rt_hw_console_output(const char *str)
{
//#error "TODO 3: Output the string 'str' through the uart."
}
#endif
void SysTick_Handler(void)
{
/* 进入中断 */
rt_interrupt_enter();
/* 更新时基 */
rt_tick_increase();

/* 离开中断 */
rt_interrupt_leave();
}

(4)重新写board.h。

/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2018-11-5      SummerGift   first version
 */
#ifndef BOARD_H
#define BOARD_H
#include "stm32f4xx_hal.h"
void rt_hw_board_init(void);
void SysTick_Handler(void);
#endif

(5)重定向 printf 函数,在 usart.c 文件的开头和末尾添加以下代码。

/* USER CODE BEGIN 0 */
#include <stdio.h>

/* USER CODE END 0 */
/* USER CODE BEGIN 1 */
int fputc(int ch, FILE *f)
{
    HAL_UART_Transmit(&huart1, (const uint8_t *)&ch, 1, 50); 
    return ch;
}
/* USER CODE END 1 */

50 而不用 0xFFFF,核心原因就一个:防止硬件异常时线程被永久卡死。

  • 0xFFFF (HAL_MAX_DELAY) 是无限等待,一旦串口状态位没响应,函数永不返回,线程甚至整个系统都会挂掉。

  • 50ms 是有上限的阻塞,足够正常发送几百个字节(115200 下非常充裕)。万一出错也会超时返回,系统还能继续跑。

(6)编写 main.c 文件。在该文件中,我们静态创建了一个线程(线程1,用于串口打印“thread1 run”),并动态创建了两个线程:线程2(串口打印“thread2 run”)和线程3(控制 LED1 和 LED2 交替闪烁)。

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2026 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "board.h"
#include "rtthread.h"
#include "stdio.h"

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
static struct rt_thread thread1;	//定义线程1句柄
static struct rt_thread *thread2;	//定义线程2句柄指针
static struct rt_thread *thread3;	//定义线程3句柄指针

static rt_uint8_t thread1_stack[1024];//设置线程1的内存栈

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* 线程1的入口函数 */ 
static void thread1_entry(void *parameter)
{
	
	while(1)
	{
		printf("Thread1 run\r\n");
    
		rt_thread_delay(1000);
	}
}

/* 线程2的入口函数 */ 
static void thread2_entry(void *parameter)
{
	
	while(1)
	{
		printf("Thread2 run\r\n");
		
		rt_thread_delay(2000);
	}
}

/* 线程3的入口函数 */ 
static void thread3_entry(void *parameter)
{
	
	while(1)
	{
		HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
		HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);
		
		rt_thread_delay(1000);
		
		HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
		HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);
		
		rt_thread_delay(1000);
	}
}

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  
  /* 初始化静态线程1,名称是Thread1,入口是thread1_entry */
  rt_thread_init(&thread1,					//线程句柄  
				 "thread1",                 //线程名字 
                 thread1_entry,    			//入口函数 
                 RT_NULL,                   //入口函数参数 
                 &thread1_stack[0],         //线程栈起始地址 
                 sizeof(thread1_stack),     //栈大小 
                 5,               			//线程优先级 
                 15);                 		//线程时间片大小
				 
  /* 启动线程1 */
  rt_thread_startup(&thread1); 				 
  
  /* 创建动态线程2,名称是thread2,入口是thread2_entry*/
  thread2 = rt_thread_create("thread2",              	//线程名字 
							 thread2_entry, 			//入口函数 
                             RT_NULL,                	//入口函数参数 
							 512,         				//栈大小 
                             5,            			//线程优先级 
                             15);          				//线程时间片大小
  
  /* 判断创建结果,再启动线程2 */
  if (thread2 != RT_NULL)
  {
	rt_thread_startup(thread2);	
  }
  
  /* 创建动态线程3,名称是thread3,入口是thread3_entry*/
  thread3 = rt_thread_create("thread3",              	//线程名字 
							 thread3_entry, 			//入口函数 
                             RT_NULL,                	//入口函数参数 
							 512,         				//栈大小 
                             5,            			//线程优先级 
                             15);          				//线程时间片大小
  
  /* 判断创建结果,再启动线程2 */
  if (thread3 != RT_NULL)
  {
	rt_thread_startup(thread3);	
  } 
  
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  rt_thread_delay(1000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 12;
  RCC_OscInitStruct.PLL.PLLN = 168;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

(7)在 Keil5 中配置工程,勾选“Use MicroLIB”选项,并完成 Debug 相关配置。

问题:在测试过程中,发现三个线程均未运行,串口无数据输出,LED 灯也不亮。

分析:通过调试发现,程序卡在 _sys_open 函数处。经排查,原因是代码中使用了 printf 这类标准 C 库 I/O 函数,而工程未勾选 Use MicroLIB 选项。标准库在启动时会尝试通过半主机(semihosting)打开 stdout,在没有调试器连接的情况下,该操作会无限等待,导致程序无法进入 main 函数,所有线程因此无法运行。

解决:有两种解决方案:一是在 Keil5 工程配置中勾选 Use MicroLIB;二是自行实现 _sys_open_write 等函数,以绕过半主机机制。


五、上机测试

下载程序到STM32F407ZGT6开发板,上电后LED周期闪烁,串口会交替输出,说明多任务线程正常调度。


六、源码分享

通过网盘分享的文件:F407_RTT_HAL.zip
链接: https://pan.baidu.com/s/1Mj4Uxjb7Pf5DU3K5cbc6gQ?pwd=taoq 提取码: taoq 
--来自百度网盘超级会员v3的分享

参考与致谢

RT-Thread 官方文档

基于STM32的嵌入式操作系统RT-Thread移植教学(HAL库版本)_rt-thread stm32移植-CSDN博客

【RT-Thread】基于STM32+HAL库的RT-Thread移植(纯手动)_rtthread-CSDN博客

以上资料提供了清晰的 HAL 库手动移植框架,本文在此基础上进行了实际验证与细节补充,特此致谢。

后续将更新使用RT-Thread Studio从零开始创建标准示例工程。

若发现文中疏漏,欢迎指正交流。创作不易,还望多多支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值