5. 从0开始学ARM-MRS、MSR、寻址操作、原子操作原理

发布者:blazings最新更新时间:2021-08-05 来源: eefocus关键字:ARM  MRS  MSR  原子操作 手机看文章 扫描二维码
随时随地手机看文章

一、程序状态寄存器访问指令

ARM微处理器支持程序状态寄存器访问指令,用于在程序状态寄存器和通用寄存器之间传送数据。


MRS

MRS{条件} 通用寄存器,程序状态寄存器(CPSR或SPSR)


MRS指令用于将程序状态寄存器的内容传送到通用寄存器中。


该指令一般用在以下几种情况:

  1. 当需要改变程序状态寄存器的内容时,可用MRS将程序状态寄存器的内容读入通用寄存器,修改后再写回程序状态寄存器。

  2. 当在异常处理或进程切换时,需要保存程序状态寄存器的值,可先用该指令读出程序状态寄存器的值,然后保存。
    如:

MRS R0,CPSR ;传送CPSR的内容到R0

MRS R0,SPSR ;传送SPSR的内容到R0


MSR

MSR{条件} 程序状态寄存器(CPSR或SPSR)_<域>,操作数


MSR指令用于将操作数的内容传送到程序状态寄存器的特定域中。其中,操作数可以为通用寄存器或立即数。<域>用于设置程序状态寄存器中需要操作的位,32位的程序状态寄存器可分为4个域:

位[31:24]为条件标志位域,用f表示;

位[23:16]为状态位域,用s表示;

位[15:8]为扩展位域,用x表示;

位[7:0]为控制位域,用c表示;


该指令通常用于恢复或改变程序状态寄存器的内容,在使用时,一般要在MSR指令中指明将要操作的域。
如:

MSR CPSR,R0 ;传送R0的内容到CPSR

MSR SPSR,R0 ;传送R0的内容到SPSR

MSR CPSR_c,R0 ;传送R0的内容到SPSR,但仅仅修改CPSR中的控制位域


应用举例

  1. 使能中断

要是能中断,必须将寄存器CPSR的bit[7]设置为0
PC要将寄存器CPSR的bit[7]设置为0,但是不能影响其他位,所以必须先用msr读取出cpsr的值到通用寄存器Rn(n取值0~8),然后修改bit[7]设置为0,再将该寄存器的值设置到CPSR中。


代码如下:

area reset,code

code32

entry

start

bl enale_irq

enale_irq

mrs r0,cpsr

bic r0,r0,#0x80

msr cpsr_c,r0

mov pc,lr


执行结果:

  1. 第8行【其实第8行还没有执行】:
    8行

  • 当前模式时SVC ,因为开机商店属于reset异常,而该异常会自动进入svc模式

  • CPSR的值是0X000000D3

  1. 9行
    9行

  • mrs r0,cpsr 将cpsr的内容读取到寄存器r0中

  • R0的值为0X000000D3

  1. 10行
    10行

  • bic r0,r0,#0x80 将r0的第7个bit位置设置为0(从低往高数,0开始计数)

  • 寄存器R0的值变成0x00000053

  1. 11行 11行
    CPSR

  • msr cpsr_c,r0 将构造好的值写回CPSR,

  • 此时CPSR的I 位已经为0从而实现了中断使能

  1. 禁止中断
    同理,我们要关闭中断,只需要将CPSR的I位设置为1即可。

area reset,code

code32

entry

start

bl diable_irq

diable_irq

mrs r0,cpsr

orr r0,r0,#0x80

msr cpsr_c,r0

mov pc,lr

end


  1. 设置各模式的栈地址
    要想初始化各个模式的栈地址,必须首先切换到对应的模式,然后再将栈地址设置到寄存器sp即可。

代码:

area reset,code

code32

entry

start

  bl stack_init

stack_init                      ; 栈指针初始化函数;    @undefine_stack                                       

    msr cpsr_c,#0xdb             ; 切换到未定义异常

    ldr sp,=0x34000000      ; 栈指针为内存最高地址,栈为倒生的栈                             ; 栈空间的最后1M 0x34000000~0x33f00000;    @abort_stack                                                      

    msr cpsr_c,#0xd7                ; 切换到终止异常模式

    ldr        sp,=0x33f00000       ; 栈空间为1M,0x33f00000~0x33e00000

 ;   @irq_stack                                                

    msr      cpsr_c,#0xd2            ; 切换到中断模式

    ldr        sp,=0x33e00000        ; 栈空间为1M,0x33e00000~0x33d00000

 ;   @ sys_stack                                               

    msr  cpsr_c,#0xdf              ; 切换到系统模式

    ldr  sp,=0x33d00000           ; 栈空间为1M,0x33d00000~0x33c00000msr  cpsr_c,#0xd3           ; 切换回管理模式

    mov pc,lr

end


