关于RS485通讯中使用STM32串口以DMA方式发送数据丢失字节的问题

1、开发平台

计算机操作系统:WIN7 64位;

开发环境:Keil MDK 5.14;

MCU:STM32F407ZET6;

STM32F4xx固件库:STM32F4xx_DSP_StdPeriph_Lib_V1.4.0;

串口调试助手;

2、问题描述

    在测试用STM32F4xx芯片的串口USART1以DMA方式进行RS485收发通讯时,出现数据字节丢失的现象,一般丢失1~2个字节。

    出现问题时测试的简单收发机制:使能串口USART1的DMA收发功能,开启了DMA发送完成中断和USART1空闲中断。通过串口调试助手发送N个字节给MCU,当MCU产生USART1空闲中断时,在USART1空闲中断服务程序中将DMA接收到的N个字节数据从接收缓存拷贝到发送缓存,准备好数据后,RS485切换为发送模式,通过启动一次DMA发送,将N个字节数据原样回送到串口调试助手。最后,在DMA发送完成中断服务程序中判断到有DMA发送完成标志TCIF7置位时,立即将RS485再次切换为接收模式。

3、原因分析

        在STM32F4xx英文参考手册(RM0090)中,USART章节的使用DMA发送小节给出了如下时序图:

        由图可见,当DMA将第3个字节Frame 3写到USART数据寄存器USART_DR时,TX线上才刚准备出现第2个字节Frame 2的时序,并且DMA发送完成中断标志在TX线还未出现第2个字节Frame 2时序时就由硬件置1了,所以,如果软件中在DMA发送完成中断服务程序中检测到DMA TCIF标志置1后马上将RS485切换为接收模式,则后面的字节数据将会丢失。

        所以,需要让数据字节不丢失的话,必须让所有字节(包括字节的停止位)在TX线上稳定发送完成后,才能将RS485切换为接收模式。

4、解决方法

        如上图所示,有一个关键点是:当所有字节(包括字节的停止位)在TX线上稳定发送完成后,串口发送完成标志(TC flag)置1。所以,有两个解决方法:

      方法一:用DMA发送完成中断,不用USART1发送完成中断。在DMA发送完成中断服务程序中检测到有TCIF7置1时,再等待USART1发送完成标志TC置1,直到USART1发送完成标志TC置1后,清零USART1发送完成标志TC,然后再将RS485切换为接收模式。

      方法二:用USART1发送完成中断,不用DMA发送完成中断。在USART1中断服务程序USART1_IRQHandler()中,检测到有USART1发送完成标志TC置1时,清零USART1发送完成标志TC,并且要清零DMA发送完成标志DMA_FLAG_TCIF7,最好同时清零DMA_FLAG_FEIF7、DMA_FLAG_DMEIF7、DMA_FLAG_TEIF7 、DMA_FLAG_HTIF7,然后再将RS485切换为接收模式。

5、参考源代码

Usart.h头文件

/*----------------------------------------------------------------------------------------------------
*Copyright: SXD Tech. Co., Ltd. 
*开发 环境: Keil MDK 5.14 && STM32F407ZET6
*文件 名称: USART串行通信驱动头文件  	       	 		
*作     者: 顺信德
*版     本: V1.0
*日     期: 2018-2-6
*说     明:          
*修改 日志: (1)	
----------------------------------------------------------------------------------------------------*/
#ifndef _USART_H_
#define _USART_H_

#include "Global.h" 

/*---------------------------------------------宏定义(S)---------------------------------------------*/
#define RS485_Recv();	{PFout(11)=0;}	//SP485接收模式,低电平有效
#define RS485_Send(); 	{PFout(11)=1;}	//SP485发送模式,高电平有效

#define USART1_SEND_MAXLEN	512 /*串口1最大发送字节长度*/
#define USART1_RECV_MAXLEN	512 /*串口1最大接收字节长度*/
/*---------------------------------------------宏定义(E)---------------------------------------------*/


