声明:
本文是本人在韦东山笔记的基础上加了一些注释,方便理解。原文地址:http://wiki.100ask.org/%E7%AC%AC014%E8%AF%BE_%E5%BC%82%E5%B8%B8%E4%B8%8E%E4%B8%AD%E6%96%AD
请来访的读者注意:如果你没读过本章笔记,请你忽略这一黑块里的总结。这一黑块是我多次阅读本笔记的总结。你可
以直接从本章“第01节 概念引入与处理流程”开始读。
每读一遍往往有得:
1、处理und、swi异常无非就是那几个步骤,两者相同
2、处理中断,中断是一种特殊的异常。在本笔记示例中,und、swi异常是你故意加一句错误语句让程序错误。
而中断不然,中断的处理函数比较复杂,你得分辨中断源,然后处理中段,还得清中断。
并且你得对中断控制器(也即mask寄存器)和中断源(比如按键配置成中断)进行初始化。
3、本章重要内容,我想两点吧,第一是异常的机制,即第4节(第5节其实和第4节一样);第二点是中断的机制,标准
的中断编程比直接看本章的最后代码即可,即把中断处理函数用一个注册函数指针数组保存起来,以后用的时候很方便。
对这种中断注册编程进行总结:
1、程序从start.S的
_start:
b reset /* vector 0 : reset */
然后执行
reset:
然后
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
2、然后看主函数:
int main(void)
{
key_eint_init(); /* ①初始化按键,②初始化相应的中断控制器相应的EINTMASK去使能相应
的位,③把key_eint_init()注册到一个函数指针数组里*/
timer_init(); /* ①初始化时钟,②初始化相应的中断控制器相应的EINTMASK去使能相应
的位,③把timer_init()注册到一个函数指针数组里*/
/*注册函数:
void register_irq(int irq, irq_func fp)
{
irq_array[irq] = fp;
INTMSK &= ~(1<<irq);
}*/
/*比如:
register_irq(0, key_eint_irq);
就表示,一旦外部来0中断,那么就会跳到key_eint_irq()函数*/
3、一旦发生中断信号,那么汇编start.S文件中就会跳到中断处理函数,handle_irq_c()
void handle_irq_c(void)
{
/* 分辨中断源 */
int bit = INTOFFSET;
/* 调用对应的处理函数执行 */
irq_array[bit](bit);/*要知道,你前面注册过了,来了哪个中断信号,就会处理相应的函数
(比如对于本章的按键中断示例来说,那就是key_eint_irq())*/
/* 清中断 : 从源头开始清 */
SRCPND = (1<<bit);
INTPND = (1<<bit);
}
4、我在练习中断编程的时候遇到的坑:
我的友善之臂的四个按键为:
k1 - GPG0 - EINT8; k2 - GPG3 - EINT11; k3 - GPG5 - EINT13; k4 - GPG6 - EINT14;
而在void key_eint_irq(int irq)函数中,
/*先把这个值 读 出来,清的时候我再把他写进去。达到了清EINTPEND的目的*/
unsigned int val1 = EINTPEND;
if (irq == 5) //注意:EINT8、EINT11、EINT13、EINT14都包含在INTOFFSET的bit5,即这四者的irp共为5!!!
{
然后使用EINTPEND区分是四者中的哪一个。区分方法为:
if(val1 & (1<<8)){
//注意:不是if(val1 == 8)这种语法!!!!
}
if(val1 & (1<<11)){
}
if(val1 & (1<<13)){
}
if(val1 & (1<<14)){
}
}
然后:
EINTPEND = val1;//即清中断(恢复原本的EINTPEND值)
4、我在练习中断编程的时候遇到的坑二:
第四点中的
对下图解释:
屏蔽字就是屏蔽寄存器中的内容,屏蔽寄存器中的内容就是把各个屏蔽触发器组合到一起,而每个中断请求触发器都有一个屏蔽触发器。总而言之,你需要知道,每个中断源都可以通过设置MASK的值而被屏蔽,注意,屏蔽的是中断源信号。
而开、关中断,是指的,CPU是否去响应中断源的信号,是从CPU的角度出发的。
第01节 概念引入与处理流程
先来取个场景解释中断概念。
假设有个大房间里面有小房间,婴儿正在睡觉,他的妈妈在外面看书。问:这个母亲怎么才能知道这个小孩醒?
第一种方法:过一会打开一次房门,看婴儿是否睡醒,让后接着看书
第二种方法:一直等到婴儿发出声音以后再过去查看,期间都在读书
第一种 叫做查询方式:
优点:简单
缺点: 累
写程序表示这种方式:
while(1)
{
1 read book(读书)
2 open door(开门)
if(睡)
return(read book)
else
照顾小孩
}
第二种叫中断方式:
优点:不累
缺点:复杂
写程序:
while(1)
{
read book
中断服务程序()//如何被调用?
{
处理照顾小孩
}
}
我们看看母亲被小孩哭声打断如何照顾小孩?
母亲的处理过程:
1 平时看书
2 发生了各种声音,如何处理这些声音
①有远处的猫叫(听而不闻,忽略)
②门铃声有快递(开门收快递)
③小孩哭声(打开房门,照顾小孩)
3 母亲的处理
故母亲只会处理门铃声和小孩哭声处理过程如下:
a 现在书中放入书签,合上书(保存现场)
b 去处理 (调用对应的中断服务程序)
c 继续看书(恢复现场)
不同情况,不同处理:
a 对于门铃:开门取快件
b 对于哭声:照顾小孩
我们将母亲的处理过程抽象化——母亲的头脑相当于CPU
耳朵听到声音会发送信号给脑袋,声音来源有很多种,有远处的猫叫,门铃声,小孩哭声。这些声音传入耳朵,再由耳朵传给大脑,除了这些可以中断母亲的看书,还有其他情况,比如身体不舒服,有只蜘蛛掉下来,对于特殊情况无法回避,必须立即处理。对比我们的arm系统:
a 有CPU,有中断控制器。
b 中断控制器可以发信号给CPU告诉它发生了那些紧急情况
c 中断源有按键、定时器、有其它的(比如网络数据)
这些信号都可以发送信号给中断控制器,再由中断控制器发送信号给CPU表明有这些中断产生了,这些成为中断(属于一种异常)。
而异常就像是有一只蜘蛛掉下来,属于特殊情况无法回避,需要中断CPU,必须处理。比如指令不对,数据访问有问题。又reset信号,这些都可以中断CPU 这些成为异常中断。
母亲处理对比我们的arm系统如下图:
异常中断的重点在于保存现场以及恢复现场
异常中断处理过程
a 保存现场(各种寄存器)
b 处理异常(中断属于一种异常)
c 恢复现场
arm对异常(中断)处理过程
1 初始化:
a 设置中断源,让它可以产生中断
b 设置中断控制器(可以屏蔽某个中断,优先级)
c 设置CPU总开关,(使能中断)
2 执行其他程序:正常程序
3 产生中断:按下按键—>中断控制器—>CPU
4 cpu每执行完一条指令都会检查有无中断/异常产生
5 发现有中断/异常产生,开始处理。对于不同的异常,跳去不同的地址执行程序。这地址上,只是一条跳转指令,跳去执行某个函数(地址)(对于下面的程序,发生中断,就跳去0x18地址去执行某个函数(地址)_irq),这个就是异常向量。如下就是异常向量表,对于不同的异常都有一条跳转指令。
(注:3-5步都是硬件强制自行做的)
.globl _start
_start: b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq //发生中断时,CPU跳到这个地址(假设地址为0x18),执行该指令
ldr pc, _fiq
//我们先在0x18这里放 ldr pc ,__irq,于是cpu最终会跳去执行__irq代码
//保护现场,调用处理函数,恢复现场
6 这些函数(地址)(如_irq)做什么事情?
软件做的:
a 保存现场(各种寄存器)
b 处理异常(中断):
b.a 分辨中断源
b.b 再调用不同的处理函数
c 恢复现场
(对比母亲的处理过程来比较arm中断的处理过程。)
中断处理程序怎么被调用?
CPU—>0x18 --跳转到其他函数->做保护现场->调用函数(分辨中断源、调用对应函数)->恢复现场
cpu到0x18是由硬件决定的,跳去执行更加复杂函数(由软件决定)。
第02节 CPU模式(Mode)、状态(State)和寄存器
这节课我们来讲CPU的工作模式(Mode) 、状态(State)和寄存器。可以参考书籍 《ARM体系结构与编程》作者:杜春雷
7种Mode:
usr/sys
undefined(und)
Supervisor(svc)
Abort(abt)
IRQ(irq)
FIQ(fiq)
2种State:
ARM state
Thumb state
寄存器:
通用寄存器
备份寄存器(banked register)
当前程序状态寄存器(Current Program Status Register);CPSR
CPSR的备份寄存器:SPSR(Save Program Status Register)
我们仍然以这个母亲为例讲解这个CPU模式:
这个母亲无压力看书 -->(正常模式)
要考试,看书—>(兴奋模式)
生病---->(异常模式)
对于ARM CPU有7种模式:
1、 usr :用户模式,类比 正常模式
2、 sys :系统模式,类比兴奋模式
3、 五种异常模式:(2440用户手册72页)
① und :未定义模式(CPU执行时遇到某条指令不认识时为此异常)
② svc :管理模式
③ abt :终止模式
a 指令预取终止(读写某条错误的指令导致终止运行,即CPU执行某条命令时,已在解析下一条指令,预取下下一条指令,而这个预取可能会出错,导致“指令预取终止”)。
b 数据访问终止 (读写某个地址,这个过程出错)。
这两者都会进入终止模式。
④ IRQ: 中断模式
⑤ FIQ: 快中断模式(快速处理中断)
我们可以称以下6种为特权模式(privileged mode)
. | 6种特权模式 |
---|---|
und | 未定义模式 |
svc | 管理模式 |
abt | 终止模式 |
IRQ | 中断模式 |
FIQ | 快中断模式 |
sys | 系统模式 |
usr用户模式(不可直接进入其他模式) 。(防止写应用程序的人意外破坏整个系统)。
而上面的6种特权模式可以随便切换,可以编程去操作CPSR寄存器直接进入其他模式。这些异常模式是为了更好的处理相应的异常,每一种异常模式的差别在于CPU寄存器的差别,查看2440手册:
这个图是有关各个模式下所能够访问的寄存器,再讲这个图之前我们先引入 2种state。
CPU有两种state:
1 ARM state:使用ARM指令集,每个指令4byte
2 Thumb state:使用的是Thumb指令集,每个指令2byte
比如同样是:
mov R0, R1
编译后
对于ARM指令集,上面的这条汇编指令编译成的机器码要占据4个字节;
对于Thumb指令集,上面的这条汇编指令编译成的机器码要占据2个字节。
因此,引入Thumb可以减少存储空间。本章第3节会演示使用Thumb指令集编译,看是否生成的bin文件会变小很多。
ARM指令集与Thumb指令集的区别:
Thumb 指令可以看作是 ARM 指令压缩形式的子集,是针对代码密度的问题而提出的,它具有 16 位的代码密度,但是它不如ARM指令的效率高 。
Thumb 不是一个完整的体系结构,不能指望处理只执行Thumb 指令而不支持 ARM 指令集。
因此,Thumb 指令只需要支持通用功能,必要时可以借助于完善的 ARM 指令集,比如,所有异常自动进入 ARM 状态。在编写 Thumb 指令时,先要使用伪指令 CODE16 声明,而且在 ARM 指令中要使用 BX指令跳转到 Thumb 指令,以切换处理器状态,编写 ARM 指令时,则可使用伪指令 CODE32声明。
好,理解了CPU的state之后我们来继续看这个图:ARM State General Registers and Program Counter即ARM状态通用寄存器和程序计数器。
上图,CPU在每种模式下都有R0 ~ R15。
1、在这张图注意到有些寄存器画有灰色的三角形,表示访问该模式下访问的专属寄存器,比如FIQ模式的r8_fiq寄存器。FIQ模式的r8_fiq寄存器和 System 模式下的r8是两个在物理性质上不同的寄存器。
2、不带灰色的三角形的寄存器是各种模式下的通用寄存器,比如r0,是这7种模式共用的寄存器,是同一个物理上的寄存器。
比如:
mov R0, R8
在System 模式下访问的是R0 ~ R8,在所有模式下访问R0都是同一个寄存器。
mov R0,R8_fiq
但是在FIQ模式下,访问R8是访问的FIQ模式专属的R8寄存器,不是同一个物理上的寄存器。
在这五种异常模式中每个模式都有自己专属的R13 R14寄存器,R13用作SP(栈), R14用作LR(返回地址) ,LR是用来保存发生异常时的指令地址。
为什么快中断(FIQ)有那么多专属寄存器(这些寄存器称为备份寄存器)。
解答这个问题,先回顾一下中断的处理过程
1 保存现场(保存被中断模式的寄存器)。
就比如说我们的程序正在系统模式/用户模式下运行,当你发生中断时,需要把R0 ~ R14这些寄存器全部保存下来,让后处理异常,最后恢复这些寄存器。
但如果是快中断,那么我就不需要保存 系统/用户模式下的R8 ~ R12这几个寄存器,在FIQ模式下有自己专属的R8 ~ R12寄存器,省略保存寄存器的时间,加快处理速度。但是注意,在Linux中并不会使用FIQ模式。
2 处理。
3 恢复现场。
上面这个图片还说到了CPSR、SPSR寄存器,CPSR是当前程序状态寄存器,这是一个特别重要的寄存器。SPSR保存的程序状态寄存器,他们二者的格式如下:
上图中的解释:
1、首先 M4 ~ M0 表示当前CPU处于哪一种模式(Mode);我们可以读取这5位来判断CPU处于哪一种模式,也可以修改这一种模式位,让其修改这种模式;
2、假如你当前处于用户模式下,是没有权限修改这些位的;
3、M4 ~ M0对应什么值,在2440手册中有说明:
4、要知道,CPU只有一个CPSR寄存器,所以从一个模式到另一个模式时,要对CPSR寄存器进行设置。
查看上上一副图中的其他位(即除了M4~M0位):
1、Bit5 State bits表示CPU工作用Thumb State还是ARM State指令集。
2、Bit6 FIQ disable当bit6等于1时,FIQ是不工作的。
3、Bit7 IRQ disable当bit5等于1时,禁止所有的IRQ中断,这个位是IRQ的总开关。
4、Bit8 ~ Bit27是保留位。
5、Bite28 ~ Bit31是状态位, 什么是状态位呢,比如说执行一条指令:
cmp R0, R1
如果R0 等于 R1 那么zero位等于1,这条指令影响 Z 位,即如果R0 == R1,则Z = 1。
beq跳转到xxx这条指令会判断Bit30(即Z)是否为1,是1的话则跳转,不是1的话则不会跳转 使用 Z 位,如果 Z 位等于1 则跳转,这些指令是借助状态位实现的。
SPSR保存的程序状态寄存器: 表示发生异常时这个寄存器会用来保存被中断的模式下他的CPSR。
就比如我的程序在system系统模式下运行的 CPSR寄存器是某个值,当发生中断时会进入irq模式,这个SPSR_irq就保存系统模式下的CPSR的值。
现在,我们来看看发生异常时CPU是如何协同工作的:
进入异常的处理流程(硬件部分实现的),查看2440手册,如下图:
我们来翻译一下上面图片中的文字(硬件部分实现的):
发生异常时,我们的CPU会做什么事情:
1、把下一条指令的地址保存在LR寄存器里(某种异常模式的LR等于被中断模式的下一条指令的地址)
它有可能是PC + 4有可能是PC + 8,到底是那种取决于不同的情况。
2、 把CPSR(当前程序状态寄存器)保存在SPSR里面(某一种异常模式下SPSR里面的值等于CPSR)
3、 修改CPSR的模式为进入异常模式(即修改CPSR的M4 ~ M0进入异常模式)。
4 、跳到向量表。
退出异常怎么做?查看2440手册,如下图:
对上面图片文字进行翻译:
1、 让LR减去某个值,让后赋值给PC(即PC = 某个异常LR寄存器减去 offset)。
减去什么值呢(offset是什么呢)?也就是说,我们怎么返回去继续执行原来的程序(被中断模式),根据下面这个表来取值。
图表含义,比如:
①、如果发生的是SWI可以把 R14_svc复制给PC;
②、如果发生的是IRQ可以把R14_irq的值减去4赋值给PC。
2、 把CPSR的值恢复(CPSR 值等于 某一个异常模式下的SPSR)。
3、 清中断(如果是中断的话,对于其他异常不用设置)。
第03节 Thumb指令集程序示例
(注:本节并不重要,本节只是演示一下thumb指令集,在后续的学习中是用不到的。对于这一节,你不需要理解透彻,记住步骤即可。甚至不用会,以后用到的时候再来看就可以。
因为ARM的flash空间比较大,使用thumb节省内存的意义不大。一般flash比较小的单片机会比较重视这个)。
在上节视频里说ARMCPU有两种状态:
1、ARM State 每条指令会占据4byte;
2、Thumb State 每条指令占据2byte。
我们说过Thumb指令集并不重要,本节演示把一个程序使用Thumb指令集来编译它。
(本节代码见013_thumb_014_003文件夹)
使用上一章节的重定位代码,打开Makefile和Start.S。
以前的Makefile文件如下:
all:
arm-linux-gcc -c -o led.o led.c
arm-linux-gcc -c -o uart.o uart.c
arm-linux-gcc -c -o init.o init.c
arm-linux-gcc -c -o main.o main.c
arm-linux-gcc -c -o start.o start.S
#arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-objcopy -O binary -S sdram.elf sdram.bin
arm-linux-objdump -D sdram.elf > sdram.dis
clean:
rm *.bin *.o *.elf *.dis
若使用Thumb指令集,对其进行更改:
all:
arm-linux-gcc -mthumb -c -o led.o led.c//只需要在arm-linux-gcc加上 mthumb命令即可
arm-linux-gcc -c -o uart.o uart.c
arm-linux-gcc -c -o init.o init.c
arm-linux-gcc -c -o main.o main.c
arm-linux-gcc -c -o start.o start.S
#arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-objcopy -O binary -S sdram.elf sdram.bin
arm-linux-objdump -D sdram.elf > sdram.dis
clean:
rm *.bin *.o *.elf *.dis
但是上面这种仅对led.c进行了使用thumb指令集,若全部使用还得一个一个改,麻烦。故进行如下改进:
all: led.o uart.o init.o main.o start.o //all依赖led.o uart.o init.o main.o start.o
#arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-objcopy -O binary -S sdram.elf sdram.bin
arm-linux-objdump -D sdram.elf > sdram.dis
clean:
rm *.bin *.o *.elf *.dis
%.o : %.c
arm-linux-gcc -mthumb -c -o $@ $< //对于所有的.c文件使用规则就可以使用thumb指令集编译 $@表示目标 $<表示第一个依赖
%.o : %.S
arm-linux-gcc -c -o $@ $< //.S文件不在这里改,而是在代码中改。
对start.S需要修改代码
原重定位章节Start.S文件:
.text
.global _start
_start:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl sdram_init
//bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */
/* 重定位text, rodata, data段整个程序 */
bl copy2sdram
/* 清除BSS段 */
bl clean_bss
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
对其更改,下面是使用thumb指令集的Start.S文件
.text
.global _start
/*更改处一------------------------------*/
.code 32 //表示后续的指令使用ARM指令集
_start:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
/*更改处二------------------------------*/
/* 怎么从ARM State切换到Thumb State? */
adr r0, thumb_func //定义此标号的地址
add r0, r0, #1 /* bit0=1时, bx就会切换CPU State到thumb state */
bx r0
/*更改处三------------------------------*/
.code 16 //下面都使用thumb指令集
thumb_func: //需要得到这个标号的地址
/*下面就是使用thumb指令来执行程序*/
bl sdram_init
//bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */
/* 重定位text, rodata, data段整个程序 */
bl copy2sdram
/* 清除BSS段 */
bl clean_bss
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr r0, =main /* 绝对跳转, 跳到SDRAM ,先把main的地址赋值给R0 */
mov pc, r0 /*让后再移动到PC*/
halt:
b halt
上传代码编译测试,出现错误,如下:
init.o(.text+0x6c):In function 'sdram_init2';
undefined reference to 'memcpy'
发现是init,o里sdram_init2使用的了memcpy函数的问题。查看init.c:
#include "s3c2440_soc.h"
void sdram_init(void)
{
BWSCON = 0x22000000;
BANKCON6 = 0x18001;
BANKCON7 = 0x18001;
REFRESH = 0x8404f5;
BANKSIZE = 0xb1;
MRSRB6 = 0x20;
MRSRB7 = 0x20;
}
#if 0
/**************************************************************************
* 设置控制SDRAM的13个寄存器
* 使用位置无关代码
**************************************************************************/
void memsetup(void)
{
unsigned long *p = (unsigned long *)MEM_CTL_BASE;
p[0] = 0x22111110; //BWSCON
p[1