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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM