嵌入式系统原理与应用 第3版 课件 第4章 微处理器ARM程序设计_第1页
嵌入式系统原理与应用 第3版 课件 第4章 微处理器ARM程序设计_第2页
嵌入式系统原理与应用 第3版 课件 第4章 微处理器ARM程序设计_第3页
嵌入式系统原理与应用 第3版 课件 第4章 微处理器ARM程序设计_第4页
嵌入式系统原理与应用 第3版 课件 第4章 微处理器ARM程序设计_第5页
已阅读5页,还剩70页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第4章微处理器ARM编程程序设计不但要掌握处理器的指令功能与使用,还必须借助ARM的集成开发环境进行程序的代码编辑、编译、和调试运行。为了提高编程效率,缩短开发周期,还必须学会使用C/C++语言进行程序设计。这就要求我们掌握ARM9汇编语言伪指令、汇编语言程序的语法结构、ARM处理器的C语言编程、汇编语言与C语言混合编程的ATPCS规则等。本章主要讲述ARM伪指令、ARM汇编语言程序设计、ARM汇编语言与C语言的混合编程以及子程序或函数之间的相互调用。最后通过实例讲述ARM汇编语言与C语言之间的参数传递方法、程序之间的相互调用等。124.1ARM汇编伪指令ARM汇编语言源程序是由伪指令、ARM指令和宏指令组成。伪指令完成的操作称为伪操作。伪指令的定义:在ARM汇编语言程序里,有一些特殊指令助记符,这些特殊的助记符与指令系统的助记符不同,没有相对应的操作码,也就是不会生成机器码,仅仅是在编译软件中起着格式化的作用,通常称这些特殊指令助记符为伪指令。伪指令的作用:是为完成汇编程序作各种准备工作的,这些伪指令仅在汇编过程中起作用,一旦汇编结束,伪指令的使命就结束了。宏指令:实际上是一段独立的程序代码,在汇编程序中通过宏名来调用,而在程序被汇编时,调用的宏将被展开,用宏的定义体代替宏名。因此宏可以使程序代码更加简洁直观,是用指令占用的空间换取执行的时间。ARM汇编可以在Windows操作系统下的ADS1.2集成开发环境中进行,也可以在Linux操作系统下的GUN开发环境中进行。以下介绍ADS1.2开发环境中ARM汇编器中使用的伪指令。34.1.1数据常量定义伪指令数据常量定义伪指令EQU用于为程序中的常量、标号等定义一个等效的字符名称,类似于C语言中的#define。EQU语法格式如下:

常量名称EQU表达式{,类型}

其中:名称为EQU伪指令定义的字符名称,当表达式为32位的常量时,可以指定表达式的数据类型,可以使用以下三种类型:CODE16、CODE32和DATA

。EQU可用符号“*”代替。应用示例:Data_inEQU200;定义标号Data_in的值为200

,AddrEQU0xff,CODE32;

定义标号Addr的值为0xff,且该处为32位的ARM指令地址

注意:EQU可以定义字节、半字、字长度的数据值,也可以认为是常数,主要在于应用的场合;也可以定义32位的地址值等。44.1.2数据变量定义伪指令数据变量定义伪指令用于定义ARM汇编程序中的变量、对变量赋值以及定义寄存器的别名等操作。全局变量伪指令GBLA、GBLL和GBLS全局变量一般是指在这个汇编程序中定义,需要在另外一个汇编程序中应用的变量。GBLA(GBLL或GBLS)全局变量名GBLA用于定义一个全局的数字变量,并初始化为0。GBLL用于定义一个全局的逻辑变量,并初始化F。GBLS用于定义一个全局的字符串变量,并初始化为空。应用示例:(说明全局变量名必须唯一)GBLAVariable1;定义全局的数字变量,变量名为Variable1GBLLVariable2;定义全局的逻辑变量,变量名为Variable2GBLSVariable3;定义全局的字符串变量,变量名为Variable352.局部变量伪指令LCLA、LCLL和LCLS

局部变量是指在某个汇编程序中定义并使用的变量,在其它汇编程序中不能使用。语法格式如下:

LCLA(LCLL或LCLS)局部变量名

LCLA伪指令用于定义一个局部的数字变量,并初始化为0。LCLL伪指令用于定义一个局部的逻辑变量,并初始化为F(假)。LCLS伪指令用于定义一个局部的字符串变量,并初始化为空。应用示例:(局部必须唯一)LCLAVariable4;定义局部的数字变量,变量名为Variable463.变量赋值伪指令SETA、SETL和SETS

变量赋值伪指令既可以给全局变量赋值,也可以给局部变量赋值。伪指令SETA、SETL、SETS用于给一个已经定义的全局变量或局部变量赋值。伪指令的格式如下:

变量名SETA(SETL或SETS)表达式

SETA伪指令用于给一个数字变量赋值。SETL伪指令用于给一个逻辑变量赋值。SETS伪指令用于给一个字符串变量赋值。应用示例:Variable1SETA0xbb;将该变量赋值为数字值0xbbVariable2SETL{TRUE};将该变量赋值为逻辑“真”Variable3SETS“Testing”;将该变量赋值为字符串Testing74.寄存器列表定义伪指令RLIST

RLIST伪指令可用于对一个通用寄存器列表定义名称,使用该伪指令定义的名称可在ARM指令LDM/STM中使用。在LDM/STM指令中,列表中的寄存器访问次序根据寄存器的编号由低到高,列表中的寄存器排列次序必须从小到大。伪指令格式如下:

寄存器列表名称RLIST{寄存器列表}

应用示例:

Reglist1RLIST{R0-R6,R8,R10};将寄存器列表名称定义为Reglist1,以后可直接指令中使用84.1.3内存分配伪指令内存分配伪指令一般用于为特定的数据分配存储单元,同时可完成已分配存储单元的初始化。以下介绍常见的内存分配数据定义伪指令。1.字节分配伪指令DCB

