打印
[国产单片机]

模拟串口发送数据有时漏发几个字节

[复制链接]
3036|39
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 一叶倾城wwq 于 2019-1-6 15:42 编辑

MCU:SC92F7321。之前我便发过串口的类似问题,在各位坛友的指点下已经得到解决,但经过反复测试,模拟发送串口存在如下问题:在发送过程中,有时会出现字节漏发的情况,本来想用分析仪之类的抓一下波形,但奈何在抓取过程中从不出现,真是让人无奈。功能就是硬件串口中断接收,再转IO口发送数据,实现一个IO口通信。                                                                                                       现在有两个怀疑对象:一个是程序写出BUG;一个是并非是MCU漏发,而是波形在那几个字节这儿畸变了,串口助手接收不认为是数据。(稍后附上发送相关代码),望各位大大指点下迷津


漏发字节左边是接收右边是发送.png (307.24 KB )

漏发字节左边是接收右边是发送.png

定时器发送数据波形如图.png (19.75 KB )

定时器发送数据波形如图.png

相关帖子

沙发
一叶倾城wwq|  楼主 | 2019-1-6 15:38 | 只看该作者
/**************************************************
函数名称:UartTxByte
函数功能:IO口模拟串口发送一字节
入口参数:dat
出口参数:
****************************************************/
void UartTxByte(u8 dat)
{        
        TxTimeCnt = 0;
        TxFlag =1;        
        TxTemp = dat; //将发送字节放入全局->在定时器里发
        //TxStep =1;          //发送步骤置1发起始位低电平
        TxOneByteFlag = 0;
        while(!TxOneByteFlag);//等待发送字节成功标志置1
}

/**************************************************
函数名称:UartTxByte
函数功能:IO口模拟串口发送一串数据
入口参数:dat
出口参数:
****************************************************/
void UartTxString(u8 *TxTab,u8 len)
{        
    u8 xdata i =0;               
        TxByteNum = len;
        TxOneByteFlag = 0;

        EUART = 0;                  //关闭UART中断
        SCON  = 0X40;     //不允许接收
        TR2 = 0;                  //关闭定时器2
        TR1 = 0;
        P1CON |= 0X0c;    //P12配置为输出                                    
        P12PM;        
        while(len--)        
        {
         TxOneByteFlag = 0;
         UartTxByte(TxTab[i++]);
         while(!TxOneByteFlag);
        }

         EUART = 1;                   //开启UART中断
         SCON  = 0X50;     //串口允许接收
         TR2 = 1;                   //开启定时器2
         TR1 = 1;
         P1CON &= 0XF3;           //P12配置为输入                                 
         P12IM;

         TxByteNum =0;

         return;         
}
//以下是定时中断发
//100us ->9600波特率
void Timer0_ISR (void) interrupt 1          //interrupt address is 0x000B
{
        TR0=0;                          //禁止Timer0计数                  
        TH0=(65536-103)/256;
        TL0=(65536-103)%256;
        TR0=1;                          //赋值完再开始计数
                                 
        if(TxFlag)                        //发送标志
        {           
          TxTimeCnt++;
          if(1 ==TxTimeCnt) //起始低电平
          {
                   P12 = 0;
          }                                                   
          else if( (TxTimeCnt>=2) &&(TxTimeCnt <=9) ) //8位数据
          {
            if(TxTemp &0x01)       //发送1
                {
                        P12 =1;                                 
            }
                else                                     //发送0
                {
                        P12 =0;                                 
                }
                TxTemp >>= 1;
          }
          else if(TxTimeCnt == 10) //停止位高电平
          {
            P12 =1;
          }
          else if(TxTimeCnt >10)   //结束一字节
          {
                TxTimeCnt = 0;
                TxFlag = 0;
                TxOneByteFlag =1;

                TxTemp = 0;               
          }
        }
}

使用特权

