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燈的簡單操作了。雖說直接使用庫函式絕對是很快捷的方法,但是瞭解了暫存器的功能,目的是為了未來要使用這些庫函式時能得心應手。我們學習這些暫存器不是為了擺脫庫函式,而是為了更好地利用庫函式。