嵌入式学习笔记(四)

LoadStore内存读写指令

单寄存器读写指令

指令码
----------------

ldr : 从内存地址中读取数据到寄存器中,读4个字节的数据

str : 将寄存器中的数据写到内存地址中,写4个字节的数据
------------------------------------------------------

ldrh : 从内存地址中读取数据到寄存器中,读2个字节的数据

strh : 将寄存器中的数据写到内存地址中,写2个字节的数据
---------------------------------------------------------

ldrb : 从内存地址中读取数据到寄存器中,读1个字节的数据

strb : 将寄存器中的数据写到内存地址中,写1个字节的数据
----------------------------------------------------------

ld : Load   st : Store   r : Register   h : half   b : byte 
---------------------------------------------------------------


指令格式
---------------
ldr/ldrh/ldrb   Rd, [Rm]

    [Rm] : 将Rm寄存器中的数据当成内存的地址
 
    功能:将[Rm]指向的地址空间的数据读到Rd寄存器中
    
    int a = 100;
 int *p = &a;
 int b = *p;
    
 [Rm]   <==>  p

    ldr Rd, [Rm]   <==>  b = *p;
    
str/strh/strb   Rn, [Rm]
    [Rm] : 将Rm寄存器中的数据当成内存的地址
    
    功能:将Rn寄存器中的值写到[Rm]指向的地址空间中
    
    int a = 100, b = 200;
 int *p = &a;
 *p = b;
-----------------------------------




指令测试代码
-----------------------

 /* 7. 单寄存器操作指令 */

 ldr r0, =0x40000800   @ 准备地址
 ldr r1, =0x12345678   @ 准备数据
 
 @ 将r1寄存器中的值写到[r0]指向的地址空间中

 str r1, [r0]
 
 @ 将[r0]指向的地址空间中的数据读到R2寄存器中

 ldr r2, [r0]
 


------------------------------------------



单寄存器操作指令的其他用法
---------------------------------
ldr/ldrh/ldrb  Rn, [Rm, #offset_addr][Rm + offset_addr]指向的地址空间的数据读到Rn寄存器中,

    Rm寄存器中存储的内存地址不变
    
ldr/ldrh/ldrb  Rn, [Rm], #offset_addr
---------------------------------------------------[Rm]指向的地址空间的数据读到Rn寄存器中,

    同时更新Rm寄存器中的地址,Rm = Rm + offset_addr
    
ldr/ldrh/ldrb  Rn, [Rm, #offset_addr]!
--------------------------------------------------------------[Rm + offset_addr]指向的地址空间的数据读到Rn寄存器中,

    同时更新Rm寄存器中的地址,Rm = Rm + offset_addr
    
    ! : 更新地址
        
    以上三种不同的指令格式同样适用于str/strh/strb指令。




-------------------------------------------------------------


 ldr r0, =0x40000800   @ 准备地址

 ldr r1, =0x11111111   @ 准备数据
 ldr r2, =0x22222222
 ldr r3, =0x33333333
 
 @ 将r1寄存器中的数据写到[R0+4]指向的地址空间中

 @ R0寄存器中的地址不变
 @ [0x40000804] = 0x11111111  r0 = 0x40000800
 str r1, [r0, #4]
 
 @ 将r2寄存器中的数据写到[r0]指向的地址空间中

 @ 同时更新r0寄存器的地址
 @ [0x40000800] = 0x22222222  r0 = 0x40000804
 str r2, [r0], #4
 
 @ 将r3寄存器中的数据写到[R0+4]指向的地址空间中

 @ 同时更新r0寄存器的地址
 @ [0x40000808] = 0x33333333  r0 = 0x40000808
 str r3, [r0, #4]!

----------------------------------------------------------

练习题:
    将0x12345678数据写到内存的0x40000800地址中,
    使用ldrb指令读取0x40000800地址中的数据,每次读1个字节,读取4次。
    将读取的4个字节的数据使用位运算的方式拼接得到0x12345678
    
ldr r0, =0x40000800
    ldr r1, =0x12345678
    str r1, [r0]
    
    @ 使用ldrb方式读取单字节的数据
    @ r2 = 0x78  r3 = 0x56   r4 = 0x34  r5 = 0x12
 /*
    ldrb r2, [r0, #0]
 ldrb r3, [r0, #1]
 ldrb r4, [r0, #2]
 ldrb r5, [r0, #3]
 */
 /*
 ldrb r2, [r0], #1
 ldrb r3, [r0], #1
 ldrb r4, [r0], #1
 ldrb r5, [r0]
    */
 
 ldrb r2, [r0, #0]!
 ldrb r3, [r0, #1]!
 ldrb r4, [r0, #1]!
 ldrb r5, [r0, #1]!
 
    @ 使用位运算进行数据的拼接
 /*
 mov r6, r5   @ r6 = 0x12
 orr r6, r4, r6, lsl #8
 orr r6, r3, r6, lsl #8
 orr r6, r2, r6, lsl #8
 */
 /*
 mov r6, r5, lsl #24
 orr r6, r6, r4, lsl #16
 orr r6, r6, r3, lsl #8
 orr r6, r6, r2
 */
 
 mov r6, r5, lsl #24
 lsl r4, r4, #16
 add r6, r6, r4
 lsl r3, r3, #8
 add r6, r6, r3
 lsl r2, r2, #0
 add r6, r6, r2

多寄存器读写指令

指令码
-----------

stm : 向内存地址中同时写入多个寄存器中的数据

ldm : 将内存地址中的数据同时读到多个寄存器中
    
    st : Store   ld : Load   m : multi
-----------------------------------------------------------

指令格式
--------------
stm Rm, {寄存器列表}
 Rm : Rm寄存器中的数据被当成一个内存地址看待
        
    功能:将寄存器列表中的所有寄存器中的数据写到Rm指向的内存地址的连续空间中

ldm Rm, {寄存器列表}
 Rm : Rm寄存器中的数据被当成一个内存地址看待
    
    功能:将Rm指向的内存地址空间中连续的数据读到寄存器列表的每个寄存器中
        
        
寄存器列表的书写格式:
    1. 如果寄存器列表中的寄存器编号连续使用“-”隔开;
        比如:{r1-r5}
    
    2. 如果寄存器列表中的寄存器编号不连续使用“,”隔开;
        比如:{r1,r3,r5}   {r1-r4,r6}
 
 3. 寄存器列表中的寄存器编号要求从小到大进行书写,不要从大到小书写
        比如:
     {r1-r5,r7}   ---> OK
     {r5-r1, r0}  ---> Error, 编译报错
     {r5,r4,r3,r2,r1}  ---> Ok, 但是编译器报警告

--------------------------------------------------------

指令测试代码
------------
ldr r0, =0x40000800   @ 准备地址
 ldr r1, =0x11111111   @ 准备数据
 ldr r2, =0x22222222
 ldr r3, =0x33333333
 ldr r4, =0x44444444
 ldr r5, =0x55555555
 
 @ 将r1-r5寄存器中的数据写到
 @ r0指向的连续的20字节内存空间中
 @ stm r0, {r1-r5}
 stm r0, {r5,r4,r3,r2,r1}
 
 @ 将r0指向的连续的20字节内存空间
 @ 的数据读到r6-r10寄存器中
 @ ldm r0, {r6-r9, r10}
 ldm r0, {r10,r9,r8,r7,r6}
--------------------------------------------


    不管寄存器列表中的寄存器的顺序如何书写,

    永远都是小编号的寄存器对应着低地址,

    大编号的寄存器对应着高地址。
---------------------------------------------------


     {r5-r1, r0}  ---> Error, 编译报错
     {r5,r4,r3,r2,r1}  ---> Ok, 但是编译器报警告

特殊功能寄存器读写指令

指令码
--------------

 对cpsr寄存器进行读写操作的指令
    
    msr : 将普通寄存器中的数据写到特殊功能寄存器cpsr中

    mrs : 将特殊功能寄存器cpsr中的数据写到普通寄存器中

 m : move  

 s : special 
  
 r : register
--------------------------------------------------------------


指令格式
----------------

    msr  cpsr, Rn
    
    mrs  Rd, cpsr

-----------------------



指令测试代码
--------------
/* 系统上电,处理器默认工作再SVC模式下,

 修改CPSR寄存器的M[4:0]位,切换到user模式下,

 SVC模式(0b10011) ---> User模式(0b10000)

 修改模式位的同时,要保证其他位不变 
 
 修改CPSR的值: 1101 0011 ---> 1101 0000*/
 
 @ 方式1:之间对CPSR寄存器进行赋值操作

 @ msr cpsr, #0xD0
 --------------------------------------------------------
 @ 方式2:使用位运算

 @ 1. 先将cpsr中的值读到普通寄存器中

 mrs r0, cpsr

 @ 2. 将普通寄存器中的[4:0]位清0

 bic r0, r0, #0x1F   @ and r0, r0, #(~0x1F) 

 @ 3. 将普通寄存器中的[4:0]位写成0x10

 orr r0, r0, #0x10

 @ 4. 将普通寄存器中的值写到CPSR中

 msr cpsr, r0
-----------------------------------------------------------

栈操作指令

栈的种类
--------------


增栈:压栈之后栈指针向高地址方向移动。
------------------------------------


减栈:压栈之后栈指针向低地址方向移动。
-------------------------------------


满栈:当前栈指针指向的栈空间中有有效的数据,要想在栈空间中压入数据,

    需要向移动栈指针指向一个空的空间,然后再压入数据。

    压入数据之后,栈指针指向的空间又有有效的数据。
----------------------------------------------------

空栈:当前栈指针指向的栈空间中没有有效的数据,可以先压入数据,

    需要移动栈指针指向一个空的空间。此时栈指针指向的又是一个空的空间。
-----------------------------------------------------------------



栈空间的操作方式
-----------------
栈空间的读写方式有4种:

    满增栈 :Full Ascending Stack

    满减栈 :Full Descending Stack

    空增栈 :Empty Ascending Stack 

    空减栈 :Empty Descending Stack
--------------------------------------


栈空间操作指令
---------------

满增栈操作指令:stmfa/ldmfa

满减栈操作指令:stmfd/ldmfd

空增栈操作指令:stmea/ldmea

空减栈操作指令:stmed/ldmed
----------------------------------------------
    
    ARM处理器中默认采用的是满减栈。
--------------------------------------------


栈空间操作指令的语法格式
-----------------------------
stmfd sp!, {寄存器列表}
 sp : sp中存放的是指向的栈空间的地址
    ! : 压栈之后更新栈指针的地址
    
    功能:将寄存器列表中的每个寄存器中的数据压到SP栈指针指向的连续的栈空间。
        
ldmfd sp!, {寄存器列表}
 sp : sp中存放的是指向的栈空间的地址
    ! : 压栈之后更新栈指针的地址
    
    功能:将SP栈指针指向的连续的栈空间中的数据出栈到寄存器列表的每个寄存器中。
        
寄存器列表的书写格式:
    1. 如果寄存器列表中的寄存器编号连续使用“-”隔开;
        比如:{r1-r5}
    
    2. 如果寄存器列表中的寄存器编号不连续使用“,”隔开;
        比如:{r1,r3,r5}   {r1-r4,r6}
 
 3. 寄存器列表中的寄存器编号要求从小到大进行书写,不要从大到小书写
        比如:
     {r1-r5,r7}   ---> OK
     {r5-r1, r0}  ---> Error, 编译报错
     {r5,r4,r3,r2,r1}  ---> Ok, 但是编译器报警告

-------------------------------------------------------------

指令测试代码
-----------------

ldr sp, =0x40000820   @ 准备地址
 ldr r1, =0x11111111   @ 准备数据
 ldr r2, =0x22222222
 ldr r3, =0x33333333
 ldr r4, =0x44444444
 ldr r5, =0x55555555
 
 @ 压栈
 @ 将r1-r5寄存器中的数据压倒sp指向的连续的栈空间中,
 @ 同时更新sp中的地址 , sp = sp - 20
 stmfd sp!, {r1-r5}
 
 @ 出栈
 @ 将sp指向的栈空间的数据出栈到r6-r10寄存器中,
 @ 同时更新sp中的地址, sp = sp + 20
 ldmfd sp!, {r6-r10}
------------------------------------------------------
#include <stdio.h>

void add_func()
{
    int a = 300, b = 400;
    int sum = a + b;  // 700
}

int main(int argc, const char *argv[])
{
    int a = 100, b = 200;
    add_func();
    int sum = a + b;  // 300
    return 0;
}
-------------------------------------------
ldr sp, =0x40000820  @ 初始化栈指针
 mov r0, #3
 mov r1, #4
 bl add_func
 add r2, r0, r1   @ r2 = r0 + r1 = 0x7
 b stop
 
 add_func:
  stmfd sp!, {r0-r1, lr}  @ 压栈保存现场
  mov r0, #5
  mov r1, #6
  bl sub_func
  add r3, r0, r1  @ r3 = r0 + r1 = 0xB
  ldmfd sp!, {r0-r1, pc}  @ 出栈恢复现场
  @ mov pc, lr
 
 sub_func:
  stmfd sp!, {r0-r1}  @ 压栈保存现场
  mov r0, #10
  mov r1, #2
  sub r4, r0, r1		@ r4 = r0 - r1 = 0x8
  ldmfd sp!, {r0-r1}  @ 出栈恢复现场
  mov pc, lr

跳转指令

指令码:
----------------
    b : 不带返回值的跳转指令

        发生跳转时,不会自动的保存返回地址到LR寄存器中

----------------------------------------------------------
    bl  : 带返回值的跳转指令

        发生跳转时,会自动保存返回地址到LR寄存器中
-------------------------------------------------------------


指令格式
----------------------------------------------------------
b/bl{cond}   Label(标签)	@ 程序跳转到label标签下的第一条指令
        
    Label:		@ 可以理解为C语言的函数名

  asm code
        
    注:
       	1. label标签可以看成函数名,表示函数的入口地址,

         及函数内的第一条汇编指令的地址

        2. 跳转指令的本质是修改PC寄存器的值, PC = Label
        
        3. b跳转指令的使用:有去无回就用b;

    比如:
                stop:
     	@ asm code

     	b  stop

  	4. bl跳转指令的使用:有去有回就用bl;

            比如:函数的调用

-------------------------------------------------------

指令测试代码
-------------------

/* 6. 跳转指令 */
 @ 定义一个交换函数,交换两个寄存器的值

 mov r0, #15

 mov r1, #9

 @ 使用bl指令调用swap_func函数

 @ 1. 查看PC寄存器是否执行函数的第一条指令

 @ 2. 查看LR寄存器中是否保存返回地址

 bl swap_func

 nop

 nop     @ 空指令,没有任何的意义,只是占位

 @ b stop

 ldr pc, stop
        
 swap_func:

  eor r0, r0, r1
  eor r1, r0, r1
  eor r0, r0, r1

  mov pc, lr    @ 函数的返回
 

 stop: 
   b stop 
----------------------------------------------------------


跳转指令的实现的其他的方式
----------------------------------

1. mov pc, lr	    @ 一般用于程序的返回

2. ldr pc, =Label   @ pc = Label  等价于 b Label

3. mov pc, #label   @ 不一定,Label不一定是一个立即数,一般不用
-----------------------------------------------------------------



练习题:使用跳转指令,比较指令,条件码,减法指令求两个数的最大公约数
---------------------------------------------------------------------


.text

.global _start

_start:

 mov r0, #9

 mov r1, #15
 
 loop:

  cmp r0, r1

  beq stop

  subhi r0, r0, r1

  subcc r1, r1, r0

  b loop
  
 stop:
 
   b stop 
 
.end 
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值