/*--------------------------------------------端口定义(S)--------------------------------------------*/

/*--------------------------------------------端口定义(E)--------------------------------------------*/


/*--------------------------------------------变量声明(S)--------------------------------------------*/
extern u32 G_u32RS485BaudRate;				//RS485通讯波特率
extern u8 G_u8Usart1SendBuf[USART1_SEND_MAXLEN];	//发送数据缓冲区
extern u8 G_u8Usart1RecvBuf[USART1_RECV_MAXLEN];	//接收数据缓冲区	
extern u16 G_u16CommRecvLen;		                //通讯接收的一帧数据长度
/*--------------------------------------------变量声明(E)--------------------------------------------*/


/*--------------------------------------------函数声明(S)--------------------------------------------*/
extern void USART1_Init(u32 baud);	                                                //USART1串口初始化
extern void USART_DMA_SendStart(DMA_Stream_TypeDef *DMA_Streamx, u16 m_u16SendCnt);	//串口USART1启动一次DMA传输
extern void USART1_DMA_SendNByte(u8 *m_pSendBuf, u16 m_u16SendCnt);			//串口USART1以DMA方式发送多字节
/*--------------------------------------------函数声明(E)--------------------------------------------*/

#endif
Usart.c源文件

方法一:用DMA发送完成中断

/*----------------------------------------------------------------------------------------------------
*Copyright: SXD Tech. Co., Ltd. 
*开发 环境: Keil MDK 5.14 && STM32F407ZET6
*文件 名称: USART串行通信驱动源文件  	       	 		
*作     者: 顺信德
*版     本: V1.0
*日     期:	2018-2-6
*说     明:          
*修改 日志:	(1)	
----------------------------------------------------------------------------------------------------*/
#include "Usart.h"	 

/*--------------------------------------------变量定义(S)--------------------------------------------*/
u32 G_u32RS485BaudRate = 9600;					//RS485通讯波特率
u8 G_u8Usart1SendBuf[USART1_SEND_MAXLEN]={0,};	//发送数据缓冲区
u8 G_u8Usart1RecvBuf[USART1_RECV_MAXLEN]={0,};	//接收数据缓冲区
u16 G_u16CommRecvLen=0;			//通讯接收的一帧数据长度
/*--------------------------------------------变量定义(E)--------------------------------------------*/


/*--------------------------------------------函数声明(S)--------------------------------------------*/
void USART1_Init(u32 baud);														//USART1串口初始化
void USART_DMA_SendStart(DMA_Stream_TypeDef *DMA_Streamx, u16 m_u16SendCnt);	//串口USART1启动一次DMA传输
void USART1_DMA_SendNByte(u8 *m_pSendBuf, u16 m_u16SendCnt);					//串口USART1以DMA方式发送多字节
/*--------------------------------------------函数声明(E)--------------------------------------------*/

