stm 社區 關於HAL庫中stm32f1xx_hal_msp.c文件的認知(新手貼)


http://www.stmcu.org.cn/module/forum/thread-620752-1-3.html
關於HAL庫中stm32f1xx_hal_msp.c文件的認知(新手貼)

要熟練使用STM32CUBEMX這個軟件,首先就要熟悉HAL庫。俺以前用的片子基本都是51核心的,都是直操寄存器的,后來用STM8的片子,用的是庫,感覺用起來很方便。隨着項目的不斷擴充和客戶要求的提高,用到了STM32F103的片子,想想自己關於STM32的認知水平還在6年前3.5版本的庫,感覺應該有點落后了,於是上網找找最新版本的庫,好做項目設計准備。就在這時候發現了STM32CUBEMX的這個軟件以及HAL庫,看了相關介紹后,瞬間覺得自己完全落伍了,學習,學習,再學習......

網上好多關於HAL庫和標准庫優劣的探討,俺再這就不多說了,俺認為只要能滿足項目需要就行,就好像筷子和刀叉的關系,只要能把飯吃到嘴里就行。俺為啥選擇學HAL庫?因為俺以前啥也不會,要學當然選最新的方向學習了。

言歸正傳

學習HAL庫,首先是對這個庫的各個文件仔細研究,每個文件的大概功能、在整個庫中所起到的作用、文件與文件之間引用關系、文件與文件之間的層級關系都要做到心中有數,這樣在使用庫做項目的時候才不至於邏輯混亂,減少調試時間。對整個庫的研究需要狠下功夫,對庫中文件的作用和關系了解透徹了,也就能明白了HAL的編程思想精髓,結合STM32CUBEMX,可以隨心應手操刀自己的設計。

在HAL庫中有一個stm32f1xx_hal_msp.c的文件,這個文件的作用就是根據用戶所提供的具體的MCU型號以及硬件配置,對HAL庫進行初始化設置操作。所以這個文件是就HAL庫與MCU結合的紐帶(不知這樣描述是否恰當)。

以下是個打比方說明方式(歡迎指正):

首先把每個片上外設看成一個功能元件,把有關所有外設的記錄合起來就是一個表格(BOM表)。

好比F1系列的HAL庫是對F1系列所有型號MCU功能BOM的集合。
現在要使用一個F1系列的單片機,型號就叫作A1。
在CUBEMX中,設置單片機型號為A1
那么CUBEMX就會根據A1這個關鍵字去配置相應的功能BOM表。並一起把相應具體的用戶硬件設置寫到stm32f1xx_hal_msp.c中(完成了對HAL庫的初始化,注:這個初始化着重的硬件電氣上的初始化,就是確定用哪個引腳,什么樣高低電平、多大的頻率等等)。


那么stm32f1xx_hal_msp.c中的設置是怎么調用和被調用的呢?

1.在stm32f1xx_hal_msp.c內包含了頭文件stm32f1xx_hal.h。(可以認為stm32f1xx_hal.h是stm32f1xx_hal_msp.c的頭文件,同樣也是stm32f1xx_hal.c的頭文件)
2.在stm32f1xx_hal.h中聲明了HAL_MspInit(void)函數。
3.在stm32f1xx_hal_msp.c內定義了HAL_MspInit(void)函數

(也就是間接的通過stm32f1xx_hal.h文件先聲明了HAL_MspInit(void)函數,再接着對其進行具體的定義,為什么要在stm32f1xx_hal.h中先聲明,是因為還要在stm32f1xx_hal.c中還進行了弱定義)。

4.stm32f1xx_hal.c內弱定義了 __weak void HAL_MspInit(void)。

5.stm32f1xx_hal_msp.c中的函數定義相對stn32f1xx_hal.c中的同名函數定義具有優先權,如果在tm32f1xx_hal_msp.c沒有定義某外設函數,則使用stn32f1xx_hal.c中的定義的那個函數。

