【嵌入式】RT-Thread入门(一)内核移植
文章目录
- 0 前言
- 1 RT-Thread Nano
- 2 Keil项目下移植
- 3 STM32CubeMX移植
- 4 代码启动流程
0 前言
因为项目的关系,需要使用实时操作系统RT-Thread,虽然很早之前就听过他的大名,但一直没有正式接触,所以也算是入门,打算写几篇文章来记录一下自己的学习例程。
1 RT-Thread Nano
既然要认真学,那肯定得参考官方的资料。首先打开官方的文档,结构还是非常明确的:
可以先从新手指导开始学习,了解整个RTOS的框架,以及各个部分的介绍,这里暂且按下不表。如果是刚接触RT-Thread,建议还是从Nano版本开始。
2 Keil项目下移植
只看文档是没有用的,关键在于实操。这里以Keil这个最常用的嵌入式开发软件,以STM32这个最常见的芯片为例,来介绍RT-Thread Nano下的移植方法,还是比较简单的。
首先在Pack Installer中找到RT-Thread(如果一直在加载中记得科学上网):
在左侧随便选中一个期间,然后在右边一直划到最底下(按字母排序,R),然后安装即可。
如果实在是加载不出来,也可以到ARM Keil官网去下载——> 链接直达。
然后正常安装即可,这一步比较简单。
安装好之后,就可以开始使用了。先找一个可以编译通过且正常运行的项目工程,至少要启用一个串口,然后在RTE中启用RT-Thread:
一般来说,只启用kernal和shell即可,device框架并不完善,如果实在要用还是移步标准版。
回到工程,可以发现多了一些文件,其中不带黄色钥匙图标的文件(非只读文件)才需要修改,也是添加到项目工程目录中的文件,其他带🔑的文件只是一个引用,原文件并没有复制到项目工程下。
- 1 finsh终端移植
首先点开board.c文件,可以看到其实已经用error标示了怎么移植(如果直接编译就可以通过error直接定位到这里)
所以第一个地方直接调用串口初始化的函数,第二个地方调用串口输出的函数即可。
和上面一样,字符串输入也需要移植,在finsh_port.c
中:
- 2 屏蔽由RTOS接管的中断
当添加RT-Thread时,它会自动接管HardFault和PendSV两个异常中断(在context_rvds.s
文件中),所以原先stm32f10x_it.c中的中断服务函数需要注释掉,不然会报重复定义的错误。
- 3 系统时钟中断函数
还是在上面的board.c文件中,有一个RT-Thread所需要的时钟中断函数:
需要将这个函数添加到Systick_Handler
中:
所有都移植之后,再点击编译,理论上应该是不会再报错了。
3 STM32CubeMX移植
作为STM32开发未来的趋势,STM32CubeMX其实也已经支持直接在配置中添加RT-Thread Nano内核。RT-Thread官网给出的教程是需要额外添加一个链接,才能够安装RT-Thread包,但其实,新版的STM32CubeMX已经可以直接下载安装RT-Thread库:
同样,在Middleware中也可以看到RT-Thread的选项:
这里再简单提个题外话。RT-Thread官网给出的教程是需要添加一个链接,然后再下载安装,而且展示的安装版本是3.1.5,和Keil中是一致的,但是我实测发现最新版本只有3.1.3,但实际添加的链接是完全一致的,所以猜测可能是出啥问题导致版本回退了?反倒是直接通过Cube安装的这个带X-CUBE前缀的RT-Thread包的版本是4.1.1,好像要新一点,所以建议还是使用Cube自带的包比较好。而且官网的B站账号下的教程视频也是使用这个版本:
STM32CubeMX使用RT-Thread nano v4.1.1资源包教学
题外话结束,接下来看一下怎么使用。
和配置其他外设一样,先选中然后使能对应的组件:
根据上面的视频介绍,kernal、libcpu是必选,shell就是finsh终端,也建议选上。配置好之后,只是表示这个可以选,但还需要使能并配置,如下图所示:
这里需要注意:配置这些的时候一定要仔细看下面的提示,因为设置时不会自动解决依赖关系,这点需要自己手动配置好,比如使能定时器就需要使能堆内存。
和Keil一样,因为使能了RTOS,有些异常中断会被RTOS接管,因此在生成代码这里要取消生成:
另外,很重要的一点是,在使能了RT-Thread,如果TimeBase还是Systick的话,会弹出一个警告:
上面的视频教程中是直接忽略了,但可能是和芯片型号有关,我在使用STM32F7时,如果这里仍然坚持使用Systick,那么程序运行就会卡在HAL_Delay函数这里,猜测应该是中断优先级设置有问题,因为Systick的中断也被接管了,中断的优先级到底是多少也不清楚,如果遇到这种情况,不用太纠结,建议直接换一个定时器。
最后,再来介绍几个可能会出现的错误:
报错找不到board.h文件,为了避免每次生成都需要改一次,这里可以自己定义一个board.h文件,空的都行。
这里的寄存器有问题,在STM32F7的HAL库中,数据寄存器分为输入数据寄存器(RDR)和输出数据寄存器(TDR),根据上下文语境,这里应该是输入数据寄存器,所以改为RDR,而且,这里有一个问题就是else部分中的延时,经过测试,发现这个延时没有任何作用,反倒是会导致数据接收不完整等问题,所以这里也需要注释掉。改完之后如下所示。
4 代码启动流程
为什么可以在原先裸机代码工程的基础上直接添加一个RT-Thread,而且不用修改任何原来的代码,这到底是怎么实现的呢?其实就涉及到了RT-Thread项目的启动流程。如果在芯片中仿真,可以发现,程序执行完了启动代码之后,首先跳转的并不是main函数,而是components.c
中的这里:
执行完了这个rtthread_startup
函数之后才会跳转到main函数。那这个在函数名中加上$Sub$$
是啥意思?其实这个涉及到MDK编译器的一个功能——即给一个函数打补丁,实现在已经被“固定死”的函数前后加上一段自定义的代码。
一般$Sub$$
要和$Super$$
一起使用,其中,后者用来声明需要打补丁的函数,前者用来定义补丁的内容,举个例子:
//以下demo来自arm mdk开发者手册
//这个demo比较清晰地列出了补丁操作的框架,这个操作可以说十分简单但是考验概念理解力;
//执行完sub函数之后,在sub函数中extern一下 super函数,然后调用super函数回到原函数去执行;
extern void ExtraFunc(void); //补丁操作;
extern void $Super$$foo(void):
void $Sub$$foo(void)
{
ExtraBeforeFunc(); // 函数执行之前的补丁操作;
$Super$$foo(); // 通过$Super$$foo()调用函数原型;
ExtraAfterFunc(); // 函数执行之后执行的部分
}
Reference