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