6.用戶可通過重新定義stm32f1xx_hal_msp.c內的函數,實現對函數的操作。

7.stm32f1xx_hal_msp.c中的函數通過stm32f1xx_hal.h頭文件引用。(也就是stm32f1xx_hal_msp.c中具體定義的函數都先在stm32f1xx_hal.h中被預先聲明了)。

通過上面可以了解到,用戶對MCU與外設配置寫在stm32f1xx_hal_msp.c內,作為回調函數被HAL庫中的其他文件使用。

 

下面是根據網上大師們和大咖們的教導結合自己的認知寫的總結,僅供參考。

英文翻譯,不知道那個對,俺也不敢說:

MSP MCU Specific Package MCU 特征 包、MCU 具體實例化 包

MSP MCU Support package MCU 支持 包

作用和目的:

根據具體的MCU型號,對片上外設進行初始化設置。

這樣做的目的是因為從以下幾個方面考慮的:

1、所有的同一類型的外設被抽象成通用的封裝,即同類型的外設在不同的MCU上的特征屬性和API接口都是一樣和相同的。

2、相同內核但不同型號的MCU(比如STM32F103C8T6和 STM32F103ZET6都是M3內核)的引腳數量、順序、資源、內存空間以及片上外設種類數量存在不同。

3、為了使HAL庫對具有相同內核但資源不同的MCU有較強兼容性,特增加了此文件,讓用戶根據每款MCU在硬件上具體區別,初始化和配置外設的IO引腳、外設的工作時鍾以及外設的中斷與MCU內核寄存器的對應關系。

4、此文件是對MCU硬件上的初始化設置(一些協議、數據格式等等上層的內容一般不涉及),把具體的硬件配置抽象,形成符合HAL庫要求的、具有統一格式的和屬性種類的結構體。此文件由用戶進行編程初始化和配置。該文件內的初始化過程強調的是外設最底層硬件上的初始化。

舉例:如果存在某個外設PPP

那么HAL_PPP_MSP_Init() 函數是對外設PPP硬件上的具體設置。

HAL_PPP_MSP_Init()這個函數又進一步被PPP_Init()外設初始化函數調用。

HAL_PPP_MSP_Init()是做為一個回調函數被用戶配置,HAL庫回調使用,從而使HAL庫在整體架構上做到統一和兼容。

就是說MSP的作用是把某個外設的接口資源給具體化了,比如對於串口外設,就是指定串口具體的接口引腳狀態(包含引腳的位置、電氣屬性等等)以及外設與CPU的接口(外設與CPU的接口就是特殊功能寄存器的映射地址,也就是告訴CPU要操作哪個外設只要操作相應地址的寄存器就可以了)。

俺是新手,歡迎大家留言斧正。

 

 

 

 

 

 

 

很久之前就聽說st出了一個新版本的庫,用於代替原來的標准庫,非常好奇,但是一直沒有機會去體驗。這次借着做畢設的機會,嘗試着切換到新庫。

官網介紹說,hal(hardware abstract layer)是一層硬件的抽象,看到這里,我非常激動,看來st終於意識到原來標准庫的問題了,原來的標准庫非常依賴於具體硬件細節,很難體現出使用庫的優勢,而且很難移植。同時我也非常好奇,st到底是如何把不同系列mcu的操作給封裝起來的,是不是足夠抽象,方便移植。

話不多說,直接上官網下下來再說。

 
Paste_Image.png

上圖就是hal庫的全部內容,其中STM32F1xx_HAL_Driver中屬於hal庫的內容。

拿到庫第一步需要做的就是配置一個簡單的hello world,我在配置的時候,出現了非常多的問題。最開始非常自信,只從文件夾里挑選自認為有用的文件加入到工程中,結果出現了各種問題,里面各種庫的依賴關系比較復雜,如果不是很熟悉整個架構的話,還是老老實實拷貝整個文件夾吧。

