【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

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

  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
以下是一个简单的STM32串口空闲中断DMA接收程序的示例: ``` #include "stm32f4xx.h" // 定义DMA缓冲区大小 #define BUFFER_SIZE 256 // 定义DMA接收缓冲区 uint8_t dma_buffer[BUFFER_SIZE]; int main(void) { // 初始化串口 USART_InitTypeDef usart_init_struct; usart_init_struct.USART_BaudRate = 115200; usart_init_struct.USART_WordLength = USART_WordLength_8b; usart_init_struct.USART_StopBits = USART_StopBits_1; usart_init_struct.USART_Parity = USART_Parity_No; usart_init_struct.USART_Mode = USART_Mode_Rx; USART_Init(USART1, &usart_init_struct); USART_Cmd(USART1, ENABLE); // 初始化DMA DMA_InitTypeDef dma_init_struct; dma_init_struct.DMA_Channel = DMA_Channel_4; dma_init_struct.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->DR); dma_init_struct.DMA_Memory0BaseAddr = (uint32_t)dma_buffer; dma_init_struct.DMA_DIR = DMA_DIR_PeripheralToMemory; dma_init_struct.DMA_BufferSize = BUFFER_SIZE; dma_init_struct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; dma_init_struct.DMA_MemoryInc = DMA_MemoryInc_Enable; dma_init_struct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; dma_init_struct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; dma_init_struct.DMA_Mode = DMA_Mode_Circular; dma_init_struct.DMA_Priority = DMA_Priority_High; dma_init_struct.DMA_FIFOMode = DMA_FIFOMode_Disable; dma_init_struct.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; dma_init_struct.DMA_MemoryBurst = DMA_MemoryBurst_Single; dma_init_struct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(DMA2_Stream2, &dma_init_struct); // 启动DMA传输 DMA_Cmd(DMA2_Stream2, ENABLE); // 配置串口空闲中断 USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); // 启动串口 USART_Cmd(USART1, ENABLE); while (1) { // 空闲中断触发后处理接收到的数据 if (USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) { USART_ClearITPendingBit(USART1, USART_IT_IDLE); uint16_t length = BUFFER_SIZE - DMA_GetCurrDataCounter(DMA2_Stream2); // 处理接收到的数据 // ... // 重新启动DMA传输 DMA_Cmd(DMA2_Stream2, ENABLE); } } } // 串口中断处理函数 void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) { // 清除中断标志 USART_ClearITPendingBit(USART1, USART_IT_IDLE); } } ``` 该程序使用了DMA来接收串口数据,并使用了空闲中断来触发数据处理。在空闲中断处理函数中,首先需要获取接收到的数据的长度,然后进行数据处理。处理完毕后,再重新启动DMA传输。注意,在空闲中断处理函数中,需要清除中断标志。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值