该伪指令用于分配一片连续的字节(8bit)存储单元,并用伪指令中指定的表达式初始化。其中表达式的值可以为0~255或字符串。DCB伪指令也可以用符号“=”代替。指令格式如下:

标号DCB表达式应用示例:Str1DCB“Thisisastring”;分配一片连续的字节存储单元并初始化。Str2DCB0x0,0x02,0x03,0x04,0x05

;分配一片连续的字节存储单元并初始化的另一种方式。92.半字分配伪指令DCW(或DCWU)

该伪指令用于分配一片连续的半字(16bit)存储单元,并用伪指令中指定的表达式初始化。其中表达式可以为程序标号或表达式。DCW要求半字对齐,而DCWU不要求半字对齐。指令格式如下:

标号DCW(或DCWU)

表达式

应用示例:DataTestDCW1,2,3;分配连续的半字存储单元并初始化注意:每一个数字均占2字节存储单元,即半字。3.字分配伪指令DCD(或DCDU)

该伪指令用于分配一片连续的字存储(32bit)单元,并用伪指令中指定的表达式初始化。可以使用符号“&”代替。指令格式如下:

标号DCD(或DCDU)表达式

应用示例:WordDataDCD1,2,3;分配连续的字存储单元并初始化,每个数字占4字节内存单元即字单元104.DCFS(或DCFSU)伪指令

该指令用于为单精度的浮点数分配一片连续的存储单元,每个双精度数占1个字单元,并用伪指令中指定的表达式初始化。伪指令格式如下

标号DCFS(或DCFSU)表达式应用示例:SFloatDataDCFS–5E7,2E-8

;分配连续的字存储单元并初始化为指定的单精度数。5.DCFD(或DCFDU)伪指令

该伪指令用于为双精度的浮点数分配一片连续的存储单元,每个双精度数占2个字单元,并用伪指令中指定的表达式初始化。伪指令格式如下:

标号DCFD(或DCFDU)

表达式应用示例:FloatDataDCFD–5E7,2E-8

;分配连续的字存储单元并初始化为指定的双精度数。116.DCQ(或DCQU)伪指令

该指令用于分配一片以8字节为单位连续的存储区域,并用伪指令中指定的表达式初始化。伪指令格式如下:

标号DCQ(或DCQU)表达式应用示例:

B8DataDCFD100,200;分配2个连续的8字节存储单元并初始化。7.SPACE

伪指令

该指令用于分配一片连续的字节存储单元并初始化为0。其中表达式是要分配的字节数。SPACE也可以用符号“%”来代替。伪指令格式如下:

标号SPACE表达式

应用示例:

SpaceDataSPACE100;分配连续100个字节单元并用0初始化。128.MAP伪指令

该指令用于定义一个结构化的内存表首地址。MAP也可以用符号“^”来代替。表达式可以是程序中的标号或数学表达式。基址寄存器可选,当它不存在时,表达式的值为内存表的首地址;当该项存在时,内存表的首地址即为表达式的值与寄存器的值之和。MAP伪指令常与FIELD伪指令配合使用来定义一个内存表。

MAP表达式{,基址寄存器}应用示例:MAP0x0100,R0;定义结构化首地址值为0x0100+R09.FIELD伪指令

该指令用于定义一个结构化内存表的数据域,也可用符号“#”代替,表达式的值为当前数据域在内存中所占的字节数。FIELD伪指令常与MAP伪指令配合使用来定义结构化的内存表。MAP伪指令定义内存表的首地址,FIELD伪指令定义内存表中的各个数据域字节个数,并可以为每个数据域指定一个标号供其他的指令引用。它们与C语言中的struct结构体雷同。FIELD伪指令的格式如下:

标号FIELD表达式13应用示例:1)在内存中定义异常模式程序入口地址的结构化存储空间,即每个字单元中的内容是相对应的异常模式程序入口地址。由它们组成的表称为异常向量表2)在内存中定义ARM的32个中断程序或中断函数入口地址的结构化存储空间,即每个字单元中的内容是相应的中断程序或中断函数入口地址。由它们组成的表称为中断向量表。;异常向量表定义如下:^_ISR_STARTADDRESS;指定结构体地址的开始位置HandleReset#4;占用第1个字单元(4字节)HandleUndef #4;占用第2个字单元(4字节)HandleSWI #4;占用第3个字单元(4字节)HandlePabort #4;占用第4个字单元(4字节)HandleDabort #4;占用第5个字单元(4字节)HandleReserved#4;(预留)占用第6个字单元(4字节)HandleIRQ #4;占用第7个字单元(4字节)HandleFIQ #4;占用第8个字单元(4字节);以上的字单元内容通过程序将它们各自的异常程序入口地址写入到相应的内存空间中。;起到由固定的异常向量地址转向到存储器的虚拟异常向量地址。以下是内存中断向量表定义:HandleEINT0 #4;占用第9个字单元(4字节)HandleEINT1 #4;占用第10个字单元(4字节)

…HandleADC #4;占用第40个字单元(4字节);以上4行实际使用时应该有32行,分别为32个ARM中断源定义存储它们入口程序的地址值;通过将汇编中断服务程序入口标号或C语言中断函数名赋值到定义的存储单元中,就可以执行相应的中断程序或调用中断函数了。关于它们具体操作在ARM中断程序设计8.2节中讲述。1410.LTORG伪指令

文字池伪指令LTORG,用于声明一个数据缓冲池(文字池)的开始。通常ARM汇编器把文字池放在代码段的最后面,但必须在END伪指令之前。LTORG伪指令经常放在无条件跳转指令之后,或子程序返回指令之后,这样处理器不会错误地将字缓冲池中的数据当作指令来执行。使用时只需要用伪指令LTORG声明,文字池中的内容是由汇编器自动生成的。应用示例:

