STM32F4 HAL库开发 -- 工程模板解读
一、關鍵文件介紹
1、HAL庫關鍵文件
stm32f4xx_hal_ppp.c/.h
基本外設的操作API,ppp代表任意外設。其中stm32f4xx_hal_cortex.c/.h比較特殊,它是一些Cortex內核通用函數聲明和定義,例如中斷優先級NVIC配置,系統軟復位以及Systick配置等。
stm32f4xx_hal_ppp_ex.c/.h
拓展外設特性的API。
sm32f4xx_hal.c
包含HAL通用API(比如HAL_Init,HAL_DeInit,HAL_Delay 等)。
stm32f4xx_hal.h
HAL的頭文件,它應被客戶代碼或包含。
stm32f4xx_hal_conf.h
HAL的配置文件,主要用來選擇使能何種外設以及一些時鐘相關參數設置。其本身應該被客戶代碼包含。
stm32f4xx_hal_def.h
包含HAL的通用數據類型定義和宏定義。
stm32f4xx_II_ppp.c/.h
在一些復雜外設中實現底層功能,它們在stm32f4xx_hal_ppp.c 中被調用。
2、stm32f4xx_it.c/stm32f4xx_it.h 文件
stm32f4xx_it.h中主要是一些終端服務函數的聲明。stm32f4xx_it.c 中是這些中斷服務函數定義,而這些函數定義除了Systick中斷服務函數Systick_Handler 外基本都是空函數,沒有任何控制邏輯。一般情況下,我們可以去掉這兩個文件,然后把中斷服務函數寫在工程中的任何一個可見文件中。
3、stm32f4xx.h 頭文件
stm32f4xx.h 頭文件,它是所有stm32f4系列的頂層頭文件。使用stm32f4任何型號的芯片,都需要包含這個頭文件。同時,因為stm32f4系列芯片型號非常多,ST為每種芯片型號定義了一個特有的片上外設訪問層頭文件,比如 STM32F407
系列, ST 定義了一個頭文件 stm32f407xx.h,然后 stm32f4xx.h 頂層頭文件會根據工程芯片型號,來選擇包含對應芯片的片上外設訪問層頭文件。我們可以打開 stm32f4xx.h 頭文件可以看到,里面有如下幾行代碼:
如果定義了宏定義標識符 STM32F407xx,那么頭文件 stm32f4xx.h 將會包含頭文件 stm32f407xx.h。
4、stm32f407xx.h 頭文件
stm32f407xx.h 是stm32f407系列芯片通用的片上外設訪問層頭文件,只要我們進行stm32f407開發,就必須使用到該文件。打開該文件我們可以看到里面主要是一些結構體和宏定義標識符。這個文件的主要作用是寄存器定義聲明以及封裝內存操作。
5、system_stm32f4xx.c/system_stm32f4xx.h 文件
頭文件system_stm32f4xx.h和源文件system_stm32f4xx.c主要是聲明和定義了系統初始化函數SystemInit以及系統時鐘更新函數SystemCoreClockUpdate。 SystemInit 函數的作用進行時鐘系統的一些初始化操作以及中斷向量表偏移地址設置,但它并沒有設置具體的時鐘值,這是與標準庫的最大區別,在使用標準庫的時候,SystemInit 函數會幫我們配置好系統時鐘配置相關的各個寄存器。在啟動文件 startup_stm32f407xx.s中會設置系統復位后,直接調用 SystemInit 函數進行系統初始化。SystemCoreClockUpdate函數是在系統時鐘配置進行修改后,調用這個函數來更新全局變量SystemCoreClock 的值,變量 SystemCoreClock 是一個全局變量,開放這個變量可以方便我們在用戶代碼中直接使用這個變量來進行一些時鐘運算。
6、stm32f4xx_hal_msp.c 文件
MSP,全稱MCU support package。stm32f4xx_hal_msp.c文件中定義了兩個函數HAL_MspInit和HAL_MspDeInit。這兩個函數分別被文件stm32f4xx_hal.c 中的 HAL_Init 和 HAL_DeInit 所調用。HAL_MspInit 函數的主要作用是進行MCU相關的硬件初始化操作。例如我們要初始化某些硬件,我們可以硬件相關的初始化配置寫在HAL_MspDeInit函數中。這樣的話,在系統啟動后調用了HAL_Init之后,會自動調用硬件初始化函數。實際上,我們在工程模塊中直接刪除stm32f4xx_hal_msp.c文件也不會對程序運行產生任何影響。
7、startup_stm32f407xx.s 啟動文件
STM32系列所以芯片工程都會有一個.s啟動文件。對于不同型號的stm32芯片啟動文件也是不一樣的。我們的開發板是STM32F407系列,所以我們需要使用與之對應的啟動文件startup_stm32f407xx.s。啟動文件的作用是進行堆棧的初始化,中斷向量表以及中斷函數定義等。啟動文件有一個很重要的作用就是系統復位后引導進行main函數。打開啟動文件startup_stm32f407xx.s ,可以看到下面幾行代碼:
; Reset handler Reset_Handler PROCEXPORT Reset_Handler [WEAK]IMPORT SystemInitIMPORT __mainLDR R0, =SystemInitBLX R0LDR R0, =__mainBX R0ENDPReset handler在我們系統啟動的時候會執行,這幾行代碼的作用是在系統啟動之后,首先調用SystemInit函數進行系統初始化,然后引導進入main函數執行用戶代碼。
接下來我們看看HAL庫工程模塊中各個文件之間的包含關系:
從上面工程文件包含關系圖可以看出,頂層頭文件stm32f4xx.h直接或間接包含了其他所有工程必要頭文件,所以在我們的用戶代碼中,我們只需要包含頂層頭文件stm32fxx.h即可。
二、HAL庫中__weak 修飾符講解
在HAL庫中,很多回調函數前面使用__weak修飾符。weak顧名思義是“弱”的意思,所以如果函數名稱前面加上__weak修飾符,我們一般稱這個函數為“弱函數”。加上了__weak修飾符的函數,用戶可以在用戶文件中重新定義一個同名函數,最終編譯器編譯的時候,會選擇用戶定義的函數,如果用戶沒有重新定義這個函數,那么編譯器就會執行__weak聲明的函數,并且編譯器不會報錯。
例如:
stm32f4xx_hal.c 文件,里面定義了一個函數 HAL_MspInit,定義如下:
可以看出,HAL_MspInit函數前面有加修飾符__weak。同時,在該文件的前面有定義函數HAL_Init,并且HAL_Init函數中調用了函數HAL_MspInit。
HAL_StatusTypeDef HAL_Init(void) { …//此處省略部分代碼 HAL_MspInit(); return HAL_OK; }如果我們沒有在工程中其他地方重新定義HAL_MspInit函數,那么HAL_Init初始化函數執行的時候,會默認執行stm32f4xx_hal.c文件中定義的HAL_MspInit函數,而這個函數沒有任何控制邏輯。如果用戶在工程中重新定義函數HAL_MspInit,那么調用HAL_Init之后,會執行用戶自己定義的HAL_MspInit函數而不是執行stm32f4xx_hal.c默認定義的函數。也就是說,表面上我們看到函數HAL_MspInit被定義了兩次,但是因為有一次定義是弱函數,使用了__weak修飾符,所以編譯器不會報錯。
__weak在回調函數的時候經常用到。這樣的好處是,系統默認定義了一個空的回調函數,保證編譯器不會報錯。同時,如果用戶自己要定義用戶回調函數,那么只需要重新定義即可,不需要考慮函數重復定義的問題,使用非常方便,在HAL庫中__weak關鍵字被廣泛使用。
三、Msp 回調函數執行過程解讀
打開工程模塊SYSTEM分組下面的usart.c文件可以看到,內部我們定義了兩個函數uart_init和HAL_UART_MspInit。我們先來大致看看這兩個函數的定義。
//初始化IO 串口1 //bound:波特率 void uart_init(u32 bound) { //UART 初始化設置UART1_Handler.Instance=USART1; //USART1UART1_Handler.Init.BaudRate=bound; //波特率UART1_Handler.Init.WordLength=UART_WORDLENGTH_8B; //字長為8位數據格式UART1_Handler.Init.StopBits=UART_STOPBITS_1; //一個停止位UART1_Handler.Init.Parity=UART_PARITY_NONE; //無奇偶校驗位UART1_Handler.Init.HwFlowCtl=UART_HWCONTROL_NONE; //無硬件流控UART1_Handler.Init.Mode=UART_MODE_TX_RX; //收發模式HAL_UART_Init(&UART1_Handler); //HAL_UART_Init()會使能UART1HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);//該函數會開啟接收中斷:標志位UART_IT_RXNE,并且設置接收緩沖以及接收緩沖接收最大數據量} //UART底層初始化,時鐘使能,引腳配置,中斷配置 //此函數會被HAL_UART_Init()調用 //huart:串口句柄void HAL_UART_MspInit(UART_HandleTypeDef *huart) {//GPIO端口設置GPIO_InitTypeDef GPIO_Initure;if(huart->Instance==USART1)//如果是串口1,進行串口1 MSP初始化{__HAL_RCC_GPIOA_CLK_ENABLE(); //使能GPIOA時鐘__HAL_RCC_USART1_CLK_ENABLE(); //使能USART1時鐘GPIO_Initure.Pin=GPIO_PIN_9; //PA9GPIO_Initure.Mode=GPIO_MODE_AF_PP; //復用推挽輸出GPIO_Initure.Pull=GPIO_PULLUP; //上拉GPIO_Initure.Speed=GPIO_SPEED_FAST; //高速GPIO_Initure.Alternate=GPIO_AF7_USART1; //復用為USART1HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化PA9GPIO_Initure.Pin=GPIO_PIN_10; //PA10HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化PA10#if EN_USART1_RXHAL_NVIC_EnableIRQ(USART1_IRQn); //使能USART1中斷通道HAL_NVIC_SetPriority(USART1_IRQn,3,3); //搶占優先級3,子優先級3 #endif }}用戶函數uart_init主要作用是設置串口1相關的參數,包括波特率,停止位,奇偶校驗位等,并且最終是通過調用HAL_UART_Init函數進行參數設置。而函數HAL_UART_MspInit則主要進行串口GPIO引腳初始化設置。接下來我們打開usart_Init函數內部調用UART初始化函數HAL_UART_Init可以看到代碼如下:
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart) { …//此處省略部分代碼 if(huart->State == HAL_UART_STATE_RESET)//如果串口沒有進行過初始化 { huart->Lock = HAL_UNLOCKED; HAL_UART_MspInit(huart); } … //此處省略部分代碼 return HAL_OK; }在函數HAL_UART_Init內部,通過判斷邏輯判斷如果串口還沒有進行初始化,那么會調用函數HAL_UART_MspInit進行相關初始化設置。同時,我們可以看到,在文件stm32f4xx_hal_uart.c 內部,有定義一個弱函數 HAL_UART_MspInit。
__weak void HAL_UART_MspInit(UART_HandleTypeDef *huart) {/* Prevent unused argument(s) compilation warning */UNUSED(huart);/* NOTE: This function should not be modified, when the callback is needed,the HAL_UART_MspInit could be implemented in the user file*/ }這里定義的弱函數HAL_UART_MspInit是一個空函數,沒有任何實際的控制邏輯。根據前面的講解可知,__weak修飾符定義的弱函數如果用戶自己重新定義了這個函數,那么會有限執行用戶定義函數。所以,實際上在函數 HAL_UART_Init 內部調用的 HAL_UART_MspInit()函數,最終執行的是用戶在 usart.c 中自定義的 HAL_UART_MspInit()函數。
那么整個串口初始化的過程為:用戶函數usart_init->HAL_UART_Init->HAL_UART_MspInit。
為什么串口相關初始化不在HAL_UART_Init函數內部一次初始化而還要調用函數HAL_UART_MspInit呢?實際計時HAL庫的一個優點,它通過開放一個回調函數HAL_UART_MspInit,讓用戶自己去編寫與串口想干的MCU級別的硬件初始化,而與MCU無關的串口參數相關的通用配置則放在HAL_UART_Init。
四、程序執行流程圖
啟動文件startup_stm32f429xx.s 中 Reset_Handler 部分會引導先執行SystemInit函數,然后再進入main函數。再main函數內部,一般情況下,我們會把HAL初始化函數HAL_Init放在最開頭部分,然后再進行時鐘初始化設置。這些設置完成之后,接下來便是調用外設初始化函數HAL_PPP_Init進行外設參數初始化設置,同時重寫回調函數HAL_PPP_MspInit進行外設MCU相關的參數設置。最后編寫我們的控制邏輯。
總結
以上是生活随笔為你收集整理的STM32F4 HAL库开发 -- 工程模板解读的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: To B的痛
- 下一篇: STM32F4 HAL库开发 --时钟使