GD32或STM32(GD32F103RB)串口DMA+IDLE空闲中断

串口通信有几种方式:

1、Rx来一个字节触发一次串口中断,读取字节,同时根据协议判断,属于什么帧,对应要执行什么操作;

void USART1_IRQHandler(void)
{  
	unsigned char	Rec_Data;
	
	if(USART_GetITStatus(USART1, USART_IT_RXNE)!=RESET)
	{
		if(USART_GetFlagStatus(USART1, USART_FLAG_PE)==RESET)//奇偶校验
		{
			Rec_Data= USART_ReceiveData(USART1);
	
			UART1_receive(Rec_Data);
			//UART_receive(com1, Rec_Data);
		}
		else
		{
			Rec_Data= USART_ReceiveData(USART1);//如果出现校验错误,只读UART1_DR,清除PE标志,不保存数据			
		} 
    }
}

void UART1_receive(unsigned char char_buf)
{ 	  
    switch(com1.ptr)
	{
        //-------------------
	    case	'0':
				break;
			
		//-------------------
		 case	'1':
			if(char_buf==DLE)
			{
				com1.ptr ='2';	
				break;
			}
			else
			{
				break;//
			}

		//-------------------
		 case	'2':
			switch(char_buf)
			{
				case STX:				//STX?
			 		com1.ptr ='3';
			 		com1.F_STX=nCLEAR;
					com1.F_resend=CLEAR;
					com1.F_EOT=CLEAR;	
					com1.length=0;
					break;				

				case EOT:				//EOT?
					com1.ptr ='0';
					com1.F_resend=CLEAR;	
					com1.F_EOT=nCLEAR;
					com1.valid = nCLEAR;
					break;

				case ENQ:				//ENQ?
					com1.ptr ='0';
					com1.F_ENQ=nCLEAR;
					com1.valid = nCLEAR;					
					break;
					
				case DLE:
					com1.ptr	='2';
					break;
					
				default:
					com1.ptr='1';
					break;				
			}
				break;

		//-------------------
		 case	'3':
			if(char_buf==DLE)
			{	
				com1.ptr='4';
			}
			else 						//received one data
			{	
				com1.buf[com1.length]=char_buf;//C开始
				com1.bcc^=char_buf;
					
			 	if(com1.length<COMMAX)
			 	{
			 		com1.length++;	
			 	}
			}
			break;
					 			
		//-------------------
		 case	'4':
			switch(char_buf)
			{
				case ETX:			//ETX?
					com1.ptr ='5';
					com1.bcc^=char_buf;	
					com1.F_ETX=nCLEAR;
					break;

				case DLE:			//data=10h?
					com1.ptr ='3';
					com1.buf[com1.length]=char_buf;	
					com1.bcc^=char_buf;
						
					if(com1.length<COMMAX)
					{
						com1.length++;
					}
					break;				
					
			     default:			//found 10 xx(NAK structure error)
					com1.ptr='0';
					com1.F_NAK=nCLEAR;
					break;
			 }
			break;

		//-------------------
		 case '5':						//com1.com1.bcc check					
			com1.F_BCC=nCLEAR;			
				
			if(com1.bcc ==char_buf)
			{
				com1.ptr='0';	
				com1.F_CMD=nCLEAR;
				com1.valid = nCLEAR; 	
				break;
			}	
			else 
			{
				com1.ptr='0';	
				com1.F_NAK=nCLEAR;
				com1.valid = nCLEAR;	
				break;
			}		

		//-------------------
		 default:					//error routine
			break;
		}
}

2、串口FIFO,较少中断触发次数;(有空再展开讲)

3、本文章的主角:采用DMA省去cpu处理,IDLE接收不定长的数据包;以下代码并非直接copy就可用,根据自己的理解实现自己代码。

(1)串口初始化主要完成以下几个事情:

        使能了串口IDLE中断,使能串口收发,使能串口收发DMA;

        DMA配置:收发都使能,禁止收发循环,发送端的DMA中断;

static uint32_t	USART_Num[COM_n]	=	{USART2};
static uint32_t DMA_Num[COM_n] = {DMA0};			//uasrt2 属于DMA0
static rcu_periph_enum DMA_RCU[COM_n] = {RCU_DMA0};			//uasrt2的DMA0时钟

static dma_channel_enum DMA_Chl_Tx[COM_n] = {DMA_CH1};//uasrt2 的发送口 属于DMA0的通道1
static dma_channel_enum DMA_Chl_Rx[COM_n] = {DMA_CH2};//uasrt2 的接收口 属于DMA0的通道2
static IRQn_Type	DMA_IRQn_Tx[COM_n] = {DMA0_Channel1_IRQn};//DMA发送中断入口函数

