此为本人初学RT-Thread的笔记可以参考如有错误可以指正谢谢。RT-Thread简介RT-Thread介绍RT-Thread全称 Real Time-Thread是一款由国内团队开发的开源嵌入式实时操作系统。它的核心特点可以概括为​不只是内核更是一个生态​这是它和FreeRTOS最核心的区别。除了实时内核它内置了丰富的中间件比如​FinSH命令行你可以用串口输入命令调试系统​、虚拟文件系统、轻量级TCP/IP网络协议栈LwIP甚至图形用户界面GUI组件。这意味着用RTT开发复杂项目时很多功能都开箱即用。​高度可裁剪​它采用模块化设计。针对资源受限的MCU可以裁剪出只占用3KB Flash和1.2KB RAM的NANO版本对于资源丰富的设备又能像搭积木一样从软件包中心添加各种功能组件比如MQTT、加密库、传感器驱动等。​活跃的中文社区​RTT在国内有非常活跃的开发者社区和丰富的中文文档对国内开发者很友好遇到问题更容易找到解决方案。RT-Thread版本RT-Thread主要提供了三个版本以适应从资源受限的MCU到需要复杂应用的高性能芯片等不同开发场景版本核心定位与特点资源占用适用场景Nano 版本​极简硬实时内核​只包含最基础的线程管理、同步通信等功能没有设备驱动框架和组件。极小~3KB Flash, ~1.2KB RAM。​资源极度受限的MCU​如STM32F0系列等入门级32位ARM芯片常用于简单的家电、传感器节点等。标准版本​功能完整的IoT OS平台​在Nano内核基础上集成了丰富的组件如设备驱动框架、文件系统、网络协议栈LwIP以及强大的软件包生态。根据配置的功能而定通常在几十KB到几百KB的Flash和RAM。​资源丰富的IoT设备​需要联网、文件存储、图形界面等复杂功能的应用是大多数开发者的首选。Smart 版本​混合操作系统​引入了用户态和内核态的隔离支持独立的进程和地址空间依赖MMU应用和内核可以分开开发运行。相比标准版更高需要芯片支持MMU。​需要类似Linux开发模式的高性能应用​应用复杂、对安全性要求高或需要从Linux平台移植代码的场景。常运行在ARM Cortex-A系列等应用处理器上。FreeRTOS与RTT的差异从 FreeRTOS 入门 RTT学习路径有FreeRTOS基础再来学RT-Thread其实思路会非常顺——很多内核概念线程、信号量、消息队列都是通用的。可以把RT-Thread看作一个功能更完整的全家桶而FreeRTOS更像一个轻量的内核引擎​对比维度FreeRTOSRT-Thread定位轻量级实时内核专注任务调度与同步。完整的IoT OS平台集成了丰富的组件和服务。核心功能内核为主网络、文件系统等需自行集成。内核​FinSH控制台​设备驱动框架文件系统网络协议栈UI等。开发工具依赖第三方IDE如Keil、IAR或配合VS Code等。官方提供一站式IDE​RT-Thread Studio​集成了工程创建、配置、调试等功能学习建议三步走第一步抓核心差异不要从头学线程、信号量这些概念。重点关注RTT特有或不同的地方比如​FinSH控制台​这是RTT的特色。学会用它来调试系统查看线程状态、内存使用甚至执行自定义函数对开发调试帮助很大。​设备驱动框架​RTT把各种外设如UART、I2C、SPI都抽象成了设备。学习如何通过统一的rt_device_xxx​接口来访问硬件而不是直接操作寄存器。​临界区保护机制​RTT和FreeRTOS在关中断的实现上略有不同RTT操作PRIMASK寄存器FreeRTOS操作BASEPRI寄存器可以对比了解一下以防踩坑。​第二步上手实战用项目驱动学习​找一块开发板官方推荐STM32系列直接​用RT-Thread Studio创建一个带FinSH的工程​。从一个简单的点灯任务开始尝试创建一个线程然后在FinSH里查看它。再试着用设备框架读取一个传感器比如I2C的温湿度传感器感受一下设备驱动的用法。​第三步善用资源加入社区​​官方文档中心​这是最权威、最全面的学习资料内容覆盖了内核、组件、工具链的方方面面。​参考书籍​如果想系统学习可以看《嵌入式实时操作系统RT-Thread原理与应用》这类书籍它通常会结合具体硬件如STM32进行项目式讲解。​软件包生态​在RTT的软件包中心找一些你感兴趣的项目比如MQTT通信、或者一个GUI Demo看看别人是怎么组织代码和配置系统的这是快速提升的捷径。RTT Nano版本移植到STM321.首先需要有一个STM32工程空工程就行无论什么库此处以STM32CubeMxKeil工程移植。2.下载RT-Thread源码库官网rt-thread.org/download.html3.解压然后在工程那里新建一个OS文件夹把解压的RTT文件夹复制到OS文件夹bsp --示例代码和配置文件components --组件docs --文档include --头文件libcpu --CPU移植文件src --Nano内核源码4.然后删除不需要的文件rt-thread/bsp文件夹只保留两个文件其余全部删除rt-thread/libcpu根据芯片选择对应文件其余删除包括risc-v文件夹STM32F103C8T6 的核心是ARM架构 Cortex-M32进入cortex-m4文件这里只需要context_rvds.s和cpuport.c文件因为是keil,可以根据你自己的编译环境选择保留其余文件删除文件一句话作用​context_gcc.S​为 GCC 编译器提供线程切换、开关中断的汇编代码。​context_iar.S​为 IAR 编译器提供同样的功能只是汇编语法不同。​context_rvds.S​为 ​Keil MDK​ARM Compiler提供同样的功能。​cpuport.c​用 C 语言实现线程栈初始化和 HardFault 异常捕获等核心功能。简单说你用的什么编译器Keil/IAR/GCC就把对应的 context_xxx.S​ 文件加入工程cpuport.c​ 则是通用的、必须添加的。然后把这些的文件也删除5.打开工程做好工程基础配置然后建立分组RTT/src添加src目录下全部的.c文件添加bsp目录下的board.c和rtconfig.h文件到App中(注意选择All Files才能看到文件)因为这两个文件属于配置文件和应用层开发的文件我们会对这两个文件进行修改RTT/ports添加这两个文件rt-thread\libcpu\arm\cortex-m3RTTt/finsh添加rt-thread\components\finsh的全部文件6.添加工程路径把所添加文件的路径全部包含即可7.编译会报错。此时我们先不急先添加一个头文件。然后编译错误会少很多8.注释两个中断服务函数RTT内部已实现void HardFault_Handler(void)和void PendSV_Handler(void),后面重新生成代码也要注释。9.此时还有报错找到borad.c文件可以看到报错的地方我们把报错的内容删除掉随后添加 SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND ); 和在文件开头添加 #include stm32f1xx.h根据自己的芯片选择头文件再次编译发现0报错和0警告10.给RTT添加心跳时基。这个很关键。打开stm32f1xx_it.c文件找到SysTick_Handler函数在函数内部添加rt_os_tick_callback();之后编译即可FreeRTOS需要占用systick芯片时基需要选择其他定时器。但是RTT也这样做的话我发现时基会有所不对rt_os_tick_callback();要与系统systick一起调用的中断才准确。也就是HAL的时基与RTT的时基需要一起。RT-Thread的时钟频率则通常由rtconfig.h​中的RT_TICK_PER_SECOND​决定SysTick的中断处理函数直接调用rt_tick_increase()​逻辑更直接​RT-Thread 和 FreeRTOS 对 SysTick 的使用方式不同​特性FreeRTOSRT-ThreadSysTick 所有权独占可共享HAL 时基必须改用其他定时器可以和 RTT 共用 SysTick​SysTick_Handler​只调用 xPortSysTickHandler()​可以同时调用 HAL_IncTick()​ rt_os_tick_callback()​FreeRTOS 的 xPortSysTickHandler()​ 会进行​任务切换​如果在中断里同时调用 HAL_IncTick()​可能导致中断嵌套冲突时间基准混乱任务切换时栈指针错乱所以 FreeRTOS 要求 HAL 库使用其他定时器如 TIM1作为时基源。RT-Thread 的 rt_os_tick_callback()​ 只做节拍计数​rt_tick_increase()​​不进行任务切换​。任务切换是在 PendSV_Handler​ 中完成的优先级最低。所以​SysTick_Handler​只更新系统节拍计数非常轻量​PendSV_Handler​执行实际的任务上下文切换SysTick 中断里可以安全地调用 HAL_IncTick()​不会影响 RTT 的调度。11.创建一个简单的线程并运行记得配置对应引脚使用//线程任务函数 void led_task(void*param) { rt_tick_t tick rt_tick_from_millisecond(500);//转换对应节拍数 while(1) { HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_1); rt_thread_mdelay(tick);//延时 } } void led_task2(void*param) { rt_tick_t tick rt_tick_from_millisecond(500); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET); rt_thread_mdelay(tick); while(1) { HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8); rt_thread_mdelay(tick); } } //主函数入口 void main() { /* 创建LED闪烁线程 */ rt_thread_t led_thread rt_thread_create( led, // 线程名称 led_task, // 线程入口函数 RT_NULL, // 入口函数参数 1024, // 线程栈大小 5, // 线程优先级数字越小优先级越高范围0~31 25 // 时间片 ); rt_thread_t led_thread2 rt_thread_create( led2, // 线程名称 led_task2, // 线程入口函数 RT_NULL, // 入口函数参数 1024, // 线程栈大小 5, // 线程优先级数字越小优先级越高范围0~31 25 // 时间片 ); /* 如果创建成功启动线程 */ if(led_thread ! RT_NULL) { rt_thread_startup(led_thread); } if(led_thread ! RT_NULL) { rt_thread_startup(led_thread2); } while(1)//会执行到这里这里的优先级不定义的话默认是10如果不让出cpu那么低于其优先级的任务将无法得到执行 { rt_thread_mdelay(10); } }函数介绍rt_thread_t rt_thread_create(const char *name, void (*entry)(void *parameter), void *parameter, rt_uint32_t stack_size, rt_uint8_t priority, rt_uint32_t tick);参数你的值含义建议​name​​led​线程名称起个有意义的名字​entry​​led_task​入口函数必须有 while(1)​ 让出CPU​parameter​​RT_NULL​传给入口函数的参数需要传参时用地址​stack_size​​1024​栈大小字节LED任务 512 足够1024 更安全​priority​​0​优先级0最高​改为 10 或 15​不建议用0​tick​​20​时间片节拍数默认 10~20 即可特性​rt_thread_delay(tick)​​rt_thread_mdelay(ms)​单位系统时钟节拍tick毫秒ms本质底层实现函数对 rt_thread_delay​ 的封装效果让出CPU延时指定节拍数让出CPU延时指定毫秒数基础移植已完成。增加rt_kprintf功能一个串口打印组件底层需要配置实际的串口我这里配置USART1:PA9,PA10引脚1、打开RT_USING_CONSOLE宏可以使用图形化配置找到rtconfig.h文件打开RT_USING_CONSOLE宏随后编译找到board.c文件删除相关的报错内容其中uart_init是我们rt_kprintf打印功能初始化的部分这里我们用USART1进行打印只需要调用串口 初始化函数即可。rt_kprintf函数会调用rt_hw_console_output进行数据输出这里我们在这个函数内部进行串口1输出即可。写代码static int uart_init(void) { return 0; } INIT_BOARD_EXPORT(uart_init); void rt_hw_console_output(const char *str) { rt_enter_critical(); while (*str ! \0) { // 处理换行\n - \r\n if (*str \n) { uint8_t ch \r; // 发送回车符 HAL_UART_Transmit(huart1, ch, 1, 100); } // 发送当前字符 HAL_UART_Transmit(huart1, (uint8_t*)str, 1, 100); str; // 移动到下一个字符 } rt_exit_critical(); }调用void led_task2(void*param) { rt_tick_t tick rt_tick_from_millisecond(100); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET); rt_thread_mdelay(tick); static uint32_t led_togggle_num0; while(1) { HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8); led_togggle_num; rt_kprintf(led_togggle_num%d\n,led_togggle_num);//打印LED翻转次数 rt_thread_mdelay(tick); } }​​增加FinSH功能RT-Thread 的 FinSH 功能就是建立在这个基础上的交互式命令行组件Shell。它让你的设备能通过串口接受命令并执行是开发和调试的利器1、添加finsh宏定义打开rtconfig.h文件在开头添加如下宏定义// 控制台相关 #define RT_USING_CONSOLE // 启用控制台 #define RT_CONSOLE_DEVICE_NAME uart1 // 使用串口1 //#define RT_CONSOLEBUF_SIZE 128 // 控制台缓冲区大小 // FinSH 相关 #define RT_USING_FINSH // 启用 FinSH #define FINSH_USING_MSH // 使用 msh 模式 #define FINSH_THREAD_NAME tshell // FinSH 线程名称 #define FINSH_USING_HISTORY // 启用命令历史 #define FINSH_HISTORY_LINES 5 // 历史命令行数 #define FINSH_USING_SYMTAB // 启用符号表 #define FINSH_CMD_SIZE 80 // 命令缓冲区大小 #define FINSH_THREAD_PRIORITY 20 // FinSH 线程优先级 #define FINSH_THREAD_STACK_SIZE 4096 // FinSH 线程栈大小 #define RT_MAIN_THREAD_PRIORITY 25 //main函数线程的优先级记得添加finsh文件的路径2.增加rt_hw_console_getchar函数使用串口1进行输入操作//ch-1似乎会导致异常可以把-1改为0试试//这其实是一个 char rt_hw_console_getchar(void) { char ch -1;//-1似乎会导致异常可以把-1改为0试试 uint8_t rx_data 0; int level rt_hw_interrupt_disable(); // 检查是否有数据可读 if (__HAL_UART_GET_FLAG(huart1, UART_FLAG_RXNE) ! RESET) { rx_data (uint8_t)(huart1.Instance-DR 0xFF); ch (char)rx_data; __HAL_UART_CLEAR_FLAG(huart1, UART_FLAG_RXNE); } else { // 检查溢出错误 if (__HAL_UART_GET_FLAG(huart1, UART_FLAG_ORE) ! RESET) { __HAL_UART_CLEAR_OREFLAG(huart1); } } rt_hw_interrupt_enable(level); // 没有数据时让出CPU if (ch -1) //-1似乎会导致异常可以把-1改为0试试 { rt_thread_mdelay(1); } return ch; }3.添加串口初始化功能正常可以使用命令基本的移植与使用就到这里了。