**摘要**
本文主要介绍RT-Thread刚刚入手,以及RTT的上电启动过程分析。
**系统移植**
关于RTT的系统移植,我觉得还是很简单的,之前我做过针对自己的开发板做了一款BSP。参考RTT官网提供的BSP制作教程,一次性成功,唯一的是要花点时间。

而本次我准备使用使用的正点原子的战舰V3的板子来学习RTT,因为这个板子上的板载资源要丰富一些。当我准备按照之前的经验,做一款战舰V3的BSP的时候才发现,原来已经有成品了。那就本着拿来主义,先用用吧。

最终我在从官网下载的压缩包里面找到正点原子战舰V3的BSP。

个人推荐,可以把用不到的BSP删了,毕竟这个源码压缩包解压之后有700多M,还是蛮占用空间的。然后在rt-thread\bsp\stm32\libraries目录下,把其他用不到的库文件也删掉,只保留了F1的库文件夹即可。
最后强调一下:学习RTT,板子不是关键,不论是正点原子、野火、安富莱,还是F1、F4、F7都是可以的,不要纠结板子,最主要的了解RTT的使用方法。
程序下载进去之后,我使用的Xshell5软件,将板载的USB串口与PC连接:

Main.C文件的代码十分简单,就一个闪灯程序。

RTT的系统初始化,以及空闲任务等启动系统启动,都不见踪影,这不得不让我想一探究竟。
**代码框架分析**
首先,看了下启动文件startup_stm32f103xe.s,这个文件还是使用的是hal库提供的标准文件,RTT并没在这个上面做文章。

启动代码还是没有变,流程还是一样:
166行:将SystemInit()函数的入口地址放到R0寄存器
167行:跳转到R0地址的,开始执行SystemInit()函数,这个函数还是ST官方提供的函数。
168行,将__main函数的入口地址给到R0
169行:跳转到__main函数,开始执行。
需要注意的是__main()是编译系统提供的一个函数,负责完成库函数的初始化和初始化应用程序执行环境,最后自动跳转到main()。
所以说,前者是库函数,后者就是我们自己编写的main()主函数;
但是RTT的启动函数呢?
经过一番查找,RTT的启动用到了MDK提供的$Super$$ 和 $Sub$$ “补丁”函数的功能,这个是在__CC_ARM 编译器环境中,编译器提供的一个功能。下图为MDK提供官方原文:
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0377g/pge1362065967698.html

其意思是,以下文中的示例代码为例:
int $Sub$$main(void)
{
补丁函数();
$Super$$main();
return 0;
}
在进入main()之前,会先执行$Sub$$main(void)函数中的内容,
是以,补丁函数();得以优先运行。
最后当执行到$Super$$main()函数时,程序才开始跳转到main()函数,去执行main()函数的内容。
而RTT也就是用的这种“补丁”的方式,也下面我将整个RTT的初始化流程做成了一个简单的流程示意图,方便大家更明白的了解RTT的启动流程。

程序从启动文件的__main函数执行完毕之后,在准备进入main()函数之前,会优先执行$Sub$$main()函数,该函数包含了RTT的初始化过程,如下图所示。

在rtthread_startup()函数中完成对RTT的所有初始化。
另外需要额外的说明,在rt_application_init()函数中,定义了一个“main”线程。

这个main线程的入口函数是main_thread_entry(),就是我们的正主,main函数的跳转入口,$Super$$main()。

程序执行到rt_application_init()时,并没有真正意义上的执行到$Super$$main(),而是将含有$Super$$main()的这个任务线程放置到就绪状态。
最后,当程序执行到rt_system_scheduler_start()函数后,此刻任务调度器已经启动了,这个含有$Super$$main()的线程会立即得到执行,从而转入到我们的main函数。

所以,这就是为什么,我们看到这个main()函数这么简洁的原因。
不用我多说,相信大家也知道这样设计的好处:尽最大的可能,保证hal库,与RTT的文件保持独立,又能很好的相互衔接。降低RTT的移植难度。
**Shell串口初始化**
这个串口也就是我使用Xshell5连接的调试串口,为什么我要单独的把它拿出来说?因为,在我们设计新的硬件的时候,由于布线方便的原因,有时候会使用其他的串口作为调试串口。
所以,针对这个串口的初始化,我重点了看了下RTT的调试串口初始化,方便以后项目中需要调整的时候,进行调整。
我也是找了很久才看明白RTT是如何初始化调试串口的。
在说RTT的调试串口初始化之前,我建议先看看单纯使用HAL库的时候,串口是如何进行初始化的,我做了截图,如下图所示:

在HAL库中,先对UartHandle结构体进行赋值,在HAL_UART_Init(&UartHandle)函数中完成串口引脚的初始化,和串口外设的配置(波特率、数据位等等)。

好了,说到了这里,在回头RTT的代码吧!根据上文,RTT是在rt_hw_board_init()函数中的rt_hw_usart_init()函数中进行串口初始化。

在rt_hw_usart_init()函数中,需要重点注意stm32_uart_ops结构体

这个stm32_configure就是我们调试串口的初始化,至于这个.configure = stm32_configure的写法,意思是,将stm32_configure赋值给configure,这里我不多做解释。
继续查看stm32_configure函数,就可以看如下图所示:

这里已经和单独使用HAL库,初始串口的流程一样了,在HAL_UART_Init(&uart->handle)函数中,将会对调试串口的引脚、以及外设参数进行配置。
**结语**
从RTT的代码中,能看到很对linux嵌入式开发的影子,同时,其又能和ST的HAL进行兼容与配套,如果有兴趣的朋友可以研究下,每当看了一段时间RTT的代码,不禁发出感叹“原来如此!”。
总之,学习RTT一定会让你有所收获!
原创作品,未经权利人授权禁止转载。详情见转载须知。
举报文章