HAL庫文件結構:
HAL驅動文件:
外設驅動API文件和頭文件:包含了常見主要的通用API,其中ppp表示外設名稱,如adc、usart、gpio、irda等;
- stm32f0xx_hal_ppp.c
- stm32f0xx_hal_ppp.h
外設驅動擴展API文件和頭文件:包含指定的API和內部不同實現以覆蓋通用API的新定義API接口函數,其中ppp表示外設名稱;
- stm32xx_hal_ppp_ex.c
- stm32xx_hal_ppp_ex.h
初始化HAL庫文件、包含DBGMCU(調試接口)、Remap(重映射)和SysTick的TimeDelay;
- stm32xx_hal.c
- stm32xx_hal.h
自帶的相應庫函數例子:包含相應外設的初始化和去初始化;
- stm32xx_hal_msp_template.c
- stm32xx_hal_conf_template.h
通用HAL資源定義:包含通用定義聲明、枚舉、結構和宏定義;
- stm32xx_hal_def.h
用戶應用文件:
用於在main函數前初始化系統時鍾,包含SystermInit()函數,但不會在StartUp時配置相同時鍾(與標准庫不同的地方);
- system_stm32f0xx.c
包含reset handler處理函數、中斷向量、並允許調整堆棧大小:
- startup_stm32f0xx.s
EWARM工具鏈文件,用以調整堆棧大小以適應應用程序的要求;
- stm32f0xx_flash.icf
用戶自定義外設初始化文件:包括初始化和去初始化,包含主例程和回調;
- stm32f0xx_hal_msp.c
用戶自定義驅動文件:允許用戶自定義HAL驅動,可以使用默認配置而無需修改;
- stm32f0xx_hal_conf.h
異常處理和外設中斷服務文件:會在SysTick_Handler()函數中反復調用HAL_IncTick()以實現延時;
- stm32f0xx_it.c/.h
主函數:調用HAL_Init()函數、在Debug模式下使用的assert_failed()時間檢測函數、系統時鍾配置函數、外設HAL初始化和應用代碼;
- main.c/.h
通過STM32CubeMX配置的工程,已經默認做好如下的配置:
- HAL初始化完成;
- SysTick中斷服務實現HAL_Delay()延時功能;
- 系統時鍾配置為器件最大頻率的時鍾;
HAL數據結構:
每一個HAL驅動都遵循以下數據結構:
- 外設句柄結構Peripheral handle structures
- 初始化和配置結構Initialization and configuration structures
- 特殊的過程結構Specific process structures
Peripheral handle structures:
PPP_HandlerTypeDef *handler是HAL驅動程序中實現的主要結構;它處理外設模塊配置、注冊、嵌入外圍設備所需要的所有結構和變量;
該句柄結構主要用於:
- 可以初始化多個實例(可以使用相同的結構定義和配置多個相同外設,如USART1、USART2、USART3),使每個初始化的外設都有相同的完整的結構;
- 外圍進程互通,管理進程之間的共享數據資源,如全局變量、DMA句柄結構、狀態機;
- 存儲,用於管理對應的初始化HAL外設驅動程序中的全局變量;
外設句柄結構舉例:
typedef struct { USART_TypeDef *Instance; /* USART registers base address */ USART_InitTypeDef Init; /* Usart communication parameters */ uint8_t *pTxBuffPtr;/* Pointer to Usart Tx transfer Buffer */ uint16_t TxXferSize; /* Usart Tx Transfer size */ __IO uint16_t TxXferCount;/* Usart Tx Transfer Counter */ uint8_t *pRxBuffPtr;/* Pointer to Usart Rx transfer Buffer */ uint16_t RxXferSize; /* Usart Rx Transfer size */ __IO uint16_t RxXferCount; /* Usart Rx Transfer Counter */ DMA_HandleTypeDef *hdmatx; /* Usart Tx DMA Handle parameters */ DMA_HandleTypeDef *hdmarx; /* Usart Rx DMA Handle parameters */ HAL_LockTypeDef Lock; /* Locking object */ __IO HAL_USART_StateTypeDef State; /* Usart communication state */ __IO HAL_USART_ErrorTypeDef ErrorCode;/* USART Error code */ }USART_HandleTypeDef;
多實例特性意味着應用程序的所有API都是可重入的,因此會避免使用全局變量,當子例程遞歸調用時,如果子例程依賴全局變量保持不變但是該變量在循環調用時發生改變,則可能會造成子例程無法重入;
因此要遵守:
可重入代碼區域不應包含任何靜態或全局的非常量數據,可重入函數則可以使用全局數據;例如在整個中斷服務函數中使用硬件狀態全局變量,會造成硬件狀態的易失性;使用靜態全局變量期間不應發生中斷或其他的信號響應,並因盡量只應用在對其本身讀-修改-寫的過程中;
可重入代碼不會修改自己的代碼;
當外設使用DMA全雙工同時管理多個外設時,每個進程的DMA接口句柄都會更新對應的外設PPP_HandleTypeDef;即每個可以使用DMA的外設句柄handler中都包含了DMA_HandleTypeDef;
對於共享(所有外設和系統配置都可以使用的)和系統外圍設備,沒有句柄或實例對象;包括GPIO、SYSTICK、NVIC、PWR、RCC、FLASH;
外設句柄結構的定義:在外設驅動頭文件stm32f0xx_hal_ppp.h中定義;其名稱通常都是PPP_HandleTypeDef;
這些結構通常都是用來初始化子模塊和子實例;
特定的進程結構:具體的流程結構使用特定的流程(通用API),通常也是定義在外設驅動頭文件中;
API分類:
通用API,存在於所有通用的HAL驅動程序中;
HAL_StatusTypeDef HAL_ADC_Init(ADC_HandleTypeDef* hadc); HAL_StatusTypeDef HAL_ADC_DeInit(ADC_HandleTypeDef *hadc); 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); void HAL_ADC_IRQHandler(ADC_HandleTypeDef* hadc);
擴展API,存在於擴展外設庫文件中,有兩類;
第一種是同於特定系列的擴展API;
HAL_StatusTypeDef HAL_ADCEx_Calibration_Start(ADC_HandleTypeDef* hadc, uint32_t SingleDiff); uint32_t HAL_ADCEx_Calibration_GetValue(ADC_HandleTypeDef* hadc, uint32_t SingleDiff);
第二種是用於特定型號的API;
#if defined(STM32F042x6) || defined(STM32F048xx) || defined(STM32F072xB) || defined(STM32F078xx) || \ defined(STM32F091xC) || defined(STM32F098xx) #endif /* STM32F042x6 || STM32F048xx || STM32F072xB || STM32F078xx || */ /* STM32F091xC || STM32F098xx */
HAL驅動規則:
HAL_API命名規則:下面這個表可以仔細看看;
其中PPP是外設模式,而不是指外設本身;
MODE指的是過程模式,是輪循、中斷或DMA模式;
FEATURE指的是實現功能,如Start、Stop;
HAL通用命名規則:
以下外圍設備其初始化不需要提供句柄handler和實例對象instance object;GPIO、SYSTICK、NVIC、RCC、FLASH
處理中斷和特定時鍾配置的宏在每個外設/模塊驅動頭文件中定義;
NVIC和SYSTICK是ARMCortex的兩個核心功能,與這些功能相關的API位於stm32f0xx_hal_cortex.c中;
從寄存器中讀取狀態位或標志時,它由位移值構成,且通常返回的寬度為32位;
在初始化HAL_PPP_Init() 的API中,Init函數在修改句柄字段之前會檢查句柄PPP_HandleTypeDef內容是否為空;
HAL_PPP_Init(PPP_HandleTypeDef) if(hppp == NULL) { return HAL_ERROR; }
宏定義分為兩類:
條件宏定義;
#define ABS(x) (((x) > 0) ? (x) : -(x))
偽代碼宏(多指令宏);
#define __HAL_LINKDMA(__HANDLE__, __PPP_DMA_FIELD_, __DMA_HANDLE_) \ do{ \ (__HANDLE__)->__PPP_DMA_FIELD_ = &(__DMA_HANDLE_); \ (__DMA_HANDLE_).Parent = (__HANDLE__); \ } while(0)
HAL中斷處理程序和回調函數
除了API,HAL外設驅動還包含:
HAL_PPP_IRQHandler()外設中斷處理函數;
用戶定義回調函數,系統默認的回調函數定義為weak屬性,一旦用戶自己定義了回調函數會覆蓋系統默認的回調函數;
有三種類型的回調函數:
外圍系統初始化/去初始化回調:HAL_PPP_MspInit()、HAL_PPP_MspDeInit();
處理完整進程的回調函數:HAL_PPP_ProcessCpltCallback;
錯誤處理回調函數:HAL_PPP_ErrorCallback;
HAL通用APIs:
通用的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 ()
HAL擴展APIs:
擴展API通常是特定系列或同一系列中特定功能或覆蓋已修改的API,擴展功能通常由stm32f0xx_hal_ppp_ex.c/h文件構成;
HAL程序由五種不同的方式處理特定的IP功能:
- 添加部分特定的功能:將新的API添加到stm32f0xx_hal_ppp_ex.c擴展文件中,並命名為HAL_PPPEx_Function();
- 添加一系列的功能:操作同添加部分特定功能相似;
- 添加新的外圍設備:在stm32f0xx_hal_newppp.c中添加新的可用外圍設備,同時在stm32f0xx_hal_conf.h中包含這個新外圍設備的宏;
#define HAL_NEWPPP_MODULE_ENABLED
- 更新已有的通用API:想要覆蓋一個在stm32f0xx_hal_ppp.c中已經存在的API函數,則在stm32f0xx_hal_ppp_ex.c擴展文件中使用相同名稱的定義,因為通用API定義位weak,所以編譯器將通過新定義的函數覆蓋原始例程;
- 升級已存在的數據結構:通過采用不同的器件宏定義來重新定義數據結構PPP_InitTypeDef;
#if defined (STM32F072xB) typedef struct { (…) }PPP_InitTypeDef; #endif /* STM32F072xB */
文件包含模型:
在這其中stm32f0xx_hal.h是連接整個HAL庫源和用戶源的唯一頭文件;
其中文件包含關系如下圖所示:其基本的包含關系是
HAL庫文件或main文件—stm32f0xx_hal.h—stm32f0xx_hal_conf.h—HAL庫頭文件
由於相應的外設功能需要在外設模塊選擇中添加相應的功能宏,在配置文件stm32f0xx_hal_conf.h中可以找到;
HAL共有配置:
HALStatus:除了布爾函數和IRQ處理程序,幾乎所有的HAL_API都使用HAL狀態,它返回當前API操作的狀態;
Typedef enum { HAL_OK = 0x00, HAL_ERROR = 0x01, HAL_BUSY = 0x02, HAL_TIMEOUT = 0x03 } HAL_StatusTypeDef;
HALLocked:鎖定狀態防止意外的共享數據修改和讀寫;
typedef enum { HAL_UNLOCKED = 0x00, /*!<Resources unlocked */ HAL_LOCKED = 0x01 /*!< Resources locked */ } HAL_LockTypeDef;
除了共有的資源,stm32f0xx_hal_def.h文件還調用CMSIS庫中的stm32f0xx.h文件來獲取所有外設的數據結構和地址映射:
外設寄存器和位定義的聲明;
用於訪問外設寄存器硬件的宏(讀寫寄存器.etc);
CommonMarco:
- NULL和HAL_MAX_DELAY宏定義;
#define HAL_MAX_DELAY 0xFFFFFFFF
- 將PPP外設鏈接到DMA結構指針的宏:__HAL_LINKDMA();
#define __HAL_LINKDMA(__HANDLE__, __PPP_DMA_FIELD_, __DMA_HANDLE_) \ do{ \ (__HANDLE__)->__PPP_DMA_FIELD_ = &(__DMA_HANDLE_); \ (__DMA_HANDLE_).Parent = (__HANDLE__); \ } while(0)
HAL配置:
配置文件stm32f0xx_hal_conf.h允許用戶自定義配置參數和定義;
示例配置文件stm32f0xx_hal_conf_template.h中開啟了所有的HAL庫定義,並將時鍾配置為最大時鍾數值;
HAL外圍設備處理:
時鍾Clock:
兩個主要的功能配置時鍾:
HAL_RCC_OscConfig (RCC_OscInitTypeDef *RCC_OscInitStruct)
用來配置和使能時鍾源,如HSE、HSI、LSE、LSI、PLL;
HAL_RCC_ClockConfig (RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency)
用來選擇SystemClock系統時鍾、配置AHB和APB時鍾分頻、配置Flash等待狀態的數量、HCLK時鍾更改時更新SysTick配置;
某些外設時鍾不是從SystemClock系統時鍾中派生的(USB、RTC),這種情況下,時鍾配置由stm32f0xx_hal_rcc_ex.c中定義的擴展API執行:
HAL_RCCEx_PeriphCLKConfig(RCC_PeriphCLKInitTypeDef *PeriphClkInit)
提供額外的RCC_HAL驅動程序功能:
HAL_RCC_DeInit()時鍾去啟動功能,將時鍾配置返回到復位狀態;
獲取時鍾功能,並允許檢索各種時鍾配置(system clock、HCLK、PCLKn);
MCO和CSS配置功能;
在stm32f0xx_hal_rcc.h和stm32f0xx_hal_rcc_ex.h中定義了一組宏;它們允許在RCC塊寄存器上執行基本操作,例如外設時鍾門控/復位控制:
__PPP_CLK_ENABLE/__PPP_CLK_DISABLE to enable/disable the peripheral clock __PPP_FORCE_RESET/__PPP_RELEASE_RESET to force/release peripheral reset __PPP_CLK_SLEEP_ENABLE/__PPP_CLK_SLEEP_DISABLE to enable/disable the peripheral clock during low power (Sleep) mode.
GPIOs:
GPIO HAL API主要包含:
HAL_GPIO_Init()/HAL_GPIO_DeInit()
HAL_GPIO_ReadPin()/HAL_GPIO_WritePin()
HAL_GPIO_TogglePin ()
除了標准GPIO模式(輸入、輸出、模擬)外,引腳模式還可以配置成帶有中斷IT和事件生成EVENT的EXTI模式;
此模式需要從stm32f0xx_it.c中調用HAL_GPIO_EXTI_IRQHandler()並實現回調函數HAL_GPIO_EXTI_Callback();
下表是介紹了GPIO_InitTypeDef結構可以配置的參數列表:
可以參考GPIO的一些基礎配置:
配置GPIO為輸出PP模式,控制LED燈;
GPIO_InitStruct.Pin = GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM; HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
配置GPIO作為下降沿觸發外部中斷模式;
GPIO_InitStructure.Mode = GPIO_MODE_IT_FALLING; GPIO_InitStructure.Pull = GPIO_NOPULL; GPIO_InitStructure.Pin = GPIO_PIN_0; HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
配置復用模式為串口USART1模式;
GPIO_InitStruct.Pin = GPIO_PIN_9; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = GPIO_AF4_USART1; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
Cortex NVIC和SysTick時鍾
HAL庫在stm32f0xx_hal_cortex.c中給了處理NVIC和SysTick的APIs,這些包含的APIs有:
- HAL_NVIC_SetPriority()
- HAL_NVIC_EnableIRQ()/HAL_NVIC_DisableIRQ()
- HAL_NVIC_SystemReset()
- HAL_SYSTICK_IRQHandler()
- HAL_NVIC_GetPendingIRQ() / HAL_NVIC_SetPendingIRQ ()/ HAL_NVIC_ClearPendingIRQ()
- HAL_SYSTICK_Config()
- HAL_SYSTICK_CLKSourceConfig()
- HAL_SYSTICK_Callback()
PWR:
PWR HAL驅動程序處理電源管理,所有的STM32系列共享的功能如下:
PVD配置,啟動/禁用和中斷處理:
HAL_PWR_PVDConfig()
HAL_PWR_EnablePVD() / HAL_PWR_DisablePVD()
HAL_PWR_PVD_IRQHandler()
HAL_PWR_PVDCallback()
Wakeup喚醒引腳配置:
HAL_PWR_EnableWakeUpPin() / HAL_PWR_DisableWakeUpPin()
低功耗模式配置:
HAL_PWR_EnterSLEEPMode()
HAL_PWR_EnterSTOPMode()
HAL_PWR_EnterSTANDBYMode()
備份的域配置:
HAL_PWR_EnableBkUpAccess()/ HAL_PWR_DisableBkUpAccess()
EXTI:
EXTI不被視為獨立外圍設備,而是其他外圍設備使用的服務,因此沒有EXTI的API;
但每個外圍HAL驅動程序實現關聯的EXTI配置,EXTI功能在其頭文件中表現為宏;
連接到GPIO的前16條EXTI線在GPIO驅動中進行管理,GPIO_InitTypeDef結構允許將IO配置為外部中斷IT或外部事件EVENT;
其內部連接到PVD、RTC、USB和COMP的EXTI線路通過庫定義好的宏配置外設HAL驅動;
EXTI中斷API:將EXTI中斷線連接到內部外設;
PPP_EXTI_LINE_FUNCTION
外設中斷使能:
__HAL_PPP_EXTI_ENABLE_IT
__HAL_PPP_EXTI_DISABLE_IT
獲取EXTI中斷狀態:
__HAL_PPP_EXTI_GET_FLAG
__HAL_PPP_EXTI_CLEAR_FLAG
生成EXTI中斷事件:
__HAL_PPP_EXTI_GENERATE_SWIT
開啟EXTI中斷線事件:
__HAL_PPP_EXTI_ENABLE_EVENT
__HAL_PPP_EXTI_DISABLE_EVENT
如果選擇了EXTI模式,則用戶必須從stm32f0xx_it.c文件中調用HAL_PPP_FUNCTION_IRQHandler()來實現HAL_PPP_FUNCTIONCallback()回調函數;
DMA:
DMA HAL驅動程序允許啟用和配置外設連接到DMA通道(內部FLASH和SRAM除外),對於給定的HAL_DMA_Init()則可以配置以下參數:
- 傳輸方向
- 源和目標的數據格式
- 循環、正常或外設流模式
- 通道優先級
- 源和目標的遞增模式
- FIFO模式或其閾值
- 源和目標的突發模式
有兩種可以定義的模式:
輪詢模式
- 使用HAL_DMA_Start()來配置源和目標地址以及要傳輸的數據長度,來啟動DMA;
- 使用HAL_DMA_PollForTransfer()來獲取當前傳輸的結果,可以根據這個判斷來配置應用程序的超時設置;
中斷模式
- 使用HAL_NVIC_SetPriority()來配置DMA的中斷優先級
- 使用HAL_NVIC_EnableIRQ()來使能DMAIRQ處理函數
- 使用HAL_DMA_Start_IT()來配置DMA的源和目標地址以及要傳輸的數據長度來使能DMA傳輸;
- 使用HAL_DMA_IRQHandler()子程序來在DMA_IRQHandler()中調用;
- 當數據傳輸完成時,執行HAL_DMA_IRQHandler()函數並且可以通過定制XferCpltCallback和XferErrorCallback來調用用戶函數;
獲取狀態來確保進行有效的DMA管理:
HAL_DMA_GetState()獲取DMA狀態;
HAL_DMA_GetError()獲取DMA錯誤標志;
HAL_DMA_Abort()終止當前操作;
最常用的DMA中斷
DMA通道使能:
__HAL_DMA_ENABLE: enables the specified DMA Channels.
__HAL_DMA_DISABLE: disables the specified DMA Channels.
獲取/清除DMA中斷標志:
__HAL_DMA_GET_FLAG: gets the DMA Channels pending flags.
__HAL_DMA_CLEAR_FLAG: clears the DMA Channels pending flags.
DMA中斷使能:
__HAL_DMA_ENABLE_IT: enables the specified DMA Channels interrupts.
__HAL_DMA_DISABLE_IT: disables the specified DMA Channels interrupts.
檢查DMA中斷源是否開啟:
__HAL_DMA_GET_IT_SOURCE: checks whether the specified DMA channel interrupt has occurred or not.
在DMA模式下使用外設,應在HAL_PPP_MspInit()回調中完成DMA的初始化,同時將DMA句柄與PPP句柄相關聯;
只有在內存到內存傳輸的情況下,用戶應用程序才需要初始化DMA通道回調;但是,當使用外設到內存的傳輸時,這些回調會通過調用使用DMA的進程API函數自動初始化;
如何使用HAL驅動:
HAL使用模型:
HAL初始化:
HAL全局初始化:除了外設初始化和去初始化,還在stm32f0xx_hal.c提供了以下的API來初始化HAL內核:
HAL_Init()
初始化數據和指令緩存、預取隊列集;
設置Systick時鍾每1s發生一次中斷(基於HSI時鍾);
調用HAL_MspInit()用戶回調函數(該函數也被設定為弱空函數)執行系統級初始化(時鍾、GPIO、DMA、中斷);
HAL_DeInit()
復位所有的外設;
調用HAL_MspDeInit()用戶回調函數;
HAL_GetTick()
用來獲取SysTick的計數值來處理外設驅動的超時;
HAL_Delay()
使用SysTick來定時1ms;
使用HAL_Delay()必須要注意,此函數基於SysTickISR中遞增的變量提供准確的延時,如果外設調用HAL_Delay()來實現延時,則SysTick中斷必須具有比外設中斷更高的優先級,否則將阻止調用ISR;
系統時鍾初始化:
系統時鍾的配置可以在main代碼前完成,也可以用戶自己在代碼中定義;
static void SystemClock_Config(void) { RCC_ClkInitTypeDef RCC_ClkInitStruct; RCC_OscInitTypeDef RCC_OscInitStruct; /* Enable HSE Oscillator and Activate PLL with HSE as source */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PREDIV = RCC_PREDIV_DIV1; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL6; if (HAL_RCC_OscConfig(&RCC_OscInitStruct)!= HAL_OK) { Error_Handler(); } /* Select PLL as system clock source and configure the HCLK, PCLK1 clocks dividers */ RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1); RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1)!= HAL_OK) { Error_Handler(); } }
HAL MSP初始化過程
通過HAL_PPP_Init()初始化外設,同時也會通過HAL_PPP_MspInit()初始化硬件資源;
MSP回調執行與各個外設不同的附加硬件資源:RCC、GPIO、NVIC、DMA;
並且所有帶句柄Handler的HAL驅動程序都包含兩個函數:
void __weak HAL_PPP_MspInit(PPP_HandleTypeDef *hppp)
void __weak HAL_PPP_MspDeInit(PPP_HandleTypeDef *hppp)
MSP回調在每個外設驅動程序中被聲明成空函數,用戶可以通過使用它來設置低級初始化或省略使用自己的初始化例程;
HAL MSP回調函數在stm32f0xx_hal_msp.c中實現,stm32f0xx_hal_msp_template.c文件在HAL文件夾中,這個文件由STM32CubeMX工具生成並進一步修改;
在文件stm32f0xx_hal_msp.c中包含以下函數:前兩個是全局初始化、后兩個是外設初始化;
當一個或多個外設需要在運行時去初始化DeInit並且給定外設的低級資源需要被另一個外設釋放和使用時,在HAL_PPP_MspDeInit()和HAL_PPP_MspInit()中定義,但是HAL_MspInit()和HAL_MspDeInit()可以保持不變;
HAL IO操作
具有內部數據處理(如發送、接收、寫入和讀取)的HAL外設功能通常都具有三種數據處理模式:
輪詢模式
中斷模式
DMA模式
輪詢模式:當阻塞模式下的數據處理完成后,HAL返回進程狀態;
當返回HAL_OK狀態時,認為操作已經完成,否則返回錯誤信息;也可以通過HAL_PPP_GetState()來獲取更多的狀態;
數據在循環內部處理,超時(ms毫秒)以防止進程掛起;
具體實現如下:
判斷接收的數據是否有誤;
判斷數據讀取是否超時;
所有的數據處理無誤后返回完成標志位;
HAL_StatusTypeDef HAL_PPP_Transmit ( PPP_HandleTypeDef * phandle, uint8_t pData, int16_tSize,uint32_tTimeout) { if((pData == NULL ) || (Size == 0)) { return HAL_ERROR; } (…) while (data processing is running) { if( timeout reached ) { return HAL_TIMEOUT; } } (…) return HAL_OK; }
中斷模式:
HAL在開始數據處理並啟用適當的中斷來返回過程狀態,操作的結果由聲明為弱函數的回調指示,回調也可以由用戶定義以實時通知過程完成,也可以使用HAL_PPP_GetState()獲取進程狀態;
在中斷模式中有四個函數:
HAL_PPP_Process_IT():啟動IT過程;
HAL_PPP_IRQHandler():全局的外設中斷;
__weak HAL_PPP_ProcessCpltCallback ():用於在處理流程完成后的回調函數;
__weak HAL_PPP_ProcessErrorCallback():用於在出現錯誤信息后的回調函數;
要在中斷模式下使用進程,需要在用戶文件中調用HAL_PPP_Process_IT(),需要在stm32f0xx_it.c中調用HAL_PPP_IRQHandler()中斷函數;
HAL_PPP_ProcessCpltCallback()初始被聲明為弱函數,用戶可以在應用層重新定義該函數;
具體應用舉例:
main.c file: UART_HandleTypeDef UartHandle; int main(void) { /* Set User Parameters */ UartHandle.Init.BaudRate = 9600; UartHandle.Init.WordLength = UART_DATABITS_8; UartHandle.Init.StopBits = UART_STOPBITS_1; UartHandle.Init.Parity = UART_PARITY_NONE; UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; UartHandle.Init.Mode = UART_MODE_TX_RX; UartHandle.Init.Instance = USART1; HAL_UART_Init(&UartHandle); HAL_UART_SendIT(&UartHandle, TxBuffer, sizeof(TxBuffer)); while (1); } void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { } void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { } stm32f0xx_it.cfile: extern UART_HandleTypeDef UartHandle; void USART1_IRQHandler(void) { HAL_UART_IRQHandler(&UartHandle); }
DMA模式:
在DMA模式下,HAL功能在通過DMA開始數據處理之后以及啟用適當的DMA中斷后返回過程狀態;操作的結束由聲明為弱函數的回調指示,並且可以由用戶定制以實時通知過程完成;用戶還可以通過HAL_PPP_GetState()函數獲取進程狀態;
對於DMA模式,在驅動程序中聲明了三個函數:
HAL_PPP_Process_DMA():開啟DMA
HAL_PPP_DMA_IRQHandler():PPP外設中斷函數定義
__weak HAL_PPP_ProcessCpltCallback():數據處理完成后調用的回調函數
__weak HAL_PPP_ErrorCpltCallback():發生錯誤了調用的回調函數
要使用DMA模式,需要在用戶文件中調用HAL_PPP_Process_DMA(),並將HAL_PPP_DMA_IRQHandler()中斷處理函數放在stm32f0xx_it.c中;
其初始化在HAL_PPP_MspInit()回調函數中實現,用戶還應將DMA句柄Handler與PPP外設句柄Handler相關聯;
要在外設中添加DMA句柄Handler如下:
typedef struct { PPP_TypeDef *Instance; /* Register base address */ PPP_InitTypeDef Init; /* PPP communication parameters */ HAL_StateTypeDef State; /* PPP communication state */ (…) DMA_HandleTypeDef *hdma; /* associated DMA handle */ } PPP_HandleTypeDef;
DMA初始化流程如下:(以USART為例)
int main(void) { /* Set User Parameters */ UartHandle.Init.BaudRate = 9600; UartHandle.Init.WordLength = UART_DATABITS_8; UartHandle.Init.StopBits = UART_STOPBITS_1; UartHandle.Init.Parity = UART_PARITY_NONE; UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; UartHandle.Init.Mode = UART_MODE_TX_RX; UartHandle.Init.Instance = UART1; HAL_UART_Init(&UartHandle); (..) } void HAL_USART_MspInit (UART_HandleTypeDef * huart) { static DMA_HandleTypeDef hdma_tx; static DMA_HandleTypeDef hdma_rx; (…) __HAL_LINKDMA(UartHandle, DMA_Handle_tx, hdma_tx); __HAL_LINKDMA(UartHandle, DMA_Handle_rx, hdma_rx); (…) }
HAL_PPP_ProcessCpltCallback()函數在驅動程序中聲明為弱函數,這意味着用戶可以在應用程序代碼中再次聲明它,而不用修改驅動程序中的功能;
在main.c中:
UART_HandleTypeDef UartHandle; int main(void) { /* Set User Paramaters */ UartHandle.Init.BaudRate = 9600; UartHandle.Init.WordLength = UART_DATABITS_8; UartHandle.Init.StopBits = UART_STOPBITS_1; UartHandle.Init.Parity = UART_PARITY_NONE; UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; UartHandle.Init.Mode = UART_MODE_TX_RX; UartHandle.Init.Instance = USART1; HAL_UART_Init(&UartHandle); HAL_UART_Send_DMA(&UartHandle, TxBuffer, sizeof(TxBuffer)); while (1); } void HAL_UART_TxCpltCallback(UART_HandleTypeDef *phuart) { } void HAL_UART_TxErrorCallback(UART_HandleTypeDef *phuart) { }
在stm32f0xx_it.c中:
extern UART_HandleTypeDef UartHandle; void DMAx_IRQHandler(void) { HAL_DMA_IRQHandler(&UartHandle.DMA_Handle_tx); }
HAL_USART_TxCpltCallback()和HAL_USART_ErrorCallback()應該通過使用以下語句在HAL_PPP_Process_DMA()函數中鏈接到DMA傳輸完成回調和DMA傳輸錯誤回調:
HAL_PPP_Process_DMA (PPP_HandleTypeDef *hppp, Params….) { (…) hppp->DMA_Handle->XferCpltCallback = HAL_UART_TxCpltCallback ; hppp->DMA_Handle->XferErrorCallback = HAL_UART_ErrorCallback ; (…) }
超時和錯誤管理:
超時管理:
超時通常用於以輪詢模式運行的API;它定義了阻塞過程應該等待直到返回錯誤的延遲;下面提供了一個示例:
HAL_StatusTypeDef HAL_DMA_PollForTransfer(DMA_HandleTypeDef *hdma, uint32_t CompleteLevel, uint32_t Timeout)
超時定義的值大小:
PS:HAL_MAX_DELAY is defined in the stm32fxxx_hal_def.h as 0xFFFFFFFF.
在某些情況下,固定超時用於系統外圍設備或內部HAL驅動程序進程,在這些情況下,超時具有相同的含義並以相同的方式使用;
獲取當前計數值並賦予超時上限;
在處理程序完成后再次獲取計數值,判斷是否超時,返回超時狀態;
#define LOCAL_PROCESS_TIMEOUT 100 HAL_StatusTypeDef HAL_PPP_Process(PPP_HandleTypeDef) { (…) timeout = HAL_GetTick() + LOCAL_PROCESS_TIMEOUT; (…) while(ProcessOngoing) { (…) if(HAL_GetTick() >= timeout) { /* Process unlocked */ __HAL_UNLOCK(hppp); hppp->State= HAL_PPP_STATE_TIMEOUT; return HAL_PPP_STATE_TIMEOUT; } } (…) }
以下示例顯式輪詢函數中如何使用超時:
HAL_PPP_StateTypeDef HAL_PPP_Poll (PPP_HandleTypeDef *hppp, uint32_t Timeout) { (…) timeout = HAL_GetTick() + Timeout; (…) while(ProcessOngoing) { (…) if(Timeout != HAL_MAX_DELAY) { if(HAL_GetTick() >= timeout) { /* Process unlocked */ __HAL_UNLOCK(hppp); hppp->State= HAL_PPP_STATE_TIMEOUT; return hppp->State; } } (…) }
錯誤管理:
通過檢查以下的參數來確定錯誤;
有效參數:使用的參數要是有效的,並且已經定義的,否則系統很可能陷入未定義狀態,在使用之前檢查這些參數;
HAL_StatusTypeDef HAL_PPP_Process(PPP_HandleTypeDef* hppp, uint32_t *pdata, uint32 Size) { if ((pData == NULL ) || (Size == 0)) { return HAL_ERROR; } }
有效句柄:PPP外圍句柄是最重要的參數,因為它保留了PPP驅動程序的重要參數;始終在HAL_PPP_Init()函數的開頭檢查它;
HAL_StatusTypeDef HAL_PPP_Init(PPP_HandleTypeDef* hppp) { if (hppp == NULL) //the handle should be already allocated { return HAL_ERROR; } }
超時錯誤:發生超時錯誤時使用以下語句:while(正在進行);
{ timeout = HAL_GetTick() + Timeout; while (data processing is running) { if(timeout) { return HAL_TIMEOUT;} }
當外設發生錯誤時,HAL_PPP_Process ()返回一個HAL_ERROR錯誤狀態,HAL PPP外設驅動程序實現HAL_PPP_GetError ()允許檢索錯誤;
HAL_PPP_ErrorTypeDef HAL_PPP_GetError (PPP_HandleTypeDef *hppp);
在所有的外設句柄中,定義了HAL_PPP_ErrorTypeDef來存儲最后一個錯誤代碼;
typedef struct { PPP_TypeDef * Instance; /* PPP registers base address */ PPP_InitTypeDef Init; /* PPP initialization parameters */ HAL_LockTypeDef Lock; /* PPP locking object */ __IO HAL_PPP_StateTypeDef State; /* PPP state */ __IO HAL_PPP_ErrorTypeDef ErrorCode; /* PPP Error code */ (…) /* PPP specific parameters */ } PPP_HandleTypeDef;
在返回錯誤之前,始終更新錯誤狀態和外圍設備的全局狀態;
PPP->State = HAL_PPP_READY; /* Set the peripheral ready */ PP->ErrorCode = HAL_ERRORCODE ; /* Set the error code */ _HAL_UNLOCK(PPP) ; /* Unlock the PPP resources */ return HAL_ERROR; /*return with HAL error */
HAL_PPP_GetError()必須在錯誤回調中的中斷模式下使用:
void HAL_PPP_ProcessCpltCallback(PPP_HandleTypeDef *hspi) { ErrorCode = HAL_PPP_GetError (hppp); /* retreive error code */ }
運行時間檢查:
HAL通過檢查所有HAL驅動程序函數的輸入值來實現運行時故障檢測;運行時檢查是通過使用assert_parammacro實現的;該宏用於具有輸入參數的所有HAL驅動程序函數;它允許驗證輸入值是否在參數允許值范圍內;
要啟用運行時檢查,請使用assert_parammacro,並在stm32f0xx_hal_conf.hfile中取消注釋定義USE_FULL_ASSERT;
void HAL_UART_Init(UART_HandleTypeDef *huart) { (..) /* Check the parameters */ assert_param(IS_UART_INSTANCE(huart->Instance)); assert_param(IS_UART_BAUDRATE(huart->Init.BaudRate)); assert_param(IS_UART_WORD_LENGTH(huart->Init.WordLength)); assert_param(IS_UART_STOPBITS(huart->Init.StopBits)); assert_param(IS_UART_PARITY(huart->Init.Parity)); assert_param(IS_UART_MODE(huart->Init.Mode)); assert_param(IS_UART_HARDWARE_FLOW_CONTROL(huart->Init.HwFlowCtl)); (..) /** @defgroup UART_Word_Length * @{ */ #define UART_WORDLENGTH_8B ((uint32_t)0x00000000) #define UART_WORDLENGTH_9B ((uint32_t)USART_CR1_M) #define IS_UART_WORD_LENGTH(LENGTH) (((LENGTH) == UART_WORDLENGTH_8B) || \ ((LENGTH) == UART_WORDLENGTH_9B))
如果傳遞給assert_param宏的表達式為false,則調用theassert_failed函數並返回源文件的名稱和失敗的調用的源行號;如果表達式為true,則不返回任何值;assert_parammacro在stm32f0xx_hal_conf.h中實現:
/* Exported macro ------------------------------------------------------------*/ #ifdef USE_FULL_ASSERT /** * @brief The assert_param macro is used for function's parameters check. * @param expr: If expr is false, it calls assert_failed function * which reports the name of the source file and the source * line number of the call that failed. * If expr is true, it returns no value. * @retval None */ #define assert_param(expr) ((expr)?(void)0:assert_failed((uint8_t *)__FILE__, __LINE__)) /* Exported functions --------------------------------------*/ void assert_failed(uint8_t* file, uint32_t line); #else #define assert_param(expr)((void)0) #endif /* USE_FULL_ASSERT */
assert_failed函數在main.c文件或任何其他用戶C文件中實現:
#ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t* file, uint32_t line) { /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* Infinite loop */ while (1) { } }
由於引入了開銷運行時檢查,因此建議在應用程序代碼開發和調試期間使用它,並將其從最終應用程序中刪除以改進代碼大小和速度;