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

    希望可以帮助到遇到同样问题的朋友,共同进步!同时,如有错漏之处,欢迎评论指正,不胜感激!转载此文请注明出处,谢谢!
————————————————
版权声明:本文为CSDN博主「顺信德」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_30691145/article/details/79481436

 

以下是一个使用DMA方式传输ADC值的示例代码,用于在STM32微控制器上使用RS485通信协议传输ADC值。 ```c #include "stm32f10x.h" #include "stdio.h" #define RS485_GPIO GPIOA #define RS485_USART USART1 #define RS485_TX_PIN GPIO_Pin_9 #define RS485_RX_PIN GPIO_Pin_10 #define RS485_TX_ENABLE_PIN GPIO_Pin_8 #define ADC_GPIO GPIOA #define ADC_PIN GPIO_Pin_0 #define ADC_CHANNEL ADC_Channel_0 ADC_InitTypeDef ADC_InitStructure; DMA_InitTypeDef DMA_InitStructure; USART_InitTypeDef USART_InitStructure; u16 ADC_Value; u8 RS485_TxBuffer[2]; void RCC_Configuration(void) { /* ADC and GPIO Clock Enable */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); /* USART and DMA Clock Enable */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); } void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; /* RS485 GPIO Configuration */ GPIO_InitStructure.GPIO_Pin = RS485_TX_PIN | RS485_RX_PIN | RS485_TX_ENABLE_PIN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(RS485_GPIO, &GPIO_InitStructure); /* ADC GPIO Configuration */ GPIO_InitStructure.GPIO_Pin = ADC_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(ADC_GPIO, &GPIO_InitStructure); } void ADC_Configuration(void) { /* ADC Configuration */ ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStructure); /* Enable ADC DMA */ ADC_DMACmd(ADC1, ENABLE); /* Enable ADC */ ADC_Cmd(ADC1, ENABLE); /* ADC Calibration */ ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); } void DMA_Configuration(void) { /* DMA Configuration */ DMA_DeInit(DMA1_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&(USART1->DR)); DMA_InitStructure.DMA_MemoryBaseAddr = (u32)RS485_TxBuffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize = 2; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel1, &DMA_InitStructure); } void USART_Configuration(void) { /* USART Configuration */ USART_InitStructure.USART_BaudRate = 9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b; 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(RS485_USART, &USART_InitStructure); /* Enable USART */ USART_Cmd(RS485_USART, ENABLE); /* Enable USART Tx Enable Pin */ GPIO_WriteBit(RS485_GPIO, RS485_TX_ENABLE_PIN, Bit_SET); /* Enable USART DMA Tx Request */ USART_DMACmd(RS485_USART, USART_DMAReq_Tx, ENABLE); } void RS485_Send(u8 *data, u16 length) { /* Enable RS485 Tx Enable Pin */ GPIO_WriteBit(RS485_GPIO, RS485_TX_ENABLE_PIN, Bit_RESET); /* Configure DMA */ DMA_Configuration(); /* Load Data into DMA Buffer */ RS485_TxBuffer[0] = data[0]; RS485_TxBuffer[1] = data[1]; /* Enable DMA Channel */ DMA_Cmd(DMA1_Channel1, ENABLE); /* Wait for DMA Transmission to Complete */ while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); /* Disable DMA Channel */ DMA_Cmd(DMA1_Channel1, DISABLE); /* Disable RS485 Tx Enable Pin */ GPIO_WriteBit(RS485_GPIO, RS485_TX_ENABLE_PIN, Bit_SET); } int main(void) { /* RCC Configuration */ RCC_Configuration(); /* GPIO Configuration */ GPIO_Configuration(); /* ADC Configuration */ ADC_Configuration(); /* USART Configuration */ USART_Configuration(); while(1) { /* Wait for ADC Conversion to Complete */ while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); /* Read ADC Value */ ADC_Value = ADC_GetConversionValue(ADC1); /* Convert ADC Value to Data */ RS485_TxBuffer[0] = ADC_Value >> 8; RS485_TxBuffer[1] = ADC_Value & 0xFF; /* Send Data */ RS485_Send(RS485_TxBuffer, 2); /* Delay */ for(u32 i = 0; i < 100000; i++); } } ``` 在上面的代码,我们使用STM32的USART、ADC和DMA模块。使用DMA方式传输数据可以减少CPU的负载,从而提高系统性能。在主循环,我们等待ADC转换完成并读取ADC值。然后,我们将ADC值转换为两个字节的数据,并使用RS485协议将数据发送出去。在发送数据之前,我们需要将RS485的Tx Enable引脚设置为低电平,以启用发送模式。发送完成后,我们将Tx Enable引脚设置为高电平,以禁用发送模式。 请注意,此示例代码仅用于演示目的。在实际应用,您需要根据您的需求进行修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值