智能车制作

 找回密码
 注册

扫一扫,访问微社区

查看: 9557|回复: 16
打印 上一主题 下一主题

【跟我学OSKinetis】第4课-从启动代码开始看!UART!

  [复制链接]

59

主题

1029

帖子

0

精华

版主

有什么需帮助的?

Rank: 9Rank: 9Rank: 9

积分
15139

热心会员奖章论坛元老奖章优秀会员奖章在线王奖章

威望
11068
贡献
1411
兑换币
1728
注册时间
2011-12-18
在线时间
1330 小时
跳转到指定楼层
1#
发表于 2013-10-21 11:11:21 | 只看该作者 回帖奖励 |正序浏览 |阅读模式
本帖最后由 洋葱圈 于 2013-10-21 11:17 编辑

注意:文中有一些字体高亮由于粘贴问题都丢失了,想看原版的可到官网原网页看,提问请在本帖提问。

(原网页地址:http://www.lpld.cn/?p=306


如果大家运行过前面几节课提到的例程,就会从串口调试助手看到许多启动信息,这些信息包含了当前固件库的版本号、单片机的内核时钟等信息。但是回到例程的app代码内,我们并没有找到这些内容的输出代码,那么它们跑到哪里去了呢?答案就是启动代码,也就是工程在运行到main()函数之前的那些代码。虽然这些代码是固件库所包含的代码,开发者不用更改,但是本着深入理解固件库和Kinetis启动流程的学习目的,我们还是要在学习UART的同时,来看看这些代码到底是怎样运行的。

启动代码

Kinetis单片机在运行到用户的main()函数之前,还要运行一些叫做启动代码的东西来进行寄存器、中断向量表、系统时钟初始化等操作。其中我们经常看到的启动信息的输出就是在系统时钟初始化之后进行的。

首先要找到这些启动代码在什么位置,在第2课中我们提到过,在IAR开发环境中的CPU组下包含的所有代码都是硬件相关,这里就是Kinetis启动代码的大本营了。其中startup_K60.s是单片机的汇编启动代码文件,system_MK60DZ10.c是K60的系统初始化c语言代码,可以统称为这些是Kinetis单片机的启动代码。

启动代码运行流程详解

startup_K60.s

在单片机的寻址空间的前1024个字节中存放着256个异常向量(exception vectors),其中我们只需要知道几个重要的即可。第一个向量是栈(Stack)指针,物理地址0×00000000;第二个向量是复位地址(Reset_Handler)指针,物理地址0×00000004;往后的所有向量都是系统中断和外设中断的地址指针了。

这些指针地址都是在startup_K60.s汇编文件内定义的,代码如下:


1
__vector_table
2
DCD sfe(CSTACK) ; Top of Stack
3
DCD Reset_Handler ; Reset Handler
4
DCD NMI_Handler ; NMI Handler
5
DCD HardFault_Handler ; Hard Fault Handler
6
DCD MemManage_Handler ; MPU Fault Handler
7
DCD BusFault_Handler  ; Bus Fault Handler
8
DCD UsageFault_Handler  ; Usage Fault Handler


Line 1:__vector_table是地址标号,代表以下代码的开始地址,它的值是由开发环境的链接文件决定的。IAR的话就是*.icf文件内定义的,打开\lib\iar_config_files\目录下的LPLD_K60DN512_FLASH.icf文件,你可以发现__VECTOR_TABLE符号已经被定义为了0×00000000。

Line 2:DCD是一个汇编伪指令,就是给指定的数据分配存储单元。例如这行,指定的数据是sfe(CSTACK),给该数据分配的存储地址就是0×00000000,该地址是上面的__vector_table标号决定的。CSTACK是*.icf文件内定义的一个,用于存放栈数据,sfe(CSTACK)代表取这个块的最后一个地址的下一个地址,为什么要取末尾的地址呢,因为栈数据是从一段空间的底部逐渐往顶部存入的,而读取是从栈顶部开始的。

Line 3:包含此行以后的代码就是系统中断指针地址和外设中断指针地址了。

当单片机上电或者复位后,单片机首先将CSTACK地址所存的地址指针读入到SP寄存器(堆栈指针寄存器),然后将Reset_Handler地址所存的地址指针读入到PC寄存器(程序计数寄存器),接下来单片机就会开始运行Reset_Handler地址开始处的程序代码了。代码如下:


1
Reset_Handler
2
LDR R0, =SystemInit ;执行系统初始化函数SystemInit()
3
BLX R0
4
LDR R0, =main ;执行用户主函数main()
5
BX  R0


Line 1:指明下一行的代码从地址Reset_Handler开始。

Line 2:将SystemInit函数地址给R0寄存器。

Line 3:跳转到R0所存的地址执行,即执行SystemInit()系统初始化函数。此时代码就已经跳转到system_MK60DZ10.c文件中的SystemInit()函数取执行了。

Line 4~5:同上,当执行完系统初始化函数后,紧接着执行用户app的main()函数,即用户自己的工程代码。

可见,当单片机上电或复位后,单片机的启动顺序是先初始化SP、PC寄存器,接下来就开始运行PC寄存器指向地址的代码了。

system_MK60DZ10.c

当单片机启动后,首先运行的代码就是该文件内的SystemInit()系统初始化函数。在这个函数中,系统干了这样几件事:1)时能全部IO口时钟、2)禁用看门狗、3)拷贝中断向量表和相关数据代码到RAM中、4)初始化相关总线时钟、5)打印系统初始化信息。

