1. ARM存储器访问指令(在寄存器和存储器之间进行数据交换)
数据从存储器 -> 寄存器 加载 Loader LDR
数据从寄存器 -> 存储器 存储 Store STR
(1)指令格式:
LDR{cond} {S} {B/H} Rd, <地址>
STR{cond} {B/H} Rd, <地址>
cond:执行条件,不加cond是无条件执行
S:数据有扩展时,有符号数扩展符号位,无符号数扩展0。Signed把地址的那个变量,当作是一个有符号的,如果没有S就把地址的那个变量,当作是一个无符号的。短 -> 长的(B/H),有符号来说,高位全部补符号位,如果是无符号的,高位全部补0。
Rd:目的寄存器
<地址>:地址是必须要的
B/H:决定加载/存储多少个字节 Byte一个字节, H: Half word半字(两个字节), 如果省略,默认为4个字节。
LDR加载,把存储器<地址>中的内容加载到 寄存器Rd中
STR存储,把寄存器Rd中的内容,存储到存储器<地址>中去
小贴士: 没有任何指令能从存储器到存储器!只能先加载到寄存器再存储到存储器。
先看一段汇编指令:这段汇编是先在存储器0x20001000单元写入一个整数3,再将0x20001000单元的数值加2转到0x20001004
MOV R0,#3
LDR R1, =0x20001000
STR R0,[R1] ;寄存器间接寻址,将R0的内容存储到存储器地址为0x20001000的地址空间中
ADD R0,R0,#2
STR R0,[R1,#4]! ;将R0的内容存储到0x20001004的地址空间中
地址确定方式: 基址寄存器 + 偏移量(地址值肯定需要在一个寄存器中)
即:
—————————————————————————
[Rn, 偏移量] Rn + 偏移量 Rn值不变
[Rn, 偏移量]! Rn + 偏移量 Rn值+偏移量
[Rn], 偏移量 Rn Rn值=Rn + 偏移量
[]内表示存储器的地址值 ,如果有!或偏移量在[]外边,则表示做完后,基址值自动增加偏移量。
偏移量有以下3种方式:
立即数:
如: LDR R1, [R0, #0x12]([R0+0x12] -> R1)
寄存器:
如: LDR R1, [R0, R2]([R0+R2] -> R1)
寄存器及移位常数:
如: LDR R1, [R0, R2, LSL #2]([R0 + R2 << 2] -> R1)
练习:将c语言代码转换成汇编指令
char ch =0x80; //编译时刻或运行时刻,为ch分配一个存储器空间 0x2000 1000
int a; //编译时刻或运行时刻,为a分配一个存储器空间 0x2000 1004
a = ch;
汇编指令如下:
MOV R0,#0x80
LDR R1, =0x20001000
STRB R0,[R1] ;char是一个字节所以要加上B
LDRSB R2,[R1];char是有符号数扩展为32位要加上S扩展符号位
STR R2,[R1,#4]!
变式:char ch =0x80; =》unsigned char ch =0x80;
将上述汇编指令中的LDRSB R2,[R1];=》LDRB R2,[R1];即可
(2) 多寄存器存取:在一个连续的存储器地址上,进行多个寄存器的存取。
多寄存器加载 LDM Loader Multi
多寄存器存储 STM Store Multi
LDM{cond}<模式> Rn{!}, reglist
STM{cond}<模式> Rn{!}, reglist
note:
a. 这两条指令只是通过一个寄存器 Rn指定了一个存储器的地址, 存储器的多个地址,是连续增加,还是连续递减呢?由<模式>来指定:
注意:无论是哪种模式,低地址都是对应编号低的寄存器
IA: Incrememt After() 每次传送后地址自动增加(+4) <—–先传地址再增加
DB: Decrement Before 每次传送前地址自动减少(-4) <—–先减再传地址
IB: Increment Before 每次传送前地址先自动增加(+4)
DA:Decrement After 每次传送后地址自动减少(-4)
ARM Cortex M4只使用IA, DB
b. reglist: 表示寄存器列表,可以包含多个寄存器,使用”,”隔开,寄存器由小到大排列(编译系统会自动按序号排)
如: {R1,R3,R4,R5}或{R1,R3-R5}
c. ! 可加可不加
加: 表示最后存储器的地址写入到Rn中去。
不加: 最后Rn的值不变
任务:将R0-R3放到存储器单元0x20000200开始的递减连续单元存放,然后再恢复
MOV R0,#1
MOV R1,#2
MOV R2,#3
MOV R3,#4
LDR R4,=0x20001000
STMIA R4!, {R0-R3};将R0-R3寄存器的值存放在0x20001000开始递增的地址中,是传数据再增加。
MOV R0,#10
MOV R1,#20
MOV R2,#30
MOV R3,#40
LDMDB R4!, {R0-R3};将数据从存储器加载出来,先递减再传数据,即使前面的寄存器已经存值,一旦执行这条指令全部恢复为原来的值。
(3) 堆栈操作:堆栈的低地址对应编号低的寄存器
压栈: PUSH < reglist>
出栈: POP < reglist>
“栈”就是一块内存,上面那两条指令,并没有指定内存地址,PUSH, POP用的地址寄存器是SP(栈顶指针寄存器)。
堆栈有四种类型:
A: add 递增堆栈 D: Dec递减堆栈
SP堆栈 栈顶指针,栈顶指针可以保存元素 -> 满堆栈 Full;也可以指向空(不保存元素) ->空堆栈 Empty
EA: 空递增
PUSH X
X -> [SP]
sp ++
POP x
sp--
[sp] -> x
FA: 满递增
PUSH X
sp ++
x -> [SP]
POP x
[sp] -> x
sp --
ED: 空递减
PUSH X
x -> [sp]
SP--
POP x
sp++
[sp] -> x
FD: 满递减 <----- ARM采用的堆栈形式是: FD满递减堆栈
PUSH X
sp--
x -> [sp]
POP x
[sp] -> x
sp++
上述汇编代码可以用PUSH和POP指令表示更为简洁:
MOV R0,#1
MOV R1,#2
MOV R2,#3
MOV R3,#4
PUSH {R0-R3} ;堆栈是满递减R3先入栈(减4再入栈)
MOV R0,#10
MOV R1,#20
MOV R2,#30
MOV R3,#40
POP {R0-R3} ;R0先出去(满的直接出去再加4)具体进出栈细节如下:
PUSH X
sp– //0x20000200
x -> [sp]
//PUSH {R0-R3}
0x20000200-4 0x200001FC R3
0x200001F8 R2
0x200001F4 R1
0x200001F0 R0
POP x
[sp] -> x
sp++
//POP {R0-R3}
//SP–0x200001F0 R0–>R1—>R2—>R3 SP 0x20000200
2. ARM数据处理指令-> 对寄存器内容操作
(1) 数据传送指令
MOV{cond}{S} Rd, operand2 ; Rd <– operand2
MVN{cond}{S} Rd, operand2 ; Rd <— ~operand2(取反)
用MOV R0,#-3指令时,机器内部会使用MVN指令
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
SUB{cond}{S} Rd, Rn, operand2; Rd <— Rn – operand2
SBC{cond}{S} Rd, Rn, operand2; Rd <— Rn – operand2 – !xPSR.C 带借位的减法
RSB 逆向减法指令 Reserve
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 按位异或
任何数和1做与运算还是它本身,和0做与运算是0
BIt Clear 位清零,把一个指定寄存器的中,指定的bit位给清掉
BIC{cond}{S} Rd, Rn, operand2; Rn & (~operand2) -> Rd
把Rn中 operand2中的为1的哪些位置上的bit位清零。
练习1:R0 低4位清零
MOV R0,#-3
;AND R0,R0,#0Xfffffff0
BIC R0,R0,#0Xf
练习2:R0 第3 5 7位清零
MOV R0,#-3
BIC R0,R0,#(1<<3)|(1<<5)|(1<<7)
练习3:取寄存器R0中的b7-b10,赋值给R1(先左移7位再将高位清零)
MOV R0,#-3
MOV R1,R0,LSR #7
AND R1,R1,#0Xf
(4) 比较指令: 不需要加S,直接影响xPSR中的标志位,运算结果不保存。
CMP Rn, operand2; 比较Rn与operand2的大小 Rn – operand2
if Rn== operand2
CMP 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
TEQ Rn, operand2 ; Rn ^ operand2 测试是否相等
Rn== operand2 => Rn ^ operand2== 0 => xPSR.Z== 1
3.分支指令:用来实现代码的跳转
有两种方式可以实现程序的跳转
(1) 分支指令
B lable ; lable -> PC, 不带返回的跳转
BL lable ; 过程调用,函数调用带返回的把下一条指令的地址 -> LR lable -> PC
(2) 直接向PC寄存器赋值
MOV PC, LR
MOV PC, #0x80000000
4. 杂项指令
MRS Rd, xPSR 程序状态寄存器的值赋值给Rd
xPSR: APSR, IPSR, EPSR
MOVS R1,#-1
MRS R0,APSR ;R0==0x80000000
MSR xPSR, Rd 将通用寄存器Rd的值,赋值给程序状态寄存器
6.伪指令
伪指令,机器不识别,但是可以表示程序员或编译器的某种想要的操作。编译器会把伪指令变成一条或多条合适的机器指令。
(1) NOP:No Operation 空操作,不产生任何实际的效果,但是占用机器周期。
(2) LDR
LDR{cond} Rd, =expr
LDR伪指令用于加载表达式expr的值,到寄存器Rd中去。
expr可以为一个任意的常数,或标号,或常量表达式…
LDR Rd, =expr
a: 如果expr是一个合法的立即数
LDR Rd, =expr
<=> MOV Rd, #expr
b: 解决立即数不合规的问题,可以直接给出存储器的地址
LDR R0,=0x12345678
LDR R0,=0x20001000
c: 标号(地址)
(1)LDR Rd, =data1
data1
DCD 0x12345678
(2)int i=4;
;先定义数据段
AREA mydata,DATA,READWRITE
data_i
SPACE 4
;标号指向数据空间的地址
MOV R0,#4
LDR R1,=data_i
STR R0,[R1]
7.ARM程序设计
-
if/else 如何实现的
if (a > b) { c = 5; } else { c = 6; } <=> a---R0 b---R1 c---R2
CMP R0, R1(影响标志位,不存放结果)
MOVGT R2, #5 ;这条指令执行还是不执行,要等 “取指” -> 译码才确定这条指令是否满足条件!
MOVLE R2, #6
if 译码时发现 GT条件满足,则清空流水线,然后再取下一条指令
or
CMP R0, R1
MOVLE R2, #6
MOVGT R2, #5
ps:指令是流水线执行的(取指令、解释指令和执行指令三个过程),当CMP在流水线的执行阶段时,MOVGT指令在解释阶段,分析出来不满足情况,则清空这条流水线,所以在写汇编指令时要把更有可能发生的写在前面。if ( likely(a > b) ) ;对结果预测
{}
else
{}
likely(x) :编译器的修饰词,告诉编译器,后面的这个表达式x很有可能成立
unlikely(x):编译器的修饰词,告诉编译器,后面的这个表达式x不太可能成立
编译器会优先取出相对应的指令 -
循环是如何实现的
for (i = 1, sum = 0; i <= 10;i++) { sum = sum + i; } => 初始条件 i--R0==1,sum--R1==0 循环条件:R0<=10 ---CMP R0,#10 LE 就执行循环 GT跳出循环 (到结束的地方lable) B 循环体:用两个标号,分别标识循环体的开始和结束,只用一个标号也可以,标识循环体的开始。
上述代码汇编指令如下:
MOV R0,#1 ;i
MOV R1,#0 ;sum
loop_sum
CMP R0,#10
BGT loop_sum_end
ADD R1,R1,R0
ADD R0,R0,#1
B loop_sum
loop_sum_end练习:计算100以内所有的偶数之和。
MOV R0,#2 ;i
MOV R1,#0 ;sum
loop_sum
CMP R0,#100
BGT loop_sum_end
ADD R1,R1,R0
ADD R0,R0,#2
B loop_sum
loop_sum_end -
过程调用(函数调用)如何实现
ATPCS: ARM/Thumb Procedure Call Standard ARM/Thumb过程调用标准ARM定的.
记住:过程调用就是这样label PROC
PUSH(保护现场:保护除了传参之外的所有寄存器R0-R12)
(看你的)
POP(恢复现场)
ENDP过程调用:两个数相加
test_start PROC
MOV R0,#5 ;参数会自动放在R0,R1寄存器中
MOV R1,#6
BL sum_two
B .
ENDP
sum_two PROC
PUSH {R2-R12,LR} ;LR链接寄存器存放返回的地址
ADD R0,R0,R1
POP{R2-R12,PC} ;把链接寄存器的值(返回地址B .的地址)给PC
ENDP
END理解难点在这里
(1)入口参数传送用 R0, R1,R2, R3,如果超过4个参数,后面的参数需要放到栈空间。
R0:对应第一个参数 R1:第二个参数……
(2) 函数的返回值放在R0寄存器里,如果是64bits,用R1(高32位) R0.超过64bits的返回值,不支持;
提醒:R0是返回值,所以在实现过程中,你要记住R0不要乱用。
(3) 函数实现
“现场保护” PUSH R0-R12 LR
Reglists -> 栈 PUSH {寄存器列表}
传递参数的那几个寄存器你不需要保护, 因为那几个寄存器本身就是传递给你用的,你无须保护。
建议: 非参数寄存器(除了SP,PC)都要保存假设你的过程参数用R0,R1
PUSH {R2-R12,LR}
“现场恢复” POP
POP {R2-R12, PC}
LR也要保存,否则,在过程中,就不能调用其他过程啦。
今天的文章嵌入式系统入门篇3之ARM指令(UAL)分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/60384.html