/*---------------------------------------------------------------------------------------------------- 
*函数名称:void USART1_Init(u32 baud)
*函数功能:USART1串口初始化函数  
*入口参数:u32 baud - 波特率(单位bps)
*出口参数:无
*说    明:用于RS485通信;
----------------------------------------------------------------------------------------------------*/
void USART1_Init(u32 baud)
{
    GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	DMA_InitTypeDef  DMA_InitStructure;
	u16 mid_u16RetryCnt = 0;
	
	USART_DeInit(USART1);
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); 		//使能GPIOA时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);		//使能USART1时钟
 
	//USART1对应引脚复用映射
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); 	//GPIOA9复用为USART1_TX
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); 	//GPIOA10复用为USART1_RX
	
	//USART1端口配置
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; 	//GPIOA9与GPIOA10
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;				//复用功能
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;			//速度25MHz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; 				//推挽输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; 				//上拉
	GPIO_Init(GPIOA, &GPIO_InitStructure); 						//初始化PA9,PA10

    //USART1初始化设置
	USART_InitStructure.USART_BaudRate = baud;					//波特率设置
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;	//字长为8位数据格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;		//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;			//无奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;					//收发模式
    USART_Init(USART1, &USART_InitStructure); 					//初始化USART1
	
    USART_Cmd(USART1, ENABLE);  								//使能USART1 
	
	USART_ClearFlag(USART1, USART_FLAG_TC); //清除发送完成标志	
	while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);	//等待空闲帧发送完成后再清零发送完成标志
	USART_ClearFlag(USART1, USART_FLAG_TC);	//清除发送完成标志
	 
	USART_ITConfig(USART1, USART_IT_TC, DISABLE);				//禁止USART1传输完成中断
    USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);				//禁止USART1接收不为空中断
	USART_ITConfig(USART1, USART_IT_TXE, DISABLE);				//禁止USART1发送空中断
	USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);				//开启USART1空闲中断 
		 
    //USART1 NVIC配置  
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;			//串口1中断通道  
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;		//抢占优先级3  
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;       	//子优先级3  
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         	//IRQ通道使能  
    NVIC_Init(&NVIC_InitStructure); 
  
    USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);  			//使能串口1的DMA发送     
    USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);  			//使能串口1的DMA接收  
    
    // - USART1发送DMA配置
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);			//DMA2时钟使能
	
    DMA_DeInit(DMA2_Stream7);  
    while ((DMA_GetCmdStatus(DMA2_Stream7) != DISABLE) && (mid_u16RetryCnt++ < 500));	//等待DMA可配置   
    //配置DMA2_Stream7 
    DMA_InitStructure.DMA_Channel = DMA_Channel_4;  						//通道选择  
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR;			//DMA外设地址  
    DMA_InitStructure.DMA_Memory0BaseAddr = (u32)G_u8Usart1SendBuf;			//DMA 存储器0地址  
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;					//存储器到外设模式  
    DMA_InitStructure.DMA_BufferSize = USART1_SEND_MAXLEN;					//数据传输量   
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;		//外设非增量模式  
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;					//存储器增量模式  
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;	//外设数据长度:8位  
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;			//存储器数据长度:8位  
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;							//使用普通模式   
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;					//中等优先级  
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;           
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;  
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;				//存储器突发单次传输  
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;		//外设突发单次传输  
    DMA_Init(DMA2_Stream7, &DMA_InitStructure);								//初始化DMA Stream  

    //DMA2_Stream7的NVIC配置    
    NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream7_IRQn;    
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;    
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;    
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;    
    NVIC_Init(&NVIC_InitStructure);
	
	DMA_ClearITPendingBit(DMA2_Stream7, DMA_IT_TCIF7);	//清除DMA发送完成中断标志
    DMA_ITConfig(DMA2_Stream7, DMA_IT_TC, ENABLE);		//使能DMA发送完成中断

	DMA_Cmd(DMA2_Stream7, ENABLE);  //使能DMA2_Stream7
  
    // - USART1接收DMA配置 
	mid_u16RetryCnt = 0;
    DMA_DeInit(DMA2_Stream5); 		
    while ((DMA_GetCmdStatus(DMA2_Stream5) != DISABLE) && (mid_u16RetryCnt++ < 500));	//等待DMA可配置   
    //配置DMA2_Stream5  
    DMA_InitStructure.DMA_Channel = DMA_Channel_4;  						//通道选择  
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR;			//DMA外设地址  
    DMA_InitStructure.DMA_Memory0BaseAddr = (u32)G_u8Usart1RecvBuf;			//DMA 存储器0地址  
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;					//外设到存储器模式  
    DMA_InitStructure.DMA_BufferSize = USART1_RECV_MAXLEN;					//数据传输量   
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;		//外设非增量模式  
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;					//存储器增量模式  
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;	//外设数据长度:8位  
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;			//存储器数据长度:8位  
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;							//使用普通模式   
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;					//中等优先级  
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;           
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;  
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;				//存储器突发单次传输  
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;		//外设突发单次传输  
    DMA_Init(DMA2_Stream5, &DMA_InitStructure);								//初始化DMA Stream  
	
    DMA_Cmd(DMA2_Stream5, ENABLE);  //使能DMA2_Stream5      
}

