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