STM32_HAL库编程_串口+DMA接收数据异常问题记录

本文介绍了一个STM32串口通信故障案例,详细分析了故障原因及解决过程。通过检查串口中断触发状态,发现串口出错会导致接收中断被关闭,进而无法接收数据。最终通过在错误回调函数中重新使能串口DMA接收解决了问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.开发环境概述

	Keil - 5.27
	STM32CUBEMX
	STM32F103RCT6

2.问题现象

在利用RCT6做主机通信,C8T6做从机通信时。拔插从机电路板,有概率出现主机通信故障无法接收到从机数据,或者从机通信故障无法接收到主机数据的问题。两者开发均使用HAL库编程,串口数据接收方式为UART+DMA接收。

3.排查过程

利用LED灯检查每次数据过来时的串口中断触发状态,发现在拔插从机导致通信出错后,从机后续发出的数据均不能触发主机的串口接收中断。出现这种情况有两种可能,第一:主机死机了程序流程无法执行,第二:中断被屏蔽了,所以无法触发。先排除第一种情况,主板程序运转时有控制另一个LED灯定时翻转,在通信异常后,工作LED灯依旧正常翻转,由此可见主机并未死机。那么只剩第二种情况,可能在哪一步流程中,MCU把串口的接收中断给关闭了。

追入HAL_UART_IRQHandler()函数中,发现其有一段串口错误处理流程,当串口出错时。该函数会关闭串口接收状态UART_EndRxTransfer(huart);,并调用HAL_UART_ErrorCallback()回调函数给用户进行对应处理。这样一来问题就很清楚了,串口出错后,程序关闭了串口接收使能,所以后续从机所有的数据均不能触发主机的接收中断进行处理。并且程序将HAL_UART_ErrorCallback()函数而非HAL_UARTEx_RxEventCallback()函数,若没有在HAL_UARTEx_RxEventCallback()函数中写入对应的处理程序,那么就相当于少执行了一次HAL_UARTEx_RxEventCallback()函数。

解决办法

单独实现HAL_UART_ErrorCallback()函数,并在其中重新使能串口DMA接收即可。
在这里插入图片描述
中断处理流程

  1. 串口中断函数
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
	  LED2_TOGGLE();
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
  /* USER CODE END USART1_IRQn 1 */
}
  1. 串口中断接收数据回调函数
/**
*串口1空闲中断回调函数
**/
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
	if (huart->Instance == USART1) //串口1空闲中断
	{
		if (Size >= 7)
		{
			if(RxBuffer1[0] == Slave_Data[0].Slave_Address	||
				 RxBuffer1[0] == Slave_Data[1].Slave_Address	||
				 RxBuffer1[0] == Slave_Data[2].Slave_Address	||
				 RxBuffer1[0] == Slave_Data[3].Slave_Address	||
				 RxBuffer1[0] == Slave_Data[4].Slave_Address	||
				 RxBuffer1[0] == Slave_Data[5].Slave_Address	||
				 RxBuffer1[0] == Slave_Data[6].Slave_Address	||
				 RxBuffer1[0] == Slave_Data[7].Slave_Address	||
				 RxBuffer1[0] == Slave_Data[8].Slave_Address	||
				 RxBuffer1[0] == Slave_Data[9].Slave_Address	||	RxBuffer1[0] == Broadcast_Modbus_Addr||RxBuffer1[0] == BAK_Modbus_Addr)	//判断地址是否符合
				{
					modbus1_ok = TRUE;		//接收标志置位
					modbus1_RxNum = Size;	//保存数据长度
				}
		}
		
//		Start_Reback_Dma = 0;					//这两个是用来打印调试信息的
//		ReStart_Dma_Switch = FALSE;
		
		uint8_t Ret = 0;
		Ret = HAL_UARTEx_ReceiveToIdle_DMA(&huart1,RxBuffer1,RBufferSize); //使用串口IDLE中断接收数据
		if (Ret != HAL_OK)
		{
			char Buff[20] = {0};
			sprintf(Buff,"ERROR:%d\r\n",Ret);
			HAL_UART_Transmit(&huart2,(uint8_t *)Buff,sizeof(Buff),0xFFFF); //输出信息提示
		}
//		LED2_TOGGLE();	//收到一包数据,翻转一次LED灯
	}
	
	else if (huart->Instance == USART3)	//串口3空闲中断
	{
		if (!memcmp(RxBuffer3,"AT+Shiled_Slave:{",17))/*屏蔽从机指令*/
		{
			UI_Command = Command_1;																			//命令标号定义
			UI_Cmd_IsArrive = TRUE;																			//命令接收标志置位
			memcpy(UI_Cmd_Recevice,RxBuffer3,Size);											//内存中缓存命令
			UICmd_Recevice_Counter = Size;															//保存接收的字节数
			
			printf("\r\nRecv AT+Shiled_Slave OK\r\n");											//回应主机
			HAL_UART_Transmit(&huart2,(uint8_t *)"Recv AT+Shiled_Slave OK\r\n",25,0xffff);
		}
		
		else if (!memcmp(RxBuffer3,"AT+SelfInspection:{",19))/*通信自检指令*/
		{
			UI_Command = Command_2;																			//命令标号定义
			UI_Cmd_IsArrive = TRUE;																			//命令接收标志置位
			memcpy(UI_Cmd_Recevice,RxBuffer3,Size);											//内存中缓存命令
			UICmd_Recevice_Counter = Size;															//保存接收的字节数
			
			printf("Recv AT+SelfInspection OK\r\n");										//回应主机
			HAL_UART_Transmit(&huart2,(uint8_t *)"\r\nRecv AT+SelfInspection OK\r\n",29,0xffff);
		}
		
		else if(!memcmp(RxBuffer3,"AT+Reset_Slave:{",16))/*复位从机指令*/
		{
			UI_Command = Command_3;																			//命令标号定义
			UI_Cmd_IsArrive = TRUE;																			//命令接收标志置位
			memcpy(UI_Cmd_Recevice,RxBuffer3,Size);											//内存中缓存命令
			UICmd_Recevice_Counter = Size;															//保存接收的字节数
			
			printf("Recv AT+Reset_Slave OK\r\n");
			HAL_UART_Transmit(&huart2,(uint8_t *)"\r\nRecv AT+Reset_Slave OK\r\n",26,0xffff);
		}
		
		else if (!memcmp(RxBuffer3,"AT+Slave_Config:{",17))/*配置主板通信指令*/
		{
			UI_Command = Command_4;																			//命令标号定义
			UI_Cmd_IsArrive = TRUE;																			//命令接收标志置位
			memcpy(UI_Cmd_Recevice,RxBuffer3,Size);											//内存中缓存命令
			UICmd_Recevice_Counter = Size;															//保存接收的字节数
			
			printf("Recv AT+Slave_Config OK\r\n");
			HAL_UART_Transmit(&huart2,(uint8_t *)"\r\nRecv AT+Slave_Config OK\r\n",27,0xffff);
		}
		
		HAL_UARTEx_ReceiveToIdle_DMA(&huart3,RxBuffer3,RBufferSize); //使用串口IDLE中断接收数据
	}
}

  1. 串口错误回调函数
