<<1.5、ARM裸机第五部分-SDRAM和重定位relocate>>
第一部分、章节目录
1.5.1、汇编写启动代码之关看门狗
1.5.2、汇编写启动代码之栈和调用c语言
1.5.3、汇编写启动代码之开iCache
1.5.4、重定位引入和链接脚本。
1.5.5、SDRAM引入
1.5.6、SDRAM初始化
1.5.7、重定位代码到SDRAM
第二部分、章节介绍
1.5.1、汇编写启动代码之关看门狗
使用汇编在启动代码中关闭看门狗,以防止启动过程中不喂狗导致复位,目的是认识看门狗这个外设,同时进一步熟悉ARM汇编编写
1.5.2、汇编写启动代码之栈和调用c语言
1.5.3、汇编写启动代码之开iCache
1.5.4、重定位引入和链接脚本。
1.5.5、SDRAM引入
1.5.6、SDRAM初始化
1.5.7、重定位代码到SDRAM
第三部分、随堂记录
1.5.1、汇编写启动代码之关看门狗
1.5.1.1、什么是看门狗?
看门狗(watch dog timer 看门狗定时器):家门口有一只狗,这狗定时会饿(比如两个小时一饿),如果狗饿了就乱咬人。人进进出出想要保持安全必须定时喂狗。
现实生活中 一些外部因素,电子设备经常会跑飞或死机,(比如极热,极寒,工业复杂场合)。在这种情况下我们希望设备自动复位(无人值守)。看门狗就是这样的工作原理。
工作原理:SoC内部的一个timer,在设定的时间内进行计时,在定时的时间内,如果没有喂狗,WDT就会复位SoC。
1.5.1.2、分析硬件物理特性、原理图、数据手册、
物理特性上WDT其实就是定时器(现实中类似于闹钟)。硬件上就是SoC内部的一个外设。
原理图:看门狗不用分析原理图,因为看门狗数据内部外设,没有外部相关的原件与他有关,所以不需要分析原理图,原理图上根本找不到WDT相关的地方。
数据手册:
1.5.1.3、总结210中看门狗特性(iROM)
为什么要关看门狗?
一般CPU设计时,在CPU启动后看门狗默认是工作的。(为了放置在启动时到开关门狗之间出现故障,故在在上电后就开启关门狗。能全程监控程序执行)
在S5PV210内部iROM代码中,其实已经关闭过看门狗了。所有我们的启动代码实际上不去关也没事。
但是在很多CPU内部没有BL0的,因此也没人关看门狗,都要在启动代码前段自己写代码关看门狗。
从datasheet得S5PV210看门狗定时器寄存器
4种时钟源可选择做看门够定时器时钟源,禁止/使能中断,禁止/使能看门狗定时器时间溢出。
看门狗定时器用于S5PV210从故障中恢复,如果不想控制复位,看门狗定时器需要禁止。如果想要将看门狗用于普通定时器,需要使能中断,和禁止看门狗
1、WTCON(Watchdog Timer Control Register)
看门狗控制寄存器
0xE270_0000 R/W
Reset Value:0x00008021
2、WTDAT(Watchdog Timer Data Register)
看门狗数据寄存器
0xE270_0004 R/W
Reset Value:0x00008000
3、WTCNT(Watchdog Timer Count Register)
看门狗定时器计数器
0xE270_0008 R/W
Reset Value:0x00008000
4、WTCLRINT(Watchdog Timer Interrupt Clear Register)
看门狗中断清除寄存器
0xE270_000C W
看门狗WTCON寄存器
使能/禁止定时器
WTCON寄存器用来禁止中断,禁止看门狗
1、WTCON(Watchdog Timer Control Register)
看门狗控制寄存器
0xE270_0000 R/W
Reset Value:0x00008021
Bit[5](Watchdog timer)
Enables or disables Watchdog timer bit.
0 = Disables
1 = Enables
Bit[2](Interrupt generation)
Enables or disables interrupt bit.
0 = Disables
1 = Enables
1.5.2、汇编写启动代码之栈和调用c语言
1.5.2.1、c语言运行时需要栈的意义
“c语言运行时runtime”需要一定条件,这些条件由汇编来提供。c语言运行时主要是需要栈
c语言与栈的关系;c语言的局部变量都是用栈来实现的。如果我们汇编部分没有给c部分预先设置合理合法的栈地址,那么c代码中定义的局部变量就会落空,整个程序就死掉了。
我们在平时编写单片机程序(比如51单片机)或者编写应用程序并没有去设置栈,但是c程序还是可以运行的。原因是:在单片机中由硬件初始化时提供了一个默认可用的栈,在应用程序中我们编写的C程序其实并不是全部,编译器(gcc)在链接的时候会帮我们自动添加一个头,这个头就是一段引导我们c程序能够执行的一段汇编代码,这个代码中帮我们的C程序设置了栈及其他运行时需要。
1.5.2.2、CPU模式和各种模式下的栈
在ARM中37个寄存器中,(User,FIQ,IRQ,SVC,Undef,Abort)六种模式下都有自己的sp(r13),为什么这样设计?
如果各个模式同时使用一个SP,那么就意味着真个程序(操作系统内核程序,用户自己编写的程序,)都是用一个栈。你的应用程序如果出错,就会连累到操作系统的栈。(sp是栈内存)
解决方案:每个模式用不同的栈,我们操作系统内核使用自己的栈,每个应用程序使用自己独立的栈,这样一个损坏不会连累其他的栈。
注意:系统在复位后默认是进入SVC模式的。
我们如何进入SVC模式下的SP呢?很简单,先把模式设置为SVC,再直接操作SP。但是因为我们复位后就已经是SVC模式了,所以直接设置SP即可。
R13 ---- 堆栈指针寄存器;在系统中用处理器作堆栈指针
R14 ---- 连接寄存器寄存器;执行调用指令或相应异常时,用于缓存返回地址。
R15
---- 程序计数器寄存器;它是个地址寄存器,总是指向下一条待取指的指令。
CPSR---- (R16)当前程序状态寄存器 (current program status register),就是
1.5.2.3、查阅文档并且设置栈指针至合法位置
栈必须是当前一段可用的内存(可用的意思就是这个地方必须已被初始化,可访问的内存,而且这个内存只会被我们用作栈,不会被其他程序征用)
当前CPU刚复位(刚启动),外部的DRRAM尚未初始化,目前的可用的内存只能在内部的SRAM(因为它不需要初始化就能用)因此我们只能在SRAM中找一段内存来作为SVC的栈。
在ARM中,ATPCS(ARM 关于程序应该怎样实现的一个规范)要求使用满减栈。
基本ATPCS寄存器的使用规则:
1. 子程序通过寄存器R0~R3来传递参数. 这时寄存器可以记作: A1~A4 , 被调用的子程序在返回前无需恢复寄存器R0~R3的内容.
2. 这时寄存器R4~R11可以记作: V1~V8 .如果在子程序中使用到V1~V8的某些寄存器, .在THUMB程序中,通常只能使用寄存器R4~R7来保存局部变量.
3. 寄存器R12用作子程序间scratch寄存器,记作ip; 在子程序的连接 代码段中经常会有这种使用规则.
4. 寄存器R13用作数据栈 指针,记做SP,在子程序中寄存器R13不能用做其他用途. 寄存器SP在进入子程序时的值和退出子程序时的值必须相等.
5. 寄存器R14用作连接寄存器,记作lr ; 它用于保存子程序的返回地址,如果在子程序中保存了返回地址,则R14可用作其它的用途.
6. 寄存器R15是 程序计数器,记作PC ; 它不能用作其他用途.
7. ATPCS中的各 寄存器在ARM编译器和 汇编器中都是预定义的.
通过查询Memory Map
SVC Stack
top:
0xD003_7D80
<--满减栈sp
bottom:0xD003_7780
<-- 增栈sp
1.5.2.4、汇编程序和c程序相互调用
链接报错led.o:(.ARM.exidx+0x0): undefined reference to `__aeabi_unwind_cpp_pr1'
解决方法 在 arm-linux-gcc 后面多加一个后缀 -nostdlib
原因是:我自己写了一个startup.S的启动文件,不需要系统自带的启动文件了
BL1 address:0xD002_0010
1.5.4.汇编写启动代码之开iCache
1.5.4.1、什么是cashe,有什么用?
cashe是一种内存,叫高速缓存。
cache高速缓冲存储器一种特殊的存储器子系统,其中复制了频繁使用的数据以利于快速访问
从价格来说:CPU > 寄存器 > cache > DDR > 硬盘/flash
从容量来说:CPU < 寄存器 < cache < DDR < 硬盘/flash
从速度来说:CPU > 寄存器 > cashe > DDR > 硬盘/flash
cashe的存在,是因为寄存器和DDR之间速度差异太大,DDR的速度远不能满足寄存器的需要(不能满足cpu的需要,所以没有cache会拉低整个系统整体速度)综合考虑了性价比较。
cache的意义;指令平时是放在硬盘/flash中的,运行时读取到DDR中,再从DDR中读给寄存器,再有寄存器送给cpu,但是DDR的速度和寄存器(代表CPU)相差太大,如果cpu运行完一句再去DDR读取下一句,。那么cpu速度完全被DDR速度拖慢了。解决方案就是icache和dcache。
210内部有32KB icache和32KB dcache。
icache用来缓存指令;dcache用来缓存数据。
icache工作时,会把我们cpu正在运行的指令和将要运行的指令,读取到icache中,(cpu设计有一个基本设计的原理:代码执行时主要按照顺序结构,所以将要执行的语句,有很大可能行时下一句。icache就将顺序结构的下一句指令存如内部,如果出现跳转,也只是影响一小部分。)(准备好提前量,缓存。这就是cache)
跳转是cache缓存不是要执行的,所以要清理缓存,再从新计算,从新缓存。
1.5.4.2、iROM中BL0对cache的操作。
首先:icache的一切不需要动作,都是自动的。我们所做的就是打开/关闭icache。
其次:在210的iROM中BL0已经打开了icache。所以之前看到的现象都是icache打开时的现象。
1.5.4.3、汇编代码读写cp15以开关cashe
1.5.4.4、实验验证
c1, Control Register
bit 12:Determines if instructions can be cached in any instruction cache at any cache level:
0 = instruction caching disabled at all levels, reset value
1 = instruction caching enabled.
MRC p15, 0, , c1, c0, 0 ; Read Control Register
MCR p15, 0, , c1, c0, 0 ; Write Control Register
// 3.icache 开/关
mrc p15, 0, r1, c1, c0, 0
bic r1, r1, #(1<<12)
//
orr r1, r1, #(1<<12)
mcr p15, 0, r1, c1, c0, 0
1.5.4、重定位引入和链接脚本。
1.5.4.1、一个事实:大部分指令是位置有关编码
位置无关编码(PIC, position independent code):汇编源码成二进制可执行程序时编码方式与位置(内存地址)无关。
位置有关代码:(PDC, position dependent code):汇编源代码成二进制可执行程序后和内存地址是有关的。
我们在设计一个程序时,会给程序指定一个运行地址(链接地址)。就是在编译程序时指定程序将来运行时的地址(运行地址)。而且必须给定编译器连接器指定这个地址(链接地址)。到最后二进制程序理论上是和你指定的运行地址有关的,将来这个程序被执行时必须放在当时编译器链接时给定的那个地址才行,否则不能运行。(位置有关代码PIC)
但是有个别的指令他可以跟指定的地址没有关系,也就是说这些代码其实运行时不管放在哪里都能正常运行。
1.5.4.2、链接地址(chained address)和运行地址(run-time address):可能相同也可能不同
对于PDC:最终执行(execute)时的run-time address和链接(link) 时的link address必须相同,否则一定出错。
在裸机程序的 Makefile中 -Ttext 0x0
来指定link address:0x0000_0000 这意味着我们人为这个程序将来会放在0x0000_0000的memory address 去 execute。
但是实际上我们execute时 地址address时:0xd0020010(我们用dnw下载时指定的download address)。这两个address看似不相同实际相同,因为在S05PV210内部做了address mapping(映射)。将SRAM mapping到了0x0000_0000 address去(在SoC内 IROM & IRAM 的地址是 0xd002_0000,又映射了0x0000_0000,故两个地址是同一个地址(去除16字节头))
分清楚两个概念:
link address: link时指定地址(指定方式:Makefile 中用 -Ttext,或者链接脚本)
execute address:程序execute时地址(指定方式:由实际运行时被加载到的内存的那个位置说了算(dnw))