配置之前,首先要了解一下整個庫的框架,官方給的框架圖如下:

 
捕獲.PNG

個人認為這幅圖和我理解的有些許出入,故重新畫了一張:

 
Paste_Image.png

有幾點區別:

  • cmsis我放在了驅動層的最底層,因為cmsis庫中包含的內容都是和具體cpu內核相關的東西,還有一些地址定義,這些都是非常底層的東西了,而且hal層確實是依賴於cmsis。
  • hal底層我增加了一層msp,類似於bsp,全稱是mcu support package,這一層相當於hal的驅動層,與硬件相關的部分比如最終的時鍾配置,gpio配置等等提取出來,交給用戶配置。

了解了架構,下面我們就來配置一個簡單的工程吧。

  1. 首先拷貝整個Driver目錄到工程中。
  2. 新建user文件夾,新建main.c文件。
    找到stm32f1xx_hal_conf_template.h,stm32f1xx_hal_msp_template.c,去掉"_template"放入user文件夾。
    找到stm32f1xx_it.c和stm32f1xx_it.h放入user文件夾。
  3. 新建工程
    添加源文件:
 
Paste_Image.png

配置工程:

  • 勾選Use MicroLib,因為hal使用了c標准庫。
  • 添加全局宏定義:USE_HAL_DRIVER,STM32F103xB。關於芯片選擇,有如下表格:


     
    捕獲1.PNG
  • 勾選c99支持,因為hal采用的是c99標准編寫,不勾選的話,會出現類似於uint32_t等類型不存在的編譯錯誤。
  • 添加包含目錄,如下圖:
 
Paste_Image.png

4.編寫代碼:

配置stm32f1xx_hal_conf.h:
這里面有許多用於配置的宏,比如用於精准延時的晶振頻率,還有各個外設模塊的開關等等。

main.c

#include "stm32f1xx_hal.h" int main() { HAL_Init(); __HAL_RCC_GPIOC_CLK_ENABLE(); GPIO_InitTypeDef gpio_initstruct; gpio_initstruct.Mode=GPIO_MODE_OUTPUT_PP; gpio_initstruct.Speed=GPIO_SPEED_FREQ_HIGH; gpio_initstruct.Pull=GPIO_NOPULL; gpio_initstruct.Pin=GPIO_PIN_13; HAL_GPIO_Init(GPIOC,&gpio_initstruct); while(1) { HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,0); HAL_Delay(150); HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,1); HAL_Delay(150); HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,0); HAL_Delay(150); HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,1); HAL_Delay(1000); } return 0; } 

stm32f1xx_hal_msp.c

#include "stm32f1xx_hal.h" void SystemClock_Config(void); void HAL_MspInit(void) { SystemClock_Config(); } void SystemClock_Config(void) { RCC_ClkInitTypeDef clkinitstruct = {0}; RCC_OscInitTypeDef oscinitstruct = {0}; /* Configure PLL ------------------------------------------------------*/ /* PLL configuration: PLLCLK = (HSI / 2) * PLLMUL = (8 / 2) * 16 = 64 MHz */ /* PREDIV1 configuration: PREDIV1CLK = PLLCLK / HSEPredivValue = 64 / 1 = 64 MHz */ /* Enable HSI and activate PLL with HSi_DIV2 as source */ oscinitstruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;//RCC_OSCILLATORTYPE_HSI; oscinitstruct.HSEState = RCC_HSE_ON;//RCC_HSE_OFF; oscinitstruct.LSEState = RCC_LSE_OFF; oscinitstruct.HSIState = RCC_HSI_OFF;//RCC_HSI_ON; oscinitstruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; oscinitstruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; oscinitstruct.PLL.PLLState = RCC_PLL_ON; oscinitstruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;//RCC_PLLSOURCE_HSI_DIV2; oscinitstruct.PLL.PLLMUL = RCC_PLL_MUL9;//RCC_PLL_MUL16; if (HAL_RCC_OscConfig(&oscinitstruct)!= HAL_OK) { /* Initialization Error */ while(1); } /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 clocks dividers */ clkinitstruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2); clkinitstruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; clkinitstruct.AHBCLKDivider = RCC_SYSCLK_DIV1; clkinitstruct.APB2CLKDivider = RCC_HCLK_DIV1; clkinitstruct.APB1CLKDivider = RCC_HCLK_DIV2; if (HAL_RCC_ClockConfig(&clkinitstruct, FLASH_LATENCY_2)!= HAL_OK) { /* Initialization Error */ while(1); } } 

