GPIO寄存器地址与结构体结合


GPIOx寄存器结构体

/** stm32f10x.h头文件中,使用了很多与寄存器对应的结构体来描述GPIO寄存器的数据结构 **/
typedef struct
{
  __IO uint32_t CRL;		/* Address offset: 0x00 */
  __IO uint32_t CRH;		/* Address offset: 0x04 */
  __IO uint32_t IDR;
  __IO uint32_t ODR;
  __IO uint32_t BSRR;
  __IO uint32_t BRR;
  __IO uint32_t LCKR;
} GPIO_TypeDef;

/** stm32f40xx.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 uint16_t BSRRL;    /*!< GPIO port bit set/reset low register,  Address offset: 0x18      */
  __IO uint16_t BSRRH;    /*!< GPIO port bit set/reset high register, Address offset: 0x1A      */
  __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;

从图中可以看出,GPIOx的7个寄存器都是32位的,每个寄存器占用4个字节,一共占用28个字节,地址偏移范围为000H~01BH。这7个寄存器的地址偏移量是相对GPIOx的基地址而言的,每个寄存器的地址偏移量不变。

GPIOx的基地址是怎么算出来的呢?

(1)获得GPIOA基地址

由于GPIO都是挂载在APB2总线上的,所以GPIOA的基地址是由APB2总线的基地址和GPIOA在APB2总线上的偏移地址决定的。获得GPIOA基地址的过程如下。

打开stm32f10x.h头文件,先定位到GPIO_TypeDef结构体定义处,前面已给出了定义GPIO寄存器结构的结构体。然后定位到GPIOA的宏定义:

#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)

通过宏定义,把GPIOA_BASE强制转化为GPIO_TypeDef类型指针,用定义的宏名GPIOA代替GPIOA_BASE。这样就可以将GPIOA作为GPIO_TypeDef结构体类型的一个指针,其基地址是GPIOA_BASE。

然后再定位到GPIOA_BASE的宏定义:

/** stm32f10x.h头文件 **/
#define GPIOA_BASE            (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE            (APB2PERIPH_BASE + 0x0C00)

/** stm32f40xx.h头文件 **/
#define GPIOA_BASE            (AHB1PERIPH_BASE + 0x0000)
#define GPIOB_BASE            (AHB1PERIPH_BASE + 0x0400)

以此类推定位到最后两个位置:

#define APB1PERIPH_BASE       PERIPH_BASE
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x20000)
/***************************************************************************************/
#define PERIPH_BASE           ((uint32_t)0x40000000)  //外设区基地址

由此可以计算出GPIOA的基地址:

GPIOA_BASE = 0x40000000 + 0x10000 + 0x0800 = 0x40010800

(2)GPIOA寄存器地址

在前面我们已经知道如何获得GPIOA的基地址,那么GPIOA的7个寄存器地址又是如何获得的呢?GPIOA寄存器地址的计算公式如下:

GPIOA寄存器地址 = GPIOA基地址 + 寄存器相对GPIOA基地址的偏移量

GPIOx端口复用使用

STM32有很多的内置外设,这些内置外设的引脚都是与GPIO引脚复用的。简单地说,GPIO的引脚可以重新定义为其他功能,这就叫作端口复用。

在使用默认的复用功能前,必须对复用的端口进行初始化。下面以串口2为例,初始化步骤如下如述。

(1)GPIO端口时钟使能。

由于要使用这个端口,所以要使能这个端口的时钟,代码如下:

RCC_APBRCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA 时钟

(2)复用的外设时钟使能。

在这里要把GPIOA口的PA2和PA3引脚复用为串口2的TX和RX引脚,所以要使能这个串口时钟,代码如下:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART2, ENABLE); //使能USART2 时钟

(3)端口模式配置。

在复用内置外设功能引脚时,必须设置GPIO端口的模式。GPIOA口的PA2和PA3引脚复用为串口2的TX和RX引脚,其初始化代码如下:

• USART2_TX、PA2复用推挽输出

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //ISART2_TX PA.2
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.2

• USART2_RX、PA3浮空输入

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //USART1_RX PA.3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); 

(4)串口参数初始化

USART_InitStructure.USART_BaudRate = bound; //波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为 8 位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl
								= USART_HardwareFlowControl_None; //无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口

(5)使能串口

USART_Cmd(USART1, ENABLE); //使能串口

GPIOx端口复用重映射

在STM32中,有很多内置外设的输入/输出引脚都具有重映射(remap)的功能,每个内置外设都有若干个输入/输出引脚,一般这些引脚的输出端口都是固定不变的。为了让设计工程师可以更好地安排引脚的走向和功能,在STM32中引入了外设引脚重映射的概念,即一个外设的引脚除了具有默认的端口外,还可以通过设置重映射寄存器的方式,把它映射到其他端口。

1.寄存器结构体

typedef struct
{
  __IO uint32_t EVCR;
  __IO uint32_t MAPR;
  __IO uint32_t EXTICR[4];
  uint32_t RESERVED0;
  __IO uint32_t MAPR2;  
} AFIO_TypeDef;

2.如何实现端口复用重映射

下面以定时器TIM3为例,介绍如何实现TIM3的重映射。AFIO_MAPR的11:10位可以控制TIM3的通道1至通道4在GPIO端口的重映射。TIM3复用功能重映射引脚一览表如图所示。

从图中可以看到,TIM3复用功能有部分重映射和完全重映射两种。部分重映射就是一部分引脚和默认是一样的,另一部分引脚重新映射到其他引脚。完全重映射就是所有引脚都重新映射到其他引脚。在默认情况下(即没有重映射),TIM3的通道1至通道4复用功能引脚是CH1/PA6、CH2/PA7、CH3/PB0和CH4/PB1。

我们也可以将通道1至通道4完全重映射到引脚CH1/PC6、CH2/PC7、CH3/PC8和CH4/PC9上。若想实现TIM3的完全重映射,要先使能复用功能的两个时钟,然后使能AFIO功能时钟,再调用重映射函数,具体步骤如下所述。

(1)使能GPIOC时钟。

RCC_APBRCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //使能GPIOC 时钟

(2)使能TIM3时钟。

RCC_APBRCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能GPIOC 时钟

(3)使能AFIO时钟。

RCC_APBRCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //使能AFIO 时钟

(4)开启重映射。

GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);

(5)最后,还需要配置重映射的引脚。

只需配置重映射后的引脚,原来的引脚不需要配置。这里重映射的引脚是PC6、PC7、PC8和PC9,配置为复用推挽输出,代码如下:

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9; //ISART2_TX PA.2
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOC, &GPIO_InitStructure); //初始化 GPIOA.2


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM