|
解析STM32的庫(kù)函數(shù)
意法半導(dǎo)體在推出 STM32 微控制器之初,也同時(shí)提供了一套完整細(xì)致的固件開(kāi)發(fā)包, 里面包含了在 STM32 開(kāi)發(fā)過(guò)程中所涉及到的所有底層操作。通過(guò)在程序開(kāi)發(fā)中引入這樣的 固件開(kāi)發(fā)包,可以使開(kāi)發(fā)人員從復(fù)雜冗余的底層寄存器操作中解放出來(lái),將精力專注應(yīng)用程 序的開(kāi)發(fā)上,這便是 ST 推出這樣一個(gè)開(kāi)發(fā)包的初衷。
但這對(duì)于許多從 51/AVR 這類單片機(jī)的開(kāi)發(fā)轉(zhuǎn)到 STM32 平臺(tái)的開(kāi)發(fā)人員來(lái)說(shuō),勢(shì)必有一 個(gè)不適應(yīng)的過(guò)程。因?yàn)槌绦蜷_(kāi)發(fā)不再是從寄存器層次起始,而要首先去熟悉 STM32 所提供 的固件庫(kù)。那是否一定要使用固件庫(kù)呢?當(dāng)然不是。但 STM32 微控制器的寄存器規(guī)?刹 是常見(jiàn)的 8 位單片機(jī)可以比擬,若自己細(xì)細(xì)琢磨各個(gè)寄存器的意義,必然會(huì)消耗相當(dāng)?shù)臅r(shí)間, 并且對(duì)于程序后續(xù)的維護(hù),升級(jí)來(lái)說(shuō)也會(huì)增加資源的消耗。對(duì)于當(dāng)前“時(shí)間就是金錢”的行 業(yè)競(jìng)爭(zhēng)環(huán)境,無(wú)疑使用庫(kù)函數(shù)進(jìn)行 STM32 的產(chǎn)品開(kāi)發(fā)是更好的選擇。本文將通過(guò)一個(gè)簡(jiǎn)單 的例子對(duì) STM32 的庫(kù)函數(shù)做一個(gè)簡(jiǎn)單的剖析。
以最常用的 GPIO 設(shè)備的初始化函數(shù)為例,如下程序段一:
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
這是一個(gè)在 STM32 的程序開(kāi)發(fā)中經(jīng)常使用到的 GPIO 初始化程序段,其功能是將 GPIOA.4 口 初始化為推挽輸出狀態(tài),并最大翻轉(zhuǎn)速率為50MHz。
下面逐一分解:
首先是○1 ,該語(yǔ)句顯然定義了一個(gè) 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 是一個(gè)結(jié)構(gòu)體類型同義字,其功能是定義一個(gè)結(jié)構(gòu)體,該結(jié) 構(gòu)體有三個(gè)成員分別是 u16 類型的 GPIO_Pin、GPIOSpeed_TypeDef類型的 GPIO_Speed 和 GPIOMode_TypeDef 類 型 的 GPIO_Mode 。 繼 續(xù) 探 查 GPIOSpeed_TypeDef 和 GPIOMode_TypeDef 類型,在“stm32f10x_gpio.h”文件中找到對(duì) GPIOSpeed_TypeDef 的 定義:
typedef enum
{
GPIO_Speed_10MHz = 1, GPIO_Speed_2MHz, GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
則可知 GPIOSpeed_TypeDef 枚舉類型同一只,其功能是定義一個(gè)枚舉類型變量,該變量 可表示 GPIO_Speed_10MHz、GPIO_Speed_2MHz 和GPIO_Speed_50MHz 三個(gè)含義(其中 GPIO_Speed_10MHz 已經(jīng)定義為 1,讀者必須知道 GPIO_Speed_2MHz 則依次被編譯器賦 予 2,而 GPIO_Speed_50MHz為 3)。
同樣也在“stm32f10x_gpio.h”文件中找到對(duì) 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;
這同樣是一個(gè)枚舉類型同義字,其成員有 GPIO_Mode_AIN、GPIO_Mode_AF_OD 等(也 可以輕易判斷出這表示 GPIO 設(shè)備的工作模式)。
至此對(duì)程序段一的○1 解析可以做一個(gè)總結(jié):
該行定義一個(gè)結(jié)構(gòu)體類型的變量 GPIO_InitStructure,并且該結(jié)構(gòu)體有 3 個(gè)成員,分別為 GPIO_Pin、GPIO_Speed 和 GPIO_Mode,并且 GPIO_Pin 表示 GPIO 設(shè)備引腳 GPIO_Speed 表示 GPIO 設(shè)備速率和 GPIO_Mode 表示 GPIO 設(shè)備工作模式。
接下來(lái)是○2 ,此句是一個(gè)賦值語(yǔ)句,把 GPIO_Pin_4 賦給 GPIO_InitStructure 結(jié)構(gòu)體中的 成員 GPIO_Pin,可以在“stm32f10x_gpio.h”文件中找到對(duì)GPIO_Pin_4 做的宏定義:
#define GPIO_Pin_4 ((u16)0x0010)
因此○2 的本質(zhì)是將 16 位數(shù) 0x0010 賦給 GPIO_InitStructure 結(jié)構(gòu)體中的成員 GPIO_Pin。
○3 語(yǔ) 句和 ○2 相似將 GPIO_Speed_50MHz 賦給 GPIO_InitStructure 結(jié) 構(gòu)體中的成員
GPIO_Speed,但注意到此處 GPIO_Speed_50MHz 只是一個(gè)枚舉變量,并非具體的某個(gè)值。
○4 語(yǔ)句亦和○2 語(yǔ)句類似,把 GPIO_Mode_Out_PP 賦給 GPIO_InitStructure 結(jié)構(gòu)體中的成 員 GPIO_Mode,從上文可知 GPIO_Mode_Out_PP 的值為0x10。
○5 是一個(gè)函數(shù)調(diào)用,即調(diào)用 GPIO_Init 函數(shù),并提供給該函數(shù) 2 個(gè)參數(shù),分別為 GPIOA 和&GPIO_InitStructure,其中&GPIO_InitStructure 表示結(jié)構(gòu)體變量 GPIO_InitStructure 的 地址,而 GPIOA 則在“stm32f10x_map.h”文件中找到定義:
#ifdef _GPIOA
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#endif
此三行代碼是一個(gè)預(yù)編譯結(jié)構(gòu) , 首先判斷是否定義了宏_GPIOA 。 可以在 “stm32f10x_conf.h”中發(fā)現(xiàn)對(duì)_GPIOA 的定義為:
#define _GPIOA
這表示編譯器會(huì)將代碼中出現(xiàn)的 GPIOA 全部替換為((GPIO_TypeDef *) GPIOA_BASE)。從 該句的 C 語(yǔ)言語(yǔ)法可以判斷出((GPIO_TypeDef *) GPIOA_BASE)的功能為將 GPIOA_BASE 強(qiáng)制類型轉(zhuǎn)換為指向 GPIO_TypeDef 類型的結(jié)構(gòu)體變量。如此則需要找出 GPIOA_BASE 的含義,依次在“stm32f10x_map.h”文件中找到:
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
和:
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
還有:
#define PERIPH_BASE ((u32)0x40000000)
明顯 GPIOA_BASE 表示一個(gè)地址,通過(guò)將以上 3 個(gè)宏展開(kāi)可以得到:
GPIOA_BASE = 0x40000000 + 0x10000 + 0x0800
此處的關(guān)鍵便在于 0x40000000、0x10000 和 0x0800 這三個(gè)數(shù)值的來(lái)歷。讀者應(yīng)該通過(guò) 宏名猜到了,這就是 STM32 微控制器的 GPIOA 的設(shè)備地址。通過(guò)查閱STM32 微控制器 開(kāi)發(fā)手冊(cè)可以得知,STM32 的外設(shè)起始基地址為 0x40000000,而 APB2 總線設(shè)備起始地 址相對(duì)于外設(shè)基地址的偏移量為 0x10000,GPIOA設(shè)備相對(duì)于 APB2 總線設(shè)備起始地址 偏移量為 0x0800。
對(duì)○5 句代碼進(jìn)行一個(gè)總結(jié):調(diào)用 GPIO_Init 函數(shù),并將 STM32 微控制器的 GPIOA 設(shè)備地 址和所定義的結(jié)構(gòu)體變量 GPIO_InitStructure 的地址傳入。
以上是對(duì) GPIOA 初始化庫(kù)函數(shù)的剖析,現(xiàn)繼續(xù)轉(zhuǎn)移到函數(shù)內(nèi)部分析,GPIO_Init 函數(shù)原 型如程序段二:
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
u32 currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
u32 tmpreg = 0x00, pinmask = 0x00;
/* 檢查參數(shù)是否正確 */ 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 變量中 */
currentmode = ((u32)GPIO_InitStruct->GPIO_Mode) & ((u32)0x0F);
/* 如果欲設(shè)置為任意一種輸出模式,則再檢查”翻轉(zhuǎn)速率“參數(shù)是否正確 */
if ((((u32)GPIO_InitStruct->GPIO_Mode) & ((u32)0x10)) != 0x00)
{
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
currentmode |= (u32)GPIO_InitStruct->GPIO_Speed;
}
/* 設(shè)置低八位引腳(即 pin0 ~ pin7) */
if (((u32)GPIO_InitStruct->GPIO_Pin & ((u32)0x00FF)) != 0x00)
{
/* 讀出當(dāng)前配置字 */
tmpreg = GPIOx->CRL;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
/* 獲取將要配置的引腳號(hào) */
pos = ((u32)0x01) << pinpos;
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
if (currentpin == pos)
{
/* 先清除對(duì)應(yīng)引腳的配置字 */
pos = pinpos << 2;
pinmask = ((u32)0x0F) << pos;
tmpreg &= ~pinmask;
/* 寫(xiě)入新的配置字 */
tmpreg |= (currentmode << pos);
/* 若欲配置為上拉 / 下拉輸入,則需要配置 BRR 和 BSRR 寄存器 */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
}
else
{
GPIOx->BRR = (((u32)0x01) << pinpos);
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((u32)0x01) << pinpos);
}
}
}
}
/* 寫(xiě)入低八位引腳配置字 */ GPIOx->CRL = tmpreg;
}
/* 設(shè)置高八位引腳(即 pin8 ~ pin15),流程和第八位引腳配置流程一致,不再作解析 */
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;
}
}
這段程序的流程是:首先檢查由結(jié)構(gòu)體變量 GPIO_InitStructure 所傳入的參數(shù)是否正確, 然后對(duì) GPIO 寄存器進(jìn)行“保存——修改——寫(xiě)入”的操作,完成對(duì) GPIO 設(shè)備的設(shè)置工作。 顯然,結(jié)構(gòu)體變量 GPIO_InitStructure 所傳入?yún)?shù)的目的是設(shè)置對(duì)應(yīng) GPIO 設(shè)備的寄存器。而 STM32 的參考手冊(cè)對(duì)關(guān)于 GPIO 設(shè)備的設(shè)置寄存器的描述如下圖 1 和表 1(僅列出低八位引 腳寄存器描述,高八位引腳類同):
 圖 1 GPIO 設(shè)備控制寄存器 GPIOx_CRL