static uint32_t COM_GPIO_PORT[COM_n] = {GPIOB};
static rcu_periph_enum COM_GPIO_CLK[COM_n] = {RCU_GPIOB};

static rcu_periph_enum USART_PERIPH_CLK[COM_n]     = {RCU_USART2};  //USART模块时钟      
static rcu_periph_enum USART_TX_GPIO_CLK[COM_n]    = {RCU_GPIOB}; //USART_TX引脚所在的GPIO端口时钟 
static rcu_periph_enum USART_RX_GPIO_CLK[COM_n]    = {RCU_GPIOB };  //USART_RX引脚所在的GPIO端口时钟 
//static rcu_periph_enum USART_RS485_GPIO_CLK[COM_n] = {RCU_GPIOB};

static uint32_t USART_TX_PIN[COM_n]    = {GPIO_PIN_10};            //USART_TX 管脚         
static uint32_t USART_RX_PIN[COM_n]    = {GPIO_PIN_11};            //USART_RX 管脚  

static IRQn_Type USART_IRQn[COM_n] = {USART2_IRQn};           //USART中断号 

void init_Uart(void)
{
	USART_Init(COM0, 115200);	    //COM0 指的是数组第一个 配置初始化
	MYDMA_Config(COM0, 168);     	//DMA初始化  
}

void USART_Init(uint8_t uComID, uint32_t uBound)
{
   //使能时钟
	rcu_periph_clock_enable(RCU_AF);
	rcu_periph_clock_enable( USART_PERIPH_CLK[ uComID ] );                       
  rcu_periph_clock_enable( USART_TX_GPIO_CLK[ uComID ] );                     
	rcu_periph_clock_enable( USART_RX_GPIO_CLK[ uComID ] );                                       
   
    //配置USART 的TXD、RXD 复用管脚 
    gpio_init(COM_GPIO_PORT[uComID], GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, USART_TX_PIN[ uComID ]);/* connect port to USARTx_Tx */
    gpio_init(COM_GPIO_PORT[uComID], GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, USART_RX_PIN[uComID]);/* connect port to USARTx_Rx */

	//设置串口工作模式和波特率 
	usart_deinit(USART_Num[uComID]);
	SetUsartPara( USART_Num[uComID], uBound );      

	/* USART DMA enable for transmission and reception */
	usart_receive_config(USART_Num[uComID], USART_RECEIVE_ENABLE);
  usart_transmit_config(USART_Num[uComID], USART_TRANSMIT_ENABLE);
	
  usart_dma_transmit_config(USART_Num[uComID], USART_DENT_ENABLE);
  usart_dma_receive_config(USART_Num[uComID], USART_DENR_ENABLE);
	usart_enable(USART_Num[uComID]);
    	
    //设置USART中断
	usart_interrupt_enable( USART_Num[uComID], USART_INT_IDLE );    //使能了串口空闲接收中断           		 
	usart_interrupt_flag_clear( USART_Num[uComID], USART_INT_FLAG_TBE );               
	usart_interrupt_flag_clear( USART_Num[uComID], USART_INT_FLAG_TC );  
	
	nvic_irq_enable( USART_IRQn[ uComID ], 0U, 3U );//串口中断优先级设置
}

