ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

上一章通過直接操作寄存器成功點亮了一個LED燈,接下來將學習一種針對複雜程序效率更高的開發方式——固件庫開發。本章將通過模仿STM32的固件庫構建一個簡易庫來幫助讀者對庫開發有一個初步認識。

2.1構建stm32f10x.h頭文件

51單片機編程主要是通過直接操作寄存器來實現的,比如:'P0 = 0x01;'讓P0.1引腳輸出"高電平",其他7個引腳輸出低電平。而上一章配置STM32的引腳輸出高電平,採用直接訪問寄存器地址的方式,比如:'*(unsigned int *)0x4001200C |= 1 << 7;'。撇開兩款單片機內核結構的差異,單純分析上面兩條語句會發現它們的區別在於'='的左邊,也就是"P0"與"*(unsigned int *)0x4001200C"的差異。如圖2-1所示, 51單片機的 "P0"之所以能夠代表'端口0'是因為使用了關鍵字"sfr"將51單片機端口0對應的端口地址"0x80"映射給關鍵字"P0"。因此,為了更方便的操作,可以新建一個頭文件對STM32的GPIO端口寄存器的物理地址進行映射。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-1 51單片機通用頭文件

2.1.1添加頭文件

新建一個文件夾,將第一章點亮LED燈的工程直接複製過來。新建一個文本文件,保存為"stm32f10x.h"並添加到工程中,內容如圖2-2所示,同時在main文件最開始添加包含語句 #include "stm32f10x.h",切記不要寫成#include

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-2 新建stm32f10x.h頭文件

2.1.2添加地址映射

STM32地址空間按照功能不同設置為不同的分區,包括代碼區、片上靜態RAM、片上外設、外部存儲器等等。STM32的外設接口主要分佈在片上外設分區,比如GPIO端口,USART、SPI、I2C等,如圖 2-3所示,可以根據外設接口的物理地址進行映射。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-3 片上外設端口結構圖

進行地址映射前首先需要了解一下STM32外設接口與總線的關係。總線是芯片各種功能部件之間傳送信息的公共通信幹線。而STM32內部也有許多總線,其中AHB(Advanced High Performance Bus)中文名稱叫高級高性能總線,主要用於高性能模塊之間進行高速通信的連接,類似於城市之間的高速公路,可以高速行駛進而縮短行程時間。當然高速公路往往不直接通到城市的任何角落,AHB總線也是如此。AHB總線與外設接口之間是通過APB總線(Advanced Peripheral Bus,外圍總線的意思) 相連的,APB總線類似於城市外環公路,而STM32的外設接口就是掛載到APB總線上的。而APB總線又分為APB1總線、APB2總線來掛載STM32眾多的外設接口。其中GPIO端口是掛載在APB2總線上的,控制外設接口使能的RCC時鐘則是掛載在AHB總線上的。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

2.1.3GPIO端口寄存器映射

按照第一章使用寄存器點亮LED燈的配置流程,接下來需要訪問GPIO端口上的寄存器來設置IO口的引腳模式,進而控制引腳輸出數據。STM32F103ZE單片機的GPIO外設包含A、B、C、D、E、F、G 共7個端口,每個端口都包含七個相同的GPIO端口寄存器,並且是連續排列在每個GPIO端口上的,每個寄存器佔用4字節的地址空間。如表2-1所示,將其使用結構體進行封裝。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

volatile是C語言關鍵字,意思是"易變的",可以確保程序代碼訪問它所修飾的變量時每次都是直接訪問其最新值,而避免編譯器進行可能的假設性優化。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

然後使用GPIO_TypeDef類型指針對GPIO端口地址進行強制類型轉換,具體操作如下:

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

2.1.4stm32f10x.h頭文件

通過前邊幾節的操作便可以像51單片機一樣使用邏輯地址"P0"代替物理地址"0x80"訪問寄存器。比如訪問"GPIO端口G的配置低寄存器(CRL)": GPIOG->CRL。

其中"->"稱作箭頭操作符,使用結構體指針對結構體成員進行訪問,相當於(* GPIOG). CRL。至此便完成了stm32f10x.h頭文件的地址映射,現在可以像操作51單片機一樣對寄存器直接賦值。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

通過調用stm32f10x.h頭文件可以更加友好的訪問GPIO端口寄存器,對比直接操作寄存器地址,可以通過讀程序而不是必須讀註釋就可以知道操作了哪些寄存器。

通過寄存器地址訪問:

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

通過友好的命名訪問:

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

2.2初始化配置

2.2.1引腳置位和復位

