1.版本
本文代码版本
代码IDE版本:Keil5
开发板:STM32F103ZET6
2.代码流程
我们从.s文件开始
根据芯片,我们的启动代码为startup_stm32f103xe.s,如下图我们可以看到和其他启动代码并没有区别,在先进入SystemInit函数初始化时钟后,经过一系列操作,最后跳转main函数
但在这里,RTT使用了MDK的一个"补丁"功能,给main函数打了一个补丁。如下图。这里__CC_ARM即通过MDK的编译器编译时,会编译下面的函数。文件名components.c
tips:
当MDK链接器链接代码链接到a()时,如果存在$Sub$$a(),则会把a()替换为$Sub$$a(),
而在遇到$Super$$a()时,则会把它替换为a()
官方详解如下:
ARM Compiler v5.06 for µVision armlink User Guide
所以可知,其实在启动文件结束后,其实代码到了$Sub$$main()里,函数rt_hw_interrupt_disable很明显是关闭中断,我们不去深究。我们仔细看一下rtthread_startup这个函数,代码如下,文件名components.c
其中main函数包含在rt_application_init里面的main任务的入口函数里,不过这里我们不作详细展开。我们看一下串口的初始化,它包含在rt_hw_board_init函数里,这个函数主要包含了所有板级初始化,代码如下,文件名:board.c
这里我们可以看一下所有外设包含串口是怎么初始化的。代码如下。文件名components.c
可以看到初始化代码非常简单,只有一个for循环,起始就是执行了从 __rt_init_rti_board_start 到 __rt_init_rti_board_end 的所有函数指针所指向的函数,起始函数地址为 __rt_init_rti_board_start 所指内容。查看 __rt_init_rti_board_start 与 __rt_init_rti_board_start 定义可以看到:
这里 INIT_EXPORT() 宏定义如下,文件名:rtdef.h
其中 RT_USED 宏定义如下:
作用即标记该变量是已使用的,即使未被使用,链接器也不得删除。(链接器会自动删除未被使用的段)
另外 init_fn_t 是一个入参为空,返回为int的函数指针。定义如下:
而宏定义 SECTION(".rti_fn."level) 定义如下:
attribute((section(x))) 为将该变量存于名称为x的段内
所以其实 INIT_EXPORT(rti_board_start, “0.end”); 这句的意思就是:
__attribute__((used)) const init_fn_t __rt_init_rti_board_start __attribute__((section(.rti_fn.0.end))) = rti_board_start
也就是把等于rti_board_start的函数指针__rt_init_rti_board_start放在段名为.rti_fn.0.end里。
同理可得
上面这句的意思就是
把等于rti_board_end的函数指针__rt_init_rti_board_end放在段名为.rti_fn.1.end里
所以初始化过程就是历遍段名为 .rti_fn.0.end 到段名为 .rti_fn.1.end 之间的所有函数并运行。所以在这两者之间到底有哪些函数?
答案是所有调用 插入这部分段 的宏的函数。也就是那个函数调用宏插入到这部分段里了,那部分函数就会在这个for循环里被执行。而插入的宏为下图,文件名:rtdef.h
可以看到这里有很多宏,分别可以插入到后缀为1到6的段。
tips
这里没有找到MDK链接器的相关内容,笔者猜测链接器链接时会根据名称排序,所以段名后缀为**.1**的会被插入到 .1.end 之前,而段名后缀为 .2 到 .6 的会被插入到 .6.end 之前,这个链接顺序为隐式规则。
所以在文件名:drv_usart.c中可以看到
rt_hw_usart_init函数里面包含了串口1的初始化,这里RTT使用了类似LINUX的设备驱动分离框架,不作展开。可以简单的理解为现在USART1已经初始化完成可以使用了。
我们回到 rt_hw_board_init 函数,它里面还剩下一个 rt_console_set_device 没有分析,从名字就可以看出这是函数是为了设置控制台(物理层终端),所以它要放在所有外设初始化完成后。函数代码如下:
可以看到入参为name字符串,此处name正是串口1:
这里 rt_device_find 是寻找与入参同名设备,函数内容如下:
函数里面的 rt_object_get_information 函数内容比较简单就是历遍结构体数组,对比数组里type元素是否与入参相同,相同则返回该结构体指针。代码如下:
我们知道前面初始化已经把串口1初始化完成了,那列表内肯定存在一个名为usart1的设备了,所以肯定可以找到,那它返回的设备信息块(结构体)到底是什么呢?