【前言】开始学习ARM的时候,基本上都要从裸机编程开始。为了减低入门的门槛,很多时候只要修改模板里的主函数main.c,可是,久而久之,就会产生些疑问,问什么下载了这些C代码编译链接生成出来的BIN就能在ARM上跑了呢?原因就在于,有几个文件已经不声不响的帮我们提前干了很多的事,而这些事C语言是干不了的,只能由汇编完成,美其名曰:ARM汇编引导代码。其实不光“裸奔”需要,Boot Loader也同样需要。那么到底这些汇编帮我干了些什么呢?笔者就结合S3C2440的Boot Loader引导代码简单分析整理下。
【一】变量及相关宏定义
开始首先用GET(相当于C语言里的#include)伪指令包含进来了三个头文件option.inc、memcfg.inc、2440addr.inc,其中option.inc里定义芯片相关的配置,memcfg.inc里定义存储器配置,2440addr.inc里定义了寄存器符号。
USERMODE EQU 0x10
FIQMODE EQU0x11
IRQMODE EQU0x12
SVCMODE EQU0x13
ABORTMODE EQU 0x17
UNDEFMODE EQU 0x1b
MODEMASK EQU 0x1f
NOINT EQU0xc0
上面的几行进行了一些处理器模式的定义,下面定义了一些各模式下的常量,等到了【四】这一块再详细说。
UserStack EQU(_STACK_BASEADDRESS-0x3800);0x33ff4800 ~
SVCStack EQU(_STACK_BASEADDRESS-0x2800);0x33ff5800 ~
UndefStack EQU (_STACK_BASEADDRESS-0x2400);0x33ff5c00 ~
AbortStack EQU (_STACK_BASEADDRESS-0x2000);0x33ff6000 ~
IRQStack EQU(_STACK_BASEADDRESS-0x1000);0x33ff7000 ~
FIQStack EQU(_STACK_BASEADDRESS-0x0);0x33ff8000 ~
注:_STACK_BASEADDRESS在option.inc中有相关定义
接下来的定义要到最后才能用到,THUMBCODE作为全局变量,其实就是一个指示的作用,在跳转到main前进行模式的切换。
下面的宏定义可能就不太好理解了,这个是一个中断跳转的工具,到【二】这再解释。
MACRO
$HandlerLabel HANDLER $HandleLabel
$HandlerLabel ;标号
sub sp,sp,#4 ;(1)减少sp(用于存放转跳地址)
stmfd sp!,{r0} ;(2)把工作寄存器压入栈
ldr r0,=$HandleLabel ;将HandleXXX的址址放入r0
ldr r0,[r0] ;把HandleXXX所指向的内容(也就是中断程序的入口)放入r0
str r0,[sp,#4] ;(3)把中断服务程序(ISR)压入栈
ldmfd sp!,{r0,pc} ;(4)用出栈方式恢复r0原值和为pc设定新值(即完成了到ISR的转跳)
MEND
还有一些,留到后面用到的时候再说。
【二】中断向量表以及其相关跳转设置
在上述定义完成之后就算真正意义来到了函数的入口处,这里处理的比较复杂,会有大小
端的处理,因为对我们理解引导代码没多少作用,暂且将其简化处理掉。省略这些之后,其实入口就是这几行代码:
b ResetHandler ;上电复位中断;0x00
b HandlerUndef ;handlerfor Undefined mode ;0x04
b HandlerSWI ;handler for SWI interrupt ;0x08
b HandlerPabort ;handler for PAbort ;0x0c
b HandlerDabort ;handler for DAbort ;0x10
b . ;其实是个死循环 ;0x14
b HandlerIRQ ;handler for IRQ interrupt ;0x18
b HandlerFIQ ;handler for FIQ interrupt ;0x1c
这就是我们有名的中断向量表!中断向量表必须位于启动代码的开始部分连续8*4字节的连续空间,它是用户程序与启动代码之间以及启动代码的各部分之间联系的纽带。它由一个一个的跳转函数组成,它就象一个普通的散转函数,只不过散转的过程中有硬件机制参与,当系统发生异常时,ARM 处理器会通过硬件机制强制将PC 指针指向中断向量表中对应的异常跳转函数存储的地址,然后程序会跳转到相应的中断服务程序去执行。因为我们开机的第一个中断是上电复位,所以进来之后首先是跳转到ResetHandler中断函数里去进行一些必要的系统设置,故在0x00处就是bResetHandler。
对于ARM的中断,其实有两种模式(可通过相关寄存器设置):向量中断模式和普通中断模式。简单的区分这两个就是:对于向量中断模式,当中断发生时,CPU会跳转到向量表中相应中断类型的表项,直接把中断服务例程的起始地址送到PC,这个有个优点就是速度快;对于普通中断模式,在跳转到中断向量表之后还要进行一次跳转查询,最红由返回ISR的最红中断处理函数的地址给PC,现在就可以说说【一】中宏定义$HandlerLabel HANDLER $HandleLabel的作用了。这个宏是用于第一次查表过程的实现中断向量的重定向,在_ISR_STARTADDRESS里定义的第一级中断向量表是采用型如Handle###的方式的,而在程序的开始处采用的是b Handler###的方式,在这里Handler###就是通过HANDLER这个宏和Handle###建立联系的.所以在后面其实还有一段初始化程序作为宏展开。
HandlerFIQ HANDLER HandleFIQ
HandlerIRQ HANDLER HandleIRQ
HandlerUndef HANDLER HandleUndef
HandlerSWI HANDLER HandleSWI
HandlerDabort HANDLER HandleDabort
HandlerPabort HANDLER HandlePabort
这种方式的优点就是正真定义的向量数据在内存空间里,而不是在ENTRY处的ROM空间里,这样就可以在程序里灵活的改动向量的数据了.这段程序用于把中断服务程序的首地址装载到pc中,也可以称之为“加载程序”。
接着跳转那一块继续说,因为外部中断几乎都是通过IRQ引入的(其实FIQ理论上也可以,但是在linux几乎用不到),于是便跳到了HandleIRQ,但是此时HandleIRQ又是多少呢,在程序的下面还有一段也必须拿上来说:
ldr r0,=HandleIRQ ;This routine is needed
ldr r1,=IsrIRQ ;if there isn't 'subs pc,lr,#4' at 0x18, 0x1c
str r1,[r0]
可见,HandleIRQ和IsrIRQ其实等价了!于是可以把IsrIRQ处的处理函数拿来分析一下:
IsrIRQ
sub sp,sp,#4 ;给PC寄存器保留 reserved for PC
stmfd sp!,{r8-r9} ;把r8-r9压入栈
;把INTOFFSET的地址装入r9, INTOFFSET是一个内部的寄存器,存着中断的偏移
ldr r9,=INTOFFSET
ldr r9,[r9] ;I_ISR
ldr r8,=HandleEINT0 ;这就是我们第二个中断向量表的入口的,先装入r8
add r8,r8,r9,lsl #2 ;地址对齐,每个中断向量占4个字节,即isr = IvectTable + Offeset * 4
ldr r8,[r8] ;装入中断服务程序的入口
str r8,[sp,#8] ;把入口也入栈,准备用旧招
ldmfd sp!,{r8-r9,pc} ;弹出栈,顺便把r8弹出到PC了,跳转成功!
【三】初始化硬件
终于可以开始对硬件真正的干涉了,ARM要能形成一个可以供C语言工作的环境,还要
要干下面的几件事:
1、 关看门狗,看门狗是用来解决软件崩溃的,这里不需要
ldr r0,=WTCON
ldr r1,=0x0
str r1,[r0]
2、关中断,引导代码里不需要处理中断事件,除了上电复位中断其它都交给C的主函数完成
ldr r0,=INTMSK
ldr r1,=0xffffffff
str r1,[r0]
3、关子中断,同上
ldr r0,=INTSUBMSK
ldr r1,=0x7fff
str r1,[r0]
4、减少PLL的lock time,调整LOCKTIME寄存器
ldr r0,=LOCKTIME
ldr r1,=0xffffff
str r1,[r0]
5、设定PLL,这个直接关系到板子的快慢,不过也不是越快越好,除了要考虑功耗外还要满足下面的公式:
Fpllo=(m*Fin)/(p*2^s)
m=MDIV+8,p=PDIV+2,s=SDIV(1<=P<=62, 1<=M<=248)
Fpllo必须大于200Mhz小于600Mhz
Fpllo*2^s必须小于1.2GHz
PLLCON设定中的M_DIV P_DIV S_DIV是取自option.inc中的
6、设置系统存储寄存器,其中SMRDATA在程序段的后面有详细描述,这里知道作用就好
adrl r0, SMRDATA
ldr r1,=BWSCON ;BWSCON Address
add r2, r0, #52 ;SMRDATA数据的结束地址,共有52字节的数据
0
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne %B0 ;%表示搜索,B表示反向-back(F表示向前-forward),0为局部标号(0~99)
【四】 初始化堆栈
ARM 有7 种模式,用户模式,快速中断模式,中断模式,管理模式,中止模式,未定义模式和系统模式。系统堆栈的初始化主要是给各个处理器模式分配堆栈空间。堆栈是为中断或程序跳转服务的,当发生中断或程序跳转时,需要将当前处理器的状态及一些参数保持在堆栈中,当中断处理完毕以后或程序执行完后返回时,再将堆栈保存的现场数据进行恢复,以保证原来的程序正确运行。在【一】中已经提到了一些与堆栈有关的变量定义。可以这样简单说,堆栈的初始化分为两个步骤:1、指定堆栈的位置和大小,这些在【一】中已经完成了;2、将各个模式下的堆栈指针指向相应的栈,下面做的就是这个工作。
InitStacks
mrs r0,cpsr
bic r0,r0,#MODEMASK
orr r1,r0,#UNDEFMODE|NOINT
msr cpsr_cxsf,r1 ;UndefMode
ldr sp,=UndefStack ; UndefStack=0x33FF_5C00
orr r1,r0,#ABORTMODE|NOINT
msr cpsr_cxsf,r1 ;AbortMode
ldr sp,=AbortStack ; AbortStack=0x33FF_6000
orr r1,r0,#IRQMODE|NOINT
msr cpsr_cxsf,r1 ;IRQMode
ldr sp,=IRQStack ; IRQStack=0x33FF_7000
orr r1,r0,#FIQMODE|NOINT
msr cpsr_cxsf,r1 ;FIQMode
ldr sp,=FIQStack ; FIQStack=0x33FF_8000
bic r0,r0,#MODEMASK|NOINT
orr r1,r0,#SVCMODE
msr cpsr_cxsf,r1 ;SVCMode
ldr sp,=SVCStack ; SVCStack=0x33FF_5800
注:仔细看看发现没有初始化user模式下的堆栈,为什么呢?很明显嘛,你一开始就运行在了user模式下了!
【五】C主函数接管前的数据搬移及入口设定
其实一直还有个东西没说,这个在进入代码段前就定义了,我提到了,后面用到会详细说,
现在是时候了。在【一】时用IMPORT伪指令引入了|Image
RO
RO
Base| |Image
RO
RO
Limit|...这些变量是通过ADS、RVDS,MDK等工具的工程设置里面设定的RO Base和RW Base设定的,这个应该有印象,可能很多人感觉这个没用,其实很有用呢!那为什么要引入这玩意呢,最简单的用处是可以根据它们拷贝自己,这些变量是编译器生成的。
RO,RW, ZI这三个段都保存在Flash中,但RW,ZI在Flash中的地址肯定不是程序运行时变量所存储的位置,因此我们的程序在初始化时应该把Flash中的RW,ZI拷贝到RAM的对应位置。一般情况下,我们可以利用编译器替我们实现这个操作。比如我们跳转到main()时,使用 b __Main,编译器就会在__Main和Main之间插入一段汇编代码,来替我们完成RW,ZI段的初始化。 如果我们使用b Main, 那么初始化工作要我们自己做。编译器会生成如下变量告诉我们RO,RW,ZI三个段应该位于什么位置,但是它并没有告诉我们RW,ZI在Flash中存储在什么位置,实际上RW,ZI在Flash中的位置就紧接着RO存储。
IMPORT |Image
RO
RO
Base| ; Base of ROM code
IMPORT |Image
RO
RO
Limit| ; End of ROM code (=start of ROM data)
IMPORT |Image
RW
RW
Base| ; Baseof RAM to initialise
IMPORT |Image
ZI
ZI
Base| ; Base and limit of area
IMPORT |Image
ZI
ZI
Limit| ; to zero initialize
在程序的最后,通过下面的代码就可以进入main()了。
[ :LNOT:THUMBCODE;ifthumbcode={false} bl main L代表logic变量
bl Main ;Don't use main() because ......
b . ;注意小圆点
]
[ THUMBCODE ;for start-up code for Thumb mode
orr lr,pc,#1
bx lr
CODE16
bl Main ;Don't use main() because ......
b . ;注意小圆点
CODE32
]
现在,就可以顺便回顾下【一】中提到的THUMBCODE了,这不就是一个指示的作用吗?!
【后记】总的来说,ARM光初始化都要这样折腾,如果这个都折腾会了,后面的就慢慢来吧!
上一篇:s3c2440的Memory Controller与外设地址线错位连接分析
下一篇:s3c2440学习之路-001 汇编点亮led
推荐阅读最新更新时间:2024-11-17 12:22
推荐帖子
- 关于输出电压保持电路的设计
- 小弟要用STM32单片机输出可控电压,但是在电压切换(改变电压)的时候,中间会出现电压信号下降或消失的情况,这在单片机中不可改变,因此想用外围电路解决这个问题。比如:我第一次输出V1作用在负载上面,然后需要输出V2,怎样才能使V2输出之前,V1始终作用在负载上而不消失?谢谢各位大神了。。。小弟也尝试过用电容的充放电原理,但是我要输出的电压是-3.3V到3.3v,怎样才能确电压保持时间一直是0.02s左右呢?关于输出电压保持电路的设计楼主没有把问题叙述清楚:是有多路各不相同的输出电
- cxsy12300 模拟电子
- 放大电路频率响应的一般分析方法
- 为了简化计算,放大电路的频率响应分析一般采用分频区分析的方法,即按低频区、中频区和高频区分别进行。在每个频区分析时,先根据其工作特点抓住影响该频区的主要参数对电路进行简化,并在此基础上求得本区的频率响应。最后,将三个频区的结果综合起来就得到电路的全频率的响应。单级共射放大电路的低频响应1、画出低频小信号等效电路在低频区应将开路,而考虑C的作用,可画出低频小信号等效电路如图所示。2、频响分析对此式进行整理,得
- qinkaiabc 模拟电子
- protel问题
- 刚开始学这个东西,麻烦各位高手指点下:原理图中我想在一根导线的两头分别定义不同的网络标号,但是电气检查的时候总是提示错误,我看英文的意思应该是重复定义或者是多重定义,怎么弄能电气检查不报错呢?protel问题是重复定义,关了电气检查中这一项就可以了。谢谢管理员里了:D我有一个问题:为什么每打开一个protel文件,关闭后都会生成好多垃圾文件啊??请各位大侠指导!
- logicshi PCB设计
- 请教:下面的图表示什么意思
- 谢谢!请教:下面的图表示什么意思伏安特性测试,但图具体怎么看,我也不知道了回复:请教:下面的图表示什么意思大体上总算明白了,就是还有一个问题图上是测试数据:电压输入从0V不断调高电压到48V,一边测试输出的电压和电流,如果坐标点在那些绿线和红线之中,就是不希望的;如果坐标点全部落在红线之外就是所期望达到的正常工作区域实际那条粗黑线有很多点落在了红线的区域之类,所以需要再进行修改调整电路刚才跟交给我这张数据测试表的人讨论了一下,听他意思,他是想模拟电源启动过程(实际工作电压为12V
- quben 模拟电子
- 可调色温LED灯具电路疑问
- 各位大侠!好!LED可调色温次级输出电路,图中红色圈起的部分是起什么作用?请指教!谢谢可调色温LED灯具电路疑问 严重怀疑此电路的正确性。三支MOS管门极均联接到漏极,如果输入电压(C2两端电压)够高,那么三支MOS管均导通,不起什么作用。 管子上面的丝印是AOH,不知是什么管,网上查了下是MOS管三个PMOS管并联有扩流的意思还有,不知道前面供电方式,疑似有防反的作用 AC120V供电的,出口北美.用的的iWATT3689-00
- wzk198005 模拟电子
- 显示屏背光闪烁是什么原因导致的?
- 我的一个设计里使用SY7200作为LCD背光的升压芯片,电压从7V升压至23V左右,背光电流50mA左右。我使用下图的设计,电感1.6A额定电流,二极管1A电流。现在极少的时候会在上电后观察到显示屏有闪烁的现象,闪烁不是很明显。有时通一会儿电就不闪烁了,所以用示波器很难观察到电压变化。这种情况有遇到过的吗?有可能是什么原因导致的呢?显示屏背光闪烁是什么原因导致的?会不会输入端就不稳定?元件的参数你没标上啊。。最好用示波器看输入和输出的电压波动。。这个要看具体的情况,
- littleshrimp 国产芯片交流
设计资源 培训 开发板 精华推荐
- 联网||机械键轴||摩尔斯电码||电报机
- CN0134
- LT6658AIMSE-2.5 200mA 电压基准的典型应用电路
- DRM149,Kinetis-M 两相功率计参考设计
- ESP8266_积木_main
- LTC3615IFE 双路 4MHz、3A 同步降压型 DC/DC 转换器的典型应用
- LT3990EDD 3.3V 降压转换器的典型应用
- 用于级联电源应用的 ADP2323 同步降压稳压器的典型应用,VIN1 = 12 V,VOUT1 = 5 V,IOUT1 = 2 A,VOUT2 = 1 V,IOUT2 = 3 A,fSW = 1.2 MHz
- LT6654AMPS6-2.048、16 位 ADC 电压基准的典型应用
- 使用 Semtech 的 SC1652 的参考设计