历史上的今天
返回首页

历史上的今天

今天是:2025年08月05日(星期二)

正在发生

2021年08月05日 | 5. 从0开始学ARM-MRS、MSR、寻址操作、原子操作原理

2021-08-05 来源:eefocus

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

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


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

推荐阅读

史海拾趣

歌尔(Goertek)公司的发展小趣事

机顶盒,全称为数字视频变换盒,是连接电视机与外部信号源的关键设备,其专业性与科普性兼具。从广义上讲,机顶盒泛指一切与电视机连接的网络终端设备,能够接收并转换多种信号源,包括有线电缆、卫星天线、宽带网络及地面广播等,为观众提供丰富多样的视听体验。

在数字电视时代,机顶盒的核心功能在于实现数模转换,即将接收到的数字信号转换为电视机可识别的模拟信号,从而使用户能在模拟电视机上观看高清甚至超高清的数字电视节目。此外,机顶盒还具备强大的增值服务功能,如提供电子节目指南、因特网网页浏览、在线购物、视频点播等,使电视机从单向接收信息的设备转变为互动的智能终端。

随着技术的不断进步,机顶盒正朝着高清化、智能化方向发展。高清、超高清技术的广泛应用,使得机顶盒能够呈现更为细腻的画面和震撼的音效。同时,智能化技术的融入,如语音助手、智能推荐等功能的实现,进一步提升了用户的使用体验。未来,随着5G、虚拟现实和增强现实等技术的不断发展,机顶盒的应用场景将更加广泛,为用户带来更为沉浸式和互动式的观影体验。

综上所述,机顶盒作为连接电视与外部世界的桥梁,不仅丰富了人们的视听生活,更推动了电视产业的数字化转型与发展。

American Power Devices Inc公司的发展小趣事

随着全球市场的不断开放,APD公司意识到要想取得更大的发展,必须走出国门,拓展国际市场。于是,公司开始实施全球化战略,通过设立海外分公司、与当地企业合作等方式,逐步打开了欧洲、亚洲等多个市场。同时,APD还积极参与国际技术交流与合作,不断提升自身的技术水平和市场竞争力。

德艺隆(DEALON)公司的发展小趣事

面对数字化转型和智能化升级的大趋势,德艺隆不甘落后。他们引进先进的生产设备和技术,提高了生产效率和产品质量;同时,公司还加大了对数字化和智能化技术的研发和应用力度,推出了一系列智能化产品解决方案。这些举措不仅提升了公司的竞争力,也为公司的未来发展奠定了坚实的基础。

海芯科技(AVIA)公司的发展小趣事

面对电子行业的快速变化和市场竞争的加剧,海芯科技始终保持着对技术创新的追求和投入。公司不断引进新技术、新工艺和新材料,对现有产品进行升级和改进,同时也在不断探索和研发新的产品和技术。这些技术升级和创新发展不仅提升了公司的核心竞争力,也为公司在未来市场竞争中保持领先地位提供了有力保障。

这五个故事展示了海芯科技在电子行业中的发展历程和取得的成就。通过不断的努力和创新,海芯科技已经逐渐成为了电子行业中的佼佼者,为行业的发展做出了积极的贡献。

微芯(CMOSIC)公司的发展小趣事

随着产品线的逐渐丰富和技术的不断进步,微芯生物开始积极拓展市场。他们与多家国内外医药企业建立了合作关系,将自主研发的药物推向市场。同时,公司也积极参与各类学术会议和展览,提升品牌知名度和市场影响力。通过不懈的努力,微芯生物逐渐获得了市场的认可和信赖。

Hamamatsu公司的发展小趣事

为了进一步提升研发实力和市场竞争力,微芯生物开始了并购之路。他们先后收购了多家在生物医药领域具有优势的企业,包括专注于生物制品生产和销售的企业、创新药物研发企业以及提供全方位生物医药研发生产外包服务的企业等。通过并购整合,微芯生物不仅增强了自身的研发实力和市场占有率,还进一步完善了公司的产业链和战略布局。

问答坊 | AI 解惑

ad/da

本帖最后由 paulhyde 于 2014-9-15 09:12 编辑 ad和da的一些芯片资料 [ 本帖最后由 lxt2006 于 2009-8-29 08:16 编辑 ]  …

查看全部问答>

谁知道sim300的60个引脚的具体功能?

由于做开发,有的引脚可能用不到,但是不知道SIM300各个引脚的具体功能,哪位大侠可告知?…

查看全部问答>

wince6.0 png透明

大家好,想在wince6.0平台上实现PNG图片的显示半透明,该怎么做?望高人指点! 谢谢!…

查看全部问答>

请各位硬件高手帮帮忙

最近我买了个新电脑,非常爱惜。但由于处于好奇,在装了XP的情况下,又装了vista,但不知怎么搞的,没用几天,系统崩溃了,听同学说VISTA虽美观但不稳定,占用的资源也大,所以决定把vista卸了,但不知道怎么卸操作系统,后经多方查找,只要格式化系 ...…

查看全部问答>

WINCE下载问题(NK.BIN > 32M)

定制OS,同时我增加了中文字体支持, 但编译出来的NK.bin超过32M(我已经设置了IMGRAM64=1), NK.bk0有80M左右,但用DWN下载的时候,总是报错, 错误如下: Download BIN file information: ----------------------------------------------- ...…

查看全部问答>

疑惑:小批量产品如何固化用户代码

基于成本,不考虑出厂掩模。st有其他固化代码的方式供选择么?不是光 jtag烧吧 …

查看全部问答>

開關電源供電中,觸摸按鍵出錯了

                                 如果電源用線性電源的話,觸摸按鍵沒有問題;可是在開關電源中,觸摸按鍵就出錯了。現在家電很多都是用開關電源, ...…

查看全部问答>

ucos的很多组件在哪里下载,uctcpip是不是占据了几十K的内存空间啊?呢

各位大拿好: 有两个问题想咨询大家, 第一个:我正在移植uC-TCPIP,在看手册时,发现一个图: 我想问下,这里面的如uC-HTTPS,uC-SMTPS这些组件是在哪里下啊?哪位大拿有全套的啊? 第二个:我把uC-TCPIP放入工程中,发现它占据了data区就有 ...…

查看全部问答>

AVR单片机可以直接控制MOS管而不加驱动吗?

RT,我仅仅想把MOS管当个开关用,而且对时间要求不严格。…

查看全部问答>

MOSFET电路出问题【有波形】

电路如下:3(黄线)、5(蓝线)端波形如下: mosfet怎么关断这么慢啊? …

查看全部问答>