STM32F103RB移植RT-Thread Nano
移植的主要工作内容
内核是操作系统最基础也是最重要的部分。上图为 RT-Thread 内核架构图,内核处于硬件层之上,内核部分包括内核库、实时内核实现。
内核库是为了保证内核能够独立运行的一套小型的类似 C 库的函数实现子集。这部分根据编译器的不同自带 C 库的情况也会有些不同,当使用 GNU GCC 编译器时,会携带更多的标准 C 库实现。
提示:C 库:也叫 C 运行库(C Runtime Library),它提供了类似 “strcpy”、“memcpy” 等函数,有些也会包括
“printf”、“scanf” 函数的实现。RT-Thread Kernel Service Library 仅提供内核用到的一小部分 C
库函数实现,为了避免与标准 C 库重名,在这些函数前都会添加上 rt_前缀。
实时内核的实现包括:对象管理、线程管理及调度器、线程间通信管理、时钟管理及内存管理等等,内核最小的资源占用情况是 3KB ROM,1.2KB RAM。我们的目标平台STM32F103RBT6有 128 KB ROM 和 20 KB SRAM,可支持RT-Thread Nano精简内核的运行。
从上面的架构图可知,RT-Thread内核移植的主要工作有两块,即分别根据芯片和开发板资源的配置移植修改 芯片架构支持部分 libcpu
和 板级支持包部分BSP
。
基于 CubeMX 移植 RT-Thread Nano
接下来介绍如何基于 CubeMX 移植 RT-Thread Nano,并说明生成代码工程的步骤。
RT-Thread Nano 已集成在 CubeMX 中,可以直接在 IDE 中进行下载添加。本文档介绍了如何使用 CubeMX 移植 RT-Thread Nano,并以一个 stm32f103 的基础工程作为示例进行讲解。
移植 Nano 的主要步骤:
- 准备一个 CubeMX 基础工程,并获取 RT-Thread Nano pack 安装包进行安装。
- 在基础工程中添加 RT-Thread Nano 源码。
- 适配 Nano,主要从 中断、时钟、内存、应用 这几个方面进行适配,实现移植。
- 最后可对 Nano 进行配置:Nano 是可裁剪的,可以通过配置文件 rtconfig.h 实现对系统的裁剪。
准备工作
- 下载 Cube MX 5.0 ,下载地址 https://www.st.com/en/development-tools/stm32cubemx.html 。
- 在 CubeMX 上下载 RT-Thread Nano pack 安装包。
Nano pack 安装
要获取 RT-Thread Nano 软件包,需要在 CubeMX 中添加 https://www.rt-thread.org/download/cube/RealThread.RT-Thread.pdsc 。
具体步骤:进入打开 CubeMX,从菜单栏 help
进入 Manage embedded software packages
界面,点击 From Url
按钮,进入 User Defined Packs Manager
界面,其次点击 new
,填入上述网址,然后点击 check
,如下图所示:
check
通过后,点击 OK 回到 User Defined Packs Manager
界面,再次点击 OK,CubeMX 自动连接服务器,获取包描述文件。回到 Manage embedded software packages
界面,就会发现 RT-Thread Nano 3.1.5
软件包,选择该软件包,点击 Install Now
,如下图所示:
点击安装之后,弹出 Licensing Agreement
,同意协议,点击 Finish
,如下图所示:
等待安装完成,成功安装后,版本前面的小蓝色框变成填充的黄绿色,现象如下图所示:
至此,RT-Thread Nano 软件包安装完毕,退出 Manage embedded software packages
界面,进入 CubeMX 主界面。
创建基础工程
在 CubeMX 主界面的菜单栏中 File
选择 New Project
,如下图所示
新建工程之后,在弹出界面芯片型号中输入某一芯片型号,方便锁定查找需要的芯片,双击被选中的芯片,如下图所示
添加 RT-Thread Nano 到工程
选择 Nano 组件
选中芯片型号之后,点击 Softwares Packages
->Select Components
,进入组件配置界面,选择 RealThread
, 然后根据需求选择 RT-Thread 组件,然后点击 OK 按钮,如下图所示:
注意:RT-Thread Nano 软件包中包含 kernel, shell 和 device 三个部分,仅选择 kernel 表示只使用 RT-Thread 内核,工程中会添加内核代码;选择 kernel 与 shell 表示在使用 RT-Thread Nano 的基础上使用 FinSH Shell 组件,工程中会添加内核代码与 FinSH 组件的代码,FinSH 的移植详见 《在 RT-Thread Nano 上添加控制台与 FinSH》。再选择 device 表示使用 rt-thread 的 device 框架,用户基于此框架编写外设驱动并注册后,就可以使用 device 统一接口操作外设。
配置 Nano
选择组件之后,对组件参数进行配置。在工程界面 Pinout & Configuration
中,进入所选组件参数配置区,按照下图进行配置
工程管理
给工程取名、选择代码存放位置、选择生成代码的 Toolchain/IDE
。Cube MX
不仅能够生成 Keil4/Keil5
的工程,而且还能够生成 IAR7/IAR8
等 IDE 的工程,功能强大,本文从下拉框中选择 MDK5,操作如图所示
代码生成选项卡Code Gennerator无需调整。
配置 MCU
根据需求配置 MCU 的功能。
在这个例子中我们会用到正点原子STM32F103 Nano开发板的GPIO(LED,按键)和 UART(控制台和Finsh组件),还需配置其运行时钟。所以需要在CubeMX中进行相应配置。
功能模块 | 关联引脚 |
---|---|
LED 0~7 | PC 0~7 |
KEY 0 | PC8 |
KEY 1 | PC9 |
CONSOLE | USART1 |
打开HSE和LSE。
将主频设为最高的72MHz。
适配 RT-Thread Nano
中断与异常处理
RT-Thread 操作系统重定义 HardFault_Handler
、PendSV_Handler
、SysTick_Handler
中断函数,为了避免重复定义的问题,在生成工程之前,需要在中断配置中,代码生成的选项中,取消选择三个中断函数(对应注释选项是 Hard fault interrupt
, Pendable request
, Time base :System tick timer
),最后点击生成代码,具体操作如下图 所示:
SysTick_Handler
将被用作操作系统的时基,如为了能成正常使用HAL 库的时基函数HAL_Delay,可将HAL时基改为TIM1基本定时器。
等待工程生成完毕,点击打开工程,如下图所示,即可进入 MDK5 工程中。
MDK工程文件介绍
打开MDK工程,可看到RT-Thread已被配置到工程目录中。
点开kernel文件夹
控制台CONSOLE的串口配置
其中移植工作中的芯片架构部分已经由CubeMX设置后自动生成完成,即其中的 context_rvds.S
文件内容,我们只需要完成BSP部分的移植即可,在board.c
文件中,点击打开文件。
在第71行,可看到,若我们使能了CONSOLE,会自动添加下面的UART初始化代码,这里只需要把其中的USART2修改为我们实际使用开发板上的UART1即可。
系统时钟配置
需要在 board.c 中实现 系统时钟配置
(为 MCU、外设提供工作时钟)与 OS Tick 的配置
(为操作系统提供心跳 / 节拍)。此处无需修改,用系统生成的默认代码即可。
如下代码所示, HAL_Init()
初始化 HAL 库, SystemClock_Config()
配置了系统时钟, SystemCoreClockUpdate()
对系统时钟进行更新,_SysTick_Config()
配置了 OS Tick。此处 OS Tick 使用滴答定时器 systick 实现,需要用户在 board.c 中实现 SysTick_Handler()
中断服务例程,调用 RT-Thread 提供的 rt_tick_increase()
,如下图所示。
/* board.c */
void rt_hw_board_init()
{
HAL_Init();
SystemClock_Config();
/* System Clock Update */
SystemCoreClockUpdate();
/* System Tick Configuration */
_SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
/* Call components board initial (use INIT_BOARD_EXPORT()) */
#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
}
RT-Thread系统配置文件rtconfig.h文件
我们通过 rtconfig.h 文件查看和修改各个宏定义信息,从而配置和裁剪RT-Thread操作系统。通过任意rtthread文件中的万能头文件 rtthread.h 即可找到rtconfig.h。打开后,可利用MDK的图形化配置向导功能,快速对RT-Thread进行配置。这里我们对照下图箭头处进行修改即可。
编写第一个应用
移植好 RT-Thread Nano 之后,则可以开始编写第一个应用代码。此时 main() 函数就转变成 RT-Thread 操作系统的一个线程,现在可以在 main() 函数中实现第一个应用:板载 LED 指示灯闪烁,并使用rt_kprintf打印。
-
首先在文件首部包含 RT-Thread 的相关头文件
<rtthread.h>
。
-
在 main() 函数中注释掉下图部分,这些初始化已在完成RT-Thread启动时完成。
-
在
MX_GPIO_Init()
函数后添加INIT_BOARD_EXPORT(MX_GPIO_Init);
即可将GPIO的初始化加入RT-Thread的启动过程。
-
在 main() 函数中(也就是在 main 线程中)写CONSOLE打印代码 和 LED 闪烁代码,在循环中点亮 / 熄灭 LED。
-
延时函数使用 RT-Thread 提供的延时函数 rt_thread_mdelay(),该函数会引起系统调度,切换到其他线程运行,体现了线程实时性的特点。
编译程序之后下载到芯片就可以看到基于 RT-Thread 的程序运行起来了,LED 正常闪烁。
Note注:当添加 RT-Thread 之后,裸机中的 main() 函数会自动变成 RT-Thread 系统中 main 线程
的入口函数。由于线程不能一直独占 CPU,所以此时在 main() 中使用 while(1) 时,需要有让出 CPU 的动作,比如使用
rt_thread_mdelay()
系列的函数让出 CPU。
与裸机 LED 闪烁应用代码的不同:
1). 延时函数不同: RT-Thread 提供的 rt_thread_mdelay()
函数可以引起操作系统进行调度,当调用该函数进行延时时,本线程将不占用 CPU,调度器切换到系统的其他线程开始运行。而裸机的 delay 函数是一直占用 CPU 运行的。
2). 初始化系统时钟的位置不同:移植好 RT-Thread Nano 之后,不需要再在 main() 中做相应的系统配置(如 hal 初始化、时钟初始化等),这是因为 RT-Thread 在系统启动时,已经做好了系统时钟初始化等的配置,这在上一小节 “系统时钟配置” 中有讲解。
在 Nano 上添加 FinSH 组件(实现命令输入)
RT-Thread FinSH 是 RT-Thread 的命令行组件(shell),提供一套供用户在命令行调用的操作接口,主要用于调试或查看系统信息。它可以使用串口 / 以太网 / USB 等与 PC 机进行通信,打开通信软件PuTTY,根据端口号和配置的串口波特率进行配置。
使用 FinSH 组件基本命令的效果图如下所示:
输入help或直接按TAB键可调出当前可使用的MSH命令。
MSH命令有自动补全功能,输入头几个字母可按TAB自动补全。
PS命令可查看当前系统运行线程的情况。
运行官方内核例程
作为一个操作系统,RT-Thread 的代码规模怎么样呢?在弄清楚这些之前,我们先要做的就是获得与本文相对应的 RT-Thread 的例子,这份例子可以从以下链接获得:
这个例子是一个压缩包文件,将它解压,我们这里解压到 D:/。解压完成后的目录结构如下图所示:
各个目录所包含的文件类型的描述如下表所示:
目录名 | 描述 |
---|---|
applications | RT-Thread 应用程序。 |
rt-thread | RT-Thread 的源文件。 |
- components | RT-Thread 的各个组件目录。 |
- include | RT-Thread 内核的头文件。 |
- libcpu | 各类芯片的移植代码,此处包含了 STM32 的移植文件。 |
- src | RT-Thread 内核的源文件。 |
- tools | RT-Thread 命令构建工具的脚本文件。 |
drivers | RT-Thread 的驱动,不同平台的底层驱动具体实现。 |
Libraries | ST 的 STM32 固件库文件。 |
kernel-sample-0.1.0 | RT-Thread 的内核例程。 |
kernel-sample-0.1.0
中存放了RTT内核相关的演示例程,我们将其复制放入自己的工程文件中。
使用工程管理工具,在创建例程目录,例程文件添加进工程。因STM32F103RB FLASH和SRAM限制,建议每次只将要使用的一个例程文件加入。此处只加入thread_sample.c
,并将程序中的 thread1/2
栈空间由512B
和1024B
改为256B
。
编译、烧录、运行,即可在串口控制台发现先导入的例程命令。输入命令thread_sample
即可运行查看例程演示效果。
如此,可对照官方参考文档,将 内核基础 结合例程学习一遍。