前幾節的程序使用"端口輸出數據寄存器"控制引腳輸出數據,此外STM32還有兩組寄存器可以實現單獨對某個引腳"置位"和"清零"功能,分別是"端口位設置/清除寄存器"和"端口位清除寄存器"。如圖2-4所示,端口位設置/清除寄存器的低16位(BS15~BS0位)對應每一組GPIO端口上的16個引腳,相應引腳位置1時設置該引腳輸出高電平。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-4 端口位設置/清除寄存器結構圖

如圖2-5所示,端口位清除寄存器的低16位(BR15~BR0)同樣對應每一組GPIO端口上的16個引腳,相應位置1時設置對應引腳輸出低電平。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-5 端口位清除寄存器結構圖

通過觀察可以發現這兩組寄存器的低16位(15~0位)置1時可以分別實現相應引腳輸出高電平和低電平。這兩組寄存器都是32位的,高16位沒有用到可以忽略,剩下的低16位可以用十六進制數表示相應引腳的狀態。比如0x0001表示選擇引腳0,0x0002表示選擇引腳1,……0xFFFF表示選擇所有引腳。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

2.2.2 設置引腳模式

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

為了方便設置GPIO端口的3種輸出速率和8種引腳模式,將結構體成員中的引腳速率和引腳模式分別定義為枚舉類型。

使用1~3表示10MHz、2MHz、50MHz三種模式。相應的枚舉結構如下:

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

如表2-2所示,為了更詳細的區分每一種模式,增加4位模式區分位,這樣就組成八位二進制數表示引腳模式,通過第5位來判定輸入輸出狀態。輸入模式第5位為0,輸出設置第5位為1。

具體模擬輸入表示為0x00;浮空輸入表示為0x04;由於上拉/下拉輸入的CNF和MODE位相同,都是0x08,因此使用0x28表示上拉;0x48表示下拉。接下來看一下輸出模式,配置輸出模式時,在配置輸出速率未確定之前,MODE的復位值為00,因此通過設置第5位為1與輸入模式區分開。具體通用推輓輸出為0x10,通用開漏輸出為0x14,複用推輓輸出為0x18,複用開漏輸出為0x1C。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

定義枚舉結構表示引腳模式:

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

需要注意的是,定義的GPIOMode_TypeDef類型枚舉和GPIOSpeed_TypeDef類型枚舉應該位於GPIO_InitTypeDef類型結構體前面。因為GPIO_InitTypeDef類型結構體中調用了這兩個枚舉類型定義引腳速率和引腳模式。

2.2.3初始化函數

GPIO初始化函數這部分代碼是官方固件庫中的源碼,用戶可以直接拿來使用,代碼相對略顯複雜,不需要做深入研究,有興趣的讀者可以參考圖2-6所示的是GPIO端口初始化函數流程圖獨立詳細學習,在此就不詳細介紹。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-6 GPIO初始化函數工作流程圖

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

2.2.4初始化引腳配置

將2.2.3節關於初始化配置的相關代碼分別添加到stm32f10x驅動文件中,接下來就使用一種全新的方式點亮LED燈。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

這種編寫程序的方式,可以在不需要查閱手冊的寄存器每一位功能的基礎上,直接調用函數完成程序功能,並且從程序代碼上就可以直觀看出對外設進行了何種操作。

代碼解析:

2、 由於沒有對RCC時鐘進行配置,因此使能GPIO端口G時鐘仍然使用賦值操作。

3、 對定義的GPIO_InitStructure結構體中的成員進行賦值操作。之前通過指針訪問結構體

成員時使用"->"箭頭操作符,而通過結構體變量名訪問可以使用"."點操作符。

4、 GPIO_Init(GPIOG, &GPIO_InitStructure);是調用初始化函數,GPIOG表示要配置的GPIO端口D的地址,&GPIO_InitStructure則是將前面配置好的GPIO_InitStructure結構體的地址傳遞給函數。初始化函數根據配置好的引腳模式自動對寄存器賦值,完成配置。

5、 GPIO_ResetBits(GPIOG,GPIO_Pin_7);調用引腳復位函數,讓PG7輸出低電平。

2.3庫函數

通過前面兩節利用stm32f10x.h的頭文件的宏定義,以及調用stm32f10x.c中的初始化函數可以很方便的對GPIO端口進行配置,避免了直接操作寄存器的繁雜。然而STM32有眾多的外設接口,比如I2C、USART、SPI、USB、CAN等等,難道需要一一去編寫驅動文件嗎?當然不是。或許有許多讀者在開始學習之前就瞭解過STM32的固件庫。通過編寫驅動文件的方式構建一個簡單的GPIO庫,目的是讓讀者能夠深入的瞭解固件庫的組成。STM32的固件庫可以簡單地理解為一個個外設接口驅動文件的集合。

