STM32串口空闲中断+DMA不定长收发数据(HAL库)

 

一、STM32 IDLE和RXNE中断

1.1.STM32的IDLE功能:

在使用串口接受字符串时,可以使用空闲中断(IDLEIE置1,即可使能空闲中断),这样在接收完一个字符串,进入空闲状态时(IDLE置1)便会激发一个空闲中断。在中断处理函数,我们可以解析这个字符串。

中断产生条件:在串口无数据接收的情况下,不会产生,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一但接收的数据断流,没有接收到数据,即产生IDLE中断。

1.2.STM32 的RXNE功能:

当串口接收到一个bit的数据时,(读取到一个停止位) 便会触发 RXNE接收数据中断,接受到一个字节的数据,触发中断。比如给上位机给单片机一次性发送了8个字节,就会产生8次RXNE中断,1次IDLE中断。当串口接收到数据时,bit5 RXNE就会自动变成1,当接收完一帧数据后,bit4就会变成1。

 

图1.串口CR1寄存器

 

二、USART+DMA接收数据

2.1.DMA计算实际接收数据字节数

图2.1.DMA返回接收空间剩余字节宏定义

 

DMA接收时该宏将返回当前接收空间剩余字节。其本质就是读取NTDR寄存器,DMA通道结构体中定义了NDTR寄存器,读取该寄存器即可得到未传输的数据数。

实际接收的字节= 预先定义的接收总字节 - __HAL_DMA_GET_COUNTER()

 

图2.2 NTDR寄存器

 

2.2.串口IDLE空闲中断的方式接收一帧数据原理

采用STM32F103的串口1,并配置成空闲中断IDLE模式且使能DMA接收,并同时设置接收缓冲区和初始化DMA。那么初始化完成之后,当外部给单片机发送数据的时候,假设这次接受的数据长度是100个字节,那么在单片机接收到一个字节的时候并不会产生串口中断,而是DMA在后台把数据默默地搬运到你指定的缓冲区数组里面。当整帧数据发送完毕之后串口才会产生一次中断,此时可以利用__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);函数计算出当前DMA接收存储空间剩余字节。

例如,本次串口接收100个字节,通过上位机发送了 Usart_Test 6个字节的数据长度,那么此时 GET_COUNTER函数读出来接收存储空间剩余字节就是94个字节,即:

实际接收的字节(6) = 预先定义的接收总字节(100) -__HAL_DMA_GET_COUNTER()(94)

9 = 100-94

图2.3.串口+DMA接收数据的变量


volatile uint8_t Array_number = 100;          //暂存数组个数         
uint8_t Rx_date[100]={0};                     //数据接收暂存数组
HAL_UART_Receive_DMA(&huart1,Rx_date,Array_number);//重启DM

 

 

三、软件设计

本例程将使用DMA+串口接受空闲中断,实现不定长数据完整接收,并将接收到的数据发送至上位机的功能。

图3.1.例程逻辑图

3.1.接收数据的流程:

首先在初始化的时候打开DMA接收,当MCU通过USART接收外部发来的数据时,在进行第①②③步的时候,DMA直接将接收到的数据写入缓存rx_buffer[100] //接收数据缓存数组,程序此时也不会进入接收中断,在软件上无需做任何事情,要在初始化配置的时候设置好配置就可以了。

3.2数据接收完成的流程:

当一帧数据接收完成之后IDLE置1,产生接收空闲中断④,在中断服务函数中做这几件事:

  • 判断是否为IDLE接收空闲中断。

②在中断服务函数中将接收完成标志位置1。

③关闭DMA防止在处理数据时候接收数据,产生干扰。

④计算出接收缓存中的数据长度,清除中断标志位。

图3.2.中断服务函数代码

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
	uint32_t temp;                                           //未传输数据的个数
	uint8_t IDLE_flag = 0;                                   //空闲中断标志位
	IDLE_flag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);  //获取IDLE标志位
	if(IDLE_flag == 1)                                       //判断是否进入空闲中断
	{ 
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);                  //清除标志位
		HAL_UART_DMAStop(&huart1);                           //停止DMA传输,防止搬错
		temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);       //获取DMA中未传输的数据个数   
		Rx_length = Array_number - temp;                     //总计数减去未传输的数据个数,得到已经接收的数据个数
		Rx_end_flag = 1;	                                 //接收完成标志位置1	
	 }

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

 

3.3.while循环主程序流程:

  • 主程序中检测到接收完成标志被置1。
  • 进入数据处理程序,将接收到的数据重新发送到上位机。
  • 将接收完成标志位置0,并清除计数。
  • 重新设置DMA下次要接收的数据字节数,使能DMA进入接收数据状态。

 

图3.1.while循环代码

 


  while (1)
  {
    /* USER CODE END WHILE */
	 if(Rx_end_flag == 1)                                                                 //判断数据是否接受完成
		{			
		    HAL_UART_Transmit_DMA(&huart1,Rx_date,Rx_length);                                 //将接收的数据返回上位机			
			Rx_length = 0;                                                                    //清除计数
			Rx_end_flag = 0;                                                                  //清除接收标志位			
        }
	 HAL_UART_Receive_DMA(&huart1,Rx_date,Array_number);                                 //重启DMA
    /* USER CODE BEGIN 3 */
  }

四、注意事项

1、一定要初始化空闲中断和DMA,空闲中断未初始化会导致无法进入空闲中断,DMA未初始化会导致第一次搬运数据为空数据。

 

图4.1.空闲中断和DMA初始化函数

 

2、while每次循环结束时,要重启DMA(HAL_UART_Receive      _DMA();),否则DMA搬运的数据不会更新,即DMA只搬运一次,Rx_date[ ]中的数据永远为第一次存进去的数据。

图4.2

 

3、接收数据时,DMA搬运来的数据将暂存至Rx_date[ ]数组中,但是将接收到的数据处理后,用HAL_UART_Transmit_DMA(&huart1,Rx_date,Rx_length)发回上位机时,数据的长度应该为Rx_length,如果用Array_number,会出现下图情况。

 

图4.3

4、__HAL_DMA_GET_COUNTER();函数获取的是DMA中未传输的数据个数,若要获取实际接收数据的长度需进一步处理。

 

图4.4 实际接收数据长度的处理

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值