历史上的今天
返回首页

历史上的今天

今天是:2025年07月27日(星期日)

正在发生

2019年07月27日 | RS485通讯中使用STM32串口以DMA方式发送数据丢失字节的问题

2019-07-27 来源:eefocus

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切换为接收模式。


      方法三:用DMA发送函数中的,发送数据长度进行+2处理,即可解决此问题。(本人项目实际使用方法)


5、参考源代码


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


 


/*-------------------------------------------------------------------------------------- 

函数名称: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发送完成中断


 

/*-------------------------------------------------------------------------------------- 

函数名称: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发送模式

推荐阅读

史海拾趣

Belkin公司的发展小趣事

在发展过程中,贝尔金也通过收购其他公司来增强自身实力。例如,2013年贝尔金正式完成对Linksys的收购,这一举措进一步丰富了其产品线,并加强了在网络设备领域的竞争力。此外,贝尔金还通过不断整合内部资源,优化生产流程,提高产品质量和效率。

乔光电子(FTR)公司的发展小趣事

随着电子行业的快速发展,贝尔金也紧跟时代步伐,不断进行技术革新和产品线拓展。2002年,贝尔金为苹果Dock连接器研发了一系列产品,包括汽车套件、电池组、读卡器等,这标志着它与苹果公司的深度合作开始。此后,贝尔金逐渐成为苹果官方合作配件制造商,其产品在苹果用户中享有很高的声誉。

HEC Inc公司的发展小趣事

在发展过程中,贝尔金也通过收购其他公司来增强自身实力。例如,2013年贝尔金正式完成对Linksys的收购,这一举措进一步丰富了其产品线,并加强了在网络设备领域的竞争力。此外,贝尔金还通过不断整合内部资源,优化生产流程,提高产品质量和效率。

超霸(GP)公司的发展小趣事

绿索超容深知品质是企业生存的根本。因此,公司从原材料采购到生产制造,再到成品检验,都实施了严格的质量控制措施。此外,公司还通过了ISO9001体系认证,进一步提升了其产品质量管理水平。这些努力不仅保证了绿索超容产品的优良品质,也为其赢得了客户的信任和好评。

Dialog公司的发展小趣事

Dialog公司最初可以追溯到1972年,当时它是由美国洛克希德导弹航空公司所属的一个情报科学实验室负责建立的。这个实验室的初衷是探索和开发新的通信技术,以支持军事和情报领域的需求。随着时间的推移,这个实验室逐渐积累了丰富的技术经验和研发实力。

AKM [Asahi Kasei Microsystems]公司的发展小趣事

到了1981年,Dialog公司从洛克希德导弹航空公司中独立出来,成为了一个子公司,并开始独立经营。在独立发展的初期,Dialog公司继续专注于通信技术的研发,并逐渐将业务范围拓展到了个人便携式应用、低功耗短程无线应用以及LED固态照明、显示和汽车应用等领域。

问答坊 | AI 解惑

plat build 与 smdk2440的关系

我想了解下pb与smdk2440 的关系是什么呢,我在看到smdk2440中有很一些驱动,以及bootloader,还有内核调试方面, 那是不是pb中集成了协议栈,但是驱动由smdk2440来提供,但是我不知道这个驱动在smdk2440下是如何区分系统的驱动与用户的驱动,好像 ...…

查看全部问答>

请问串口打开后第一个数据为什么收不到?

请问串口打开后第一个数据为什么收不到? 我用单片机连接pc, 打开串口之后,发现缓存里只有第二个byte开始的数据,获得的数据长度也比发过来的少1。用虚拟终端显示同样收不到第一个字节。但是用analyzer夹在单片机和pc中间监视发现单片机那边过 ...…

查看全部问答>

为什么evc4无法在真实设备上调试?

为什么我装了WINCE4.2的SDK以后EVC的目标设备中还是只有模拟器可以选择,却没有真实设备可以选择?...…

查看全部问答>

-条码手持终端应用程序开发!!!

-条码手持终端应用程序开发!!! 本人有多年的PDA开发工作经验,一直从事条码手持终端应用程序的开发 开发过多种设备: CASIO DT900,DT300,DTX10; Cipher 711 ; SYMBOL MC50,MC1000,MC3000,PPT8800; Intermec 700系列 有需要合作的请联系 ...…

查看全部问答>

帮忙看下吧,仿真有误

来自EEWORLD合作群:12425841 reg ChZFilt;reg [5:0] ChZBuf;assign LineOrigin = ChZFilt;always @ ( posedge Clk4MHz or negedge Reset_ )beginif ( !Reset_ )beginChZBuf <= 6\'h00;ChZFilt <= LO;endelsebeginChZBuf <= ChZBuf ...…

查看全部问答>

那位师兄有利用串口的DMA发送的例子

                                 我试了几次没有成功,望指教,给个例子参详…

查看全部问答>

Step 导出

[backcolor=rgb(244, 244, 244) !important]Summary [backcolor=rgb(244, 244, 244) !important][size=0.95em]Altium Designer在必要时可以导出满载板以及相关的模型的信息。通常来说,导出的满载板信息用来在第三方CAD工具中进行整机验证。 从 ...…

查看全部问答>

ADUC7060是怎样将printf转向到串口的?

ADUC7060是怎样将printf转向到串口的? 看了ADI的例程,也没有得到答案。疑问中…

查看全部问答>

请教uCOS II的中断调用问题

遇到一个问题,我学uCOS ii ,也是Keil 编译器,没有搞懂的是系统时钟中断是在哪里调用的!比如51系列,在Keil里就有一个中断号什么的来判断,那么ucos ii 应该是如何做的呢?也就是在什么判断是进入中断的处理程序!…

查看全部问答>

工程经理初次使用Multisim

作为一名在模拟电路领域有着几十年经验的工程经理,我迄今为止还没有用过SPICE或其它仿真软件,说起来有些惭愧。最近,我在电脑上安装了Analog Devices版的Multisim,下面我要尝试能否设计出一个简单可行的运算放大器电路。我锁好办公室大门,打开M ...…

查看全部问答>