STM32:GPIO口的使用


前言

  "GPIO的使用1"中主要從內核代碼開始,從寄存器的地址映射開始,對GPIO的封裝和操作執行邏輯詳細分析了一下;

  內核的函數接口標准是都是一樣的CMSIS,了解了GPIO外設的原理,也就了解了其他外設是如何封裝的;

  GPIO使用時先確定是否為外設復用;目的是確定輸入輸出數據是給外設處理,還是存放在GPIO寄存器里就完了;

  然后確定IO的輸入輸出模式;目的是通過軟件配置,選擇端口在芯片內部的電路連接方式;

  GPIO上電默認為浮空輸入模式,禁止了上下拉電阻;上下拉電阻默認30-50kΩ;保護二極管防止電流反向擊穿;

  0.1 輸入模式

    上拉輸入:使能上拉電阻的連接,斷開下拉電阻的連接;

    下拉輸入:斷開上拉電阻的連接,使能下拉電阻的連接;

    模擬輸入:將IO引腳連接至內部ADC;  

  0.2 輸出模式

    推挽輸出:將P-MOS管和N-MOS管以推挽方式連接;通過兩個MOS管的導通與截止來輸出高低電平;配置上下拉電阻不使能;

            特點是既可以消耗負載的拉電流,也可以向負載輸出拉電流,開關時間快;

    開漏輸出:P-MOS管始終截止,通過N-MOS管結合上下拉電阻,控制輸出高低電平;配置上下拉電阻同時使能;

            特點是輸出高電平的驅動能力完全由上拉電阻決定,輸出低電平的驅動能力十分穩定;

  0.3 可以將多個開漏輸出並連至同一個上拉電阻,形成"線與"邏輯;當其中一個開漏輸出輸出低電平時,相當於並聯回路被導線短路;其他輸出也被接到地了;

  0.4 TSM32H7IO口總的電流最大值為140mA,單個IO口的電流最大值為20mA;具體硬件參數見數據手冊;

  0.5 TTL和CMOS電平標准手冊可以查看安富萊論壇:http://www.armbbs.cn/forum.php?mod=viewthread&tid=87676

1 GPIO port

  STM32一共有7組GPIO port,分別是GPIOA[15:0]~GPIOG[15:0],每組GPIO port 有16個 pin;每組GPIO port都有一組寄存器;

  GPIO寄存器的控制單位是GPIO port,而不是pin;所以寄存器的最小處理單位是一個16位的字長(0xFFFF);

  至於寄存器的配置我們之后小節在解析,首先來了解一下標准庫是如何將GPIO映射到地址上的;

  

/*stm32f10x.h 1408-1414行聲明如下*/
#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC               ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD               ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE               ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF               ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG               ((GPIO_TypeDef *) GPIOG_BASE)

/* stm32f10x.h  1001-1010行;
*把結構體的首地址映射到GPIO的首寄存器地址,就可以通過該結構體對硬件寄存器操作;
*結構體的地址通過結構體指針來賦值對應上*/
#define __IO  volatile /*core_cm3.h  NO.116*/
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;

/*stm32f10x.h 1315-1321;*/
#define GPIOA_BASE            (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE            (APB2PERIPH_BASE + 0x0C00)
#define GPIOC_BASE            (APB2PERIPH_BASE + 0x1000)
#define GPIOD_BASE            (APB2PERIPH_BASE + 0x1400)
#define GPIOE_BASE            (APB2PERIPH_BASE + 0x1800)
#define GPIOF_BASE            (APB2PERIPH_BASE + 0x1C00)
#define GPIOG_BASE            (APB2PERIPH_BASE + 0x2000)

/*stm32f10x.h 1282-1283; GPIO都屬於APB2總線,使用的時候要使能APB2總線的時鍾源;*/
#define APB1PERIPH_BASE       PERIPH_BASE
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)

/*stm32f10x.h 1274*/
#define PERIPH_BASE           ((uint32_t)0x40000000)

2 GPIO 寄存器

  GPIO涉及的寄存器較多,復用功能和重映射功能都需要配置專門的AFIO寄存器,但是本節暫時沒有涉及;

  本節主要介紹了通用GPIO口涉及到的7個寄存器,具體寄存器的說明和使用如下;

  2.1 CRL端口配置低寄存器,CRH端口配置高寄存器 control register low,control register high;

    CRL和CRH都是32位寄存器,如下圖所示,一起用來控制該組GPIO port的16個引腳配置;

   

    2.1.1 CRL和CRH寄存器復位值為0x4444_4444;CRL偏移地址:0x00,CRH偏移地址:0x04;配置信息封裝如下;

//定義了CRH和CRL寄存器需要的參數;以下聲明在stm32f10x_gpio.h的前200行;
typedef struct
{
  uint16_t GPIO_Pin;                /*用16位bit的每一位分別表示一個引腳*/            
  GPIOSpeed_TypeDef GPIO_Speed;     /*用2位bit來表示輸出模式的最大速度*/ 
  GPIOMode_TypeDef GPIO_Mode;       /*CNF MODE,具體見結構體*/
}GPIO_InitTypeDef;

#define GPIO_Pin_0                 ((uint16_t)0x0001)  /*0000 0000 0000 0001b*/
#define GPIO_Pin_1                 ((uint16_t)0x0002)  /*0000 0000 0000 0010b*/
#define GPIO_Pin_2                 ((uint16_t)0x0004)  /*0000 0000 0000 0100b*/
#define GPIO_Pin_3                 ((uint16_t)0x0008)  /*0000 0000 0000 1000b*/
#define GPIO_Pin_4                 ((uint16_t)0x0010)  /*0000 0000 0001 0000b*/
#define GPIO_Pin_5                 ((uint16_t)0x0020)  /*0000 0000 0010 0000b*/
#define GPIO_Pin_6                 ((uint16_t)0x0040)  /*0000 0000 0100 0000b*/
#define GPIO_Pin_7                 ((uint16_t)0x0080)  /*0000 0000 1000 0000b*/
#define GPIO_Pin_8                 ((uint16_t)0x0100)  /*0000 0001 0000 0000b*/
#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 IS_GPIO_PIN(PIN) ((((PIN) & (uint16_t)0x00) == 0x00) && ((PIN) != (uint16_t)0x00))

typedef enum
{ 
  GPIO_Speed_10MHz = 1,     /*output MODE[1:0]*/
  GPIO_Speed_2MHz,          /*output MODE[1:0]*/
  GPIO_Speed_50MHz          /*output MODE[1:0]*/
}GPIOSpeed_TypeDef;
#define IS_GPIO_SPEED(SPEED) (((SPEED) == GPIO_Speed_10MHz) || ((SPEED) == GPIO_Speed_2MHz) || \
                              ((SPEED) == GPIO_Speed_50MHz))
                              
typedef enum
{ GPIO_Mode_AIN = 0x0,                /*0000 0000b [4]0 input [3:0]CNF+MODE*/
  GPIO_Mode_IN_FLOATING = 0x04,       /*0000 0100b [4]0 input [3:0]CNF+MODE*/
  GPIO_Mode_IPD = 0x28,               /*0010 1000b [4]0 input [5]下拉,[3:0]CNF+MODE*/
  GPIO_Mode_IPU = 0x48,               /*0100 1000b [4]0 input [6]上拉,[3:0]CNF+MODE*/
  GPIO_Mode_Out_OD = 0x14,            /*0001 0100b [4]1 output,[3:2]CNF*/
  GPIO_Mode_Out_PP = 0x10,            /*0001 0000b [4]1 output,[3:2]CNF*/
  GPIO_Mode_AF_OD = 0x1C,             /*0001 1100b [4]1 output,[3:2]CNF*/
  GPIO_Mode_AF_PP = 0x18              /*0001 1000b [4]1 output,[3:2]CNF*/
}GPIOMode_TypeDef; 
#define IS_GPIO_MODE(MODE) (((MODE) == GPIO_Mode_AIN) || ((MODE) == GPIO_Mode_IN_FLOATING) || \
                            ((MODE) == GPIO_Mode_IPD) || ((MODE) == GPIO_Mode_IPU) || \
                            ((MODE) == GPIO_Mode_Out_OD) || ((MODE) == GPIO_Mode_Out_PP) || \
                            ((MODE) == GPIO_Mode_AF_OD) || ((MODE) == GPIO_Mode_AF_PP))
#define IS_GET_GPIO_PIN(PIN) (((PIN) == GPIO_Pin_0) || ((PIN) == GPIO_Pin_1) ||((PIN) == GPIO_Pin_2) || \
                              ((PIN) == GPIO_Pin_3) || ((PIN) == GPIO_Pin_4) || ((PIN) == GPIO_Pin_5) || \
                              ((PIN) == GPIO_Pin_6) || ((PIN) == GPIO_Pin_7) || ((PIN) == GPIO_Pin_8) || \
                              ((PIN) == GPIO_Pin_9) || ((PIN) == GPIO_Pin_10) || ((PIN) == GPIO_Pin_11) || \
                              ((PIN) == GPIO_Pin_12) ||((PIN) == GPIO_Pin_13) || ((PIN) == GPIO_Pin_14) || \
                              ((PIN) == GPIO_Pin_15))

