您的位置:首页 > 编程语言 > C语言/C++

汇编写启动代码之关看门狗和设置栈和调用C语言和ICache

2016-09-25 02:03 573 查看
参考朱老师教程视频讲解,视频链接地址:http://edu.51cto.com/lecturer/user_id-9584512.html



1、看门狗

看门狗(watch dog timer 看门狗定时器)

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

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

为什么要关看门狗?

一般CPU设计,在CPU启动后看门狗默认是工作的,好处就是没有空当和漏洞,坏处就是在启动代码段我们不方便去喂狗(或者说懒得去喂狗)时看门狗会复位,所以为了偷懒我们就在启动代码前端先去关闭看门狗,然后在后面系统启动起来之后再根据需要决定是否要打开看门狗(一旦打开就必须同时提供喂狗)。

在S5PV210内部的iROM代码(BL0)中,其实已经关过看门狗了。所以我们的启动代码实际上是不用去关也没事的,也就是说今天写的关闭看门狗的代码运行后没有任何现象(没有现象就是正常现象).

很多CPU内部是没有BL0的,因此也没人给你关看门狗,都要在启动代码前段自己写代码关看门狗。

2、C语音运行时需要和栈的意义

“C语音运行时(runtime)”需要一定的条件,这些条件由汇编提供。C语音运行时主要是需要栈。

C语音与栈的关系:C语音的局部变量都是用栈来实现的。如果我们的汇编部分没有给C部分预先设置合理合法的栈地址,那么C代码中定义的局部变量就回落空,整个程序就会跑飞。

平常用到的单片机程序或者应用程序并没有去设置栈,C程序同样可以运行。原因是单片机中有硬件初始化时提供了一个默认的可用的栈,在应用程序中编写的C程序其实不是全部的程序代码,编译器(gcc)在链接的时候会帮我们自动添加一个头,这个头就是一段引导我们的C程序能够执行的一段汇编编写的代码,这个代码中就帮我们的C程序设置了栈以及其他的运行时的需要。

3、CPU模式和各种模式下的栈

在ARM中37个寄存器中,每种模式下都有自己的SP寄存器(r13),为什么有这样的设计?

如果各种模式都是用同一个SP,那么就意味着整个程序(操作系统的内核程序、用户自己编写的应用程序)都是用一个栈的。一旦你的应用程序出错(如栈溢出),就会连累操作系统的栈也损坏,导致整个程序崩溃。这样的操作系统设计是非常脆弱而且不合理的。

解决方案就是各个模式下用不同的栈。相互独立。

系统在复位后默认是进入SVC模式的

我们如何访问SVC模式下的SP呢?很简单,先把模式设置为SVC,再直接操作SP。



栈必须是当前一段可用的内存(可用的意思就是说这个地方必须有被初始化过的可以访问的内存,而且这个内存指挥被我们用作栈,不会被其他程序占用)

当前的CPU刚复位或刚启动,外部的DRAM尚未初始化,目前可用的内存只有内部的SRAM(因为它不需要初始化即可使用)。因此我们只能在SRAM中找一段内存来作为SVC的栈。

栈有四种:满减栈、满增栈、空减栈、空增栈、

在ARM中,ATPCS(ARM关于程序应该怎么实现的一个规范)要求使用满减栈,所以不出意外都是用满减栈。结合《iROM_application_note》中的memory map,可知SVC栈应该设置为0xD0037D80



4.汇编程序和C程序相互调用

在工程中新建并添加一个C语言的源文件(led.c),注意添加时要修改Makefile

修改Makefile中的编译选项为:
arm-linux-gcc -o $@ $< -c -nostdlib


编译报错(实际上是连接阶段报错):undefined reference to `__aeabi_unwind_cpp_pr1’

解决:在编译时添加-nostdlib这个编译选项即可解决。nostdlib就是不使用标准函数库。标准函数库就是编译器中自带的函数库,用-nostdlib可以让编译器链接器优先选择我程序内自己写的函数库。

在汇编启动代码中设置好栈后,使用(bl cfunction)的方式来调用C语言中的函数cfunction,如单片机中的main函数(bl main)。

5、使用C语言来访问寄存器的方式

寄存器的地址类似于内存地址(IO与内存统一编址的),所以这里的问题是用C语言读写寄存器,即用C语言来读写内存地址。如下操作:

volatile  unsigned int *p = (unsigned int *)0xE0200240;
*p = 0x11111111;


上面两句话其实可以简化为:

*((volatile  unsigned int *)0xE0200240) = 0x11111111;


可按照这种方式来操作,直接操作rGPJ0CON 和 rGPJ0DAT 就可以操控J0的IO脚

#define GPJ0CON     0xE0200240
#define GPJ0DAT     0xE0200244

#define rGPJ0CON    *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT    *((volatile unsigned int *)GPJ0DAT)


6、神奇的volatile

volatile的作用是让程序在编译时,编译器不对程序做优化。优化有时候是ok的,但是有时候是自作聪明会造成程序不对。如果你的一个变量是易变的,不希望编译器帮我们做优化,就在这个变量定义时加volatile。

加不加有没有差别,取决于编译器。如果编译器做了优化则有差异;如果编译器本身没做优化,那就没有差别。

在我们这里(编译器是arm-2009q3),实际测试加不加效果是一样的。

void delay(void)
{
// volatile 让编译器不要优化,这样才能真正的减, 才能消耗时间,实现delay
volatile unsigned int i = 900000;
while (i--);
}


7、iCache

什么是iCache ? 有什么用?

cache 是一种内存,叫做高速内存。

从容量来说:CPU < 寄存器 < cache < DDR

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

cache 的存在,是因为寄存器和DDR之间的速度差异太大,DDR的速度远不能满足寄存器的需要,即也不能满足CPU的需要,没有cache会拉低整个系统的速度。

整个系统中CPU的供应链为:寄存器+cache+DDR+硬盘/Flash 四阶组成,这是综合考虑了性能、成本后得到的妥协的结果。

S5PV210内部有32KB iCache 和 32KB dCache的一级缓存和512KB的二级缓存。

iCache是用来缓存指令的,dCache是用来缓存数据的。



cache的意义:指令平时是放在硬盘/Flash中的,运行时读取到DDR中,再从DDR中读取到寄存器,再由寄存器送给CPU。但是DDR的速度和寄存器相差太大,如果CPU运行完一句再去DDR读取下一句,那么CPU的速度完全被DDR给拖慢了,解决方案就是使用icache。

icache工作时,会把CPU正在运行的指令的旁边几句指令事先给读取到icache中(CPU设计中有个基本原理:代码执行时,下一句执行当前一句代码旁边代码的可能性要大很多)。当下一句CPU要指令时,cache首先检查自己事先准备的缓存指令中有没有CPU需要的这句指令,如果有直接给CPU,如果没有则需要从DDR中重新去读取拿给CPU,并同时做一系列的动作:清缓存、重新缓存。

8、iROM中BL0对cache的操作

首先,icache的一切动作都是自动的,不需人为干预。我们所需要做的就是打开/关闭icache。

其次,在210的iROM中BL0已经打开了icache。所以之前看到的现象都是icache打开时的现象。

汇编代码读写cp15以开关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
mrc p15, 0, r0, c1, c0, 0;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息