解读STM32标准库的程序架构 - 以GPIO操作为例
前言
在開發(fā)新產(chǎn)品時(shí),想必大家都曾像我一樣碰到過(guò)一時(shí)難以解決的技術(shù)難題,在苦惱和無(wú)助中,只得求助于互聯(lián)網(wǎng),如果在網(wǎng)上突然發(fā)現(xiàn)有此問(wèn)題相關(guān)的解決辦法,以此解決了困擾了一整天甚至好幾天的問(wèn)題,這時(shí)大家的心情想必也是和我一樣慶幸并心懷感激的。感謝互聯(lián)網(wǎng)讓我們生活變得如此便利,把世界上的人都聚集到了一起,大家可以暢所欲言;更感激大佬們?cè)敢飧冻鲎约簩氋F的時(shí)間寫下各種技術(shù)分享,以便其他同仁遇到問(wèn)題時(shí)能愉快地解決問(wèn)題。本著開源共享,共同進(jìn)步的精神,我也開始寫分享!如有錯(cuò)誤或講述不妥的地方,請(qǐng)務(wù)必留言指出,也是幫助其他人避坑,謝謝!
?
?
什么是固件庫(kù)
STM32 標(biāo)準(zhǔn)函數(shù)庫(kù),它是由 ST 公司針對(duì) STM32 提供的函數(shù)接口,即API(Application Program Interface),開發(fā)者可調(diào)用這些函數(shù)接口來(lái)配置 STM32的寄存器,使開發(fā)人員得以脫離最底層的寄存器操作,有開發(fā)快速,易于閱讀,維護(hù)成本低等優(yōu)點(diǎn)。
如果學(xué)過(guò)51單片機(jī)肯定知道有個(gè)頭文件是reg52.h,其中的內(nèi)容就是將所有的寄存器取一個(gè)別名,之后你想對(duì)這個(gè)寄存器進(jìn)行操作只需要直接對(duì)其賦值即可。
舉例:P0 = 0x01;//就是將P0寄存器的bit0置1,其余位清0
STM32當(dāng)然也可以通過(guò)指針直接訪問(wèn)寄存器,舉例:
STM32 標(biāo)準(zhǔn)函數(shù)庫(kù)不僅對(duì)芯片內(nèi)所有寄存器取了別名,還將底層一些必要的配置操作全部封裝成函數(shù),供用戶直接調(diào)用來(lái)配置底層,讓用戶即使對(duì)相關(guān)外設(shè)寄存器不熟悉,也能輕易將需要用的片上外設(shè)配置完成,從而有更多時(shí)間專注于應(yīng)用層的開發(fā)。
下圖中,驅(qū)動(dòng)層就是用戶針對(duì)自己的設(shè)備調(diào)用庫(kù)函數(shù)提供的接口開發(fā)的驅(qū)動(dòng),比如說(shuō)最簡(jiǎn)單的驅(qū)動(dòng)LED燈閃爍,就直接調(diào)用庫(kù)函數(shù)提供的GPIO函數(shù)控制相應(yīng)的IO口輸出高低電平即可實(shí)現(xiàn)。
PS:其實(shí)利用庫(kù)函數(shù)開發(fā),將應(yīng)用層和底層分離開,只是好處其一。另一好處在于大家的應(yīng)用驅(qū)動(dòng),至少對(duì)于底層這一塊,大體的風(fēng)格和調(diào)用的函數(shù)都是一致的,從而起到標(biāo)準(zhǔn)化程序開發(fā)的作用。
?
?
以GPIO操作為例詳解標(biāo)準(zhǔn)庫(kù)底層代碼
【首先看到stm32f10x.h中相關(guān)代碼段】
#define __IO volatile //防止編譯器優(yōu)化,在core_cm3.h中 typedef unsigned int uint32_t; //stdint.h文件 C99新定義的標(biāo)準(zhǔn),獨(dú)立于庫(kù)函數(shù)外typedef struct {__IO uint32_t CRL;__IO uint32_t CRH;__IO uint32_t IDR;__IO uint32_t ODR;__IO uint32_t BSRR;__IO uint32_t BRR;__IO uint32_t LCKR; } GPIO_TypeDef; //注意以上結(jié)構(gòu)體成員的排列順序恰好跟GPIO外設(shè)相關(guān)寄存器的排列順序相同#define PERIPH_BASE ((uint32_t)0x40000000) //片上外設(shè)基地址#define APB1PERIPH_BASE PERIPH_BASE //APB1總線的基地址,與外設(shè)基地址相同,表明它是片上外設(shè)的第一個(gè)外設(shè) #define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) //APB2總線基地址 #define AHBPERIPH_BASE (PERIPH_BASE + 0x20000) //AHB總線基地址#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800) //GPIOA的基地址 //以上代碼通過(guò) (基地址+偏移地址) 的形式 用宏定義封裝了所有的寄存器,以上只是抽取其中一部分#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)注意最后的這句 #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
將GPIOA_BASE強(qiáng)制轉(zhuǎn)換成(GPIO_TypeDef *) 類型的指針,即((GPIO_TypeDef *) GPIOA_BASE) 指向的是一個(gè)GPIO_TypeDef 類型的結(jié)構(gòu)體且該結(jié)構(gòu)體地址為GPIOA_BASE,該結(jié)構(gòu)體的類型定義GPIO_TypeDef 在前面已經(jīng)給出了,可以直接通過(guò) ((GPIO_TypeDef *) GPIOA_BASE) 訪問(wèn)到該結(jié)構(gòu)體的成員,比如((GPIO_TypeDef *) GPIOA_BASE) ->BSRR ;由于宏定義,可以寫成GPIOA->BSRR;其實(shí)這就完成了將寄存器的地址與結(jié)構(gòu)體成員的地址一一對(duì)應(yīng)起來(lái),訪問(wèn)結(jié)構(gòu)體成員就是訪問(wèn)寄存器;
注意:對(duì)不同的GPIO端口,如GPIOA,GPIOB,GPIOC…,并不需要分別定義一個(gè)GPIO_TypeDef 的結(jié)構(gòu)體變量,只是把GPIOx_BASE開頭的一塊sizeof(GPIO_TypeDef) 大小的地址區(qū)域當(dāng)成結(jié)構(gòu)體成員去訪問(wèn),從而對(duì)成員的讀寫就是對(duì)相對(duì)應(yīng)寄存器的讀寫。
?
?
下面再看看其他GPIO操作相關(guān)的代碼:
【stm32f10x.h】
這些都是對(duì)相關(guān)寄存器的每一個(gè)位設(shè)立開關(guān)宏,以供庫(kù)函數(shù)調(diào)用,最主要是為了便于程序閱讀。
?
?
【stm32f10x_gpio.h】
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;typedef struct {uint16_t GPIO_Pin; GPIOSpeed_TypeDef GPIO_Speed; GPIOMode_TypeDef GPIO_Mode; }GPIO_InitTypeDef;GPIO_InitTypeDef是GPIO初始化結(jié)構(gòu)體類型,主要用于對(duì)相關(guān)的GPIO外設(shè)作初始化操作。GPIOSpeed_TypeDef和GPIOMode_TypeDef 是兩個(gè)枚舉類型。
下面看相關(guān)的庫(kù)函數(shù)代碼舉例:
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx) {/* Check the parameters */assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); //斷言檢查參數(shù)return ((uint16_t)GPIOx->IDR); }應(yīng)用舉例:GPIO_read_data = GPIO_ReadInputData(GPIOA);
讀整個(gè)GPIOA端口的數(shù)據(jù),由函數(shù)中可以看出事實(shí)上就是返回了GPIOA->IDR的值。IDR是端口輸入數(shù)據(jù)寄存器。
應(yīng)用舉例:GPIO_SetBits(GPIOA, GPIO_Pin_0);
將GPIOA端口的Pin0拉高,由函數(shù)中可以看出事實(shí)上就是將GPIOA->BSRR賦值為GPIO_Pin_0的值。BSRR是端口位設(shè)置/清除寄存器。
在stm32f10x_gpio.h中給出了GPIO_Pin_0的定義如下
#define GPIO_Pin_0 ((uint16_t)0x0001)
stm32f10x_gpio.c文件中最重要的是GPIO_Init函數(shù),函數(shù)原型如下:
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
該函數(shù)主要用于根據(jù)傳進(jìn)來(lái)的GPIO_TypeDef類型的結(jié)構(gòu)體確定要配置的GPIO端口號(hào),GPIO_InitTypeDef類型的結(jié)構(gòu)體是確定配置的內(nèi)容,F1的3.5.0標(biāo)準(zhǔn)庫(kù)主要配置對(duì)象是引腳號(hào),引腳模式,引腳翻轉(zhuǎn)速度,由于GPIO_Init函數(shù)過(guò)長(zhǎng),程序就不在這里貼出來(lái)了。
通過(guò)庫(kù)函數(shù),GPIO點(diǎn)燈過(guò)程舉例:
int main(void) {GPIO_InitTypeDef GPIO_InitStructure;//定義GPIO_InitTypeDef類型的結(jié)構(gòu)體GPIO_InitStructure用于初始化配置GPIORCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);//打開GPIOB的時(shí)鐘,因?yàn)槟J(rèn)是關(guān)閉的GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//選定引腳GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //通用推挽輸出模式GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//引腳速率50HzGPIO_Init(GPIOB, &GPIO_InitStructure);//調(diào)用GPIO_Init按照上述的參數(shù)進(jìn)行配置GPIO_ResetBits(GPIOB,GPIO_Pin_0);//假設(shè)LED是低電平點(diǎn)亮,輸出低電平即可點(diǎn)亮連接在GPIOB0的LED燈 }結(jié)束語(yǔ)
希望可以幫到大家,有問(wèn)題可以提出來(lái)大家一起探討,謝謝!不喜勿噴,感謝
?
?
題外話
#ifndef __STM32F10x_H
#define __STM32F10x_H
#endif
經(jīng)常在每個(gè)頭文件之前都會(huì)看到類似這樣的一段代碼,這是為了防止頭文件重復(fù)包含
?
?
#ifdef __cplusplus
extern “C” {
#endif
//代碼…
#ifdef __cplusplus
}
#endif
這是為了兼容C++編譯器,在C++編譯器中,通常會(huì)添加宏定義__cplusplus,這樣上下兩個(gè)條件編譯就會(huì)成立,為了在C++代碼中調(diào)用用C寫成的庫(kù)文件,就需要用extern"C"來(lái)告訴編譯器:這是一個(gè)用C寫成的庫(kù)文件,請(qǐng)用C的方式來(lái)鏈接它們。這樣在C++的工程中也能順利編譯這個(gè)C文件
?
?
標(biāo)準(zhǔn)庫(kù)函數(shù)中用于映射寄存器的結(jié)構(gòu)體比如GPIO_TypeDef,其中的結(jié)構(gòu)體成員為什么要用volatile修飾呢?
C 語(yǔ)言中的關(guān)鍵字“volatile”,在 C 語(yǔ)言中該關(guān)鍵字用于表示變量是易變的,要求編譯器不要優(yōu)化。這些結(jié)構(gòu)體內(nèi)的成員,都代表著寄存器,而寄存器很多時(shí)候是由外設(shè)或 STM32 芯片狀態(tài)修改的,也就是說(shuō)即使 CPU 不執(zhí)行代碼修改這些變量,變量的值也有可能被外設(shè)修改、更新,所以每次使用這些變量的時(shí)候,我們都要求 CPU 去該變量的地址重新訪問(wèn)。若沒(méi)有這個(gè)關(guān)鍵字修飾,在某些情況下,編譯器認(rèn)為沒(méi)有代碼修改該變量,就直接從 CPU 的某個(gè)緩存獲取該變量值,這時(shí)可以加快執(zhí)行速度,但該緩存中的是陳舊數(shù)據(jù),與我們要求的寄存器最新狀態(tài)可能會(huì)有出入。
都看到這了,點(diǎn)個(gè)贊再走唄~~
總結(jié)
以上是生活随笔為你收集整理的解读STM32标准库的程序架构 - 以GPIO操作为例的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Java 判断文件夹、文件是否存在、否则
- 下一篇: NRF24L01跳频抗信道干扰功能探讨