typedef enum
{ Bit_RESET = 0,
  Bit_SET
}BitAction;

    2.1.2 配置CRL和CRH的初始化代碼如下;

 /*以下代碼位於stm32f10x_gpio.c中,配置相應port的CRL和CRH*/
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;
  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));  
  
  currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F); //currentmode保留了GPIO_Mode[3:0];

  if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)//如果GPIO_Mode[4]為1,表示為輸出模式;
  { 
     /*if(輸出模式),將CNF[1:0]和MODE[1:0]的信息保存到currentmode[3:0]*/
    assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
    currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;//currentmode或上GPIO_Speed[1:0];
  }
  
  /*以下部分為CRL Configuration*/
  if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)        //if(是低8位pin);
  {
    tmpreg = GPIOx->CRL;                                //temreg存放CRL寄存器的信息;
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)        //pinpos為幾,表示引腳幾;        
    {
      pos = ((uint32_t)0x01) << pinpos;                    
      currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;    //currentpin:要么為當前pin值,要么為0;
      if (currentpin == pos)
      {
        pos = pinpos << 2;                                //pos:引腳對應的CRL配置位
        pinmask = ((uint32_t)0x0F) << pos;                //pinmask:引腳對應的CRL[3:0]置1
        tmpreg &= ~pinmask;                                //temreg中對應引腳的[3:0]清0
        tmpreg |= (currentmode << pos);                    //temreg中對應引腳的[3:0]配置成currentmode[3:0]

        //此處的if else應該是通過ODR來配置硬件,由中文參考手冊8.1.7原理圖推測可知       
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD) //if(輸入連下拉電阻)
        {
          GPIOx->BRR = (((uint32_t)0x01) << pinpos);     //通過配置ODR的16bit,對應pin的bit置0,連接下拉電阻;
        }
        else
        {
          if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)//if(輸入連上拉電阻)
          {
            GPIOx->BSRR = (((uint32_t)0x01) << pinpos);     //通過配置ODR的16bit,對應pin的bit置1,連接上拉電阻;
          }
        }
      }
    }
    GPIOx->CRL = tmpreg;  //把配置好對應pin腳的temreg放回CRL中
  }
  
/*---------------------------- 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;
  }
}

    2.1.3 推挽輸出,開漏輸出,上拉輸入和下拉輸入的原理推薦參考安富萊文檔的解釋,以及 https://www.cnblogs.com/tkxja/p/8708732.html

  2.2 LCKR端口配置鎖定寄存器:lock register

    復位值為0x0000_0000;偏移地址:0x18;

    用來鎖存對應端口的CRL,CRH寄存器的配置;修改完LCK[15:0]然后鎖定[LCKK],對應的CRL,CRH配置將會持續到下次系統復位信號來臨;

    

  2.3 IDR端口輸入數據寄存器,ODR端口輸出數據寄存器:input data register,output data register;

    復位值為0x0000_0000;IDR偏移地址:0x08;ODR偏移地址:0x0C

    對於輸入數據而言,每個APB2時鍾會采樣I/O腳上的數據存入數據寄存器中,對寄存器的讀取可以獲得輸入數據;

    對於輸出數據而言,應該也是通過APB2時鍾控制,把數據放入ODR即可;

    

    2.3.1 IDR寄存器的使用函數

/*以下代碼位於stm32f10x_gpio.c中
*1 讀取IDR寄存器某一位的值,即pin值;
*2 讀取IDR寄存器的值,即port值;
*/ uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) { uint8_t bitstatus = 0x00; /* Check the parameters */ assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); assert_param(IS_GET_GPIO_PIN(GPIO_Pin)); /*先讀取整個IDR寄存器,然后通過&來讀取bit,IDR寄存器的最小處理單位是16bit*/ if ((GPIOx->IDR & GPIO_Pin) != (uint32_t)Bit_RESET) { bitstatus = (uint8_t)Bit_SET; } else { bitstatus = (uint8_t)Bit_RESET; } return bitstatus; } uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx) { /* Check the parameters */ assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); return ((uint16_t)GPIOx->IDR); }

    2.3.2 ODR寄存器的使用函數

