nullnull13254ARM嵌入式微处理器概述ARM体系结构概览ARM程序设计基础ARM编程模型ARM 指令集6嵌入式微处理器相关基本知识null ARM编译器一般都支持汇编语言的程序设计和C/C++语言的程序设计,以及两者的混合编程。
主要
:
6.1- 程序示例
6.2- 汇编语言的语句格式
6.3- 汇编语言的程序结构
6.4- ARM编译器所支持的伪指令
6.5- C/C++和汇编语言的混合编程
6.6-嵌入式程序设计技巧null6.1 ARM程序示例;文件名:TEST1.S
;功能:实现两个寄存器相加
;说明:使用ARMulate软件仿真调试
AREA Example1,CODE,READONLY ;声明代码段Example1
ENTRY ;标识程序入口
CODE32 ;声明32位ARM指令
START MOV R0,#0 ;设置参数
MOV R1,#10
LOOP BL ADD_SUB ;调用子程序ADD_SUB
B LOOP1 ;跳转到LOOP1
ADD_SUB
ADDS R0,R0,R1 ;R0 = R0 + R1
MOV PC,LR ;子程序返回
END ;文件结束 使用“;”进行注释标号顶格写实际代码段声明文件结束null根据程序计算值调用一个子程序
Note: slow when the list is long, and all subroutines are equally frequent6.1 ARM程序示例null AREA HelloW,CODE,READONLY ;声明代码区
SWI_WriteC EQU &0 ;输出r0中的字符
SWI_Exit EQU &11 ;0x11程序结束
ENTRY ;代码入口
START ADR r1,TEXT ;r1---“Hello World”
LOOP LDRB r0,[r1],#1 ;读取下一字节
CMP r0,#0 ;检查文本终点
SWINE SWI_WriteC ;若非终点,则打印
BNE LOOP ;并返回LOOP
SWI SWI_Exit ;执行结束
TEXT = “Hello World”,&0a,&0d,0
END ;程序结束Example:Hello ARM World!6.1 ARM程序示例null6.2 汇编语言的语句格式
ARM(Thumb)汇编语言的语句格式为:
{标号} {指令或伪指令} {;注释}
每一条指令的助记符可以全部用大写、或全部用小写,但不允许在一条指令中大、小写混用。
同时,如果一条语句太长,可将该长语句分为若干行来书写,在行的末尾用“\”
示下一行与本行为同一条语句。 6.2.1 在汇编语言程序中常用的符号
使用各种符号代替地址、变量和常量等,以增加程序的可读性。必须遵循以下的约定:
■命名时可以使用大小写字符、数字和下划线;
■符号区分大小写。
■一个程序段中不能出现重名;
■符号名不能使用关键字;
■除本地行号名称外,名称不能以数字开头null 6.2.2 汇编语言程序中的表达式和运算符
1、 数字表达式及运算符
数字表达式一般由数字常量、数字变量、数字运算符和括号构成。与数字表达式相关的运算符如下:
“+”、“-”、“×”、“/” 及“MOD”算术运算符
X/Y 表示X除以Y的商。
X :MOD:Y 表示X除以Y的余数。
“ROL”、“ROR”、“SHL”及“SHR”移位运算符
X :ROL:Y 表示将X循环左移Y位。
X :SHL:Y 表示将X左移Y位。
“AND”、“OR”、“NOT”及“EOR”按位逻辑运算符
:NOT:Y 表示将Y按位作逻辑非的操作。
X :EOR:Y 表示将X和Y按位作逻辑异或的操作。 null 2、 逻辑表达式及运算符
逻辑表达式一般由逻辑量、逻辑运算符和括号构成,其表达式的运算结果为真或假。与逻辑表达式相关的运算符如下:
“=”、“>”、“<”、“>=”、“<= ”、“/=”、“ <>” 运算符
X <= Y 表示X小于等于Y。
X /= Y 表示X不等于Y。
X <> Y 表示X不等于Y。
“LAND”、“LOR”、“LNOT”及“LEOR”运算符
X :LAND:Y 表示将X和Y 作逻辑与的操作。
X :LOR:Y 表示将X和Y作逻辑或的操作。
:LNOT:Y 表示将Y作逻辑非的操作。
X :LEOR:Y 表示将X和Y作逻辑异或的操作。 null 3、 字符串表达式及运算符
字符串表达式一般由字符串常量、字符串变量、运算符和括号构成。编译器所支持的字符串最大长度为512字节。常用的与字符串表达式相关的运算符如下:
LEN运算符:返回字符串的长度(字符数)
:LEN:X
CHR运算符:将0~255之间的整数转换为一个字符
:CHR:M
STR运算符:将一个数字表达式或逻辑表达式转换为一个字符串。对于数字表达式,STR运算符将其转换为一个以十六进制组成的字符串;对于逻辑表达式,STR运算符将其转换为字符串T或F,其语法格式如下:
:STR:X
LEFT运算符:返回某个字符串左端的一个子串,其语法格式如下:
X:LEFT:Y ;X为源字符串,Y为一个整数,表示要返回的字符个数。
RIGHT运算符:返回某个字符串右端的一个子串,其语法格式如下:
X:RIGHT:Y
CC运算符:用于将两个字符串连接成一个字符串,其语法格式如下:
X:CC:Ynull6.3 汇编语言的程序结构
在ARM(Thumb)汇编语言程序中,以程序段为单位组织代码。
一个汇编程序至少应该有一个代码段,当程序较长时,可以分割为多个代码段和数据段,多个段在程序编译链接时最终形成一个可执行的映象文件。
可执行映象文件通常由以下几部分构成:
一个或多个代码段,代码段的属性为只读。
零个或多个包含初始化数据的数据段,数据段的属性为可读写。
零个或多个不包含初始化数据的数据段,数据段的属性为可读写。
链接器根据系统默认或用户设定的
,将各个段安排在存储器中的相应位置。因此源程序中段之间的相对位置与可执行的映象文件中段的相对位置一般不会相同。 null以下是一个汇编语言源程序的基本结构:
AREA Init,CODE,READONLY
ENTRY
Start
LDR R0,=0x3FF5000
LDR R1,0xFF
STR R1,[R0]
LDR R0,=0x3FF5008
LDR R1,0x01
STR R1,[R0]
┉┉
END
用AREA伪指令定义一个段,并说明所定义段的相关属性,本例定义一个名为Init的代码段,属性为只读。ENTRY伪指令标识程序的入口点,接下来为指令序列,程序的末尾为END伪指令,每一个汇编程序段都必须有一条END伪指令,指示代码段的结束。 null6.4 ARM编译器所支持的伪指令
ADS编译环境下的伪操作可分为以下几类:
符号定义(Symbol Definition)伪操作
数据定义(Data Definition)伪操作
汇编控制(Assembly Control)伪操作
其他(Miscellaneous)伪操作
null6.4.1 符号定义(Symbol Definition)伪指令null
1、GBLA、GBLL和GBLS
GBLA(GBLL或GBLS) 全局变量名
GBLA伪指令用于定义一个全局的数字变量,并初始化为0;
GBLL伪指令用于定义一个全局的逻辑变量,并初始化为F(假);
GBLS伪指令用于定义一个全局的字符串变量,并初始化为空;
由于以上三条伪指令用于定义全局变量,因此在整个程序范围内变量名必须唯一。
GBLA Test1 ;定义一个全局的数字变量,变量名为Test1
Test1 SETA 0xaa ;将该变量赋值为0xaa
GBLL Test2 ;定义一个全局的逻辑变量,变量名为Test2
Test2 SETL {TRUE} ;将该变量赋值为真
GBLS Test3 ;定义一个全局的字符串变量,变量名为Test3
Test3 SETS “Testing” ;将该变量赋值为“Testing” null 2、 LCLA、LCLL和LCLS
LCLA(LCLL或LCLS) 局部变量名
LCLA、LCLL和LCLS伪指令用于定义一个ARM程序中的局部变量,并将其初始化。其中:
LCLA伪指令用于定义一个局部的数字变量,并初始化为0;
LCLL伪指令用于定义一个局部的逻辑变量,并初始化为F(假);
LCLS伪指令用于定义一个局部的字符串变量,并初始化为空;
以上三条伪指令用于声明局部变量,在其作用范围内变量名必须唯一。
LCLA Test4 ;声明一个局部的数字变量,变量名为Test4
Test4 SETA 0xaa ;将该变量赋值为0xaa
LCLL Test5 ;声明一个局部的逻辑变量,变量名为Test5
Test5 SETL {TRUE} ;将该变量赋值为真
LCLS Test6 ;定义一个局部的字符串变量,变量名为Test6
Test6 SETS “Testing” ;将该变量赋值为“Testing” null 3、 SETA、SETL和SETS
变量名 SETA(SETL或SETS) 表达式
伪指令SETA、SETL、SETS用于给一个已经定义的全局变量或局部变量赋值。
SETA伪指令用于给一个数学变量赋值;
SETL伪指令用于给一个逻辑变量赋值;
SETS伪指令用于给一个字符串变量赋值;
其中,变量名为已经定义过的全局变量或局部变量,表达式为将要赋给变量的值。
LCLA Test3 ;声明一个局部的数字变量
Test3 SETA 0xaa ;将该变量赋值为0xaa
LCLL Test4 ;声明一个局部的逻辑变量
Test4 SETL {TRUE} ;将该变量赋值为真 null 4、 RLIST
名称 RLIST {寄存器列表}
RLIST伪指令可用于对一个通用寄存器列表定义名称,使用该伪指令定义的名称可在ARM指令LDM/STM中使用。
在LDM/STM指令中,列表中的寄存器访问次序为根据寄存器的编号由低到高,而与列表中的寄存器排列次序无关。
RegList RLIST {R1,R2,R3,R4}
LDMIA R0,{R1,R2,R3,R4} - LDMIA R0, RegListnull 6.4.2 数据定义(Data Definition)伪指令
数据定义伪指令一般用于为特定的数据分配存储单元,同时可完成已分配存储单元的初始化。
常见的数据定义伪指令有如下几种:
用于分配一片连续的存储单元并用指定的数据初始化:
DCB 分配字节存储单元
DCW(DCWU) 分配半字存储单元
DCD(DCDU) 分配字存储单元
DCFD(DCFDU) 为双精度的浮点数分配字存储单元 DCFS(DCFSU) 为单精度的浮点数分配字存储单元 DCQ(DCQU) 分配一片以8字节为单位的存储单元
SPACE 用于分配一片连续的存储单元
MAP 用于定义一个结构化的内存表首地址
FIELD 用于定义一个结构化的内存表的数据域 null1、 DCB
标号 DCB 表达式
DCB伪指令用于分配一片连续的字节存储单元并用伪指令中指定的表达式初始化。其中,表达式可以为0~255的数字或字符串。DCB也可用“=”代替。
Str DCB “This is a test!” 2、 DCW(或DCWU)
标号 DCW(或DCWU) 表达式
DCW(或DCWU)伪指令用于分配一片连续的半字存储单元并用伪指令中指定的表达式初始化。其中,表达式可以为程序标号或数字表达式。。
用DCW分配的字存储单元是半字对齐的,而用DCWU分配的字存储单元并不严格半字对齐。
DataTest DCW 1,2,3 ;分配一片连续的半字存储单元并初始化。 null 3、 DCFD(或DCFDU)
标号 DCFD(或DCFDU) 表达式
DCFD(或DCFDU)伪指令用于为双精度的浮点数分配一片连续的字存储单元并用伪指令中指定的表达式初始化。每个双精度的浮点数占据两个字单元。
用DCFD分配的字存储单元是字对齐的,而用DCFDU分配的字存储单元并不严格字对齐。
FDataTest DCFD 2E115,-5E7 4、 DCFS(或DCFSU)
标号 DCFS(或DCFSU) 表达式
DCFS(或DCFSU)伪指令用于为单精度的浮点数分配一片连续的字存储单元并用伪指令中指定的表达式初始化。每个单精度的浮点数占据一个字单元。
用DCFS分配的字存储单元是字对齐的,而用DCFSU分配的字存储单元并不严格字对齐。
FDataTest DCFS 2E5,-5E-7 null 5、 SPACE
标号 SPACE 表达式
SPACE伪指令用于分配一片连续的存储区域并初始化为0。其中,表达式为要分配的字节数。SPACE也可用“%”代替。
DataSpace SPACE 100 ;分配连续100字节的存储单元并初始化为0。 6、 MAP
MAP 表达式{,基址寄存器}
MAP伪指令用于定义一个结构化的内存表的首地址。MAP也可用“^”代替。
表达式可以为程序中的标号或数学表达式,基址寄存器为可选项,当基址寄存器选项不存在时,表达式的值即为内存表的首地址,当该选项存在时,内存表的首地址为表达式的值与基址寄存器的和。
MAP伪指令通常与FIELD伪指令配合使用来定义结构化的内存表。
MAP 0x100,R0;定义结构化内存表首地址的值为0x100+R0。 null 7、 FILED
标号 FIELD 表达式
FIELD伪指令用于定义一个结构化内存表中的数据域。FILED也可用“#”代替。
表达式的值为当前数据域在内存表中所占的字节数。
FIELD伪指令常与MAP伪指令配合使用来定义结构化的内存表。MAP伪指令定义内存表的首地址,FIELD伪指令定义内存表中的各个数据域,并可以为每个数据域指定一个标号供其他的指令引用。
注意MAP和FIELD伪指令仅用于定义数据结构,并不实际分配存储单元。
使用示例:
MAP 0x100 ;定义结构化内存表首地址的值为0x100。
A FIELD 16 ;定义A的长度为16字节,位置为0x100
B FIELD 32 ;定义B的长度为32字节,位置为0x110
S FIELD 256 ;定义S的长度为256字节,位置为0x130
数据域引用: LDR R0,B ;将B地址处对应的内容加载到R0
null 6.4.3 汇编控制(Assembly Control)伪指令
汇编控制伪指令用于控制汇编程序的执行流程,常用的汇编控制伪指令包括以下几条:
IF、ELSE、ENDIF
WHILE、WEND
MACRO、MEND
MEXITnull MACRO
$标号 宏名 $参数1,$参数2,……
指令序列
MEND
MACRO、MEND伪指令可以将一段代码定义为一个整体,称为宏指令,然后就可以在程序中通过宏指令多次调用该段代码。其中,$标号在宏指令被展开时,标号会被替换为用户定义的符号,
宏指令可以使用一个或多个参数,当宏指令被展开时,这些参数被相应的值替换。
宏指令和子程序的区别
MACRO、MEND伪指令可以嵌套使用。
MACRO
$label swap $reg1,$reg2,$reg_tmp
MOV $reg_tmp,$reg1
MOV $reg1,$reg2
MOV $reg2,$reg_tmp
MEND
MEXIT
MEXIT用于从宏定义中跳转出去 null 6.4.4 其他常用的伪指令
AREA
ALIGN
CODE16、CODE32
ENTRY
END
EQU
EXPORT(或GLOBAL)
IMPORT
EXTERN
GET(或INCLUDE)
INCBIN
RN
ROUT null 1、 AREA
AREA 段名 属性1,属性2,……
AREA伪指令用于定义一个代码段或数据段。其中,段名若以数字开头,则该段名需用“|”括起来,如|1_test|。
属性字段表示该代码段(或数据段)的相关属性,多个属性用逗号分隔。常用的属性如下:
— CODE属性:用于定义代码段,默认为READONLY。
— DATA属性:用于定义数据段,默认为READWRITE。
— READONLY属性:指定本段为只读,代码段默认为READONLY。
— READWRITE属性:本段为可读可写,数据段的默认属性为READWRITE。
— ALIGN属性:使用方式为ALIGN 表达式。在默认时,可执行连接文件的代码段和数据段是按字对齐的,表达式的取值范围为0~31,相应的对齐方式为2表达式次方。
— COMMON属性:该属性定义一个通用的段,不包含任何的用户代码和数据。各源文件中同名的COMMON段共享同一段存储单元。
一个汇编语言程序至少要包含一个段,当程序太长时,也可以将程序分为多个代码段和数据段。
AREA Init,CODE,READONLY;定义一个代码段,段名为Init,属性只读
指令序列null 2、 ALIGN
ALIGN {表达式{,偏移量}}
ALIGN伪指令可通过添加填充字节的方式,使当前位置满足一定的对齐方式。其中,表达式的值用于指定对齐方式,可能的取值为2的表达式次幂,如1、2、4、8、16等。若未指定表达式,则将当前位置对齐到下一个字的位置。偏移量也为一个数字表达式,若使用该字段,则当前位置的对齐方式为:2的表达式次幂+偏移量。null 3、 CODE16、CODE32
语法格式:
CODE16(或CODE32)
CODE16伪指令通知编译器,其后的指令序列为16位的Thumb指令。
CODE32伪指令通知编译器,其后的指令序列为32位的ARM指令。
在使用ARM指令和Thumb指令混合编程的代码里,可用这两条伪指令进行切换,但注意他们只通知编译器其后指令的类型,并不能对处理器进行状态的切换。
AREA Init,CODE,READONLY,ALIGN=3
……
CODE32 ;通知编译器其后的指令为32位的ARM指令
LDR R0,=NEXT+1 ;将跳转地址放入寄存器R0
BX R0 ;程序跳,并将处理器切换到Thumb工作状态
……
CODE16 ;通知编译器其后的指令为16位的Thumb指令
NEXT LDR R3,=0x3FF
……
END ;程序结束 null 5、 END
END
END伪指令用于通知编译器已经到了源程序的结尾。
使用示例:
AREA Init,CODE,READONLY
……
END ;指定应用程序的结尾 4、 ENTRY
ENTRY
ENTRY伪指令用于指定汇编程序的入口点。在一个完整的汇编程序中至少要有一个ENTRY(也可以有多个,当有多个ENTRY时,程序的真正入口点由链接器指定),但在一个源文件里最多只能有一个ENTRY(可以没有)。
使用示例:
AREA Init,CODE,READONLY
ENTRY ;指定应用程序的入口点
……null 7、 EXPORT(或GLOBAL)
EXPORT 标号{[WEAK]}
EXPORT伪指令用于在程序中声明一个全局的标号,该标号可在其他的文件中引用。EXPORT可用GLOBAL代替。标号在程序中区分大小写,[WEAK]选项声明其他的同名标号优先于该标号被引用。
AREA Init,CODE,READONLY
EXPORT Stest ;声明一个可全局引用的标号Stest
……
END 6、 EQU
名称 EQU 表达式{,类型}
EQU伪指令用于为程序中的常量、标号等定义一个等效的字符名称,类似于C语言中的#define。其中EQU可用“*”代替。
名称为EQU伪指令定义的字符名称,当表达式为32位的常量时,可以指定表达式的数据类型,可以有以下三种类型:CODE16、CODE32和DATA
使用示例:
Test EQU 50 ;定义标号Test的值为50
Addr EQU 0x80,CODE32 ;定义Addr的值为0x80,且该处为32位的ARM指令。 null8、 IMPORT
语法格式:
IMPORT 标号{[WEAK]}
IMPORT伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用,而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中。
标号在程序中区分大小写,[WEAK]选项表示当所有的源文件都没有定义这样一个标号时,编译器也不给出错误信息,在多数情况下将该标号置为0,若该标号为B或BL指令引用,则将B或BL指令置为NOP操作。
使用示例:
AREA Init,CODE,READONLY
IMPORT Main ;通知编译器当前文件要引用标号Main,但Main在其他源文件中定义
……
END null 9、 EXTERN
EXTERN 标号{[WEAK]}
EXTERN伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用,如果当前源文件实际并未引用该标号,该标号就不会被加入到当前源文件的符号表中。
标号在程序中区分大小写,[WEAK]选项表示当所有的源文件都没有定义这样一个标号时,编译器也不给出错误信息,在多数情况下将该标号置为0,若该标号为B或BL指令引用,则将B或BL指令置为NOP操作。
使用示例:
AREA Init,CODE,READONLY
EXTERN Main ;通知编译器当前文件要引用标号Main,但Main在其他源文件中定义
……
END null 10、 GET(或INCLUDE)
GET 文件名
GET伪指令用于将一个源文件包含到当前的源文件中,并将被包含的源文件在当前位置进行汇编处理。可以使用INCLUDE代替GET。
汇编程序中常用的方法是在某源文件中定义一些宏指令,用EQU定义常量的符号名称,用MAP和FIELD定义结构化的数据类型,然后用GET伪指令将这个源文件包含到其他的源文件中。使用方法与C语言中的“include”相似。
GET伪指令只能用于包含源文件,包含目标文件需要使用INCBIN伪指令
使用示例:
AREA Init,CODE,READONLY
GET a1.s ;通知编译器包含源文件a1.s
GET C:\a2.s ;通知编译器包含源文件C:\a2.s
……
END null 11、 INCBIN
INCBIN 文件名
INCBIN伪指令用于将一个目标文件或数据文件包含到当前的源文件中,被包含的文件不作任何变动的存放在当前文件中,编译器从其后开始继续处理。
使用示例:
AREA Init,CODE,READONLY
INCBIN a1.dat ;通知编译器包含文件a1.dat
INCBIN C:\a2.txt ;通知编译器包含文件C:\a2.txt
……
END null 12、 RN
名称 RN 表达式
RN伪指令用于给一个寄存器定义一个别名。采用这种方式可以方便程序员记忆该寄存器的功能。其中,名称为给寄存器定义的别名,表达式为寄存器的编码。
使用示例:
Temp RN R0 ;将R0定义一个别名Temp 13、 ROUT
{名称} ROUT
ROUT伪指令用于给一个局部变量定义作用范围。在程序中未使用该伪指令时,局部变量的作用范围为所在的AREA,而使用ROUT后,局部变量的作为范围为当前ROUT和下一个ROUT之间。nullARM伪指令——小范围的地址读取 ADR伪指令将基于PC相对偏移的地址值或基于寄存器相对偏移的地址值读取到寄存器中。在汇编编译器编译源程序时,ADR伪指令被编译器替换成一条合适的指令。通常,编译器用一条ADD指令或SUB指令来实现该ADR伪指令的功能,若不能用一条指令实现,则产生错误,编译失败。ADR{cond} register,exprADR伪指令格式指令执行的条件码加载的目标寄存器地址表达式 地址表达式expr的取指范围:
当地址值不是字对齐时,其取指范围为-255~255;
当地址值是字对齐时,其取指范围为-1020~1020;
当地址值是16字节对齐时,其取指范围将更大。由ADD、SUB指令立即数域(4+8位)决定的nullARM伪指令——小范围的地址读取 ADR伪指令将基于PC相对偏移的地址值或基于寄存器相对偏移的地址值读取到寄存器中。在汇编编译器编译源程序时,ADR伪指令被编译器替换成一条合适的指令。通常,编译器用一条ADD指令或SUB指令来实现该ADR伪指令的功能,若不能用一条指令实现,则产生错误,编译失败。 ...
ADR R0,Delay
...
Delay
MOV R0,r14
...应用示例(源程序):使用伪指令将程序标号Delay的地址存入R0null ...
0x20 ADD r0,pc,#0x3c
...
...
0x64 MOV r0,r14
...ARM伪指令——小范围的地址读取 ADR伪指令将基于PC相对偏移的地址值或基于寄存器相对偏移的地址值读取到寄存器中。在汇编编译器编译源程序时,ADR伪指令被编译器替换成一条合适的指令。通常,编译器用一条ADD指令或SUB指令来实现该ADR伪指令的功能,若不能用一条指令实现,则产生错误,编译失败。 ...
ADR R0,Delay
...
Delay
MOV R0,r14
...应用示例(源程序):编译后的反汇编代码:使用伪指令将程序标号Delay的地址存入R0地址程序代码nullARM伪指令——小范围的地址读取 ADR伪指令将基于PC相对偏移的地址值或基于寄存器相对偏移的地址值读取到寄存器中。在汇编编译器编译源程序时,ADR伪指令被编译器替换成一条合适的指令。通常,编译器用一条ADD指令或SUB指令来实现该ADR伪指令的功能,若不能用一条指令实现,则产生错误,编译失败。 ...
ADR R0,Delay
...
Delay
MOV R0,r14
...应用示例(源程序): ...
0x20 ADD r0,pc,#0x3c
...
...
0x64 MOV r0,r14
...编译后的反汇编代码:使用伪指令将程序标号Delay的地址存入R0ADR伪指令被汇编成一条指令nullARM伪指令——小范围的地址读取 ADR伪指令将基于PC相对偏移的地址值或基于寄存器相对偏移的地址值读取到寄存器中。在汇编编译器编译源程序时,ADR伪指令被编译器替换成一条合适的指令。通常,编译器用一条ADD指令或SUB指令来实现该ADR伪指令的功能,若不能用一条指令实现,则产生错误,编译失败。 应用示例2(查表):
ADR R0,DISP_TAB ; 加载转换表地址
LDRB R1,[R0,R2] ; 使用R2作为参数,进行查表
…
DISP_TAB
DCB 0xC0,0xF9,0xA4,0xB0,0x99, 0x92,0x82,0xF8nullARM伪指令——中等范围的地址读取 ADRL伪指令将基于PC相对偏移的地址值或基于寄存器相对偏移的地址值读取到寄存器中,比ADR伪指令可以读取更大范围的地址 。在汇编编译器编译源程序时,ADRL伪指令被编译器替换成两条合适的指令。若不能用两条指令实现,则产生错误,编译失败。ADRL{cond} register,exprADRL伪指令格式指令执行的条件码加载的目标寄存器地址表达式 地址表达式expr的取指范围:
当地址值不是字对齐时,其取指范围为-64K~64K;
当地址值是字对齐时,其取指范围为-256K~256K;
当地址值是16字节对齐时,其取指范围将更大。nullARM伪指令——中等范围的地址读取 ADRL伪指令将基于PC相对偏移的地址值或基于寄存器相对偏移的地址值读取到寄存器中,比ADR伪指令可以读取更大范围的地址 。在汇编编译器编译源程序时,ADRL伪指令被编译器替换成两条合适的指令。若不能用两条指令实现,则产生错误,编译失败。 ...
ADRL R0,Delay
...
Delay
MOV R0,r14
...应用示例(源程序):使用伪指令将程序标号Delay的地址存入R0nullARM伪指令——中等范围的地址读取 ADRL伪指令将基于PC相对偏移的地址值或基于寄存器相对偏移的地址值读取到寄存器中,比ADR伪指令可以读取更大范围的地址 。在汇编编译器编译源程序时,ADRL伪指令被编译器替换成两条合适的指令。若不能用两条指令实现,则产生错误,编译失败。 ...
ADRL R0,Delay
...
Delay
MOV R0,r14
...应用示例(源程序): ...
0x20 ADD r0,pc,#40
0x24 ADD r0,r0,#0
...
0x68 MOV r0,r14
...编译后的反汇编代码:使用伪指令将程序标号Delay的地址存入R0地址程序代码nullARM伪指令——中等范围的地址读取 ADRL伪指令将基于PC相对偏移的地址值或基于寄存器相对偏移的地址值读取到寄存器中,比ADR伪指令可以读取更大范围的地址 。在汇编编译器编译源程序时,ADRL伪指令被编译器替换成两条合适的指令。若不能用两条指令实现,则产生错误,编译失败。 ...
ADRL R0,Delay
...
Delay
MOV R0,r14
...应用示例(源程序): ...
0x20 ADD r0,pc,#40
0x24 ADD r0,r0,#0
...
0x68 MOV r0,r14
...编译后的反汇编代码:使用伪指令将程序标号Delay的地址存入R0ADRL伪指令被汇编成两条指令,尽管第2条指令并没有意义nullARM伪指令——大范围的地址读取 LDR伪指令用于加载32位的立即数或一个地址值到指定寄存器。在汇编编译源程序时,LDR伪指令被编译器替换成一条合适的指令。若加载的常数未超出MOV或MVN的范围,则使用MOV或MVN指令代替该LDR伪指令,否则汇编器将常量放入文字池,并使用一条程序相对偏移的LDR指令从文字池读出常量。LDR{cond} register,=exprLDR伪指令格式指令执行的条件码加载的目标寄存器基于PC的地址表达式或外部表达式nullARM伪指令——大范围的地址读取 LDR伪指令用于加载32位的立即数或一个地址值到指定寄存器。在汇编编译源程序时,LDR伪指令被编译器替换成一条合适的指令。若加载的常数未超出MOV或MVN的范围,则使用MOV或MVN指令代替该LDR伪指令,否则汇编器将常量放入文字池,并使用一条程序相对偏移的LDR指令从文字池读出常量。应用示例(源程序): ...
LDR R1,=InitStack
...
InitStack
MOV R0, LR
...使用伪指令将程序标号InitStack的地址存入R1nullARM伪指令——大范围的地址读取 LDR伪指令用于加载32位的立即数或一个地址值到指定寄存器。在汇编编译源程序时,LDR伪指令被编译器替换成一条合适的指令。若加载的常数未超出MOV或MVN的范围,则使用MOV或MVN指令代替该LDR伪指令,否则汇编器将常量放入文字池,并使用一条程序相对偏移的LDR指令从文字池读出常量。应用示例(源程序):编译后的反汇编代码: ...
LDR R1,=InitStack
...
InitStack
MOV R0, LR
... ...
0x60 MOV R0,0xb4
LDR R1,[R0]
...
0x64 MOV R0, LR
...
0xb4 DCD 0x64使用伪指令将程序标号InitStack的地址存入R1地址程序代码nullARM伪指令——大范围的地址读取 LDR伪指令用于加载32位的立即数或一个地址值到指定寄存器。在汇编编译源程序时,LDR伪指令被编译器替换成一条合适的指令。若加载的常数未超出MOV或MVN的范围,则使用MOV或MVN指令代替该LDR伪指令,否则汇编器将常量放入文字池,并使用一条程序相对偏移的LDR指令从文字池读出常量。应用示例(源程序):编译后的反汇编代码: ...
LDR R1,=InitStack
...
InitStack
MOV R0, LR
... ...
0x60 MOV R0,0xb4
LDR R1,[R0]
...
0x64 MOV R0, LR
...
0xb4 DCD 0x64使用伪指令将程序标号InitStack的地址存入R1LDR伪指令被汇编成一条LDR指令,并在文字池中定义了一个常量,该常量为InitStack标号的地址nullARM伪指令——大范围的地址读取 LDR伪指令用于加载32位的立即数或一个地址值到指定寄存器。在汇编编译源程序时,LDR伪指令被编译器替换成一条合适的指令。若加载的常数未超出MOV或MVN的范围,则使用MOV或MVN指令代替该LDR伪指令,否则汇编器将常量放入文字池,并使用一条程序相对偏移的LDR指令从文字池读出常量。注意:
1.从指令位置到文字池的偏移量必须小于4KB;
2.与ARM指令的LDR相比,伪指令的LDR的参数有“=”号。nullARM伪指令——空操作伪指令 NOP伪指令在汇编时将会被代替成ARM中的空操作,比如可能是“MOV R0,R0”指令等。NOP可用于延时操作。NOPNOP伪指令格式应用示例(延时子程序):Delay
NOP ;空操作
NOP
NOP
SUBS R1,R1,#1 ;循环次数减一
BNE Delay ;如果循环没有结束,跳转Delay继续
MOV PC,LR ;子程序返回null ADS编译环境下伪指令与GNU编译环境下伪指令对比:(主要是符号及伪操作的不同)null6.5 汇编语言与C/C++的混合编程null6.5.1 APCS介绍 APCS(ARM Produce Call Standard)是ARM程序中子程序调用的基本规则,目的是为了使单独编译的C语言程序和汇编程序之间能够相互调用。这些基本规则包括子程序调用过程中寄存器的使用规则、数据栈的使用规则和参数的传递规则。 null寄存器的使用规则子程序间通过寄存器R0~R3来传递参数,这时,寄存器R0~R3可以记作A1~A4。
在子程序中,使用寄存器R4~R11来保存局部变量。这时,寄存器 R4~R11可以记作V1~V8。在Thumb程序中,通常只能使用寄存器R4~R7来保存局部变量 。
寄存器R12用作子程序间的scratch寄存器(用于保存SP,在函数返回时使用该寄存器出栈),记作ip。
寄存器R13用作数据栈指针,记作sp。 SP在进出子程序时的值必须相等
寄存器R14称为链接寄存器,记作lr。
寄存器R15是程序计数器,记作pc。 nullnull数据栈的使用规则根据堆栈指针指向位置的不同 和增长方向的不同可以分为以下4种数据栈 :
FD (Full Descending) 满递减
ED (Empty Descending)空递减
FA (Full Ascending) 满递增
EA (Empty Ascending) 空递增
APCS规定数据栈为FD(满递减)类型null参数的传递规则
当参数不超过4个时,可以使用寄存器R0~R3来传递参数;当参数超过4个时,还可以使用数据栈来传递参数,入栈的顺序与参数顺序相反,即最后一个数据先入栈。null6.5.2 混合编程:
汇编语言与C/C++的混合编程通常有以下几种方式:
- 在C/C++代码中嵌入汇编指令。
- 在汇编程序和C/C++的程序之间进行变量的互访。
- 汇编程序、C/C++程序间的相互调用。
在以上的几种混合编程技术中,必须遵守一定的调用规则,如物理寄存器的使用、参数的传递等。
一般情况:
程序的初始化部分用汇编语言完成,
然后用C/C++完成主要的编程任务,
汇编程序和C/C++程序之间一般没有参数的传递,也没有频繁的
相互调用,因此,整个程序的结构显得相对简单,容易理解。 嵌入式汇编语句嵌入式汇编语句ANSI C++中:
asm(“instruction[; instruction]”)
另一种:__asmnull 这个例子中汇编部分的作用是将m和n两个变量的值交换
嵌入式汇编和普通汇编有很多不同
首先,嵌入式汇编不直接使用ARM的物理寄存器(CPSR除外),但可以直接访问变量(当作寄存器用)
此外.使用嵌入式汇编时应注意:
l)指令无法得到PC值
2)不能使用“LDR Rn,=expr”伪语言,也不能使用ADR和ADRL,只能使用MOV
3) 不要在高级语言中使用r0, r1,CPRS之类的变量
4)无法修改程序栈和CPU模式
降低了可维护性,在较少的场合中使用 nullnull高级语言和汇编语言函数间的相互调用 :
汇编调用C:
IMPORT Main ;通知编译器该标号为一个外部标号
AREA Init,CODE,READONLY ;定义一个代码段
ENTRY ;定义程序的入口点
LDR R0,=0x3FF0000 ;初始化系统配置寄存器
LDR R1,=0xE7FFFF80
STR R1,[R0]
LDR SP,=0x3FE1000 ;初始化用户堆栈
BL Main ;跳转到Main()函数处的C/C++代码执行
END ;标识汇编程序的结束
以上的程序段完成一些简单的初始化,然后跳转到Main()函数所标识的
C/C ++代码处执行主要的任务,此处的Main仅为一个标号,也可使用其他名称。 nullnullC函数原型:
int g(int a,int b,int c,int d,int e)
{
return a+b+c+d+e;
}
////汇编程序调用C程序g()计算5个整数i, 2*i, 3*i, 4*i, 5*i 的和。
汇编源程序:
EXPORT f
AREA f,CODE,READONLY
IMPORT g ;声明该变量函数g( ),i在R0中
STR LR,[SP,#- 4]! ;预先保存LR
ADD R1,R0,R0 ;计算2 * i(第2个参数)
ADD R2,R1,R0 ;计算3*i(第3个参数)
ADD R3,R1,R2 ;计算5*i(第5个参数)
STR R3,[SP,#- 4]! ;将第5个参数压人堆栈
ADD R3,R1,R1 ;计算4 * i(第4个参数)
BL g ;调用C程序g( )
ADD SP,SP,#4 ;调整数据栈指针,准备返回
LDR PC,[SP],#4 ;从子程序返回
ENDnullC调用汇编:nullC源程序:
# include
extern void strcopy(char *d,const char *s);用extern声明一个函数为外部函数,
int main()
{
const char *srcstr =“First string-source”;
char * dststr =“Second string-destination”;
strcopy(dststr,srcstr) ;调用汇编函数strcopy()
return(0);
}
汇编源程序:
AREA SCopy,CODE,READONLY
EXPORT strcopy ;用EXPORT伪操作声明该变量可以被其他
;文件引用,相当于声明了一个全局变量。
Strcopy ;R0指向目标字符串,R1指向源字符串
LDRB R2,[R1],#1 ;字节加载,并更新地址
STRB R2,[R0],#1 ;字节保存,并更新地址
CMP R2,#0 ;检测R2是否等于0
BNE strcopy ;若条件不成立则继续执行
MOV PC,LR ;从子程序返回
ENDnull高级语言和汇编语言函数间的通过变量互访 :
使用IMPORT在汇编中定义高级语言中的全局变量,由于此时传递的是地址,因此使用LDR/LDRB/LDRH伪指令将变量加载到寄存器中:
IMPORT globvar
LDR r1,=globvar
高级语言数据类型不同,分配的字长不同,使用时应加以注意
一般情况下:
char 1byte 使用 LDRB/STRB
short 2byte LDRH/STRH
int 1word LDR /STR
6.6 嵌入式程序设计技巧6.6 嵌入式程序设计技巧变量定义
参数传递
循环条件
以空间换时间
数学方法解决问题
使用位操作
嵌入汇编一、变量定义 一、变量定义 在变量声明的时候,最好把所有相同类型的变量放在一起定义,这样可以优化存储器布局。由下例可以看出:
对于局部变量类型的定义,使用short或char来定义变量并不是总能节省存储空间。有时使用32位int或unsinged int局部变量更有效率一些,如下图所示:
变量定义中,为了精简程序,程序员总是竭力避免使用冗余变量。但有时使用冗余变量可以减少存储器访问的次数,提高系统性能。 二、参数传递 二、参数传递 为了使单独编译的C语言程序和汇编程序能够互相调用,定义了统一的函数过程调用ATPCS。ATPCS定义了寄存器组中的{R0~R3}作为参数传递和结果返回寄存器,如果参数数目超过四个,则使用堆栈进行传递。
内部寄存器的访问速度是远远大于存储器的,所以要尽量使参数传递在寄存器里面进行,即应尽量把函数的参数控制在四个以下。 三、循环条件 三、循环条件 计数循环是程序中十分常用的流程控制结构,一般有以下两种形式:
for (loop=1;loop<=limit;loop++)
for (loop=limit;loop!=0;loop--)
这两种循环形式在逻辑上并没有效率差异,但是映射到具体的体系结构中时,就产生了很大的不同,如下图所示。 四、以空间换时间 四、以空间换时间 计算机程序中最大的矛盾是空间和时间的矛盾,从这个角度出发逆向思维来考虑程序的效率问题,比如若系统的实时性很高,内存还有剩余,则我们就有可以用以空间换时间的方法来提高程序执行的效率。 五、数学方法解决问题 五、数学方法解决问题 数学是计算机之母,计算机的发展是以数学为依据和基础的,所以在编写程序的时候,适当地采用一些数学