正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-6.3

ops/2024/11/14 2:05:51/

 前言:

本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM(MX6U)裸机篇”视频的学习笔记,在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。

引用:

正点原子IMX6U仓库 (GuangzhouXingyi) - Gitee.com

《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》第 8.1 章

《正点原子资料_A盘/02开发板原理图/IMX6ULL_MINI_V2.2(Mini底板原理图).pdf》

  • 资料盘 开发板资料链接: https://pan.baidu.com/s/1j5Jzbdx9i-g0cWIi3wf2XA 提取码:ag1u


正文:

本文是 “正点原子[第二期]Linux之ARM(MX6U)裸机篇--第6.3讲” 的读书笔记。

1. Contex-A7 寄存器介绍

本节介绍 ARM Contex-A 的内核寄存器,注意不是芯片的外设寄存器,本节主要参考 《ARM Contex-A(armV7)编程手册V4.0.pdf》第三章 ARM Processor Modes And Registers。

ARM构架提供了 16 个 32 位通用寄存器(R0~R15)供软件使用,前 15 个寄存器(R0~R14)可以用作通用寄存器,R15 是程序计数器 PC (Program Counter),用来保存将要执行的指令。ARM 还提供了一个当前程序状态寄存器 CPSR (Current Program Status Register) 和一个备份程序状态寄存器 SPSR (S Program Status Register),SPSR 寄存器就是 CPSR 寄存器的备份。这18个寄存器如图 6.3.1 所示。

上一小节我们讲到 ARM Contex-A7 有9种运行模式,每一种运行模式都有一组与之对应的寄存器组。每一种模式可见的寄存器包括15个通用寄存器(R0~R14),一两个状态寄存器,和一个程序计数器PC。在这些寄存器中,有些是所有模式公用的同一个物理寄存器,有一些是个模式自己拥有的,各个模式所拥有的寄存器如下表所示。

在上图中浅色字体的是与 User 模式所共有的寄存器,蓝绿色背景的是各个模式所独有的寄存器。可以看出,在所有模式总,低寄存器组(R0~R7)是共享同一组物理寄存器的,只是一些高寄存器组在不同的模式下有自己独有的寄存器,比如 FIQ 模式下 R8~R14 是独立的物理寄存器。假如某个程序在 FIQ 模式下访问 R13 寄存器,那它实际访问的是寄存器 R13_fiq,如果程序在 SVC  模式下访问 R13 寄存器,那它实际访问的是寄存器R13_svc 。总结一下,ARM Contex-A 内核的寄存器组组成如下:

  1. 34个通用寄存器,包括R15 程序计数器(PC),这些寄存器是32位的。
  2. 8个状态寄存器
  3. HYP模式下一个独有的 ELR_Hyp寄存器
1.1 通用寄存器

R0-R15 就是通用寄存器,通用寄存器可以分为一下3类

  1. 未备份寄存器,即 R0~R7
  2. 备份寄存器,即 R8~R14
  3. 程序计数器PC,即 R15
1.1.1 未备份寄存器

未备份寄存器指的是 R0~R7 这8个寄存器,因为在所有的处理器模式下这个8个寄存器都是同一个物理寄存器,在不同模式下下,这8个寄存器的数据就会被破坏。所以这8个寄存器没有被用作特殊用途。

1.1.2. 备份寄存器

备份寄存器中的 R8 ~ R12 这 5 个寄存器有两种物理寄存器,在快速中断模式下 FIQ 它们对应着 Rx_irq(x=8~12)物理寄存器,其它模式下对应着 Rx(8~12)物理寄存器。FIQ是快速中断模式,看名字就知道这个中断模式要求快速执行。FIQ模式下中断处理程序可以使用 R8 ~ R12 寄存器,因为 FIQ 模式下下 R8~R12 是独立的,因此中断可以不用执行保存和恢复中断现场的指令,从而加速中断的执行过程。

备份寄存器 R13 一共8个物理寄存器,其中一个是用户模式(User)和系统模式(Sys)公用的,剩下一个分别对应7种不同的模式。R13 也叫做 SP (Stack Pointer),用来做栈指针。基本上每种模式都有一个自己的R13寄存器,应用程序会初始化R13,使其指向该模式专用的栈地址,这就是常说的初始化SP指针。