结果分析:
我们只分析undef栈的初始化。

  1. 8行
    8行

  • 模式切换前,当前模式时svc模式,CPSR的值是0x000000D3

  • 注意看下SVC和undef模式的SP值都是0

  1. 9行
    9行

  • msr cpsr_c ,# 0xdb 直接对CPSR进行赋值,将当前模式设置为undef模式

  • Current模式看到的LR寄存器值变成了0,因为模式切换成了undef模式,该模式下有自己的LR、SP寄存器

  • SVC模式的私有寄存器SP和LR没有改变

  1. 12行

12行

  • ldr sp,=0x34000000 将常数装载到寄存器sp中,(=表示这是一条伪指令)

  • 注意观察,SVC模式的sp没有变化,undef模式的SP被设置为 0x34000000

其他模式的栈初始化以此类推。


二、寻址方式

处理器根据指令中给出的地址信息来寻找物理地址的方式。

在讲解寻址方式之前,我们首先来看下LDR、STR指令。


1. 加载存储指令

ARM微处理器支持加载/存储指令用于在寄存器和存储器之间传送数据,加载指令用于将存储器中的数据传送到寄存器,存储指令则完成相反的操作。


我们之前讲的寻址方式都是直接对立即数或者寄存器寻址,如果我们想访问外部存储器的某个内存地址或者一些外设的控制器寄存器该如何操作呢?


那就需要进行寄存器间接寻址。如下图所示,访问外存需要通过AHB、APB总线,所以往往需要几个指令周期才能实现1个数据的读写。

访问外存

LDR指令

LDR指令的格式为:

LDR{条件} 目的寄存器,<存储器地址>LDR指令用于从存储器中将一个32位的字数据传送到目的寄存器中。


1) 用于从存储器中读取32位的字数据到通用寄存器,然后对数据进行处理。
2) 当程序计数器PC作为目的寄存器时,指令从存储器中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。
如:

LDR R0,[R1]            ;将存储器地址为R1的字数据读入寄存器R0。

LDR R0,[R1,R2]      ;将存储器地址为R1+R2的字数据读入寄存器R0。

