【STM32】USART IDLE + DMA 异常解决方案

背景

又出 BUG 呗~

设计背景

之前使用 STM32F207 做了一个 UART -> I2C 的转接板。UART 部分是用来与上位机进行交互的,接收数据采用 IDLE 中断 + DMA 的方式,发送数据采用阻塞的方式。上位机可以通过指令触发中转板定时采集 Slave 的数据,也会通过指令对 Slave 进行配置。

问题描述

先来说下问题是什么
若在 Tx 进行数据 A 发送的时间内接收到第一帧数据 B,后续若接收到第二帧数据 C,程序内部实际获取到的第二帧数据是第一帧数据 B 的内容,而不是 C,但并不影响后续接收数据 D。每次均能正常进入 IDLE 中断。当 Tx 为空闲状态时,接收到数据,不存在上述的问题。
在这里插入图片描述

这个问题实际是个很严重的问题,因为这会导致配置指令丢失的情况,从而导致 Slave 存在未配置的情况。

首先这个问题之前很偶然的被规避掉了。先说下这是怎么被规避的(我也知道我的废话有点多,可耐不住我先记录下~):之前在编程时存在 I2C 总线拔除 Slave 导致总线挂掉的情况,实在没辙了,我在“关闭数据采集”(B 指令)进行了 System Reset,异常都被复位,而且 Tx 不会再发送 A 数据,那么 C 指令总能正常接收。

再来说下,这个问题是怎么被发现的。在我解决了 I2C 总线问题后,我把“关闭数据采集”(B 指令)中的 System Reset 功能去除了。在测试人员测试多台 Slave 后,出现了几台 Slave 不合格,这个几台 Slave 均是之前测试通过的。通过数据分析,发现均是 C 指令未配置。

然后,通过在 IDLE 中断内通过另外一个串口打印接收数据,发现偶尔会出现 B 指令接收了 2 次。原以为是上位机的问题,后来通过逻辑分析仪发现:每次异常时,均是发生在如上图时的时序,而且 C 指令是已经下发了,是下位机程序没有接收 C 指令,但进入了 IDLE 中断,导致接收到的仍是 C 指令。

问题定位

首先先来看看 Rx 的工作流程:

  • DMA 保存 Data 至其 Buffer 中(用户不可见)
  • 当一帧数据接收完成后,USART 产生空闲中断(IDLE),程序进入中断回调函数
  • 中断回调函数中,清除 IDLE Flag 并 Disable DMA,再将数据 Copy 下来,设置 DMA Channel 的 NDTR 寄存器,最后 Enable DMA
  • 退出中断后,再进行数据处理

在这里插入图片描述

从之前的问题描述中,每一帧的 IDLE 均能产生,第二帧指令 C 没有进行接收。中断回调函数的基本内容就是做数据搬运。那问题就定位到了 DMA Receive 中了?为什么出现 DMA 不进行接收?
我们来列举可能性:

  • DMA 关闭
  • DMA 异常
  • USART 异常

下面做下测试补偿:

  • 在整个通讯过程中,并没有产生 DMA 中断,排除 DMA 异常的可能性。因为 DMA 的中断时开启的。
  • 通过 Debug 发现,当出现问题场景时,从 DMA 的寄存器中可以看出, DMA 被失能了。

IDLE + DMA 的用法

其实对于上面所提及的问题,本质的原因是 DMA 的用法不当所致。下面提供正常可用的代码块。

STM32CUBE 配置

NOTE
USART 的 DMA Rx Mode一定要选择 Cirular 模式。Tx 选择 Normal 即可。
在这里插入图片描述

User USART 初始化部分

     void USER_Usart3_Init(void)
    {
    	if(HAL_UART_Receive_DMA(&UPPER_USART, (uint8_t *)rxBuffer, USART_BUF_SIZE) == HAL_OK)
    	{
    		__HAL_UART_ENABLE_IT(&UPPER_USART, UART_IT_IDLE);
    		printf("STM32F207 Ready!\r\n");
    	}
    	else
    	{
    		printf("Err: USER_Usart3_Init\r\n");
    		FAILEDProc();
    	}
    }

中断处理部分

HAL 库没有现成的 IDLE 中断回调函数,需要用户自定义。

void HAL_UART_IDLECallback(UART_HandleTypeDef *huart)
{
	uint32_t clearStatus = 0;

	if(__HAL_UART_GET_FLAG(&huart3, UART_FLAG_IDLE) != RESET)
	{
		__HAL_UART_CLEAR_IDLEFLAG(huart);	
		
		clearStatus = huart->Instance->SR;
		clearStatus = huart->Instance->DR;
		
		__HAL_DMA_DISABLE(huart->hdmarx);
		rxBufLen = USART_BUF_SIZE - __HAL_DMA_GET_COUNTER(huart->hdmarx);
		UsartSaveRxDataToProcBuffer((uint8_t *)rxBuffer, rxBufLen, procBuffer, &procBufLen);
		__HAL_DMA_SET_COUNTER(huart->hdmarx, USART_BUF_SIZE);
		__HAL_DMA_ENABLE(huart->hdmarx);
	}
}

To Do

上面写了这么多,才发现实际上我并没有弄懂是什么原因。因此,等我查个水落石出再回来!
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/XiuHua_Wu/article/details/82885880