汇编写启动代码之关看门狗
1.什么是看门狗?看门狗(watch dog timer 看门狗定时器)。
1、大家想象这样一个场景:家门口有一只狗,这个狗定时会饿(譬如说2小时一饿),狗饿了会胡乱咬死人。人进进出出要想保证安全,必须提前喂狗(必须在上次喂过后的2小时内喂狗才行)。如果超时没喂狗就会被咬死,如果提前喂狗没关系,但是本次喂狗时间就会从这里开始计算。
2、现实中因为一些外部因素,电子设备经常会跑飞或者死机(譬如极端炎热、极端寒冷、工业复杂场合)。在这种情况下我们希望设备自动复位而不需要人工干预(无人值守)。看门狗用来完成这个工作。看门狗其实是我们SoC内部的一个定时器(类似于闹钟,类似于门口的狗),定好时间之后看门狗定时器会去计时,时间到之前(狗饿了之前)必须去重新置位看门狗定时器(喂狗),如果没有喂狗则系统会被强制复位。
系统在正常工作时,系统软件会自己去喂狗,所以看门狗定时器不会复位。但是系统一旦故障跑飞,看门狗就
没人喂了,然后下一个周期就会自动复位,达到我们期望的效果。
2.分析硬件功能
物理特性上看门狗其实是个定时器(跟现实中的闹钟类似),硬件上就是SoC内部的一个内部外设。
3.找到关键性操作SFR(特殊功能寄存器)
WTCON(0xE2700000),其中bit5是看门狗开关:0代表关,1代表开
4.写代码关开门狗
#define WTCON 0xE2700000
.global _start // 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
//关看门狗(向WTCON的bit5写入0即可)
ldr r0, =WTCON
ldr r1, =(0<<5)
str r1, [r0]
5.总结210中看门狗特性(iROM中已经关看门狗)
为什么要关看门狗?
一般CPU设计,在CPU启动后看门狗默认是工作的(为什么默认不关闭而要工作?猜测是因为怕你的程序在启动代码前端就死机了或者跑飞了没人管),好处就是没有空当和漏洞,坏处就是在启动代码段我们不方便去喂狗(或者说懒得去喂狗)时看门狗会复位,所以为了偷懒我们就在启动代码的前段先去关闭看门狗,然后在后面系统启动起来之后再根据需要决定是否要打开看门狗(一旦打开就必须同时提供喂狗)。
在S5PV210内部的iROM代码(BL0)中,其实已经关过看门狗了。所以我们的启动代码实际上是不用去关的也没事的,也就是说上面写的关闭看门狗的代码运行后没有任何现象(没有现象就是正常现象)。
很多CPU内部是没有BL0的,因此也没人给你关看门狗,都要在启动代码前段自己写代码关看门狗,
汇编写启动代码之设置栈和调用C语言1
1.C语言运行时需要和栈的意义“C语言运行时(runtime)”需要一定的条件(主要是栈),这些条件由汇编来提供。
1、C语言与栈的关系:C语言中的局部变量都是用栈来实现的。如果我们汇编部分没有给C部分预先设置合理合法的栈地址,那么C代码中定义的局部变量就会落空,整个程序就死掉了。
2、我们平时在编写单片机程序(譬如51单片机)或者编写应用程序时并没有去设置栈,但是C程序还是可以运行的。
原因:
@单片机中由硬件初始化时提供了一个默认可用的栈。
@在应用程序中我们编写的C程序其实并不是全部,编译器(gcc)在链接的时候会帮我们自动添加一个头,这个头就是一段引导我们的C程序能够执行的一段汇编实现的代码,
这个代码中就帮我们的C程序设置了栈及其他的运行时需要。2.CPU模式和各种模式下的栈
@在ARM中37个寄存器中,每种模式下都有自己的独立的sp寄存器(r13)<注:sp-堆栈指针>,为什么这么设计?
如果各种模式都使用同一个sp,那么就意味着整个程序(OS内核程序、用户自己编写的应用程序)都是用一个栈的。
(你的应用程序如果一旦出错(譬如栈溢出),就会连累OS的栈也损坏,整个OS的程序就会崩溃。这样的OS设计是非常脆弱的,不合理的。)
所以各种模式要用不同的栈。我的OS系统内核使用自己的栈,每个应用程序也使用自己独立的栈,这样各是各的,一个损坏不会连累其他。我们现在要设置栈,我们先要找到自己的模式,然后设置自己的模式下的栈到合理合法的位置,即可。
注意:系统在复位后默认是进入SVC模式的
@我们如何访问SVC模式下的sp呢?很简单,先把模式设置为SVC,再直接操作sp.
注:因为我们复位后就已经是SVC模式了,所以直接设置sp即可。
3.查阅文档并设置栈指针至合法位置
栈必须是当前一段可用的内存(可用的意思是这个地方必须有被初始化过可以访问的内存,而且这个内存只会被我们用作栈,不会被其他程序征用)
当前CPU刚复位(刚启动),外部的DRAM尚未初始化,目前可用的内存只有内部的SRAM(因为它不需初始化即可使用)。
因此我们只能在SRAM中找一段内存来作为SVC的栈。
如图:
在ARM中,ATPCS(ARM关于程序应该怎么实现的一个规范)要求使用满减栈,所以不出意外都是用满减栈。
结合iROM_application_note中的memory map,可知SVC栈应该设置为0xD0037D80而不是0xD0037780
补充:
栈有4种:满减栈 满增栈 空减栈 空增栈
进栈时 | 出栈时 | |
满减栈 | 指针向下移动 -> 存数据 | 出数据 -> 指针向上移动 |
满增栈 | 指针向上移动 -> 存数据 | 出数据 -> 指针向下移动 |
空减栈 | 存数据 -> 指针向下移动 | 指针向上移动 -> 出数据 |
空增栈 | 存数据 -> 指针向上移动 | 指针向下移动 -> 出数据 |
4.接下来就可以实现汇编程序和C程序互相调用
#define WTCON 0xE2700000
#define SVC_STACK 0xd0037d80
.global _start // 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
//关看门狗(向WTCON的bit5写入0即可)
ldr r0,= WTCON
ldr r1,= (0<<5)
str r1,[r0]
//设置SVC栈,实现汇编与C的相互调用
ldr sp ,= SVC_STACK
//接下来就可以直接调用C程序了
bl Cfunction
汇编写启动代码之设置栈和调用C语言2
1.使用C语言来访问寄存器的语法
寄存器的地址类似于内存地址(IO与内存统一编址的),所以这里的问题是用C语言来读写寄存器,就是用C语言来读写内存地址。
用C语言来访问内存,就要用到指针;
格式:
unsigned int *p = (unsigned int *)0xE0200240;*p = 0x11111111;
上面这两句等价于*((unsigned int *)0xE0200240) = 0x11111111;
2.
代码如下:
start.S文件:
#define WTCON 0xE2700000
#define SVC_STACK 0xD0037D80
.global _start // 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
//关看门狗(向WTCON的bit5写入0即可)
ldr r0,= WTCON
ldr r1,= (0<<5)
str r1,[r0]
//设置SVC栈,实现汇编与C的相互调用
ldr sp,= SVC_STACK
//接下来就可以直接调用C程序了
b czg_led
Makefile文件:
led.bin: start.o led.o
arm-linux-ld -Ttext 0x0 -o led.elf $^ #代码段运行地址为0x0,将所有依赖文件链接为led.elf
arm-linux-objcopy -O binary led.elf led.bin #将led.elf复制一份为led.bin文件
arm-linux-objdump -D led.elf > led_elf.dis #将led.elf文件转换为.dis反汇编文件
gcc mkv210_image.c -o mkx210
./mkx210 led.bin 210.bin
%.o : %.S
arm-linux-gcc -o $@ $< -c -nostdlib
%.o : %.c
arm-linux-gcc -o $@ $< -c -nostdlib
clean:
rm *.o *.elf *.bin *.dis mkx210 -f
led.c文件:
#define GPJ0CON 0xE0200240
#define GPJ0DAT 0xE0200244
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
void delay(void){
unsigned int i = 900000;
while(i--);
}
void czg_led(void){
rGPJ0CON = (0x111<<12); // 将三个引脚设置成out模式
while(1){
rGPJ0DAT = (0<<3)|(1<<4)|(1<<5);
delay();
rGPJ0DAT = (1<<3)|(0<<4)|(1<<5);
delay();
rGPJ0DAT = (1<<3)|(1<<4)|(0<<5);
delay();
}
}
3.关于volatile解释:
http://blog.youkuaiyun.com/czg13548930186/article/details/52454032
汇编写指令代码之开iCache
1.什么是cache,有什么用?1、cache是一种内存,叫高速缓存
从容量来说:CPU < 寄存器 < cache < DDR
从速度来说:CPU > 寄存器 > cache > DDR
2、整个系统中CPU的供应链由:寄存器+cache+DDR+硬盘/flash四阶组成,这是综合考虑了性能、成本后得到的妥协的结果。
3、210内部有32KB icache和32KB dcache。
icache用来缓存指令的;
dcache是用来缓存数据的。
4、cache的意义:
指令平时是放在硬盘/flash中的,运行时读取到DDR中,再从DDR中读给寄存器,再由寄存器送给CPU。但是DDR的速度和寄存器(代表的就是CPU)相差太大,如果CPU运行完一句再去DDR读取下一句,那么CPU的速度完全就被DDR给拖慢了。
解决方案就是:icache.icache工作时,会把我们CPU正在运行的指令的旁边几句指令事先给读取到icache中(CPU设计有一个基本原理:代码执行时,下一句执行当前一句代码旁边代码的可能性要大很多)。当下一句CPU要指令时候,cache首先检查自己事先准备的缓存指令中有没这句,如果有就直接拿给CPU,如果没有则需要从DDR中重新去读取拿给CPU,并同时做一系列的
动作:清缓存、重新缓存。
2.iROM中BL0对cache的操作
首先,icache的一切动作都是自动的,不需人为干预。我们所需要做的就是打开/关闭icache。
齐次,在210的iROM中BL0已经打开了icache。所以之前看到的现象都是icache打开时的现象。
3.查阅ARM手册中CP15寄存器的相关部分
1、ARM处理器中CP15协处理器的寄存器,如下图所示:
2、由上图可知,CP15包括了16个寄存器,其中C1寄存器是控制寄存器,主要用于:
(1)禁止/使能MMU以及其它与存储系统有关的功能
(2)配置存储系统以及ARM处理器相关的工作
C1寄存器的位定义,如下图所示:
注:由上图可知,bit12用于开关iCache,其中0代表关闭iCache,1代表开启iCache。
3.代码
start.S文件:
#define WTCON 0xE2700000
#define SVC_STACK 0xD0037D80
.global _start // 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
//关看门狗(向WTCON的bit5写入0即可)
ldr r0,= WTCON
ldr r1,= (0<<5)
str r1,[r0]
//设置SVC栈,实现汇编与C的相互调用
ldr sp,= SVC_STACK
//开关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 //将r0写入cp15中的c1中
//接下来就可以直接调用C程序了
b czg_led
4.实验验证
直接使用BL0中对icache的操作 | 手动关icache | 手动开icache | |
现象(结论) | 速度不变,且和手动开icache相同,证明BL0初始化了icache | 速度明显变慢 | 速度不变 |