嵌入式技术基础与实践(第5版)
上QQ阅读APP看书,第一时间看更新

2.4 汇编语言的基本语法

能够在MCU内直接执行的指令序列是机器语言,用助记符号来表示机器指令便于记忆,这就形成了汇编语言。因此,用汇编语言写成的程序不能直接放入MCU的程序存储器中去执行,必须先转为机器语言。把用汇编语言写成的源程序“翻译”成机器语言的工具称为汇编程序或汇编器(Assembler),以下统一称为汇编器。

本书给出的所有样例程序均是在CCS6.2开发环境下实现的,CCS6.2环境在汇编编程时推荐使用GNU v4.9.3汇编器,汇编语言格式满足GNU汇编语法,以下简称ARM-GUN汇编。为了有助于解释涉及的汇编指令,下面将介绍一些汇编语法的基本信息参见《GNU汇编语法》。

2.4.1 汇编语言格式

汇编语言源程序可以用通用的文本编辑软件编辑,以ASCII码形式存盘。具体的编译器对汇编语言源程序的格式有一定的要求,同时,编译器除了识别MCU的指令系统外,为了能够正确地产生目标代码及方便汇编语言的编写,编译器还提供了一些在汇编时使用的命令、操作符号,在编写汇编程序时,也必须正确使用它们。由于编译器提供的指令仅是为了更好地做好“翻译”工作,并不产生具体的机器指令,因此这些指令被称为伪指令(Pseudo Instruction)。例如,伪指令告诉编译器:从哪里开始编译、到何处结束、汇编后的程序如何放置等相关信息。当然,这些相关信息必须包含在汇编源程序中,否则编译器就难以编译好源程序,难以生成正确的目标代码。

汇编语言源程序以行为单位进行设计,每一行最多可以包含以下4个部分。

    标号:操作码操作数注释

1. 标号

标号(labels)可以确定代码当前位置的程序计数器(PC)值。对于标号有下列要求及说明。

(1)如果一个语句有标号,则标号必须书写在汇编语句的开头部分。

(2)可以组成标号的字符有:字母A~Z、字母a~z、数字0~9、下画线“_”、美元符号“$”,但开头的第一个符号不能为数字和$。

(3)编译器对标号中字母的大小写敏感,但指令不区分大小写。

(4)标号长度基本上不受限制,但实际使用时通常不要超过20个字符。若希望更多的编译器能够识别,建议标号(或变量名)的长度小于8个字符。

(5)标号后必须带冒号“:”。

(6)一个标号在一个文件(程序)中只能定义一次,否则重复定义,不能通过编译。

(7)一行语句只能有一个标号,编译器将把当前程序计数器的值赋给该标号。

2. 操作码

操作码(opcodes)包括指令码和伪指令,其中伪指令是指CCS开发环境ARM Cortex-M4F汇编编译器可以识别的伪指令。对于有标号的行,必须用至少一个空格或制表符(TAB)将标号与操作码隔开。对于没有标号的行,不能从第一列开始写指令码,应以空格或制表符(TAB)开头。编译器不区分操作码中字母的大小写。

3. 操作数

操作数(operands)可以是地址、标号或指令码定义的常数,也可以是由伪运算符构成的表达式。若一条指令或伪指令有操作数,则操作数与操作码之间必须用空格隔开书写。操作数多于一个的,操作数之间用逗号“,”分隔。操作数也可以是ARM Cortex-M4F内部寄存器,或者另一条指令的特定参数。操作数中一般都有一个存放结果的寄存器,这个寄存器在操作数的最前面。

1)常数标识

编译器识别的常数有十进制(默认不需要前缀标识)、十六进制(0x前缀标识)、二进制(用0b前缀标识)。

2)“#”表示立即数

一个常数前添加“#”表示一个立即数,不添加“#”时,表示一个地址。

特别说明:初学者常常会将立即数前的“#”遗漏,如果该操作数只能是立即数时,编译器会提示错误,例如:

    mov r3,1  //给寄存器r3赋值为1(这个语句不对)

编译时会提示“immediate expression requires a#prefix--'mov r3,1'”,应该改为:

    mov r3,#1  //给寄存器r3赋值为1(这个语句对)

3)圆点“.”

若圆点“.”单独出现在语句的操作码之后的操作数位置上,则代表当前程序计数器的值被放置在圆点的位置。例如,b.指令代表转向本身,相当于永久循环,在调试时希望程序停留在某个地方可以添加这种语句,调试之后应删除。

4)伪运算符

表2-22所示为CCS ARM Cortex-M4F编译器识别的伪运算符。

表2-22 CCS ARM Cortex-M4F编译器识别的伪运算符

4. 注释

注释(comments)即说明文字,类似于C语言,多行注释以“/*”开始,以“*/”结束。这种注释可以包含多行,也可以独占一行。在CCS环境的ARM Cortex-M4F处理器汇编语言中,单行注释以“#”引导或用“//”引导。用“#”引导时,“#”必须为单行的第一个字符。

2.4.2 常用伪指令简介

不同集成开发环境下的伪指令稍有不同,伪指令书写格式与所使用的开发环境有关

常用的伪指令主要有用于常量及宏的定义伪指令、条件判断伪指令、文件包含伪指令等。在CCS6.2.0开发环境下,所有的汇编命令都是以“.”开头。这里以本书使用的开发环境(CCS)为例介绍有关汇编伪指令。

1. 系统预定义的段

C语言程序经过gcc编译器最终生成.elf格式的可执行文件。.elf可执行程序是以段为单位来组织文件的。通常划分为3个段:.text、.data和.bss。其中,.text是只读的代码区,.data是可读可写的数据区,而.bss则是可读可写且没有初始化的数据区。.text段开始地址为0x0,接着分别是.data段和.bss段。

2. 常量的定义

汇编代码常用的功能之一为常量的定义。使用常量定义,能够提高程序代码的可读性,并且使代码维护更加简单。常量的定义可以使用.equ汇编指令,下面是GNU汇编器的一个常量定义的例子:

常量的定义还可以使用.set汇编指令,其语法结构与.equ相同。

3. 程序中插入常量

对于大多数汇编工具来说,一个典型特性为可以在程序中插入数据。GNU汇编器语法可以写为:

为了在程序中插入不同类型的常量,GNU汇编器中包含许多不同的伪指令,表2-23中列出了常用的例子。

表2-23 用于程序中插入不同类型常量的常用伪指令

4. 条件伪指令

.if条件伪指令后面紧跟着一个恒定的表达式(即该表达式的值为真),并且最后要以.endif结尾。中间如果有其他条件,可以用.else填写汇编语句。

.ifdef标号,表示如果标号被定义,执行下面的代码。

5. 文件包含伪指令

    .include"filename"

.include是一个附加文件的链接指示命令,利用它可以把另一个源文件插入当前的源文件一起汇编,成为一个完整的源程序。filename是一个文件名,可以包含文件的绝对路径或相对路径,但建议一个工程的相关文件放到同一个文件夹中,所以更多的时候使用相对路径。具体例子参见第3章的第一个汇编实例程序。

6. 其他常用伪指令

除了上述的伪指令外,GNU汇编还有其他常用伪指令。

(1).section伪指令。用户可以通过.section伪指令来自定义一个段。例如:

    .section .isr_vector, "a" @定义一个.isr_vector段,"a"表示允许段

(2).global伪指令。.global伪指令可以用来定义一个全局符号。例如:

    .global symbol  @定义一个全局符号symbol

(3).extern伪指令。.extern伪指令的语法为.extern symbol,声明symbol为外部函数,调用时可以遍历所有文件找到该函数并且使用它。例如:

    .extern main      @声明main为外部函数
    bl main           @进入main函数

(4).align伪指令。.align伪指令可以通过添加填充字节使当前位置满足一定的对齐方式。语法结构为.align[exp[,fill]],其中,exp为0~16之间的数字,表示下一条指令对齐至2exp位置,若未指定,则将当前位置对齐到下一个字节的位置,fill给出为对齐而填充的字节值,可省略,默认为0x00。例如:

    .align 3 @把当前位置计数器值增加到23的倍数上,若已是23的倍数,不做改变

(5).end伪指令。.end伪指令声明汇编文件的结束。

还有有限循环伪指令、宏定义和宏调用伪指令等,参见《GNU汇编语法》。