void MYDMA_Config(uint8_t uComID,uint16_t BuffSize)
{
  dma_parameter_struct  dma_init_struct;
  dma_channel_enum  uDMA_RXCHx,uDMA_TXCHx;
	
    IRQn_Type         nvic_irq;
	uint32_t          uDMAx;
	uint32_t          uRecvBuff,uSendBuff;
	uint32_t 		  		usart_periph;
	                                      
	{
		usart_periph = USART_Num[uComID];
		uDMAx = DMA_Num[uComID]; 
		uDMA_TXCHx = DMA_Chl_Tx[uComID];                  //DMA发送模式:CH1 
		uDMA_RXCHx = DMA_Chl_Rx[uComID];                  //DMA接收模式:CH2
		nvic_irq = DMA_IRQn_Tx[uComID];         //DMA发送中断请求
		uRecvBuff = (uint32_t)rx_buffer;          //USART0接收数据缓冲区首地址
		uSendBuff = (uint32_t)tx_buffer;      	   //USART0发送数据缓冲区首地址				
	}
	
	rcu_periph_clock_enable(DMA_RCU[uComID]);                  //使能DMA0时钟
	
    //配置使用DMA接收数据
		dma_deinit(uDMAx, uDMA_RXCHx);                                     
		dma_init_struct.direction = DMA_PERIPHERAL_TO_MEMORY;               	
		dma_init_struct.memory_addr = (uint32_t)uRecvBuff;  		    	
		dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;        	
		dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;    	
		dma_init_struct.number = BuffSize;  

		dma_init_struct.periph_addr = (uint32_t)&USART_DATA(usart_periph);
		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(uDMAx, uDMA_RXCHx, &dma_init_struct);     

		/* configure DMA mode */
		dma_circulation_disable(uDMAx, uDMA_RXCHx);
		dma_memory_to_memory_disable(uDMAx, uDMA_RXCHx);

		usart_dma_receive_config(usart_periph, USART_DENR_ENABLE);          
		dma_channel_enable(uDMAx, uDMA_RXCHx);  

		//=======================================================
		//配置使用DMA发送数据
		dma_deinit(uDMAx, uDMA_TXCHx);                                      
		dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;               	 
		dma_init_struct.memory_addr = (uint32_t)uSendBuff;              	
		dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;        	
		dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;    	
		dma_init_struct.number = BuffSize;  			                	

		dma_init_struct.periph_addr = (uint32_t)&USART_DATA(usart_periph);
		dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
		dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;       	

		dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;             	
		dma_init(uDMAx, uDMA_TXCHx, &dma_init_struct);     
		/* configure DMA mode */
		dma_circulation_disable(uDMAx, uDMA_TXCHx);//禁止DMA循环
		dma_memory_to_memory_disable(uDMAx, uDMA_TXCHx);

		usart_dma_transmit_config(usart_periph, USART_DENT_ENABLE);     	
		dma_interrupt_enable(uDMAx, uDMA_TXCHx, DMA_CHXCTL_FTFIE); //DMA发送中断使能   		
		nvic_irq_enable(nvic_irq, 0U, 3U);  //DMA发送中断DMA0_Channel1_IRQn优先级设置
             
}

(2)中断处理

DMA0_Channel1_IRQHandler,串口发送完数据,后触发的中断;在中断做了两件事:1、清除相应标志位;2、做了一个特殊处理(这个特殊处理完全没有必要):使能了串口的TC中断,目的是需要确保数据已经完全发出,发送完,后拉高485电平(有的方案不需要),进行等待接收;DMA发送中断触发并不代表串口完全已经把数据发出。TC置1,才是已经发出。这里多此一举:讲讲TC和TBE的关系:TBE=1,表示USART_TDATA为空,即可以把你要发的数据给USART_TDATA赋值了,可以理解buf缓存寄存器,但是并没有发出(可能还处在移位寄存器);TC=1,表示TBE为空,移位寄存器也是空,即完全已经发出了。

/*
USART2的发送中断
*/
void DMA0_Channel1_IRQHandler(void)
{
  if(dma_interrupt_flag_get(DMA_Num[COM0], DMA_CH1, DMA_INT_FLAG_FTF))          
  { 
    dma_interrupt_flag_clear(DMA_Num[COM0], DMA_CH1, DMA_INT_FLAG_FTF); 	 
	dma_channel_disable(DMA_Num[COM0], DMA_CH1);//禁止DMA发送中断 已经发完 需要发送的函数会重新开启DMA中断   						 
	usart_interrupt_enable(USART_Num[COM0], USART_INT_TC);   //启动串口TC中断 为什么要与串口发送完中断互动?其实也不用这样操作																									//想确保数据已经移位出去后,开启485引脚,进行等待接收的操作 这样更准确
	} 
}

void USART2_IRQHandler(void)      
{ 
	uint8_t   uDataBuf;
	uint16_t  uLen; 

	uDataBuf = uDataBuf;
	if(RESET != usart_interrupt_flag_get(USART_Num[COM0], USART_INT_FLAG_IDLE)) 
	{ 
		uDataBuf = usart_data_receive(USART_Num[COM0]);  //IDLE的清除:1读SR;2读DR                    
		dma_channel_disable(DMA_Num[COM0], DMA_Chl_Rx[COM0]);  //禁止DMA接收中断                      
		dma_interrupt_flag_clear(DMA_Num[COM0], DMA_Chl_Rx[COM0], DMA_INT_FLAG_FTF);   //清楚标记

		uLen = UART_DMA_SIZE - dma_transfer_number_get(DMA_Num[COM0], DMA_Chl_Rx[COM0]);
		if( uLen < UART_DMA_SIZE )
		{
		g_uRecvLen[COM0] = uLen;   //记录获取多少长度字节                               
		g_uRecvDataOK[COM0] = 0x01;    //标记接收成功                          
		}
		MYDMA_Enable(COM0, 0, UART_DMA_SIZE);    //使能DMA 接收中断                 
	}   

	if(usart_interrupt_flag_get(USART_Num[COM0], USART_INT_FLAG_TBE) == RESET)//始终是RESET因为没有使能TBE中断 虽然TBE置位了
	{
        usart_flag_clear(USART_Num[COM0],USART_FLAG_TC);//清除TC
		usart_interrupt_disable(USART_Num[COM0], USART_INT_TC);
		g_uSendFlag[COM0] = 0x00;	//设置发送完成标志
		//gpio_bit_reset( USART_RS485_GPIO_PORT[COM0], USART_RS485_PIN[COM0] ); //电平拉低 485等待接收
	}
}