STM32標準外設庫也稱為固件函數庫,簡稱固件庫。實際是由程序、數據結構和宏定義組成的固件函數包。類似於前邊編寫的GPIO端口的驅動文件,包含了STM32微控制器所有外設的寄存器命名和相關操作。通過使用固件函數庫,無需深入掌握底層硬件細節,就可以輕鬆應用每一個外設。該函數庫還包括每一個外設的驅動描述和應用實例,為開發者訪問底層硬件提供了一箇中間API(application programming interface 應用編程界面)。API其實就是預先定義的函數,每個外設驅動都由一組函數組成,這組函數覆蓋了該外設所有功能。比如GPIO外設的API,定義了GPIO端口初始化函數,設置引腳輸出高電平/低電平函數,讀取引腳輸入數據函數等等。因此,使用函數庫可以大大減少用戶的程序編寫時間,進而降低開發成本。API對驅動程序的結構,函數和參數名稱都進行了標準化。有了各外設的API驅動,不需要再查找地址,對寄存器賦值來操作外設,只需要調用相應的函數,從而大幅提高了開發效率。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-7 庫函數開發和寄存器開發

簡單的說,使用固件函數庫進行開發最大的優勢就在於開發者不用深入瞭解底層硬件和寄存器的細節就可以靈活規範的使用每一個外設。標準外設庫覆蓋了從GPIO到定時器,再到CAN、I2C、SPI、UART和ADC等等的所有外設。對應的C源代碼只是用了最基本的C編程的知識,所有代碼經過嚴格測試,易於理解和使用,並且配有完整的文檔,非常方便進行二次開發和應用。缺點是函數的相互調用會增加系統響應時間,而寄存器開發的優勢就在於執行效率高。所以在實際項目開發中,一般採用固件函數庫開發為主,少數對執行效率要求高的場合可採用直接操作寄存器的方式。

2.4固件庫移植

固件庫開發首先要移植固件庫到STM32的工程中,建立一個通用的工程文件夾,為今後做開發提供便利。

(1)首先新建一個文件夾命名為Template,再在Template文件夾下新建4個文件夾,來分類存放不同文件,具體如圖2-8所示。

"Library"——用於存放庫文件;

"Output"——用於存放輸出文件以及編譯器生成的文件;

"Project"——用於存放工程文件;

"Source"——用於存放用戶自己編寫的代碼;

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-8 新建文件夾

(2) 如圖2-9所示,在開發板配套光盤"Kingst-32F1開發板資料\STM32固件庫"中找到3.5.0版本的STM32庫文件源碼—"STM32F10x_StdPeriph_Lib_V3.5.0",打開固件庫Libraries文件夾,將"CMSIS"和"STM32F10x_StdPeriph_Driver"兩個文件夾複製到 "Template\Library"文件夾中。

"CMSIS"——主要存放庫自帶的啟動文件和Cortex-M3 系列單片機通用的文件。

"STM32F10x_StdPeriph_Driver"——主要存放外設驅動的源文件,其中"scr"用於存放外設的源文件,"inc"存放對應的頭文件。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-9 固件庫源碼

(3)如圖2-10所示,將固件庫"Project\STM32F10x_StdPeriph_Template"路徑下的4個文件複製到"Template\Source"文件夾中。至此固件庫移植所需的所有文件都已經複製完畢,接下來就是新建一個Keil工程,然後向工程中添加文件。

"main.c" —— 主函數體示例文件

"stm32f10x_conf.h"——包含所有外設的頭文件,如果屏蔽其中的頭文件,則相應外設不能使 用。

"stm32f10x_it.h"—— 包含所有中斷處理函數原型的頭文件

"stm32f10x_it.c" ——外設中斷函數文件

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-10 添加文件

(4)打開Keil,新建工程,並且將工程保存在Template文件夾下的Project文件夾中,工程命名為"Template"。工程建立完畢後,點擊Keil工具欄中的File Extension按鈕,如圖2-11所示,點擊"Groups"一欄中最左邊的添加按鈕,向工程中添加3個Group。

"User" —用於存放中斷函數、主函數

"CMSIS"—存放Cortex-M3內核驅動

"STM32_StdPeriph"—用於存放外設源文件

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-11 添加庫文件到工程(一)

(5)選中"User"Group,點擊"Add Files…"向"User"Group中添加Sources文件夾下的3個文件,如圖2-12所示

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-12添加庫文件到工程(二)

(6) 如圖2-13,向"CMSIS"Group添加啟動文件,具體路徑如圖2-14所示。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-13添加庫文件到工程(三)

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-14 文件所在路徑

(7)添加外設源文件,將複製到"Template\Library\STM32F10x_StdPeriph_Driver\src"文件夾下的所有文件添加到"STM32_StdPeriph"Group中,如圖2-15所示。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-15添加庫文件到工程(四)

(8)庫文件添加完畢後,點擊Options for Target按鈕。分別打開"Output"和"Listing"選項卡,在"Select Folder for xxx….."中,選擇將輸出文件和編譯文件保存路徑設置為 "Output"文件夾,設置完畢後點擊"OK"即可,如圖2-16所示。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-16 設置輸出路徑

