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