/*以下代碼位於stm32f10x_gpio.c中;
*1 讀取ODR寄存器某一位的值;即讀取pin值;
*2 讀取ODR寄存器的值;即讀取port值;
*3 向ODR寄存器寫入port值;
*/ uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) { uint8_t bitstatus = 0x00; assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); assert_param(IS_GET_GPIO_PIN(GPIO_Pin)); /*先讀取整個ODR寄存器,然后通過&來讀取bit,ODR寄存器的最小處理單位是16bit*/ if ((GPIOx->ODR & GPIO_Pin) != (uint32_t)Bit_RESET){ bitstatus = (uint8_t)Bit_SET; } else{ bitstatus = (uint8_t)Bit_RESET; } return bitstatus; } uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx) { assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); return ((uint16_t)GPIOx->ODR); } void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal) { assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); GPIOx->ODR = PortVal; }

  2.4 BSRR端口置位/復位寄存器,BRR端口復位寄存器:bit set/reset register,bit reset register;

    復位值為0x0000_0000;BSRR偏移地址:0x10;BRR偏移地址:0x14;

    對BSRR,BRR的操作,就是對該組GPIO口的ODR寄存器寄存器的操作;

    

    2.4.1 BSRR寄存器:pin置1

void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_PIN(GPIO_Pin));
  
  GPIOx->BSRR = GPIO_Pin;
}

    2.4.2 BRR寄存器:pin清0

void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_PIN(GPIO_Pin));
  
  GPIOx->BRR = GPIO_Pin;
}

    2.4.3 設置pin腳相應的數值:pin的置1清0

void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal)
{
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GET_GPIO_PIN(GPIO_Pin));
  assert_param(IS_GPIO_BIT_ACTION(BitVal)); 
  
  if (BitVal != Bit_RESET)
  {
    GPIOx->BSRR = GPIO_Pin;
  }
  else
  {
    GPIOx->BRR = GPIO_Pin;
  }
}

  雖然BSRR和BRR都可以用來設置單獨bit位,但是它們也是通過16bit長度來設置的,不要被函數具有迷惑性的名字給欺騙;

3 GPIO的復用和重映射寄存器

  3.1 GPIO的復用

    復用功能具體見中文參考手冊8.1.11小節;

    看起來只要把輸入輸出模式配置成相應的外設模式,就可以使用外設了;

    看起來好像沒有區分通用GPIO模式和復用GPIO模式啊,而且有的引腳有兩三個復用功能的,感覺結構比較瑣碎;;

  3.2 GPIO的重映射

    重映射功能具體見<中文參考手冊>8.3和8.4小節;

    8.3小節主要是列出了外設的重映射引腳;8.4小節是外設的重映射寄存器的配置;

    3.1.1 外設的重映射功能需要通過AFIO_MAPR寄存器來映射到GPIO口;然后配置GPIO口復用該外設;這時候就可以使用該外設了;

    3.1.2 芯片為GPIO的復用外設提供了16個可設置的中斷,對應16個引腳號;

      AFIO_EXTICRx中斷寄存器配置端口號;EXTICR1配置引腳[3:0]的端口號,EXTICR4配置引腳[15:12]的端口號;

    3.1.3 外設應該有外設自己的中斷函數的吧,這里為什么又為復用的外設提供了16個中斷呢?這些中斷對應哪些中斷處理函數呢?

  3.3 位帶操作

    另外標准庫還為GPIO口提供了位帶操作,主要就是有兩個區域地址塊的寄存器是可以直接以bit為單位進行設置;

    相當於給GPIO開了個小灶方便某些不想使用標准庫的人可以直接設置寄存器,個人不太中意這個功能;

4 通用GPIO的示例代碼

  配置GPIO端口,在寄存器層面來說:首先使能所在外設的時鍾源,然后配置完CRL和CRH后GPIO口就可以使用了;

  如果GPIO pin 配置成了輸入引腳,則從IDR讀取數據即可;

  如果GPIO pin 配置成了輸出引腳,輸出的信號可以直接寫入ODR寄存器,也可以通過BSRR和BRR來設置;

#include "delay.h"

int main(void)
{ 
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitTypeDef GPIO_Struct;

    //portA pin1 as input;     
    GPIO_Struct.GPIO_Pin = GPIO_Pin_1;             
    GPIO_Struct.GPIO_Mode =GPIO_Mode_IPD ;     
    GPIO_Init(GPIOA, &GPIO_Struct); 
    
    //portA pin2 as output;                        
    GPIO_Struct.GPIO_Pin = GPIO_Pin_2; 
    GPIO_Struct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Struct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOA, &GPIO_Struct);                                          
    while(1)
    {
        GPIO_SetBits(GPIOA,GPIO_Pin_2);
        delay_ms(100);
        GPIO_ResetBits(GPIOA,GPIO_Pin_2);
        delay_ms(100);
    } 
}

5 小結

  本文主要是結合通用GPIO口的寄存器,對標准庫中通用GPIO口的代碼進行了分析和概括;

  然后提供了一個代碼示例;如果只是使用而不想理解標准庫原理,則直接使用接口函數即可,如代碼示例所示;

  本文主要參考文檔為正點原子的<STM32F1開發指南_庫函數版本V3.3>,<STM32F1xxx中文參考手冊>的第8章;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM