串口空闲中断

这篇博客主要记录了笔者学习串口空闲中断的过程、遇到的问题和解决的过程。目前仍有一点地方不是很清楚,也希望各位路过的巨佬能高抬贵手指点迷津

主要的目的是利用STM32的串口空闲中断实现将发送的数据完整回传到上位机,相关的教程站内已经比较多了,讲的也比较清楚,这里就不再赘述.我参考的教程是Z小旋的:STM32 HAL CubeMX 串口IDLE接收空闲中断+DMA

在上面的教程的例程1中采取的方案:

  • 空闲中断处理函数中:确认产生空闲中断->清除空闲中断标志位->暂停DMA传输,防止干扰数据处理->计算已接收数据长度->接收完成标志置1
  • 主函数while循环中:循环检查接收完成标志是否为1,若为1->发送已接收数据->已接收长度置0,清除接收完成标志位,清空接收数组->开启DMA接收

笔者在学习时的思路是从目的出发,一步步推导。

首先能想到的思路是不开启中断直接在While循环里重复接收和发送:

while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  HAL_UART_Receive_DMA(&huart1,(uint8_t*)DMA_Buffer,200);
	  HAL_UART_Transmit_DMA(&huart1,(uint8_t*)DMA_Buffer,200);
	  HAL_Delay(1000);
  }
  /* USER CODE END 3 */

但是这样的结果是串口不断的发送DMA_Buffer中的内容,并且末尾的字符串结束符\0全部显示出来了。这样的方案显然不是我们想要的。

为了能够只在数据发送过去时产生回发的操作,就需要开启空闲中断,在数据流中断时产生空闲中断,这样就能在发送数据结束后执行回发的操作,将已发送的数据一下全部回发。
同时,为了不显示末尾的\0,需要计算已接收数据的长度。

而我与上方的教程略有不同的地方在于我把所有操作都放在了空闲中断处理函数中。而这也带来了一些问题。

以下为代码:

在stm32f1xx_it.c中

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
	HAL_UART_Idle_Callback(&huart1);

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

HAL_UART_Idle_Callback()定义在usart.c中

void HAL_UART_Idle_Callback(UART_HandleTypeDef *huart)
{
	//开启空闲接收中断,在中断处理函数中:
	//判断是否为IDLE中断->清除中断标志位->暂停DMA接收->计算数据长度->发送数据->清空缓冲数组->开启DMA传输
	
	uint32_t tmp_flag = 0;
	tmp_flag = __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);//获取IDLE标志位
	if(tmp_flag != RESET)//判断是否产生IDLE中断
	{
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除中断标志位
		HAL_UART_DMAStop(&huart1);//暂停DMA接收
		rx_len = Buffer_Size - hdma_usart1_rx.Instance->CNDTR;
		HAL_UART_Transmit_DMA(&huart1,(uint8_t*)DMA_Buffer,rx_len);//发送数据
		memset(DMA_Buffer,0,rx_len);//清空缓冲数组
		HAL_UART_Receive_DMA(&huart1,(uint8_t*)DMA_Buffer,Buffer_Size);//开启DMA接收
	}
}

在usart.c的MX_USART1_UART_Init()中

void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */
  __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);//使能空闲中断
  HAL_UART_Receive_DMA(&huart1,(uint8_t*)DMA_Buffer,200);//先接收到数据才能有空闲产生

  /* USER CODE END USART1_Init 2 */

}

写完代码烧录到板子上之后验证时发现串口回传的数据有问题

串口空闲中断

uint8_t buffer[] = "段哥yyds";
void HAL_UART_Idle_Callback(UART_HandleTypeDef *huart)
{
	//开启空闲接收中断,在中断处理函数中:
	//判断是否为IDLE中断->清除中断标志位->暂停DMA接收->计算数据长度->发送数据->清空缓冲数组->开启DMA传输
	
	uint32_t tmp_flag = 0;
	tmp_flag = __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);//获取IDLE标志位
	if(tmp_flag != RESET)//判断是否产生IDLE中断
	{
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除中断标志位
		HAL_UART_DMAStop(&huart1);//暂停DMA接收,再次开启DMA接收时会从头开始填充缓冲数组
		rx_len = Buffer_Size - hdma_usart1_rx.Instance->CNDTR;
		
		//验证1.收到的数据是没问题的
		printf("收到的数据:%s\n",DMA_Buffer);
		//验证2:构造一个与接收数据类似的字节更少的数据,DMA_Buffer200位;buffer只有rx_len位:
		for(int i = 0;i < rx_len; i++){
				buffer[i] = DMA_Buffer[i];
		}
		printf("与接收数据类似的字节更少的数据: %s\n",buffer);
		//验证3:HAL_UART_Transmit_DMA发送一个与接收数据类似的字节更少的数据是没问题的
		printf("HAL_UART_Transmit_DMA发送buffer\n");
		HAL_UART_Transmit_DMA(&huart1,(uint8_t*)buffer,rx_len);//发送数据
		//验证4:HAL_UART_Transmit_DMA发送DMA_Buffer
		printf("HAL_UART_Transmit_DMA发送DMA_Buffer\n");
		HAL_UART_Transmit_DMA(&huart1,(uint8_t*)DMA_Buffer,rx_len);//发送数据

	   //	HAL_UART_Transmit_DMA(&huart1,(uint8_t*)buffer,sizeof(buffer)-1);//发送数据
		//	HAL_UART_Transmit_DMA(&huart1,(uint8_t*)DMA_Buffer,rx_len);//发送数据
		
		//	printf("\n%d\n",sizeof(DMA_Buffer));
	    //	printf("\n%d\n",rx_len);
		memset(DMA_Buffer,0,rx_len);//清空缓冲数组,这个操作会干涉DMA发送,使发送出现乱码
		HAL_UART_Receive_DMA(&huart1,(uint8_t*)DMA_Buffer,Buffer_Size);
	}
}

经过一系列的验证,发现:

  • 用重定义的printf输出DMA_Buffer没问题
  • 将DMA_Buffer的内容存到buffer中后printf没问题
  • 用DMA发送buffer没问题
  • 但是DMA发送DMA_Buffer还是不行

由于乱码的数据中有很多\0,猜想可能是memset的问题。将memset去除后,发送DMA_Buffer没问题。
猜测可能是清空数组的操作干扰到了它前一步的发送。但是为什么会干扰还是不太清楚。

另外,将最后的重新开启DMA注释掉之后发现效果还是不变,也不太清除为什么。

以上为本次学习的总结,欢迎大家指正!

上一篇:【FPGA】Buffer专题介绍(二)


下一篇:eBPF开发指南