写在前面
在工作中,部分产品使用了ST最新的 HAL驱动库,发现 HAL 库 BUG 还是挺多的!本文重点针对在使用HAL库的 UART / USART 部分时,发现的以下几个个比较严重Bug。其严重影响正常产品中使用!特此备注说明。
不过需要说明的是,HAL库的串口驱动确实很好使用,绝大部分繁杂的工作都被封装在了 HAL 库 函数之中。但是,这种封装也存在一定的弊端,因为作为嵌入式产品,我不需要大而全的东西,只求精简高效!
在使用 USB部分驱动时,BUG也是不少(目前产品已全部替换使用旧版独立版本的USB库了),后续文章在说明!HAL库的USB部分是真的难用!与旧版差多了!
分析解决
- 如果在DMA发送过程中,出现串口错误(可能是发送错误,也可能是接收错误),将进入HAL的中断处理函数,但是处理函数中只处理了DMA的接收部分(将DMA关闭,清除串口的接收标志CR3->DMAR),而对于发送的DMA标志(CR3->DMAT)则没有处理,这样重新初始化后,CR3->DMAT先于DMA通道使能,就导致了再次配置完串口启用DMA发送时出现错误。因此***必须在错误回调函数中,显式清除CR3->DMAT***
- 由于DMA在其设计上,在使能时NDTR会自动重装之前的值,而串口使用DMA接收时,通常是工作在循环模式下,重装后将导致循环模式重新计算,进一步导致串口接收循环覆盖。(STM32F407手册有说明,其他芯片未知)因此,不能在驱动的任何地方关闭DMA(发生错误,重新初始化除外)。
针对这一点,网上很多文章说,在获取DMA收到的数据长度时,最好先关闭DMA,这种情况不适用于循环模式!
- 关于错误回调函数的调用问题:在DMA模式下,出现错误时,根据HAL库的设计,错误回调函数是在DMA中断处理函数中被调用的。具体流程为:
串口接收产生错误 -> 关闭使用的DMA -> DMA传输完成 -> 产生完成中断 -> 进入DMA中断处理函数并发现有错误,则调用串口错误处理函数
。这就导致了在正常传输时,DMA接收中断是用不到的,但是一旦产生错误时,则会产生DMA传输完成中断。 因此,DMA接收中断只在产生错误时使用。 寄存器的值具体如下所示:
在之前的使用中,我曾认为DMA的接收中断没有用到就删除了,后来发现无法处理错误情况!
- 在HAL库的设计上,调用对应的反初始化函数是非常有必要的!例如:对于串一般需要根据波特率等信息反复初始化。但是,在使用了DMA时,HAL库在设计上将DMA作为MSP部分,而一旦初始化过之后,MSP便不会再次初始化。这就导致了,DMA接收不会复位(NDTR寄存器不会重装)。进一步导致DMA接收存放数据位置有偏移。但是,又不可能先调用反初始化,再调用初始化(没初始化之前,对应的外设的Handle还没有赋值)。反初始化必须在初始化后调用,所以对于HAL库的该问题需要进行额外处理。以下以串口为例(仅仅是两处例子,其他库函数也可能有问题),说明如下:
/* 问题 一 */
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)
{
/* 省略 */
if(huart->gState == HAL_UART_STATE_RESET) /* 只有在 HAL_UART_STATE_RESET 时,才会初始化 MSP部分 */
{
/* Allocate lock resource and initialize it */
huart->Lock = HAL_UNLOCKED;
/* Init the low level hardware */
HAL_UART_MspInit(huart);
}
huart->gState = HAL_UART_STATE_BUSY; /* 一旦初始化之后,状态即为 HAL_UART_STATE_BUSY */
/* 省略 */
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->gState= HAL_UART_STATE_READY; /* 最后,状态为 HAL_UART_STATE_READY */
huart->RxState= HAL_UART_STATE_READY;
}
/* 问题 二 */
HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
{
/* 省略 */
if(HAL_DMA_STATE_READY == hdma->State)/* 只有在 HAL_DMA_STATE_READY 时,才会初始化重新赋值DMA */
{
/* 省略 */
}
/* 省略 */
}
- 当发生错误时,HAL库的中断处理函数
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
没有清除错误中断标志!导致重新初始化之后,一直不停的进入中断。其手册中给出的中断标志清除如下所示:
* @param __FLAG__ specifies the flag to check.
* This parameter can be any combination of the following values:
* @arg UART_FLAG_CTS: CTS Change flag (not available for UART4 and UART5).
* @arg UART_FLAG_LBD: LIN Break detection flag.
* @arg UART_FLAG_TC: Transmission Complete flag.
* @arg UART_FLAG_RXNE: Receive data register not empty flag.
*
* @note PE (Parity error), FE (Framing error), NE (Noise error), ORE (Overrun
* error) and IDLE (Idle line detected) flags are cleared by software
* sequence: a read operation to USART_SR register followed by a read
* operation to USART_DR register.
* @note RXNE flag can be also cleared by a read to the USART_DR register.
* @note TC flag can be also cleared by software sequence: a read operation to
* USART_SR register followed by a write operation to USART_DR register.
* @note TXE flag is cleared only by a write to the USART_DR register.
/** @brief Clears the specified UART pending flag.
* @param __HANDLE__ specifies the UART Handle.
* This parameter can be UARTx where x: 1, 2, 3, 4, 5, 6, 7 or 8 to select the USART or
* UART peripheral.
* @param __FLAG__ specifies the flag to check.
* This parameter can be any combination of the following values:
* @arg UART_FLAG_CTS: CTS Change flag (not available for UART4 and UART5).
* @arg UART_FLAG_LBD: LIN Break detection flag.
* @arg UART_FLAG_TC: Transmission Complete flag.
* @arg UART_FLAG_RXNE: Receive data register not empty flag.
*
* @note PE (Parity error), FE (Framing error), NE (Noise error), ORE (Overrun
* error) and IDLE (Idle line detected) flags are cleared by software
* sequence: a read operation to USART_SR register followed by a read
* operation to USART_DR register.
* @note RXNE flag can be also cleared by a read to the USART_DR register.
* @note TC flag can be also cleared by software sequence: a read operation to
* USART_SR register followed by a write operation to USART_DR register.
* @note TXE flag is cleared only by a write to the USART_DR register.
*
* @retval None
*/
#define __HAL_UART_CLEAR_FLAG(__HANDLE__, __FLAG__) ((__HANDLE__)->Instance->SR = ~(__FLAG__))
由上可知,想要清除错误标志(FE、PE、NE等)必须先读取USART_SR register,紧接着读取USART_DR register。但是中断处理函数中没有该过程!因此,必须在错误处理函数中显示执行以上步骤。HAL库给出了本身清除以上标志对应的宏(其实,任意调用一个即可!) ,如下:
/** @brief Clear the UART PE pending flag.
* @param __HANDLE__ specifies the UART Handle.
* This parameter can be UARTx where x: 1, 2, 3, 4, 5, 6, 7 or 8 to select the USART or
* UART peripheral.
* @retval None
*/
#define __HAL_UART_CLEAR_PEFLAG(__HANDLE__) \
do{ \
__IO uint32_t tmpreg = 0x00U; \
tmpreg = (__HANDLE__)->Instance->SR; \
tmpreg = (__HANDLE__)->Instance->DR; \
UNUSED(tmpreg); \
} while(0U)
/** @brief Clear the UART FE pending flag.
* @param __HANDLE__ specifies the UART Handle.
* This parameter can be UARTx where x: 1, 2, 3, 4, 5, 6, 7 or 8 to select the USART or
* UART peripheral.
* @retval None
*/
#define __HAL_UART_CLEAR_FEFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)
/** @brief Clear the UART NE pending flag.
* @param __HANDLE__ specifies the UART Handle.
* This parameter can be UARTx where x: 1, 2, 3, 4, 5, 6, 7 or 8 to select the USART or
* UART peripheral.
* @retval None
*/
#define __HAL_UART_CLEAR_NEFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)
/** @brief Clear the UART ORE pending flag.
* @param __HANDLE__ specifies the UART Handle.
* This parameter can be UARTx where x: 1, 2, 3, 4, 5, 6, 7 or 8 to select the USART or
* UART peripheral.
* @retval None
*/
#define __HAL_UART_CLEAR_OREFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)
/** @brief Clear the UART IDLE pending flag.
* @param __HANDLE__ specifies the UART Handle.
* This parameter can be UARTx where x: 1, 2, 3, 4, 5, 6, 7 or 8 to select the USART or
* UART peripheral.
* @retval None
*/
#define __HAL_UART_CLEAR_IDLEFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)
在不同芯片的HAL库中,其处理稍有不同。例如:在STM32F407的库中,在函数HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
中会有调用__HAL_UART_CLEAR_OREFLAG(huart);
,但是,在STM32F205对应的库中则没有调用。具体如下图:
- 中断模式下的接收函数
static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
存在越界问题。具体如下:
/* Check that a Rx process is ongoing */
if(huart->RxState == HAL_UART_STATE_BUSY_RX)
{
if(huart->Init.WordLength == UART_WORDLENGTH_9B)
{
tmp = (uint16_t*) huart->pRxBuffPtr; /* 当成16位处理,当接收最后一个数据时,导致越界 */
if(huart->Init.Parity == UART_PARITY_NONE)
{
*tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
huart->pRxBuffPtr += 2U;
}
else
{
*tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x00FF);
huart->pRxBuffPtr += 1U;
}
}
else
{
if(huart->Init.Parity == UART_PARITY_NONE)
{
*huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
}
else
{
*huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
}
}
}
STM32F205最新版本(1.7.0)的HAL库中,对以上问题(5)已经进行了修复!建议更新到最新版本!!!