轉載http://blog.csdn.net/wuwuhuizheyisheng/article/details/8239599
STM32的GPIO總結
作者:JCY
該文是自己學習了一段STM32后所寫,是對STM32使用固件庫編程最簡單的一段程序,是對固件庫函數的一部分進行解析。如有錯誤之處請指正,不勝感激。
一、GPIO_Init函數解析
首先來看一下GPIO_Init函數的原型void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)。這個函數的實現是在Stm32f10x_gpio.c文件中,若要使用該函數在相應的應用程序的前面包含Stm32f10x_gpio.h頭文件。
1、參數GPIO_TypeDef
該函數的第一個參數為GPIO_TypeDef,它是一個結構體類型,該類型在Stm32f10x.h中被定義。定義的原型為:
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;
在這個結構體類型當中有7個32(8字節)位的變量,這些變量在存儲空間的地址是相鄰的。打開STM32數據手冊不難看出,每個端口對應有16的引腳,由7個寄存器控制GPIO行為,並且這7個寄存器的順序也是連續的。各個端口都有相同的結構。STM32的固件庫就將這種結構抽象出一個類型GPIO_TypeDef。在操作寄存器之前你一定要有一個寄存器映射的操作,否則無法訪問指定的寄存器,在這里我們只需要映射一次而不需要映射7此。這樣做是不是很方便,也提高了代碼的可讀性,使代碼規范化。
既然GPIO_Init的第一個參數GPIO_TypeDef的指針變量,這個指針變量存放的就是某一個端口的首地址。某一個程序的調用語句是這樣的GPIO_Init(GPIOD,&GPIO_InitStructure); //初始化GPIOD
GPID是固件庫中定義的一個宏,在編譯的時候會宏展開,先列出與GPIOD端口地址映射有關的宏定義如下:
#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define PERIPH_BASE ((uint32_t)0x40000000)
看到了0x4000 0000這個數字是不是非常熟悉,它是外設的首地址。在STM32芯片的內部STM32有兩個,一個叫APB1,一個叫APB2。每一個APB橋都會管理很多外設。STM32F10x把這兩個APB的外設寄存器訪問地址放在了不同的存儲空間。0x10000就是APB2外設的存儲空間首地址相對於整個外設的偏移。而0x1400是GPIOD端口外設首地址相對於APB2外設的存儲空間首地址的偏移。這樣就找到了GPIOD外設的基地址了!而((GPIO_TypeDef *) GPIOD_BASE)可以同時實現所有控制GPIOD端口的7個寄存器的映射。若訪問某一個寄存器只需要通過指向GPIO_TypeDef 變量的指針。
2、參數GPIO_InitStruct
第二個參數的為GPIO_InitTypeDef* GPIO_InitStruct。就是一個指向GPIO _InitTypeDef的地址。第一個參數只找到配置的目標寄存器,第二個參數就是對相應端口如何配置的數據參數。這些參數存儲在指向GPIO_InitTypeDef變量的首地址處。先列處該參數由來的一斷代碼
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitTypeDef 是一個結構體的變量,該變量在Stm32f10x_gpio.h頭文件中被定義,定義的原型如下:
typedef struct
{
uint16_t GPIO_Pin;
GPIOSpeed_TypeDef GPIO_Speed;
GPIOMode_TypeDef GPIO_Mode;
}GPIO_InitTypeDef;
GPIO_InitTypeDef的第一個變量為GPIO_Pin是一個16為的無符號數,該數只有16位,每一位代表一個引腳,若要配置某一個端口的某一個引腳只需要把相應的位設置為1就可以了。在STM32的固件庫中有如下引腳號定義:
#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */
#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */
#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */
#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */
#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */
#define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */
#define GPIO_Pin_6 ((uint16_t)0x0040) /*!< Pin 6 selected */
#define GPIO_Pin_7 ((uint16_t)0x0080) /*!< Pin 7 selected */
#define GPIO_Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */
#define GPIO_Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */
#define GPIO_Pin_10 ((uint16_t)0x0400) /*!< Pin 10 selected */
#define GPIO_Pin_11 ((uint16_t)0x0800) /*!< Pin 11 selected */
#define GPIO_Pin_12 ((uint16_t)0x1000) /*!< Pin 12 selected */
#define GPIO_Pin_13 ((uint16_t)0x2000) /*!< Pin 13 selected */
#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */
#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */
#define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< All pins selected */
使用這些定義好的宏就方便多了,要配置某幾個引腳只需要把相應的引腳相或就可以了。若你要多某一個端口的所有為進行配置,那么只需要使用一個宏GPIO_Pin_All 。簡單吧!哈哈!
GPIOSpeed_TypeDef是一個枚舉變量,它用於存儲GPIO速度的參數,它的定義如下:
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
通過定義可以知道,GPIOSpeed_TypeDef的變量有三種取值,那么GPIO的速度有三種,
枚舉變量的值 |
對應的速度 |
1 |
10MHZ |
2 |
2MHZ |
3 |
50MHZ |
GPIOMode_TypeDef也是一個枚舉變量,它用於存儲GPIO工作的模式,它的定義如下:
typedef enum
{ GPIO_Mode_AIN = 0x0,
GPIO_Mode_IN_FLOATING = 0x04,
GPIO_Mode_IPD = 0x28,
GPIO_Mode_IPU = 0x48,
GPIO_Mode_Out_OD = 0x14,
GPIO_Mode_Out_PP = 0x10,
GPIO_Mode_AF_OD = 0x1C,
GPIO_Mode_AF_PP = 0x18
}GPIOMode_TypeDef;
設計這個枚舉變量的可取值有一定的意義。在第四位當中只用到了其中的高兩位,這兩位數據用來存儲到某一個引腳的模式控制位MODEx[1:0] ,而高四位用來標志某一些標志。
高四位的取值 |
意義 |
0 |
輸入模式 |
1 |
輸出模式 |
2 |
下拉輸入 |
4 |
上拉輸入 |
3、函數代碼詳解
上面是GPIO_Init函數參數的解釋。我在我們就可以進入GPIO_Init函數的內部看看了。
先把函數的代碼列出,對代碼的解釋都放在了注釋當中 ,如下:
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)//若為輸出上拉就會配置GPIO的速度
{
/* 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)//若對第八個引腳進行配置,GPIO_Pin的值某一位為1就會對該引腳配置
{
tmpreg = GPIOx->CRL;//暫存GPIO控制寄存器原來的值
for (pinpos = 0x00; pinpos < 0x08; pinpos++)//掃描8次決定,查看哪一引腳需要配置,若 //需要配置則進行配置
{
pos = ((uint32_t)0x01) << pinpos;//獲得要查看的某一個引腳所對應的位為1的值
/* Get the port pins position */
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;//currentpin 的值為0或者為pos
if (currentpin == pos)//若為pos說明該位需要配置
{
pos = pinpos << 2;//pinpos 的值乘以4得到某一引腳配置位的最低位號:0,4,8......28
/* Clear the corresponding low control register bits *///用於屏蔽某一個引腳的配置位, 使這4位為0
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
/* Write the mode configuration in the corresponding bits */
tmpreg |= (currentmode << pos);//因為模式所對應的數都存放在第四位,所以需要向左移位到某一個引腳對應的配置位的最低位出,然后對存儲到tmpreg 中
/* 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;//對低8個引腳配置寄存器賦值
}
/*---------------------------- GPIO CRH Configuration ------------------------*/
/* Configure the eight high port pins */
if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
{
tmpreg = GPIOx->CRH;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = (((uint32_t)0x01) << (pinpos + 0x08));
/* Get the port pins position */
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
if (currentpin == pos)
{
pos = pinpos << 2;
/* Clear the corresponding high 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 + 0x08));
}
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
}
}
GPIOx->CRH = tmpreg;
}
}
4、備注
assert_param函數是對參數的檢測。參數要么是邏輯0或者1。IS_GPIO_ALL_PERIPH也是一個宏,宏定義為:
#define IS_GPIO_ALL_PERIPH(PERIPH) (((PERIPH) == GPIOA) || \
((PERIPH) == GPIOB) || \
((PERIPH) == GPIOC) || \
((PERIPH) == GPIOD) || \
((PERIPH) == GPIOE) || \
((PERIPH) == GPIOF) || \
((PERIPH) == GPIOG))
其他的參數檢測函數當中使用的宏都是相似的,具體可以查看相應的宏定義,在此不一一列出。
對低8位的配置和對高8位的配置原理是一樣的。所以在此只對低8引腳配置進行說明。