startBLfunc1;调用子程序

func1LDRR1,=0x33333334;子程序入口,LDR是伪指令,立即数不可用前述的12bit来表示…;0x33333334将会自动写入到文字池预留的3000个字节单元中

MOVPC,RL;子程序返回

LTORG;声明文字池DataSPACE3000;预留3000字节存储空间

END;汇编结束伪指令154.1.4汇编控制伪指令汇编控制伪指令用于控制汇编程序的执行流程,主要有条件汇编伪指令、宏定义伪指令和重复汇编控制伪指令等,该类伪指令如下:1)条件汇编控制:IF,ELSE,ENDIF2)宏定义:MACRO和MEND3)重复汇编:WHILE和WEND161.IF、ELSE、ENDIF

伪指令IF、ELSE、ENDIF伪指令能够根据条件把一段程序代码包括在汇编程内或将其排除在汇编程序之外。“[”可代替IF,“|”可代替ELSE,“]”可代替ENDIF。伪指令的格式如下:IF逻辑表达式

指令序列1

ELSE

指令序列2

ENDIF

在格式中,如果逻辑表达式为真,则指令序列1被汇编;

否则,指令序列2被汇编。ELSE可有可无。应用示例:GBLLTHUMBCODE;定义全局逻辑变量[{CONFIG}=16;如果变量CONFIG==16,即目前处于16位Thumb模式,;则条件为真。这里花括号{}是指取变量的值THUMBCODESETL{TRUE};Thumb状态模式变量赋值为真CODE32;转入ARM模式,以下是32位ARM指令代码|;ELSETHUMBCODESETL{FALSE};Thumb状态模式变量赋值为假]2.MACRO和MEND伪指令MACRO用于标识宏定义的开始,MEND用于标识宏定义的结束。用MACRO和MEND定义的代码,称为宏定义,这样在程序中就可以通过宏指令多次调用该代码段。宏指令的格式如下:MACRO$标号

宏名$参数1,$参数2,……

指令序列

MEND

其中,$表示其后的标号或参数是宏变量,使用时可以进行宏替换。例如:$标号在宏指令被展开时,标号会被替换为用户定义的符号,宏指令的参数可以使用一个或多个,$参数n在宏指令被展开时,这些参数被相应的数值所替换。1718应用示例:MACRO;定义异常向量转移宏汇编,主要用于中断模式$HandlerLabelHANDLER$HandleLabel$HandlerLabelSUBSP,SP,#4;①修改堆栈指针,用于保存跳转地址STMFDSP!,{R0};②压栈保护R0寄存器内容LDRR0,=$HandleLabel;③将转移的地址存入R0LDRR0,[R0];④将$HandleLabel的内容送R0STRR0,[SP,#4];⑤将R0的内容压栈到①的单元中LDMFDSP!,{R0,PC};⑥弹栈回复R0,PC指向$HandleLabelMEND该宏主要实现由异常向量地址处的程序计数器PC值(HandlerXxx)向内存储器中的定义的异常模式程序入口地址(HandleXxx的内容)处跳转。即从固定的异常向量指针处跳转到内存储器中程序可控的入口地址处。对于普通中断调用执行时的语法是:HandlerIRQHANDLERHandleIRQ实现PC指针从HandlerIRQ跳转到HandleIRQ的功能。193.MEXIT

伪指令

MEXIT伪指令用于从宏定义中跳转出去。伪指令格式如下:MEXIT

若需要从MACRO和MEND定义的宏体中跳出来时可以使用该伪指令。4.WHILE和WEND伪指令

WHILE用于标识条件循环伪指令的开始,WEND用于标识循环体的结束。伪指令格式如下:

WHILE逻辑表达式

指令序列WEND如果条件为真,几乎重复汇编相同的或几乎相同的一段程序代码。该伪指令可以嵌套使用。204.1.5汇编程序中常用伪指令伪指令名称功能描述AREA定义代码段或数据段CODE16、CODE32告诉汇编器其后是Thumb程序、ARM程序ENTRY指定程序的入口点ALIGN指定程序或数据的对齐方式,如半字对齐,字对齐等END汇编程序结束EXPORT/GLOBAL在本汇编程序中声明标号可以在其他源文件使用IMPORT/EXTURN在其他文件中用上一栏方法声明,在本文件中声明使用GET/INCLUDE将一个其他源文件包含到本文件中使用INCBIN将一个二进制的文件包含在本文件中使用RN给特定的寄存器重新命名ROUT标记局部标号使用范围的界限KEEP保留符号表中的局部符号表4-1常用汇编程序伪指令表211.AREA伪指令

用于定义一个代码段或数据段,它是每个汇编程序不可缺少的部分。语法如下:

AREA段名,属性1,属性2,……

其中,段名若以数字开头,则该段名需用“|”括起来,如|1_test|。属性字段表示该代码段(或数据段)的相关属性,多个属性用逗号分隔。属性段如表4-2所示。属性伪指令功能描述CODE声明定义的是一个代码段DATA声明定义的是一个数据段READONLY指定本段为只读,是代码段的默认属性READWRITE指定本段为可读可写,是数据段的默认属性ALIGN={exp}指定段的对齐方式为2exp,exp取值在0~31。默认为字对齐COMMON指定一个通用段,该段不包含任何用户代码和数据NOINIT指定此数据段仅保留内存单元,而没有将初值写入到内存单元应用示例:

AREAStart,CODE,READONLY