1)前面的时能IO口时钟是使单片机的所有IO口全部处于激活状态,如果在不先使能的状况下对相关IO口进行操作,就会触发系统硬件错误中断(HardFault_Handler)。

2)禁用看门狗模块会方便开发调试,以防在总线初始化或者调试过程中出现系统复位状况。

3)这一步是初学者比较难懂的部分,为什么要拷贝中断向量表、相关变量和函数到RAM中呢?大家都知道,RAM的读写速度要比ROM的读写速度快很多,而我们的中断向量表是从单片机存储空间的物理地址开头0×00000000处开始存储的,这部分属于ROM空间,为了让单片机能更快的响应中断事件,把中断向量表拷贝到RAM中运行是目前通行的做法。还有一些函数和变量也是要拷贝到RAM中的,这些变量指的是已经初始化的全局变量,函数指的是由__RAMFUNC关键字定义的函数,这些概念我们会在后面的小节中讲到。这部分用到的代码如下:


1
//将中断向量表、需在RAM中运行的函数等数据拷贝到RAM中
2
common_relocate();


Line 2:该函数是在\lib\common\目录下的relocate.c代码内实现的,大家可以自行参考注释。

4)这一部分的代码除了初始化各部分时钟,还包括读取各部分时钟的频率到相关全局变量内,以便固件库的其他模块进行调用。代码如下:


1
//初始化各部分时钟:系统内核主频、总线时钟、FlexBus时钟、Flash时钟
2
LPLD_PLL_Setup(CORE_CLK_MHZ);
3

4
//更新内核主频
5
SystemCoreClockUpdate();


Line 2:调用MCG模块的库函数LPLD_PLL_Setup()对时钟进行初始化,其中宏定义CORE_CLK_MHZ是定义在k60_card.h内的,用户可以自行修改数值以改变系统内核频率。

Line 5:获取实际初始化后的内核频率,将频率值赋值到全局变量SystemCoreClock中。

5)终于说到本节课的主题了——UART!这一步首先初始化输出调试信息需要用到的UART串口模块。代码如下所示:


1
term_port_structure.UART_Uartx = TERM_PORT;
2
term_port_structure.UART_BaudRate = TERMINAL_BAUD;
3
LPLD_UART_Init(term_port_structure);


Line 1:配置串口号,这里采用宏定义TERM_PORT,该定义在k60_card.h内定义为UART5,也就是说默认采用UART5模块输出调试信息。

Line 2:配置波特率,这里采用宏定义TERMINAL_BAUD,该定义在k60_card.h内定义为115200。

Line 3:条用初始化函数进行初始化。

初始化完UART5就该输出调试信息了,代码如下:


1
#ifdef DEBUG_PRINT
2
printf("\r\n");
3
//以下都是printf的函数调用,请看实际代码
4
#endif


Line 1:这里采用宏定义DEBUG_PRINT来控制系统启动时是否输出这些调试信息。

至此,OSKinetis固件库的启动代码就基本讲完了,一些对于初学者比较晦涩难懂的概念,我们会在后面解释,你也可以百度谷歌这些概念,看看大家的解释。例如一些汇编指令、堆栈的区别、icf链接文件的格式等等。


1

主题

42

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
429
威望
219
贡献
120
兑换币
139
注册时间
2016-10-18
在线时间
45 小时
毕业学校
天津大学
17#
发表于 2017-1-14 15:54:17 | 只看该作者
太棒了,学习到了好多
回复 支持 反对

使用道具 举报

4

主题

60

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1234
QQ
威望
607
贡献
365
兑换币
394
注册时间
2016-10-5
在线时间
131 小时
毕业学校
厦门大学嘉庚学院
16#
发表于 2016-12-18 14:19:06 | 只看该作者
洋葱圈 发表于 2013-10-21 11:12
UART模块讲解UART模块是通用异步收发器的英文缩写(Universal Asynchronous Receiver/Transmitter),这是 ...

优秀

回复 支持 反对

使用道具 举报

9

主题

127

帖子

0

精华

高级会员

Rank: 4

积分
916
威望
449
贡献
259
兑换币
288
注册时间
2013-5-19
在线时间
104 小时
毕业学校
洛阳理工
15#
发表于 2014-8-4 19:17:52 | 只看该作者
我想问一下这个SystemCoreClockUpdate();函数是不是可有可无啊????我不理解这个。。删了好像也没影响
回复 支持 反对

使用道具 举报

22

主题

321

帖子

0

精华

常驻嘉宾

Rank: 8Rank: 8

积分
3258

论坛元老奖章在线王奖章活跃会员奖章优秀会员奖章

QQ
威望
1776
贡献
824
兑换币
625
注册时间
2011-9-30
在线时间
329 小时
14#
发表于 2014-4-30 12:03:34 | 只看该作者
拉普兰德大锅,来顶你
回复 支持 反对

使用道具 举报

48

主题

493

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
2947
威望
1774
贡献
469
兑换币
705
注册时间
2012-4-22
在线时间
352 小时
毕业学校
hgd
13#
发表于 2014-4-13 22:11:17 | 只看该作者
洋葱圈 发表于 2014-4-12 20:18
__BOOT_STACK_ADDRESS不是一个变量,而是一个符号,仅仅代表一个地址。
0x20010000是RAM的最后一个字节地 ...

哦,对,0X20010000只是最后一个字节的下一地址,已超出RAM区。

我是在__INITIAL_SP地址处定义了一个数组,然后将数组名强制转换成函数指针。(KEIL5)如下:
__attribute__((section(".__INITIAL_SP"))) volatile  static  INT32U  __initial_sp[1] __attribute__((used));

谢谢您的回答!
回复 支持 反对

使用道具 举报

2

主题

321

帖子

0

精华

常驻嘉宾

Rank: 8Rank: 8

积分
3737
威望
1811
贡献
1188
兑换币
1144
注册时间
2014-2-17
在线时间
369 小时
毕业学校
非常小学
12#
发表于 2014-4-13 12:55:11 | 只看该作者
LPLD的支持支持
回复 支持 反对

使用道具 举报

2

主题

146

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
2584

优秀会员奖章活跃会员奖章

威望
1215
贡献
753
兑换币
737
注册时间
2013-9-28
在线时间
308 小时
11#
发表于 2014-4-13 12:36:35 | 只看该作者
正在用,很不错的底层库
回复 支持 反对

使用道具 举报

59

主题

1029

帖子

0

精华

版主

有什么需帮助的?

Rank: 9Rank: 9Rank: 9

积分
15139

热心会员奖章论坛元老奖章优秀会员奖章在线王奖章

威望
11068
贡献
1411
兑换币
1728
注册时间
2011-12-18
在线时间
1330 小时
10#
 楼主| 发表于 2014-4-12 20:18:41 | 只看该作者
m__dd 发表于 2014-4-11 23:22
关于K60,栈顶(MSP)为何是0x2000FFF8,而不是0x20010000
/*将SP指针指向RAM的最顶端*/
define exported ...

__BOOT_STACK_ADDRESS不是一个变量,而是一个符号,仅仅代表一个地址。
0x20010000是RAM的最后一个字节地址的下一个地址,你说的没错,堆栈要8字节对齐,所以要-8,就是0x2000FFF8。
其实在V3库中的icf文件已经不用__BOOT_STACK_ADDRESS这个符号了,而是直接用sfe(CSTACK)找到栈顶。
回复 支持 反对

使用道具 举报

48

主题

493

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
2947
威望
1774
贡献
469
兑换币
705
注册时间
2012-4-22
在线时间
352 小时
毕业学校
hgd
9#
发表于 2014-4-11 23:22:38 | 只看该作者
本帖最后由 m__dd 于 2014-4-11 23:24 编辑

关于K60,栈顶(MSP)为何是0x2000FFF8,而不是0x20010000
/*将SP指针指向RAM的最顶端*/
define exported symbol __BOOT_STACK_ADDRESS = __region_RAM2_end__ - 8;        //0x2000FFF8;

是不是因为:
  1、__BOOT_STACK_ADDRESS定义的变量本身占4字节,所以是0x2000FFFC
  2、考虑到8字节对齐,而不能是0x2000FFFC,而是0x2000FFF8

不好意思,已经给您留言,不要烦哦
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

关于我们|联系我们|小黑屋|智能车制作 ( 黑ICP备2022002344号

GMT+8, 2024-9-22 17:20 , Processed in 0.092806 second(s), 32 queries , Gzip On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表