在学习和使用STM32的过程中和寄存器打交道是必不可免的。今天就大家详细了解一下STM32内部的寄存器。
1.寄存器的概念
寄存器是CPU内部的存储区域,具有高速读写的特点。寄存器既可以暂存指令,也可以存储数据和地址。
因此在存储内容上,寄存器就可以分为指令寄存器,数据寄存器,和地址寄存器。
2.寄存器的分类
1.内核寄存器
-
General-Purpose 简称GPR 通用目的寄存器:用于存储数据和地址,参与算术逻辑运算等操作。在 STM32 中,通用目的寄存器包括 R0-R15 等。这里要注意的是R12-R15只是名义上的通用目的寄存器,实际上并不是通用目的寄存器。其中 R0-R7 是低组寄存器,所有指令都能访问;R8-R12 是高组寄存器,仅支持32 位指令。
-
Stack Pointer 简称SP 堆栈指针寄存器(R13):每一种异常模式都有其自己独立的 R13寄存器,通常指向异常模式所专用的堆栈。不同的模式下都有各自独立的堆栈,用于在程序执行过程中保存临时数据、局部变量等,保证各种模式下程序的状态的完整性。在Cortex-M中采用双堆栈模式,异常采用Main SP(简称MSP),线程采用Process SP(PSP),通过CONTROL[1]进行切换,这种双堆栈模式可以将中断和用户程序隔离开,在一定程度上保护了中断。
-
Link Register 简称LR 连接寄存器(R14):保存子程序返回地址。当使用 BL 或 BLX 指令进行跳转时,跳转指令自动把返回地址放入 R14 中,子程序通过把 R14 复制到程序计数器 PC 来实现返回。当异常发生时,异常模式的 R14 用来保存异常返回地址。
-
Program Counter 简称PC 程序计数器(R15):存放正在执行的指令的地址。在读取时,返回的值是当前指令的地址加上一定的偏移量(这与处理器的架构和流水线设计有关);向 PC 中写数据,会引起一次程序的分支。
2.外设寄存器
-
控制寄存器(xxx_CR):用来控制、配置外设的工作方式,例如 GPIO 端口模式寄存器(GPIOx_MODER),可以配置 GPIO 引脚为输入、输出、模拟等不同的工作模式。
-
状态寄存器(xxx_SR):存储了当前外设的工作状态,例如串口的状态寄存器(USART_SR),可以通过读取该寄存器的某些位来判断串口是否发送完成、是否接收到数据等,这跟标志位有很大关系,可以这么说状态寄存器就是标志位的🏠。
-
数据寄存器(xxx_DR):用于存储外设进行输入输出的数据。比如 GPIO 端口的输入数据寄存器(GPIOx_IDR)用于读取 GPIO 引脚的输入状态,输出数据寄存器(GPIOx_ODR)用于设置 GPIO 引脚的输出状态。
-
位操作寄存器:针对某些需要对单个位进行操作的场景,STM32 提供了位操作寄存器。例如 GPIOx_BSRR(设置 / 清除寄存器),可以对 GPIO 引脚的单个位进行置位和复位操作,方便了对特定引脚位的控制,而不必对整个寄存器进行操作。
-
锁定寄存器:用于锁定某些寄存器的配置,防止意外的修改。比如 GPIO 端口配置锁定寄存器(GPIOx_LCKR),可以在配置完成后锁定 GPIO 的配置,避免误操作改变引脚的配置。
特别注意,这些外设寄存器不是单独置于外设之外的,而是所有外设内部包含共有的。这些外设寄存器只是在功能上进行了大体分类,实际在具体的外设中,比如GPIO,它的内部会具体分出输出速度寄存器,输出类型寄存器等。
3.如何使用寄存器
熟悉C语言的同学一定了解过指针和宏定义。没错,寄存器就是主要依靠指针和宏定义进行操作的。大家都听过的库函数也是ST公司基于指针操作寄存器开发的,样例代码如下。
#define PERIPH_BASE ((uint32_t)0x40000000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
#define RCC ((RCC_TypeDef *) RCC_BASE)
/**
* @brief Reset and Clock Control
*/
typedef struct
{
__IO uint32_t CR;
__IO uint32_t CFGR;
__IO uint32_t CIR;
__IO uint32_t APB2RSTR;
__IO uint32_t APB1RSTR;
__IO uint32_t AHBENR;
__IO uint32_t APB2ENR;
__IO uint32_t APB1ENR;
__IO uint32_t BDCR;
__IO uint32_t CSR;
#ifdef STM32F10X_CL
__IO uint32_t AHBRSTR;
__IO uint32_t CFGR2;
#endif /* STM32F10X_CL */
#if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || defined (STM32F10X_HD_VL)
uint32_t RESERVED0;
__IO uint32_t CFGR2;
#endif /* STM32F10X_LD_VL || STM32F10X_MD_VL || STM32F10X_HD_VL */
} RCC_TypeDef;
/**
* @brief Enables or disables the High Speed APB (APB2) peripheral clock.
* @param RCC_APB2Periph: specifies the APB2 peripheral to gates its clock.
* This parameter can be any combination of the following values:
* @arg RCC_APB2Periph_AFIO, RCC_APB2Periph_GPIOA, RCC_APB2Periph_GPIOB,
* RCC_APB2Periph_GPIOC, RCC_APB2Periph_GPIOD, RCC_APB2Periph_GPIOE,
* RCC_APB2Periph_GPIOF, RCC_APB2Periph_GPIOG, RCC_APB2Periph_ADC1,
* RCC_APB2Periph_ADC2, RCC_APB2Periph_TIM1, RCC_APB2Periph_SPI1,
* RCC_APB2Periph_TIM8, RCC_APB2Periph_USART1, RCC_APB2Periph_ADC3,
* RCC_APB2Periph_TIM15, RCC_APB2Periph_TIM16, RCC_APB2Periph_TIM17,
* RCC_APB2Periph_TIM9, RCC_APB2Periph_TIM10, RCC_APB2Periph_TIM11
* @param NewState: new state of the specified peripheral clock.
* This parameter can be: ENABLE or DISABLE.
* @retval None
*/
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
{
/* Check the parameters */
assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph));
assert_param(IS_FUNCTIONAL_STATE(NewState));
if (NewState != DISABLE)
{
RCC->APB2ENR |= RCC_APB2Periph;
}
else
{
RCC->APB2ENR &= ~RCC_APB2Periph;
}
}
这段代码是外设时钟控制函数。在使用外设前,需要先开启外设时钟,这时候就需要调用该函数开启对应时钟。
4.结尾
至此,你已经对STM32底层逻辑有了基本的认识,希望大家能继续深入STM32的学习,文章有哪些不对的地方欢迎指正。