/**
*串口错误回调函数
**/
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
		if (huart->Instance == USART1)
		{
			char Buff[20] = {0};
			snprintf(Buff,20,"USART ERROR:\r\n");
			HAL_UART_Transmit(&huart2,(uint8_t *)Buff,strlen(Buff),0xFFFF); //输出信息提示
			
			uint8_t Ret,St;
			St = (&huart1)->RxState;
			Ret = HAL_UARTEx_ReceiveToIdle_DMA(&huart1,RxBuffer1,RBufferSize); //重设,使用串口IDLE中断接收数据
			
			if (Ret != HAL_OK)
			{
				memset(Buff,0,20);
				snprintf(Buff,20,"ERROR:%d,RXs = %d\r\n",Ret,St);
				HAL_UART_Transmit(&huart2,(uint8_t *)Buff,sizeof(Buff),0xFFFF); //输出信息提示
			}
			else
				HAL_UART_Transmit(&huart2,(uint8_t *)"DMA START OK\r\n",sizeof("DMA START OK\r\n"),0xFFFF); //输出信息提示
		}
		
		else if (huart->Instance == USART3)
		{
			char Buff[20] = {0};
			snprintf(Buff,20,"USART ERROR:\r\n");
			HAL_UART_Transmit(&huart2,(uint8_t *)Buff,strlen(Buff),0xFFFF); //输出信息提示
			
			uint8_t Ret,St;
			St = (&huart3)->RxState;
			Ret = HAL_UARTEx_ReceiveToIdle_DMA(&huart3,RxBuffer3,RBufferSize); //重设,使用串口IDLE中断接收数据
			
			if (Ret != HAL_OK)
			{
				memset(Buff,0,20);
				snprintf(Buff,20,"ERROR:%d,RXs = %d\r\n",Ret,St);
				HAL_UART_Transmit(&huart2,(uint8_t *)Buff,sizeof(Buff),0xFFFF); //输出信息提示
			}
			else
				HAL_UART_Transmit(&huart2,(uint8_t *)"DMA START OK\r\n",sizeof("DMA START OK\r\n"),0xFFFF); //输出信息提示
		}
}

DMA(Direct Memory Access,直接内存访问)上溢报错是指在数据传输过程中,DMA控制器尝试访问的内存地址超出了其寻址能力,导致溢出错误。解决DMA上溢报错的一般步骤如下: 1. 理解DMA的工作原理:首先需要了解DMA的工作原理,包括DMA控制器、DMA通道以及内存的结构等等。这样能帮助我们更好地定位和理解上溢错误的原因。 2. 检查DMA配置:检查DMA控制器的配置是否正确。确保DMA通道的起止地址和传输长度的设置是正确的,并且与要操作的设备或外设匹配。 3. 检查内存映射:确认被访问的内存地址是正确的,不会超出DMA控制器的寻址范围。若需要,可以重新映射内存,或者重新配置DMA通道以适应现有的内存映射。 4. 检查硬件连接:确认DMA控制器与设备或外设之间的连接是正确的,没有接触不良或者松动的地方。 5. 检查操作系统或驱动程序:有时,DMA上溢错误可能是由操作系统或驱动程序中的错误所引起的。更新或重新安装相关的驱动程序,或者升级操作系统可能会解决这个问题。 6. 确保数据完整性:检查输入数据的完整性。可能的原因之一是输入数据溢出,导致DMA上溢错误。确保输入数据的大小和长度与DMA通道的配置相匹配。 7. 调试和日志记录:在程序中添加调试代码,并通过日志记录来查看和分析DMA操作过程中的错误信息,以便更好地定位和解决上溢错误。 总之,解决DMA上溢报错需要仔细检查DMA的配置、内存映射、硬件连接以及操作系统或驱动程序等方面的问题,并进行相应的调试和日志记录工作。除此之外,还应理解DMA的工作原理,以便更好地定位和解决这类错误
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值