位 31:30 27:26 23:22 19:18 15:14 11:10 7:6 3:2 | 在輸入模式(MODE[1:0]=00): 00:模擬輸入模式 01:浮空輸入模式(復(fù)位后的狀態(tài)) 10:上拉/下拉輸入模式 11:保留
在輸出模式(MODE[1:0]>00): 00:通用推挽輸出模式 01:通用開(kāi)漏輸出模式 10:復(fù)用功能推挽輸出模式 11:復(fù)用功能開(kāi)漏輸出模式 | 位 29:28 25:24 21:20 17:16 13:12 9:8 5:4 1:0 |
MODEy[1:0] :端口 x 的模式位(y = 0„7) (Port x mode bits) 軟件通過(guò)這些位配置相應(yīng)的 I/O 端口,請(qǐng)參考表 17 端口位配置表。 00:輸入模式(復(fù)位后的狀態(tài)) 01:輸出模式,最大速度 10MHz 10:輸出模式,最大速度 2MHz 11:輸出模式,最大速度 50MHz |
表 1 GPIO 設(shè)備控制寄存器 GPIOx_CRL 描述 該寄存器為 32 位,其中分為 8 份,每份 4 位,對(duì)應(yīng)低八位引腳的設(shè)置。每一個(gè)引腳的 設(shè)置字分為兩部分,分別為 CNF 和 MODE,各占兩位空間。當(dāng)MODE 的設(shè)置字為 0 時(shí),表 示將對(duì)應(yīng)引腳配置為輸入模式,反之設(shè)置為輸出模式,并有最大翻轉(zhuǎn)速率限制。而當(dāng)引腳配 置為輸出模式時(shí),CNF 配置字則決定引腳以哪種輸出方式工作(通用推挽輸出、通用開(kāi)漏輸 出等)。通過(guò)對(duì)程序的閱讀和分析不難發(fā)現(xiàn),本文最初程序段中 GPIO_InitStructure 所傳入?yún)?數(shù)的對(duì)寄存器的作用如下:
GPIO_Pin_4 被宏替換為 0x0010,對(duì)應(yīng)圖 1 可看出為用于選擇配置 GPIOx_CRL 的[19:16]
位,分別為 CNF4[1:0]、MODE4[1:0]。
GPIO_Speed_50MHz 為枚舉類型,包含值 0x03,被用于將 GPIOx_CRL 位中的 MODE4[1:0]
配置為 b11(此處 b 意指二進(jìn)制)。
GPIO_Mode 亦為枚舉類型,包含值 0x10,被用于將 GPIOx_CRL 位中的 MODE4[1:0]配置 為 b00。事實(shí)上 GPIO_Mode 的值直接影響寄存器的只有低四位,而高四位的作用可以 從程序段二中看出,是用于判斷此參數(shù)是否用于 GPIO 引腳輸出模式的配置。 至此應(yīng)不難知道 STM32 的固件庫(kù)最后是怎樣影響最底層的寄存器的?偨Y(jié)起來(lái)就是:
固件庫(kù)首先將各個(gè)設(shè)備所有寄存器的配置字進(jìn)行預(yù)先定義,然后封裝在結(jié)構(gòu)或枚舉變量中, 待用戶調(diào)用對(duì)應(yīng)的固件庫(kù)函數(shù)時(shí),會(huì)根據(jù)用戶傳入的參數(shù)從這些封裝好的結(jié)構(gòu)或枚舉變量中 取出對(duì)應(yīng)的配置字,最后寫(xiě)入寄存器中,完成對(duì)底層寄存器的配置。
可以看到,STM32 的固件庫(kù)函數(shù)對(duì)于程序開(kāi)發(fā)人員來(lái)說(shuō)是十分便利的存在,只需要填 寫(xiě)言簡(jiǎn)意賅的參數(shù)就可以在完全不關(guān)心底層寄存器的前提下完成相關(guān)寄存器的配置,具有相 當(dāng)不錯(cuò)的通用性和易用性,也采取了一定措施保證庫(kù)函數(shù)的安全性(主要引入了參數(shù)檢查函
數(shù) assert_param)。但同時(shí)也應(yīng)該知道,通用性、易用性和安全性的代價(jià)是加大了代碼量, 同時(shí)增加了一些邏輯判斷代碼造成了一定的時(shí)間消耗,在對(duì)時(shí)間要求比較苛刻的應(yīng)用場(chǎng)合需 要評(píng)估使用固件庫(kù)函數(shù)對(duì)程序運(yùn)行時(shí)間所帶來(lái)的影響。讀者在使用 STM32 的固件庫(kù)函數(shù)進(jìn) 行程序開(kāi)發(fā)時(shí),應(yīng)該意識(shí)到這些問(wèn)題。
完整的pdf格式文檔51黑下載地址:
STM32學(xué)習(xí)筆記.pdf
(310.69 KB, 下載次數(shù): 29)
2018-8-16 03:30 上傳
點(diǎn)擊文件名下載附件
下載積分: 黑幣 -5
|
評(píng)分
-
查看全部評(píng)分
|