STM32寄存器映射

1. 寄存器基本原理

寄存器是单片机内部一种特殊的内存,可以实现对单片机各个功能的控制,我们编写程序最终就是去控制寄存器
下面的举例平台为STM32F407ZG

1.1 STM32寄存器分类

大类 小类 说明
内核寄存器 内核相关寄存器 包含R0~R15、xPSR、特殊功能寄存器等
中断控制寄存器 包含NVIC和SCB相关寄存器,NVIC有:ISER、ICER、ISPR、IP等;SCB有:VTOR、AIRCR、SCR等
SysTick寄存器 包含CTRL、LOAD、VAL和CALIB四个寄存器
内存保护寄存器 可选功能,STM32F103没有
调试系统寄存器 ETM、ITM、DWT、IPIU等相关寄存器
外设寄存器 包含GPIO、UART、IIC、SPI、TIM、DMA、ADC、DAC、RTC、I/WWDG、PWR、CAN、USB等各种外设寄存器

2. 寄存器映射

给寄存器地址命名的过程就叫寄存器映射

在 STM32F4 中 0x4002 000C 是 GPIOA_PUPDR 的地址,但是我们直接看 0x4002 000C 并不知道他是谁的地址,虽然我们可以通过查找手册的方式找到,但是这样的方式实在的过于繁琐而且不利于我们后续开发程序,所以我们需要对寄存器地址命名,以便于我们看到新的名字就知道他的作用是什么

2.1 地址映射基本概念

我们首先要知道下面的概念:

  • 总线基地址(BUS_ BASE_ ADDR)
  • 外设基于总线基地址的偏移量(PERIPH_OFFSET)
  • 寄存器相对外设基地址的偏移量(REG_OFFSET)
  • 寄存器地址= BUS_BASE_ADDR + PERIPH_OFFSET + REG_OFFSET

而上面的地址可以很轻易的在手册中找到,下面以寻找 STM32F4 中 GPIOA_PUPDR 的地址为例

2.2 如何通过手册查找寄存器地址

首先我们要明确要寻找的地址是什么东西(总线?外设?寄存器?)。GPIOA_PUPDRGPIOA 的一个寄存器,所以我们在这里要寻找的是一个寄存器,而寄存器是基于外设偏移的,而外设是基于总线偏移的,所以我们需要先找到 GPIOA 是挂载到哪一个总线的哪一个外设上的。
打开《STM32F4xx参考手册》,此手册可以在正点原子资料中免费获取,也可以去 ST 官方下载英文版或者中文社区寻找中文版资料,
2.3 存储器映射中的表2.STM32F4xx寄存器边界地址,找到外设为 GPIOA 的地方,如下图所示:

①:即是我们要寻找的外设
②:可以通过表格看到 GPIOA 是挂载在 AHB1
③:GPIOA 的地址刚好是 AHB1 最低的地址,也就是说相对于 AHB1 的起始地址没有偏移,也即 BUS_ BASE_ ADDR = 0x4002 0000,PERIPH_OFFSET=0
④:点击这个超链接我们可以看到 GPIOA 其他寄存器的详细情况

STM32寄存器映射_第1张图片
点击④会跳转到 7.4.11 GPIO 寄存器映射,在表32.GPIO 寄存器映射和复位值中,寻找 GPIOA_PUPDR 如下图所示:

①:即是我们要寻找的寄存器
②:相对于 GPIOA 基地址的偏移量,也即是 REG_OFFSET=0x0C
③:可以看到这是 32 位的寄存器,也就是每一个寄存器的大小是 4B(后续会用到)
④:注意到 GPIOB_PUPDRGPIOA_PUPDR 甚至 GPIOx_PUPDR(x为A…I)都是同一个偏移量,这就为我们使用一个结构体来初始化所有的GPIOx提供了可能(往后继续看)
STM32寄存器映射_第2张图片
到这里我们已经得到了所有的地址了

BUS_ BASE_ ADDR PERIPH_OFFSET REG_OFFSET
0x4002 0000 0x00 0x0C

把他们拼接起来就得到了 GPIOA_PUPDR = 0x4002 000C
那么我们如何对这个地址的正确性进行验证呢?
理论上可以使用 keil5 的 debug 功能来调试查看寄存器的地址,但是我试过好像有点问题,所以换一种方式来验证。使用正点原子的串口例程,我们使用串口把地址发送到电脑上进行查看,我编写了如下的程序

int main(void)
{
    uint8_t len;
    uint16_t times = 0;
    
    HAL_Init();                             /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7);     /* 设置时钟,168Mhz */
    delay_init(168);                        /* 延时初始化 */
    usart_init(115200);                     /* 串口初始化为115200 */
    led_init();                             /* 初始化LED */
    while(1)
    {
        printf("%p\r\n",&GPIOA->PUPDR);
        delay_ms(1000);
    }
}

收到的结果为
STM32寄存器映射_第3张图片
与我们计算的结果一致,说明我们的计算方式是正确的,下面将会从代码层面寻找他是如何拼接地址的

2.3 寄存器命名的方式

2.3.1 直接操作寄存器

由于 GPIOA_PUPDR 是 32 位的,所以我们需要使用 int 类型的指针来拿他的地址,使用(unsigned int *)把地址强转为指针类型,然后使用 * 访问地址所指向的值,来对寄存器进行赋值(需要注意寄存器是否可读、可写等)

*(unsigned int *)(0x4002 000C)=0XFFFF;
2.3.2 命名后再操作寄存器

使用宏定义对地址进行了一次命名,相较于第一种方式可读性更好

#define GPIOA_PUPDR *(unsigned int *)(0x4002 000C)
GPIOA_PUPDR = 0XFFFF;
2.3.3 使用结构体命名寄存器

下面是 stm32f407xx.h 里面的内容,他使用了结构体的方式来完成寄存器的映射,下面将会讲解为什么可以通过结构体进行映射

typedef struct
{
  __IO uint32_t MODER;    /*!< GPIO port mode register,               Address offset: 0x00      */
  __IO uint32_t OTYPER;   /*!< GPIO port output type register,        Address offset: 0x04      */
  __IO uint32_t OSPEEDR;  /*!< GPIO port output speed register,       Address offset: 0x08      */
  __IO uint32_t PUPDR;    /*!< GPIO port pull-up/pull-down register,  Address offset: 0x0C      */
  __IO uint32_t IDR;      /*!< GPIO port input data register,         Address offset: 0x10      */
  __IO uint32_t ODR;      /*!< GPIO port output data register,        Address offset: 0x14      */
  __IO uint32_t BSRR;     /*!< GPIO port bit set/reset register,      Address offset: 0x18      */
  __IO uint32_t LCKR;     /*!< GPIO port configuration lock register, Address offset: 0x1C      */
  __IO uint32_t AFR[2];   /*!< GPIO alternate function registers,     Address offset: 0x20-0x24 */
} GPIO_TypeDef;

通过 c 语言的基础知识我们知道,结构体变量的名字实际代表的是结构体的首地址,而结构体内部成员是顺序排列的,在上面的手册中可以看到 GPIOA 的寄存器(除了 AFR)都是 32 位寄存器,意味着我们可以在结构体中依次按照寄存器的名字进行成员的定义。例如 GPIOA_PUPDR ,我们在手册中看到基于 GPIOA 的偏移量为 0x0C ,根据上面代码的定义 __IO uint32_t PUPDR; 位于第 4 行,前面有 3 行其他成员的定义,一共占用空间为 32bit x 3 =12B,正好对应了在手册中的偏移量,同时我们也可以在代码的注释中看到地址偏移为0x0C。上一节提到了 GPIOx_PUPDR(x为A…I)都是同一个偏移量,所以当 GPIO_TypeDef 的基地址不同的时候,便可以定义不同组的 IO 口了,这便是使用结构体的好处。

接下来通过源码的方式来寻找 GPIOx_PUPDR 的地址。
打开 stm32f407xx.h ,搜索 GPIOA ,可以看到下图的宏定义,这里的宏定义和上一小节讲的是一样的,但是他还套娃了很多层,
在这里插入图片描述
我们双击 GPIOA_BASE ,按照下图方式操作
STM32寄存器映射_第4张图片
可以得到这个
在这里插入图片描述
我们继续重复上面的步骤
在这里插入图片描述
继续重复
STM32寄存器映射_第5张图片
直到这里我们知道了他的地址结构为

PERIPH_BASE AHB1PERIPH_BASE GPIOA_BASE
0x40000000UL PERIPH_BASE + 0x00020000UL AHB1PERIPH_BASE + 0x0000UL

计算得到 GPIOA_BASE = 0x4002 0000,这个地址通过 #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) 就转化为了 GPIOA 结构体的基地址了,然后我们再加上 GPIOx_PUPDR 相对于 GPIOA 偏移量就得到了 GPIOx_PUPDR = 0x4002 000C
至此寄存器映射就结束了,可以通过类似的方式查找到所有的寄存器地址,多看手册和程序源码。

你可能感兴趣的:(stm32,stm32,单片机,嵌入式硬件)