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

本文介绍在STM32F4xx使用DMA方式通过USART1进行RS485通信时遇到的数据丢失问题及解决方法。文中详细分析了问题原因,并提供了两种解决方案,帮助开发者避免数据丢失。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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、声明

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

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

### STM32F4 UART1 DMA Transmit Receive Configuration Tutorial #### Using STM32CubeMX to Configure UART1 with DMA on the STM32F407 Development Board To configure UART1 using Direct Memory Access (DMA) for both transmission and reception, follow these guidelines: The STM32F4 series microcontrollers support multiple serial communication interfaces including USARTs that can operate in asynchronous mode. For configuring UART1 specifically with DMA capabilities, one should use STM32CubeMX software which simplifies peripheral setup through a graphical interface[^1]. For setting up UART1 with DMA functionality within STM32CubeMX: - Open STM32CubeMX. - Select the target device as `STM32F407`. - Navigate to the "Connectivity" tab where all available UART/USART peripherals are listed. - Choose `UART1` from this list. Ensure enabling of DMA streams associated with UART1's TX (transmit) and RX (receive). This involves selecting appropriate DMA requests under the NVIC settings section after expanding UART1 properties. Additionally, ensure proper pin assignments by checking GPIO configurations linked to UART1_TX and UART1_RX pins according to your hardware layout. After completing initial configuration via CubeMX, proceed into HAL library-based code generation step provided by the tool itself. Generated initialization functions will handle low-level register setups necessary for establishing functional UART-DMA links automatically when initializing system resources during startup sequence execution. Below is an example snippet demonstrating how generated C source files might look like post-generation concerning UART1_DMA_Init function calls made inside main.c or similar entry point file: ```c /* USER CODE BEGIN Includes */ #include "stm32f4xx_hal.h" /* USER CODE END Includes */ UART_HandleTypeDef huart1; DMA_HandleTypeDef hdma_usart1_tx; DMA_HandleTypeDef hdma_usart1_rx; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_DMA_Init(void); /* Initializes the DMA controller channels used in the application */ static void MX_USART1_UART_Init(void); int main(void){ /* Reset of all peripherals, Initializes the Flash interface and Systick. */ HAL_Init(); /* Configure the system clock */ SystemClock_Config(); /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_DMA_Init(); // Call before any other init routines requiring DMA services MX_USART1_UART_Init(); while(1){ // Application loop here... } } ``` This approach ensures seamless integration between UART operations and direct memory access mechanisms without manual intervention at assembly level programming stages typically required otherwise for such tasks involving complex interactions among different subsystem components present onboard selected MCU models partaking in STMicroelectronics' ARM Cortex-M family lineup. --related questions-- 1. What specific steps must be taken outside of STM32CubeMX once it has been utilized to set up UART1? 2. How does one verify correct operation of UART1 alongside its connected DMA channel(s)? 3. Can you provide examples illustrating common pitfalls encountered while implementing UART communications utilizing DMA features? 4. In what scenarios would disabling/enabling interrupts related to either UART or DMA prove beneficial during development processes centered around embedded systems based upon STM32 platforms?
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值