概述
学习RT-Thread系统有一段时间了,感觉RT-Thread系统使用起来很简单,也很方便。但是在最开始移植的时候网上的教程很多,也很杂乱。比如可以使用官方的软件RT-Thread Studio 直接创建工程,创建好了之后系统就可以用了。也可以直接在 Keil MDK下载RT-Thread RTOS,下载完成后默认就会添加到工程中,系统直接也可以用了。还可以使用 CubeMX 移植 RT-Thread Nano,在创建工程的时候,直接将RT-Thread就添加进去了,工程创建完了之后,系统就直接可以使用。虽然会使用了,但是总感觉这种学习是知其然而不知其所以然。没有像学习其他系统时一步步拷贝文件,修改各种参数,那种深入的感觉。于是就查找各种文档,自己慢慢摸索着,从零开始如何通过拷贝文件和修改参数这种方法去真正自己动手移植系统。现在就将在STM32F103C8T6单片机上移植 RT-Thread Nano系统的方法分享出来。
工程中用到的电路板是STM32F103C8T6最小系统,所使用的MDK为 keil 5.29版本,使用的是STM32的标准库文件。
目录
- 1.第一步 裸板工程模板
- 2.第二步 下载RT-Thread Nano源码
- 3.第三步 拷贝rtt 到裸机工程中
- 4.第四步 删除rtt中用不到的文件
- 5.第五步 修改工程目录结构
- 6.第六步 编译工程并修改错误
- 7.第七步 编写第一个操作系统测试代码
- 8.第八步 修改board.c文件
- 9.第九步 rt-thread移植完成
- 10.第十步 rt-thread添加rt_printf支持
- 11.第十一步 rt-thread添加Finsh组件支持
1.第一步 裸板工程模板
要添加系统,首先得有一个完整的裸板工程,裸板工程的创建就不详细说了。这里用LED工程演示。
2.第二步 下载RT-Thread Nano源码
裸板工程准备好之后,下来需要准备RT-Thread的源码,去RT-Thread官网下载源码。
下载地址:https://www.rt-thread.org/document/site/tutorial/nano/an0038-nano-introduction/
准备好RT-Thread Nano的源码文件夹
3.第三步 拷贝rtt 到裸机工程中
在LED的裸机工程中新建一个rt-thread的文件夹,将刚才下载好的 rt-thread-3.1.3 文件夹拷贝到rt-thread文件夹中。
4.第四步 删除rtt中用不到的文件
rt-thread-3.1.3 文件夹中的内容比较多,在项目中只需要用到其中一部分文件,为了使代码看起来更清爽,需要将不需要的文件删除掉。打开工程中rt-thread--->rt-thread-3.1.3--->bsp文件夹。
BSP文件夹中只留下board.c 和 rtconfig.h文件 其余全部删除。
删除rt-thread--->rt-thread-3.1.3--->docs文件夹
删除rtt中用不到的文件 rt-thread--->rt-thread-3.1.3--->libcpu--->arm,只留下工程中用到的cortex-m3文件夹,其余全部删除。
5.第五步 修改工程目录结构
- 打开工程
- 打开工程项目管理 Manage Project Items
- 在Groups中选择USER,在右边Files中添加rt-threadrt-thread-3.1.3bsp文件中的board.c文件
- 在Groups中选择USER,在右边Files中添加rt-threadrt-thread-3.1.3bsp文件中的rtconfig.h文件
- 在Groups中选择新建,新建rtt/source组,在右边Files中添加rt-threadrt-thread-3.1.3src文件夹中的所有*.c文件
- 在Groups中选择新建,新建rtt/port组,在右边Files中添加rt-threadrt-thread-3.1.3libcpuarmcortex-m3文件夹下的context_rvds.S和cpuport.c文件
- 给工程添加头文件路径,凡是文件夹中包含*.h文件,就需要将文件夹路径添加进去
..rt-threadrt-thread-3.1.3bsp
..rt-threadrt-thread-3.1.3componentsfinsh
..rt-threadrt-thread-3.1.3includelibc
..rt-threadrt-thread-3.1.3include
6.第六步 编译工程并修改错误
准备工作完成了,下来就可以开始编译工程了,点全部编译按钮。
报了好多错误,基本都是不能打开文件 RTE_Components.h。头文件 RTE_Components.h是在 MDK中添加 RT-Thead Package 时由 MDK 自动生成的,目前我们没有使用 MDK 中自带的 RT-Thread 的 Package,所以这个头文件不存在,如果包含了该头文件,编译的时候会报错,需要修改 rtconfig.h 头文件,将相关代码注释掉。
打开 rtconfig.h 头文件,将 #include "RTE_Components.h" 这行代码注释掉。
注释之后再编译一次
这时候错误就剩下了三个了,提示是有三个中断函数重复定义了。这时因为RT-Thread系统把这三个中断函数已经实现了,但是在stm32f10x_it.c这个文件中,也有这个三个中断函数,但是函数内是空的,没有实现。需要将 stm32f10x_it.c 文件中的这个三个中断函数注释掉。
屏蔽掉这三个函数后,继续编译工程。
现在说明操作系统已经移植成功了。
7.第七步 编写第一个操作系统测试代码
下来就可以先简单的编写一个测试程序,先测试测试RT-Thread系统。这里直接使用动态线程的创建方法,而系统默认的是静态创建方法,所以先要修改配置文件。
删除rtconfig.h文件夹中第107行 前面的"//"符号,也就是将 #define RT_USING_HEAP 这行代码屏蔽去掉。
这个宏的含义就是,使用动态堆栈。
然后将main函数修改成下面这样
#include "stm32f10x.h"
#include "bsp_led.h"
#include <rthw.h>
#include "rtthread.h"
/* 定义线程控制块 */
static rt_thread_t led1_thread = RT_NULL;
/*
*************************************************************************
* 函数声明
*************************************************************************
*/
static void led1_thread_entry(void* parameter);
int main( void )
{
/* LED 端口初始化 */
LED_GPIO_Config();
led1_thread = /* 线程控制块指针 */
rt_thread_create( "led1", /* 线程名字 */
led1_thread_entry, /* 线程入口函数 */
RT_NULL, /* 线程入口函数参数 */
512, /* 线程栈大小 */
3, /* 线程的优先级 */
20); /* 线程时间片 */
/* 启动线程,开启调度 */
if (led1_thread != RT_NULL)
rt_thread_startup(led1_thread);
else
return -1;
}
/*
*************************************************************************
* 线程定义
*************************************************************************
*/
static void led1_thread_entry(void* parameter)
{
while (1)
{
LED1_ON;
rt_thread_delay(500); /* 延时500个tick */
LED1_OFF;
rt_thread_delay(500); /* 延时500个tick */
}
}
添加需要用到的RT-Thread系统头文件,定义线程控制块、声明线程入口函数。然后在主函数中动态创建一个led的线程。在led线程中闪烁LED灯。
编译工程,将代码下载到开发板上,就可以看见LED灯在闪烁了。
到这里RT-Thread实时操作系统移植已经全部完成了。
8.第八步 修改board.c文件
经过前面七步,RT-Thread实时系统已经可以运行了。但是为了方便工程管理,还需要对工程进行一点小改动。
由于board.c和rtconfig.h文件是在每个工程中都需要修改的,为了方便管理,将rt-threadrt-thread-3.1.3bsp文件夹中的board.c和rtconfig.h文件拷贝到工程中User文件夹中。这样User文件夹下的所有文件,就代表是用户可以修改的。而rt-thread文件夹中的文件在以后的工程中就不需要做任何改动了。
同时在User文件夹下新建 board.h 头文件,可以将工程用到的相关头文件,统一放到这个文件中。
在board.h头文件中添加以下内容
#ifndef __BOARD_H__
#define __BOARD_H__
/* STM32 固件库头文件 */
#include "stm32f10x.h"
/* RT-Thread相关头文件 */
#include <rthw.h>
#include <rtthread.h>
/* 开发板硬件bsp头文件 */
#include "bsp_led.h"
#endif /* __BOARD_H__ */
这个头文件中以后就可以存放在项目中所用到的所有相关头文件和函数声明。
下面还需要修改board.c文件,将board.c包含的头文件全部删除,只添加"board.h" 头文件
将时钟相关的宏定义和时钟配置函数全部屏蔽掉
屏蔽掉rt_hw_board_init()函数中的 SystemCoreClockUpdate(); 和 _SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND ); 函数调用。
重新添加时钟初始化语句 SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND );
将LED初始化代码从主函数中剪切到时钟初始化语句下面。
由于board.c和rtconfig.h文件位置发生了变化,所以要在工程管理中,将User文件夹下原来的board.c和rtconfig.h文件删除掉,然后重新从User文件夹里面添加这两个文件。
由于这两个文件已经移动了位置,所以头文件中就不需要 ..rt-threadrt-thread-3.1.3bsp这个路径了,将这个路径从头文件中删除。
修改后的主函数如下
下来就可以编译下载代码,如果LED灯正常闪烁,说明修改成功了。
9.第九步 rt-thread移植完成
最后就可以将上面步骤中屏蔽掉的那些代码删除了,这样以后在看代码时就会更清爽了,不会被那些无用的代码干扰了。删除stm32f10x_it.c中屏蔽的代码,删除board.c文件中屏蔽的代码。
由于工程中 rt-thread 文件夹中存放的都是关于操作系统的代码,这些代码比较关键,不能够随便修改。 rt-thread 文件夹中以前的board.c和rtconfig.h文件已经复制到了User文件夹里面,所以rt-thread--->rt-thread-3.1.3--->bsp,这个BSP文件夹就可以删除掉了。
下来就将工程目录下的 rt-thread 文件夹属性设置为只读,这样以后在编写代码过程中就不会因为误操作修改了系统文件而导致错误了。
在工程目录下rt-thread 文件夹上单击鼠标右键,选择属性。
打开属性对话框后,在只读前面点一下鼠标,让方框中打上对勾,然后点右下角 应用 按钮,在弹出的对话框中选择将更改应用于此文件夹、子文件夹和文件,然后点确定按钮,继续在属性对话框中点确定按钮。
这时关闭掉工程,在重新打开一次。
这时就会发现rtt相关文件前面都多了一个黄色的小钥匙图标,这就说明这些文件是只读的,不能修改。这样就不会因为误操作将操作系统的文件改动,而导致工程错误。
到这一步,一个完美的RT-Thread系统工程模板就创建好了,以后的项目只需要在这个模板上修改就行了。
10.第十步 rt-thread添加rt_printf支持
在平时调试代码的时候,经常需要用到串口的打印功能。RT-Thread提供了一个专用的打印函数rt_kprintf(),这个函数的功能和printf()一样。要实现串口打印功能,首先需要给工程添加串口初始化相关代码,串口初始化和裸机中串口的使用是一模一样的。这里串口只需要使能接收功能,发送功能和发送中断可以不使用。
在RT-Thread系统中rt_kprintf()函数是通过调用 rt_hw_console_output()函数来实现的。而rt_hw_console_output()函数系统默认是没有实现。需要用户自己实现。
下面就自己实现rt_hw_console_output()函数
在串口文件中添加rt_hw_console_output()函数实现代码,串口代码如下:
#include "bsp_usart.h"
#include "board.h"
/**
* @brief USART GPIO 配置,工作参数配置
* @param 无
* @retval 无
*/
void USART_Config( void )
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// 打开串口GPIO的时钟
DEBUG_USART_GPIO_APBxClkCmd( DEBUG_USART_GPIO_CLK, ENABLE );
// 打开串口外设的时钟
DEBUG_USART_APBxClkCmd( DEBUG_USART_CLK, ENABLE );
// 将USART Tx的GPIO配置为推挽复用模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure );
// 将USART Rx的GPIO配置为浮空输入模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init( DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure );
// 配置串口的工作参数
// 配置波特率
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
// 配置 针数据字长
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
// 配置停止位
USART_InitStructure.USART_StopBits = USART_StopBits_1;
// 配置校验位
USART_InitStructure.USART_Parity = USART_Parity_No ;
// 配置硬件流控制
USART_InitStructure.USART_HardwareFlowControl =
USART_HardwareFlowControl_None;
// 配置工作模式,收发一起
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
// 完成串口的初始化配置
USART_Init( DEBUG_USARTx, &USART_InitStructure );
// 使能串口
USART_Cmd( DEBUG_USARTx, ENABLE );
}
//*************************************************************//
// 系统rt_kprintf()函数是通过调用 rt_hw_console_output();来实现
// 而rt_hw_console_output();函数系统默认是没有实现的,所以如果要
// 使用rt_kprintf()函数,就需要用户自己实现rt_hw_console_output()函数
// 实现rt_hw_console_output()函数的前提是,要初始化串口功能,串口初始化
// 过程和裸机串口初始化过程完全一样
//************************************************************//
/**
* @brief 重映射串口DEBUG_USARTx到rt_kprintf()函数
* Note:DEBUG_USARTx是在bsp_usart.h中定义的宏,默认使用串口1
* @param str:要输出到串口的字符串
* @retval 无
*
* @attention
*
*/
void rt_hw_console_output( const char *str )
{
/* 进入临界段 */
rt_enter_critical();
/* 直到字符串结束 */
while ( *str != '