LDR R0,[R1,#8]      ;将存储器地址为R1+8的字数据读入寄存器R0。

LDR R0,[R1,R2] !      ;将存储器地址为R1+R2的字数据读入寄存器R0,并将

                                    ;新地址R1+R2写入R1。

LDR R0,[R1,#8] !      ;将存储器地址为R1+8的字数据读入寄存器R0,并将新

                                    ;地址R1+8写入R1。

LDR R0,[R1],R2       ;将存储器地址为R1的字数据读入寄存器R0,并将新地

                                    ;址R1+R2写入R1。

LDR R0,[R1,R2,LSL#2]!     ;将存储器地址为R1+R2×4的字数据读入寄存器R0,

                                    ;并将新地址R1+R2×4写入R1。

LDR R0,[R1],R2,LSL#2 ;将存储器地址为R1的字数据读入寄存器R0,并将新地

                                    ;址R1+R2×4写入R1。

STR指令

STR指令的格式为:
STR{条件} 源寄存器,<存储器地址>
STR指令用于从源寄存器中将一个32位的字数据传送到存储器中。该指令在程序设计中比较常用,且寻址方式灵活多样,使用方式可参考指令LDR。
如:

STR R0,[R1],#8 ;将R0中的字数据写入以R1为地址的存储器中,并将新地址R1+8写入R1。

STR R0,[R1,#8] ;将R0中的字数据写入以R1+8为地址的存储器中。


LDR/STR指令都可以加B、H、SB、SH的后缀,分别表示加载/存储字节、半字、带符号的字节、带符号的半字。如LDRB指令表示从存储器加载一个字节进寄存器。当使用这些后缀时,要注意所使用的存储器要支持访问的数据宽度。


LDRB指令

LDRB指令的格式为:

LDR{条件}B 目的寄存器,<存储器地址>


LDRB指令用于从存储器中将一个8位的字节数据传送到目的寄存器中,同时将寄存器的高24位清零。该指令通常用于从存储器中读取8位的字节数据到通用寄存器,然后对数据进行处理。

指令示例:


LDRB   R0,[R1]   ;将存储器地址为R1的字节数据读入寄存器R0,并将R0的高24位清零。

LDRB   R0,[R1,#8];将存储器地址为R1+8的字节数据读入寄存器R0,并将R0的高24位清零。


LDRH指令

LDRH指令的格式为:

LDR{条件}H 目的寄存器,<存储器地址>


LDRH指令用于从存储器中将一个16位的半字数据传送到目的寄存器中,同时将寄存器的高16位清零。该指令通常用于从存储器中读取16位的半字数据到通用寄存器,然后对数据进行处理。


指令示例:

LDRH   R0,[R1]  ;将存储器地址为R1的半字数据读入寄存器R0,并将R0的高16位清零。

LDRH  R0,[R1,R2];将存储器地址为R1+R2的半字数据读入寄存器R0,并将R0的高16位清零。


举例

1) STR r0,[r1,#12]

str
如上图所示:

  1. 寄存器r0中的值是0x5,r1中的值是0x200

  2. 将r1的值加上#12,得到地址0x20c

  3. 将r0寄存器里的值发送给该地址对应的内存,即向地址0x20c中赋值0x5

2) STR r0,[r1],#12

str
如上图所示:

  1. 寄存器r0的值是0x5,r1中的值是0x200

  2. 将r0寄存器里的值发送给该r1中的值对应的内存,即向地址0x200中赋值0x5

  3. 将r1的值加上#12并赋值给r1,r1的值就变成了0x20c

扩展:
比如有以下c代码

int *ptr;x = *ptr++;


经过编译器编译,可以将这两行代码编译为一条单指令:

LDR r0, [r1], #4


2. 立即寻址

立即寻址也叫立即数寻址,这是一种特殊的寻址方式,操作数本身就在指令中给出,只要取出指令也就取到了操作数。这个操作数被称为立即数,对应的寻址方式也就叫做立即寻址。

例如以下指令:


Add r0,r0,#1                ;R0=R0+1


在以上两条指令中,第二个源操作数即为立即数,要求以“#”为前缀,对于以十六进制表示的立即数,还要求在“#”后加上“0x”或“&”。


3. 寄存器寻址

利用寄存器中的数值作为操作数,这种寻址方式是各类微处理器经常采用的一种方式,也是一种执行效率较高的寻址方式。


Add R0 , R1,R2         ;R0=R1+R2


该指令的执行效果是将寄存器R1和R2的内容相加,其结果存放在寄存器R0中。


4. 寄存器间接寻址

以寄存器中的值作为操作数的地址,而操作数本身存放在存储器中。例如以下指令:


Add R0,R1,[R2]     ; R0=R1+[R2]LDR R0,[R1]        ; R0=[R1]


在第一条指令中,以寄存器R2的值作为操作数的地址,在存储器中取得一个操作数后与R1相加,结果存入寄存器R0中。第二条指令将以R1的值为地址的存储器中的数据传送到R0中。


5. 基址变址寻址

将寄存器(该寄存器一般称作基址寄存器)的内容与指令中给出的地址偏移量相加,从而得到一个操作数的有效地址:


LDR R0,[R1,#4]       ;R0=[R1+4]LDR R0,[R1,#4] !      ;R0=[R1+4]、R1=R1+4LDR R0,[R1],#4       ;R0=[R1]    、R1=R1+4LDR R0,[R1,R2]       ;R0=[R1+R2]


6. 多寄存器寻址

采用多寄存器寻址方式,一条指令可以完成多个寄存器值的传送。这寻址方式可以用一条指令完成传送最多16个通用寄存器的值。以下指令:


LDMIA  R0,{R1,R2,R3,R4}       ;R1=[R0]    R2=[R0+4]    R3=[R0+8]  R4=[R0+12]


该指令的后缀IA表示在每次执行完加载/存储操作后,R0按字长度增加,因此,指令可将连续存储单元的值传送到R1~R4。


7. 相对寻址

与基址变址寻址方式相类似,相对寻址以程序计数器PC的当前值为基地址,指令中的地址标号作为偏移量,将两者相加之后得到操作数的有效地址。以下程序段完成子程序的调用和返回,跳转指令BL采用了相对寻址方式:


BL NEXT     ;跳转到子程序NEXT处执行

……

NEXT

……

MOV PC,LR   ;从子程序返回


8. 堆栈寻址、批量加载/存储指令

堆栈是一种数据结构,按先进后出(First In Last Out,FILO)的方式工作,使用一个称作堆栈指针的专用寄存器指示当前的操作位置,堆栈指针总是指向栈顶。


批量数据加载/存储指令可以一次在一片连续的存储器单元和多个寄存器之间传送数据。常用的加载存储指令如下:


LDM批量数据加载指令

STM批量数据存储指令


LDM(或STM)指令的格式为:


LDM(或STM){条件}{类型} 基址寄存器{!},寄存器列表{∧}


LDM(或STM)指令用于从由基址寄存器所指示的一片连续存储器到寄存器列表所指示的多个寄存器之间传送数据,该指令的常见用途是将多个寄存器的内容入栈或出栈。其中,{类型}为以下几种情况:


    IA 每次传送后地址加1;

    IB 每次传送前地址加1;

    DA 每次传送后地址减1;

    DB 每次传送前地址减1;

    FD 满递减堆栈;              向低地址方向生长

    ED 空递减堆栈;

    FA 满递增堆栈;              向高地址方向生长

EA 空递增堆栈;

【满堆栈】:堆栈指针SP指向最后压入堆栈的有效数据项

【空堆栈】:堆栈指针指向下一个要放入数据的空位置


【特别注意】


{!}为可选后缀,若选用该后缀,则当数据传送完毕之后,将最后的地址写入基址寄存器,否则基址寄存器的内容不改变。


基址寄存器不允许为R15,寄存器列表可以为R0~R15的任意组合。{∧}为可选后缀,当指令为LDM且寄存器列表中包含R15,选用该后缀时表示:除了正常的数据传送之外,还将SPSR复制到CPSR。同时,该后缀还表示传入或传出的是用户模式下的寄存器,而不是当前模式下的寄存器。


如:


STMFD  R13!,{R0,R4-R12,LR} ;将寄存器列表中的寄存器(R0,R4到R12,LR)存入堆栈,向低地址方向生长。

LDMFD  R13!,{R0,R4-R12,PC} ;将堆栈内容恢复到寄存器(R0,R4到R12,LR)。


【注意】

要压栈的寄存器顺序可以乱序,但是实际压栈和出栈仍然会将寄存器顺序调整后再操作。


9. 举例

例1 数组求和

编写一个ARM汇编程序,累加一个“数组”的所有元素,碰上0时停止。结果放入 r4。


实在步骤如下:

1) 在源文件末尾按如下方式声明“数组”:


array:.word 0x11.word 0x22.word 0

1.

2) 用r0指向“数组”的入口


LDR r0,=array

1.

3) 使用LDR r1,[r0],#4从“数组”中装载数据

4) 累加并放入r4

5) 循环,直到r1为0

