1. 程式人生 > >stm32深入淺出——由GPIO談談暫存器配置

stm32深入淺出——由GPIO談談暫存器配置

相信大家對GPIO的配置並不陌生,只需簡單的幾個庫函式就能完成。而本菜今天要講的不是怎麼用這些庫函式,而是要講講這些庫函式是怎麼工作的。本菜留意了下,無論是網上還是書籍,涉及這方面的知識很少,直接抄了使用手冊就上了。那麼本菜在這裡就詳細講一講,做些補充,希望能幫助到大家。

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;

GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;

GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;

GPIO_Init(GPIOD, &GPIO_InitStructure);

 這幾行程式碼是司空見慣了。我們先從它們入手。

GPIO_InitStructure  看名字,就知道是個結構體,在主檔案裡是這樣宣告的:

GPIO_InitTypeDef  GPIO_InitStructure;

於是乎,我們再看看GPIO_InitTypeDef 是什麼東西吧。

typedef struct

{

  u16 GPIO_Pin;

  GPIOSpeed_TypeDef GPIO_Speed;

  GPIOMode_TypeDef GPIO_Mode;

}GPIO_InitTypeDef;

其中各有宣告為:

typedef enum

  GPIO_Speed_10MHz = 1,

  GPIO_Speed_2MHz, 

  GPIO_Speed_50MHz

}GPIOSpeed_TypeDef;

//================================================================//

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;

    我們可以發現,這些配置資訊都有它們的固定數值。這些數值,代表什麼意義呢?我們接下去看看GPIO_Init(GPIOD, &GPIO_InitStructure)這個函式吧。

先看引數的型別GPIO_TypeDef* (GPIO_InitTypeDef之前已經有講解) :

GPIO_TypeDef  :

typedef struct

{

  vu32 CRL;

  vu32 CRH;

  vu32 IDR;

  vu32 ODR;

  vu32 BSRR;

  vu32 BRR;

  vu32 LCKR;

} GPIO_TypeDef;

    這裡有幾個暫存器需要簡單講解的:CRL是低位配置暫存器,是用來儲存低位資料(低8位)的配置情況。CRH是高位暫存器。ODR是寫入輸出暫存器,在配置為輸出時,該暫存器的值就輸出到I/O引腳。IDR是輸入資料暫存器,在每個APB2時鐘週期讀取並捕獲對應I/O口資料。BSRR暫存器是32位置/復位暫存器,高16位可以對2位元組(16位)的ODR上對應位進行位操作,低16位則進行復位操作,一般都用這個暫存器對I/O口的輸出進行操作,當然也可以通過修改ODR暫存器來實現,像在這個庫函式裡就是通過BSRR來實現的。BRR是16位復位暫存器,只能進行復位操作。LCKR是埠鎖定暫存器,開鎖定以後,對該埠的某位的配置修改不可行。具體請參照STM32F10X使用手冊。

然後另一個實參(舉例):GPIOD  查詢其宣告,就可以發現:  #define GPIOD         ((GPIO_TypeDef *) GPIOD_BASE)

就和之前的例子一樣,是以GPIOD_BASE為基地址的結構體。那麼當你再查詢GPIOD_BASE的定義時,就會發現其值為0x40011800,也即:

這是什麼意思呢?就是指管理GPIOD的暫存器的初始地址就是0x40011800。那麼我們再來看看第一個暫存器CRL的資訊:

我們發現CRL的偏移地址是00h,也即其是基地址上的第一個暫存器組。仔細一看便知,這個暫存器組包含32個暫存器,也即佔了32位空間。我們再看CRL在結構體的型別是vu32,就能明白,這些空間分配,是遵循著線性規律的。當我們再看CRH,也就是第二個暫存器組時,不難發現其偏移地址是04h,也即4*8=32位的偏移地址。這和我們之前的分析是完全符合的。

    再回到程式中,可以發現I/O口的配置是分高低8位的,原因在於GPIO的配置暫存器分為高位配置暫存器CRH和低位配置CRL。在STM32中通常一個暫存器有32位,在這裡卻用了2個暫存器來配置GPIO,說明一個I/O口需要4個位來配置。就僅僅以CRL為例,來看繼續看上圖。

    我們再看GPIO_PIN_x的定義:

從中不難發現一些規律。看官心中有些明瞭之後,我們再回到程式碼中,只看這幾句:

if (((u32)GPIO_InitStruct->GPIO_Pin & ((u32)0x00FF)) != 0x00)

  {

    tmpreg = GPIOx->CRL;

    for (pinpos = 0x00; pinpos < 0x08; pinpos++)

    {

      pos = ((u32)0x01) << pinpos;

      currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;

    我們大致揣摩,就能得出這幾句語句是用來選擇當前的引腳的。這也就是為何上面GPIO_PIN_x的定義是以2的倍數遞增。當然,CRL這個暫存器的位的排列也是遵循這個規律的:

 這是PIN_0腳上的4個配置位,可見這是與它們名稱的尾號是一致的,MODEx和CNFx控制PIN_x。掌握這個規律,後面幾條語句自然容易推敲出來是什麼意思。

    不過,有些不同尋常的是:

// Reset the corresponding ODR bit 

        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)

        {

          GPIOx->BRR = (((u32)0x01) << (pinpos + 0x08));

        }

        //Set the corresponding ODR bit 

        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)

        {

          GPIOx->BSRR = (((u32)0x01) << (pinpos + 0x08));

        }

很匪夷所思,根據判斷的條件,顯然是要選擇下拉輸入還是上拉輸入。但它居然需要對輸出資料復位置位暫存器BRR和BSRR來操作。這是為何?不管那麼多,先看手冊:

在CNFX[1:0]中,顯然不能選定上拉和下拉(MODEX[1:0]同樣也沒有),再看輸入口配置電路圖:

    必須有一個控制器控制著這個ON/OFF。那麼在下圖中,我們就可以找到答案:

這下了然了吧!ODR暫存器是參與選擇上拉和下拉模式的。

    現在大家可以通過寫暫存器來做一些像點LED燈的簡單操作了。雖說直接使用庫函式絕對是很快捷的方法,但是瞭解了暫存器的功能,目的是為了未來要使用這些庫函式時能得心應手。我們學習這些暫存器不是為了擺脫庫函式,而是為了更好地利用庫函式。