STM32 之 HAL庫詳解 及 手動移植
- 2018.1.19 更新其中的錯誤部分
HAL庫結構
說到STM32的HAL庫,就不得不提STM32CubeMX,其作為一個視覺化的配置工具,對於開發者來說,確實大大節省了開發時間。STM32CubeMX就是以HAL庫為基礎的,且目前僅支援HAL庫及LL庫!首先看一下,官方給出的HAL庫的包含結構:
- **stm32f2xx.h**主要包含STM32同系列晶片的不同具體型號的定義,是否使用HAL庫等的定義,接著,其會根據定義的晶片訊號包含具體的晶片型號的標頭檔案:#if defined(STM32F205xx) #include "stm32f205xx.h" #elif defined(STM32F215xx) #include "stm32f215xx.h" #elif defined(STM32F207xx) #include "stm32f207xx.h" #elif defined(STM32F217xx) #include "stm32f217xx.h" #else #error "Please select first the target STM32F2xx device used in your application (in stm32f2xx.h file)" #endif
緊接著,其會包含stm32f2xx_hal.h
。
- stm32f2xx_hal.h:
stm32f2xx_hal.c/h
主要實現HAL庫的初始化、系統滴答相關函式、及CPU的除錯模式配置 - stm32f2xx_hal_conf.h :該檔案是一個使用者級別的配置檔案,用來實現對HAL庫的裁剪,其位於使用者檔案目錄,不要放在庫目錄中。
接下來對於HAL庫的原始碼檔案進行一下說明,HAL庫檔名均以stm32f2xx_hal開頭,後面加上_外設或者模組名(如:stm32f2xx_hal_adc.c):
庫檔案:
stm32f2xx_hal_ppp.c/.h // 主要的外設或者模組的驅動原始檔,包含了該外設的通用API
stm32f2xx_hal_ppp_ex.c/.h // 外圍裝置或模組驅動程式的擴充套件檔案。這組檔案中包含特定型號或者系列的晶片的特殊API。以及如果該特定的晶片內部有不同的實現方式,則該檔案中的特殊API將覆蓋_ppp中的通用API。
stm32f2xx_hal.c/.h // 此檔案用於HAL初始化,並且包含DBGMCU、重對映和基於systick的時間延遲等相關的API
其他庫檔案
使用者級別檔案:
stm32f2xx_hal_msp_template.c // 只有.c沒有.h。它包含使用者應用程式中使用的外設的MSP初始化和反初始化(主程式和回撥函式)。使用者複製到自己目錄下使用模板。
stm32f2xx_hal_conf_template.h // 使用者級別的庫配置檔案模板。使用者複製到自己目錄下使用
system_stm32f2xx.c // 此檔案主要包含SystemInit()函式,該函式在剛復位及跳到main之前的啟動過程中被呼叫。 **它不在啟動時配置系統時鐘(與標準庫相反)**。 時鐘的配置在使用者檔案中使用HAL API來完成。
startup_stm32f2xx.s // 晶片啟動檔案,主要包含堆疊定義,終端向量表等
stm32f2xx_it.c/.h // 中斷處理函式的相關實現
main.c/.h //
根據HAL庫的命名規則,其API可以分為以下三大類:
- 初始化/反初始化函式: 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 ().
注意:
- 目前LL庫是和HAL庫捆綁釋出的,所以在HAL庫原始碼中,還有一些名為 stm32f2xx_ll_ppp的原始碼檔案,這些檔案就是新增的LL庫檔案。
- 使用CubeMX生產專案時,可以選擇LL庫
HAL庫最大的特點就是對底層進行了抽象。在此結構下,使用者程式碼的處理主要分為三部分:
- 處理外設控制代碼(實現使用者功能)
- 處理MSP
- 處理各種回撥函式
###外設控制代碼定義
使用者程式碼的第一大部分:對於外設控制代碼的處理。 HAL庫在結構上,對每個外設抽象成了一個稱為ppp_HandleTypeDef的結構體,其中ppp就是每個外設的名字。*所有的函式都是工作在ppp_HandleTypeDef指標之下。
1. 多例項支援:每個外設/模組例項都有自己的控制代碼。 因此,例項資源是獨立的
2. 外圍程序相互通訊:該控制代碼用於管理程序例程之間的共享資料資源。
下面,以ADC為例
/**
* @brief ADC handle Structure definition
*/
typedef struct
{
ADC_TypeDef *Instance; /*!< Register base address */
ADC_InitTypeDef Init; /*!< ADC required parameters */
__IO uint32_t NbrOfCurrentConversionRank; /*!< ADC number of current conversion rank */
DMA_HandleTypeDef *DMA_Handle; /*!< Pointer DMA Handler */
HAL_LockTypeDef Lock; /*!< ADC locking object */
__IO uint32_t State; /*!< ADC communication state */
__IO uint32_t ErrorCode; /*!< ADC Error code */
}ADC_HandleTypeDef;
從上面的定義可以看出,ADC_HandleTypeDef中包含了ADC可能出現的所有定義,對於使用者想要使用ADC只要定義一個ADC_HandleTypeDef的變數,給每個變數賦好值,對應的外設就抽象完了。接下來就是具體使用了。
當然,對於那些共享型外設或者說系統外設來說,他們不需要進行以上這樣的抽象,***這些部分與原來的標準外設庫函式基本一樣。***例如以下外設:
- GPIO
- SYSTICK
- NVIC
- RCC
- FLASH
以GPIO為例,對於HAL_GPIO_Init() 函式,其只需要GPIO 地址以及其初始化引數即可。
###三種程式設計方式
HAL庫對所有的函式模型也進行了統一。在HAL庫中,支援三種程式設計模式:輪詢模式、中斷模式、DMA模式(如果外設支援)。其分別對應如下三種類型的函式(以ADC為例):
HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Start_IT(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Stop_IT(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length);
HAL_StatusTypeDef HAL_ADC_Stop_DMA(ADC_HandleTypeDef* hadc);
其中,帶_IT的表示工作在中斷模式下;帶_DMA的工作在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_XXXX (__HANDLE__, __PARAM__)
:指定外設的巨集定義__HAL_PPP_GET_ IT_SOURCE (__HANDLE__, __ INTERRUPT __)
:檢查中斷源
###三大回調函式
在HAL庫的原始碼中,到處可見一些以__weak
開頭的函式,而且這些函式,有些已經被實現了,比如:
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
/*Configure the SysTick to have interrupt in 1ms time basis*/
HAL_SYSTICK_Config(SystemCoreClock/1000U);
/*Configure the SysTick IRQ priority */
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,0U);
/* Return function status */
return HAL_OK;
}
有些則沒有被實現,例如:
__weak void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(hspi);
/* NOTE : This function should not be modified, when the callback is needed,the HAL_SPI_TxCpltCallback should be implemented in the user file
*/
}
所有帶有__weak
關鍵字的函式表示,就可以由使用者自己來實現。如果出現了同名函式,且不帶__weak
關鍵字,那麼聯結器就會採用外部實現的同名函式。通常來說,HAL庫負責整個處理和MCU外設的處理邏輯,並將必要部分以回撥函式的形式給出到使用者,使用者只需要在對應的回撥函式中做修改即可。 HAL庫包含如下三種使用者級別回撥函式(PPP為外設名):
1. 外設系統級初始化/解除初始化回撥函式(使用者程式碼的第二大部分:對於MSP的處理):HAL_PPP_MspInit()
和 HAL_PPP_MspDeInit
**。例如:__weak void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
。在HAL_PPP_Init() 函式中被呼叫,用來初始化底層相關的裝置(GPIOs, clock, DMA, interrupt)
2. 處理完成回撥函式:HAL_PPP_ProcessCpltCallback
*(Process指具體某種處理,如UART的Tx),例如:__weak void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
。當外設或者DMA工作完成後時,觸發中斷,該回調函式會在外設中斷處理函式或者DMA的中斷處理函式中被呼叫
3. 錯誤處理回撥函式:HAL_PPP_ErrorCallback
例如:__weak void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
**。當外設或者DMA出現錯誤時,觸發終端,該回調函式會在外設中斷處理函式或者DMA的中斷處理函式中被呼叫
- 絕大多數使用者程式碼均在以上三大回調函式中實現。
- HAL庫結構中,在每次初始化前(尤其是在多次呼叫初始化前),先呼叫對應的反初始化(DeInit)函式是非常有必要的。某些外設多次初始化時不呼叫返回會導致初始化失敗。
- 完成回撥函式有多中,例如串列埠的完成回撥函式有
HAL_UART_TxCpltCallback 和 HAL_UART_TxHalfCpltCallback
等- (使用者程式碼的第三大部分:對於上面第二點和第三點的各種回撥函式的處理)
- 在實際使用中,發現HAL仍有不少問題,例如在使用USB時,其庫配置存在問題
HAL庫移植使用
基本步驟
- 複製
stm32f2xx_hal_msp_template.c
,參照該模板,依次實現用到的外設的HAL_PPP_MspInit()
和HAL_PPP_MspDeInit
。 - 複製
stm32f2xx_hal_conf_template.h
,使用者可以在此檔案中自由裁剪,配置HAL庫。 - 在使用HAL庫時,必須先呼叫函式:
HAL_StatusTypeDef HAL_Init(void)
(該函式在stm32f2xx_hal.c
中定義,也就意味著第一點中,必須首先實現HAL_MspInit(void)
和HAL_MspDeInit(void)
) - HAL庫與STD庫不同,HAL庫使用RCC中的函式來配置系統時鐘,使用者需要單獨寫時鐘配置函式(STD庫預設在
system_stm32f2xx.c
中) - 關於中斷,HAL提供了中斷處理函式,只需要呼叫HAL提供的中斷處理函式。使用者自己的程式碼,不建議先寫到中斷中,而應該寫到HAL提供的回撥函式中。
- 對於每一個外設,HAL都提供了回撥函式,回撥函式用來實現使用者自己的程式碼。整個呼叫結構由HAL庫自己完成。例如:Uart中,HAL提供了
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);
函式,使用者只需要觸發中斷後,使用者只需要呼叫該函式即可,同時,自己的程式碼寫在對應的回撥函式中即可!如下:
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);
使用了哪種就用哪個回撥函式即可!
基本結構
綜上所述,使用HAL庫編寫程式(針對某個外設)的基本結構(以串列埠為例)如下:
- ***配置外設控制代碼。***例如,建立
UartConfig.c
,在其中定義串列埠控制代碼UART_HandleTypeDef huart;
,接著使用初始化控制代碼(HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)
) - 編寫Msp。 例如,建立
UartMsp.c
,在其中實現void HAL_UART_MspInit(UART_HandleTypeDef* huart) 和 void HAL_UART_MspDeInit(UART_HandleTypeDef* huart)
- 實現對應的回撥函式。例如,建立
UartCallBack.c
,在其中實現上文所說明的三大回調函式中的完成回撥函式和錯誤回撥函式**
參考文件
- ST - Description of STM32F4 HAL and LL drivers.pdf
- ST - en.stm32_embedded_software_offering.pdf
至此,HAL庫的總體結構就介紹完了!具體的每個檔案的詳細說明,官方原始碼註釋很詳細!