这里定义一个代码段,段名:Start,属性为只读。注意:一个大的汇编程序可以包含多个代码段和数据段,一个汇编程序至少包含一个代码段。222.CODE16和CODE32伪指令CODE16伪指令的功能是告诉汇编器后面的指令序列为16位的Thumb指令。CODE32伪指令的功能是告诉汇编器后面的指令序列为32位的ARM指令。注意:CODE16和CODE32只告诉其后指令的类型,该伪操作本身不进行ARM工作状态的切换。3.ENTRY伪指令

该指令指定程序的入口点。伪指令格式是:ENTRY应用示例:AREAStartUp,CODE,READONLY;指定一个代码段StartUp,属性为只读ENTRY;指定代码段程序入口点CODE32;指定其后为32位ARM指令LDRR0,=start+1;使用ARM伪指令LDR给R0赋值,并使R0[0]=1BXR0;R0[0]=1将跳转到Thumb指令程序处执行

…CODE16;指定其后为16位Thumb指令程序startMOVR1,#20

…END;汇编结束注意:一个程序(可以包含多个源文件)中至少要有一个ENTRY,但一个源文件中最多只有一个ENTRY(可以没有ENTRY)。234.END伪指令END伪指令告诉汇编器源文件已经结束,也就是说其后的指令将不起作用,一个源文件只能使用一次END伪指令。伪指令格式如下:END5.EXPORT/GLOBAL伪指令

该两条伪指令用于在程序中声明一个全局标号,可以在其它文件中使用。该伪指令语法格式如下:

EXPORT标号{[WEAK]}GLOBAL标号{[WEAK]}其中:[WEAK]选项声明其它的同标号优先于该标号被应用。EXPORT/GLOBAL同功能。应用示例:AREAExample,CODE,READONLYEXPORTDoAddDoAddADDR0,R1,R2246.IMPORT伪指令IMPORT伪指令是用于通知编译器声明的标号是在其它汇编语言文件中已经声明,在本文件中使用的标号。而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中。伪指令语法格式如下:IMPORT标号{[WEAK]}

其中:[WEAK]

选项声明在其它文件中没有定义时,编译器也不给出错误信息。在多数情况下将该标号置为0,若该标号为B或BL指令引用,则将B或BL指令置为NOP操作。7.EXTERN伪指令EXTERN伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用。如果当前源文件实际并未引用该标号,该标号就不会被加入到当前源文件的符号表中。伪指令语法格式如下:

EXTERN标号{[WEAK]}标号在程序中区分大小写,[WEAK]选项表示当所有的源文件都没有定义这样一个标号时,编译器也不给出错误信息。在多数情况下将该标号置为0,若该标号为B或BL指令引用,则将B或BL指令置为NOP操作。258.GET/INCLUDE伪指令

这两条伪指令是将一个源文件包含到当前的源文件中,并将包含的文件在当前文件中进行汇编处理。指令格式如下:

GET文件名或

INCLUDE文件名应用举例:GETd:\arm\filename.s;文件名使用全文件名,即包含文件的路径9.INCBIN伪指令INCBIN伪指令是将一个已经编译好的二进制文件包含到当前源程序中,汇编器不再进行汇编处理。指令格式如下:

INCBIN文件名应用示例:INCBINd:\arm\binfile.bin注意:与GET/INCLUDE伪指令的区别。INCBIN伪指令是将一个已经编译好的二进制文件或数据文件包含到当前源文件中使用;而GET/INCLUDE伪指令是将一个汇编源文件(未编译的)包含到当前文件中使用。2610.RN伪指令RN伪指令是给一个通用寄存器重新命名一个别名,在特定环境下使用。指令的格式如下:

别名RN通用寄存器应用示例:COUNTRNR6;定义R6为计数器COUNT,定义后直接使用COUNT11.KEEP伪指令KEEP伪指令指示编译器保留符号表中的局部符号。伪指令的格式如下:KEEP{symbol}其中:symbol是要保留的局部符号;如果没有此项,则除了基于寄存器的所有符号都将保含在目标文件的符号表中。12.ROUTROUT伪指令用于定义局部标号的有效范围。伪指令的格式如下:{name}ROUT其中:name是定义作用范围的名称。当没有使用ROUT伪指令时,局部标号的作用范围为其所在段。ROUT的作用范围是在本ROUT伪指令和下一条ROUT伪指令之间(指同一段中的伪指令)。274.1.6汇编语言中的运算符与表达式在汇编语言设计中,也经常使用各种表达式。表达式一般由变量、常量、运算符和括号组成。常用的表达式有算术表达式、逻辑表达式、关系表达式和字符串表达式,其运算遵循以下优先级:

括号运算符的优先级最高。一般在搞不清优先级的情况下,可以增加括号来提高表达式的优先级别;

单目运算符的优先级高于其他运算符;

优先级相同的双目运算符运算顺序是从左到右;

优先级相同的单目运算符运算顺序是从右到左。281.数字运算符与表达式数字表达式一般由算术常量、算术变量、算术运算符和括号组成。与数字运算符和表达式有关的内容如下:⑴算术运算符与表达式主要有:“+”、“-”、“×”、“/”、“MOD”运算符。以下是由X和Y以及运算符所构成的算术表达式和含义:X+Y表示X与Y之和;X-Y表示X与Y之差;X×Y表示X与Y之乘积;X/Y表示X除以Y之商;X:MOD:Y表示X除以Y的余数。⑵移位运算符与表达式主要有:“ROL”、“ROR”、“SHL”、“SHR”运算符。以下是由X和Y以及其运算符所构成的移位表达式和含义:X:ROL

:Y表示X循环左移Y位;

X

:ROR

:Y表示X循环右移Y位;X:SHL

:Y表示X左移Y位;

X:SHR

:Y表示X右移Y位。292.逻辑运算符与表达式逻辑表达式一般由逻辑常量、逻辑变量、逻辑运算符和括号组成。与逻辑运算符和表达式有关的内容如下:⑴位逻辑运算符与表达式主要有:“AND”、“OR”、“NOT”、“EOR”运算符。以下是由X和Y以及其运算符所构成的位逻辑运算表达式和含义:X:AND

