新闻中心

EEPW首页>嵌入式系统>设计应用> IAR下的汇编/单片机启动代码汇编

IAR下的汇编/单片机启动代码汇编

作者: 时间:2016-11-25 来源:网络 收藏
1、
IAR汇编指令SFB和SFE
SFB Segment begin 段开始
语法格式
SFB(segment [{+|-} offset])
参数
segment: 可重定位段的段名, 必须在SFB使用前已定义
offset : 从开始地址的偏移, 是一个可选参数, 当偏移量省略时, 可以不添加小括号
描述
SFB 右边可以接受一个操作数, 而且这个操作数必须是一个可重位段的段名.
这个操作符计算段的首字节地址. 这个操作发生在连接时.
NAME demo
RSEG CODE
start: DC16 SFB(CODE)
即使上面的代码和多个其他的模块进行连接, start标号处仍被置为段的首字节地址
语法格式
SFE (segment [{+|-} offset])
参数
segment: 可重定位段的段名, 必须在SFB使用前已定义
offset : 从开始地址的偏移, 是一个可选参数, 当偏移量省略时, 可以不添加小括号
描述
SFE在其右边接收一个操作数. 操作数必须是一个可重定位段的段名. SFE操作符将段起始地址和段大小相加. 这个操作在连接时发生.
SFE accepts a single operand to its right. The operand must be the name of a relocatable segment. The operator evaluates to the segment start address plus the segment size. This evaluation takes place at linking time.
NAME demo
RSEG CODE
end: DC16 SFE(CODE)
即使当上面的代码被和多个模块想连接时, end标号仍然会被置为段最后一个字节的地址. Even if the above code is linked with many other modules, end will still be set to the address of the last byte of the segment.
段MY_SEGMENT的大小可以通过以下方式计算而得:
SFE(MY_SEGMENT)-SFB(MY_SEGMENT)
--------------------------------------------------------------------------------
arm中的几种跳转
arm汇编的跳转指令无非是b和ldr。但是如果没有足够理解,别人灵活的用一下你就犯晕了。
首先我们要知道两者的两个本质区别:
1、b是位置无关的,ldr不是位置无关的。
2、b的范围只能是+—32MB,而ldr是4GB。
在arm的启动汇编的中断向量表是必然用跳转指令的,但是就是这里也有很多实现形式:
方式1:
B InitReset ; 0x00 Reset handler
undefvec:
B undefvec ; 0x04 Undefined Instruction
swivec:
B swivec ; 0x08 Software Interrupt
pabtvec:
B pabtvec ; 0x0C Prefetch Abort
dabtvec:
B dabtvec ; 0x10 Data Abort
rsvdvec:
B rsvdvec ; 0x14 reserved
irqvec:
B IRQ_Handler_Entry ; 0x18 IRQ
这个我很容易理解,实现的标号油InitReset和IRQ_Handler_Entry,其他没有实现的在原地跳转。
方式二:
LDR pc, =resetHandler ; Reset
LDR pc, Undefined_Addr ; Undefined instructions
LDR pc, SWI_Addr ; Software interrupt (SWI/SYS)
LDR pc, Prefetch_Addr ; Prefetch abort
LDR pc, Abort_Addr ; Data abort
B . ; RESERVED
LDR pc, =irqHandler ; IRQ
LDR pc, FIQ_Addr ; FIQ
LDR PC,[PC,#0x18]
Undefined_Addr: DCD Undefined_Handler
SWI_Addr: DCD SWI_Handler
Prefetch_Addr: DCD Prefetch_Handler
Abort_Addr: DCD Abort_Handler
FIQ_Addr: DCD FIQ_Handler
我们注意到两种ldr
一种LDR PC,=label,这时把LDR当做伪指令,他要被翻译成:
LDR PC,[PC,offset_to_label2]
label2:DCD resetHandler
这种label是在程序中标记的了,如resetHandler和irqHandler
还有一种LDR PC,label,这时直接把label地址内存内容copy到PC中
这种label都是 label:DCD label2 ,这些label2可以在任何地方实现
我们来理解b和ldr两者的不同
1,b是位置无关,因为他的跳转都是相对PC来的,而ldr不是位置无关的,因为他的跳转是根据DCD里面的值,这个值是连接的时候确定的,他是跟连接地址有关的
2,b的范围只能是32M,是因为指令里面给偏移的空间只有24bit,而ldr是一个32bit的DCD,所以他是32bit的
注意:像上面方式二
LDR pc, =resetHandler ; Reset
resetHandler:
....
....
如果运行地址是0,那么LDR pc, =resetHandler还在以基址为0的空间运行
但是执行完了,假设装载地址是0x3000000,所以resetHandler的基址不是0,而是0x30000000
所以resetHandler标号以后的指令应该存在以0x3000000为基址的空间,pc跳过去了
这种技巧在at91的remap模式经常用到
--------------------------------------------------------------------------------
理解启动代码(ADS) 所谓启动代码,就是处理器在启动的时候执行的一段代码,主要任务是初始化处理器模式,设置堆栈,初始化变量等等.由于以上的操作均与处理器体系结构和系统配置密切相关,所以一般由汇编来编写. 具体到S64,启动代码分成两部分,一是与ARM7TDMI内核相关的部分,包括处理器各异常向量的配置,各处理器模式的堆栈设置,如有必要,复制向量到RAM,以便remap之后处理器正确处理异常,初始化数据(包括RW与ZI),最后跳转到Main.二是与处理器外部设备相关的部分,这和厂商的联系比较大.虽然都采用了ARM7TDMI的内核,但是不同的厂家整合了不同的片上外设,需要不同的初始化,其中比较重要的是初始化WDT,初始化各子系统时钟,有必要的话,进行remap.这一部分与一般控制器的初始化类似,因此,本文不作重点描述. 在进行分析之前,请确认如下相关概念: S64片上FLASH起始于0x100000,共64kB,片上RAM起始于0x200000,共16kB. S64复位之后,程序会从0开始执行,此时FLASH被映射到0地址,因此,S64可以取得指令并执行.显然,此时还是驻留在0x100000地址.如果使用remap命令,将会把RAM映射到0地址,同样的这时0地址的内容也只是RAM的镜像. S64的FLASH可以保证在最差情况时以30MHz进行单周期访问,而RAM可以保证在最大速度时的单周期访问. OK,以下开始分析启动代码. 一,处理器异常 S64将异常向量至于0地址开始的几个直接,这些是必需要处理的.由于复位向量位于0,也需要一条跳转指令.具体代码如下: RESET B SYSINIT ; Reset B UDFHANDLER ; UNDEFINED B SWIHANDLER ; SWI B PABTHANDLER ; PREFETCH ABORT B DABTHANDLER ; DATA ABORT B . ; RESERVED B VECTORED_IRQ_HANDLER B . ; ADD FIQ CODE HERE UDFHANDLER B . SWIHANDLER B . PABTHANDLER B . DABTHANDLER B . 请注意,B指令经汇编后会替换为当前PC值加上一个修正值(+/-),所以这条指令是代码位置无关的,也就是不管这条指令是在0地址还是在0x100000执行,都能跳转到指定的位置,而LDR PC,=???将向PC直接装载一个标号的值,请注意,标号在编译过后将被替换为一个与RO相对应的值,也就是说,这样的指令无论在哪里执行,都只会跳转到一个指定的位置.下面举一个具体的例子来说明两者的区别: 假定有如下程序: RESET B INIT 或者 LDR PC,=INIT … INIT … 其中RESET为起始时的代码,也就是这条代码的偏移为0,设INIT的偏移量为offset.如果将这段程序按照RO=0x1000000编译, 那么B INIT可理解为ADD PC, PC, #offset,而LDR PC,=INIT可被理解为 MOV PC,#(RO+offset) .显然当系统复位时,程序从0开始运行,而0地址有FLASH的副本,执行B INIT将把PC指向位于0地址处的镜像代码位置,也即INIT;如果执行LDR PC,=INIT将会将PC直接指向位于FLASH中的原始代码.因此以上两者都能正确运行.下面将RO设置为0x200000,编译后生成代码,还是得烧写到FLASH中,也就是还是0x100000,系统复位后从0地址执行,还是FLASH的副本,此时执行B INIT,将跳到副本中的INIT位置执行,此处有对应的代码;但是如果执行LDR PC,=INIT,将向PC加载0x200000+offset,这将使得PC跳到RAM中,而此时由于代码没有复制,RAM中的指定位置并没有代码,程序无法运行. 二,处理器模式 ARM的处理器可工作于多种模式,不同模式有不同的堆栈 ,以下设置各模式及其堆栈. 预定义一些参数: MODUSR EQU 0x10 MODSYS EQU 0x1F MODSVC EQU 0x13 MODABT EQU 0x17 MODUDF EQU 0x1B MODIRQ EQU 0x12 MODFIQ EQU 0x11 IRQBIT EQU 0x80 FIQBIT EQU 0x40 RAMEND EQU 0x00204000 ; S64 : 16KB RAM VECTSIZE EQU 0x100 ; UsrStkSz EQU 8 ; size of USR stack SysStkSz EQU 128 ; size of SYS stack SvcStkSz EQU 8 ; size of SVC stack UdfStkSz EQU 8 ; size of UDF stack AbtStkSz EQU 8 ; size of ABT stack IrqStkSz EQU 128 ; size of IRQ stack FiqStkSz EQU 16 ; size of FIQ stack 修改这些值即可修改相应模式堆栈的尺寸. 以下为各模式代码: SYSINIT ; MRS R0,CPSR BIC R0,R0,#0x1F MOV R2,#RAMEND ORR R1,R0,#(MODSVC :OR: IRQBIT :OR: FIQBIT) MSR cpsr_cxsf,R1 ; ENTER SVC MODE MOV sp,R2 SUB R2,R2,#SvcStkSz ORR R1,R0,#(MODFIQ :OR: IRQBIT :OR: FIQBIT) MSR CPSR_cxsf,R1 ; ENTER FIQ MODE MOV sp,R2 SUB R2,R2,#FiqStkSz ORR R1,R0,#(MODIRQ :OR: IRQBIT :OR: FIQBIT) MSR CPSR_cxsf,R1 ; ENTER IRQ MODE MOV sp,R2 SUB R2,R2,#IrqStkSz ORR R1,R0,#(MODUDF :OR: IRQBIT :OR: FIQBIT) MSR CPSR_cxsf,R1 ; ENTER UDF MODE MOV sp,R2 SUB R2,R2,#UdfStkSz ORR R1,R0,#(MODABT :OR: IRQBIT :OR: FIQBIT) MSR CPSR_cxsf,R1 ; ENTER ABT MODE MOV sp,R2 SUB R2,R2,#AbtStkSz ;ORR R1,R0,#(MODUSR :OR: IRQBIT :OR: FIQBIT) ;MSR CPSR_cxsf,R1 ; ENTER USR MODE ;MOV sp,R2 ;SUB R2,R2,#UsrStkSz ORR R1,R0,#(MODSYS :OR: IRQBIT :OR: FIQBIT) MSR CPSR_cxsf,R1 ; ENTER SYS MODE MOV sp,R2 ; 三,初始化变量 编译完成之后,连接器会生成三个基本的段,分别是RO,RW,ZI,并会在image中顺序摆放.显然,RW,ZI在运行开始时并不位于指定的RW位置,因此必须初始化 LDR R0,=|Image$$RO$$Limit| LDR R1,=|Image$$RW$$Base| LDR R2,=|Image$$ZI$$Base| 1 CMP R1,R2 LDRLO R3,[R0],#4 STRLO R3,[R1],#4 BLO � MOV R3,#0 LDR R1,=|Image$$ZI$$Limit| 2 CMP R2,R1 STRLO R3,[R2],#4 BLO � 四,复制异常向量 由于代码于RAM运行时,有明显的速度优势,而且变量可以动态配置,因此可以通过remap将RAM映射到0,使得出现异常时ARM从RAM中取得向量. IMPORT |Image$$RO$$Base| IMPORT |Image$$RO$$Limit| IMPORT |Image$$RW$$Base| IMPORT |Image$$RW$$Limit| IMPORT |Image$$ZI$$Base| IMPORT |Image$$ZI$$Limit| COPY_VECT_TO_RAM LDR R0,=|Image$$RO$$Base| LDR R1,=SYSINIT LDR R2,=0x200000 ; RAM START 0 CMP R0,R1 LDRLO R3,[R0],#4 STRLO R3,[R2],#4 BLO � 这段程序将SYSINIT之前的代码,也就是异常处理函数,全部复制到RAM中, 这就意味着不能将RW设置为0x200000,这样会使得向量被冲掉. 四,在RAM中运行 如果有必要,且代码足够小,可以将代码置于RAM中运行,由于RAM中本身没有代码,就需要将代码复制到RAM中: COPY_BEGIN LDR R0,=0x200000 LDR R1,=RESET ; =|Image$$RO$$Base| CMP R1,R0 ; BLO COPY_END ; ADR R0,RESET ADR R2,COPY_END SUB R0,R2,R0 ADD R1,R1,R0 LDR R3,=|Image$$RO$$Limit| 3 CMP R1,R3 LDRLO R4,[R2],#4 STRLO R4,[R1],#4 BLO � LDR PC,=COPY_END COPY_END 程序首先取得RESET的连接地址,判断程序是否时是在RAM中运行,方法是与RAM起始地址比较,如果小于,那么就跳过代码复制. 在复制代码的时候需要注意,在这段程序结束之前的代码没有必要复制,因为这些代码都已经执行过了,所以,先取得COPY_END,作为复制起始地址,然后计算其相对RESET的偏移,然后以RO的值加上这个偏移,就是复制目的地的起始地址,然后开始复制. 五,开始主程序 以上步骤完成,就可以跳转到main运行 IMPORT Main LDR PC,=Main B . 六,器件初始化 主程序首先要进行器件的初始化,对S64而言,应该先初始化WDT,因为默认情况下,WDT是打开的,然后是各设备的时钟分配,最后应该remap
--------------------------------------------------------------------------------
RemapSRAM:
MOV R0, #0x40000000 //RAM 区首地址
LDR R1, =Vectors //向量表首地址
#下面一段程序是把从0x00000000 开始的64 个字节(FLASH 中的中断向量表和地址表)搬移到以
#0x40000000 为首地址的RAM 区中
LDMIA R1!, {R2-R9} //把以[R1]为首地址的32 个字节数据装载到R2-R9 中
STMIA R0!, {R2-R9} //把R2-R9 中的数据存入以[R0]为首地址的单元中
LDMIA R1!, {R2-R9} //把以[R1]为首地址的32 个字节数据装载到R2-R9 中
STMIA R0!, {R2-R9} ////把R2-R9 中的数据存入以[R0]为首地址的单元中
--------------------------------------------------------------------------------
ARM的启动代码分析
相信使用过MDK的朋友都会发现,在新建任意一个ARM芯片的工程时,MDK开发环境都会自动的在代码中加入一个Startup.s的文件。
大家不能小看这个代码的功能了,这个代码中就是对应芯片的启动代码了。
本文就以在MDK开发环境下对于LPC2103的启动代码分析了。
在MDK开发环境下,系统复位后,都是会将ARM芯片切换到系统模式下工作。启动代码会在系统复位后立即执行,其功能如下:
1、定义中断和异常向量。
2、配置CPU时钟源(对于一些设备来说)。
3、使用内存重映射命令拷贝ROM中的异常向量到RAM中(例如:Atmel)。
4、初始化外部总线控制器和执行内存重映射(如果有必要)。
5、如果有必要,初始化其它外设。
6、为所有的模式预留和初始化堆栈。
7、清零数据初始化(仅针对GNU)。
8、跳转到C语言函数执行。
本文只是在探讨第一个步骤(定义中断和异常向量)。因为这个将会在之后我们要讲到的在uC/OS-II中进行中断函数的编写有关系,其他步骤,如果大家有兴趣,可以到网上去查阅相关资料。
下面代码就是我在MDK中,拷贝出来的一段初始化异常向量的代码了,详细代码如下:
AREA
RESET, CODE, READONLY
ARM
Vectors
LDRPC, Reset_Addr
LDRPC, Undef_Addr
LDRPC, SWI_Addr
LDRPC, PAbt_Addr
LDRPC, DAbt_Addr
NOP
;LDRPC, IRQ_Addr
LDRPC, [PC, #-0x0FF0]
LDRPC, FIQ_Addr
Reset_Addr DCD Reset_Handler
Undef_Addr DCD Undef_Handler
SWI_Addr DCD SWI_Handler
PAbt_Addr DCD PAbt_Handler
DAbt_Addr DCD DAbt_Handler
DCD 0
IRQ_Addr DCD IRQ_Handler
FIQ_Addr DCD FIQ_Handler
Undef_Handler B Undef_Handler
SWI_Handler B SWI_Handler
PAbt_Handler B PAbt_Handler
DAbt_Handler B DAbt_Handler
IRQ_Handler B IRQ_Handler
FIQ_Handler B FIQ_Handler
下面就进行一步一步代码分析:
1、
AREA
RESET, CODE, READONLY
ARM
这两段话表达的意思是:在代码段中定义一个段名为RESET的只读段,该代码段中的代码都在ARM指令集下进行。
2、Vectors
LDR PC, Reset_Addr
LDR PC, Undef_Addr
LDR PC, SWI_Addr
LDR PC, PAbt_Addr
LDR PC, DAbt_Addr
NOP
;LDR PC, IRQ_Addr
LDR PC, [PC, #-0x0FF0]
LDR PC, FIQ_Addr
上面的代码是ARM内核定义的异常像量表
异常类型
异常向量地址
该地址中的内容
复位
0x00000000(或0xFFFF0000)
LDR PC, Reset_Addr
未定义异常
0x00000004(或0xFFFF0004)
LDR PC, Undef_Addr
软件中断
0x00000008(或0xFFFF0008)
LDR PC, SWI_Addr
指令预取异常
0x0000000C(或0xFFFF000C)
LDR PC, PAbt_Addr
数据预取异常
0x00000010(或0xFFFF0010)
LDR PC, DAbt_Addr
预留
0x00000014(或0xFFFF0014)
NOP
IRQ中断
0x00000018(或0xFFFF0018)
LDR PC, [PC, #-0x0FF0]
FIQ中断
0x0000001C(或0xFFFF001C)
LDR PC, FIQ_Addr
大家注意一下,在中断向量地址中必须保存一条ARM指令集。在对应的异常向量地址中将跳转到对应的异常处理程序。
当然大家在这里也可以使用B指令,但是大家要注意一下,使用LDR指令可以实现±4GB地址空间实现。但是使用B执行跳转的话,就只能在±32M地址空间。所以建议大家使用LDR指令实现跳转。
上一页 1 2 3 下一页

评论


技术专区

关闭