1. 程式人生 > 實用技巧 >STM32 HAL庫與標準庫的區別_淺談控制代碼、MSP函式、Callback函式

STM32 HAL庫與標準庫的區別_淺談控制代碼、MSP函式、Callback函式

最近筆者開始學習STM32的HAL庫,由於以前一直用標準庫進行開發,於是發現了HAL庫幾點好玩的地方,在此分享。

1.控制代碼
在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

可以看到,要初始化一個串列埠,需要對六個位置進行賦值,然後引用Init函式,並且USART_InitStructure並不是一個全域性結構體變數,而是隻在函式內部的區域性變數,初始化完成之後,USART_InitStructure就失去了作用。

而在HAL庫中,同樣是USART初始化結構體變數,我們要定義為全域性變數。

UART_HandleTypeDef UART1_Handler;
  • 1

右鍵檢視結構體成員

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;

我們發現,與標準庫不同的是,該成員不僅包含了之前標準庫就有的六個成員(波特率,資料格式等),還包含過取樣、(傳送或接收的)資料快取、資料指標、串列埠 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就好。

2.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.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庫的可移植性之強大,並且使用者可以完全不去理會底層各個暫存器的操作,程式碼也更有邏輯性。但與此帶來的是複雜的程式碼量,極慢的編譯速度,略微低下的效率。看怎麼取捨了。

轉自:https://blog.csdn.net/weixin_43186792/article/details/88759321?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-3.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-3.control