整個編程步驟就是,hal庫初始化->開外設時鍾->外設初始化->用戶程序,然后在msp.c文件里實現其他平台相關的雜七雜八的操作,需要調用的時候會自動調用,我這里只實現了一個點亮led的功能,故只實現了HAL_MspInit()函數。如果我們要使用uart、adc等其他更復雜的外設,我們需要在msp.c文件里重寫HAL_UART_MspInit()、HAL_ADC_MspInit()等函數,當我們調用HAL_PPP_Init()時,他們都會自動被調用。

說到這里,我要說一下這里其實使用了一個c語言的技巧,實現了類似於c++的重載功能。比如我們來看UART的源文件:

/** * @brief USART MSP Init. * @param husart: Pointer to a USART_HandleTypeDef structure that contains * the configuration information for the specified USART module. * @retval None */ __weak void HAL_USART_MspInit(USART_HandleTypeDef *husart) { /* Prevent unused argument(s) compilation warning */ UNUSED(husart); /* NOTE: This function should not be modified, when the callback is needed, the HAL_USART_MspInit can be implemented in the user file */ } 

函數被__weak修飾了,意思就是,如果別處沒定義,這個函數就是他,如果別處重定義了,就用新的函數,這樣就實現了重載。這有一個很大的好處,就是實現oo思想中的差異化編程,hal實現所有硬件通用的功能,而把不通用的部分通過可重載的函數開放給用戶修改。

體現oo的還有個地方,每個函數中,都會接收到一個handle指針,這其實和this指針非常類似,每個函數都不用知道自己到底是在操作某一個具體的對象,只需要根據handle的指向來操作就可以了。

回到上面的重載。在hal庫中有一點比較大的改變是,中斷都是通過回調函數來開放給用戶的,具體使用方式也是重載相關回調函數,不像標准庫是直接在stm32fxxx_it.c里填寫相關中斷處理函數。這樣做的好處是,hal幫我們處理了一些中斷來臨時的雜務,只把我們感興趣的事件開放給用戶。

但是個人覺得這個改變需要再徹底一點,因為這並沒有解決代碼耦合性的痛點,每一次我們需要寫中斷函數的時候,總是要去改底層代碼,而如果st給我們實現一個注冊回調的接口,那么上層和下層之前就完全分離了,應用層各個模塊之間也不會產生耦合。

總結:
總體而言,hal相比於標准庫,層次架構更加清晰了,對平台更加抽象,但是還遠遠不夠,依然非常依賴於具體的硬件,如果能實現Qt的那種抽象就完美了。用戶使用的時候,只用包含hal.h而不用去管是hal_f1還是hal_f2或是什么其他系列的頭文件,所有系列的代碼打包在一起,通過條件編譯來實現真正的跨平台,而如果需要使用某款mcu的特色功能時,就再包含一個hal_f1extend.h。如果這些st都實現了,那么單片機編程將會變得和應用編程一樣簡單方便!



作者:logic_wei
鏈接:https://www.jianshu.com/p/c6809c2bcb4f
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
 
 
 
 

新建基於STM32F103ZET6的工程-HAL庫版本

 

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組刪除了很多文件,再編譯發現也能通過,而且編譯的速度比刪除前快了很多。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM