STM32 之 HAL庫(韌體庫) _
1 STM32的三種開發方式
通常新手在入門STM32的時候,首先都要先選擇一種要用的開發方式,不同的開發方式會導致你程式設計的架構是完全不一樣的。一般大多數都會選用標準庫和HAL庫,而極少部分人會通過直接配置暫存器進行開發。網上關於標準庫、HAL庫的描述相信是數不勝數。可是一個對於很多剛入門的朋友還是沒法很直觀的去真正瞭解這些不同開發發方式彼此之間的區別,所以筆者想以一種非常直白的方式,用自己的理解去將這些東西表述出來,如果有描述的不對的地方或者是不同意見的也可以大家提出。
一、直接配置暫存器
不少先學了51的朋友可能會知道,會有一小部分人或是教程是通過組合語言直接操作暫存器實現功能的,這種方法到了STM32就變得不太容易行得通了,因為STM32的暫存器數量是51微控制器的十數倍,如此多的暫存器根本無法全部記憶,開發時需要經常的翻查晶片的資料手冊,此時直接操作暫存器就變得非常的費力了。但還是會有很小一部分人,喜歡去直接操作暫存器,因為這樣更接近原理,知其然也知其所以然。
二、標準庫
上面也提到了,STM32有非常多的暫存器,而導致了開發困難,所以為此ST公司就為每款晶片都編寫了一份庫檔案,也就是工程檔案裡stm32F1xx…之類的。在這些 .c .h檔案中,包括一些常用量的巨集定義,把一些外設也通過結構體變數封裝起來,如GPIO口時鐘等。所以我們只需要配置結構體變數成員就可以修改外設的配置暫存器,從而選擇不同的功能。也是目前最多人使用的方式,也是學習STM32接觸最多的一種開發方式,我也就不多闡述了。
三、HAL庫
HAL庫是ST公司目前主力推的開發方式,全稱就是Hardware Abstraction Layer(抽象印象層)。庫如其名,很抽象,一眼看上去不太容易知道他的作用是什麼。它的出現比標準庫要晚,但其實和標準庫一樣,都是為了節省程式開發的時期,而且HAL庫尤其的有效,如果說標準庫把實現功能需要配置的暫存器集成了,那麼HAL庫的一些函式甚至可以做到某些特定功能的整合。也就是說,同樣的功能,標準庫可能要用幾句話,HAL庫只需用一句話就夠了。並且HAL庫也很好的解決了程式移植的問題,不同型號的stm32晶片它的標準庫是不一樣的,例如在F4上開發的程式移植到F3上是不能通用的,而使用HAL庫,只要使用的是相通的外設,程式基本可以完全複製貼上,注意是相通外設,意思也就是不能無中生有,例如F7比F3要多幾個定時器,不能明明沒有這個定時器卻非要配置,但其實這種情況不多,絕大多數都可以直接複製貼上。是而且使用ST公司研發的STMcube軟體,可以通過圖形化的配置功能,直接生成整個使用HAL庫的工程檔案,可以說是方便至極,但是方便的同時也造成了它執行效率的低下,在各種論壇帖子真的是被吐槽的數不勝數。
2 HAL庫韌體庫安裝與 使用者手冊
1.首先設定讓Cube可以自動聯網下載相關韌體庫
選擇updater Settings
設定如下
2.根據晶片選擇所需韌體
版本是向下相容的,可以直接選擇最新版。但如果覺得最新版太大,可以閱讀下面的Main Changes.能夠支援你目前的晶片就好。
選好了,點選Install Now就行,過程可能有點長。建議直接官網下載到本地,再安裝
檔案會被下載到如下位置,建議更改此目錄,不要選在C盤!!!
查詢幫助手冊
3.尋找使用者幫助手冊
進入韌體所在資料夾,裡面包含很多內容。
比如說 官方提供的開發板程式
每個型號下面都有對應功能的實現
作者:My木子銘
來源:CSDN
原文:https://blog.csdn.net/sinat_37853238/article/details/85141168
版權宣告:本文為博主原創文章,轉載請附上博文連結!
3 STM32 HAL庫與標準庫的區別_淺談控制代碼、MSP函式、Callback函式
3.1.控制代碼
控制代碼(handle),有多種意義,其中第一種是指程式設計,第二種是指Windows程式設計。現在大部分都是指程式設計/程式開發這類。
- 第一種解釋:控制代碼是一種特殊的智慧指標 。當一個應用程式要引用其他系統(如資料庫、作業系統)所管理的記憶體塊或物件時,就要使用控制代碼。
- 第二種解釋:整個Windows程式設計的基礎。一個控制代碼是指使用的一個唯一的整數值,即一個4位元組(64位程式中為8位元組)長的數值,來標識應用程式中的不同物件和同類中的不同的例項,諸如,一個視窗,按鈕,圖示,滾動條,輸出裝置,控制元件或者檔案等。應用程式能夠通過控制代碼訪問相應的物件的資訊,但是控制代碼不是指標,程式不能利用控制代碼來直接閱讀檔案中的資訊。如果控制代碼不在I/O檔案中,它是毫無用處的。 控制代碼是Windows用來標誌應用程式中建立的或是使用的唯一整數,Windows大量使用了控制代碼來標識物件。
STM32的標準庫中,控制代碼是一種特殊的指標,通常指向結構體!
在STM32的標準庫中,假設我們要初始化一個外設(這裡以USART為例),我們首先要初始化他們的各個暫存器。在標準庫中,這些操作都是利用韌體庫結構體變數+韌體庫Init函式實現的:
USART_InitTypeDef USART_InitStructure; | |
USART_InitStructure.USART_BaudRate = bound;//串列埠波特率 | |
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字長為8位資料格式 | |
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一個停止位 | |
USART_InitStructure.USART_Parity = USART_Parity_No;//無奇偶校驗位 | |
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//無硬體資料流控制 | |
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收發模式 | |
USART_Init(USART3, &USART_InitStructure); //初始化串列埠1 |
可以看到,要初始化一個串列埠,需要:
- 1、對六個位置進行賦值,
- 2、然後引用Init函式,
USART_InitStructure並不是一個全域性結構體變數,而是隻在函式內部的區域性變數,初始化完成之後,USART_InitStructure就失去了作用。
而在HAL庫中,同樣是USART初始化結構體變數,我們要定義為全域性變數。
UART_HandleTypeDef UART1_Handler; |
右鍵檢視結構體成員
typedef struct | |
{ | |
USART_TypeDef *Instance; /*!< UART registers base address */ | |
UART_InitTypeDef Init; /*!< UART communication parameters */ | |
uint8_t *pTxBuffPtr; /*!< Pointer to UART Tx transfer Buffer */ | |
uint16_t TxXferSize; /*!< UART Tx Transfer size */ | |
uint16_t TxXferCount; /*!< UART Tx Transfer Counter */ | |
uint8_t *pRxBuffPtr; /*!< Pointer to UART Rx transfer Buffer */ | |
uint16_t RxXferSize; /*!< UART Rx Transfer size */ | |
uint16_t RxXferCount; /*!< UART Rx Transfer Counter */ | |
DMA_HandleTypeDef *hdmatx; /*!< UART Tx DMA Handle parameters */ | |
DMA_HandleTypeDef *hdmarx; /*!< UART Rx DMA Handle parameters */ | |
HAL_LockTypeDef Lock; /*!< Locking object */ | |
__IO HAL_UART_StateTypeDef State; /*!< UART communication state */ | |
__IO uint32_t ErrorCode; /*!< UART Error code */ | |
}UART_HandleTypeDef; |
我們發現,與標準庫不同的是,該成員不僅:
- 1、包含了之前標準庫就有的六個成員(波特率,資料格式等),
- 2、還包含過取樣、(傳送或接收的)資料快取、資料指標、串列埠 DMA 相關的變數、各種標誌位等等要在整個專案流程中都要設定的各個成員。
該 UART1_Handler就被稱為串列埠的控制代碼,它被貫穿整個USART收發的流程,比如開啟中斷:
HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE); |
比如後面要講到的MSP與Callback回撥函式:
void HAL_UART_MspInit(UART_HandleTypeDef *huart); | |
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); |
在這些函式中,只需要呼叫初始化時定義的控制代碼UART1_Handler就好。
3.2.MSP函式
MSP: MCU Specific Package 微控制器的具體方案
MSP是指和MCU相關的初始化,引用一下正點原子的解釋,個人覺得說的很明白:
我們要初始化一個串列埠,首先要設定和 MCU 無關的東西,例如波特率,奇偶校驗,停止
位等,這些引數設定和 MCU 沒有任何關係,可以使用 STM32F1,也可以是 STM32F2/F3/F4/F7
上的串列埠。而一個串列埠裝置它需要一個 MCU 來承載,例如用 STM32F4 來做承載,PA9 做為發
送,PA10 做為接收,MSP 就是要初始化 STM32F4 的 PA9,PA10,配置這兩個引腳。所以 HAL
驅動方式的初始化流程就是:
HAL_USART_Init()—>HAL_USART_MspInit() ,先初始化與 MCU無關的串列埠協議,再初始化與 MCU 相關的串列埠引腳。
在 STM32 的 HAL 驅動中HAL_PPP_MspInit()作為回撥,被 HAL_PPP_Init()函式所呼叫。當我們需要移植程式到 STM32F1平臺的時候,我們只需要修改 HAL_PPP_MspInit 函式內容而不需要修改 HAL_PPP_Init 入口引數內容。
在HAL庫中,幾乎每初始化一個外設就需要設定該外設與微控制器之間的聯絡,比如IO口,是否複用等等,可見,HAL庫相對於標準庫多了MSP函式之後,移植性非常強,但與此同時卻增加了程式碼量和程式碼的巢狀層級。可以說各有利弊。
同樣,MSP函式又可以配合控制代碼,達到非常強的移植性:
void HAL_UART_MspInit(UART_HandleTypeDef *huart); |
入口引數僅僅需要一個串列埠控制代碼,這樣有能看出控制代碼的方便。
3.3.Callback函式
類似於MSP函式,個人認為Callback函式主要幫助使用者應用層的程式碼編寫。
還是以USART為例,在標準庫中,串列埠中斷了以後,我們要先在中斷中判斷是否是接收中斷,然後讀出資料,順便清除中斷標誌位,然後再是對資料的處理,這樣如果我們在一箇中斷函式中寫這麼多程式碼,就會顯得很混亂:
void USART3_IRQHandler(void) //串列埠1中斷服務程式 | |
{ | |
u8 Res; | |
if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) //接收中斷(接收到的資料必須是0x0d 0x0a結尾) | |
{ | |
Res =USART_ReceiveData(USART3); //讀取接收到的資料 | |
/*資料處理區*/ | |
} | |
} | |
} |
而在HAL庫中,進入串列埠中斷後,直接由HAL庫中斷函式進行託管:
void USART1_IRQHandler(void) | |
{ | |
HAL_UART_IRQHandler(&UART1_Handler); //呼叫HAL庫中斷處理公用函式 | |
/***************省略無關程式碼****************/ | |
} |
HAL_UART_IRQHandler這個函式完成了判斷是哪個中斷(接收?傳送?或者其他?),然後讀出資料,儲存至快取區,順便清除中斷標誌位等等操作。
比如我提前設定了,串列埠每接收五個位元組,我就要對這五個位元組進行處理。
在一開始我定義了一個串列埠接收快取區:
/*HAL庫使用的串列埠接收緩衝,處理邏輯由HAL庫控制,接收完這個陣列就會呼叫HAL_UART_RxCpltCallback進行處理這個陣列*/ | |
/*RXBUFFERSIZE=5*/ | |
u8 aRxBuffer[RXBUFFERSIZE]; |
在初始化中,我在控制代碼裡設定好了快取區的地址,快取大小(五個位元組)
/*該程式碼在HAL_UART_Receive_IT函式中,初始化時會引用*/ | |
huart->pRxBuffPtr = pData;//aRxBuffer | |
huart->RxXferSize = Size;//RXBUFFERSIZE | |
huart->RxXferCount = Size;//RXBUFFERSIZE |
則在接收資料中,每接收完五個位元組,HAL_UART_IRQHandler才會執行一次Callback函式:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); |
在這個Callback回撥函式中,我們只需要對這接收到的五個位元組(儲存在aRxBuffer[]中)進行處理就好了,完全不用再去手動清除標誌位等操作。
所以說Callback函式是一個應用層程式碼的函式,我們在一開始只設置控制代碼裡面的各個引數,然後就等著HAL庫把自己安排好的程式碼送到手中就可以了~
綜上,就是HAL庫的三個與標準庫不同的地方之個人見解。
個人覺得從這三個小點就可以看出HAL庫的可移植性之強大,並且使用者可以完全不去理會底層各個暫存器的操作,程式碼也更有邏輯性。但與此帶來的是複雜的程式碼量,極慢的編譯速度,略微低下的效率。看怎麼取捨了。
4 STM32 HAL庫結構
說到STM32的HAL庫,就不得不提STM32CubeMX,其作為一個視覺化的配置工具,對於開發者來說,確實大大節省了開發時間。STM32CubeMX就是以HAL庫為基礎的,且目前僅支援HAL庫及LL庫!首先看一下,官方給出的HAL庫的包含結構:
- 1、stm32f4xx.h主要包含STM32同系列晶片的不同具體型號的定義,是否使用HAL庫等的定義,接著,其會根據定義的晶片訊號包含具體的晶片型號的標頭檔案:
緊接著,其會包含stm32f4xx_hal.h。
- 2、stm32f4xx_hal.h:stm32f4xx_hal.c/h 主要實現HAL庫的初始化、系統滴答相關函式、及CPU的除錯模式配置
- 3、stm32f4xx_hal_conf.h :該檔案是一個使用者級別的配置檔案,用來實現對HAL庫的裁剪,其位於使用者檔案目錄,不要放在庫目錄中。
接下來對於HAL庫的原始碼檔案進行一下說明,HAL庫檔名均以stm32f4xx_hal開頭,後面加上_外設或者模組名(如:stm32f4xx_hal_adc.c):
- 4、庫檔案:
stm32f4xx_hal_ppp.c/.h // 主要的外設或者模組的驅動原始檔,包含了該外設的通用API
stm32f4xx_hal_ppp_ex.c/.h // 外圍裝置或模組驅動程式的擴充套件檔案。這組檔案中包含特定型號或者系列的晶片的特殊API。以及如果該特定的晶片內部有不同的實現方式,則該檔案中的特殊API將覆蓋_ppp中的通用API。
stm32f4xx_hal.c/.h // 此檔案用於HAL初始化,並且包含DBGMCU、重對映和基於systick的時間延遲等相關的API - 5、其他庫檔案
使用者級別檔案:
stm32f4xx_hal_msp_template.c // 只有.c沒有.h。它包含使用者應用程式中使用的外設的MSP初始化和反初始化(主程式和回撥函式)。使用者複製到自己目錄下使用模板。
stm32f4xx_hal_conf_template.h // 使用者級別的庫配置檔案模板。使用者複製到自己目錄下使用
system_stm32f4xx.c // 此檔案主要包含SystemInit()函式,該函式在剛復位及跳到main之前的啟動過程中被呼叫。 它不在啟動時配置系統時鐘(與標準庫相反)。 時鐘的配置在使用者檔案中使用HAL API來完成。
startup_stm32f4xx.s // 晶片啟動檔案,主要包含堆疊定義,終端向量表等
stm32f4xx_it.c/.h // 中斷處理函式的相關實現 - 6 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
- 處理各種回撥函式
相關知識如下:
- (1) 外設控制代碼定義
使用者程式碼的第一大部分:對於外設控制代碼的處理。 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 地址以及其初始化引數即可。
(2) 三種程式設計方式
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 __):檢查中斷源
(3)三大回調函式
在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)
- 處理完成回撥函式:HAL_PPP_ProcessCpltCallback*(Process指具體某種處理,如UART的Tx),例如:__weak void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)。當外設或者DMA工作完成後時,觸發中斷,該回調函式會在外設中斷處理函式或者DMA的中斷處理函式中被呼叫
- 錯誤處理回撥函式:HAL_PPP_ErrorCallback例如:__weak void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)**。當外設或者DMA出現錯誤時,觸發終端,該回調函式會在外設中斷處理函式或者DMA的中斷處理函式中被呼叫
絕大多數使用者程式碼均在以上三大回調函式中實現。
HAL庫結構中,在每次初始化前(尤其是在多次呼叫初始化前),先呼叫對應的反初始化(DeInit)函式是非常有必要的。
某些外設多次初始化時不呼叫返回會導致初始化失敗。完成回撥函式有多中,例如串列埠的完成回撥函式有HAL_UART_TxCpltCallback 和 HAL_UART_TxHalfCpltCallback等
(使用者程式碼的第三大部分:對於上面第二點和第三點的各種回撥函式的處理)
在實際使用中,發現HAL仍有不少問題,例如在使用USB時,其庫配置存在問題
5 HAL庫移植使用
基本步驟
1.複製stm32f2xx_hal_msp_template.c,參照該模板,依次實現用到的外設的HAL_PPP_MspInit()和 HAL_PPP_MspDeInit。
2.複製stm32f2xx_hal_conf_template.h,使用者可以在此檔案中自由裁剪,配置HAL庫。
3.在使用HAL庫時,必須先呼叫函式:HAL_StatusTypeDef HAL_Init(void)(該函式在stm32f2xx_hal.c中定義,也就意味著第一點中,必須首先實現HAL_MspInit(void)和HAL_MspDeInit(void))
4.HAL庫與STD庫不同,HAL庫使用RCC中的函式來配置系統時鐘,使用者需要單獨寫時鐘配置函式(STD庫預設在system_stm32f2xx.c中)
5.關於中斷,HAL提供了中斷處理函式,只需要呼叫HAL提供的中斷處理函式。使用者自己的程式碼,不建議先寫到中斷中,而應該寫到HAL提供的回撥函式中。
6.對於每一個外設,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庫編寫程式(針對某個外設)的基本結構(以串列埠為例)如下:
1, 配置外設控制代碼 例如,建立UartConfig.c,在其中定義串列埠控制代碼 UART_HandleTypeDef huart;,接著使用初始化控制代碼(HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef huart))
2,編寫Msp 例如,建立UartMsp.c,在其中實現void HAL_UART_MspInit(UART_HandleTypeDef huart) 和 void HAL_UART_MspDeInit(UART_HandleTypeDef* huart)
3,實現對應的回撥函式 例如,建立UartCallBack.c,在其中實現上文所說明的三大回調函式中的完成回撥函式和錯誤回撥函式
參考文件
ST - Description of STM32F4 HAL and LL drivers.pdf
ST - en.stm32_embedded_software_offering.pdf