/*-------------------------------------------------------------------------------------- 
函数名称:void USART_DMA_SendStart(DMA_Stream_TypeDef *DMA_Streamx, u16 m_u16SendCnt)
函数功能:串口USART1启动一次DMA传输函数  
入口参数:DMA_Stream_TypeDef DMA_Streamx - DMA数据流(DMA1_Stream0~7/DMA2_Stream0~7);
		 u16 m_u16SendCnt - 待传输数据字节数
出口参数:无
说    明:无
---------------------------------------------------------------------------------------*/
void USART_DMA_SendStart(DMA_Stream_TypeDef *DMA_Streamx, u16 m_u16SendCnt)  
{    
	u16 l_u16RetryCnt = 0;
	
    DMA_Cmd(DMA_Streamx, DISABLE);                      //关闭DMA传输           
    while ((DMA_GetCmdStatus(DMA_Streamx) != DISABLE) && (l_u16RetryCnt++ < 500));	//等待DMA可配置	
    DMA_SetCurrDataCounter(DMA_Streamx, m_u16SendCnt);  //数据传输量 	   
    DMA_Cmd(DMA_Streamx, ENABLE);                      	//开启DMA传输   
}        

/*-------------------------------------------------------------------------------------- 
函数名称:void USART1_DMA_SendNByte(u8 *m_pSendBuf, u16 m_u16SendCnt) 
函数功能:串口USART1以DMA方式发送多字节函数  
入口参数:u8 *m_pSendBuf - 待发送数据缓存, u16 m_u16SendCnt - 待发送数据个数
出口参数:无
说    明:无
---------------------------------------------------------------------------------------*/  
void USART1_DMA_SendNByte(u8 *m_pSendBuf, u16 m_u16SendCnt)  
{    
	memcpy(G_u8Usart1SendBuf, m_pSendBuf, m_u16SendCnt);	  
    USART_DMA_SendStart(DMA2_Stream7, m_u16SendCnt); //启动一次DMA传输      
}  
  
/*-------------------------------------------------------------------------------------- 
函数名称:void DMA2_Stream7_IRQHandler(void) 
函数功能:串口USART1以DMA方式发送完成中断服务程序  
入口参数:无
出口参数:无
说    明:无
---------------------------------------------------------------------------------------*/
void DMA2_Stream7_IRQHandler(void)  
{    
    if(DMA_GetFlagStatus(DMA2_Stream7, DMA_FLAG_TCIF7) != RESET)	//DMA发送完成?  
    {   
		DMA_ClearFlag(DMA2_Stream7, DMA_FLAG_TCIF7 | DMA_FLAG_FEIF7 | 
					  DMA_FLAG_DMEIF7 | DMA_FLAG_TEIF7 | DMA_FLAG_HTIF7); 	//清除标志位			
		
		while(!USART_GetFlagStatus(USART1, USART_FLAG_TC));	//等待USART1发送完成标志TC置1
		USART_ClearFlag(USART1, USART_FLAG_TC); 	//清除发送完成标志
		
		RS485_Recv();		//切换为RS485接收模式		
    }  
}

/*-------------------------------------------------------------------------------------- 
函数名称:void USART1_IRQHandler(void)
函数功能:USART串口1中断服务程序  
入口参数:无
出口参数:无
说    明:无
---------------------------------------------------------------------------------------*/
void USART1_IRQHandler(void)  
{  
    u16 l_u16Temp = 0;
	
    if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)	//若有空闲中断  
    {  
        DMA_Cmd(DMA2_Stream5, DISABLE); //关闭DMA2_Stream5,防止处理期间有数据 
		
		DMA_ClearFlag(DMA2_Stream5, DMA_FLAG_TCIF5 | DMA_FLAG_FEIF5 | 
					  DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5); 	//清除标志位		
  
		//清除USART总线空闲中断标志(只要读USART1->SR和USART1->DR即可)
		l_u16Temp = USART1->SR; 		
		l_u16Temp = USART1->DR;
          
        G_u16CommRecvLen = USART1_RECV_MAXLEN - DMA_GetCurrDataCounter(DMA2_Stream5); 	//求出接收到数据的字节数 
		if(G_u16CommRecvLen <= USART1_RECV_MAXLEN)
		{
			RS485_Send();		//RS485发送模式
			USART1_DMA_SendNByte(G_u8Usart1RecvBuf, G_u16CommRecvLen);	//回送接收到的数据
		}
          
        DMA_SetCurrDataCounter(DMA2_Stream5, USART1_RECV_MAXLEN);  //设置传输数据长度
        DMA_Cmd(DMA2_Stream5, ENABLE);     //使能DMA2_Stream5  
    } 	
}

方法二:用USART1发送完成中断

/*----------------------------------------------------------------------------------------------------
*Copyright: SXD Tech. Co., Ltd. 
*开发 环境: Keil MDK 5.14 && STM32F407ZET6
*文件 名称: USART串行通信驱动源文件  	       	 		
*作     者: 顺信德
*版     本: V1.0
*日     期:	2018-2-6
*说     明:          
*修改 日志:	(1)	
----------------------------------------------------------------------------------------------------*/
#include "Usart.h"	 

/*--------------------------------------------变量定义(S)--------------------------------------------*/
u32 G_u32RS485BaudRate = 9600;					//RS485通讯波特率
u8 G_u8Usart1SendBuf[USART1_SEND_MAXLEN]={0,};	//发送数据缓冲区
u8 G_u8Usart1RecvBuf[USART1_RECV_MAXLEN]={0,};	//接收数据缓冲区
u16 G_u16CommRecvLen=0;							//通讯接收的一帧数据长度
/*--------------------------------------------变量定义(E)--------------------------------------------*/


/*--------------------------------------------函数声明(S)--------------------------------------------*/
void USART1_Init(u32 baud);														//USART1串口初始化
void USART_DMA_SendStart(DMA_Stream_TypeDef *DMA_Streamx, u16 m_u16SendCnt);	//串口USART1启动一次DMA传输
void USART1_DMA_SendNByte(u8 *m_pSendBuf, u16 m_u16SendCnt);					//串口USART1以DMA方式发送多字节
/*--------------------------------------------函数声明(E)--------------------------------------------*/

