8.1 關于GPIO
GPIO(General-Purpose IO ports,通用輸入/輸出介面),用于感知外界信號(輸入模式)和控制外部設備(輸出模式),如圖 6.1.1 所示的STM32F103ZET6芯片四周的細引腳就是GPIO,
在嵌入式開發中,經常需要用到一些外部功能模塊,比如LED、按鍵、蜂鳴器、溫度傳感器等,這些外設模塊都比較簡單,只需要MCU的GPIO與模塊連接,控制引腳輸出/讀取高低電平即可,還有一些外部功能模塊,需要多個引腳構成的“協議”進行通信,比如UART、I2C、SPI介面等,
如今的MCU大都采用引腳復用技術,即一個GPIO,即可以直接控制其輸出高低電平,也可以設定為某個協議的引腳之一,比如I2C的時鐘信號引腳SCK,此外,有些MCU的引腳,還能設定為ADC模式讀取模擬信號,或者設定為DAC模式輸出模擬信號,本章主要針對引腳的GPIO模式講解,
對于GPIO模式,不同的MCU的功能細節略有差異,比如STM32的GPIO可以設定輸出速度,51單片機就沒有該功能,因此本章主要針對STM32的GPIO特性進行講解,其它MCU大同小異,稍微類比一下也能很快上手,
8.1.1STM32的GPIO
GPIO(General-Purpose IO ports,通用輸入/輸出介面),用于感知外界信號(輸入模式)和控制外部設備(輸出模式),如圖 6.1.1 所示的STM32F103ZET6芯片四周的細引腳就是GPIO,
在嵌入式開發中,經常需要用到一些外部功能模塊,比如LED、按鍵、蜂鳴器、溫度傳感器等,這些外設模塊都比較簡單,只需要MCU的GPIO與模塊連接,控制引腳輸出/讀取高低電平即可,還有一些外部功能模塊,需要多個引腳構成的“協議”進行通信,比如UART、I2C、SPI介面等,
如今的MCU大都采用引腳復用技術,即一個GPIO,即可以直接控制其輸出高低電平,也可以設定為某個協議的引腳之一,比如I2C的時鐘信號引腳SCK,此外,有些MCU的引腳,還能設定為ADC模式讀取模擬信號,或者設定為DAC模式輸出模擬信號,本章主要針對引腳的GPIO模式講解,
對于GPIO模式,不同的MCU的功能細節略有差異,比如STM32的GPIO可以設定輸出速度,51單片機就沒有該功能,因此本章主要針對STM32的GPIO特性進行講解,其它MCU大同小異,稍微類比一下也能很快上手,
8.1.1STM32的GPIO
STM32F103ZET6一共有144個引腳,除去電源引腳、晶振時鐘引腳、復位引腳、啟動選擇引腳、程式下載引腳(大部分為最小系統必須引腳),剩下的則是GPIO引腳,
STM32將這些眾多按GPIOx(x為A、B、C等)分組,每組包含16個引腳,編號為0~15,STM32F103ZET6就有7組GPIO,每組16個引腳,即112個GPIO引腳,對于本開發板,讀者可以通過《原理圖》或表 3.4.1 查詢每個引腳的用處,比如引腳名為PB0引腳,表示GPIOB組的0號引腳,在原理圖的網路標識為LED_R,用于控制LED三色燈的紅色控制引腳,
下圖 8.1.1 為STM32F103系列GPIO的基本結構,左側連接MCU內部,中間上半部分為輸入,中間下半部分為輸出,右側為MCU引出的外設I/O引腳,

8.1.2 GPIO作業模式
STM32F103系列的I/O引腳共有8種作業模式,其中輸出模式有四種:推挽輸出、開漏輸出、復用推挽輸出、復用開漏輸出;輸入模式有四種:上拉輸入、下拉輸入、浮空輸入、模擬輸入,
1)推挽輸出(Push-Pull,PP)
推挽結構由兩個MOS管按互補對稱的方式連接,任意時刻總是其中一個三極管導通,另一個三極管截止,如圖 8.1.1 中①所示,內部由一個P-MOS管和一個N-MOS管組成,兩個MOS管的柵極(Gate,G)接到了左側“輸出控制”,漏極(Drain,D)接到外部輸出,P-MOS管的源極(Source,S)接到VDD(3.3V),N-MOS管的源極接到Vss(0V),
MOS管作為開關使用,“輸出控制”向兩個MOS管柵極加一定電壓,P-MOS管源極和漏極之間導通,VDD 經過P-MOS管的S->G->D輸出,N-MOS管處于高阻態(電阻很大,近似開路),整體對外為高電平;“輸出控制”取消向兩個MOS管柵極施加電壓,P-MOS管源極和柵極截止,P-MOS管處于高阻態,N-MOS管源極和漏極導通,整體對外為低電平,
推挽模式,讓“輸出控制”變為了VDD/Vss輸出,使得輸出電流增大,提高了輸出引腳的驅動能力,提高了電路的負載能力和開關的動作速度,
2)開漏輸出(Open-Drain,OD)
開漏模式下,“輸出控制”不會控制P-MOS管,“輸出控制”只會向N-MOS管柵極加一定電壓,兩個MOS管都處于截止狀態,兩個漏極處于懸空狀態,稱之為漏極開路,“輸出控制”取消柵極的施加電壓,P-MOS管依舊處于高阻態,N-MOS管導通,整體對外為低電平,
開漏輸出模式可以等效將圖 8.1.2 中灰色框的P-MOS管看作不存在,即該模式下只能輸出低電平,若要輸出高電平,則需要外接電阻,所接的電阻稱為上拉電阻,此時輸出電平取決于此時上拉電阻的外部電源電壓情況,如圖 8.1.2 中藍色框的外部電路,

推挽輸出模式可以直接輸出高電平,開漏輸出模式需要外接上拉電阻才能輸出高電平,但開漏輸出擁有一些推挽輸出不具有的特性:
①利用外部電路驅動能力,如圖 8.1.2 所示,“輸出控制”只需要提供一個很小的柵極驅動電流,VCC經過上拉電阻為外部負載提高驅動電流;
②實作電平轉換,推挽輸出模式由VDD提供,即只能提供3.3V電平,使用開漏輸出模式后,VCC可以為5V,從而實作了電平轉換的效果,
③方便實作“邏輯與”功能,多個開漏的引腳可以直接并在一起使用,統一接一個合適的上拉電阻,就可以實作“邏輯與”關系,即當所有引腳均輸出高電平時,輸出才為高電平,若任一引腳輸出低電平,則輸出低電平,在I2C、SMBUS等總線電路中經常會用到,
3)復用功能推挽/開漏輸出(Alternate Function,AF)
GPIO引腳除了作為通用輸入/輸出引腳使用外,還可以作為片上外設(USART、I2C、SPI等)專用引腳,即一個引腳可以有多種用途,但同一時刻一個引腳只能使用復用功能中的一個,
當引腳設定為復用功能時,可選擇復用推挽輸出模式或復用開漏輸出模式,在設定為復用開漏輸出模式時,需要外接上拉電阻,
4)上拉輸入模式(Input Pull-up)
如圖 8.1.1 中②所示,VDD經過開關、上拉電阻,連接外部I/O引腳,當開關閉合,外部I/O無輸入信號時,默認輸入高電平,
該模式的典型應用就是外接按鍵,當沒有按鍵按下時候,MCU的引腳為確定的高電平,當按鍵按下時候,引腳電平被拉為低電平,
5)下拉輸入模式(Input Pull-down)
如圖 8.1.1 中②所示,Vss經過開關、下拉電阻,連接外部I/O引腳,當開關閉合,外部I/O無輸入信號時,默認輸入低電平,
6)浮空輸入模式(Floating Input)
如圖 8.1.1 中②所示,兩個上/下拉電阻開關均斷開,既無上拉也無下拉,I/O引腳直接連接TTL肖特基觸發器,此時I/O引腳浮空,讀取的電平是不確定的,外部信號是什么電平,MCU引腳就輸入什么電平,MCU復位上電后,默認為浮空輸入模式,
7)模擬輸入模式(Analog mode)
如圖 8.1.1 中②所示,兩個上/下拉電阻開關均斷開,同時TTL肖特基觸發器開關也斷開,引腳信號直接連接模擬輸入,實作對外部信號的采集,
8.1.3GPIO輸出速度
STM32的I/O引腳作業再輸出模式下時,需要配置I/O引腳的輸出速度,該輸出速度不是輸出信號的速度,而是I/O口驅動電路的回應速度,
STM32提供三個輸出速度:2MHz、10MHz、50MHz,實際開發中需要結合實際情況選擇合適的相應速度,以兼顧信號的穩定性和低功耗,通常,當設定為高速時,功耗高、噪聲大、電磁干擾強;當設備為低速時,功耗低、噪聲小、電磁干擾弱,
通常簡單外設,比如LED燈、蜂鳴器燈,建議使用2MHz的輸出速度,而復用為I2C、SPI等通信信號引腳時,建議使用10MHz或50MHz以提高回應速度,
8.2硬體設計
LED(Light Emitting Diode,發光二極管),是一種能夠將電能轉化為可見光的半導體器件,當給P極施加正向電壓后,空穴和自由電子在P-N結復合,輻射出光子而發光,如圖 8.2.1 所示為目前市面常見的LED燈,第一個是插件LED燈,第二個是貼片LED燈,第三個是貼片三色LED燈,由三個紅、綠、藍小燈組成,后面可以通過PWM實驗控制每個燈亮度,實作顏色組合,更具可玩性,

如圖 8.2.2 所示為開發板三色LED燈部分的原理圖,LED燈的正極直接連接了VDD_3V3,LED燈的負極分別連接了三個GPIO引腳,紅色LED連接的PB0,綠色LED連接的PB1,藍色LED連接的PB5,只需要控制PB0、PB1、PB5為相應低電平,即可點亮對應LED燈,輸出為高電平時熄滅對應LED燈,

8.3軟體設計
本節將會對軟體設計中關鍵部分進行講解,完整工程請參考本章配套工程,同時,為了方便移植與代碼閱讀,完整工程中將LED部分的驅動代碼新建獨立,源代碼檔案與頭檔案分別是“driver_led.c”,“driver_led.h”,保存在工程檔案夾“Driver”中,
8.3.1軟體設計思路
實驗目的:體驗嵌入式的“Hello Word”,點亮LED燈,
1)選擇LED對應的GPIO;
2)使能所選擇GPIO的時鐘;
3)配置其為上拉輸出模式;
4)控制其輸出高或低來控制LED的亮與滅;
本實驗配套代碼位于“5_程式原始碼\2_GPIO—LED點燈”,
8.3.2軟體設計講解
1)GPIO選擇與介面定義
宏定義GPIO介面的作用是,當實際設計中的LED對應的GPIO發生變化時,只需在宏定義處改變GPIO的值即可完成對整個LED設計的修改,這樣就增強了可移植性,代碼段 8.3.1 引腳宏定義(driver_led.h)
/*********************
* 引腳宏定義
**********************/
#define R_LED_GPIO_PIN GPIO_PIN_0
#define R_LED_GPIO_PORT GPIOB
#define R_LED_GPIO_CLK_EN() __HAL_RCC_GPIOB_CLK_ENABLE()
#define G_LED_GPIO_PIN GPIO_PIN_1
#define G_LED_GPIO_PORT GPIOB
#define G_LED_GPIO_CLK_EN() __HAL_RCC_GPIOB_CLK_ENABLE()
#define B_LED_GPIO_PIN GPIO_PIN_5
#define B_LED_GPIO_PORT GPIOB
#define B_LED_GPIO_CLK_EN() __HAL_RCC_GPIOB_CLK_ENABLE()
根據原理圖可知,RGB燈三色對應的引腳分別是:紅燈-PB0、綠燈-PB1、藍燈-PB5,因此得到代碼段 8.3.1 的引腳和引腳對應時鐘使能函式的宏定義,當用戶工程LED引腳發生變化時,只需在此處做出修改即可,然后是對三個GPIO的輸出函式進行介面宏定義以方便移植與閱讀,如代碼段 8.3.2 所示,
/*
* LED亮滅函式宏定義
*/
#define ON GPIO_PIN_RESET
#define OFF GPIO_PIN_SET
#define RLED(flag) HAL_GPIO_WritePin(R_LED_GPIO_PORT, R_LED_GPIO_PIN, flag)
#define GLED(flag) HAL_GPIO_WritePin(G_LED_GPIO_PORT, G_LED_GPIO_PIN, flag)
#define BLED(flag) HAL_GPIO_WritePin(B_LED_GPIO_PORT, B_LED_GPIO_PIN, flag)
HAL庫中,對GPIO的輸出控制函式是:
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
第一個引數 GPIOx表示GPIOA/B/C/D/E…/H中某一組埠,此處我們的實驗是GPIOB,但是為方便移植我們使用宏定義的埠R_LED_GPIO_PORT、G_LED_GPIO_PORT、B_LED_GPIO_PORT;
第二個引數GPIO_Pin表示在某組埠中的某一個引腳,與選擇埠類似,我們選擇已宏定義好的R _LED_GPIO_PIN、G _LED_GPIO_PIN、B _LED_GPIO_PIN;
第三個引數PinState表示對這個IO控制輸出的狀態,是一個列舉型別,包含兩個成員:GPIO_PIN_RESET和GPIO_PIN_SET,因為低電平亮燈,所以定義ON對應GPIO_PIN_RESET,OFF對應GPIO_PIN_SET,
2)GPIO的初始化
當選擇好LED對應的GPIO后,還需要對其進行初始化,以完成對這些GPIO時鐘的使能,作業模式的選擇以及輸出速度的設定,
代碼段 8.3.3 GPIO初始化(driver_led.c)
/*
* 函式名:void LedGpioInit(void)
* 輸入引數:無
* 輸出引數:無
* 回傳值:無
* 函式作用:初始化LED的引腳,配置為上拉推挽輸出
*/
void LedGpioInit(void)
{
// 定義GPIO的結構體變數
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 使能LED的GPIO對應的時鐘
R_LED_GPIO_CLK_EN();
G_LED_GPIO_CLK_EN();
B_LED_GPIO_CLK_EN();
GPIO_InitStruct.Pin = R_LED_GPIO_PIN | G_LED_GPIO_PIN | B_LED_GPIO_PIN; // 選擇LED的引腳
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 設定為推挽輸出模式
GPIO_InitStruct.Pull = GPIO_PULLUP; // 默認上拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;// 引腳反轉速度設定為快
// 初始化引腳配置
HAL_GPIO_Init(R_LED_GPIO_PORT, &GPIO_InitStruct);
// 默認LED滅:OFF-滅,ON-亮
RLED(OFF);
GLED(OFF);
BLED(OFF);
}
在此函式中,首先定義了一個結構體變數GPIO_InitStruct,先看看這個結構體中都有哪些成員,代碼段 8.3.4 結構體GPIO_InitStruct定義(stm32f1xx_hal_gpio.h)
/**
* @brief GPIO Init structure definition
*/
typedef struct
{
uint32_t Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
uint32_t Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIO_mode_define */
uint32_t Pull; /*!< Specifies the Pull-up or Pull-Down activation for the selected pins.
This parameter can be a value of @ref GPIO_pull_define */
uint32_t Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIO_speed_define */
} GPIO_InitTypeDef;
- 6行:第一個成員pin是引腳選擇;
- 9行:第二個成員mode是模式選擇:輸出、輸入、復用、中斷、事件;
- 12行:第三個成員是上拉、下拉、懸空選擇;
- 15行:第四個成員是輸出速度選擇;
當我們不清楚這些成員可以設定成哪些值時,可以看到每個成員的注釋的最后有個@ref xxx,比如第7行末尾的“@ref GPIO_pins_define”,我們只需要選中這個GPIO_pins_define,然后按Ctrl+f尋找這個GPIO_pins_define,就會找到例如pin如下宏定義,
代碼段 8.3.5 GPIO引腳宏定義(stm32f1xx_hal_gpio.h)
/** @defgroup GPIO_pins_define GPIO pins define
* @{
*/
#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 */
#define GPIO_PIN_MASK 0x0000FFFFu /* PIN mask for assert test */
這里只需要選擇其中需要的宏定義即可,然后使用三個宏定義的時鐘使能函式使能了選擇的GPIO的時鐘,用上述設定GPIO_InitStruct成員講解的方法設定每個成員的值之后,使用下述函式對選擇的某組埠的GPIO引腳進行初始化,
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)
此處我們選擇的是紅燈的埠,但是在程式中我們的引腳選擇是紅綠藍三個引腳都選擇且進行了初始化,且紅綠藍三個燈所對應的埠均是GPIO B,所以實際上這個初始化函式是將三個LED的引腳都完成了初始化,
3)GPIO輸出控制
在初始化函式的末尾,我們使用了宏定義的IO控制介面控制三個IO輸出高電平,即讓三燈均呈熄滅狀態:代碼段 8.3.6 控制LED燈熄滅(driver_led.c)
// 默認LED滅:OFF-滅,ON-亮
RLED(OFF);
GLED(OFF);
BLED(OFF);
4)HAL庫延時
HAL庫使用系統滴答定時器(此定時器在后序章節中詳細講解)封裝了一個延時函式:
__weak void HAL_Delay(uint32_t Delay)
在不對系統滴答定時器進行重新定義的情況下,此延時函式的效果是ms級,即延時Delay個毫秒,
5)整體控制邏輯
代碼段 8.3.7 LED燈主函式(main.c)
int main(void)
{
// 初始化HAL庫函式必須要呼叫此函式
HAL_Init();
// 系統時鐘即AHB/APB時鐘配置
SystemClock_Config();
// 初始化LED
LedGpioInit();
// 點亮三色燈
RLED(ON);
HAL_Delay(1000);
RLED(OFF);
GLED(ON);
HAL_Delay(1000);
GLED(OFF);
BLED(ON);
HAL_Delay(1000);
BLED(OFF);
while(1)
{
RLED(ON);
HAL_Delay(1000);
RLED(OFF);
HAL_Delay(1000);
}
}
- 4行:對HAL庫的初始化,這一步的作用是初始化中斷優先級組別以及對系統滴答定時器進行默認的初始化;
- 6行:時鐘初始化,本實驗所選擇的是外部高速時鐘,最終配置為72MHz系統時鐘,關于時鐘配置在后一章節講解;
- 9行:初始化LED;
- 11~22行:初始控制邏輯:只紅燈亮->只綠燈亮->只藍燈亮;
- 24~30行:死回圈邏輯:閃爍紅燈,周期是2s;
8.4實驗效果
開發板三色LED燈,只紅燈亮->只綠燈亮->只藍燈亮,最后紅燈間隔1秒閃爍,
百問網技術論壇:
http://bbs.100ask.net/
百問網嵌入式視頻官網:
https://www.100ask.net/index
百問網開發板:
淘寶:https://100ask.taobao.com/
天貓:https://weidongshan.tmall.com/
技術交流群(鴻蒙開發/Linux/嵌入式/驅動/資料下載)
QQ群:869222007(已滿)752871361
單片機-嵌入式Linux交流群:
QQ群:536785813
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/276265.html
標籤:其他
