https://blog.csdn.net/weixin_40054234/article/details/115314361
三、寄存器
3.1 什么是寄存器映射
所谓的寄存器映射,实际上是给寄存器的地址赋予一个名称,后续编程的时候我们就无需面对一个抽象的,不好辨别的,以数字为编码的地址量进行操作,可以将其宏定义为一串英文字母,通过操作宏定义进而操作对应的地址
3.2 寄存器的偏移
寄存器的排布并不是没有规律的,实际上同类寄存器它们之间的地址是连续的,比如以GPIOA为例,GPIOA的相关寄存器地址从0x4001 0800 - 0x4001 0BFF(参考手册-存储器映像)也就是说GPIOA的所有寄存器都排布在这些地址范围内,我们要操作的第1个寄存器的地址为0x4001 0800,而后续需要操作的GPIOA寄存器都以这个地址为基础进行偏移,而STM32一个寄存器,需要4个字节32位,也就是说占用了4个地址值,那么也就是说他们的寄存器偏移量为4,我只需要在首地址上加4就可以获得下一个寄存器的地址,以此类推不断加4就可以获取最终所需要的寄存器的地址是而在实际操作中偏移量的基础都以首地址为基础,也就是说以GPIOA为例,其基地址为0x4001 0800,以此为偏移,查看参考手册的GPIO章节可以看到详细说明, 第一个寄存器为端口配置低寄存器,因此偏移地址为0X00,也就是说地址为0x4001 0800
第二个寄存器为端口配置高寄存器,因此偏移地址为0X40,也就是说地址为0x4001 0840
第三个寄存器为端口输入数据寄存器,因此偏移地址为0X80,也就是说地址为0x4001 0880
以此类推,所以寄存器的地址排列是有顺序的,AHB上桥接着APB1与APB2总线,所有的片上外设都挂载在APB1与APB2总线(内核外设不在此处)。 我们查看参考手册可以发现片上外设的首地址为0X4000 0000,
该地址也为APB1的基地址,APB2,AHB在此基础上偏移 , 总结为如下表
可以在参考手册存储器映像章节找到该地址
那么我们只需要根据这些偏移关系就可以很容易的封装映射寄存器。
3.3 标准库函数对寄存器地址的封装
3.3.1 地址的封装
打开stm32f10x.h文件
Copy #define FLASH_BASE ((uint32_t)0x08000000) /*!< FLASH base address in the alias region */
#define SRAM_BASE ((uint32_t)0x20000000) /*!< SRAM base address in the alias region */
#define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
#define SRAM_BB_BASE ((uint32_t)0x22000000) /*!< SRAM base address in the bit-band region */
#define PERIPH_BB_BASE ((uint32_t)0x42000000) /*!< Peripheral base address in the bit-band region */
#define FSMC_R_BASE ((uint32_t)0xA0000000) /*!< FSMC registers base address */
/*!< Peripheral memory map */
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define TIM2_BASE (APB1PERIPH_BASE + 0x0000)
#define TIM3_BASE (APB1PERIPH_BASE + 0x0400)
#define TIM4_BASE (APB1PERIPH_BASE + 0x0800)
#define TIM5_BASE (APB1PERIPH_BASE + 0x0C00)
#define TIM6_BASE (APB1PERIPH_BASE + 0x1000)
#define TIM7_BASE (APB1PERIPH_BASE + 0x1400)
#define TIM12_BASE (APB1PERIPH_BASE + 0x1800)
#define TIM13_BASE (APB1PERIPH_BASE + 0x1C00)
#define TIM14_BASE (APB1PERIPH_BASE + 0x2000)
#define RTC_BASE (APB1PERIPH_BASE + 0x2800)
#define WWDG_BASE (APB1PERIPH_BASE + 0x2C00)
#define IWDG_BASE (APB1PERIPH_BASE + 0x3000)
#define SPI2_BASE (APB1PERIPH_BASE + 0x3800)
可以发现通过宏定义将常用的外设的地址通过APB1,APB2的总线基地址偏移进行了封装,但是这样的宏定义虽然便于我们找到地址,但显然无法将其作为一个可操作的变量写入值,操作寄存器,因此还要将其封装为指针
3.3.2 将寄存器封装为指针
在往下翻到1400行左右,可以看到,为了便于操作,将这些寄存器通过上面的宏定义地址定义为了指针变量,后续就可以通过操作指针操作寄存器。
Copy #define TIM2 ((TIM_TypeDef *) TIM2_BASE)
#define TIM3 ((TIM_TypeDef *) TIM3_BASE)
#define TIM4 ((TIM_TypeDef *) TIM4_BASE)
#define TIM5 ((TIM_TypeDef *) TIM5_BASE)
#define TIM6 ((TIM_TypeDef *) TIM6_BASE)
#define TIM7 ((TIM_TypeDef *) TIM7_BASE)
#define TIM12 ((TIM_TypeDef *) TIM12_BASE)
#define TIM13 ((TIM_TypeDef *) TIM13_BASE)
#define TIM14 ((TIM_TypeDef *) TIM14_BASE)
#define RTC ((RTC_TypeDef *) RTC_BASE)
#define WWDG ((WWDG_TypeDef *) WWDG_BASE)
#define IWDG ((IWDG_TypeDef *) IWDG_BASE)
#define SPI2 ((SPI_TypeDef *) SPI2_BASE)
#define SPI3 ((SPI_TypeDef *) SPI3_BASE)
#define USART2 ((USART_TypeDef *) USART2_BASE)
#define USART3 ((USART_TypeDef *) USART3_BASE)
仔细观察代码,可以发现不同的外设定义的指针类型不同,则是因为不同的外设需要操作的寄存器不同,这里的指针类型通常都是结构体。为什么需要这样设置呢,见下面这一小节
3.3.3 寄存器结构体
一个外设所需要操作的集成器的数量往往不是一个,而我们前面的封装的地址实际上通常都是一个外设的基地址而已,实际上也就是需要操作的第1个寄存器的地址而已,后续需要操作的寄存器正如我们前面所提到的都是在此基础上进行一个偏移,那么既然是有规律的偏移,我们就无需再进行一个个宏定义,为了减少代码的冗余,我们可以通过创建结构体指针进行封装,在实际操作中,正如3.3.2小节,我们将该结构体的指针,此项的地址编写为该外设的基地址,那么由于结构体指针里面,我们定义的成员变量是32位的,也就是说每一个成员变量占4个字节,这样刚好与寄存器的偏移地址量是相互应,那么我们后续只要通过操作该指针的对应成员变量即可实现向该外设的对应寄存器写入值的功能
Copy typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;
3.3.4 以GPIO初始化为例解析封装过程
led.c代码如下
Copy void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE); //使能PB,PE端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0-->PB.5 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIOB.5
GPIO_SetBits(GPIOB,GPIO_Pin_5); //PB.5 输出高
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED1-->PE.5 端口配置, 推挽输出
GPIO_Init(GPIOE, &GPIO_InitStructure); //推挽输出 ,IO口速度为50MHz
GPIO_SetBits(GPIOE,GPIO_Pin_5); //PE.5 输出高
}
3.3.4.1 首先定义一个GPIO_InitTypeDef类的结构体
通过操作该结构体可以,操作GPIO的速度,模式属性。
Copy typedef struct
{
uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef */
GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIOMode_TypeDef */
}GPIO_InitTypeDef;
这样操作只是单纯的操作变量,没有操作寄存器,接着往下看led.c的内容,主要是GPIO_Init(GPIOB, &GPIO_InitStructure); 该函数在stm32f103x_gpio.c文件中定义,代码比较长,只截取以下一部分
Copy void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
uint32_t tmpreg = 0x00, pinmask = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
/*---------------------------- GPIO Mode Configuration -----------------------*/
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
{
/* Check the parameters */
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
/* Output mode */
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
}
/*---------------------------- GPIO CRL Configuration ------------------------*/
/* Configure the eight low port pins */
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
{
tmpreg = GPIOx->CRL;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = ((uint32_t)0x01) << pinpos;
/* Get the port pins position */
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
if (currentpin == pos)
{
pos = pinpos << 2;
/* Clear the corresponding low control register bits */
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
/* Write the mode configuration in the corresponding bits */
tmpreg |= (currentmode << pos);
/* Reset the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
}
else
{
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
}
}
GPIOx->CRL = tmpreg;
}
该函数有两个参数 一个是GPIO_TypeDef 类型指针,参数传入的正是3.3.2小节 宏定义的寄存器指针,第二个参数就是GPIO_InitTypeDef 类型指针,传入的正是GPIO的速度,模式属性。在该函数中会将这些属性解析出对应的值写入寄存器中,实现GPIO外设的初始化操作