/*---------------------------------------------------------------------------------------------------- 
*函数名称:void USART1_Init(u32 baud)
*函数功能:USART1串口初始化函数  
*入口参数:u32 baud - 波特率(单位bps)
*出口参数:无
*说    明:用于RS485通信;
----------------------------------------------------------------------------------------------------*/
void USART1_Init(u32 baud)
{
    GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	DMA_InitTypeDef  DMA_InitStructure;
	u16 mid_u16RetryCnt = 0;
	
	USART_DeInit(USART1);
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); 		//使能GPIOA时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);		//使能USART1时钟
 
	//USART1对应引脚复用映射
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); 	//GPIOA9复用为USART1_TX
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); 	//GPIOA10复用为USART1_RX
	
	//USART1端口配置
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; 	//GPIOA9与GPIOA10
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;				//复用功能
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;			//速度25MHz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; 				//推挽输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; 				//上拉
	GPIO_Init(GPIOA, &GPIO_InitStructure); 						//初始化PA9,PA10

    //USART1初始化设置
	USART_InitStructure.USART_BaudRate = baud;					//波特率设置
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;	//字长为8位数据格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;		//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;			//无奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;					//收发模式
    USART_Init(USART1, &USART_InitStructure); 					//初始化USART1
	
    USART_Cmd(USART1, ENABLE);  								//使能USART1 
	
	USART_ClearFlag(USART1, USART_FLAG_TC); //清除发送完成标志	
	while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);	//等待空闲帧发送完成后再清零发送完成标志
	USART_ClearFlag(USART1, USART_FLAG_TC);	//清除发送完成标志
	
	USART_ITConfig(USART1, USART_IT_TC, ENABLE);				//使能USART1传输完成中断 
    USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);				//禁止USART1接收不为空中断
	USART_ITConfig(USART1, USART_IT_TXE, DISABLE);				//禁止USART1发送空中断
	USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);				//开启USART1空闲中断 
		 
    //USART1 NVIC配置  
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;			//串口1中断通道  
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;		//抢占优先级3  
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;       	//子优先级3  
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         	//IRQ通道使能  
    NVIC_Init(&NVIC_InitStructure); 
  
    USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);  			//使能串口1的DMA发送     
    USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);  			//使能串口1的DMA接收  
    
    // - USART1发送DMA配置
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);			//DMA2时钟使能
	
    DMA_DeInit(DMA2_Stream7);  
    while ((DMA_GetCmdStatus(DMA2_Stream7) != DISABLE) && (mid_u16RetryCnt++ < 500));			//等待DMA可配置   
    //配置DMA2_Stream7 
    DMA_InitStructure.DMA_Channel = DMA_Channel_4;  						//通道选择  
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR;			//DMA外设地址  
    DMA_InitStructure.DMA_Memory0BaseAddr = (u32)G_u8Usart1SendBuf;			//DMA 存储器0地址  
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;					//存储器到外设模式  
    DMA_InitStructure.DMA_BufferSize = USART1_SEND_MAXLEN;					//数据传输量   
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;		//外设非增量模式  
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;					//存储器增量模式  
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;	//外设数据长度:8位  
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;			//存储器数据长度:8位  
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;							//使用普通模式   
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;					//中等优先级  
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;           
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;  
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;				//存储器突发单次传输  
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;		//外设突发单次传输  
    DMA_Init(DMA2_Stream7, &DMA_InitStructure);								//初始化DMA Stream  

	DMA_Cmd(DMA2_Stream7, ENABLE);  //使能DMA2_Stream7
  
    // - USART1接收DMA配置 
	mid_u16RetryCnt = 0;
    DMA_DeInit(DMA2_Stream5); 		
    while ((DMA_GetCmdStatus(DMA2_Stream5) != DISABLE) && (mid_u16RetryCnt++ < 500));	//等待DMA可配置   
    //配置DMA2_Stream5  
    DMA_InitStructure.DMA_Channel = DMA_Channel_4;  						//通道选择  
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR;			//DMA外设地址  
    DMA_InitStructure.DMA_Memory0BaseAddr = (u32)G_u8Usart1RecvBuf;			//DMA 存储器0地址  
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;					//外设到存储器模式  
    DMA_InitStructure.DMA_BufferSize = USART1_RECV_MAXLEN;					//数据传输量   
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;		//外设非增量模式  
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;					//存储器增量模式  
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;	//外设数据长度:8位  
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;			//存储器数据长度:8位  
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;							//使用普通模式   
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;					//中等优先级  
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;           
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;  
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;				//存储器突发单次传输  
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;		//外设突发单次传输  
    DMA_Init(DMA2_Stream5, &DMA_InitStructure);								//初始化DMA Stream  
	
    DMA_Cmd(DMA2_Stream5, ENABLE);  //使能DMA2_Stream5      
}

/*-------------------------------------------------------------------------------------- 
函数名称:void USART_DMA_SendStart(DMA_Stream_TypeDef *DMA_Streamx, u16 m_u16SendCnt)
函数功能:串口USART1启动一次DMA传输函数  
入口参数:DMA_Stream_TypeDef DMA_Streamx - DMA数据流(DMA1_Stream0~7/DMA2_Stream0~7);
		 u16 m_u16SendCnt - 待传输数据字节数
出口参数:无
说    明:无
---------------------------------------------------------------------------------------*/
void USART_DMA_SendStart(DMA_Stream_TypeDef *DMA_Streamx, u16 m_u16SendCnt)  
{    
	u16 l_u16RetryCnt = 0;
	
    DMA_Cmd(DMA_Streamx, DISABLE);                      //关闭DMA传输           
    while ((DMA_GetCmdStatus(DMA_Streamx) != DISABLE) && (l_u16RetryCnt++ < 500));	//等待DMA可配置	
    DMA_SetCurrDataCounter(DMA_Streamx, m_u16SendCnt);  //数据传输量 	   
    DMA_Cmd(DMA_Streamx, ENABLE);                      	//开启DMA传输   
}        