评论回复
板凳
gx_huang| | 2019-1-6 16:33 | 只看该作者
你确认是模拟串口发送问题还是硬件串口接收问题?
为何把硬件串口接收中途关闭了?

使用特权

评论回复
评论
一叶倾城wwq 2019-1-6 17:14 回复TA
如在一楼所说,接收用的硬件串口中断,发送时转为IO发送,也就是RX转为普通IO口比如P12,单线通信,在发送时需要将串口关闭且不允许接收数据,您觉得会是哪里的问题呢? 
地板
gx_huang| | 2019-1-6 18:29 | 只看该作者
原来是单线通信,还以为是用其它GPIO转发。
你这个定时器定时发送程序,显然有很多需要完善的细节。
除了定时器,是否还有其它中断?
定时器的程序也不够精细,MCU机器周期是多少?从代码看,应该是1us,这个已经是比较慢速的MCU了。
定时器里关闭T0,赋值,计算输出是0还是1,都需要时间,导致实际输出波形严重偏离设计初衷。
1、进定时器,不可以暂时关闭T0
2、TH0永远赋值0xff,TL0是-103,也就是153,如果设置T0是16bit定时器,直接:
TL0+=153;TH0=0XFF;
这样没有计时误差。
由于TH永远是0XFF,直接用8bit重装载模式,更简单,也没有累计计时误差。
3、关于1和0的输出,中间计算判断占用时间,导致时序错误抖动,应该本次中断计算是1还是0,下次进中断时立即输出,这样每次输出不会前后抖动。
这些都是自己都可以分析出来的问题,示波器一看波形就知道了。

使用特权

评论回复
5
gx_huang| | 2019-1-6 18:54 | 只看该作者
修正,这个MCU速度还是比较快的,不是1us。
但是1个bit时间是104us,还是需要考虑误差和抖动。

使用特权

评论回复
6
mohanwei| | 2019-1-6 20:38 | 只看该作者
最好定义一个半双工通信协议,即平时接收命令时绝不会发送;发送应答时决不会接收新命令。
这样发送时就可以关闭中断,通过纯软件或查询定时器计数寄存器的方法来算延时,发送时序可以非常精准。

使用特权

评论回复
7
linqing171| | 2019-1-6 22:10 | 只看该作者
1 你逻辑分析仪的采样率是多少。如果实际上抖动很小,应该是软件bug,和时序部分逻辑可能关系不大。
2 TH0和TL0自动重载模式,这样能改善一些固定抖动。
3 P12赋值的值,进入中断后直接赋值,然后计算下次进中断要赋值多少,开中断前也要准备好P12要输出的值。这样能节省一些随机抖动。
4         TxFlag =1;        
        TxTemp = dat; //将发送字节放入全局->在定时器里发
      这两句是不是要换个位置?
5 Flag太多,计数太多,状态机待优化。

使用特权

评论回复
8
ayb_ice| | 2019-1-7 08:50 | 只看该作者
这种发送还不如直接软件延时,禁止中断,波特率搞高点,毕竟发送的数据应该不多
每发送完一个字节重新开一下中断,这样最多中断延时一个字节时间,大多数应用是满足要求的
你这样逻辑复杂了,容易出错,

定时中断应该最高优先级,同时要自动重装才好,中断函数不够简练

使用特权

评论回复
9
一叶倾城wwq|  楼主 | 2019-1-7 16:04 | 只看该作者
gx_huang 发表于 2019-1-6 18:29
原来是单线通信,还以为是用其它GPIO转发。
你这个定时器定时发送程序,显然有很多需要完善的细节。
除了定 ...

感谢指点,先回答您的提问:除了2个定时器和串口接收中断外没有其他中断,AD使用的查询方法,发送的这个定时器0为高优先级。
您说的赋值方法我待会儿试一下,我也想用示波器抓取波形看看就找到问题,只是我守着它一直抓波形的时候偏偏没有漏发出现,昨天加了测试代码大概守了一个多小时,也是醉了,等我不抓波形又时不时来一下漏发,估计也不是什么累计误差,抖动倒是真有可能

使用特权

评论回复
10
一叶倾城wwq|  楼主 | 2019-1-7 16:07 | 只看该作者
gx_huang 发表于 2019-1-6 18:54
修正,这个MCU速度还是比较快的,不是1us。
但是1个bit时间是104us,还是需要考虑误差和抖动。 ...

但是您说的“进定时器,不可以暂时关闭T0”,这是为何,有人和我说过在赋值是关闭定时器还能减小赋值时的这个时间误差呢?

使用特权

评论回复
11
一叶倾城wwq|  楼主 | 2019-1-7 16:10 | 只看该作者
mohanwei 发表于 2019-1-6 20:38
最好定义一个半双工通信协议,即平时接收命令时绝不会发送;发送应答时决不会接收新命令。
这样发送时就可 ...

是的,现在就是在发送时不接收(关闭接收中断和不允许接收),然后通过定时器来翻转电平实现数据发送,但这个漏发,我检查程序下来并未发现有逻辑问题,这个问题有些棘手

使用特权

评论回复
12
一叶倾城wwq|  楼主 | 2019-1-7 16:20 | 只看该作者
linqing171 发表于 2019-1-6 22:10
1 你逻辑分析仪的采样率是多少。如果实际上抖动很小,应该是软件bug,和时序部分逻辑可能关系不大。
2 TH0 ...

采样频率是2M还是5M记不太清了。我也觉得不是逻辑的问题;
自动重载模式?好的,我待会儿试下看看是否有些改善;
您说的第三点“P12赋值的值,进入中断后直接赋值,然后计算下次进中断要赋值多少,开中断前也要准备好P12要输出的值”最后一句能理解,前面不是太理解,是把赋值(1和0)放到一堆判断的前面?以减小错误率吗?
第四点的换位置,要换到哪里?定时器?连串发送里?定时器里本来就需要减少代码量了,应该不是往这儿放;
是的,状态的确需要优化下,我也觉得有些啰嗦和繁琐

使用特权

评论回复
13
一叶倾城wwq|  楼主 | 2019-1-7 16:27 | 只看该作者
ayb_ice 发表于 2019-1-7 08:50
这种发送还不如直接软件延时,禁止中断,波特率搞高点,毕竟发送的数据应该不多
每发送完一个字节重新开一 ...

软件延时?很赞的思路,只是我这个是客户要求的波特率,不然和另一端对不上了,发送的最大字节是150Byte,定时0中断是高优先级了,开始发送后也把其他中断都关闭了(比如Timer1、UART)这些,而且主函数发送这儿也有while等待;确实不够简练,我自己也觉得越写越“臃肿”

使用特权

评论回复
14
ayb_ice| | 2019-1-7 16:33 | 只看该作者
一叶倾城wwq 发表于 2019-1-7 16:27
软件延时?很赞的思路,只是我这个是客户要求的波特率,不然和另一端对不上了,发送的最大字节是150Byte ...

细节之处可以考虑更仔细 点
比如,下面的代码
void UartTxByte(u8 dat)
{        
        TxTimeCnt = 0;
        TxFlag =1;        
        TxTemp = dat; //将发送字节放入全局->在定时器里发
        //TxStep =1;          //发送步骤置1发起始位低电平
        TxOneByteFlag = 0;
        while(!TxOneByteFlag);//等待发送字节成功标志置1
}

改成这样会可靠些
void UartTxByte(u8 dat)
{        
        EA = 0;
        TxTimeCnt = 0;
        TxFlag =1;        
        TxTemp = dat; //将发送字节放入全局->在定时器里发
        //TxStep =1;          //发送步骤置1发起始位低电平
        TxOneByteFlag = 0;
        EA = 1;
        while(!TxOneByteFlag);//等待发送字节成功标志置1
}

使用特权

评论回复
评论
一叶倾城wwq 2019-1-9 11:21 回复TA
@ayb_ice :感谢您的思路,按照您这个方法试过,可能是因为我这里的实际情况不一样吧,和之前差不多的概率,我在考虑换种方法了,可能方法本身就有问题 
ayb_ice 2019-1-8 13:00 回复TA
@一叶倾城wwq : 抖动应该不是主要问题,可以有些误差 
ayb_ice 2019-1-8 12:58 回复TA
@一叶倾城wwq : 这可不是防止抖动,是防止逻辑瞬间可能产生错误,也就是临界代码 
一叶倾城wwq 2019-1-7 18:56 回复TA
在赋值的时候把中断都关闭,是为了避免在发送时因中断可能引起的抖动吗? 
15
mohanwei| | 2019-1-7 16:34 | 只看该作者
一叶倾城wwq 发表于 2019-1-7 16:10
是的,现在就是在发送时不接收(关闭接收中断和不允许接收),然后通过定时器来翻转电平实现数据发送,但这 ...

问题相当多……
你这种定时中断+多分支多跳转的,相位抖动太大了
建议重新写一个查询法+软件延时的单字节发送函数。大概是这样子:
void tx_byte(u8 ucData)
{
    _Clr_TXD;//起始位
   bps115200_delay();
   if(ucData&0x80)//第1bit
   {
          _Set_TXD;
   }
   else
   {
           _Clr_TXD;
   }
   bps115200_delay();
   .......
   if(ucData&0x01)//第8bit
   {
          _Set_TXD;
   }
   else
   {
           _Clr_TXD;
   }
   bps115200_delay();

    _Set_TXD;//停止位
   bps115200_delay();
}

使用特权

评论回复
16
gx_huang| | 2019-1-7 16:55 | 只看该作者
一叶倾城wwq 发表于 2019-1-7 16:07
但是您说的“进定时器,不可以暂时关闭T0”,这是为何,有人和我说过在赋值是关闭定时器还能减小赋值时的 ...

即使只有一个中断,关定时器也会增加抖动,有些指令只要1t,有些要好多t,进中断的时间不一样,这就产生了误差。如果用8位重装载,或者TL0+=153,则不会有误差,抖动还是有的可以忽略。

使用特权

评论回复
17
千岁寒| | 2019-1-8 11:24 | 只看该作者
可能是串口線的影響 或者 是波特率不那麼精確!

使用特权

评论回复
18
edan_lee| | 2019-1-8 12:53 | 只看该作者
看波形不对,忽长忽短。长是不是因为发生了更高或者同级优先级的中断? 9600 的位宽应该是在104us的样子。

使用特权

评论回复
19
edan_lee| | 2019-1-8 12:59 | 只看该作者
//========================================================================================================
//函数名: SUSendByte()
//参   数:
//返   回:
//说   明: 发送一个字节
//========================================================================================================
void SUSendByte(uint8_t dat)
{
    u8 i = 0;
    u16 temp = dat;
    temp |= 0xF00;      //把停止位加上
    EnableSUTXTimer();  //初始化定时器,周期为104us,不需要中断
    SU_TX_L();         
    while(1)
    {
        if(0 != __HAL_TIM_GET_FLAG(&SUTXTimer, TIM_FLAG_UPDATE))//等待中断溢出
        {
            __HAL_TIM_CLEAR_FLAG(&SUTXTimer,TIM_FLAG_UPDATE);   //清溢出标志
            if(temp & 0x01)
                SU_TX_H();
            else
                SU_TX_L();
            temp >>= 1;
            i++;
            if(10 <= i)
                break;
        }
    }
    SU_TX_H();
    HAL_TIM_Base_Stop(&SUTXTimer);   //停止定时器
}

使用特权

评论回复
20
li880wert| | 2019-1-8 17:50 | 只看该作者
定时器时间改80us,你定时器里面2个除法就占用了20us.你原来定时100的变成120了,

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

80

主题

1342

帖子

11

粉丝