打印
[应用相关]

HAL 库串口(USART/UART)驱动 BUG 及解决方法

[复制链接]
616|5
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
wowu|  楼主 | 2021-8-4 20:40 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
写在前面
  在工作中,部分产品使用了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的接收中断没有用到就删除了,后来发现无法处理错误情况!


使用特权

评论回复
沙发
wowu|  楼主 | 2021-8-4 20:40 | 只看该作者
在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 */
    {
        /* 省略 */
    }
    /* 省略 */
}


使用特权

评论回复
板凳
wowu|  楼主 | 2021-8-4 20:41 | 只看该作者
当发生错误时,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__))


使用特权

评论回复
地板
wowu|  楼主 | 2021-8-4 20:42 | 只看该作者
由上可知,想要清除错误标志(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__)


使用特权

评论回复
5
wowu|  楼主 | 2021-8-4 20:42 | 只看该作者
在不同芯片的HAL库中,其处理稍有不同。例如:在STM32F407的库中,在函数HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)中会有调用__HAL_UART_CLEAR_OREFLAG(huart);,但是,在STM32F205对应的库中则没有调用。具体如下图:



使用特权

评论回复
6
wowu|  楼主 | 2021-8-4 20:43 | 只看该作者
中断模式下的接收函数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)已经进行了修复!建议更新到最新版本!!!

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

80

主题

3852

帖子

1

粉丝