/*-------------------------------------------------------------------------------------- 
函数名称:void USART1_DMA_SendNByte(u8 *m_pSendBuf, u16 m_u16SendCnt) 
函数功能:串口USART1以DMA方式发送多字节函数  
入口参数:u8 *m_pSendBuf - 待发送数据缓存, u16 m_u16SendCnt - 待发送数据个数
出口参数:无
说    明:无
---------------------------------------------------------------------------------------*/  
void USART1_DMA_SendNByte(u8 *m_pSendBuf, u16 m_u16SendCnt)  
{    
	memcpy(G_u8Usart1SendBuf, m_pSendBuf, m_u16SendCnt);	  
    USART_DMA_SendStart(DMA2_Stream7, m_u16SendCnt); //启动一次DMA传输      
}

/*-------------------------------------------------------------------------------------- 
函数名称:void USART1_IRQHandler(void)
函数功能:USART串口1中断服务程序  
入口参数:无
出口参数:无
说    明:无
---------------------------------------------------------------------------------------*/
void USART1_IRQHandler(void)  
{  
    u16 l_u16Temp = 0;
	
    if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)	//若有空闲中断  
    {  
        DMA_Cmd(DMA2_Stream5, DISABLE); //关闭DMA2_Stream5,防止处理期间有数据     
		DMA_ClearFlag(DMA2_Stream5, DMA_FLAG_TCIF5 | DMA_FLAG_FEIF5 | 
					  DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5);//清零标志位 		
  
		//清除USART总线空闲中断标志(只要读USART1->SR和USART1->DR即可)
		l_u16Temp = USART1->SR; 		
		l_u16Temp = USART1->DR;
          
        G_u16CommRecvLen = USART1_RECV_MAXLEN - DMA_GetCurrDataCounter(DMA2_Stream5); 	//求出接收到数据的字节数 
		if(G_u16CommRecvLen <= USART1_RECV_MAXLEN)
		{
			RS485_Send();		//RS485发送模式
			USART1_DMA_SendNByte(G_u8Usart1RecvBuf, G_u16CommRecvLen);
		}
          
        DMA_SetCurrDataCounter(DMA2_Stream5, USART1_RECV_MAXLEN);  //设置传输数据长度
        DMA_Cmd(DMA2_Stream5, ENABLE);     //使能DMA2_Stream5  
    } 

    if(USART_GetITStatus(USART1, USART_IT_TC) != RESET)	//若有发送完成中断  
    {  
		USART_ClearITPendingBit(USART1, USART_IT_TC);	//清除USART1发送完成中断标志
		DMA_ClearFlag(DMA2_Stream7, DMA_FLAG_TCIF7 | DMA_FLAG_FEIF7 | 
					  DMA_FLAG_DMEIF7 | DMA_FLAG_TEIF7 | DMA_FLAG_HTIF7);//清零标志位
			
		RS485_Recv();		//切换为RS485接收模式
    }	
}

6、声明

    本程序的收发机制只是简单的处理机制,只是为了说明解决数据丢失字节问题的方法,对进行快速大数据通讯时会出现乱码。所以,用于实际项目中,需对此程序的收发处理机制进行重新设计。两种方法中,个人认为方法二更好,因为方法一在中断里面等待白白耗费了时间。

    希望可以帮助到遇到同样问题的朋友,共同进步!同时,如有错漏之处,欢迎评论指正,不胜感激!转载此文请注明出处,谢谢!

  • 26
    点赞
  • 91
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值