1、STM32F103ZET6簡介
STM32F103ZET6的FLASH容量為512K,64K的SRAM。按照STM32芯片的容量產品划分,STM32F103ZET6屬於大容量的芯片。
2、下載HAL固件庫
打開STM32的官方網址:https://www.st.com/。
在官網上通過搜素STM32CubeF1找到如上圖所顯示的內容,這個就是STM32F1系列的固件包文件,點擊“獲取軟件”下載。
3、安裝STM32F1的Pack
到http://www.keil.com/dd2/pack/ 網站下載STM32F1 Pack 安裝包,如果不安裝STM32F1的Pack,則在使用MDK創建工程選擇芯片型號的時候找不到對應的IC型號,當然如果安裝MDK的時候已經有對應的Pack了,則不用安裝了。如下圖:
下載Pack后雙擊下載的文件進行安裝,Pack安裝包會自動找到MDK的安裝目錄進行安裝,直接點擊下一步進行安裝即可,安裝完成后可以在MDK的Device中看到STM32F1Series。
4、STM32CubeF1固件包說明
STM32CubeF1固件包的根目錄如下圖所示。
_htmresc文件夾:
這個文件夾放的是一些圖片和網站的資料,個人覺得沒什么用。
Documentation文件夾:
里面就一個STM32CubeF1的說明文檔,而且是英文的,看得懂的可以琢磨。
Utilities文件夾:
該文件夾下面是一些其他組件,基本沒怎么使用。
Middlewares文件夾:
如果做基本的開發,這個文件夾也不怎么使用。Middlewares文件夾中包含了ST和Third_Party兩個子文件夾,它們的內容如下:
ST\STemWim文件夾:
存放的是STemWin工具包。
ST\STM32_TouchSensing_Library文件夾:
存放的是STM32電容觸摸支持包。
ST\STM32_USB_Device_Library文件夾:
USB從機設備支持包。
ST\STM32_USB_Host_Library文件夾:
USB主機設備支持包。
Third_Party\FatFs文件夾:
FAT文件系統支持包。
Third_Party\FreeRTOS文件夾:
FreeRTOS實時系統支持包。
Drivers文件夾:
Dreivers文件夾下有如圖所示幾個子文件夾。
BSP文件夾:
該文件夾也叫板級支持包,該包提供的是直接與硬件打交道的API,如觸摸屏,LCD,SRAM以及EEPROM等板載硬件資源驅動。該文件夾中還有多種ST官方Discovery開發板,Nucleo開發板以及EVAL板的硬件驅動API文件,如果要編寫相關的硬件驅動,可以參考這些文件夾中的程序。
CMSIS文件夾:
該文件夾內包含的是符號CMSIS標志的軟件抽象層組件的相關文件。主要包括DSP庫(DSP_LIB文件夾),Cortex-M內核及其設備文件(Include文件夾),微控制器專用頭文件/啟動代碼/專用系統文件等(Device文件夾)。
STM32F1xx_HAL_Driver文件夾:
該文件夾包含了所有的STM32F1xx系列HAL庫頭文件和源文件,也就是所有底層硬件抽象層API聲明和定義。它的作用是屏蔽了復雜的硬件寄存器操作,統一了外設的接口函數。該文件夾包含Src和Inc兩個子文件夾,其中Src子文件夾存放的是.c源文件,Inc子文件夾存放的是與之對應的.h頭文件。每個.c源文件對應一個.h頭文件。源文件名稱基本遵循stm32f1xx_hal_ppp.c定義格式,頭文件名稱基本遵循stm32f1xx_hal_ppp.h定義格式。
Projects文件夾:
該文件夾存放的是一些可以直接編譯的實例工程。每一個文件夾對應一個ST官方的Demo板。
通過上面的介紹可以看出在STM32CubeF1固件包中用到的就Drivers、Projects文件夾中的文件。
5、創建基於HAL的新工程
新建一個文件夾,用來存放創建的工程,在這個文件夾中創建幾個子文件夾,用來存放對應的文件。
CORTEX文件夾:
存放內核和啟動文件。
MDK-PRO文件夾:
存放工程文件。
OUTPUT文件夾存:
存放工程編譯生成的一些文件
STM32F1xx_HAL_Driver文件夾:
存放STM32F1 HAL庫頭文件和源文件。
最后在MDK-PRO文件夾下新建Inc和Src兩個子文件夾。
打開MDK並點擊創建新工程。
輸入工程名稱,然后選擇將工程保存到MDK-PRO文件夾中。
下一步是選擇對應的STM32 IC的型號,在這里選擇STM32F103ZE。
點擊OK就創建好新的工程了,只是這是一個空的工程,什么都沒有,如下圖。
接下來先將工程的編譯輸出文件設置到OUTPUT文件夾中。在MDK軟件中,選擇“Options for Target”,在彈出來的界面中選擇Output選項卡,點擊“Select Folder for Listings…”,在彈出來的界面中選擇OUTPUT文件夾。
然后再選擇Listing選項卡,點擊“Select Folder for Listings…”,也在彈出來的界面中選擇OUTPUT文件夾。
從STM32CubeF1包里復制文件到新工程里。
1、將"STM32Cube_FW_F1_V1.7.0\Drivers\STM32F1xx_HAL_Driver"文件夾中的Inc和Src文件夾復制到新工程的STM32F1xx_HAL_Driver文件夾下。
2、復制相應的啟動文件和關鍵頭文件到新工程目錄下:
將STM32Cube_FW_F1_V1.7.0\Drivers\CMSIS\Device\ST\STM32F1xx\Source\Templates\arm文件夾中的startup_stm32f103xe.s文件復制到CORTEX文件夾中。
將STM32Cube_FW_F1_V1.7.0\Drivers\CMSIS\Include文件夾中的cmsis_armcc.h、core_cm3.h、core_cmFunc.h、core_cmInstr.h、core_cmSimd.h文件復制到CORTEX文件夾中。
3、復制工程需要用到的一些頭文件和源文件。
在MDK-PRO文件夾中創建Inc和Src兩個子文件夾,Inc存放頭文件,Src存放源文件。
將STM32Cube_FW_F1_V1.7.0\Drivers\CMSIS\Device\ST\STM32F1xx\Include文件夾中的stm32f1xx.h、stm32f103xe.h、system_stm32f1xx.h文件復制到MDK-PRO文件夾的Inc文件夾中。
將STM32Cube_FW_F1_V1.7.0\Projects\STM32F103RB-Nucleo\Templates\Inc文件夾中的main.h、stm32f1xx_hal_conf.h、stm32f1xx_it.h文件復制到MKD-PRO文件夾的Inc文件夾中。
將STM32Cube_FW_F1_V1.7.0\Projects\STM32F103RB-Nucleo\Templates\Src文件夾中的main.c、stm32f1xx_hal_msp.c、stm32f1xx_it.c、system_stm32f1xx.c文件復制到MDK-PRO文件夾的Src文件夾中。
將上面步驟的文件復制完之后。用鼠標右擊MDK工程欄中的Group,選擇‘Manage Project Items’。然后添加如下圖所示的Group。
在MDK中創建完分組之后,往Group里面添加需要的文件。
將STM32F1xx_HAL_Driver\Src文件夾下的.c源文件添加到工程中的HAL-LIBRARY組。
其它組所需要添加的文件如下圖所示:
添加完文件到MDK后,點擊OK,工程如下圖所示:
指定頭文件(.h文件)所在的路徑:
很多.c源代碼都有一個對應的.h文件,但是.h文件是存在不同的目錄的,必須告訴編譯器.h文件在哪個目錄下,編譯器才能找到對應的.h文件進行編譯,否則就會報錯。
如下圖所示選擇C/C++選項卡,點擊Include Paths。將所有.h文件所在的文件夾添加進去。最后點擊OK退出。
定義全局宏定義標識符,定義了全局宏定義標識符后,編譯器會根據宏定義進行選擇編譯。打開C/C++選型卡,在define欄輸入“USE_HAL_DRIVER, STM32F103xE”,這兩個標識符在stm32f1xx.h文件當中有用到。
點擊編譯工程。提示找不到“stm32f1xx_nucleo.h”,雙擊跳轉到出錯點。
將44行的#include "stm32f1xx_nucleo.h"刪除,再編譯就沒發現錯誤了。
但是當進行全部編譯的時候發現有提示又錯誤。
這里的錯誤提示是指函數重復定義了。定義的函數在“stm32f1xx_hal_msp.c”和“stm32f1xx_hal_msp_template.c”這兩個文件中。在這里將“stm32f1xx_hal_msp_template.c”文件Remove掉。如下圖:
再將工程能的模板文件刪除掉,找到“stm32f1xx_hal_timebase_rtc_alarm_template.c”和“stm32f1xx_hal_timebase_tim_template.c”並刪除,帶有template的就是模板文件。
再次編譯文件,可以看到沒有錯誤產生了。
6、系統初始化之后的中斷優先級分組號和時鍾設置
默認情況下調用HAL初始化函數HAL_Init之后,會將中斷優先級分組設置為4,可以通過修改HAL_Init函數的內容來更改分組。打開stm32f1xx_hal.c文件,找到HAL_Init函數,如下圖:
修改173行的HAL_NVIC_SetPPriorityGrouping函數中的參數就可以了。
時鍾設置:
關於時鍾的設置,需要注意stm32f1xx_hal_conf.h文件里面HSE_VALUE宏定義的值,這個值是外部晶振大小的值,如果跟實際值對不上,那么跑的時鍾可能會跟預計的不同,需要注意。比如外部晶振用的是8M,那么HSE_VALUE宏的值應該設為8000000U,如下圖:
7、工程文件說明
任何一個MDK工程,不管有多復雜,無非就是一些.c源文件和.h頭文件,還有一些類似.s的啟動文件或者lib文件等。在工程中,他們通過各種包含關系組織在一起,被用戶代碼最終調用或者引用。只要了解了這些文件的作用以及它們之間的包含關系,就能理解這個工程的運行流程。熟悉了文件后,可以自己刪減外設功能,減少工程的代碼量。
1、文件介紹:
1、HAL庫文件介紹:
stm32f1xx_hal_ppp.c/.h文件是一些基本外設的操作API(接口函數),ppp代表任意外設。其中stm32f1xx_hal_cortex.c/.h比較特殊,它是一些Cortex內核通用函數聲明和定義,如中斷優先級NVIC配置,系統軟復位以及Systick配置等。
stm32f1xx_hal_ppp_ex.c/.h文件是拓展外設特性的API(接口函數)。
stm32f1xx_hal.c文件包含了HAL通用API(比如HAL_Init,HAL_DeInit,HAL_Delay等)。
stm32f1xx_hal.h文件是HAL庫的頭文件,它應被客戶代碼所包含。
stm32f1xx_hal_conf.h文件是HAL庫的配置文件,主要用來選擇使能何種外設以及一些時鍾相關參數設置。它應被客戶代碼所包含。
stm32f1xx_hal_def.h文件包含了HAL庫的通用數據類型定義和宏定義。
2、stm32f1xx_it.c/ stm32f1xx_it.h文件
stm32f1xx_it.h中主要是一些中斷服務函數的申明。stm32f1xx_it.c中是這些中斷服務函數的定義,而這些函數定義除了Systick中斷服務函數SysTick_Handler外基本都是空函數,沒有任何控制邏輯。一般情況下,可以去掉這兩個文件,然后把中斷服務函數寫在工程中的任何一個可見文件中。
3、stm32f1xx.h頭文件
stm32f1xx.h文件很重要,它是所有stm32f1系列的頂層頭文件,使用STM32F1任何型號的芯片,都需要包含這個頭文件。同時因為stm32f1系列芯片型號非常多,ST為每種芯片型號定義了一個特有的片上外設訪問層頭文件,比如STM32F103xE系列,ST定義了一個頭文件stm32f103xe.h,然后stm32F1xx.h頂層頭文件會根據工程芯片型號,來選擇包含對應芯片的片上外設訪問層頭文件。打開stm32f1xx.h文件可以看到如下圖所示:
以STM32F103ZET6為例,如果定義了宏定義標識符STM32F103xE,那么頭文件stm32f1xx.h將會包含頭文件stm32f103xe.h。在前面的時候我們已經在C/C++選項卡里面輸入全局宏定義標識符STM32F103xE.所以頭文件stm32f103xe.h一定會被整個工程所引用。
4、stm32f103xe.h頭文件
stm32f103xe.h頭文件是STM32F103xE系列芯片通用的片上外設訪問層頭文件,只要進行STM32F103ZET6開發,就必然要使用到該文件。該文件主要是一些結構體和宏定義標識符。這個文件的主要作用是寄存器定義聲明以及封裝內存操作。
5、system_stm32f1xx.c/ system_stm32f1xx.h文件
這兩個文件主要是聲明和定義了系統初始化函數SystemInit以及系統時鍾更新函數 SystemCoreClockUpdate。函數SystemInit的作用是進行時鍾系統的一些初始化操作以及中斷向量表編譯地址設置,但它並沒有設置具體的時鍾值。在啟動文件startup_stm32f103xe.s中設置系統復位后,直接調用SystemInit函數進行系統初始化。SystemCoreClockUpdate函數是在系統時鍾配置進行修改后,調用這個函數來更新全局變量SystemCoreClock的值,變量SystemCoreClock是一個全局變量,開放這個變量可以方便我們在用戶代碼中直接使用這個變量來進行一些時鍾運算。
6、stm32f1xx_hal_msp.c文件
MSP,全稱MCU support package,函數名字找那個帶有MSPInit的函數的作用是進行MCU級別硬件初始化設置,並且它們通常會被上一層的初始化函數所調用,這樣做的目的是為了把MCU相關的硬件初始化剝奪出來,方便用戶代碼在不同型號的MCU上移植。stm32lxx_hal_msp.c文件定義了兩個函數HAL_MspInit和HAL_MspDeInit。這兩個函數分別被文件stm32f1xx_hal.c中的HAL_Init和HAL_DeInit所調用。HAL_MspInit函數的作用主要是進行MCU相關的硬件初始化操作。這樣的話,在系統啟動后調用了HAL_Init之后,會自動調用硬件初始化函數。實際上,在工程中直接刪除掉stm32f1xx_hal_msp.c文件也不會對程序運行產生影響。
7、startup_stm32f103xe.s啟動文件
STM32系列所有芯片工程都會有一個.s的啟動文件,不同型號的stm32芯片啟動文件也是不一樣的。啟動文件的作用主要是進行堆棧的初始化,中斷向量表以及中斷函數定義等。啟動文件有一個很重要的作用就是系統復位后引導進入C語言的main函數。
如上圖所示,Rest_handler是復位函數,在系統啟動的時候會執行,系統啟動后,先調用SystemInit函數進行系統初始化,然后引導進入main函數執行用戶代碼。
8、文件包含關系
整個工程的文件包含關系如下圖所示:
從上圖可以看出各個文件之間的包含關系,頂層頭文件stm32f1xx.h直接或間接包含了其他所有工程必要頭文件,所以在用戶代碼中,只需要包含頂層頭文件stm32f1xx.h即可。
首先包含stm32f1xx.h,然后包含stm32f1xx_hal.h(.c/.h文件主要實現HAL庫的初始化、系統滴答相關函數及CPU的調試模式配置),再包含stm32f1xx_hal_conf.h(該文件是一個用戶級別的配置文件,用來實現對HAL庫的裁剪)。
2、HAL庫源文件說明:
HAL庫文件名均以stm32f1xx_hal開頭,后面加上_外設或者模塊名,比如stm32f1xx_hal_adc.c。
初始化/反初始化函數:HAL_PPP_Init(),HAL_PPP_DeInit()。
IO操作函數:HAL_PPP_Read(),HAL_PPP_Write(),HAL_PPP_Transmit(),HAL_PPP_Receive()。
控制函數:HAL_PPP_Set(),HAL_PPP_Get()。
狀態和錯誤:HAL_PPP_GetState(),HAL_PPP_GetError()。
在STM32Cube中包含了LL庫,目前LL庫和HAL庫捆綁發布,所以在HAL庫源碼中,還有一些名為stm32f1xx_ll_ppp的源碼文件,這些文件就是新增的LL庫文件,使用CubeMX生成項目時,也可以選擇LL庫。
HAL庫最大的特點就是對底層進行了抽象。在此結構下,用戶代碼的處理主要分為三大部分:
處理外設句柄(實現用戶功能)。
處理MSP。
處理各種回調函數。
外設句柄:
HAL庫在結構上,對每個外設抽象成了一個稱為ppp_HandleTypeDef的結構體,其中ppp就是每個外設的名字。所有的函數都是工作的ppp_HandleTyepDef指針之下。
多實例支持:
每個外設/模塊實例都有自己的句柄。因此實例資源是獨立的。
外圍進程相互通信:
該句柄用於管理進程例程之間的共享數據資源。
3、三種編程方式:
HAL庫對所有的函數模型也進行了統一。在HAL庫中,支持三種編程模式:輪詢模式、中斷模式、DMA模式(如果外設支持)。其中帶有_IT的表示工作在中斷模式下;帶_DMA的工作在DMA模式下;什么都沒帶的就是輪詢模式。
HAL庫架構下統一采用宏的形式對各種中斷等進行配置,針對每種外設主要有以下宏:
__HAL_PPP_ENABLE_IT(__HANDLE__,__INTERRUPT__):使能一個指定的外設中斷。
__HAL_PPP_DISABLE_IT(__HANDLE__,__INTERRUPT__):失能一個指定的外設中斷。
__HAL_PPP_GET_IT(__HANDLE__,__INTERRUPT__):獲得一個指定的外設中斷狀態。
__HAL_PPP_CLEAR_IT(__HANDLE__,__INTERRUPT__):清除一個指定的外設中斷狀態。
__HAL_PPP_GET_FLAG(__HANDLE__,__FLAG__):獲得一個指定的外設的標志狀態。
__HAL_PPP_CLEAR_FLAG(__HANDLE__,__FLAG__):清除一個指定的外設的標志狀態。
__HAL_PPP_ENABLE(__HANDLE__):使能外設。
__HAL_PPP_DISABLE(__HANDLE__):失能外設。
__HAL_PPP_XXX(__HANDLE__,__PARAM__):指定外設的宏定義。
__HAL_PPP_GET_IT_SOURCE(__HANDLE__,__INTERRUPT__):檢查中斷源。
8、HAL庫函數中__weak修飾符講解
在HAL庫中,很多回調函數前面使用__weak修飾符,weak顧名思義是“弱”的意思,所以一般稱加了__weak修飾符的函數為“弱函數”,最終編譯器編譯的時候,會選擇用戶自己定義的函數,如果用戶沒有重新定義這個函數,那么編譯器就會執行__weak聲明的函數,並且編譯不會報錯。
__weak在回調函數的時候經常用到。這樣的好處是,系統默認定義了一個空的回調函數,保證編譯器不會報錯。同時,如果用戶自己要定義用戶回調函數,那么只需要重新定義即可,不需要考慮函數重復定義的問題。
9、Msp回調函數執行過程解讀
當我們調用HAL的外設初始化函數,比如HAL_PPP_Init()(PPP代表某某某外設),那么在HAL_PPP_Init()函數內一般就會包含一個HAL_PPP_MspInit()的函數,該函數就是回調函數。該回調函數一般是一個空函數,而且是用__weak修飾符定義的,沒有任何實際的控制邏輯,由於它是用__weak修飾符定義的函數,那么就可以重新定義HAL_PPP_MspInit()函數,最終執行的也是用戶重新定義的HAL_PPP_MspInit()函數。
10、系統時鍾
HAL庫的系統時鍾是通過SystemClock_Config函數進行初始化的。但是手動移植過來的SystemClock_Config函數並沒有配置好。可以通過STM32CubeMX軟件進行重新配置。
11、刪減工程
HAL庫包含了很多的功能,所有外設的功能都抽象出來了。在實際應用中有些外設是用不到的,如果把整個HAL庫都包含着工程之中,那就會使得編譯后的工程很大,不僅編譯時間久,占用的資源也多。所以在實際應用中,只要包含有用到的外設功能就可以了了,其他的可以裁剪掉。
stm32f1xx_hal_conf.h這個頭文件里面引用了HAL庫源文件的.h文件。stm32f1xx_hal_conf.h中所定義的一些宏,通過定義宏來判斷是否引用所對應的頭文件,比如HAL_ADC_MODULE_ENABLED宏,用來確定是否引用頭文件“stm32f1xx_hal_adc.h”。如下圖所示:
如果某些外設用不到,可以屏蔽掉該外設所對應的宏使得對應的頭文件不被包含。比如如果只用到GPIO口,那么其他外設可以屏蔽掉,但是需要注意的是,就算只用GPIO外設,有一些文件也是要用到的,比如RCC,如果屏蔽掉這些文件,就會造成編譯出錯。
如果實在不知道哪一些文件屏蔽刪除會出錯,可以逐條屏蔽刪除后編譯,確認是否會出錯。
以下是只保留GPIO口功能的工程:
從圖中可以看到只保留了HAL_MODULE_ENABLED、HAL_CORTEX_MODULE_ENABLED、HAL_FLASH_MODULE_ENABLED、HAL_GPIO_MODULE_ENABLED、HAL_RCC_MODULE_ENABLED這4個宏,其他的都屏蔽掉,編譯並沒有報錯。
接下來是刪減工程中添加的HAL庫.c文件。刪減后如下圖所示:
可以看到HAL-LIBRARY組刪除了很多文件,再編譯發現也能通過,而且編譯的速度比刪除前快了很多。