2. GPIO讀寫

凪风sama發表於2024-07-29

GPIO簡介

GPIO(全稱為General Purpose Input/Output),即通用輸入/輸出,可以認為GPIO是片外外設與片內的通訊介面,透過控制GPIO的電平狀態,可以實現片外外設與片內的通訊以及資料的輸入輸出。

對於st32F103系列的GPIO,其命名規則為GPIO+埠號,如GPIOA,GPIOB。對於每個埠又有著16個引腳(pin),編號為0~15。

以下便是一個GPIO引腳的結構圖:
image

透過對不同暫存器的配置,可以切換GPIO的工作模式。

GPIO工作模式

從大致上分,GPIO有兩種工作模式:

  • 輸入模式:GPIO可以作為輸入,透過讀取GPIO的電平狀態來獲取外設的輸入訊號。
  • 輸出模式:GPIO可以作為輸出,透過控制GPIO的電平狀態來驅動外設的輸出訊號。

而輸入模式又可以分為:

  • 上拉輸入模式(GPIO_MODE_IPU):透過內部上拉電阻,引腳懸空時預設為高電平,當輸入低電平時,GPIO的電平狀態為低電平。
  • 下拉輸入模式(GPIO_MODE_IPD):透過內部下拉電阻,引腳懸空時預設為低電平,當輸入高電平時,GPIO的電平狀態為高電平。
  • 浮空輸入模式(GPIO_MODE_IN_FLOATING):內部無上拉電阻或下拉電阻,易受外部電平干擾,精度不高,可外接上拉或下拉電阻來實現輸入電平的上拉或下拉。
  • 模擬輸入模式(GPIO_MODE_AIN):透過ADC模組,將模擬訊號轉換為數字訊號,輸入到GPIO。

輸出模式又可以分為:

  • 推輓輸出模式(GPIO_MODE_OUT_PP):輸出高電平或低電平(三極體導通壓降),透過控制GPIO的電平狀態來驅動外設的輸出訊號。輸出的高低電平均有驅動能力。
  • 開漏輸出模式(GPIO_MODE_OUT_OD):該模式下,高電平無驅動能力(高阻態),低電平有驅動能力。
  • 複用推輓輸出模式(GPIO_MODE_AF_PP)和複用開漏輸出模式(GPIO_MODE_AF_OD),用到再學。

使用標準庫來實現GPIO讀寫

image

由上圖GPIO的結構可知,GPIO算是stm32的片內外設,且掛載在APB2匯流排上。在
stm32結構中,可以知道想要使用stm32的片內外設,必須先使能外設對應的時鐘,這樣才能讓外設執行。

這裡使用標準庫來實現GPIO的各種操作。

以下給出使用標準庫使能GPIOA第0引腳的例子。

#include "stm32f10x.h"

int main(void)
{
    // 使能GPIOA時鐘
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    // 設定GPIO的各種引數
    GPIO_InitTypeDef GPIO_InitStructure;
    // 設定GPIO為推輓輸出模式
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    // 設定GPIO初始化第0引腳 
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_0;
    // 設定GPIO速度為50MHz
    GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
    // 呼叫GPIO初始化函式,並指定初始化的GPIO,傳入引數結構
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    while(1)
    {

    }
}

RCC_APB2PeriphClockCmd函式中,傳入需要使能的外設名稱,具體引數的名稱可以在函式定義中找到,第二個引數選擇ENABLE或DISABLE,即使能或失能時鐘。

而在GPIO_Init函式中,需要指定初始化的GPIO代號,如GPIOA等,而第二個引數接受一個GPIO_InitTypeDef結構體,該結構體中包含了GPIO的各種引數,如GPIO模式、引腳號、速度等,需要提前定義並配置好該結構,並以結構指標的形式傳入。

值得注意的是GPIO_InitTypeDef的結構體變數一般命名為GPIO_InitStructure,這樣可以使得程式碼更加易讀。

初始化後,可以透過幾個庫函式來實現對應引腳的輸入輸出操作。

/*輸入讀取操作*/
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);

/*輸出設定操作*/
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);

在這些函式中,GPIOx引數代表GPIO的代號,如GPIOA等,GPIO_Pin引數代表GPIO的引腳號,如GPIO_Pin_0等。

對於有字尾Bits / Bit的函式,可以設定或讀取單個引腳的電平狀態,而對於沒有字尾的函式,可以設定或讀取整個埠的電平狀態。

最後實現使用GPIO讀取光敏感測器輸入,並透過輸入控制另一個GPIO的輸出來控制Led亮滅。

程式碼如下

#include "stm32f10x.h"                  // Device header
#define true 1
#define false 0
typedef unsigned int uint;

int main(void)
{   
    // 使能GPIOA, GPIOB時鐘
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
    
    // 配置GPIO模式
    GPIO_InitTypeDef GPIO_InitStructureA;
    GPIO_InitTypeDef GPIO_InitStructureB;

    // 配置輸入GPIOA_Pin_0, 連線到光敏感測器
    GPIO_InitStructureA.GPIO_Mode = GPIO_Mode_IPU; // 上拉輸入
    GPIO_InitStructureA.GPIO_Pin  = GPIO_Pin_0;
    GPIO_InitStructureA.GPIO_Speed= GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructureA);

    // 配置輸出GPIOB_Pin_0,控制Led亮滅
    GPIO_InitStructureB.GPIO_Mode = GPIO_Mode_Out_PP; 
    GPIO_InitStructureB.GPIO_Pin  = GPIO_Pin_0;
    GPIO_InitStructureB.GPIO_Speed= GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructureB);

    // 變數區
    uint8_t light = 0;
    while(true)
    {
        light = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
        if (light == 1)
            GPIO_WriteBit(GPIOB, GPIO_Pin_0, Bit_RESET);
        else
            GPIO_WriteBit(GPIOB, GPIO_Pin_0, Bit_SET);
    }
}

引腳連線: Led正極連線到3.3V,負極連線到GPIOB_Pin_0, 光敏感測器的VCC連線3.3V,GND連線GND,訊號端DO連線GPIOA_Pin_0。當未被遮擋時,DO端一直將引腳下拉至0,當被遮擋時,DO端將保持高電平。

相關文章