使用汇编代码启动S5PV210开发板。
使用汇编进行启动,需要经过下面几个基本步骤:
- 关看门狗
- 设置栈
- 初始化iCache
看门狗
看门狗的概念
看门狗,watchDog Timer,其实是一个定时器,这个定时器每隔固定时间会发出一次让CPU复位的指令,以防止CPU跑飞或者出现其他异常,之后可以复位重置。
为什么要喂狗
在启动阶段如果不去喂狗,则看门狗就会发出复位指令,所以我们为了省事,一般在启动的前阶段关闭看门狗,当系统起来之后再决定是否打开看门狗,一旦打开就需要定时喂狗,否则就会复位。
在S5PV210的iROM代码BL0中其实已经关闭看门狗了,我们在这里再关一遍只是为了例行公事,因为在其他开发板或者Soc中还是有很多没有BL0的,所以它不会给你关看门狗。
看门狗的关闭位置
查看用户手册得到,看门狗寄存器有这么几个:
Register | Address | R/W | Description | Reset Value |
---|---|---|---|---|
WTCON | 0xE270_0000 | R/W | Watchdog Timer Control Register | 0x00008021 |
WTDAT | 0xE270_0004 | R/W | Watchdog Timer Data Register | 0x00008000 |
WTCNT | 0xE270_0008 | R/W | Watchdog Timer Count Register | 0x00008000 |
WTCLRINT | 0xE270_000C | W | Watchdog Timer Interrupt Clear Register | - |
关键操作的就是WTCON寄存器,第5位表示看门狗的开关,1代表开,0代表关。
汇编代码如下:
#define WTCON 0xE2700000
// 关闭看门狗
ldr r0,=0<<5
ldr r1,=WTCON
str r0,[r1]
关门狗代码应该放在程序最前边,越早关闭看门狗,就越早不会受到打扰。
设置栈和C语言调用
C语言运行环境
C语言的运行需要一定的环境和条件,这些环境和条件由汇编提供,只有当运行环境完备之后,C语言才能正常的运行起来,这也是在内核中起始代码只能为汇编的原因,C语言的运行环境最重要的元素就是栈。
栈的概念
栈是一种数据类型的表示,这种数据类型具有先进后出(FILO)的特性,在C语言中,所有的局部变量都存在于栈中.
栈的寄存器是SP,在每种模式下都有自己的独立SP寄存器,这样每个工作模式的栈互不影响,如果我们要设置栈,不可能也没有权限设置每种模式下的栈,只能设置当前当前程序下的栈,所以需要知道自己当前程序的栈。
通过查阅用户手册可知,系统上电/复位之后是默认进入SVC模式的,所以我们需要访问SVC模式下的栈寄存器,则需要这样的步骤:
- 设置当前模式为SVC,
- 设置SP寄存器
系统复位之后默认是SVC模式,所以可以直接设置SP寄存器,由于当前CPU刚复位,则外部DRAM尚未被初始化,可用的内存只有内部的SRAM,而且SRAM不需要初始化就可以使用,所以我们的栈只能设置在SRAM中。
我们查阅iROM文档得到SRAM的分配图,则得到SVC Stack区域的范围,内存地址为0xD0037780~0xD0037D80的1.5K的区域,
我们已经知道栈的进出形式,有增减,满增和满减几种形式,我们的ARM的ATPCS中要求使用满减栈,根据满减栈的规则,则我们的栈设置的位置就在0xD0037D80这个起始地址上,以后的压栈和出栈都会基于这个地址下的1.5K内存区域进行,汇编代码为:
#define SVC_STACK 0xD0037D80
// 设置SVC栈
ldr sp,=SVC_STACK // 从这里开始可以愉快的调用C程序了
我们可以将原来的led.S汇编代码改造成调用C语言来实现,则汇编代码为:
#define WTCON 0xE2700000
#define SVC_STACK 0xD0037D80
.global _start // 把_start声明为外部可访问,以便汇编程序定位到这里开始运行
_start:
// 关闭看门狗
ldr r0,=0<<5
ldr r1,=WTCON
str r0,[r1]
// 设置SVC栈
ldr sp,=SVC_STACK // 从这里开始可以愉快的调用C程序了
bl led_blink // 调用C语言函数led_blink
b. // 死循环不能丢
调用语言进行LED闪烁的代码为:
#define GPJ0CON 0xE0200240
#define GPD0CON 0xE02000A0
#define GPJ0DAT 0xE0200244
#define GPD0DAT 0xE02000A4
#define rGPJ0CON *((int *)GPJ0CON)
#define rGPD0CON *((int *)GPD0CON)
#define rGPJ0DAT *((int *)GPJ0DAT)
#define rGPD0DAT *((int *)GPD0DAT)
void sleep();
void led_blink() {
// 初始化LED寄存器,设置寄存器模式为输出
rGPJ0CON = 1<<12 | 1<<16 | 1<<20;
rGPD0CON = 1<<4;
while (1)
{
// led3亮
rGPJ0DAT = 0<<3 | 1<<4 | 1<<5;
rGPD0DAT = 1<<1;
// 延时
sleep();
// led4亮
rGPJ0DAT = 1<<3 | 0<<4 | 1<<5;
rGPD0DAT = 1<<1;
// 延时
sleep();
// led5亮
rGPJ0DAT = 1<<3 | 1<<4 | 0<<5;
rGPD0DAT = 1<<1;
// 延时
sleep();
// led6亮
rGPJ0DAT = 1<<3 | 1<<4 | 1<<5;
rGPD0DAT = 0<<1;
// 延时
sleep();
}
}
void sleep() {
volatile int i = 999999; // volatile 禁止编译器优化该句代码,就按照我们写的执行
while(i--); // 这样才能真正的延时
}
指令Cache(iCache)
Cache是一种高速缓存,iCache是寄存器和DDR之间的一块高速内存,用于平衡DDR和寄存器之间的速度差异,S5PV210内部有32K的I/D Cache,ICache是指令缓存,DCache是数据缓存。
iCache的一切动作都是自动的,不需要认为干预,我们所需要做的就是打开或者关闭iCache,S5PV210在iROM中的BL0已经打开了iCache。
iCache在CP15协处理器中进行操作,CP15中的C1引脚中bit12就是iCache的开关,0为关闭,1为开启,汇编代码为:
// 开关icache
mrc p15,0,r0,c1,c0,0; // 读取CP15协处理器的c1寄存器到r0中
bic r0,r0,#(1<<12) // bit12置为0,关闭iCache
// orr r0,r0,#(1<<12) // bit12置为1,开启iCache
mcr p15,0,r0,c1,c0,0;