教材:嵌入式系统及应用,罗蕾、李允、陈丽蓉等,电子工业出版社
ARM 程序设计
- 由于 C 语言便于理解,有大量的支持库,所以它是当前 ARM 程序设计所使用的主要编程语言
- 对硬件系统的初始化、CPU 状态设定、中断使能、主频设定以及 RAM 控制参数初始化等 C 程序力所不能及的底层操作,还是要由汇编语言程序来完成
- ARM 程序通常是 C/C++ 语言和汇编语言的混合程序
ARM 汇编语言程序设计
段
在ARM(Thumb)汇编语言程序中,通常以段为单位来组织代码
段是具有特定名称且功能相对独立的指令或数据序列
根据段的内容,分为代码段和数据段
一个汇编程序至少应该有一个代码段,当程序较长时,可以分割为多个代码段和数据段
-
一个汇编语言程序段的基本结构
AREA Init, CODE, READONLY ;只读的代码段Init ENTRY ;程序入口点 start LDR R0,= 0X3FF5000 LDR R1,= 0XFF ;或MOV R1,#0XFF STR R1,[R0] LDR R0,= 0X3FF5008 LDR R1,= 0X01 ;或MOV R1,#0X01 STR R1,[R0] …… END ;段结束
每一个汇编程序段都必须有一条伪指令 END,指示代码段的结束
分支程序设计
- 具有两个或两个以上可选执行路径的程序叫做分支程序
- 执行到分支点时,应有条件指令或条件转移指令,根据当前 CPSR 中的状态标志值选择路径
普通分支程序
-
使用带有条件码的指令实现的分支程序段
CMP R5,#10 MOVNE R0,R5 MOV R1,R5
-
用条件转移指令实现的分支程序段
CMP R5,#10 BEQ doequal MOV R0,R5 doequal MOV R1,R5
HI:无符号数大于,LS:无符号数小于或等于;
GT:带符号数大于,LE:带符号数小于或等于。
多分支(散转)程序
程序分支点上有多于两个以上的执行路径的程序叫做多分支程序。利用条件测试指令或跳转表可以实现多分支程序
-
编写一个程序段,判断寄存器R1中数据是否为 10、15、12、22。如果是,则将R0 中的数据加 1;否则将 R0 设置为0XF
MOV R0,#0 TEQ R1,#10 ;条件测试指令 TEQNE R1,#15 ;NE则执行 TEQ R1,#15 TEQNE R1,#12 TEQNE R1,#22 ADDEQ R0,R0,#1 ;EQ则执行 ADD R0,R0,#1 MOVNE R0,#0XF
-
多分支程序的每个分支所对应的是一个程序段时,常常把各个分支程序段的首地址依次存放在一个叫做跳转地址表的存储区域,然后在程序的分支点处使用一个可以将跳转表中的目标地址传送到 PC 的指令来实现分支
MOV R0,N ;N为表项序号0~2 ADR R5,JPTAB LDR PC,[R5,R0,LSL #2] ;每一个跳转目标地址占4个字节 JPTAB ;跳转表 DCD FUN0 DCD FUN1 DCD FUN2 FUN0 ... ;分支FUN0的程序段 FUN1 ... ;分支FUN1的程序段 FUN2 ... ;分支FUN2的程序段
带 ARM/Thumb 状态切换的分支程序
系统提供了一条专用的、可以实现 4GB 空间范围内的绝对跳转交换指令 BX
PC 寄存器中的目标地址的最低位一定是0(指令按4字节或2字节对齐),只需提供目标地址的高31位即可,最低位可用来表示状态切换信息
-
从 ARM 指令程序段跳转到 Thumb 指令程序
;ARM指令程序 CODE32 ;以下为ARM指令程序段 ... ADR R0, Into_Thumb + 1 ;目标地址和切换状态传送至R0 BX R0 ;跳转 ... ;其他代码 ;Thumb指令程序 CODE16 ;以下为Thumb指令程序段 Into_Thumb ... ;目标
-
从 Thumb 指令程序段跳转到 ARM 指令程序
;Thumb指令程序 CODE16 ;以下为Thumb指令程序段 ... ADR R5, Back_to_ARM ;目标地址送至R5 BX R5 ;跳转 ... ;其他代码 ;ARM指令程序 CODE32 ;以下为ARM指令程序段 Back_to_ARM ... ;目标
循环程序设计
有 DO-WHILE 结构和 DO-UNTIL 结构
-
在汇编语言程序设计中,常用的是DO-UNTIL 结构循环程序
MOV R1,#10 LOOP ... SUBS R1,R1,#1 BNE LOOP
-
编写一个程序,把首地址为DATA_SRC 的 80 个字的数据复制到首地址为 DATA_DST 的目标数据块中
LDR R1, = DATA_SRC ;源数据块首地址 LDR R0, = DATA_DST ;目标数据块首地址 MOV R10, #10 ;循环计数器赋值 LOOP LDMIA R1!, {R2-R9} STMIA R0!, {R2-R9} SUBS R10, R10, #1 BNE LOOP
子程序及其调用
为进行识别,子程序的第1条指令之前必须赋予一个标号,以便其他程序可以用这个标号调用子程序
-
在 ARM 汇编语言程序中,主程序一般通过 BL 指令来调用子程序。该指令在执行时完成如下操作
- 将子程序的返回地址存放在连接寄存器LR中
- 同时将程序计数器PC指向子程序的入口点
-
为使子程序执行完毕能返回主程序的调用处,子程序末尾处应有 MOV、B、BX、LDMFD 等指令,并在指令中将返回地址重新复制到 PC 中
MOV PC, LR
B LR
在调用子程序的同时,也可以使用R0~R3 来进行参数的传递和从子程序返回运算结果
-
使用 BL 指令调用子程序的汇编语言源程序的基本结构
AERA Init, CODE, READONLY ENTRY start LDR R0, = 0X3FF5000 LDR R1, = 0XFF STR R1, [R0] LDR R0, = 0X3FF5008 LDR R1, = 0X01 STR R1, [R0] BL PR ... PR ... MOV PC,LR ;返回主程序 ... END
-
子程序中堆栈的使用(子程序需要的寄存器与主程序使用的寄存器发生冲突时)
STMFD R13!, {R0-R12,LR};压入堆栈 ...... ;子程序代码 LDMFD R13!, {R0-R12,PC};弹出堆栈并返回
汇编程序访问全局 C 变量
一般来说,汇编语言程序与 C 语言程序不在同一个文件上
解决引用不同文件定义的变量的办法就是使用关键字 IMPORT 和 EXPORT
-
在 C 程序中声明的全局变量可以被汇编程序通过地址间接访问,具体访问方法如下
- 使用IMPORT伪指令声明该全局变量
- 使用LDR指令读取该全局变量的内存地址,通常该全局变量的内存地址值存放在程序的数据缓冲池中
- 根据该数据的类型,使用相应的LDR指令读取该全局变量的值;使用相应的STR指令修改该全局变量的值
希望某个变量或符号可以被外部文件引用,必须在定义这个变量或符号的文件里使用 EXPORT 声明
引用全局变量的文件中,使用 IMPORT 来声明该全局变量
-
下面是一个汇编代码的函数,它引用了一个在其他文件中定义的全局变量globvar,将其加 2 后写回 globvar
AREA globals, CODE, READONLY EXPORT asmsubrouttine ;声明可导出符号,其他文件可以调用该函数(asmsubrouttine) IMPORT globvar ;声明引用的外部符号 asmsubrouttine LDR R1, = globvar LDR R0, [R1] ADD R0, R0,#2 STR R0, [R1] MOV PC, LR END