找回密碼
 立即注冊(cè)

QQ登錄

只需一步,快速開(kāi)始

搜索
查看: 3207|回復(fù): 1
打印 上一主題 下一主題
收起左側(cè)

解析STM32的庫(kù)函數(shù)-學(xué)習(xí)筆記分享

[復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
ID:386207 發(fā)表于 2018-8-16 03:21 | 只看該作者 回帖獎(jiǎng)勵(lì) |倒序?yàn)g覽 |閱讀模式
解析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)


評(píng)分

參與人數(shù) 1黑幣 +50 收起 理由
admin + 50 共享資料的黑幣獎(jiǎng)勵(lì)!

查看全部評(píng)分

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏 分享淘帖 頂 踩
回復(fù)

使用道具 舉報(bào)

沙發(fā)
ID:1 發(fā)表于 2018-8-16 03:37 | 只看該作者
好資料,51黑有你更精彩!!!
回復(fù)

使用道具 舉報(bào)

本版積分規(guī)則

手機(jī)版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表