原本我寫上位機的,最近工作需要寫了一下下位機的代碼。
使用的是STM32F412RETx的芯片,板子是電子工程師做的
使用STM32CubeMX V5.2.1、Keil uVision5做開發,使用HAL庫
使用過程中多次出現串口接收的問題,最後都解決了,這裏記錄一下
串口的HAL有3類API
// 同步堵塞收發
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
// 異步中斷傳輸
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
// 異步DMA傳輸(Direct Memory Access,DMA),不經過CPU,外設直接讀寫內存
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
我使用的是異步中斷HAL_UART_Receive_IT的進行串口數據接收,這個需要用到
接收完成中斷回調
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
在中斷回調中只進行數據的解析,不做過多其他處理,避免中斷時間過長,中斷中也不要使用延時函數,儘量不在中斷中進行IO輸出操作。
第一種串口接收問題:現象爲開機後串口可以接收數據,一會兒後就一直沒有數據了。
查看錯誤爲:HAL_UART_Receive_IT返回HAL_BUSY
先說一下我的使用方法:使用HAL_UART_Receive_IT接收數據,需要在每次接收完成後,再次調用HAL_UART_Receive_IT函數,一般是在HAL_UART_RxCpltCallback函數的末尾再次HAL_UART_Receive_IT。
HAL_UART_Receive_IT有個狀態返回值,可以自己看一下這個函數的實現,代碼也就幾十行
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
/* Check that a Rx process is not already ongoing */
if (huart->RxState == HAL_UART_STATE_READY)
{
if ((pData == NULL) || (Size == 0U))
{
return HAL_ERROR;
}
/* Process Locked */
__HAL_LOCK(huart);
huart->pRxBuffPtr = pData;
huart->RxXferSize = Size;
huart->RxXferCount = Size;
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->RxState = HAL_UART_STATE_BUSY_RX;
/* Process Unlocked */
__HAL_UNLOCK(huart);
/* Enable the UART Parity Error Interrupt */
__HAL_UART_ENABLE_IT(huart, UART_IT_PE);
/* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
__HAL_UART_ENABLE_IT(huart, UART_IT_ERR);
/* Enable the UART Data Register not empty Interrupt */
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
一般這個函數失敗都是返回HAL_BUSY居多,這裏有兩種情況會返回HAL_BUSY
1. huart->RxState != HAL_UART_STATE_READY,串口沒準備好或者串口正在接收數據中,如果時正在接收數據,意味着其他地方已經調用過HAL_UART_Receive_IT,直接忽略等待接收完成就好,而我知道只有在HAL_UART_RxCpltCallback有再次調用,所以其他地方調用不存在,而且失敗時查看過狀態huart->RxState 是等於 HAL_UART_STATE_READY
2. __HAL_LOCK(huart);加鎖失敗,鎖被佔用。
看一下__HAL_LOCK(__HANDLE__)的定義
#define __HAL_LOCK(__HANDLE__) \
do{ \
if((__HANDLE__)->Lock == HAL_LOCKED) \
{ \
return HAL_BUSY; \
} \
else \
{ \
(__HANDLE__)->Lock = HAL_LOCKED; \
} \
}while (0U)
這裏就判斷了一次,如果鎖被佔用,直接返回,從我這邊測試可以看到,一般都是因爲鎖被暫用然後返回了HAL_BUSY,那麼就要看一下哪裏佔用了鎖。
接收數據使用的是異步中斷的函數HAL_UART_Receive_IT(),然而發送數據我使用的是同步堵塞的函數HAL_UART_Transmit(),可以自己看一下HAL_UART_Transmit()的實現,這裏不列代碼了,HAL_UART_Transmit()函數內從開始發送開始加鎖,等待全部數據發送完成後才解鎖,所以佔用鎖的時間是比較長的。而數據發送也比較多,所以基本判定是發送造成的加鎖。
有兩種方法可以解決發送造成的加鎖問題:
1. 使用異步函數發送,這樣佔用鎖時間就短,不過也有概率鎖佔用,可以選擇重試幾次可能就可以了
2. huart->RxState=HAL_UART_STATE_READY,且保證不存在多處代碼同時發送,那麼可以選擇暴力解鎖,我使用這種方案
下面確認一下被加鎖的代碼
加鎖一般是因爲要操作一下公用的數據。
下面我們分析一下串口UART_HandleTypeDef結構體
typedef struct __UART_HandleTypeDef
{
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 */
__IO uint16_t TxXferCount; /*!< UART Tx Transfer Counter */
uint8_t *pRxBuffPtr; /*!< Pointer to UART Rx transfer Buffer */
uint16_t RxXferSize; /*!< UART Rx Transfer size */
__IO 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 gState; /*!< UART state information related to global Handle management
and also related to Tx operations.
This parameter can be a value of @ref HAL_UART_StateTypeDef */
__IO HAL_UART_StateTypeDef RxState; /*!< UART state information related to Rx operations.
This parameter can be a value of @ref HAL_UART_StateTypeDef */
__IO uint32_t ErrorCode; /*!< UART Error code */
} UART_HandleTypeDef;
結合HAL_UART_Receive_IT()、HAL_UART_Transmit()兩個函數,發現其實收發數據時使用的成員變量基本是分開的,
發送使用pTxBuffPtr、TxXferSize、TxXferCount、hdmatx、gState
接收使用pRxBuffPtr、RxXferSize、RxXferCount、hdmarx、RxState
共用部分:ErrorCode
gState也可能是公用的,不過暫時在函數HAL_UART_Receive_IT中沒發現有使用gState
檢查自己的代碼,發現確實是HAL_UART_Transmit()造成的鎖,既然基本上主要成員沒有共用,那就暴力解鎖
在判斷接收狀態爲HAL_UART_STATE_READY時,且被加鎖,直接暴力解鎖
if(HAL_UART_STATE_READY == huart->RxState && HAL_LOCKED == huart->Lock)
{
__HAL_UNLOCK(huart); // 暴力解鎖
}
至此由於加鎖問題而造成的串口突然接收不到數據的問題暫時解決了,很暴力的方式
第一種串口接收問題:同樣是上電後串口可以接收數據,接收一段時間後沒數據了,
而且HAL_UART_Receive_IT()函數返回的是HAL_OK
檢查了HAL_UART_GetError(),獲取到錯誤HAL_UART_ERROR_ORE,應該是串口溢出的意思。
只是爲什麼溢出後就直接停止接收了?就算是丟包也不要給我直接停止工作了呀!!!刪庫後跑路了???
解決方法如下
既然知道報錯,那就考慮清楚錯誤標誌,特地也看了一下函數HAL_UART_IRQHandler(UART_HandleTypeDef *huart);內,的確是有錯誤標誌的時候,不會調用接收完成回調
看了多篇文章,試了幾種方法後終於找到一個清除錯誤標誌有效的
使用__HAL_UART_CLEAR_OREFLAG(__HANDLE__)可以清空錯誤標誌
使用__HAL_UART_CLEAR_FLAG(__HANDLE__, __FLAG__)是沒用的
最終完整版的修改如下:
HAL_StatusTypeDef K_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
HAL_StatusTypeDef status = HAL_OK;
for(int32_t i = 1; i < 1000; ++i)
{
#if 1
// 清除錯誤
uint32_t isrflags = READ_REG(huart->Instance->SR);
if((__HAL_UART_GET_FLAG(huart, UART_FLAG_PE))!=RESET)
{
__HAL_UART_CLEAR_PEFLAG(huart);
}
if((__HAL_UART_GET_FLAG(huart, UART_FLAG_FE))!=RESET)
{
__HAL_UART_CLEAR_FEFLAG(huart);
}
if((__HAL_UART_GET_FLAG(huart, UART_FLAG_NE))!=RESET)
{
__HAL_UART_CLEAR_NEFLAG(huart);
}
if((__HAL_UART_GET_FLAG(huart, UART_FLAG_ORE))!=RESET)
{
//READ_REG(huart->Instance->CR1);//ORE清標誌,第二步讀CR
//__HAL_UART_CLEAR_FLAG(huart, UART_FLAG_ORE);
__HAL_UART_CLEAR_OREFLAG(huart);
}
if(HAL_UART_ERROR_NONE != huart->ErrorCode)
{
huart->ErrorCode = HAL_UART_ERROR_NONE;
}
#endif
// 請求接收
status = HAL_UART_Receive_IT(huart, pData, Size);
if(HAL_OK == status)
{ // 成功
return status;
}
else if(HAL_BUSY == status)
{
//printf("HAL_UART_Receive_IT failed. status:%d, RxState:0X%x, Lock:%d\r\n", status, huart->RxState, huart->Lock);
if(HAL_UART_STATE_READY == huart->RxState && HAL_LOCKED == huart->Lock && i % 500 == 0)
{ // 接收是已經ready的,只是修改數據的鎖被lock了,應該是堵塞發送那邊一直在lock中, 稍微重試多次後(即等待一下解鎖)就直接暴力解鎖
__HAL_UNLOCK(huart);
continue;
}
}
else if(HAL_ERROR == status)
{ // 直接返回錯誤
//printf("HAL_UART_Receive_IT HAL_ERROR\r\n");
return status;
}
else if(HAL_TIMEOUT == status)
{ // HAL_UART_Receive_IT 不存在timeout返回
//printf("HAL_UART_Receive_IT HAL_TIMEOUT\r\n");
}
}
if(HAL_OK != status)
{
//printf("HAL_UART_Receive_IT Err status:%d\r\n", status);
}
// 重試了N次
return status;
}
發送也稍微處理一下
HAL_StatusTypeDef K_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
HAL_StatusTypeDef status = HAL_OK;
for(int32_t i = 1; i < 100000; ++i)
{
status = HAL_UART_Transmit(huart, pData, Size, Timeout);
if(HAL_OK == status)
{ // 成功
return status;
}
else if(HAL_BUSY == status)
{
//printf("HAL_UART_Transmit failed. status:%d, gState:0X%x, Lock:%d\r\n", status, huart->gState, huart->Lock);
if(HAL_UART_STATE_READY == huart->gState && HAL_LOCKED == huart->Lock && i % 50 == 0)
{ // 發送狀態是已經ready的,只是修改數據的鎖被lock了,應該是其他發送或者接收鎖了, 重試幾次後還是一樣則暴力解鎖
__HAL_UNLOCK(huart);
continue;
}
}
else if(HAL_ERROR == status)
{ // 直接返回錯誤
return status;
}
else if(HAL_TIMEOUT == status)
{ // 超時則增加時間重試
Timeout += 200;
continue;
}
}
// 重試了N次
return status;
}
這樣處理後,接收是可以了,雖然有丟包,至少能一直接收,之後發現之所以那麼不穩定,因爲STM32F412RETx需要在輸入電源增加一個電容進行濾波,增減濾波後就正常了,至少沒發現丟包,不加清除標誌也沒問題
參考了很多篇文章,僅列舉有用的:
https://blog.csdn.net/mickey35/article/details/78529637
http://bbs.21ic.com/icview-2514912-1-1.html?ordertype=1