(9)選擇"C/C++"選項卡,首先向編譯器添加"STM32F10X_HD和USE_STDPERIPH_DRIVER"兩個宏,用英文","符號隔開。

STM32F10X_HD——告訴編譯器使用的STM32F103ZE型號是大容量(高密度)產品,即Flash大於128K,不同容量的產品添加的宏不同。

USE_STDPERIPH_DRIVER——讓編譯器調用stm32f10x_conf.h頭文件

接下來是添加使用的庫函數的頭文件路徑,便於編譯器查找工程中使用的頭文件的位置。需要注意:工程中調用的任何驅動文件都需要設置頭文件的路徑,否則編譯器會因找不到目標文件而報錯,具體操作如圖2-17所示。(注意:外設接口頭文件路徑不是src文件夾而是inc文件夾)

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-17 添加頭文件路徑

(10)路徑設置完畢後,編譯一下工程,顯示"0 Error , 0 Warning(s)"表示固件庫移植成功,如果提示有錯誤,根據提示錯誤,重新檢查移植步驟是否出錯。

(11) 新建兩個文本文件保存在新建工程的Source文件夾下,分別保存文件名為config.c和config.h,兩個文件作為系統配置文件,如圖2-18所示。簡單的延時函數可以寫在config.c文件中,同時將main.c中包含的stm32f10x.h頭文件添加到config.h中,這樣只需要在main.c中包含config.h頭文件,以後編寫外設驅動程序時也只需要包含config.h頭文件即可。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-18 新建config文件

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

(11)如圖2-19所示,新建庫函數工程界面。將這個工程保存為工程模板,以後每次使用時直接複製一份。此外下載程序時還需要在Debug選項中設置下載器,大家可以到第一章1.7小結"配置工程"中查找。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-19 新建固件庫工程界面

2.5 LED和RGB小燈

RGB小燈由紅光LED、綠光LED、藍光LED並聯而成,可以發出紅、綠、藍三原色及其混合光。RGB小燈有共陽和共陰之分,共陽就是三種顏色光源的陽極為公共端,陰極為控制端。本書採用的是共陽RGB小燈,其第2引腳為公共端,接電源3.3V,而第1、3、4引腳分別為紅、綠、藍三色光源的陰極控制端,由於紅、綠、藍三個光源的導通壓降並不相同紅光LED的壓降為1.8~2.6V,綠色和藍色LED的壓降為2.8~3.6V,;除此之外它們的發光強度也各不相同,為了達到良好的發光效果,需要對R、G、B三個LED串聯不同的電阻,如圖2-20所示。

可以像點亮普通LED燈一樣控制RGB小燈,不同的是RGB可以由紅、綠、藍三原色任意組合,達到多色光效果。表2-2是Kingst-32F1開發板LED燈和RGB小燈與STM32F103ZE單片機的引腳連接關係。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

2.6流水燈

使用庫函數開發時,結合《STM32固件庫使用手冊》可以更好的理解每個函數的作用以及用法,該文件位於配套光盤"Kingst-32F1開發板資料\數據手冊\STM32相關"。STM32固件庫參考手冊就像產品說明書一樣,包含所有外設模塊的配置函數,切記不要盲目全部閱讀參考手冊,用到哪些外設功能直接到參考手冊中查找即可。前幾節使用的簡易庫就是從標準函數庫中摘出的關於GPIO引腳相關的函數,下面用一個簡單延時的流水燈的例子鞏固這個知識點。

為了提高開發效率,直接將模板固件庫工程複製過來,將工程所在文件夾的名稱修改為LED,同時將Projects文件夾下與工程名稱相關的兩個文件重命名為LED,如圖2-21所示,以後移植工程文件夾時也是直接修改這兩個文件。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-21 修改工程文件名

打開LED工程,新建兩個文本文件分別保存為led.c和led.h,將其保存在LED工程的Source文件夾下,作為LED小燈的驅動文件,如圖2-22所示。當使用LED時可以直接複製這兩個文件到所在工程,今後每個模塊都會採用這種形式編程,避免重複編寫驅動代碼。

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

圖2-22 新建LED驅動文件

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

上面程序中使用了庫函數中的GPIO引腳置位和清除函數,分別對IO口進行置1和清0;由於流水燈至少需要3個LED才能達到演示效果,為此採用RGB小燈的一個顏色作為一個LED,具體點亮順序為: LED_R—>LED1—>LED2,大家可以先嚐試一下自己編寫,然後再比對一下參考程序。

流水燈代碼:

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫

ARM嵌入式編程與實戰應用(STM32F1系列)—第2章從頭文件到庫


分享到:


相關文章: