一条汇编指令唯一对应一条机器指令 RISC
指令是用来“运算”:运算符操作数
(由编译器厂商定的,keil环境下有keil的伪指令,GNU环境有gnu的伪指令)
keil环境下常用的伪指令:
data_a //顶格写的 标号,标号表示一个地址
DCD 5 //DCD X : 分配4bytes空间并且把X的值,填入此处
data_b
SPACE 40 //SPACE X : 在此处开辟 X 个字节的空间,内容不定。
data_c
DCD 2
DCD 3
SPACE 8*4
“段”:分区域
代码段 这个区域都是代码—ROM 0x08000000—
数据段 这个区域存放的是全局的数据
堆栈段 stack
…
定义段:AREA 段名, 段的属性1, 段的属性2, …(段名自己定义,规定的定义)
AREA mstack, DATA, READWRITE
段的属性1:
CODE 代码段
CODE32 thumb-2代码
DATA 数据段
NOINIT 不初始化
属性2:
READWRITE 只读可写
READONLY 只读
属性3:
ALIGN=3 8字节对齐(2的3次幂对齐)
Cortex-M4要求代码必须是 8字节对齐,否则编译不会通过;
PRESERVE8 指令,表示后续如果没有特殊说明,都是采用 8字节对齐
当有C语言和汇编并存时,C语言的编译后的指令也需要8字节对齐。
由编译器厂商写的,keil环境下有keil的宏指令,在keil中需要顶格
lable 指令 ;注释
lable:顶格写,标识这一行的地址
指令:ARM指令,伪指令
; 到行末,都是注释部分。
stack_size EQU 0x200 ;宏指令,定义了两个宏
vector_size EQU 0x400
;define stack ;名字为mystack,数据,可读可写
AREA mystack,DATA,READWRITE
stack_S ;顶格
SPACE stack_size ;设置空间大小
stack_end ;stack top--SP
;define vector table ;定义向量表,只读
AREA RESET,DATA,READONLY
vector_s
DCD stack_end ;the first must be stack top
DCD code_start ;this must be start code
SPACE vector_size
vector_end
;define code
AREA mycode,CODE,READONLY,ALIGN=3
PRESERVE8 ;8字节对齐
code_start
MOVS R0,#-2
MOVS R1,#2
CMP R1,R0
MOVGT R2,#2
MOVLT R3,#3
MOVLT R4,#4
MOVLT R5,#5
B .
END
所谓寻址方式就是处理器根据指令中给出的地址信息来寻找物理地址的方式。
也叫立即寻址,是一种特殊的寻址方式,操作数本身包含在指令中,只要取出指令也就取到了操作数。这个操作数叫做立即数,对应的寻址方式叫做立即寻址。
例如:
MOV R0,#64 ;R0 ← 64
ADD R0, R0, #1 ; R0 ← R0 + 1
SUB R0, R0, #0X3D ; R0 ← R0 – 0X3D
在ARM处理器中,立即数必须对应8位位图格式,即立即数是一个在16位或32位的寄存器中的8bit常数,经循环移动偶数位得到。合法的立即数必须能够找到得到它的那个常数,否则这个立即数就是非法的。
判断一个立即数是否合法可以用以下的办法:即对于这个立即数进行循左移或右移操作,看看经过移动偶数位后,是否可以得到一个不大于0XFF的立即数(即不超过8位的立即数),如果可以得到,这个立即数就是合法的,否则就是非法的。
寄存器寻址就是利用寄存器中的数值作为操作数,也称为寄存器直接寻址。
例如:
ADD R0,R1, R2 ;R0 ← R1 + R2
这种寻址方式是各类微处理器经常采用的一种方式,也是执行效率较高的寻址方式。
寄存器间接寻址就是把寄存器中的值作为地址,再通过这个地址去取得操作数,操作数本身存放在存储器中。
;R0 ←[R1],以寄存器R1的值作为操作数的地址,把取得操作数传送到R0中
LDR R0,[R1]
;R0 ←R1 + [R2],以寄存器R2的值作为操作数的地址,取得操作数后与R1相加,结果存入寄存器R0中。
ADD R0,R1,[R2]
这是ARM指令集特有的寻址方式,它是在寄存器寻址得到操作数后再进行移位操作,得到最终的操作数。
例如:
MOV R0,R2,LSL #3 ;R0 ← R2 * 8 ,R2的值左移3位,结果赋给R0。
MOV R0,R2,LSL R1 ;R2的值左移R1位,结果放入R0。
它将寄存器(该寄存器一般称作基址寄存器)中的值与指令中给出的地址偏移量相加,从而得到一个地址,通过这个地址取得操作数。
例如:
;R0 ←[R1 + 4],将R1的内容加上4形成操作数的地址,取得的操作数存入寄存器R0中。
LDR R0,[R1,#4]
;R0 ←[R1 + 4]、R1 ←R1 + 4,将R1的内容加上4形成操作数的地址,取得的操作数存入寄存器R0中,然后,R1的内容自增4个字节。其中!表示指令执行完毕把最后的数据地址写到R1。
LDR R0,[R1,#4]!
;R0 ←[R1 + R2],将寄存器R1的内容加上寄存器R2的内容形成操作数的地址,取得的操作数存入寄存器R0中。
LDR R0,[R1,R2]
;R0→[R1 -4],将R1中的数值减4作为地址,把R0中的数据存放到这个地址中。
STR R0, [R1,#-4]
LDR R0,[R1],#4 ;R0 ←[R1]、R1 ←R1+4
一次完成多个寄存器值的传送。
例如:
;R1←[R0],R2←[R0+4],R3←[R0+8],R4←[R0+12]
LDMIA R0,{R1,R2,R3,R4} ;LDM:Load Data from Memory to Register.
相对寻址是一种特殊的基址寻址,特殊性是它把程序计数器PC中的当前值作为基地址,语句中的地址标号作为偏移量,将两者相加之后得到操作数的地址。
例:
BL NEXT ;相对寻址,跳转到NEXT处执行。
堆栈是一种数据结构,按先进后出(First In Last Out,FILO)的方式工作,使用堆栈指针(Stack Pointer, SP)指示当前的操作位置,堆栈指针总是指向栈顶。
根据堆栈的生成方式不同,可以把堆栈分为递增堆栈和递减堆栈两种类型:
同时,根据堆栈指针(SP)指向的位置,又可以把堆栈分为满堆栈(Full Stack)和空堆栈(Empty Stack)两种类型。
块拷贝寻址用于寄存器数据的批量复制,它实现从由基址寄存器所指示的一片连续存储器到寄存器列表所指示的多个寄存器传送数据。块拷贝寻址与堆栈寻址有所类似。两者的区别在于:堆栈寻址中数据的存取是面向堆栈的,块拷贝寻址中数据的存取是面向寄存器指向的存储单元的。
ARM指令的基本格式:
< opcode > {< cond >}{S} < Rd >, < operand1 > {, < operand2 >}
<>内的必须要的 {} 可选的
条件码 | 含义 | 判断方式 |
---|---|---|
EQ | Equal(相等) | Z == 1 |
NE | Not Equal(不相等) | Z == 0 |
CS/HS | Carry Set (C == 1) unsigned Higher or Same a>=b |
C == 1 |
CC/LO | Carry Clear(C == 0) a | C == 0 |
MI | MInous(负数) | N == 1 |
PL | Positive or Zero(非负数) | N == 0 |
VS | V Set(溢出) | V == 1 |
VC | V Clear(没溢出) | V == 0 |
HI | unsigned Higher | (C == 1) && (Z == 0) |
LS | unsigned Lower or Same | (C == 0) || (Z == 1) |
GE | signed Greater or Equal | N == V |
LT | Less Than | N != V |
GT | Greater Than | (N == V) && (Z == 0) |
LE | Less than or Equal | (Z==1) || ((N != V)) |
S: Status 表示该指令执行结果是否影响xPSR(程序状态寄存器)的标志位
例如:
MOV R0, #0 ;-> 不会影响任何状态标志
MOVS R0, #0 ;->会影响状态标志位
有一些指令如: CMP, CMN, TEQ … 这些不加S也影响状态标志位,因为这些指令,不保存运算结果,只影响状态标志位
Rd: Register Desatiation 目标寄存器(用来保存运算的结果)
operand1: 第一个操作数
operand2: 第2个操作数(有些指令没有第二个操作数)
操作数形式:
#immer_8r 立即数(常量表达式))
ADD R0, R1, #250
ADD R0, R1, #0x10
ADD R0, R1, #(1 << 3) | (1 <<4)
为了避免立即数不合规,建议使用LDR R0, =666666
Rm 寄存器
ADD R0, R1, R2
Rm,shift 寄存器移位方式
LSL #n
Logic Shift Left 逻辑左移n位,
Logic逻辑移位,无论是左移还是右移,空出的位,都补0
LSR #n
Logic Shift Right 逻辑右移n位,
在移出的位为0的情况下,
LSL #n 就相当于在原寄存器值 乘以 2的n次方
LSR #n 就相当于在原寄存器值 除以 2的n次方
ASR #n 算术右移n位
算术移位,不应该改变它的符号。
最高n位补符号位的值.
ASL #n 没有 => LSL
ROR #n Rotate Right 循环右移
把右边移的n位,补到最左边。
RRX 带扩展的循环右移1位
带C(xPSR.C) 那个位
(1)
MOV R1,#3 ;r1==3
MOV R2,#1 ;r2==1
LSL R2,R1 ;r2==8 1<<3
(2)
ADD R0, R1, R2, LSR #2 ;(2)和(1)组合测试
(3) R0<—2 R0*12—>R2
MOV R0,#2
MOV R2,R0
LSL R0,2
ADD R2,R0,R2,LSL #3
用来在存储器(Memory) 《-》寄存器之间传递数据
把数据从存储器 -> 寄存器 加载 Loader LDR
把数据从寄存器 -> 存储器 存储 Store STR
LDR{cond} {S} {B/H} Rd, <地址>
STR{cond} {B/H} Rd, <地址>
任务:char a=0x41 char b=a
MOV R0,0x41
LDR R1,=0X20001000
STRB R0,[R1]
LDRB R2,[R1]
STRB R2,[R1,#4]
B: Byte一个字节, H: Half word半字(两个字节),如果省略,默认为4个字节,B/H决定加载/存储多少个字节。
S: Signed把地址的那个变量,当作是一个有符号的。如果没有S就把地址的那个变量,当作是一个无符号的。
地址确定方式: 基址寄存器 + 偏移量
[Rn] | Rn | 地址值 |
---|---|---|
[Rn, 偏移量] | Rn + 偏移量 | Rn值不变 |
[Rn, 偏移量]! | Rn + 偏移量 | Rn值+偏移量 |
[Rn], 偏移量 | Rn | Rn值+偏移量 |
[ ]内表示存储器的地址值,如果有**!或,偏移量**在[]外边,则表示做完后,基址值自动增加偏移量。
偏移量有以下3种方式:
LDR R1, [R0, #0x12]
LDR R1, [R0, R2]
LDR R1, [R0, R2, LSL #2]
任务1: 给存储器0x20001000单元写入一个整数(-2)
MOV R0, #-2
LDR R1, =0x20001000
STR R0, [R1]
任务2: 将0x20001000单元的数值转到0x20001008
LDR R1,=0X20001000
LDR R2,[R1]
STR R2,[R1,#8]
练习1.1
char ch =0x80; //编译时刻或运行时刻,为ch分配一个存储器空间 0x2000 1000
int a; //编译时刻或运行时刻,为a分配一个存储器空间 0x2000 1004
MOV R0,#0x80
LDR R1,=0x20001000
STRB R0,[R1]
LDRSB R2,[R1]
STR R2,[R1,#4]
练习1.2
unsigned char ch =0x80; //编译时刻或运行时刻,为ch分配一个存储器空间 0x2000 1000
int a; //编译时刻或运行时刻,为a分配一个存储器空间 0x2000 1004
MOV R0,#0x80
LDR R1,=0x20001000
STRB R0,[R1]
LDRB R2,[R1]
STR R2,[R1,#4]
多寄存器存取
在一个连续(地址递增/地址递减)的存储器地址上,进行多个(用列表的形式)寄存器的存取。
多寄存器加载 LDM Loader Multi
多寄存器存储 STM Store Multi
LDM{cond}<模式> Rn{!}, reglist
STM{cond}<模式> Rn{!}, reglist
由<模式>来指定存储器的多个地址,是连续增加,还是连续递减:
无论是哪种模式,低地址都是对应编号低的寄存器
ARM Cortex M4只使用IA, DB
例子:
MOV R0,#12
MOV R1,#34
MOV R2,#56
MOV R8,#78
;R-->M
LDR R3,=0x20001000
① STMIA R3!,{R0-R2,R8} ;R3=0x20001010 //空递增,先放R0
② STMDB R3!,{R0-R2,R8} ;R3=0x20000FF0 //满递减,先减再传输
MOV R0,#0x12
MOV R1,#0x34
MOV R2,#0x56
MOV R8,#0x78
;M-->R
① LDMDB R3!,{R0-R2,R7} ;//满递减,出栈,先出R8
② LDMIA R3!,{R0-R2,R7}
reglist: 表示寄存器列表,可以包含多个寄存器,使用","隔开,寄存器由小到大排列(编译系统会自动按序号排)
如: {R1,R3,R4,R5} {R1,R3-R5}
! 可加可不加
加: 表示最后存储器的地址写入到Rn中去
不加: 最后Rn的值不变
任务: 将R0-R3放到存储器单元0x20000200开始的递减连续单元存放,然后再恢复。
MOV R1,#1
MOV R2,#2
MOV R3,#3
LDR R0,=0x20000200
STMDB R0!,{R1-R3}
MOV R1,#12
MOV R2,#34
MOV R3,#56
LDMIA R0!,{R1-R3}
堆栈操作: 堆栈的低地址对应编号低的寄存器
压栈:PUSH < reglist >
出栈:POP < reglist >
"栈"就是一块内存,上面那两条指令,并没有指定内存地址,PUSH, POP用的地址寄存器是SP
堆栈有四种类型:
STMDB SP!,{R0-R2,R8} ;PUSH {R0-R2,R8}
LDMIA SP!,{R7,R0-R2} ;POP {R0-R2,R7}
(1)数据传送指令
MOV{cond}{S} Rd, operand2 Rd <— operand2
MVN{cond}{S} Rd, operand2 Rd <— ~operand2(取反)
例如:MOV R0,#-1 <==> MVN R0,#0
(2)算术运算: 加减
ADD{cond}{S} Rd, Rn, operand2 Rd <— Rn + operand2
ADC{cond}{S} Rd, Rn, operand2 Rd <— Rn + operand2 + xPSR.C
SBC{cond}{S} Rd, Rn, operand2 Rd <— Rn - operand2 - !xPSR.C 带借位的减法
RSB{cond}{S} Rd, Rn, operand2 operand2 - Rn —> Rd 逆向减法指令
RSC{cond}{S} Rd, Rn, operand2 operand2 - Rn - !xPSR.C —> Rd 带借位的逆向减法
(3)逻辑运算指令 (按位)
AND{cond}{S} Rd, Rn, operand2 AND 与, Rn & operand2 —> Rd 按位与
ORR{cond}{S} Rd, Rn, operand2 OR 或, Rn | operand2 —> Rd 按位或
EOR{cond}{S} Rd, Rn, operand2 EOR 异或 Rn ^ operand2 —> Rd 按位异或
BIC{cond}{S} Rd, Rn, operand2 Rn & (~operand2) —> Rd 把Rn中 operand2中的为1的哪些位置上的bit位清零
练习:
R0 低4位清零
MOV R0,#0x7fffffff
AND R0,R0,#~0xf
BIC R0,R0,#0xf
R0 清零
MOV R0,#0x7fffffff
AND R0,R0,#0
EOR R0,R0
取寄存器R0中的b7-b10,赋值给R1
MOV R0,#0xffffffff
MOV R1,R0,LSR #7
AND R1,R1,#0xf
高4位清零
MOV R0,#0x7fffffff
BIC R0,R0,#0xf<<28
bit15 bit13 清零
MOV R0,#0xffffffff
BIC R0,R0,#1<<3
BIC R0,R0,#1<<15
取出bit10到bit7放到R1
MOV R1,R0,LSR #7
AND R1,R1,#0xf
(4) 比较指令: 不需要加S,直接影响xPSR中的标志位。运算结果不保存。
CMP Rn, operand2 比较Rn与operand2的大小,Rn - operand2
xPSR.Z == 1 => EQ
CMN Rn, operand2 比较Rn与operand2的大小,Rn + operand2(负数比较)
TST Rn, operand2 Rn & operand2,用来测试Rn中特定的bit位是否为1
Rn & operand2 => xPSR.Z == 1
=> 说明operand2中为1的哪些bit的,在Rn都为0
例:测试R2中的第2和第3bit位,是否为1(需要分开测,不能一起)
MOV R2,#0xb
TST R2,#1<<2
TST R2,#1<<3
TEQRn, operand2 Rn ^ operand2,测试是否相等
Rn == operand2 => Rn ^ operand2 == 0 => xPSR.Z ==
略
(1)分支指令
B lable lable -> PC, 不带返回的跳转
BL lable 过程调用,函数调用 带返回的
把下一条指令的地址 —> LR lable -> PC
(2)直接向PC寄存器赋值
MOV PC, LR
MOV PC, #0x80000000
;第二种
LDR R2,=code_start
MOV PC, R2
MRS Rd, xPSR
xPSR:APSR, IPSR, EPSR
程序状态寄存器的值 赋值给 Rd
MOVS R1,#-1
MRS R0,APSR ;R0==0x80000000
MSR xPSR, Rd 将通用寄存器Rd的值,赋值给程序状态寄存器
伪指令,机器不识别,但是可以表示程序员或编译器的某种想要的操作。
编译器会把伪指令变成一条或多条合适的机器指令。
(1)NOP 空操作,不产生任何实际的效果,但是占用机器周期。
(2)LDR{cond} Rd, =expr
如果expr是一个合法的立即数:
LDR Rd, =expr <=> MOV Rd, #expr
解决立即数不合规的问题,可以直接给出存储器的地址
LDR R0,=0x12345678
标号(地址)
;先定义数据段
AREA mydata,DATA,READWRITE
data_i
SPACE 4
;标号指向数据空间的地址
MOV R0,#4
LDR R1,=data_i
STR R0,[R1]
练习: 2*24 的值放到数据段存储
MOV R0,#2
LSL R1,R0,#4
ADD R0,R1,R0,LSL #3
LDR R2,=data_i
STR R0,[R2]
B .
if (a > b){
c = 5;
}else{
c = 6;
}
CMP R0, R1
MOVGT R2, #5 ;
MOVLE R2, #6
;或者
CMP R0, R1
MOVLE R2, #6
MOVGT R2, #5
编译器如何选择?if ( likely(a > b) )
for (i = 1, sum = 0; i <= 10;i++){
sum = sum + i;
}
loop_sum
CMP R0, #10 ;设置循环条件
BGT loop_sum_end ;不满足跳出循环
ADD R1,R1,R0
ADD R0,R0,#1
B loop_sum ;继续循环
loop_sum_end
练习: 计算100以内所有的奇数之和。
code_start
;1+3+..+99
MOV R0,#1
MOV R1,#0
loop_sum
CMP R0,#100 ;R0<=100
BGT loop_sum_end ;R0>100
ADD R1,R1,R0 ;sum=sum+i
ADD R0,R0,#2 ;i=i+2
B loop_sum
loop_sum_end
B .
END
格式:
label PROC
PUSH
(看你的)
POP
ENDP
难点:
入口参数传送用 R0, R1,R2, R3,如果超过4个参数,后面的参数需要放到栈空间
R0:对应第一个参数 R1:第二个参数……
函数的返回值用R0,如果是64bits,用R1(高32位)
提醒: R0是返回值,所以在实现过程中,你要记住R0不要乱用。
“单值类型” (所以在一进入过程,就把R0的值存到另一个寄存器里)
函数实现
”现场保护” PUSH {R2-R12,LR}
LR也要保存,否则,在过程中,就不能调用其他过程啦。
保护除了传参外的所有寄存器
“现场恢复” POP {R2-R12, PC}
练习: 写一个过程调用 计算两个数之和
code_start PROC
;3+5=8
MOV R0,#3
MOV R1,#5
BL sum_two
B .
ENDP
sum_two PROC
PUSH {R2-R12, LR}
ADD R0,R0,R1
POP {R2-R12, PC}
ENDP
END
第一点:汇编中如何调用C代码
需要在汇编文件,“进口”:引入相应的全局变量名或全局函数, “符号”
IMPORT 函数名or全局变量名
直接调用就好
BL sum_three
第二点:C文件中如何调用汇编
extern int sum_two(int a, int b);
EXPORT sum_two
练习: 汇编过程sum_two,编写一个c语言函数实现三个数之和的计算
extern int sum_two(int a, int b);
int sum(int a, int b, int c){
return c+sum_two(a, b);
}
IMPORT sum_three
EXPORT sum_two
AREA mycode,CODE,READONLY,ALIGN=3
PRESERVE8
code_start PROC
MOV R0,#2
MOV R1,#3
MOV R2,#4
BL sum_three
B .
ENDP
sum_two PROC
PUSH {R2-R12, LR}
ADD R0,R0,R1
POP {R2-R12, PC}
ENDP
END
用汇编语言实现一个函数,判断a是否为b的倍数
输入:a,b
输出:1是倍数 0不是倍数
/*
return 1: a是b的倍数
return 0: a不是b的倍数
*/
int Is_Multi(int a, int b) 3,2{
int i = b; 2
while (b <= a){
if (a == b){
return 1;
}
b = b + i;
}
return 0;
}
code_start PROC
MOV R0,#4
MOV R1,#2
BL Is_Multi
B .
ENDP
Is_Multi PROC
PUSH {R2-R12, LR}
MOV R3,R0
MOV R4,R1
MOV R0,#0
loop
CMP R4,R3
BGT loop_end
CMP R4,R3
MOVEQ R0,#1
BEQ loop_end
ADD R4,R4,R1
B loop
loop_end
POP {R2-R12, PC}
ENDP
END
用汇编语言实现一个函数,判断一个数x是否为质数/完全数
int Is_Prime(int x){
int i ;
for (i = 2; i < x; i++){
if (Is_Multi(x, i)){
return 0;
}
}
return 1;
}
code_start PROC
MOV R0,#28
BL Is_Perfect
B .
ENDP
Is_Perfect PROC
PUSH {R1-R12,LR}
;R0=x, R1=i
MOV R1,#2 ;loop i
MOV R2,#1 ;sum
MOV R3,R0 ;R3=x
loop_get_sum
MOV R0,R3 ;the first param = x
CMP R1,R3 ;R1
用汇编语言实现100以内所有素数之和
提示:
把100以内所有的质数,都保存在一个数组中。
x是不是质数 拿 < x的所有素数去整除x,即可。
2—data—{2}
3—3:2—{2,3}
4—4:2
5—5:2—5:3—{2,3,5}
求1000以内的完数,把个数保存到R0,R1-R12保存求出来的完数
data_size EQU 0x100
data_i
SPACE data_size
data_end
;define code
AREA mycode,CODE,READONLY,ALIGN=3
PRESERVE8
code_start PROC
MOV R1,#1000
MOV R2,#2 ;i=2
MOV R3,#0 ;num=0
LDR R4,=data_i
loop_is_perfect
MOV R0,R2
CMP R2,R1
BGT loop_is_perfect_end
BL Is_Perfect
CMP R0,#0
STRNE R2,[R4]
ADDNE R4,R4,#4 ;R4+4
ADDNE R3,R3,#1 ;num++
ADD R2,R2,#1 ;i++
B loop_is_perfect
loop_is_perfect_end
MOV R0,R3
LDMDB R4,{R1-R12}
B .
ENDP
Is_Perfect PROC
PUSH {R1-R12,LR}
;R0=x, R1=i
MOV R1,#2 ;loop i
MOV R2,#1 ;sum
MOV R3,R0 ;R3=x
loop_get_sum
MOV R0,R3 ;the first param = x
CMP R1,R3 ;R1
判断闰年函数
求最近质数函数
求一个正整数n的最近质数,如果n本身就是质数,则返回本身,如果有两个最近质数,返回较小的哪个。
水仙花数 100-1000的水仙花数,并保存 0x20000000查看
水仙花数(Narcissistic number)也被称为超完全数字不变数(pluperfect digital invariant, PPDI)、自恋数、自幂数、阿姆斯壮数或阿姆斯特朗数(Armstrong number),水仙花数是指一个 3 位数,它的每个位上的数字的 3次幂之和等于它本身。例如:1^3 + 5^3+ 3^3 = 153。
第一部分 嵌入式系统概述
第二部分 ARM指令系统
第三部分 通用 I/O (GPIO)
第四部分 中断机制
第五部分 时钟定时器