:Y表示将X与Y按位进行逻辑“与”运算;X

:OR

:Y表示将X与Y按位进行逻辑“或”运算;:NOT

:Y表示将Y按位进行逻辑“非”运算;X:EOR

:Y表示将X与Y按位进行逻辑“异或”运算。⑵逻辑运算符与表达式主要有:“LAND”、“LOR”、“LNOT”、“LEOR”运算符。以下是由X和Y以及其运算符所构成的逻辑运算表达式和含义:X:LAND

:Y表示将X与Y进行逻辑“与”运算;X

:LOR

:Y表示将X与Y进行逻辑“或”运算;:LNOT

:Y表示将Y进行逻辑“非”运算;X:LEOR

:Y表示将X与Y进行逻辑“异或”运算。303.关系运算符与表达式关系表达式一般由常量、变量、关系运算符和括号组成。如果比较的两个变量关系满足,则输出为“真”,否则,输出为“假”。关系运算符主要有:“=”、“>”、“<”、“>=”“<=”、“/=”、“<>”,以下是由变量X、Y所构成的关系表达式及含义:X=Y表示X等于Y;X

>Y表示X大于Y;X<Y表示X小于Y;X>=Y表示X大于等于Y;X<=Y表示X小于等于Y;

X/=Y表示X不等于Y;X<>Y表示X不等于Y;314.字符串运算符与表达式字符串表达式一般由字符串常量、字符串变量、字符串运算符和括号组成。编译器所支持的最大字符串长度是512字节。注意:下面的表达式也可以是变量或常量。⑴LEN运算符LEN运算符返回的是字符串变量中的字符串长度,语法格式是:

:LEN:字符表达式⑵CHR运算符CHR运算符是将0~255中的整数转换为一个字符,语法格式是:

:CHR:整数表达式⑶STR运算符STR运算符是将一个数字表达式或一个逻辑表达式转换成一个字符串。对于数字表达式将其转换为一个由16进制数组成的字符串;对于一个逻辑表达式将其转换成字符T或F。语法格式是::STR:表达式324.字符串运算符与表达式(续)⑷LEFT运算符LEFT运算符返回的是左端字符串X,从左边取出Y个字符组成的子串。语法格式是:X:LEFT:Y

其中X是源字符串;Y是一个整数,表示从左侧取出字符的个数。⑸RIGHT运算符RIGHT运算符返回的是左端字符串X,从右边取出Y个字符组成的子串。语法格式是:X:RIGHT:Y

其中X是源字符串;Y是一个整数,表示从右侧取出字符的个数。⑹CC运算符CC运算符是将其左端和右端的2个字符连接为一个长的字符串,X在前,Y在后。其语法格式是:X:CC:Y

其中X、Y均为字符串表达式。335.其他运算符和表达式⑴?运算符

?运算符返回代码行所生成的可执行代码的字节长度。

语法格式如下:?行标号⑵DEF运算符

DEF运算符判断是否定义了其后的符号。

语法格式如下::DEF

:X如果符号X已经定义,其值为真,否则为假。注意:目前在集成开发环境ADS1.2中,也支持对C语言环境下的部分操作符和表达式的应用,或者说与GNU开发环境下使用基本相同的操作符与表达式。344.1.7Linux操作系统中GNU开发环境下的伪指令在上面介绍的伪指令都是在Windows操作系统中,ADS集成开发环境里使用的伪指令,它们也被称为标准的ARM伪指令。在Linux操作系统中,GNU开发环境下的伪指令与ADS中具有相同的作用,但是它们的书写格式是有区别的。目前在ARM的产品开发中,引入Linux操作系统的产品较多,因此本节以比较的方法介绍GNU环境下的伪指令,加快读者对其伪指令的理解和掌握。351.内存分配伪指令

GNU开发环境下有更多的内存分配伪指令,下表列出了最常用到的大部分伪指令,要知道更多的内容请参见有关GNU环境下的伪指令。ADS伪指令GNU伪指令GNU伪指令语法格式功能简述EQU/SETX.equ/.set.equsymbol,expr定义symbol的值是exprDCB.byte/.ascii.byteexpr{,expr}…定义字节数据单元DCW.hword/.short.shortexpr{,expr}…定义半字数据单元DCD.word/.long/.int.longexpr{,expr}…定义字数据单元DCQ.quad.quadexpr{,expr}…定义8字节数据单元DCFS.float/.single.floatexpr{,expr}…定义单精度数单元DCFD.double.doubleexpr{,expr}…定义双精度数单元SPACE.zero.zerosize分配size字节单元,初始化为0LTORG.ltorg/.pool.ltorg声明缓冲池SPACE.space/.skip.spacesize{,value}与zero基本相同,填充值是value注意:表里的SETX代表ADS下的SETA、SETL、SETS伪指令。362.汇编控制伪指令汇编控制伪指令主要有条件编译控制伪指令和宏定义伪指令。与ADS环境下的伪指令区别请仔细观察注释字段。⑴条件编译控制伪指令.ifGNU环境下的.if伪指令也是根据表达式值的真与假,决定是否要编译它下面的代码,用.endif伪指令作为条件控制伪指令的结束,中间可以使用.else伪指令进行分支选择。它的指令格式丰富,形式是多种多样的。以下进行简要介绍:.ifdefsymbol/*与ADS伪指令IF:DEF:symbol功能相同*/

指令序列1

@如果symbol以定义,则汇编指令序列1.else

/*与ADS伪指令ELSE功能相同*/

指令序列2.endif

/*与ADS伪指令ENDIF功能相同*/注意:在GNU环境下的汇编语句注释使用符号“@”开头到本行结束,或与C语言使用相同的注释符:

