引言
ARM寄存器组是ARM架构中至关重要的组成部分,理解这些寄存器有助于深入掌握嵌入式系统的开发。ARM处理器包含16个主要寄存器(R0-R15),每个寄存器有不同的用途,如通用数据存储、堆栈指针、程序计数器等。掌握这些寄存器的功能,不仅能提升代码的优化能力,还能增强对ARM指令集和体系结构的理解,为后续开发奠定坚实基础。
0x04
上一期 ARM嵌入式软件开发 概念 点击 -> 传送门
这是 深入学习ARM架构 专栏的 第四篇文章, 感谢您的观看,欢迎批评指正!
(ARM-Cortex-M3 为例)
寄存器组
下图是 ARM-Cortex-M3 的寄存器组,如果在这之前,您并不了解这些概念, 其实也没啥事,我会尽可能的让您更好理解这些内容。
可能看到上图,您可能会比较懵,那太正常不过了!!! 当初我也是懵着过来的, 但现在我会尽可能的把它讲明白!
再看下图,我们在使用STM32调试时,可以发现页面的左侧有寄存器组的子页面。这得益于Keil强大的调试器功能,使我们我们可以更好的进行Debug等操作。本文我会利用 Keil 的 STM32工程对寄存器组进行深入的刨析。
首先我们要搞清楚的是: 在程序运行过程中,ARM Cortex-M3 的寄存器值会不断发生变化,驱动整个程序的执行。可以理解为,程序的执行实际上是处理器通过不断修改寄存器的值来进行指令处理、数据存储和状态管理的过程。 得先有个概念。
ARM Cortex-M3 的寄存器组可以分为以下几类:
1. 通用寄存器 (R0 - R15)
- R0 - R12:这 13 个寄存器是通用的工作寄存器,可以用于任意目的。在函数调用中,它们用于传递参数和返回值。
- R13 (SP):堆栈指针(Stack Pointer),分为两种:
- MSP (Main Stack Pointer):主堆栈指针,复位后默认使用。
- PSP (Process Stack Pointer):处理堆栈指针,通常用于线程模式。
- R14 (LR):链接寄存器(Link Register),用于存储函数调用返回地址。
- R15 (PC):程序计数器(Program Counter),指向下一条要执行的指令地址。
2. 程序状态寄存器 (PSR)
3. 特殊寄存器
4. 中断和系统控制寄存器
以上的寄存器我们过一遍,知道有哪些寄存器即可, 以后的章节中我还会结合寄存器所对应的功能详细讲解它们的作用,本文主要介绍 SP, LR, PC寄存器 , 它们太重要了!!!
R13 (SP):堆栈指针(Stack Pointer)
在介绍 SP 指针之前, 我们需要知道 什么是堆栈? 堆栈有什么用?
堆栈(Stack) 是一种用于存储数据的内存结构,具有后进先出 (LIFO, Last In First Out) 的特点。在嵌入式系统和程序运行中,堆栈是非常关键的,用来保存函数调用时的局部变量、返回地址、寄存器的状态以及中断处理数据等。
堆栈的主要作用:
-
函数调用管理:
当一个函数被调用时,处理器会将函数的返回地址(LR 寄存器的值)和函数的局部变量等数据存储到堆栈中。当函数执行完成后,通过弹出堆栈中的返回地址,继续执行调用函数时的下一条指令。 -
保存寄存器状态:
在中断或异常处理时,处理器需要保存当前的工作寄存器和其他状态信息。处理器会将这些信息压入堆栈,以便中断处理结束后恢复程序的正常运行。 -
局部变量存储:
函数中的局部变量会存储在堆栈中,当函数调用时,这些变量被压入堆栈,函数执行完后这些变量就会从堆栈中弹出。
堆栈的工作原理:
-
压栈(Push):当有数据需要保存时,数据会被推入(压入)堆栈,堆栈指针(SP, Stack Pointer)向下移动。
-
弹栈(Pop):当需要恢复数据时,从堆栈中弹出最近存入的数据,堆栈指针向上移动。
堆栈指针 (SP):
堆栈指针(SP)是专门用于管理堆栈的寄存器。Cortex-M3 使用两个堆栈指针:
- MSP (Main Stack Pointer):主堆栈指针,通常在处理器复位后使用,用于处理大多数中断和异常。
- PSP (Process Stack Pointer):进程堆栈指针,常用于操作系统中的用户进程。
总结来说,堆栈是处理函数调用、局部变量存储以及中断处理的核心机制,它帮助程序有序管理临时数据,并通过堆栈指针动态调整。 若你还是不要太理解 ,可以再看看其他的贴子 堆栈的讲解
看到这您应该对栈有了一定的理解。接下来我们看看在 STM32 中 栈在哪些地方有所体现。
我们在STM32CubeMX生成初始化代码时, 可以在Project Manager -> Project -> Linker Settings ->Minimun Stack Size
这里定义为 [ 0x400 ] ,也就是 1Kbyte 的大小 。
栈是在 RAM 内存中申请的, 我们可以通过下图知道 RAM 的起始地址,
栈的空间就是 0x20000000 + 0x400
, SP指针最开始指向 0x20000400
如果有数据需要入栈, 栈指针会向下生长, 也就是0x20000400 - data len
栈是C语言运行环境的基本要求, 在汇编代码中第一件事就是去初始化栈, 才去调用 Reset_Handler 程序入口的。栈在RTOS中十分重要,理解好栈的作用,才会明白RTOS的任务调度原理。
想要完整的讲栈, 本文的篇幅 是完全不够的, 我又不能都讲只讲栈, 那大家也难以理解,只能寻求在以后的实验里和大家一起, 一点一点加深对知识点的理解吧~
R14 (LR):链接寄存器(Link Register)
链接寄存器(Link Register,LR),在一些处理器架构(如 ARM)中用来存储返回地址。当调用一个函数或子程序时,LR 保存当前指令的下一条地址,以便函数执行完后能够返回到正确的位置。
R15 (PC):程序计数器(Program Counter)
程序计数器(Program Counter,简称 PC)是计算机中的一个重要寄存器,它用于存储当前正在执行的指令的地址。每当 CPU 读取一条指令后,程序计数器会自动更新,以指向下一条指令的地址。
我们结合以下的例子来理解 LR, PC 这两个寄存器。
左侧寄存组页面 可以看到 PC 和 LR 寄存器的值。
中部Disassembly页面:
-
第一个箭头: 指令机器码在 FLASH 中的地址, 右下角的
Memoly Page : 0x08000906
地址下存的指令和中部显示的一致。 这就是我们最终要写入芯片FLASH ROM区 的数据。 -
第二个箭头: 指令的机器码
-
第三个箭头: 指令汇编码
-
第四个箭头: 函数的地址
-
第五个箭头: 函数名
状态一:
PC -> 0x08000906
LR -> 不关注
HAL_Init();
-> 0x080002B0
状态二:
进入 HAL_Init();
函数
PC -> 0x080002B0
LR -> 0x0800090B
状态三:
退出 HAL_Init();
函数
PC -> 0x0800090A
LR -> 不关注
我们可以由以上 3个状态得知:
最开始 pc 指向 906
进入函数后 指向 2B0, 函数的地址 (LR 存函数返地址: 0x0800090B)
函数运行结束后指向 90A
这里产生一个问题 LR 为什么是 0x0800090B
,而不是 0x08000906 + 4
呢
这是ARM的字节对齐机制, 这个问题和答案都抛出来啦, 希望你再通过其他渠道再去研究一下 ARM的字节对齐是什么回事。
通过本篇文章,我们了解到了以 ARM-Cortex-M3 为例的 寄存器组, 特别是 PC , LR , SP 寄存器,
它们十分重要,我们还会在后续的文章中,继续研究它们。 至于其他的寄存器,它们需要结合一定的场景来介绍,我们先有个概念就好。
由于篇幅有限,我无法给大家在本篇文章中都详细的介绍各个章节,其实每个知识点都可以引申出大量的知识, 但是一篇文章,篇幅实在有限, 在深入学习ARM 专栏中 我们会反复的研究和学习,如果你对此感兴趣,欢迎关注!
下一期 : ARM-Cortex-M3 特殊功能寄存器
连载中, 如果你对本文有兴趣或者意见,我十分希望能看到您的留言和评论!