嵌入式系统入门篇3之ARM指令(UAL)

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程序设计

  1. 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不太可能成立
    编译器会优先取出相对应的指令

  2. 循环是如何实现的

     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

  3. 过程调用(函数调用)如何实现
    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也要保存,否则,在过程中,就不能调用其他过程啦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值