USART+DMA+循环队列接收不定长数据

简介

这篇文章介绍了串口+DMA的发送过程:记群内因串口DMA发送而引发的讨论_哈士奇去买菜的博客-CSDN博客

本文将介绍基于串口+DMA循环模式+循环队列的接收过程。其最大的优点是每次接收完成后不需要禁止MDA通道然后重新配置接收长度。并且还支持接收不定长数据。

USART的IDLE中断

我已经写过IDLE中断的解释,请参考:GD32F130之USART串口通信_哈士奇去买菜的博客-CSDN博客_gd32f130串口

这里需要补充强调的是,当本机串口检测到IDLE中断时,只是说明对方已经结束了一串连续的串口帧发送过程,而不一定说明对方已经完成了一包应用层数据的发送。首先串口通信本身没有规定串口帧之间的间隔时间,可以没有间隔,或者可以有任意长间隔。其次,对方在发送一包应用层数据的时候,可能在发送过程中被中断打断,导致发送产生间隔,从而会让本机产生IDLE中断。

在DMA接收方式下,IDLE中断用于告诉应用程序,对方已经完整发送了一包数据,或者,对方只发送了一包数据的一部分。因此,在IDLE中断发生时,应该检查收到数据的完整性,只有收到了完整的数据,才去分析处理这个数据。最好的做法是在应用层设计一个串口通信协议,使用帧头和帧尾以及校验来协助检查数据的完整性和合法性。

我们使用IDLE中断而不是RBNE中断,是因为在接收一包应用层数据时,发生IDLE中断的次数远低于RXNE中断,这样接收起来就更高效,在最好的情况下,接收一包数据只会发生一次IDLE中断。而且在对方彻底完成一包数据的发送后,一定会产生IDLE中断。最重要的原因是,我们要接收不定长数据,则不能使用DMA的传输完成中断。

也可以使用接收超时中断替换IDLE中断。如果你的串口支持接收超时中断(RT)则更好,因为接收超时中断可以设置接收超时时间阈值,而IDLE的超时时间固定为一个串口帧的时间,RT比IDLE更加灵活。

我们也不需要使用DMA的接收完成中断。整个接收过程只需要使用IDLE中断(或者接收超时中断)。

DMA的循环模式

关于DMA循环模式,我也写过说明:

GD32F130之DMA_哈士奇去买菜的博客-CSDN博客

使用循环模式的好处是,当指定长度的串口数据通过DMA接收完成后,DMA硬件自动重新设置传输长度,同时开启下一个接收过程,当接收缓冲区满了后,会自动从接收缓冲区数组的开始存储。接收过程不会停止,因为DMA通道总是处于使能状态。否则,我们需要在每次传输完成后,通过代码禁止DMA通道,配置下一次的传输长度,最后使能通道,而在这个过程,需要CPU的介入,最严重的问题是,可能错过串口数据的接收导致丢数据。

循环队列缓冲区

循环队列就是将数组的首位在逻辑上连接起来,臆造成环形形态。

如下图所示,假设DMA接收缓冲区定义为字节数组DMA_buf,长度为6,用DMA_RX_BUF_SIZE表示。把接收缓冲区当做一个循环队列来使用:定义2个循环队列头指针和尾指针数组索引变量front和rear,用于指示当前接收的串口数据在DMA接收缓冲数组中的起始和结束位置。

  • front初始化为0,且在处理收到的串口数据包时更新:每次取一个队列字节数据后,更新为:front = (front+1)%DMA_RX_BUF_SIZE。软件从缓冲区取数据实现了出队操作。
  • 每次IDLE中断时,代表收到一包数据,在中断中可以计算出rear的值,rear=DMA_RX_BUF_SIZE -  DMA通道的剩余传输长度CNT寄存器的值。DMA硬件将串口数据放到缓冲区实现了入队操作。
  • 有了front和rear,就可以计算出本次接收到的数据的长度:len = (DMA_RX_BUF_SIZE  - front + rear) % DMA_RX_BUF_SIZE 。同时需要注意,采用这种计算接收长度的算法时,能正确识别的最大长度为缓冲区长度-1,因为可以发现,当接收长度刚好等于DMA_RX_BUF_SIZE  时,计算出的长度为0。所以如果你的应用层最大的数据报文长度为MAX_LEN,则应该将接收缓冲区数组长度定义为DMA_RX_BUF_SIZE  = MAX_LEN+1。另外,由于数据处理和接收都是在同一个缓冲区进行的,所以如果存在处理不及时的情况(处理慢接收快)则新接收的数据可能会覆盖正在处理的数据导致错乱,因此在这种情况下建议将接收缓冲区扩大为2倍,这样接收缓冲数组长度为DMA_RX_BUF_SIZE  = (MAX_LEN+1)*2。

图(a):现在接收到了3个字符(A,B,C)

图(b):现在接收到了2个字符(h,g)

图(c):现在接收到了3个字符(T,N,Z)

USART+DMA+循环队列接收不定长数据_第1张图片

代码实现:GD32F303的USART0+keil

#include "gd32f30x.h"
#include 
#include 
#include 


void RCU_config(void)
{
	rcu_periph_clock_enable(RCU_GPIOA);
	rcu_periph_clock_enable(RCU_GPIOB);
	rcu_periph_clock_enable(RCU_USART0);
	rcu_periph_clock_enable(RCU_DMA0);
}


void NVIC_config(void)
{
	nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);
	nvic_irq_enable(USART0_IRQn,1,0);
}