备份寄存器R14一共有7个物理寄存器,其中一个是用户模式(User)和系统模式(Sys)和超级监视模式(Hyp)所共有的,剩下的6中分别对应着6中不同的模式。R14也称为连接寄存器(LR),LR寄存器在ARM中的主要用途有以下2种:

  1. 每种处理器模式使用R14(LR)来存放当前子程序的返回地址,如果使用 BL 或则 BLX 来调用子函数的话,R14(LR)用来存放当前函数的返回地址,在子函数中,将R14(LR)的值赋值给R15(PC)即可完成子函数的返回,比如在子程序中使用如下代码:
    MOV PC, LR @寄存器LR中的值赋值给PC,实现跳转
    或者可以在子程序入口将LR入栈:
    PUSH {LR} @将LR寄存器入栈
    在子程序的最后出栈即可
    POP {PC}   @将上面压栈的LR寄存器出栈给PC寄存器,严格意义上来讲应该是将@LR-4 赋
                       @赋值给PC,应为是3级流水线,这里只是演示代码
  2. 当异常发生时,该异常模式对应的R14寄存器被设置成该异常模式将要返回的地址,R14也可以当做普通寄存器使用。
1.1.3. 程序计数器R15

程序计数器R15也叫做PC,R15保存着当前正在执行的指令地址加8字节,这是因为ARM的流水线机制导致的。ARM处理器3级流水线:取指->译码->执行,这3级流水香循环执行,比如当前正在执行第一条指定的同时,也对第二条指令译码,第三条指令也同时被去除存放到 R15 (PC)中。我们喜欢以当前你正在执行的指令作为参考点,也就是以第一条执行为参考点,那么 R15(PC)中存放就是第三条指令,换句话说R15(PC)总是指向当前正在执行的指令地址再加上2条指令的地址。对于32位的ARM处理器,每条指令时4个字节,所以
R15(PC)值 = 当前执行的程序位置 + 8 个字节

1.2 程序状态寄存器

所有的处理器模式都公用一个 CPSR (Current Program State Register)物理寄存器,因此 CPSR 可以在任何模式下访问。CPSR 是当前程序状态寄存器,该寄存器包含了条件标志位,当前处理器模式标志等一些状态为以及一些控制位。所有的处理器模式都公用一个CPSR必然会导致冲突,为此,除了 User 和 Sys 这两个模式以外,其它7个模式都配备了一个专用的物理状态寄存器,叫做 SPSR (备份程序状态寄存器),当特定的异常中断发生时,SPSR寄存器用来保存当前策划给你续状态寄存器(CPSR)的值,当异常退出以后可以用SPSR中保存的值来回复CPSR。

因为User和Sys两个模式不是异常模式,所以并没有配置SPSR,因此不能再User和Sys模式下访问SPSR,会导致不可预知的结果。由于SRSR是CPSR的备份,因此SPSR和CPSR的寄存器结构相同,如下图所示

位位置功能
N(bit31)当两个补码表示的有符号整数运算的时候,N=1表示运算结果为负数,N=0表示结果为正数。
Z(bit30)Z=1表示运算结果为零,Z=0表示运算结果不为0,对于CMP指令,Z=1表示比较的两个数大小相等。
C(bit29)

在加法指令中,当结果产生了进位,则C=1,表示无符号数运算发生了上溢,其它结果C=0。

在加法指令中,当运算中发生借位,则C=0,表示无符号运算发生下溢,其它情况c=1。

对于包含一位操作的非加/减法运算指令,C中包含最后一次溢出的位的数值,对于其它非加/减法运算指令,C位的值通常不收影响。

V(bit28)对于加/减法运算指令,当操作数和运算结果表示为二进制的补码表示的带符号数时,V=1表示符号位溢出,通常其它不会影响V位。
Q(bit27)仅ARM V5TE_j 构架支持,表示饱和状态,Q=1表示累积饱和,Q=0表示累积不饱和。
IT[1:0](bit26:25)和 IT[7:2] (bit15:bit10)一起表示IT[7:0],作为IF_THEN指令状态。
J(bit24)仅ARM V5TE_j 构架支持,J=1 表示处于 Jazelle 状态,此位通常和 T(bit5)位一起表示当前所使用的指令集,如表 6.3.2.1 所示:
GE[3:0](bit19:16)SIMD指令有效,大于或等于
IT[7:2](bit15:10)参考IT[1:0]
E(bit9)大小端控制位,E=1表示大端,E=0 表示小端
A(bit8)禁止异步中断为,A=1表示禁止异步中断
I(bit7)I=1 禁止IRQ,I=0使能IRQ
F(bit6)F=1 禁止FIRQ,F=0 使能 FIRQ
T(bit5)控制指令执行状态,表名指令时ARM指令还是Thumb指令,通常和J(bit24)一起表示指令类型,参考J(bit24)位。
M[4:0]处理器模式位,含义如表 6.3.2.2 所示

M[4:0]处理器模式
10000User模式
10001FIQ模式
10010IRQ模式
10011Supervisor (SVC)模式
10110Monitor(MON)模式
10111Abort(ABT)模式
11010Hyper(HYP)模式
11011Undef(UND)模式
11111System(SYS)模式

表 6.3.2.2 


 

2. ARM 汇编语言简介

在进行嵌入式Linux开发的时候绝对要掌握基本的ARM汇编,因为 Cortex-A 芯片一上电 SP 指针还没有初始化,C环境还没准备好,所以肯定不能运行C代码,必须先用汇编语言设置好C环境,比如初始化DDR,设置SP指针等等,当汇编把C环境设置好了以后才可以运行C代码。所以Contex-A一开始肯定是汇编代码。本章我们只讲解最常用的一些指令,满足我们后续学习即可。

I.MX6U-ALPHA/Mini 使用的是NXP的I.MX6UL芯片,这是一款 Contex-A7 内核的芯片,所以我们要讲的是 Cortex-A 汇编指令。为此我们需要参考两份 Contex-A 内核有关的文档:

  • 《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》
  • 《ARM CortexA(armV7)编程手册 V4.0.pdf》

第一份文档主要讲解 ARMv7-A 和 ARMv7-R 指令集的开发,Contex-A7 使用的是 ARMv7-A 指令集。第二份文档主要讲解 Cortex-A(armV7)编程的,这两份文档是学习Cortex-A不可或缺的文档。在《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》的 A4 章详细的讲解了 Cortex-A 的汇编指令,要想系统的学习 Cortex-A的指令就要认真的阅读 A4 章节。

对于Cortex-A芯片来讲,大部分芯片在上电以后C语言环境还没有准备好,所以第一行程序肯定是汇编,至于要写多少汇编程序,那就看一能在哪一步把C语言环境准备好。所谓的C语言环境就是保证C语言能够正常运行。C语言的函数调用涉及到出栈入栈,出栈入栈就要对堆栈进行操作,所谓的堆栈其实就是一段内存,这段内存比较特殊,由SP指针访问,SP指针指向栈顶。芯片一上电SP指针还没有初始化,所以C语言没法执行,对于有些芯片还需要初始化DDR,因为芯片本身没有RAM,或者内部RAM不开放给用户使用,用户代码需要再DDR中运行,因此一开始要用汇编来初始化DDR控制器。后面学习Uboot和Linux内核的时候汇编是必须要会的,是不是觉得好难啊?还要会汇编!前面都说了只是在芯片上电以后用汇编来初始化一个外设,不会涉及到复杂的代码,而且用到的指令都是很简单的,用到就是那么十几个指令。所以,不要看到汇编就觉得复杂,打击学习信心。

2.1 GNU汇编语法

不同的汇编器其汇编的语法就有一些小的差别。我们要编写的是ARM汇编,编译器使用的是GCC交叉编译器,所以我们的汇编代码要符合 GNU语法。

GNU汇编语法适用于所有的架构,并不是ARM独享的,GNU汇编由一系列的语句组成,每行一条语句,每条语句有三个可选的部分:

