單片機型號STM32F407VET6。
概述
GPIO的分類:
-
可接受5V輸入的(FT),絕大多數引腳都是;
-
只能接受3.3V輸入的(TTa),只有
PA4
和PA5
,就是DAC輸出的兩個引腳; -
其他,包括
BOOT0
和NRST
這兩個特殊功能的引腳。
GPIO不僅可以用作GPIO,每個GPIO都有復用功能(alternate function,AF)和附加功能(additional function),AF用GPIOx_AFR
來配置,附加功能用外設中的寄存器。
一組GPIO為16個,從Px0
到Px15
,x
為A
到I
,有些封裝上有些引腳不存在。
GPIO的功能主要有4類:
-
輸出,推挽(push-pull,PP)或開漏(open-drain,OD),可選上拉(pull-up,PU)或下拉(pull-down,PD),4檔速度;
-
AF,細節同上;
-
輸入,可選上拉或下拉;
-
模擬,用於ADC和DAC。
HAL
HAL把外部中斷也歸到了GPIO中,這里暫且不涉及外部中斷。
初始化這種事情我都交給STM32CubeMX來完成(STM32CubeIDE內置)。我已經初步領略到HAL的設計思想,以后專門開一篇寫。
GPIO有以下函數:
-
HAL_GPIO_Init()
:初始化一組GPIO中的一個或多個; -
HAL_GPIO_DeInit()
:把一組GPIO中的一個或多個還原為復位狀態; -
HAL_GPIO_ReadPin()
:讀引腳電平,返回GPIO_PinState
枚舉類型,可能值為GPIO_PIN_RESET = 0
和GPIO_PIN_SET = 1
; -
HAL_GPIO_WritePin()
:寫引腳電平,是原子操作,允許中斷發生; -
HAL_GPIO_TogglePin()
:翻轉引腳電平; -
HAL_GPIO_LockPin()
:鎖定引腳配置,在復位前不可修改,引腳電平還可以寫。
#include "main.h"
#include <stdbool.h>
int main(void)
{
bool prev = true;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1)
{
if (HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) == GPIO_PIN_RESET)
HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);
else
HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_SET);
bool now = HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET;
if (!prev && now)
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
prev = now;
HAL_Delay(1);
}
}
(STM32CubeMX生成的代碼是兩格縮進的,這讓我非常不爽!)
LED0
和LED1
分別連接到PA6
和PA7
,低電平亮;KEY0
和KEY1
分別連接到PE4
和PE3
,上拉。程序的功能為:KEY0
按下時LED0
亮,松開熄滅;KEY1
按下時切換LED1
的亮暗狀態。
寄存器
每一組GPIO都有10個寄存器:
-
GPIOx_MODER
,32位,2位MODERy[1:0]
一組(y
為0
到15
,下同),設置GPIO模式; -
GPIOx_OTYPER
,16位,1位OTy
一組,設置GPIO輸出類型; -
GPIOx_OSPEEDR
,32位OSPEEDRy[1:0]
,設置GPIO輸出速度; -
GPIOx_PUPDR
,32位PUPDRy[1:0]
,設置上拉下拉; -
GPIOx_IDR
,16位IDRy
,讀取輸入電平; -
GPIOx_ODR
,16位ODRy
,設置輸出電平; -
GPIOx_BSRR
,低16位BSy
,寫1
把GPIOx_ODR
中對應位置1
;高16位BRy
,寫1
把GPIOx_ODR
中對應位清0
;同時寫1
時BSy
優先; -
GPIOx_LCKR
:低16為LCKy
,第16位LCKK
,需要一個特定的寫入過程(參考datasheet或HAL_GPIO_LockPin
實現),可以鎖定GPIOx_MODER
、GPIOx_OTYPER
、GPIOx_OSPEEDR
、GPIOx_PUPDR
、GPIOx_AFRL
、GPIOx_AFRH
這6個控制寄存器中的對應位; -
GPIOx_AFRL
和GPIOx_AFRH
,4位AFRHy[3:0]
為一組,設置復用輸出。
GPIO的輸出級有一個NMOS和一個PMOS:
-
在推挽輸出模式下,
ODRx
為0
,NMOS導通;ODRx
為1
,PMOS導通; -
在開漏輸出模式下,
ODRx
為0
,NMOS導通;ODRx
為1
,高阻態;PMOS都不會導通。
開漏輸出的應用有矩陣鍵盤和I²C等,需要上拉電阻,通常用內置的即可。
用寄存器重寫上面的程序:
#include "main.h"
#include <stdbool.h>
#define LED0_Bit 6
#define LED1_Bit 7
#define KEY0_Bit 4
#define KEY1_Bit 3
int main(void)
{
bool prev = true;
HAL_Init();
SystemClock_Config();
__HAL_RCC_GPIOA_CLK_ENABLE();
LED0_GPIO_Port->ODR |= 1 << LED0_Bit;
LED0_GPIO_Port->MODER |= 0b01 << (LED0_Bit * 2);
LED1_GPIO_Port->ODR |= 1 << LED1_Bit;
LED1_GPIO_Port->MODER |= 0b01 << (LED1_Bit * 2);
__HAL_RCC_GPIOE_CLK_ENABLE();
KEY0_GPIO_Port->PUPDR |= 0b01 << (KEY0_Bit * 2);
KEY1_GPIO_Port->PUPDR |= 0b01 << (KEY1_Bit * 2);
while (1)
{
if (!(KEY0_GPIO_Port->IDR & 1 << KEY0_Bit))
LED0_GPIO_Port->BSRR = 1 << (16 + LED0_Bit);
else
LED0_GPIO_Port->BSRR = 1 << LED0_Bit;
bool now = !(KEY1_GPIO_Port->IDR & 1 << KEY1_Bit);
if (!prev && now)
LED1_GPIO_Port->ODR ^= 1 << LED1_Bit;
prev = now;
HAL_Delay(1);
}
}
只把GPIO相關的改成了寄存器操作,時鍾之類的還是用的HAL。