04 看门狗和Cache

注:本文学习朱有鹏老师课程的学习笔记

汇编写启动代码之关看门狗

1.什么是看门狗?
看门狗( watch dog timer 看门狗定时器)。
1、大家想象这样一个场景:家门口有一只狗,这个狗定时会饿(譬如说2小时一饿),狗饿了会胡乱咬死人。人进进出出要想保证安全,必须提前喂狗(必须在上次喂过后的2小时内喂狗才行)。如果超时没喂狗就会被咬死,如果提前喂狗没关系,但是本次喂狗时间就会从这里开始计算。
2、现实中因为一些外部因素,电子设备经常会跑飞或者死机(譬如极端炎热、极端寒冷、工业复杂场合)。在这种情况下我们希望设备自动复位而不需要人工干预(无人值守)。看门狗用来完成这个工作。看门狗其实是我们SoC内部的一个定时器(类似于闹钟,类似于门口的狗),定好时间之后看门狗定时器会去计时,时间到之前(狗饿了之前)必须去重新置位看门狗定时器(喂狗),如果没有喂狗则系统会被强制复位。
系统在正常工作时,系统软件会自己去喂狗,所以看门狗定时器不会复位。但是系统一旦故障跑飞,看门狗就
没人喂了,然后下一个周期就会自动复位,达到我们期望的效果。



2.分析硬件功能
物理特性上看门狗其实是个定时器(跟现实中的闹钟类似),硬件上就是SoC内部的一个内部外设。

3.找到关键性操作SFR(特殊功能寄存器)

04 看门狗和Cache_第1张图片

WTCON(0xE2700000),其中bit5是看门狗开关:0代表关,1代表开

04 看门狗和Cache_第2张图片

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的栈。

如图:

04 看门狗和Cache_第3张图片

在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.csdn.net/czg13548930186/article/details/52454032

汇编写指令代码之开iCache

1.什么是cache,有什么用?
1、cache是一种内存,叫 高速缓存
从容量来说:CPU < 寄存器 < cache < DDR

从速度来说:CPU > 寄存器 > cache > DDR

04 看门狗和Cache_第4张图片

cache的存在,是因为寄存器和DDR之间速度差异太大,DDR的速度远不能满足寄存器的需要(不能满足CPU的需要,所以没有cache会拉低整个系统的整体速度)
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 速度明显变慢 速度不变

你可能感兴趣的:(cortex-A8裸机编程)