void GPIO_config(void)
{
	//PA9 USART0_Tx 
    gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
    //PA10 USART0_Rx 
    gpio_init(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
}

#define USART0_DMA_TXBUF_SIZE  100
#define USART0_DMA_RXBUF_SIZE  100
uint8_t USART0_DMA_txbuf[USART0_DMA_TXBUF_SIZE];
uint8_t USART0_DMA_rxbuf[USART0_DMA_RXBUF_SIZE];

void USART0_config(void)
{
	usart_deinit(USART0);
    usart_baudrate_set(USART0, 115200U);
	
	//--接收超时设置
	usart_receiver_timeout_threshold_config(USART0,100);
	usart_interrupt_enable(USART0,USART_INT_RT);
	usart_receiver_timeout_enable(USART0);
	
	//=====配置USART使用的DMA通道======
	//USART0_TX:DMA0_CH3
	dma_parameter_struct dma_init_struct;  //DMA初始化结构体
	dma_deinit(DMA0, DMA_CH3);        //复位DMA通道为默认配置
	dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;   //传输方向
	dma_init_struct.memory_addr = (uint32_t)USART0_DMA_txbuf; //存储器基地址
	dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; //增量式存储器地址生成模式
	dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;    //存储器单个数据宽度
	dma_init_struct.number = 0;                             //传输长度
	dma_init_struct.periph_addr = (uint32_t)&(USART_DATA(USART0)); //外设基地址(寄存器地址)
	dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; //增量式外设地址生成模式
	dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT; //外设单个数据宽度
	dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;       //通道的优先级
	dma_init(DMA0, DMA_CH3, &dma_init_struct);               //进行配置
	dma_circulation_disable(DMA0, DMA_CH3);          //配置循环传输模式(单次模式和循环模式)
	//dma_interrupt_enable(DMA0,DMA_CH3,DMA_INT_FTF);  //DMA中断使能
	//dma_channel_enable(DMA0, DMA_CH3);               //使能DMA通道
	
	
	//USART0_RX:DMA0_CH4
	dma_deinit(DMA0, DMA_CH4);        //复位DMA通道为默认配置
	dma_init_struct.direction = DMA_PERIPHERAL_TO_MEMORY;   //传输方向
	dma_init_struct.memory_addr = (uint32_t)USART0_DMA_rxbuf; //存储器基地址
	dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; //增量式存储器地址生成模式
	dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;    //存储器单个数据宽度
	dma_init_struct.number = USART0_DMA_RXBUF_SIZE;          //传输长度
	dma_init_struct.periph_addr = (uint32_t)&(USART_DATA(USART0)); //外设基地址(寄存器地址)
	dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; //增量式外设地址生成模式
	dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT; //外设单个数据宽度
	dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;       //通道的优先级
	dma_init(DMA0, DMA_CH4, &dma_init_struct);               //进行配置
	dma_circulation_enable(DMA0, DMA_CH4);          //配置循环传输模式(单次模式和循环模式)
	//dma_interrupt_enable(DMA0,DMA_CH4,DMA_INT_FTF);  //DMA中断使能
	dma_channel_enable(DMA0, DMA_CH4);               //使能DMA通道
	
	//=====使能串口的DMA发送和接收=====
	usart_dma_transmit_config(USART0, USART_DENT_ENABLE); //使能串口的DMA发送
	usart_dma_receive_config(USART0,USART_DENR_ENABLE);  //使能串口的DMA接收
	
	
    usart_receive_config(USART0, USART_RECEIVE_ENABLE);
    usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
    usart_enable(USART0);
	
}


void USART0_printf(const char*format , ...)
{
	int tx_len;
	va_list args;
	
	//如果dma传输通道是使能的,且传输没完成
	while((DMA_CHCTL(DMA0,DMA_CH3)&0x01) &&  (!dma_flag_get(DMA0,DMA_CH3,DMA_FLAG_FTF)));  //等待DMA传输完成
	dma_flag_clear(DMA0,DMA_CH3,DMA_FLAG_FTF);       //清除传输完成标志
	
	va_start(args,format);
	tx_len = vsnprintf((char*)USART0_DMA_txbuf,USART0_DMA_TXBUF_SIZE,format,args);
	va_end(args);
	
	if(tx_len>0)
	{
		dma_channel_disable(DMA0,DMA_CH3);     //关闭DMA通道,这样才能设置传输长度
		dma_transfer_number_config(DMA0,DMA_CH3,tx_len); //设置本次DMA传输长度
		dma_channel_enable(DMA0,DMA_CH3);     //使能USART0_TX使用的DMA通道,开始DMA传输		
	}
}


volatile uint32_t USART0_get_msg=0;
volatile uint32_t USART0_rear=0;    
volatile uint32_t USART0_front=0;   

int main(void)
{
    RCU_config();
	NVIC_config();
	GPIO_config();
	USART0_config();
	
	
    while(1)
    {
		if(USART0_get_msg)
		{
			USART0_get_msg=0;
			
			uint32_t len = (USART0_DMA_RXBUF_SIZE - USART0_front + USART0_rear)%USART0_DMA_RXBUF_SIZE;//本次消息长度
			USART0_printf("\nlen=%u:",len);
			//打印出收到的字符
			for(uint32_t i=0;i

你可能感兴趣的:(GD32F130开发笔记,FreeRTOS学习笔记,stm32,单片机,gd32)