计算机软件及应的用有关ARM的汇编语言编程.docx
- 文档编号:9876978
- 上传时间:2023-05-21
- 格式:DOCX
- 页数:82
- 大小:82.47KB
计算机软件及应的用有关ARM的汇编语言编程.docx
《计算机软件及应的用有关ARM的汇编语言编程.docx》由会员分享,可在线阅读,更多相关《计算机软件及应的用有关ARM的汇编语言编程.docx(82页珍藏版)》请在冰点文库上搜索。
计算机软件及应的用有关ARM的汇编语言编程
5.1有关ARM的汇编语言编程
从事基于ARM处理器的底层驱动开发,同样要求对硬件电路有一定的了解,而且在ARM处理器平台上进行底层开发和在其他平台上的底层开发一样,需要阅读比较多的有关ARM处理器及外围芯片的数据手册。
在ARM处理器平台上的底层驱动开发,通常会有两种方式,一种是脱离操作系统的裸机驱动设计方法,这与以前在8位机和16位机上的常用开发方式类同。
还有一种就是基于操作系统的底层驱动设计方法。
在基于ARM处理器的底层开发的过程中,如果实际设计的系统只是一个单任务系统,那么完全可以不使用操作系统,这个时候所进行的底层驱动程序设计就可以称为裸机底层驱动设计。
其实即使是使用了操作系统,还是一样离不开裸机底层驱动设计,就如前面介绍的启动代码BootLoader,在BootLoader里面的设备驱动同样也是一种裸机驱动。
本章把重点放在了基于ARM处理器的底层驱动设计上。
当然在介绍底层驱动设计之前先要介绍一下ARM处理器的汇编语言编程。
5.1 有关ARM的汇编语言编程
这里需要提到的是ARM处理器的汇编语言编程的一些内容,在嵌入式ARM系统的程序设计中往往离不开ARM汇编语言编程。
正如大家所熟知的处理器初始化部分的代码通常都是用汇编来编写的,还有一些操作协处理器的代码,以及部分中断处理程序一样也是用汇编语言写成的。
在开始介绍ARM处理器汇编语言编程之前建议读者先阅读一些有关ARM指令集的资料,主要是指ARM指令集、Thumb指令集及ARM宏汇编部分。
有关ARM指令集和Thumb指令集这里就不做具体介绍,只从ARM汇编伪指令、模块化汇编语言程序设计、混合语言编程等几个方面对ARM处理器汇编语言做一些简单介绍。
5.1.1 ARM汇编伪指令介绍
在ARM处理器汇编语言程序设计里,有一些特殊的指令助记符。
这些助记符与指令系统的助记符不同,没有相对应的操作码,通常称这些特殊的指令助记符为伪指令,它们所完成的操作称为伪操作。
伪指令在源程序中的作用是为完成汇编程序做各种准备工作的,这些伪指令仅在汇编过程中起作用,一旦汇编结束,伪指令的使命就完成了。
在ARM处理器的汇编程序中,大体有如下几种伪指令:
符号定义伪指令、数据定义伪指令、汇编控制伪指令、宏指令及其他伪指令。
伪操作符可以分为以下几类。
1)数据定义伪操作符
数据定义伪操作符主要包括LTORG、MAP、DCB、FIELD、SPACE、DCQ、DCW等,主要用于数据表定义、文字池定义、数据空间分配等。
常用的有DCB/DCQ/DCW分配一段字节/双字/字内存单元,并且将它们初始化。
2)符号定义伪操作符
符号定义伪操作符包括GBLA、GBLL、GBLS、LCLA、CN、CP、DN、FN、RLIST、SETA等,用于定义ARM汇编程序的变量,对变量进行赋值,以及定义寄存器名称等。
其中用于全局变量声明的GBLA、GBLL、GBLS和局部变量声明的LCAL、LCLL、LCLS伪指令较为常用。
3)报告伪操作符
报告伪操作符包括ASSERT、INFO、OPT等,主要用于汇编报告等。
其中比较常用的有ASSERT,表示断言错误。
4)条件汇编伪操作符
条件汇编伪操作符包括IF、ELSE、ENDIF、WHIL、WEND、MACRO、MEND等,主要用于条件汇编、宏定义、重复汇编控制等操作。
5)杂项伪操作符
杂项伪操作符包括AREA、ALIGN、ENTRY、EQU、EXPORT、GLOBAL、IMPORT、CODE16、CODE32等。
这些伪指令在汇编程序设计中较为常用,如段定义、入口点设置等伪指令。
常用的伪指令主要有以下几条。
n AREA:
用来定义段;
n ALIGN:
用来设定边界对齐;
n CODE16/CODE32:
用来指定指令集;
n ENTRY:
指定程序入口;
n END:
汇编结束。
有关这些伪指令的详细用法,可以参考ARM指令集参考手册。
5.1.2 模块化程序设计
尽管是汇编程序设计,也同样要求程序设计的模块化。
先按模块独立汇编,然后再与应用的其他模块(有可能是汇编程序模块,也有可能是C程序模块)链接形成一个可执行的程序。
在模块化程序设计中有几点是需要引起注意的,比如说全局符号的定义与引用、各个模块间符号的互相调用方法等。
1.全局符号
在基于ARM处理器的汇编程序中,模块中定义的、要被其他模块使用的符号(包括变量名和函数名)都必须被声明为全局符号。
大体有两种。
(1)在本模块中定义,其他模块中被应用的,具体定义方法如下:
.globalsymbol
(2)在本模块中要使用其他模块中的全局符号,可以声明,具体方式如下:
.externsymbol
在这种方式下也可以不用声明,在汇编时会自动被认为它是其他模块中的全局符号。
全局符号的定义就是为了方便在各个模块之间互相调用,全局符号增加了编程的灵活性,当然,要灵活地使用这些全局符号,在ARM嵌入式程序设计中自然也有一套规则来确保这些全局符号灵活调用。
2.模块间的符号互用
在ARM嵌入式程序设计中,这些符号不仅可以在汇编程序模块中调用,也可以在C程序模块中调用,模块间的符号互相调用主要有以下几种情况。
(1)汇编模块与汇编模块间的调用:
只要是全局符号在汇编模块间就可以直接使用。
(2)汇编模块调用C语言模块中的函数:
汇编模块调用C语言模块时,要注意不同芯片传递参数的方式有差别。
(3)汇编模块使用C语言模块中的变量:
首先保证该变量在C语言中是全局变量,然后在汇编中直接使用变量名。
要注意:
C语言中的变量名在汇编中不用加下划线。
另外该变量名不能用static修饰,否则该变量只局限于对所在的模块有效。
(4)C语言模块调用汇编模块中的函数:
该函数名在汇编程序中必须是全局符号,即必须用.global声明,然后在C语言中申明该函数的原型,最后在使用时与一般的C函数一样。
(5)C语言模块使用汇编模块中的变量:
该变量在汇编程序中必须是全局符号,即必须用.global声明,然后在C语言中申明该变量的原型,最后在使用时与一般的C变量一样。
5.1.3 混合语言编程
在基于ARM处理器的嵌入式应用系统程序设计中,若所有的编程任务均用汇编语言来完成,其工作量是可想而知的。
而且,所有程序如果都是汇编程序,将不利于系统升级或应用软件移植。
事实上,ARM体系结构支持C/C++及与汇编语言的混合编程,在一个完整的程序设计中,除了初始化处理器部分及一些中断有关函数用汇编语言完成外,其主要的编程任务一般都用C/C++完成。
ARM汇编与C/C++的混合编程通常有以下几种方式。
n 在C/C++代码中嵌入汇编指令;
n 在汇编程序和C/C++的程序之间进行变量的互访;
n 汇编程序、C/C++程序间的相互调用。
在实际的编程中,用得比较多的方法是:
(1)程序的初始化部分可以用汇编语言完成,然后用C/C++完成主要的编程任务。
(2)程序在执行时首先完成初始化过程,然后跳转到C/C++程序代码中,汇编程序和C/C++程序之间一般没有参数传递,也没有频繁的相互调用,因此,整个程序的结构显得相对简单,容易理解。
那么,如何在C/C++语言中内嵌汇编语言呢?
在程序设计中嵌入汇编程序的目的通常是为了实现一些高级语言所没有的功能,提高程序执行效率等。
armcc编译器的内嵌汇编器支持ARM指令集,tcc编译器的内嵌汇编器支持ThumbT指令集。
下面将介绍在使用内嵌汇编指令编程时的一些注意事项。
1.内嵌汇编的通用语法
在ARMC/C++程序中嵌入汇编代码必须遵守一定的语法规则,否则编译通过不了,常用的语法如下。
__asm
{
[指令]/*注释*/
___
___
[指令]
}
下面以两个常用的程序函数enbale_IRQ()和disable_IRQ()来说明具体语法的使用。
在ARM处理器程序代码中有两个十分常见的函数enable_IRQ和disable_IRQ用来使能/关闭IRQ中断。
具体的函数源码如例程5-1所示。
例程51 enable_IRQ()和disable_IRQ()函数
__inlinevoidenable_IRQ(void)
{
inttmp;
__asm //嵌入汇编代码
{
MRS tmp,CPSR //读取CPSR的值
BIC tmp,tmp,#0x80
MSR CPSR_c,tmp
}
}
__inlinevoiddisable_IRQ(void)
{
inttmp;
__asm
{
MRS tmp,CPSR
ORR tmp,tmp,#0x80
MSR CPSR_c,tmp
}
}
2.内嵌汇编的指令用法
在使用内嵌汇编的方法来设计程序的时候,除了要遵循上面介绍的语法规则以外,还需要注意内嵌汇编指令的具体用法。
这里要注意操作数定义、物理寄存器的使用规则、常量的定义,以及内存单元的分配等问题。
1)操作数
在内嵌的汇编指令中作为操作数的寄存器和常量可以是C表达式。
这些表达式可以是char、short或int类型,而且这些表达式都是作为无符号数进行操作的。
若需要有符号数,用户需要自己处理与符号有关的操作。
编译器将会计算这些表达式的值,并为其分配寄存器。
2)物理寄存器
在内嵌汇编中使用物理寄存器有以下限制:
n 不能直接向PC寄存器赋值,程序跳转只能使用B或BL指令;
n 使用物理寄存器的指令时,不要使用过于复杂的表达式。
因为表达式过于复杂时,将需要较多的物理寄存器。
这些寄存器可能与指令中的物理寄存器在使用时发生冲突;
n 编译器可能会使用R12或R13寄存器来存放编译的中间结果,在计算表达式的值时,可能会将寄存器R0~R3、R12、R14用于子程序调用。
因此,在内嵌的汇编指令中,不要将这些寄存器同时指定为指令中的物理存储器;
n 通常在内嵌的汇编指令中不要指定物理寄存器,因为这可能会影响编译器分配寄存器,进而影响代码的效率。
3)常量
在内嵌的汇编指令中,常量前面的“#”可以省略。
4)指令展开
在内嵌汇编指令中,如果包含常量操作数,则该指令有可能被内嵌汇编器展开成几条指令。
5)标号
程序中的标号可以被内嵌的汇编指令使用。
但是只有指令B可以使用C程序中的标号,而指令BL则不能使用。
6)内存单元的分配
所有的内存分配均由编译器完成,分配的内存单元通过变量供内嵌汇编器使用。
内嵌汇编器不支持内嵌汇编程序中用于内存分配的伪指令。
7)SWI和BL指令
在内嵌的SWI和BL指令中,除了正常的操作数域外,还必须增加以下几个可选的寄存器列表:
n 第一个寄存器列表中的寄存器用于输入参数的存储;
n 第二个寄存器列表中的寄存器用于存储返回的结果;
n 第三个寄存器列表中的寄存器的内容可能被调用的子程序破坏,即这些寄存器是供被调用的子程序作为工作寄存器。
3.内嵌汇编器与armasm汇编器的差异
内嵌汇编器与armasm汇编器在处理ARM汇编指令时是有所区别的,这就需要在使用内嵌汇编程序设计方法时,了解这些差异点,从而确保编程的正确性。
(1)内嵌汇编器不支持通过“.”指示符或PC值来获取当前指令的地址。
(2)不支持“LDRLRn,=expr”伪指令,而使用“MOVRn,expr”指令向寄存器赋值。
(3)不支持标号表达式。
(4)不支持ADR和ADRL伪指令。
(5)不支持BX指令。
(6)不能向PC直接赋值。
(7)使用0x前缀代替“&”符号,表示十六进制数。
(8)当使用8位移位常数导致CPSR的ALU标志更新时,N、Z、C和V标志中的C不具有真实意义。
4.内嵌汇编的注意事项
在了解内嵌汇编的具体使用方法之后,还有几点事项必须在编程时注意。
(1)必须小心使用物理寄存器。
在使用内嵌汇编程序设计方法时,必须要小心使用物理寄存器,主要指R0~R3、PC、LR寄存器,以及CPSR中的N、Z、C和V标志位等。
因为在计算汇编代码中的C表达式时,可能会使用这些物理寄存器,并会修改N、Z、C和V标志位。
例如:
__asm
{
MOV var,x
ADD y,var,x/y
}
计算x/y时R0会被修改。
内嵌汇编器探测到隐含的寄存器冲突就会报错。
(2)不要使用寄存器代替变量。
尽管有时寄存器明显对应某个变量,但也不能直接使用寄存器代替变量。
例如:
intbad_f(intx) //x存放在R0中
{
__asm
{
ADDR0,R0,#1
//发生寄存器冲突,实际上R0的值没有变化
}
return(x);
}
尽管根据编译器的编译规则R0对应x,但这样的代码会使内嵌汇编器认为发生了寄存器冲突。
用其他寄存器代替R0存放参数x,使得该函数将x原封不动地返回。
这段代码的正确写法如下:
intbad_f(intx)
{ __asm
{
ADDx,x,#1
return(x);
}
}
(3)使用内嵌汇编无须保存和恢复寄存器。
事实上,除了CPSR和SPSR寄存器以外,对物理寄存器先读后写都会引起汇编器报错。
例如:
intf(intx)
{
__asm
{
STMFDSP!
{R0} //保存R0,先读后写,汇编出错
ADDR0,x,ll
EORx,R0,x
LDMFDSP!
{R0}
}
return(x);
}
(4)LDM和STM指令的寄存器列表中只允许使用物理寄存器。
内嵌汇编可以修改处理器模式、协处理器模式及FP、SL、SB等APCS寄存器。
但是编译器在编译时并不了解这些变化,因此必须保证在执行C代码前恢复相应被修改的处理器模式。
(5)汇编语言中的“,”号作为操作数分隔符。
如果有C表达式作为操作数,若表达式中包含有“,”,则必须使用符号“(”和“)”将其归约为一个汇编操作数。
例如:
__asm
{
ADDx,y,(f(),z) //"f(),z"为一个带有","的C表达式
}
关于ARM汇编语言编程中的ARM汇编伪指令介绍,以及ARM程序设计中的模块化程序设计、ARM汇编和C/C++的混合语言编程的介绍如上所述。
接下来将分别探讨基于ARM处理器的底层驱动开发的方式。
5.2 裸机底层驱动设计方法
所谓裸机在这里主要是指系统软件平台没有用到操作系统。
在基于ARM处理器平台的软件设计中,如果整个系统只需要完成一个相对简单而且独立的任务,那么可以不使用操作系统,只需要考虑在平台上如何正确地执行这个单任务程序。
不过,在这种方式下同样需要一个BootLoader,这个时候的BootLoader一般是自己写的一个简单的启动代码加载程序。
大家所熟悉的各种BootLoader下的设备驱动,其实就是很好的裸机驱动程序。
比如说U-Boot下的网卡驱动、串口驱动、LCD驱动等。
在裸机方式下,ARM的软件集成开发环境就显得极为重要,因为在这种方式下可以把所有代码都放在这个环境里面编写、编译和调试。
在这种方式下测试驱动程序,首先要完成CPU的初始化,然后把需要测试的程序装载到系统的RAM区/或者SDRAM中。
当然,如果需要处理一些复杂的中断处理的话,最好也把CPU的复位向量表放到RAM区中。
把所有程序都调试好之后,再把最后的程序烧写到Flash里面去执行。
5.2.1 复位向量表
所谓复位向量表,其实就是一些跳转指令表,这些跳转指令就是针对ARM处理器的多种异常处理的,有关处理器的工作模式及异常处理的介绍,读者可以回顾本书1.4的介绍。
复位向量表通常是放在CPU复位执行的第一块内存地址空间中,一般来说都是0x00000000这个地址。
在32位ARM系统中,都是在中断向量表中放置一条分支指令或PC寄存器加载指令,来实现程序跳转到中断服务例程的功能。
例如:
IRQEntry B HandleIRQ ;跳转范围较小一般在64KB之内
B HandleFIQ ;
或
IRQEntry LDR PC,HandleIRQ;跳转的范围是任意32位地址空间
LDRPC,HandleFIQ
LDR伪指令等效于生成一条存储读取指令和一条32位常数定义指令。
32位常数存储在LDR指令附近的存储单元中,相对偏移小于4KB。
该32位数据就是要跳转到中断服务程序的入口地址。
之所以使用LDR伪指令,是因为ARM的RISC指令为单字指令,不能装载32位的立即数(常数),无法直接把一个32位常数数据或地址数据装载到寄存器中。
下面一段程序与上述伪指令功能等效,但中断向量表描述得更为清晰。
其中VectorTable为相对LDR指令的偏移量。
IRQEntry LDRPC,VectorTable+0 ;与LDRPC,=HandleIRQ等效
LDRPC,VectorTable+4;与LDRPC,=HandleFIQ等效
……
VectorTable DCDHandleIRQ
DCDHandleFIQ
……
HandleIRQ
……
HandleFIQ
一个典型的复位向量表的例子(Nucleus系统中比较常用)如例程5-2所示。
例程52 典型复位向量表
;**********************************
;*VECTORTABLE *
;**********************************
.sect "vectors"
.def INT_Vectors
INT_Vectors
LDR pc,INT_Initialize_Addr ;复位异常中断地址
LDR pc,INT_Undef_Inst_Addr ;未定义指令异常中断地址
LDR pc,INT_Software_Addr ;软件异常中断地址
LDR pc,INT_Data_Abort_Addr ;取数据错误异常中断地址
LDR pc,INT_Reserved_Addr ;保留
LDR pc,INT_IRQ_Addr ;IRQ异常中断地址
LDR pc,INT_FIQ_Addr ; 快速IRQ异常中断地址
INT_Initialize_Addr
.word _INT_Initialize
INT_Undef_Inst_Addr
.word _INT_Undef_Inst
INT_Software_Addr
.word _INT_Software
INT_Prefetch_Abort_Addr
.word _INT_Prefetch_Abort
INT_Data_Abort_Addr
.word _INT_Data_Abort
INT_Reserved_Addr
.word _INT_Reserved
INT_IRQ_Addr
.word _INT_IRQ
INT_FIQ_Addr
.word _INT_FIQ
从上面的复位向量程序可以看出,CPU判断如果是复位异常则会跳转到_INT_Initialize执行,如果是未定义指令异常则跳转到_INT_Undef_Inst执行程序,其他几个异常也是同样的处理方式。
一般ARM嵌入式系统的程序都是固化在从0x00000000开始的低端ROM空间中,中断向量表VectorTable也固化在ROM中,所以上述两种方法都无法在程序运行时动态、随机地修改中断向量表。
不论对于初学ARM处理器的程序员还是有经验的程序员,设置中断向量都相当烦琐,必须修改ARM的C程序启动代码。
还有一点需要指出的是,如果CPU有REMAP功能的话,一定要在初始化程序的结尾把整个程序包含复位向量表一起复制到RAM中,然后执行REMAP,如果没有REMAP功能,则需要把复位向量表单独转移到RAM中去。
5.2.2 中断服务程序
ARM处理器的中断处理程序实际可以称为对于处理器的异常处理程序。
在对这些异常进行处理时就需要一定的中断服务程序。
一般在进入中断服务程序之前,都要完成对程序现场和一定数据、堆栈的保护,然后跳转到中断服务程序执行中断服务,完成中断服务之后,在退出中断服务程序之前恢复保存的数据、堆栈信息等。
在ARM处理器设计中通常把复位初始化程序也算到异常中断处理程序中。
对应处理器的8个异常,分别有如下8个处理程序。
n _INTCPU_Initialize:
用来处理CPU复位初始化异常中断;
n _INT_Undef_Inst:
用来处理未定义指令异常中断;
n _INT_Software:
用来处理软件异常中断;
n _INT_Prefetch_Abort:
用来处理取指溢出异常中断;
n _INT_Data_Abort:
用来处理数据溢出异常中断;
n _INT_Reserved:
保留异常中断;
n _INT_FIQ:
用来处理FIQ异常中断;
n _INT_IRQ:
用来处理IRQ异常中断。
在8个异常中断处理函数里面,常用的主要是处理器复位异常中断、软件异常中断、常规异常中断(IRQ)、快速异常中断(FIQ)4个。
下面结合实际的异常处理源码来具体分析这8个异常中断程序。
这些代码针对的ARM处理器是OMAP5910。
1)CPU复位初始化异常中断处理INTCPU_Initialize,该异常中断处理的主要功能有:
(1)初始化处理器系统控制寄存器。
(2)初始化中断向量表。
(3)初始化系统堆栈指针。
(4)跳转到高级初始化函数,进行高级初始化。
该异常处理函数的具体代码通常如例程5-3所示。
例程53 INTCPU_Initialize
.def _c_int00
_c_int00
.def _INTCPU_Initialize
_INTCPU_Initialize:
;//确保处理器进入Super模式
MRS r0,CPSR ;获取当前CPSR值
BIC r0,r0,#MODE_MASK ;清除CPSR中对应的CPU模式位
ORR r0,r0,#SUP_
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 计算机软件 有关 ARM 汇编语言 编程