/*
这段参考:https://blog.csdn.net/luliplus/article/details/123190671

void USART0_sendStr(const char*msg)
{
	uint32_t i;
	usart_flag_clear(USART0,USART_FLAG_TC);  //为了使用TC,在发送前要先清除TC
	for(i=0;msg[i];i++)
	{
		while(!usart_flag_get(USART0,USART_FLAG_TBE)){}  //等待TBE硬件置1
		usart_data_transmit(USART0,msg[i]);    //向TDATA寄存器写入字节数据
	}
	while(!usart_flag_get(USART0,USART_FLAG_TC)){}  //等待TC硬件置1,代表发送完成
}

*/

(3)使用

//--------------------------------轮询接收
void runUart(void)
{
	unsigned short uiLen;

	if(IS_TIM10MS)
	{
		if( g_uRecvDataOK[COM0] == 0x01) 
		{
			uiLen = g_uRecvLen[COM0];
			g_uRecvDataOK[COM0] = 0x00;
			
			uartHandleFunc(rx_buffer,uiLen);
		}
	}
}

//----------------------------------发送 每次发送的时候会使能DMA因为 发送完中断里面关闭了DMA
SendData(COM0,tx_buffer,Len);

void SendData(unsigned char ucCOM,unsigned char ucData[],unsigned int usDataLen)
{
	unsigned char *pucData;
	unsigned int iTmp2;
	
	switch(ucCOM)
	{
		case COM0:
		{
			pucData = ( unsigned char * )tx_buffer;
			break;
		}
			
		default:
		{
			return;
		}
	}
	
	for( iTmp2 = 0; iTmp2 < usDataLen; iTmp2++ )
	{
		*( pucData + iTmp2 ) = ucData[ iTmp2 ];
	}		
	//发送数据
	UARTDMADataSend( ucCOM, usDataLen );	
}

void UARTDMADataSend(uint8_t ucCOM, uint16_t uLen)            
{	
	uint16_t uTemp; 	
	uint32_t usart_periph;
		
	switch(ucCOM)
	{
		case  COM0:	
			usart_periph = USART_Num[COM0];			
			//uSendBuf = ( uint8_t * )UART0_DMA_TX_SRC;  					//USART0发送数据缓冲区首地址
			break;
						
		case  COM1:
			usart_periph = USART1;		
			//uSendBuf = ( uint8_t * )UART1_DMA_TX_SRC;  					//USART1发送数据缓冲区首地址
			break;
		
		default:
			return;
	}
	
	if(uLen > 0)	
	{	
		//while(g_uSendFlag[ucCOM]) ;                                            //等待上次发送完成		
		g_uSendFlag[ucCOM] = 0x01;                                             //设置准备发送标志
		//gpio_bit_set( USART_RS485_GPIO_PORT[ucCOM], USART_RS485_PIN[ucCOM] );  //设置RS485为发送模式
		
		for(uTemp = 0x00; uTemp < 100; uTemp++) ;
				
		DMA_Enable(ucCOM, 1, uLen);                                          //开启一次DMA传输
		usart_dma_transmit_config(usart_periph, USART_DENT_ENABLE);         //使能串口DMA发送   USART0		
		uLen = 0x00;
	}				
}

void DMA_Enable(uint8_t uComID, uint8_t uMode, uint16_t BufSize)  
{ 
	uint32_t          uDMAx;
	dma_channel_enum  uDMA_CHx;
	
	switch(uComID)
	{
		case  COM0:	
			uDMAx = DMA0;
		  if(uMode == 0) uDMA_CHx = DMA_CH2;                  //DMA接收模式:CH4
			else           uDMA_CHx = DMA_CH1;                  //DMA发送模式:CH3 
			break;
							
		default:
			return;
	}
	
	dma_channel_disable(uDMAx, uDMA_CHx);  					   //关闭DMA的通道X    DMA_CH0
	dma_transfer_number_config(uDMAx, uDMA_CHx, BufSize);	   //DMA通道的DMA缓存的大小  
	dma_channel_enable(uDMAx, uDMA_CHx);           			   //使能DMA通道X
}

  • 9
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值