/*语句说明*/。37⑵.macro、.exitm与.endm伪指令宏定义体的作用在前面已经讲述,以下是在GNU环境下的宏体定义语法格式。.macro/*与ADS伪指令MACRO功能相同*/\标号:宏体名\参数1,\参数2…

指令序列1

.exitm@需要跳出宏体时使用这条伪指令。对应ADS下的伪指令MEXIT

指令序列2.endm/*与ADS伪指令MEND功能相同*/

关于宏的调用与前述相同。在ADS集成开发环境中使用符号“$”表示宏变量,在GNU环境下使用符号“\”表示宏变量。383.汇编程序控制伪指令

GNU环境下的汇编程序控制伪指令同样也很丰富,以下也是通过比较的方法将它们列于下表中。ADS伪指令GNU伪指令语法格式功能简述包含在段定义中.section.sectionsection_name定义域中包含的段无.type.typesymbol,description指定符号类型(对象或函数)定义码段的字串.text.text{subsection}声明一个代码段定义数据段的字串.data.data{subsection}声明一个初始化数据段无.bss.bss{subsection}声明一个未初始化数据段CODE16.code16/.thumb.code16/.thumb声明以下代码是Thumb指令CODE32.code32/.arm.code32/.arm声明以下代码是ARM指令ALIGN.align/.balign.align{expt}声明对齐方式INCLUDE.include.includefilename在当前文件中引用filenameEXPORT.global/.globl.globalsymbol声明标号,在其它文件中使用IMPORT.extern.externsymbol声明外部标号在当前文件使用END.end.end汇编结束RN.reg.regalias,Rn定义通用寄存器别名有特用394.2ARM汇编语言程序设计程序是由指令组成的,要编写好程序其一就是要掌握指令的功能与使用方法,其二就是要熟悉汇编语言的程序结构等。在此基础上,熟悉程序实现中使用的算法、控制过程与步骤,复杂时画好程序的流程图,然后进行程序的编写。评价一个程序的好坏,就是在实现预先规定的运算或控制过程功能的前提下,一是程序占用的存储空间最小,即使用的指令条数最少;二是代码的运行速度最快。但这两者又是很难同时满足,往往取一个折中的实现方案。本节主要介绍与之相关的主要内容。404.2.1ARM汇编中的源文件类型

在ARM的程序设计中,必不可少的部分使用汇编语言编写,大部分程序使用C/C++语言开发,常用的源文件如下表所列。源程序文件文件类型后缀说明汇编程序文件*.s或*.S用汇编语言编写的ARM程序或Thumb程序汇编头文件*.a或*.A使用伪指令定义的端口地址、内存分配等内容C程序文件*.c或*.C使用C语言编写的程序代码C程序头文件*.h或*.H使用预处理命令定义的常量、宏体,函数的声明等414.2.2ARM汇编语言的语句格式1.基本语句格式ARM(Thumb)汇编语言的语句格式为:{标号} {指令或伪指令} {;注释}规则:如果一条语句太长,可将其分为若干行来书写,在行的尾部用续行符“\”来标识下一行与本行为同一条语句。

每一条指令助记符可以全部用大写、或全部用小写,但不能在一条指令中大、小写混用。注意:在使用ADS1.2编辑ARM程序时,书写标号一定要顶格,在指令前如果没有标号时指令一定要退格书写。由于ADS环境下标号后没有冒号“:”,ADS是通过顶格来区分标号和ARM指令的。422.汇编语言程序中常用的符号

在汇编语言程序设计中,可以使用各种符号代替地址、变量和常量等,以增加程序的可读性。以下为符号命名的约定:

符号名不应与指令或伪指令同名。

符号在其作用范围内必须唯一。

符号区分大小写,同名的大、小写符号被视为两个不同的符号。

自定义的符号名不能与系统保留字相同。3.程序中的常量

程序中的常量是指其值在程序的运行过程中不能被改变的量。ARM(Thumb)汇编程序所支持的常量有逻辑常量、数字常量和字符串常量。

数字常量一般为32位的整数,无符号常量取值范围为0~232-1,有符号常量取值范围为-231~231-1。

逻辑常量只有两种取值:真(True)或假(False)。

字符串常量为一个固定的字符串,一般用来提示程序运行时的信息。435.程序中的变量代换

程序中的变量可通过代换操作取得一个常量。代换操作符为“$”。如果“$”在数字变量前面,编译器会将该数字变量的值转换为十六进制的字符串,并将该十六进制的字符串代换“$”后的数字变量。ARM中的常量使用EQU定义,ARM中使用GBLA、GBLL、GBLS定义全局变量,使用LCLA、LCLL、LCLS定义局部变量,使用SETA、SETL、SETS为它们赋值。例如:

GBLSSTR1

GBLSSTR2

STR1

SETS

”pen”

STR2SETS“Thisisa$STR1"编译后的结果是STR2的值为Thisisapen.444.2.3ARM汇编语言的程序结构ARM汇编语言程序是以段(Sectoin)为单位组织源文件的。段是相对独立的、具有特定名字的、不可分割的指令序列或数字序列。段可以分为代码段和数据段,代码段的内容为执行代码,数据段存放代码段运行时需要用到的数据。一个汇编程序至少应该有一个代码段,也可以分割为多个代码段和数据段,多个段在程序编译链接时最终形成一个可执行的映象文件(*.elf)。可执行映象文件通常由以下几部分构成:

一个或多个代码段,代码段的属性为只读。

零个或多个包含初始化数据的数据段,数据段的属性为可读写。

零个或多个不包含初始化数据的数据段,数据段的属性为可读写。汇编连接器根据系统默认或用户设定的规则,将各个段安排在存储器中的相对位置,因此源程序中段之间的相对位置与可执行的映像文件中段的相对位置一般不会相同。ARM汇编程序结构分为基于Windows环境下ADS汇编语言程序设计结构和Linux环境下GNU汇编语言程序设计结构,以下分别介绍。451.基于Windows环境下ADS的汇编语言程序结构

ADS环境下的ARM汇编语言程序结构与GNU环境下的汇编语言程序结构大体相同,整个程序也是以段为单元来组织代码。其语法规则总结如下:

所有标号必须在一行的顶格书写,其后不要添加“:”号。

所有的指令均不能顶格写。

大小写敏感(可以全部大写或全部小写,但不能大小写混合使用)。

注释使用分号“;”。应用示例:AREAInit,CODE,READONLYENTRYstartLDRR0,=0x3FF5000;使用伪指令LDR将立即数0x3FF5000→R0

LDRR1,[R0]

;读R0地址单元内容→R1

LDRR0,=0x3FF5004;使用伪指令LDR将立即数0x3FF5004→R0

LDRR2,[R0]

ADDR3,R2,R1;R3←R2+R1

LDRR0,=0x3FF5008;使用伪指令LDR将立即数0x3FF5008→R0STRR3,[R0]

;R3的内容写入R0内容作为字地址的存储单元中

END

;标识汇编源程序结束,其后如果还有内容将会被忽略。在ADS环境中,使用伪指令AREA定义一个段,并说明定义段的属性。本例定义了一个段名为Init的代码段CODE,属性为只读READONLY;ENTRY伪指令标识程序的入口点;start为程序标号,必须顶格书写;462.基于Linux环境下GNU的汇编语言程序结构Linux环境下GNU的汇编语言规则总结如下:

所有标号必须在一行的顶格书写,并且其后必须添加“:”号;

所有的指令均不能顶格写;

大小写敏感(可以全部大写或全部小写,但不能大小写混合使用);

注释使用符号“@”或使用C语言的/*注释内容*/注释符。(注释的内容由“@”号起到此行结束,注释可以在一行的顶格书写)。应用示例:.text@表示为只读代码段_start:.globalstart@_start作为连接器使用。在GNU环境下,必须在这使用此标号.globalmain@声明全局标号main,在其它程序中调用,在C中就是调用的main函数

bmain@跳转到main函数main:

movr0,#0/*使用mov指令将立即数0送r0*/

ldrr1,=0x01/*使用ldr伪指令将立即数0x01送r1*/addop:addr2,r1,r0@r2=r1+r0

movpc,lr@返回pc处

.end/*.end汇编结束伪指令*/473.ADS与GNU环境下程序代码的比较与移植

在ADS环境下的汇编代码与GNU环境下的汇编代码有较多的不同点,主要是符号与伪指令的不同,ARM指令的格式是基本相同的。掌握了它们之间的不同点,将会更好地进行它们之间的代码移植。下表列出了在两个环境下常用的伪指令的对应关系。ADS伪指令GNU伪指令ADS伪指令GNU伪指令INCLUDE.includeALIGN.alignTCLK2EQU30.equTCLK2,30MACRO.macroEXPORT.globalMEND.endmIMPORT.externEND.endDCD.long/.intAREAx,CODE,READONLY.textIF:DEF:.ifdefAREAy,DATA,READWRITE.dataELSE.elseCODE32.armENDIF.endifCODE16.thumb:AND:<ORG.ltorg:SHL:<<SPACE.zeroRN.regENTRY.entryGLBA.globalldrpc,[pc,#&10]ldrpc,[pc,#0x10]BUSWIDTHSETA16.equBUSWIDTH,16ldrpc,[pc,#&-10]ldrpc,[pc,#-0x10]483.ADS与GNU环境下程序代码的比较与移植(续)注意:ADS环境下的操作符与表达式在4.1.6节已经介绍;GNU环境下的操作符有:“~”表示取数的补码,“-”表示取负,只有“<>”表示不等于,其它的符号如+、-、*、/、%、<、<<、>、>>、|、&、^、!、==、>=、<=、&&、||,与C语言中的用法相似。实际上,目前GNU环境下的许多操作符和表达式在ADS环境下也可以使用,以后将会看到。494.3ARM汇编语言与C语言混合编程在ARM的应用系统中,汇编语言编程主要用于系统的启动与初始化,之后通过调用C语言main()函数后基本进入C语言的编程模式,关键涉及到的是它们在各自环境中定义的标号(在C语言中可以是一个函数)、变量、函数之间的相互使用问题。汇编语言和C/C++的混合编程通常有以下几种方式:①在汇编语言程序中调用C程序。②在C语言程序中调用汇编程序。③在C语言程序中内嵌汇编语句。④在汇编语言程序中访问C语言程序变量。504.3.1基本的ATPCS基本的ATPCS(ARMThumbProcedureCallStandard)规定了在混合编程时子程序调用的一些基本规则,主要有:寄存器的使用;堆栈的使用;参数传递;子程序结果的返回等方面的规则。511.寄存器的使用规则①程序通过寄存器R0~R3来传递参数,此时这些寄存器可以记作A0~A3,被调用的子程序在返回前无需恢复寄存器R0~R3的内容。②在子程序中使用R4~R11来保存局部变量,此时这些寄存器可以记作V1~V8。③寄存器R12用作子程序间scratch寄存器,记作ip,在子程序的连接代码段中经常会有这种使用规则。Scratch寄存器用于保存SP寄存器,在函数返回时使用该寄存器出栈。④寄存器R13用作数据栈指针,记做SP,在子程序中寄存器R13不能用做其他用途。⑤寄存器R14用作连接寄存器,记作LR,它用于保存子程序的返回地址。⑥寄存器R15是程序计数器,记作PC,它不能用作其他用途。⑦ATPCS中的各寄存器在ARM编译器和汇编器中都是预定义的。522.堆栈的使用规则堆对栈指针通常可以指向不同的位置,如果当前堆栈指针指定的单元中存在有效数据时,称为FULL栈(满栈)。如果当前堆栈指针指定的单元中不存在有效数据时,称为Empty栈(空栈)。堆栈的增长方向也可以不同,当堆栈向内存减小的地址方向增长时,称为Descending栈(递减栈);反之称为Ascending栈(递增栈)。注意:ATPCS规定堆栈使用FD(满栈递减型)类型,并要求堆栈的操作是8字节对齐的。也就是说在应用系统的程序设计中,均使用的是该种类型的堆栈。533.参数的传递规则根据参数个数是否固定,可以将子程序分为参数个数固定的子程序和参数个数可变的子程序,这两种子程序的参数传递规则是不同的。

第一,参数个数可变的子程序参数传递规则。对于参数个数可变的子程序,当参数不超过4个时,可以使用寄存器R0~R3来传递参数;当参数个数超过4个时,可以使用堆栈来传递参数。

第二,参数个数固定的子程序参数传递规则。对于参数个数固定的子程序,参数传递与参数个数可变的子程序参数传递规则不同。如果系统包含浮点运算的硬件部件,浮点参数将按照下面的规则传递:各个浮点参数按顺序处理;为每个浮点参数分配FP寄存器;分配的方法是,满足该浮点参数需要的且编号最小的一组连续的FP寄存器。第1个整数参数通过寄存器R0~R3来传递,其他参数通过堆栈来传递.544.子程序结果返回规则结果为一个32位的整数时,可通过寄存器R0返回。结果为一个64位整数时,可以通R0和R1返回,依此类推。结果为一个浮点数时,可以通过浮点运算部件的寄存器f0,d0或者s0来返回。结果为一个复合的浮点数时,可以通过寄存器f0-fN或者d0~dN来返回。对于位数更多的结果,则需要通过调用内存来传递。554.3.2汇编语言程序调用C语言程序在汇编语言中调用C语言,需要使用IMPORT对C函数进行说明;然后将C的代码放在一个独立的C文件中进行编译,剩下的工作由连接器完成。应用示例:该项目由1个汇编语言文件和1个C语言文件组成,以下是它们的代码内容。1.汇编语言文件:

AREAasmfile,CODE,READONLY

IMPORTcFun;声明在C语言中定义的函数

ENTRY

;汇编程序入口

MOVR0,#0;传递函数cFun第1参数

MOVR1,#1;传递函数cFun第2参数

MOVR2,#2;传递函数cFun第3参数

blcFun;调用C语言函数

MOVR3,R0;将调用C语言函数的结果保存在R3中

END

;汇编结束伪指令562.C语言文件/*Cfile,calledbyasmfile*/intcFun(inta,intb,intc)/*C语言函数定义,形参分别对应R0、R1、R2寄存器*/{returna+b+c;}注意:在汇编语言程序中声明C语言函数时只使用函数名,C语言中的函数名是一个常量地址;由于C语言中函数使用的形参是整型变量,所以在汇编语言传递参数时使用的是寄存器,即对应于寄存器寻址;这里的参数传递是利用寄存器R0~R2。需要指出的是当函数的参数个数大于4时就要借助于堆栈。57

4.3.3C语言程序中调用汇编语言程序在汇编程序中使用EXPORT伪指令声明程序(是一个标号),使得本程序可以被其他的程序调用;在C语言中使用EXTERN关键词声明该汇编程序,这样就可以在C中使用该函数了。从C的角度,并不知道该函数的实现是用C语言还是汇编语言。应用示例:该项目由1个C语言文件和1个汇编语言文件组成,以下是它们的代码内容。581.C语言文件#include<stdio.h>

externvoidasm_strcpy(constchar*src,char*dest);/*将汇编语言中的子程序声明为C语言函数*/intmain(){constchar*s=“hello,world!”;chard[32];asm_strcpy(s,d);/*调用声明的函数*/printf(“source,%s”,s);printf(“destination:%d”,d);return0;}2.汇编语言文件AREAasmfile,CODE,READONLY;定义一个名为asmfile的代码段EXPORTasm_strcpy;声明汇编程序标号,在C语言中可以调用并且将作为一个函数名asm_strcpy;汇编程序入口标号,C语言中的程序名loopldrbr4,[r0],#1;r0在C函数中作为第1个参数。r4←[r0],r0←r0+1cmpr4,#0beqover;如果是字符串结尾,eq=1,程序跳转到标号over处strbr4,[r1],#1;r1在C函数中作为第2个参数。[r0]←r4,r1←r1+1bloopovermovpc,lr;pc←lr,子程序返回END594.3.4C语言程序中内嵌汇编语言程序在C程序中内嵌的汇编指令支持大部分的ARM和Thumb指令,不过其使用与汇编文件中的指令有些不同,存在一些限制,主要有下面几个方面:①不能直接向PC赋值,程序跳转要使用B或者BL指令;②在使用物理寄存器时,不要使用过于复杂的C表达式,避免物理寄存器冲突;③R12和R13可能被编译器用来存放中间编译结果;④一般不要直接指定物理寄存器,而让编译器进行分配;601.内嵌汇编语言标记内嵌汇编语言标记是__asm或者asm关键字,用法如下:__asm{

指令1[;注释]……

指令n}

或:在关键字asm后使用圆括号“()”将关键字为__asm中的花括号“{}”代换也可。2.应用示例#include<stdio.h>voidmy_strcpy(constchar*src,char*dest)/*定义字符串拷贝函数*/{charch;/*定义字符串变量,在汇编中有系统指定寄存器替换*/__asm/*使用内嵌汇编关键字*/{loopldrbch,[src],#1;src寄存器间接寻址,编译时系统指定R0。ch←[src],src←src+1

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论