GPIO簡介
GPIO:每個連接到I/O總線上的設備都有自己的I/O地址集,即所謂的I/O端口。類似51單片機的P0~P3,但與51單片機不同的是,對stm32的GPIO來說,使用前需要設置其工作方式。。STM32 的每個 IO 端口都有 7 個寄存器來控制其工作方式,而每一個寄存器都需要用32bit來控制。在STM32中,一組GPIO有16個IO口。
端口位基本結構:
工作方式
stm32的IO口一共有8種工作方式:
1、 輸入浮空:讀取相應外部的電平,但當引腳不輸入時,相當於懸空,輸入電平未知,範圍爲0~VCC。
2、 輸入上拉:保證在無信號輸入時輸入端的電平爲高電平。而在信號輸入爲低電平時輸入端的電平也爲低電平。
3、 輸入下拉:保證在無信號輸入時輸入端的電平爲低電平。而在信號輸入爲高電平時輸入端的電平也爲高電平。
4、 模擬輸入:傳統方式的輸入,將0,1的二進制數字信號,通過數模轉換,變成模擬信號,應用ADC模擬輸入,或者低功耗下省電。簡單來說,就是將原本的高低電平轉換爲範圍爲0~VCC信號。
5、 開漏輸出:IO口輸出0接地,輸出1由外接電阻控制爲0/1.
6、 推輓輸出:低電平輸出爲0,高電平輸出爲VCC
鏈接:用三極管很好地說明上面兩者輸出
7、 複用推輓輸出:用作串口的輸出。
8、 複用開漏輸出 :用在IIC。
7和8兩種輸出共同點:可以理解爲GPIO口被用作第二功能時的配置情況(即並非作爲通用IO口使用),給內部外設使用的推輓,開漏輸出,此時端口必須配置爲複用功能輸出模式。
STM32 的 IO 口位配置表 :
STM32 輸出模式配置 :
相關寄存器介紹
配置模式的 2 個 32 位的端口配置寄存器 CRL 和 CRH; 2 個 32 位的數據寄存器 IDR 和 ODR; 1 個 32 位的置位/復位寄存器BSRR;一個 16 位的復位寄存器 BRR; 1 個 32 位的鎖存寄存器 LCKR。
1.CRL 和 CRH(控制着每個 IO 口的模式及輸出速率)
CRH 的作用和 CRL 完全一樣,只是 CRL 控制的是低 8 位輸出口,而 CRH 控制的是高 8位輸出口。
2.IDR 和 ODR
IDR:IDR 是一個端口輸入數據寄存器,只用了低 16 位。該寄存器爲只讀寄存器,並且只能以16 位的形式讀出。
在固件庫中操作 IDR 寄存器讀取 IO 端口數據是通過GPIO_ReadInputDataBit 函數實現的:
uint8_tGPIO_ReadInputDataBit(GPIO_TypeDef*GPIOx,uint16_tGPIO_Pin)
比如讀取 GPIOA.5 的電平狀態,那麼方法是:
GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5);
返回值是 1(Bit_SET)或者 0(Bit_RESET);
ODR:ODR 是一個端口輸出數據寄存器,也只用了低 16 位。該寄存器爲可讀寫,從該寄存器讀出來的數據可以用於判斷當前 IO 口的輸出狀態。而向該寄存器寫數據,則可以控制某個 IO 口的輸出電平。
在固件庫中設置 ODR 寄存器的值來控制 IO 口的輸出狀態是通過函數GPIO_Write 來實現的:
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
該函數一般用來對一個 GPIO 的多個端口設值。
3.BSRR:BSRR 寄存器是端口位設置/清除寄存器。 該寄存器和 ODR 寄存器具有類似的作用,都可以用來設置 GPIO 端口的輸出位是 1 還是 0。
4. BRR: BRR 寄存器是端口位清除寄存器。該寄存器的作用跟 BSRR 的高 16 位相同。
5.LCKR(不常用)
IO 操作步驟
1) 使能 IO 口時鐘。調用函數爲 RCC_APB2PeriphClockCmd()。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOX,ENABLE);//GPIOX 使能時鐘,X=A~E
2) 初始化 IO 參數。調用函數 GPIO_Init();(確定操作的IO口)
void GPIO_Init(GPIO_TypeDef*GPIOx,GPIO_InitTypeDef*GPIO_InitStruct);
其中,兩個參數分別爲:配置引腳組(GPIO_TypeDef* GPIOx),配置的參數( GPIO_InitTypeDef* GPIO_InitStruct)。
3) 操作 IO。(固件庫操作,寄存器操作,位操作)
IO口三種操作細解
1.固件庫操作
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_Out_PP; //模式選擇爲推輓輸出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;//確定選位爲第五個IO口
GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;//速率選擇爲50M
GPIO_Init(GPIOX,&GPIO_InitStructure);//初始化GPIOX
GPIO_SetBits(GPIOX,GPIO_Pin_5);//設置該位爲高電平
GPIO_ResetBits(GPIOX,GPIO_Pin_5);//設置該位爲低電平
八種模式在 MDK 中是通過一個枚舉類型定義的:
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;
IO 口速度設置, 有三個可選值,在 MDK 中同樣是通過枚舉類型定義 :
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
在模式設置完成後,可通過其它庫函數進行IO口設置:
控制IDR:
uint_8t GPIO_ReadIputDataBit(GPIO_TypeDef*GPIOx,uint16_t GPIO_Pin)
控制ODR:
void GPIO_Write(GPIO_TypeDef*GPIOx,uint16_t PortVal)
控制BSRR與BRR:
void GPIO_SetBits(GPIO_TypeDef*GPIOx,uint16_t GPIO_Pin)
void GPIO_ResetBits(GPIO_TypeDef*GPIOx,uint16_t GPIO_Pin)
2.寄存器操作
完整IO口初始化示例:
void LED_Init(void)
{
RCC->APB2ENR |= 1<<2; //使能PTA時鐘
RCC->APB2ENR |= 1<<5; //使能PTD時鐘
GPIOA->CRH&=0XFFFF FFF0; //清空PA8設置
GPIOA->CRH|=0X0000 0003; //設置PA8推輓輸出
GPIOA->ODR|=1<<8; //PA8輸出高
GPIOD->CRL&=0XFFFF F0FF;//清空PD2設置
G)PIOD->CRL|=0X0000 0300;//設置PD2推輓輸出
GPIOD->ODR|=1<<2;//PD2輸出高
}
一般用寄存器控制IO口的形式如上,可以直接賦值,也可以通過與(&),或(|),非(~)進行控制 。
3.位操作:通過直接對IO口的地址進行操作來改變IO口的值,達到操作IO的目的。
原理:將每個位膨脹爲一個32位的字(即其地址),稱爲這個位的“位帶別名區 ”,如下圖:
STM32中有兩個位帶區:
對於片上外設位帶區的某個比特,記它所在字節的地址爲 A,位序號爲 n(0<=n<=7),則該比特在別名區的地址爲:
AliasAddr= 0x42000000+((A‐0x40000000)*8+n)*4 =0x42000000+ (A‐0x40000000)*32 + n*4
對 SRAM 位帶區的某個比特,記它所在字節地址爲 A,位序號爲 n(0<=n<=7),則該比特 在別名區的地址爲:
AliasAddr= 0x42000000+((A‐0x40000000)*8+n)*4 =0x42000000+ (A‐0x40000000)*32 + n*4
“*4”表示一個字爲 4 個字節,“*8”表示一個字節中有 8 個比特。
使用(以控制GPIOA的16個IO口中的第一個爲例):
//IO口操作宏定義,獲取相應地址
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
//IO口地址映射,即獲取GPIOA_ODR的地址
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //輸出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //輸入
PAin(1)=1//使得GPIOA的第一個IO口爲高電平
附錄:C語言相關
1.C語言位操作
2.define 是 C 語言中的預處理命令,它用於宏定義,可以提高源代碼的可讀性,爲編程提供方便。常見的格式:#define 標識符 字符串(無分號)“標識符”爲所定義的宏名。“字符串”可以是常數、表達式、格式串等。 (類似typedef )
3.單片機程序開發過程中,經常會遇到一種情況, 當滿足某條件時對一組語句進行編譯,而當條件不滿足時則編譯另一組語句。 條件編譯命令最常見的形式爲:
#ifdef 標識符
程序段 1
#else
程序段 2
#endif
當標識符已經被定義過(一般是用#define 命令定義),則對程序段 1 進行編譯,否則編譯程序段 2。 其中#else 部分也可以沒有,。
4.C 語言中 extern 可以置於變量或者函數前,以表示變量或者函數的定義在別的文件中,提示編譯器遇到此變量和函數時在其他模塊中尋找其定義。 如:
extern u16 USART_RX_STA;
此時,在其它文件會有:
u16 USART_RX_STA;
但是如果有多個文件同時要對應用的變量進行操作,而且可能會修改該變量,那就會影響其他模塊的使用。
5.結構體
聲明結構體類型:
Struct 結構體名{
成員列表;
}變量名列表;
例:
struct U_TYPE {
int BaudRate
int WordLength;
}usart1,usart2;
結構體成員變量的引用方法是:結構體變量名字.成員名
usart1.BaudRate//引用usart1中的成員BaudRate
當變量定義過多,或者某幾個變量是用來描述某一個對象,結構體可以使函數更加易讀。