一、理解汇编语言用法
ARM汇编(适用于ARM realview工具链 DS-5 Keil微控制器开发套件)指令格式如下:
label
mnemonic operand1,operand2, … ;注释
label(标号)表示地址位置,是可选的。有些指令的前面可能会有标号,这样就可以通过这个标号得到指令的地址。标号也可以用于表示数据地址。例如,可以在程序内的查找表处放一个标号。mnemonic为助记符,也就是指令的名称,其后跟着的是多个操作数。
对于在ARM汇编器中编写的数据处理指令,第一个操作数为操作的目的。
对于存储器读指令(多加载指令除外),第一个操作数为数据被加载进去的寄存器
对于存储器写指令(多存储指令除外),第一个操作数为保存待写入寄存器的数据的寄存器。
处理多次加载和存储的指令具有不同的语法。
每条指令的操作数个数由指令的类型决定。
有些指令不需要任何操作,而有些则可能只需要一个。
处理多次加载和存储的指令具有不同的语法。
每条指令的操作数个数由指令类型决定。
有些指令不需要任何操作,而有些则可能只需要一个。
注意,助记符后可能会存在不同类型的操作数,这样可能会得到不同的指令编码。例如,MOV指令可以在两个寄存器间传输数据,也可以将立即数放到寄存器中。指令中操作数的个数取决于指令的种类,而操作数的语法也可能会各不相同。
对于GNU工具链,汇编语法一般为:
label
mnemonic operand1,operand2, /*注释 */
ARM汇编,定义常量的一个例子为:
NVIC_IRQ EQU 0xE00E100
NVIC_IRQ0_ENABLE EQU 0x1
GNU工具链的汇编语法:
.equ NVIC_IRQ, 0xE000E100
.equ NVIC_IRQ0_ENABLE, 0x1
多数汇编工具允许将数据插入程序中,这是另外一个典型特性。例如,可以在程序存储器中的特定位置定义数据,并用存储器读指令进行访问。
ARM 汇编器实现:
DCD用于插入字大小的数据,DCB则用于将字节大小的数据插入到程序中。ALGN后的数据决定了对齐的大小。
LDR R3, = MY_NUMBER ;获取MY_NUMBER的存储器位置
LDR R4, [R3] ;将数据值0x12345678读入R4
LDR R0, =HELLO_TEXT ;获取HELLO_TEXT的起始地址
BL PrintText
ALGN 4
MY_NUMBER DCD 0X12345678
HELLO_TEXT DCB"Hello\n", 0 ;以NULL结束的字符串
GNU工具链的汇编语法如下
LDR R3, = MY_NUMBER /*获取MY_NUMBER的存储器位置*/
LDR R4, [R3] /*将数据值0x12345678读入R4*/
LDR R0, =HELLO_TEXT /*获取HELLO_TEXT的起始地址*/
BL PrintText
.algn 4
MY_NUMBER:
.word 0x12345678
HELLO_TEXT:
.asciz"Hello\n" /*以NULL结尾的字符串*/
ARM汇编器与GNU汇编器中多个不同伪指令可将数据插入到程序中。如下表所示
插入的数据类型 | ARM汇编器(如keil MDK-ARM) | GNU汇编器 |
---|---|---|
字节 | DCB 如 DCB 0X12 | .byte 如.byte 0x12 |
半字 | DCW 如DCW 0X1234 | .hword/.2byte 如.hword 0xAA55, |
字 | DCD 如DCD 0X12345678 | .word/.4byte 如.word 0x12345678 |
双字 | DCQ 如DCQ 0X12345678ABCDEFAA | .quad/.octa 如 .quad 0x12345678ABCDEFAA |
浮点(单精度) | DCFS 如DCFS 1E3 | .float 如.float 1E3 |
浮点(半精度) | DCFD 如DCFD 3.14159 | .double 如.double 3.14159 |
字符串 | DCB 如DCB"Hello\n"0, | .ascii/.asciz(以NULL结束) 如.ascii “JNZ” @插入字节0x4A 0x4E 0x5A.asciz “JNZ” @插入字节0x4A 0x4E 0x5A 0x00 |
指令 | DCI 如DCI 0XBE00; | .word/.hword 如.hword 0XBE00 |
多数情况下,还可以在伪指令前加一个标号,以便利用这个标号确定数据的地址。
二、指令后缀的使用
对于ARM处理器的汇编器,有些指令后会跟着后缀。CORTEX-M处理器可用的后缀如下:
三、指令集
CORTEX-M3和CORTEX-M4处理器的指令可以按功能分为以下几类:
存储器内传送数据、存储器访问、算术运算、逻辑运算、移位和循环移位运算、转换(展开和反转顺序)运算、位域处理指令、
程序流控制(跳转、条件跳转、条件执行和函数调用)、乘累加(MAC)指令、除法指令、存储器屏障指令、异常相关指令、休眠模式相关指令、其他指令。另外,cortex-m4处理器支持增强DSP指令:
SIMD运算和打包指令、快速乘法和MAC指令、饱和运算、浮点指令(前提是浮点单元存在)
3.1处理器内传送数据
微处理器中最基本的操作为在处理器内来回传送数据。例如,可能会
①将数据从一个寄存器送到另外一个寄存器
②在寄存器和特殊寄存器间(如CONTROL PRIMASK FAULTMASK BASEPRI)传送数据
③将立即数送到寄存器
对于具有浮点单元的CORTEX-M4处理器,还可以:
①在内核寄存器组中的寄存器和浮点单元寄存器组中的寄存器间传送数据。
②在浮点寄存器组中的寄存器间传送数据
③在浮点系统寄存器和内核寄存器间传送数据
④将立即数送入浮点寄存器。
处理器内传送数据的指令
指令 | 目的 | 源 | 操作 |
---|---|---|---|
MOV | R4, | R0 | 从R0复制数据到R4 |
MOVS | R4, | R0 | 从R0复制数据到R4,且更新APSR(标志) |
MRS | R7, | PRIMASK | 将数据从PRIMASK复制到R7 |
MSR | CONTROL, | R2 | 将数据从R2复制到CONTROL |
MOV | R3, | #0X34 | 设置R3为0x34 |
MOVS | R3, | 0X34 | 设置R3为0x34,且更新APSR(标志) |
MOVW | R6, | #0X1234 | 设置R6为16位常量0x1234 |
MOVT | R6, | #0x8765 | 设置R6高16位为0x8765 |
MVN | R3, | R7 | 将R7取反后送至R3 |
浮点单元和内核寄存器间传送数据的指令
指令 | 目的 | 源 | 操作 |
---|---|---|---|
VMOV | R0, | S0 | 将数据从浮点寄存器S0复制到通用目的寄存器R0 |
VMOV | S0, | R0 | 将数据从通用目的寄存器R0复制到浮点寄存器S0 |
VMOC | S0, | S1 | 将数据从浮点寄存器S1复制到S0(单精度) |
VMRS.F32 | R0, | FPSCR | 将数据从浮点单元系统寄存器FPSCR复制到R0 |
VMRS | APSR_nzcv, | FPSCR | 复制FPSCR中的标志到APSR中的标志 |
VMSR | FPSCR, | R3 | 复制R3到浮点单元系统寄存器FPSCR |
VMOV.F32 | S0, | #1.0 | 将单精度数据送到浮点单元寄存器S0 |
要将寄存器设置为一个较大的立即数(9-16位),可以使用MOVW指令。根据所使用的汇编器工具,若立即数位于9~16位,MOV或MOVS可能会被自动转换为MOVW.
若需要将寄存器设置为32位立即数,可以使用多种方法。最常见的方法为利用一个名为LDR的伪指令。例如:
LDR R0, =0x12345678 ;将R0设置为0x12345678
这不是一个实际的指令,汇编器会将其转换为存储器传输指令及存储在程序映像中的常量
LDR R0, [PC, #offset]
…
DCD 0x12345678
LDR读取[PC+offset]位置的数据,并存入R0。注意,由于处理器的流水线结构,PC的值并非LDR指令的地址,不过汇编器会计算偏移,因此,不必担心。
若需要将寄存器设置为程序代码中位于一定范围内的地址,则可以使用ADR伪指令,它会被转化为一个单独的指令;或者使用ADRL伪指令,它可以提供更大的地址范围,不过会被转换为两条指令。例如:
ADR R0, DataTable
...
ALIGN
DataTable
DCD 0, 245, 132, ...
ADR指令会被转换为基于程序计数器数值的加法或减法运算。
另外一种生成32位立即数的方法为组合使用MOVW和MOVT指令。例如:
MOVW R0, #0X789A ;设置R0为0x00007