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