6) 停止,进入死循环


代码:


area first, code, readonly                                

code32

entry

start

ldr r0,=array; adr r0,array   ;ADR为小范围的地址读取伪指令

loop

ldr r1,[r0],#4

cmp r1,#0

addne r4,r4,r1

bne loop

stop

b stop ; DCD 伪操作  数据缓冲池技术   

; dcd  机器码

array

dcd 0x11

dcd 0x22

dcd 0


我们看一下最终执行代码在内存中的机器码对比图

由上

由上图可知:


ldr r0,=array,编译器会计算出array标号的地址0x0018,注意该值是偏移当前指令所在内存位置的偏移量,所以该指令最终被翻译成

ldr r0,[pc,#0x001c]


为什么是0x001c而不是0x0018呢?

刚上电时此时pc的值是-4,因为下一条要执行的指令的0x0000这个地址的指令


数组元素的3个值依次存放在0x0018、0x001c、0x001c这3个地址中

ldr r1,[r0],#4每次取出r0指向的内存的值并写入到r1,同时将r0值自加4

bne loop 的loop被编译器计算为地址0x0004

例2 内存数据读写

将某个整型值写入到内存0x40000000 中然后再将其读出。


代码:


  area first, code, readonly

code32

entry

start

    mov r0, #0x10000003

mov r1, #0x40000000  ; SAMSUNG 2410 , 2410 = > sram 0x40000000  0x3fffff00

str r0, [r1]             ;内存单元的地址r1寄存器的内容指示

ldr r2,[r1]stop 

b stop

end


做这个实验之前需要做以下设置。

[1] [2]
关键字:ARM  MRS  MSR  原子操作 引用地址:5. 从0开始学ARM-MRS、MSR、寻址操作、原子操作原理

上一篇:6. 从0开始学ARM-异常及中断处理、异常向量表、swi
下一篇:8. 从0学ARM-内联汇编、混合汇编、ATPCS规则

小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
  • ARM裸机篇--按键中断
    先看看GPOI的输入实验:按键电路图:GPF1管教的功能:EINT1要使用GPF1作为EINT1的功能时,只要将GPFCON的3:2位配置成10就可以了!GPF1先配 ...
  • 网上下的--ARM入门笔记
    简单的介绍打今天起菜鸟的ARM笔记算是开张了,也算给我的这些笔记找个存的地方。为什么要发布出来?也许是大家感兴趣的,其实这些笔记之所 ...
  • 学习ARM开发(23)
    三个任务准备与运行结果下来看看创建任务和任运的栈空间怎么样的,以及运行输出。Made in china by UCSDN(caijunsheng)Lichee 1 0 0 ...
  • 学习ARM开发(22)
    关闭中断与打开中断中断是一种高效的对话机制,但有时并不想程序运行的过程中中断运行,比如正在打印东西,但程序突然中断了,又让另外一个 ...
  • 学习ARM开发(21)
    先要声明任务指针,因为后面需要使用。 任务指针 volatile TASK_TCB* volatile g_pCurrentTask = NULL;volatile TASK_TCB* vol ...
  • 学习ARM开发(20)
  • 学习ARM开发(19)
  • 学习ARM开发(14)
  • 学习ARM开发(15)
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved