嵌入式之详解:寄存器


前言

32 位单片机寄存器种类繁多且功能各异,下面以常见的 ARM Cortex - M 系列 32 位单片机为例,详细介绍一些重要的寄存器。

在这里插入图片描述

一、通用寄存器(R0 - R15)

1、R0 - R12

a)功能

这 13 个寄存器是通用目的寄存器,主要用于数据的临时存储、操作数传递以及中间结果的保存。在函数调用过程中,它们可以用来传递参数和返回值。例如,在一个简单的加法函数中,R0 和 R1 可以用来传递两个要相加的操作数,相加的结果可以存放在 R2 中。

b)代码示例(汇编)

将R0和R1中的值相加,结果存放在R2中
ADD R2, R0, R1

2、R13(SP - 堆栈指针)

a)功能

堆栈指针寄存器,用于指示当前堆栈的栈顶位置。在函数调用时,需要将当前的寄存器状态、返回地址等信息压入堆栈保存,函数返回时再从堆栈中弹出这些信息以恢复现场。在 ARM Cortex - M 系列中有两个堆栈指针,主堆栈指针(MSP)和进程堆栈指针(PSP),可以根据需要进行切换。

b)工作流程

1.初始化: 在单片机复位时,会从向量表的第一个条目获取初始栈指针的值,并将其加载到 MSP 寄存器中,完成堆栈指针的初始化。例如:

LDR SP, =_estack ; 将初始栈指针值加载到SP寄存器

2.压栈操作: 当进行函数调用或中断处理时,需要将一些寄存器的值、返回地址等信息压入堆栈保存。压栈操作会使堆栈指针的值减小,指向新的栈顶位置。例如,在 ARM 汇编中,使用PUSH指令进行压栈操作:

    PUSH {R0 - R3} ; 将R0 - R3寄存器的值压入堆栈,SP值减小

3.出栈操作: 当函数返回或中断服务程序执行完毕时,需要从堆栈中弹出之前保存的信息,恢复现场。出栈操作会使堆栈指针的值增加,指向新的栈顶位置。例如,使用POP指令进行出栈操作:

    POP {R0 - R3} ; 从堆栈中弹出值到R0 - R3寄存器,SP值增加

c)示例代码及解释

    MOV R0, #1
    PUSH {R0}     ; 将R0的值压入堆栈,SP值减小
    MOV R0, #2
    POP {R0}      ; 从堆栈中弹出值到R0,恢复原来的值,SP值增加

3、R14(LR - 链接寄存器)

a)功能

在函数调用过程中,链接寄存器(LR,Link Register)存储的是函数返回时要执行的下一条指令的地址,也就是调用函数之后紧跟的那条指令的地址,而不是被调用函数的入口地址。
在大多数基于 ARM 架构的处理器中,当发生函数调用时,程序执行流程会从当前位置跳转到被调用函数的入口地址开始执行。为了在函数执行完毕后能够返回到原来的调用位置继续执行后续代码,就需要保存返回地址。而 LR 寄存器就承担了保存这个返回地址的重要任务。

b)工作流程

1.函数调用:当使用BL(带链接的跳转)指令调用函数时,硬件会自动将下一条指令的地址保存到 LR 中,然后将 PC 的值设置为被调用函数的入口地址,使程序跳转到被调用函数执行。例如:

    BL Function   ; 调用Function函数,下一条指令地址保存到LR

2.函数返回:在被调用函数执行完毕后,使用BX LR指令返回原来的位置。BX LR指令会将 LR 中的返回地址加载到 PC 中,使程序跳转到原来的位置继续执行。例如:

Function:
    ; 函数体
    BX LR        ; 返回原来的位置

3.综合示例
例如:

    MOV R0, #10      ; 将立即数10赋值给寄存器R0
    BL func          ; 调用函数func,此时LR寄存器会被自动设置为下一条指令的地址
    ADD R1, R0, #2   ; 函数返回后执行的指令

func:
    ADD R0, R0, #1   ; 函数func内部的操作
    BX LR            ; 函数返回,跳转到LR寄存器所保存的地址继续执行

在执行BL func指令时,BL(Branch with Link)是带链接的分支指令,它会完成两个操作:
1)首先将程序计数器(PC)的值加 4(因为在 ARM 架构中,指令是按字对齐的,每条指令占 4 个字节),得到调用函数之后紧跟的那条指(即ADD R1, R0, #2)的地址。然后将这个地址保存到 LR 寄存器中。最后将程序跳转到func函数的入口地址开始执行。
2)在func函数执行完毕后,执行BX LR指令,BX(Branch and Exchange)是分支并交换指令,它会将程序跳转到 LR 寄存器所保存的地址(也就是ADD R1, R0, #2指令的地址)继续执行,从而实现函数的返回。

4、R15(PC - 程序计数器)

a)功能

程序计数器,它存储了下一条要执行的指令的地址。在程序顺序执行时,PC 的值会自动递增,指向下一条指令;当遇到跳转指令(如B、BL等)时,PC 的值会被修改为跳转目标地址,从而实现程序的跳转。

b)工作流程

1.顺序执行:在程序正常顺序执行时,每执行完一条指令,PC 的值会自动增加,增加的幅度取决于指令的长度。例如,在 ARM Cortex - M 系列中,Thumb 指令集的指令长度通常为 16 位或 32 位,执行完一条 16 位指令后,PC 的值会加 2;执行完一条 32 位指令后,PC 的值会加 4。这样,PC 就指向下一条要执行的指令地址。

2.跳转执行:当程序遇到跳转指令(如B、BL、BX等)时,PC 的值会被修改为跳转目标地址。例如,B指令用于无条件跳转,执行B label指令时,PC 的值会被设置为label所代表的地址,从而使程序跳转到该地址继续执行。

3.中断处理:当中断发生时,硬件会自动保存当前 PC 的值到堆栈中,然后将 PC 的值设置为中断服务程序的入口地址,从而使程序跳转到中断服务程序执行。当中断服务程序执行完毕后,会从堆栈中恢复之前保存的 PC 值,使程序回到原来的位置继续执行。

c)示例代码及解释

    MOV R0, #1     ; 执行这条指令后,PC自动加2或4
    B Label        ; 跳转到Label处,PC被设置为Label的地址
Label:
    MOV R1, #2

5、R13.R14.R15的工作流程示例讲解

下面以一个简单的 C 语言程序调用示例,详细描述程序计数器(PC)、堆栈指针(SP)以及链接寄存器(LR)的工作流程。假设我们有如下 C 语言代码:

#include <stdio.h>

// 被调用函数
int add(int a, int b) {
    return a + b;
}

// 主函数
int main() {
    int x = 3;
    int y = 5;
    int result = add(x, y);
    printf("The result is: %d\n", result);
    return 0;
}

1)程序启动与初始化

  • 程序计数器(PC):单片机上电复位后,PC 被设置为复位向量地址(如 ARM Cortex - M 系列通常是 0x00000000),从这里开始执行启动代码。启动代码会进行一系列初始化操作,之后 PC 会指向 main 函数的入口地址,开始执行 main 函数。
  • 堆栈指针(SP):同样在复位时,SP 会从向量表中获取初始栈指针的值并完成初始化。例如,在启动代码中可能有类似 LDR SP, =_estack 的指令,将栈顶地址加载到 SP 寄存器。
  • 链接寄存器(LR):在程序启动阶段,LR 没有特殊的初始操作,其值是未定义的,直到发生函数调用时才会被使用。

2)进入 main 函数

  • 程序计数器(PC):PC 指向 main 函数的第一条指令地址,开始顺序执行 main 函数内的代码。例如,先执行 int x = 3; 和 int y = 5; 这些局部变量的初始化操作,每执行一条指令,PC 会根据指令长度自动递增(Thumb 指令集下,16 位指令 PC 加 2,32 位指令 PC 加 4)。
  • 堆栈指针(SP):在 main 函数内部,SP 指向当前堆栈的栈顶。随着局部变量的创建,SP 可能会根据编译器的实现进行相应的调整,以预留空间给这些局部变量。不过在简单的情况下,局部变量可能直接存放在寄存器中,不一定会立即影响 SP。
  • 链接寄存器(LR):在 main 函数顺序执行时,LR 未被使用,其值保持不变。

3)调用 add 函数

  • 程序计数器(PC):当执行到 int result = add(x, y); 时,会生成一条 BL(带链接的跳转)指令。执行这条指令时,PC 会被设置为 add 函数的入口地址,从而跳转到 add 函数开始执行。
  • 堆栈指针(SP):在调用 add 函数之前,可能需要将一些寄存器的值压入堆栈保存(根据调用约定)。例如,使用 PUSH 指令将部分寄存器(如传递参数的寄存器)的值压入堆栈,此时 SP 的值会减小,指向新的栈顶位置。假设使用 PUSH {R0, R1} 指令,SP 会减去 8(假设每个寄存器 4 字节)。
  • 链接寄存器(LR):执行 BL 指令时,硬件会自动将 BL 指令的下一条指令地址保存到 LR 中。这个地址就是 add 函数执行完毕后需要返回的地址。

4)执行 add 函数

  • 程序计数器(PC):PC 在 add 函数内部顺序执行代码,每执行一条指令,PC 按指令长度自动递增。例如,执行 return a + b; 等指令。
  • 堆栈指针(SP):在 add 函数内部,如果有局部变量需要存储在堆栈中,SP 会进一步调整。例如,若 add 函数中有临时变量,编译器可能会分配堆栈空间,SP 会再次减小。当函数执行过程中不需要额外的堆栈空间时,SP 保持不变。
  • 链接寄存器(LR):LR 一直保存着 add 函数执行完毕后需要返回的地址,在 add 函数执行期间,LR 的值不会被修改(除非函数内部手动修改,但这不符合正常的函数调用逻辑)。

5)从 add 函数返回

  • 程序计数器(PC):当 add 函数执行到返回语句时,会执行 BX LR 指令。这条指令会将 LR 中保存的返回地址加载到 PC 中,PC 就指向了 main 函数中 BL 指令的下一条指令,从而回到 main 函数继续执行。
  • 堆栈指针(SP):在返回之前,需要恢复之前压入堆栈的寄存器值,使用 POP 指令将寄存器值从堆栈中弹出。例如,执行 POP {R0, R1} 指令,SP 的值会增加 8,恢复到调用 add 函数之前的位置。
  • 链接寄存器(LR):BX LR 指令执行后,LR 的使命完成,其值在后续程序执行中可能会被其他函数调用所覆盖。

6)继续执行 main 函数

  • 程序计数器(PC):PC 继续顺序执行 main 函数中 add 函数调用之后的代码,如 printf 语句等,每执行一条指令,PC 按指令长度自动递增。
  • 堆栈指针(SP):在 main 函数后续执行过程中,SP 可能会根据需要进行调整,例如在 printf 函数调用时可能会再次进行压栈和出栈操作。
  • 链接寄存器(LR):如果 main 函数后续还有其他函数调用,LR 会再次被用于保存返回地址,重复上述函数调用和返回的过程。

总结: 通过这个示例可以看到,程序计数器控制着程序的执行流程,堆栈指针管理着堆栈的使用,链接寄存器则在函数调用和返回过程中起着关键的桥梁作用,三者相互配合,确保程序能够正确地执行和完成函数调用。

二、 特殊功能寄存器

1、程序状态寄存器(PSR)

a)功能

程序状态寄存器用于保存程序运行过程中的各种状态标志,如负数标志(N)、零标志(Z)、进位标志(C)、溢出标志(V)等。这些标志位可以反映指令执行后的结果状态,用于程序的条件判断和分支控制。在 ARM Cortex - M 系列中,PSR 又分为 APSR(应用程序状态寄存器)、IPSR(中断程序状态寄存器)和 EPSR(执行程序状态寄存器)。

b)示例代码

; 判断R0中的值是否为0
CMP R0, #0 ; 比较R0和0
BEQ zero_case ; 如果R0等于0,跳转到zero_case
...
zero_case:
; 处理R0为0的情况

2、控制寄存器(如 SCB - 系统控制块寄存器)

a)功能

系统控制块寄存器用于配置和控制单片机的系统功能,如向量表重定位、中断优先级分组等。其中,SCB->VTOR寄存器用于设置向量表的起始地址,通过修改这个寄存器的值,可以将向量表重定位到 SRAM 中,提高中断响应速度。

b)示例代码

// 将向量表重定位到0x20000000
SCB->VTOR = 0x20000000;

3. 中断控制寄存器(如 NVIC - 嵌套向量中断控制器寄存器)

a)功能

嵌套向量中断控制器寄存器用于管理和控制中断,包括中断使能、中断优先级设置等。例如,NVIC->ISER寄存器用于使能中断,NVIC->IP寄存器用于设置中断优先级。

b)示例代码

// 使能中断号为10的中断
NVIC->ISER[10 / 32] = (1 << (10 % 32));
// 设置中断号为10的中断优先级为2
NVIC->IP[10] = 2;

4. 定时器 / 计数器寄存器(如 SysTick 寄存器)

a)功能

32 位单片机通常集成了丰富的外设,如串口、SPI、I2C 等,每个外设都有相应的控制寄存器用于配置和控制外设的工作模式、数据传输等。例如,串口的控制寄存器可以设置波特率、数据位长度、停止位长度等参数。

b)示例代码

// 配置SysTick定时器产生1ms中断
SysTick->LOAD = SystemCoreClock / 1000 - 1; // 设置重装载值
SysTick->VAL = 0; // 清空当前值寄存器
SysTick->CTRL = SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk; // 使能定时器和中断

5. 外设控制寄存器

a)功能

32 位单片机通常集成了丰富的外设,如串口、SPI、I2C 等,每个外设都有相应的控制寄存器用于配置和控制外设的工作模式、数据传输等。例如,串口的控制寄存器可以设置波特率、数据位长度、停止位长度等参数。

b)示例代码

// 配置USART1的波特率为115200
USART1->BRR = SystemCoreClock / 115200;
// 使能USART1的发送和接收功能
USART1->CR1 |= USART_CR1_TE | USART_CR1_RE;

总结

这些寄存器在 32 位单片机的编程中起着至关重要的作用,通过合理配置和使用这些寄存器,可以实现各种复杂的功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

&春风有信

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值