label: instruction @comment

label: 即标号,表示地址位置,有些指令前面可能会有标号,这样就可以通过这个标号得到指令的地址,标号也可以用来表示数据地址。注意 label 后面的 “:”,任何以“:” 结尾的标识符都会被识别为一个标号。

instruction: 即指令,也就是汇编指令或伪指令。

@符号,表示后面是注释,就和C语言中的的"/*"和"*/"一样,其实GNU汇编文件中也可以使用"/*"和"*/" 来进行注释。

comment: 就是注释内容

比如如下代码:

add:MOVS R0, #0X12 @设置R0=0x12

上面代码中的“add”就是标号,“MOVRS R0,#0x12”就是指令,最后的“ @设置R0=0x12”就是注释。

注意!ARM中的指令,伪指令,伪操作,寄存器等可以全部使用大写,也可以全部使用小写,但不能大小写混用。

用户也可以使用 .section 伪操作来定义一个段,汇编系统预留了一些段名:

  • .text 表示代码段
  • .data 初始化的数据段
  • .bss 未初始化数据段
  • .rodata 只读数据

我们当然可以自己使用 .section 来定义一个段,每个段以段名开始,以下一段或者文件结尾结束,比如:

.section .testsection @定义一个 .testsectjion 段

汇编程序的默认入口标志是 _start,不过我们可以在链接脚本中使用 ENTRY 来指明其它的入口点,西面就是使用 _start 作为入口标号。

.global _start_start:ldr r0, =0x12 @r0=0x12

上面的代码中 .global 是伪操作,表示 _start 是一个全局符号,类似C语言里面的全局变量一样,常见的伪操作有:

  • .byte         定义单字节数据,比如 .byte 0x12
  • .short        定义双字节数据,比如 .short 0x1234
  • .long          定义一个4字节数据,比如 .long 0x12345678
  • .equ           赋值语句,格式为: .equ 变量名,表达式,比如 .equ num, 0x12,表示 num=0x12
  • .align         数据字节对齐,比如 .align 4 表示 4字节对齐
  • .end           表示源文件结束
  • .global        定义一个全局符号,格式为 .global symbol,比如,.global _start

GNU 汇编还有其他的伪操作,但是最常见的就是上面这些,如果想详细了解全部的伪操作,可以参考《ARM Cortex-A(armV7)编程手册V4.0.pdf》的第57页。

GNU汇编同样支持函数,函数个数如下:

函数名:函数体返回语句

GNU 汇编函数的返回语句不是必须得,如下代码就是用GNU汇编编写的 Cortex-A7 中断服务函数:

//示例代码 7.1.1.1 汇编函数定义/* 未定义中断 */
Undefined_Handler:ldr r0, =Undefined_Handlerbx r0/* SVC 中断 */
SVN_Handler:ldr r0, =SVC_Handlerbx r0/* 预取终止中断 */
PerfAbort_Handler:ldr r0, =PerfAbort_Handlerbx r0
  • 上述代码中定义了3个汇编函数: Undefined_Handler, SVC_Handler, 和 PerfAbort_Handler。以函数 Underfined_Handler 为例我们来看一下汇编函数组成,Underfined_Handler 就是函数名,"ldr r0, =Undefined_Handler" 是函数体,"bx r0" 是函数返回语句,"bx" 是返回指令,函数返回语句不是必须得。
2.2 Cortex-A7 常用汇编指令

本节我们将介绍一些常用的 Corex-A7 汇编指令,如果想系统的了解  Corex-A7 的所有汇编指令请参考《ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition.pdf》的A4章节。

2.2.1 处理器内部数据传送指令

使用处理器做的最多的事情就是在处理器内部来回的传送数据,常见的操作有:

  • 将数据从一个寄存器传送到另一个寄存器
  • 将数据从一个寄存器传送到特殊寄存器,如 CPSR 和 SPSR 寄存器
  • 将立即数传送到寄存器

数据传送常用的指令有三个: MOV,MRS 和 MSR,这三个指令的用法如下表所示

指令目的描述
MOVR0R1将R1里面的数据复制到R0中
MRSR0CPSR将特殊寄存器CPSR里面的数据复制到R0
MSRCPSRR1将R1里面的数据复制到特殊寄存器CPSR里

分别来详细介绍一下如何使用这3个指令:

1. MOV 指令

MOV指令用来将一个数据从给一个寄存器拷贝到另外一个寄存器,或者将一个立即数传递到寄存器里面,使用示例如下:

MOV R0, R1        @将寄存器R1里的数据传送到R0,即 R0=R1
MOV R0, #0x12     @将立即数0x12 传递给R0寄存器,即R0=0x12

2.MRS 指令

MRS指令用来将特殊寄存器(如CPSR和SPSR)中的数据传送给通用寄存器,要读取特殊寄存器的数据智能使用MRS指令,使用示例如下

MRS R0,CPSR        @将特殊寄存器CPSR里面的数据传送给R0,即R0=CPSR

3.MSR指令

MSR指令和MRS指令正好相反,MSR指令用来将普通寄存器里的数据传递给特殊寄存器,也就是写特殊寄存器,写特殊寄存器智能使用MSR,使用示例如下

MSR CPSR, R0         @将R0中的数据复制到CPSR中,即CPSR=R0
2.2.2 存储器访问指令

ARM不能直接访问存储器,比如RAM中的数据,I.MX6Ul 中的寄存器就是RAM类型的,我们用汇编配置 I.MX6Ul 寄存器的时候需要借助存储器访问指令,一般先将要配置的值写入到 Rx(0~12)寄存器中,然后借助存储器访问指令将Rx中的数据写到 I.MX6U 寄存器中。读I.MX6U  寄存器也是一样的,只是过程相反。常用的存储器访问指令有两种 LDR 和 STR, 用法如下表:

指令描述
LDR Rd, [Rn,#offset]从存储器 Rn+offset 的位置读取数据存放到Rd中
STD Rd, [Rn,#offset]将Rd中的数据写入到存储器Rn+offset位置

分别来详细的介绍一些如何使用这两个指令,

1. LDR指令

LDR主要用户从存储器加载数据到寄存器Rx中,LDR也可以将一个立即数加载到寄存器Rx中,LDR加载立即数的时候要使用 "=",而不是"#"。在嵌入式开发中,LDR最常用的就是读取CPU的寄存器值,比如 I.MX6U 有个寄存器 GPIO1_GDIR ,其地址为0x0209_c004 ,我们要读取这个寄存器中的数据,示例代码如下:

LDR R0, =0x0209C004    @将寄存器地址0x0209C004 加载到R0中,即R0=0x0209C004
LDR R1, [R0]           @读地址0x0209c004中的数据到R1寄存器

2. STR指令

LDR从存储器读取数据,STR就是将数据写入到寄存器,同样以 I.MX6U 寄存器 GPIO1_GDIR 为例,现在我们要配置 GPIO1_GDIR 的值为 0x20000002 ,示例代码如下:

LDR R0, =0x0209C004    @将寄存器地址0x0209C004加载到R0,即R0=0x0209C004
LDR R1, =0x20000002    @将R1要写入到寄存器的值,即R1=R0==0x20000002
STR R1, [R0]           @将R1的值写入到R0中所保存的地址中

LDR和STR都是按照字(4个字节)进行读取和写入的,也就是操作32位数据,如果按照字节,半字进行操作的话可以在执行“LDR”后面加上B或H,比如按字节操作的指令就是 LDRB 和 STRB,按照半字进行操作的就是 LDRH 和 STRH。

2.2.3 压栈和出栈指令

我们通常会再 A 函数总调用 B 函数,当B函数执行完以后再回到A函数继续执行。要想再调回A函数以后代码能够接着正常运行,那么必选在调到B函数之前将当前处理器状态保存起来(就是保存R0~R15这些寄存器的值),当 B 函数执行完成以后再用前面保存的寄存器值恢复 R0~R15 即可。保存R0~R15寄存器的操作就叫做现场保护,恢复 R0~R15 寄存器的操作就叫做恢复现场。在进行现场保护的时候需要压栈(入栈)操作,恢复现场的时候要进行出栈操作。压栈的指令为PUSH,出栈的指令为POP,PUSH和POP是一种多存储和多加载指令,即可以一次操作多个寄存器数据,它们利用当前栈指针 SP 来生成地址,PUSH 和 POP 的用法如下表所示:

指令描述
PUSH <reg lisg>将寄存器列表入栈
POP <reg list>从栈中恢复寄存器列表

假如我们现在要将 R0~R3 和 R12 这5个寄存器压栈,当前 SP 指针指向 0x8000_0000 ,处理器的堆栈是向下增长的,使用汇编代码如下:

PUSH {R0~R3,R12}    @将R0~R3和R12寄存器压栈

压栈完成后的堆栈如图 7.2.3.1 所示

图 7.2.3.1就是R0~R3,R12进行压栈以后得堆栈示意图,此时的SP指向 0x7FFF_FFEC,假如我们现在要再将 LR 进行压栈,汇编代码如下:

PUSH {LR}    @将LR进行压栈

对 LR 进行压栈完成以后的堆栈模型如图 7.2.3.2 所示

图 7.2.3.2 就是分两步对 R0~R3,R12和 LR 进行压栈以后的堆栈模型,如果我们要出栈的话使用如下的代码:

POP {LR}        @先恢复LR
POP {R0~R3,R12} @再恢复R0~R3,R12

出栈的就只从栈顶,也就是 SP 当前指向的位置开始,地址依次减小来提取堆栈中的数据到要恢复的寄存器列表中。PUSH和POP的另外一种写法是 “STMFD SP!” 和 “LDMFD SP!”,因此上面的汇编代码页可以改写为:

STMFD SP! , {R0~R3,R12}        @R0~R3,R12入栈
STMFD SP! , {LR}               @LR入栈LDMFD SP!, {LR}                @先恢复LR
LDMFD SP!, {R0~R3, R12}        @再恢复R0~R3,R12

STMFD 可以分为两部分: STM 和 FD,同理 LDMFD 也可以分为两部分 LDM 和 FD。看到 STM 和 LDM 有没有觉得似曾相识,前面我们讲了 LDR 和 STR ,这两个是数据加载和存储指令,但是每次智能读写存储器中的一个数据。STM 和 LDM 就是多存储和多加载,可以连续的读写存储器中的多个连续数据。

FD 是 Full Decending 的缩写,即满递减的意思。根据 ATPCS (ARM-Thumb Procedure Call Standard)规则,ARM使用的是FD类型的堆栈,SP指向的是最后一个入栈的数值,堆栈是由高地址向下增长的,也就是前面说的向下增长的堆栈,因此最常用的指令就是 STMFD(STM Full Decending)和 LDMFD(LDM Full Decending)。STM 和 LDM 的指令寄存器列表编号小的对应低地址,编号高的对应高地址。(即PUSH/POP, LDMFD/STMFD 入栈顺序是从右往左入栈)。

2.2.4 跳转指令

有多种跳转指令,比如:

  1. 直接跳转指令,B, BL, BX
  2. 直接向 PC 寄存器里面写数据

上述两种方法都可以完成跳转,但是一般常用的还是 B, BL, BX ,用法如下表

指令描述
B <lable>跳转到 label,如果跳转范围超过了 +/- 2KB 可以指定 B.W <lable>使用32位版本的跳转指令,这样可以得到较大范围的跳转
Bx <Rm>间接跳转指令,跳转到存放于 Rm 中的地址处,并且切换指令集
BL <label>跳转到标号地址,并将访问地址保存在LR中
BLX <Rm>结合 BX 和 BL 的特点,跳转到 Rm 指定的地址,并将返回地址保存在LR中,切换指令集。

我们重点来看一下B和BL指令,因为这两个是我们用的最多的,如果要再汇编中进行函数调用使用的就是B和BL指令。

1. B 指令

这是最简单的跳转指令,B指令会将 PC 寄存器的值设置为跳转目标地址,一旦执行B指令,ARM处理器就会立即跳转到指定的目标地址。如果要调用的函数不会再返回到原来的执行处,那就可以使用 B 指令,如下例所示:

.global _start_start:LDR SP, =0x80200000    @设置栈指针 SP=0x8020_0000B main                 @跳转到 main 函数,main是一个label(标签)

上述代码就是典型的在汇编中初始化C运行环境,然后跳转到C文件的 main 函数执行,上述代码只是初始化了 SP 指针,有些处理器还需要做其他初始化,比如初始化 DDR 等等。因为跳转到 C 文件以后再也不会回到汇编了,所以在第4行直接使用了B指令来完成跳转。

2. BL 指令

BL 指令相比于 B 指令,在跳转之前会再寄存器 LR(R14) 中保存当前 PC 寄存器的值,所以可以通过 LR 寄存器的值重新加载到 PC  来继续从跳转之前的代码处运行,这是子程序调用的一个基本但常用的手段。比如 Cortex-A 处理器的 irq 中断服务函数都是汇编编写的,主要用汇编来实现现场保护和恢复,获取中断号等。但是具体的中断处理过程都是C函数,所以就会存在在汇编中调用C的问题。而且C语言版本的中断处理函数执行万策划给你以后是需要返回到 irq 汇编服务函数,因此还要处理其他的工作,一般是恢复现场。这个时候就不能直接使用 B 指令了,因为B指令一旦跳转就再也不会回来了,这个时候需要使用 BL 指令,示例代码如下:

push {r0,r1}            @保存r0,r1, 压栈
cps #0x13               @进入SVC模式,允许其它中断再次进去bl system_irqhandler    @加载C语言中断处理函数到r2寄存器中cps #0x12               @进入IRQ模式
pop {r0,r1}             @恢复r0,r1,出栈
str r0 [r1, #0x10]      @中断执行完成,写EOIR

上述代码第5行就是执行C语言版本的中断处理函数,当处理完成以后是需要返回来继续执行下面的程序,所以使用了BL指令。

2.2.5 算术运算指令

汇编也可以进行算术运算,比如加减乘除,常用的运算指令如下表所示

指令计算公式备注
ADD Rd, Rn, RmRd = Rn + Rm加法运算,指令为ADD
ADD Rd, Rn, #immedRd = Rn + #immed加法运算,指令为ADD
ADC Rd, Rn, RmRd = Rn + Rm + 进位带进位的加法运算,指令为ADC
ADC Rd, Rn, #immedRd = Rn + #immed + 进位带进位的加法运算,指令为ADC
SUB Rd, Rn, RmRd = Rn - Rm减法
SUB Rd, #immedRd = Rd - #immed减法
SUB Rd, Rn, #immed Rd = Rn - #immed 减法
SBC Rd, Rn, #immedRd = Rn - #immed - 借位带借位的减法
SBC Rd, Rn, RmRd = Rn - Rm - 借位带借位的减法
MUL Rd, Rn, RmRd = Rn * Rm 乘法(32位)
UDIV Rd, Rn, RmRd = Rn/Rm无符号除法
SID Rd, Rn, RmRd = Rn/Rm有符号除法

在嵌入式开发中最常会用的就是加减指令,乘除基本用不到。

2.2.6 逻辑运算指令

我们用 C 语言进行CPU寄存器配置的时候常常需要用到逻辑运算符号,比如 “&” , “| ” 等 逻辑运算符。使用汇编语言的时候也可以使用逻辑运算指令,常用的运算指令如下表所示。

指令计算公式备注
AND Rd, RnRd = Rd & Rn按位与
 
AND Rd, Rn, #immedRd = Rn & #immed
AND Rd, Rn, RmRd = Rn & Rm
ORR Rd, RnRd = Rd | Rn按位或
 
ORR Rd, Rn, #immedRd = Rn | #immed
ORR Rd, Rn, RmRd = Rn | Rm
BIC Rd, RnRd = Rd & (~Rn)位清除
 
BIC Rd, Rn, #immedRd = Rn & (~#immed)
BIC Rd, Rn, RmRd = Rn & (~Rm)
ORN Rd, Rn, #immedRd = Rn | (#immed)按位或非
 
ORN, Rd, Rn, RmRd = Rn | (Rm)
EOR Rd, RnRd = Rd ^ Rn按位异或
 
EOR Rd, Rn, #immedRd = Rn ^ #immed
EOR Rd, Rn, RmRd = Rn ^ Rm

逻辑运算指令都很好理解,后面时候汇编配置 I.MX6Ul 的外设的时候可能会用到,ARM 汇编就讲到这里,本节主要讲解了一些最常用的汇编指令,还有很多不常用的指令没有讲解,但是够我们后续学习使用了。要想详细学习 ARM 的所有指令请参考 《ARM Cortex-A(armV7) 编程手册V4.opdf》和 《ARm Architecutre Refercen Manual ARMv7-A and ARMv7-R edition.pdf》。


http://www.ppmy.cn/ops/22728.html

相关文章

40+ Node.js 常见面试问题 [2024]

今天就开始你的Node.js生涯。在这里&#xff0c;我们探讨了最佳Node.js面试问题和答案&#xff0c;以帮助应届生和经验丰富的候选人获得理想的工作。 Node.js 是许多大公司技术堆栈的重要组成部分&#xff0c;例如 PayPal、Trello、沃尔玛和 NASA。 根据 ZipRecruiter 的数据&…

BTCOIN的革命之路:通过SocialFi重塑全球金融生态系统

BTCOIN的革命之路&#xff1a;通过SocialFi重塑全球金融生态系统 今日&#xff0c;BTCOIN宣布发布WEB3.0论坛引发业内现象级关注&#xff1a;作为一个倡导WEB3.0理念的数字金融平台&#xff0c;在数字货币的波澜壮阔中&#xff0c;BTCOIN以其独特的生态定位和战略愿景&#xff…

NLP transformers - 文本分类

Text classification 文章目录 Text classification加载 IMDb 数据集Preprocess 预处理EvaluateTrainInference 本文翻译自&#xff1a;Text classification https://huggingface.co/docs/transformers/tasks/sequence_classification notebook : https://colab.research.googl…

Nginx 配置 SSL(HTTPS)详解

Nginx作为一款高性能的HTTP和反向代理服务器&#xff0c;自然支持SSL/TLS加密通信。本文将详细介绍如何在Nginx中配置SSL&#xff0c;实现HTTPS的访问。 随着互联网安全性的日益重要&#xff0c;HTTPS协议逐渐成为网站加密通信的标配。Nginx作为一款高性能的HTTP和反向代理服务…

C语言—柔性数组

C99 中&#xff0c;结构中的最后一个元素允许是未知大小的数组&#xff0c;这就叫做『柔性数组』成员。 typedef struct st_type {int i;int a[0];//柔性数组成员 }type_a;有些编译器会报错无法编译可以改成&#xff1a; typedef struct st_type {int i;int a[];//柔性数组成员…

LeetCode 刷题 -- Day 6

今日题目 题目难度备注102. 二叉树的层序遍历 中等一招鲜吃遍天107. 二叉树的层序遍历 II &#xff09;中等199. 二叉树的右视图 中等637. 二叉树的层平均值简单429. N 叉树的层序遍历中等515. 在每个树行中找最大值中等116. 填充每个节点的下一个右侧节点指针中等104. 二叉树…

C语言 | Leetcode C语言题解之第55题跳跃游戏

题目&#xff1a; 题解&#xff1a; #define max(a, b) (((a) > (b)) ? (a) : (b))bool canJump(int* nums, int numsSize){int cover 0;int i;// 只可能获取cover范围中的步数&#xff0c;所以i<coverfor(i 0; i < cover; i) {// 更新cover为从i出发能到达的最大…

flutter开发实战-混淆minifyEnabled及shrinkResources

flutter开发实战-混淆minifyEnabled及shrinkResources 最近开发中&#xff0c;出现了在Debug模式下完全正常&#xff0c;打包build后出现插件代码调用提示未实现。 No implementation found for method login on channel app_plugin 经过查找发现在build apk时候出现了混淆的问…