意法半導體在推出STM32微控制器之初,也同時提供了一套完整細致的固件開發包,里面包含了在STM32開發過程中所涉及到的所有底層操作。通過在程序開發中引入這樣的固件開發包,可以使開發人員從復雜冗余的底層寄存器操作中解放出來,將精力專注應用程序的開發上,這便是ST推出這樣一個開發包的初衷。
但這對於許多從51/AVR這類單片機的開發轉到STM32平台的開發人員來說,勢必有一個不適應的過程。因為程序開發不再是從寄存器層次起始,而要首先去熟悉STM32所提供的固件庫。那是否一定要使用固件庫呢?當然不是。但STM32微控制器的寄存器規模可不是常見的8位單片機可以比擬,若自己細細琢磨各個寄存器的意義,必然會消耗相當的時間,並且對於程序后續的維護,升級來說也會增加資源的消耗。對於當前“時間就是金錢”的行業競爭環境,無疑使用庫函數進行STM32的產品開發是更好的選擇。本文將通過一個簡單的例子對STM32的庫函數做一個簡單的剖析。
以最常用的GPIO設備的初始化函數為例,如下程序段一:
GPIO_InitTypeDef GPIO_InitStructure; 1
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; 2
GPIO_InitStructure.GPIO_Speed =
GPIO_Speed_50MHz; 3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 4
GPIO_Init(GPIOA , &GPIO_InitStructure 5
這是一個在STM32的程序開發中經常使用到的GPIO初始化程序段,其功能是將GPIOA.4口初始化為推挽輸出狀態,並最大翻轉速率為50MHz。下面逐一分解:
l 首先是1,該語句顯然定義了一個GPIO_InitTypeDef類型的變量,名為GPIO_InitStructure,則找出GPIO_InitTypeDef的原型位於“stm32f10x_gpio.h”文件,原型如下:
typedef struct
{
u16 GPIO_Pin;
GPIOSpeed_TypeDef GPIO_Speed;
GPIOMode_TypeDef GPIO_Mode;
}GPIO_InitTypeDef;
由此可知GPIO_InitTypeDef是一個結構體類型同義字,其功能是定義一個結構體,該結構體有三個成員分別是u16類型的GPIO_Pin、GPIOSpeed_TypeDef 類型的GPIO_Speed和GPIOMode_TypeDef 類型的GPIO_Mode。繼續探查GPIOSpeed_TypeDef和GPIOMode_TypeDef類型,在“stm32f10x_gpio.h”文件中找到對GPIOSpeed_TypeDef的定義:
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
則可知GPIOSpeed_TypeDef枚舉類型同一只,其功能是定義一個枚舉類型變量,該變量可表示GPIO_Speed_10MHz、GPIO_Speed_2MHz和GPIO_Speed_50MHz三個含義(其中GPIO_Speed_10MHz已經定義為1,讀者必須知道GPIO_Speed_2MHz則依次被編譯器賦予2,而GPIO_Speed_50MHz為3)。
同樣也在“stm32f10x_gpio.h”文件中找到對GPIOMode_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_Mode_AIN、GPIO_Mode_AF_OD等(也可以輕易判斷出這表示GPIO設備的工作模式)。
至此對程序段一的○1解析可以做一個總結:
該行定義一個結構體類型的變量GPIO_InitStructure,並且該結構體有3個成員,分別為GPIO_Pin、GPIO_Speed和GPIO_Mode,並且GPIO_Pin表示GPIO設備引腳GPIO_Speed表示GPIO設備速率和GPIO_Mode表示GPIO設備工作模式。
接下來是2,此句是一個賦值語句,把GPIO_Pin_4賦給GPIO_InitStructure結構體中的成員GPIO_Pin,可以在“stm32f10x_gpio.h”文件中找到對GPIO_Pin_4做的宏定義:
#define GPIO_Pin_4 ((u16)0x0010)
因此○2的本質是將16位數0x0010賦給GPIO_InitStructure結構體中的成員GPIO_Pin。
3語句和2相似將GPIO_Speed_50MHz賦給GPIO_InitStructure結構體中的成員GPIO_Speed,但注意到此處GPIO_Speed_50MHz只是一個枚舉變量,並非具體的某個值。
4語句亦和2語句類似,把GPIO_Mode_Out_PP賦給GPIO_InitStructure結構體中的成員GPIO_Mode,從上文可知GPIO_Mode_Out_PP的值為0x10。
5是一個函數調用,即調用GPIO_Init函數,並提供給該函數2個參數,分別為GPIOA和&GPIO_InitStructure,其中&GPIO_InitStructure表示結構體變量GPIO_InitStructure的地址,而GPIOA則在“stm32f10x_map.h”文件中找到定義:
#ifdef _GPIOA
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#endif
此三行代碼是一個預編譯結構,首先判斷是否定義了宏_GPIOA。可以在“stm32f10x_conf.h”中發現對_GPIOA的定義為:
#define _GPIOA
這表示編譯器會將代碼中出現的GPIOA全部替換為((GPIO_TypeDef *) GPIOA_BASE)。從該句的C語言語法可以判斷出((GPIO_TypeDef *) GPIOA_BASE)的功能為將GPIOA_BASE強制類型轉換為指向GPIO_TypeDef類型的結構體變量。如此則需要找出GPIOA_BASE的含義,依次在“stm32f10x_map.h”文件中找到:
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
和:
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
還有:
#define PERIPH_BASE ((u32)0x40000000)
明顯GPIOA_BASE表示一個地址,通過將以上3個宏展開可以得到:
GPIOA_BASE = 0x40000000 + 0x10000 + 0x0800
此處的關鍵便在於0x40000000、0x10000和0x0800這三個數值的來歷。讀者應該通過宏名猜到了,這就是STM32微控制器的GPIOA的設備地址。通過查閱STM32微控制器開發手冊可以得知,STM32的外設起始基地址為0x40000000,而APB2總線設備起始地址相對於外設基地址的偏移量為0x10000,GPIOA設備相對於APB2總線設備起始地址偏移量為0x0800。
對○5句代碼進行一個總結:調用GPIO_Init函數,並將STM32微控制器的GPIOA設備地址和所定義的結構體變量GPIO_InitStructure的地址傳入。
以上是對GPIOA初始化庫函數的剖析,現繼續轉移到函數內部分析,GPIO_Init函數原型如程序段二:
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
u32 currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
u32 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 = ((u32)GPIO_InitStruct->GPIO_Mode) & ((u32)0x0F);
if ((((u32)GPIO_InitStruct->GPIO_Mode) & ((u32)0x10)) != 0x00)
{
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
currentmode |= (u32)GPIO_InitStruct->GPIO_Speed;
}
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;
if (currentpin == pos)
{
pos = pinpos << 2;
pinmask = ((u32)0x0F) << pos;
tmpreg &= ~pinmask;
tmpreg |= (currentmode << pos);
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((u32)0x01) << pinpos);
}
else
{
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((u32)0x01) << pinpos);
}
}
}
}
GPIOx->CRL = tmpreg;
}
if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
{
tmpreg = GPIOx->CRH;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = (((u32)0x01) << (pinpos + 0x08));
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
if (currentpin == pos)
{
pos = pinpos << 2;
pinmask = ((u32)0x0F) << pos;
tmpreg &= ~pinmask;
tmpreg |= (currentmode << pos);
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((u32)0x01) << (pinpos + 0x08));
}
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((u32)0x01) << (pinpos + 0x08));
}
}
}
GPIOx->CRH = tmpreg;
}
}
這段程序的流程是:首先檢查由結構體變量GPIO_InitStructure所傳入的參數是否正確,然后對GPIO寄存器進行“保存——修改——寫入”的操作,完成對GPIO設備的設置工作。顯然,結構體變量GPIO_InitStructure所傳入參數的目的是設置對應GPIO設備的寄存器。而STM32的參考手冊對關於GPIO設備的設置寄存器的描述如下圖1和表1(僅列出低八位引腳寄存器描述,高八位引腳類同):
[attach]65378[/attach]
該寄存器為32位,其中分為8份,每份4位,對應低八位引腳的設置。每一個引腳的設置字分為兩部分,分別為CNF和MODE,各占兩位空間。當MODE的設置字為0時,表示將對應引腳配置為輸入模式,反之設置為輸出模式,並有最大翻轉速率限制。而當引腳配置為輸出模式時,CNF配置字則決定引腳以哪種輸出方式工作(通用推挽輸出、通用開漏輸出等)。通過對程序的閱讀和分析不難發現,本文最初程序段中GPIO_InitStructure所傳入參數的對寄存器的作用如下:
1、GPIO_Pin_4被宏替換為0x0010,對應圖1可看出為用於選擇配置GPIOx_CRL的[19:16]位,分別為CNF4[1:0]、MODE4[1:0]。
2、GPIO_Speed_50MHz為枚舉類型,包含值0x03,被用於將GPIOx_CRL位中的MODE4[1:0]配置為b11(此處b意指二進制)。
3、GPIO_Mode亦為枚舉類型,包含值0x10,被用於將GPIOx_CRL位中的MODE4[1:0]配置為b00。事實上GPIO_Mode的值直接影響寄存器的只有低四位,而高四位的作用可以從程序段二中看出,是用於判斷此參數是否用於GPIO引腳輸出模式的配置。
至此應不難知道STM32的固件庫最后是怎樣影響最底層的寄存器的。總結起來就是:固件庫首先將各個設備所有寄存器的配置字進行預先定義,然后封裝在結構或枚舉變量中,待用戶調用對應的固件庫函數時,會根據用戶傳入的參數從這些封裝好的結構或枚舉變量中取出對應的配置字,最后寫入寄存器中,完成對底層寄存器的配置。
可以看到,STM32的固件庫函數對於程序開發人員來說是十分便利的存在,只需要填寫言簡意賅的參數就可以在完全不關心底層寄存器的前提下完成相關寄存器的配置,具有相當不錯的通用性和易用性,也采取了一定措施保證庫函數的安全性(主要引入了參數檢查函數assert_param)。但同時也應該知道,通用性、易用性和安全性的代價是加大了代碼量,同時增加了一些邏輯判斷代碼造成了一定的時間消耗,在對時間要求比較苛刻的應用場合需要評估使用固件庫函數對程序運行時間所帶來的影響。讀者在使用STM32的固件庫函數進行程序開發時,應該意識到這些問題。
