搜索
bottom↓
回复: 18

单片机串口环形缓冲区进行数据帧接收和处理的问题

[复制链接]

出200入657汤圆

发表于 2015-8-29 14:24:49 | 显示全部楼层 |阅读模式
设计目标:
1. 单片机时钟10MHz,串口波特率115200
2. 由于波特率较高,串口必须采用中断方式接收
3. 串口接受完数据必须对数据进行解帧,即判断出帧头和帧尾,这部分放while循环里面处理

搜索了下,发现下面的代码比较符合要求
但是根据信息有几个问题
1. UART_Receive_Size在中断函数和一般函数都出现,会产生其真实值被中断后覆盖掉的问题,导致值出错(最终结果是有可能丢帧)
2. 可不可以不用UART_Receive_Size,而是定义两个指针,分别指向接收位置和处理位置,则缓冲区的满(full)
    不用UART_Receive_Size进行判断,而是利用这两个指针的关系进行判断。这样子可以避免问题1?()
3. 求测试过的健壮性好的代码,谢谢


  1. //系统可修改参数宏定义
  2. #define BUFFER_SIEZ 64
  3. //控制命令定义
  4. #define COMMUNCIATE 0
  5. #define SET_SYSTEM_CAL_FULL 1
  6. #define SET_SYSTEM_CAL_MV_V 2
  7. #define SET_SYSTEM_OL 3
  8. #define SET_POWER_OFF_TIME 4 //设定系统关机时间
  9. #define READ_SYSTEM_DATA 5

  10. //变量定义
  11. //串口缓冲区 建立一个环形缓冲区,收发
  12. unsigned char xdata UART_Receive_Size=0;//串口缓冲区接收字节数
  13. unsigned char xdata UART_Receive_First=0;//串口缓冲区接收字节开始位置
  14. unsigned char xdata UART_Read_First=0;
  15. unsigned char xdata UART_Buffer[BUFFER_SIEZ];//串口缓冲区
  16. unsigned char xdata UART_Send_Byte_Ok=0;//发送一字节成功
  17. //中断处理
  18. //串口初始化根据自己的单片机写就行
  19. void UART0_Interrupt (void) interrupt 4  
  20. {
  21.    if(RI0)                         //如果是发中断,返回
  22.    {   
  23.      RI0=0;  //清除中断标志
  24.      if(UART_Receive_Size<=BUFFER_SIEZ)//缓冲区未满,装载数据
  25.      {
  26.        UART_Buffer[UART_Receive_First++]=SBUF0;
  27.        UART_Receive_Size++;//串口缓冲区接收字节数
  28.        if(UART_Receive_First>=BUFFER_SIEZ)//循环装入缓冲区
  29.          UART_Receive_First = 0;
  30.      }
  31.    }
  32.   if(TI0)
  33.   {         
  34.     TI0=0;
  35.    UART_Send_Byte_Ok=1;
  36.   }
  37. }

  38. //在缓冲区中读取一帧数据
  39. void Do_Commend()
  40. {
  41.   unsigned char Buf[8]={0,0,0,0,0,0,0,0};//帧数据缓冲区
  42.   unsigned char i=0;
  43.   unsigned char data_packge_flag=0;
  44.   
  45.   if(UART_Receive_Size>=8)//缓冲区字节数大于等于一个包字节数
  46.   {
  47.     while(UART_Receive_Size!=0)//寻找帧数据头
  48.     {
  49.          if(UART_Buffer[UART_Read_First]==0xaa)
  50.          {
  51.            if(UART_Read_First+7<BUFFER_SIEZ)
  52.            {
  53.               if(UART_Buffer[UART_Read_First+7]==0x55)
  54.               {
  55.                data_packge_flag=1;
  56.                break;
  57.               }
  58.            }
  59.            else
  60.            {
  61.               if(UART_Buffer[7-(BUFFER_SIEZ-UART_Read_First)]==0x55)
  62.               data_packge_flag=1;
  63.               break;
  64.            }
  65.          }
  66.          UART_Read_First++;
  67.          if(UART_Read_First>=BUFFER_SIEZ)//环形 缓冲区折行
  68.          UART_Read_First=0;
  69.          UART_Receive_Size--;
  70.     }
  71.     if(data_packge_flag==1)//寻找到枕头
  72.     {
  73.        for(i=0;i<8;i++)//读取帧数据
  74.        {
  75.           Buf=UART_Buffer[UART_Read_First];
  76.           UART_Receive_Size--;
  77.           UART_Read_First++;
  78.           if(UART_Read_First>=BUFFER_SIEZ)//环形 缓冲区折行
  79.             UART_Read_First=0;
  80.        }
  81.        data_packge_flag=0;
  82.     }
  83.     if(Buf[7]==0x55&&Buf[0]==0xaa)  //接收到一个正确的数据包
  84.     {
  85.           switch(Buf[1])
  86.           {
  87.             ...
  88.           }
  89.         }
  90.   }
  91. }
复制代码

阿莫论坛20周年了!感谢大家的支持与爱护!!

曾经有一段真挚的爱情摆在我的面前,我没有珍惜,现在想起来,还好我没有珍惜……

出0入0汤圆

发表于 2015-9-4 17:01:27 | 显示全部楼层
以前有段时间研究过这个,心得就是要好的效果,必须加大接收/发送的buffers,以及加快数据的处理时间。
环形缓冲区的样子有点像贪吃蛇,蛇头和蛇尾撞上了必然出错。解决办法就是要判断蛇头和蛇尾的距离,一
旦距离过小,蛇头要暂停接收数据,等待蛇尾取走数据,等距离拉开了就继续。

以下是以前记录的数据:
* 把接收的字节立即回显,接收无缓冲,发送经缓冲区:
   8字节缓冲区,能正确接收并回显大约4KB;
  16字节缓冲区,能正确接收并回显大约9KB;
  32字节缓冲区,能正确接收并回显大约18KB;

出0入0汤圆

发表于 2015-9-26 16:06:02 来自手机 | 显示全部楼层
我是这样的,定义一个缓冲区,大小至少为数据一帧的两倍,

出0入0汤圆

发表于 2015-9-26 16:08:15 来自手机 | 显示全部楼层
然后定义一个指针,遍历寻找帧头,找到头后直接判断尾是否相等,帧尾正确则找到了数据入口,就可以用数组处理数据了

出200入657汤圆

 楼主| 发表于 2015-9-26 21:17:56 | 显示全部楼层
Ray______ 发表于 2015-9-26 16:08
然后定义一个指针,遍历寻找帧头,找到头后直接判断尾是否相等,帧尾正确则找到了数据入口,就可以用数组处 ...

原理当然不复杂
复杂的是 当找到了帧头继续找帧尾时,到了数据缓冲区最后一个接收字节还没找到时,该怎么处理?
针对定义的帧协议,优化处理方式就很多了
如何在复杂度以及尽量少丢帧之间折衷,并不是那么容易的事

这个程序编不好很容易死掉,
如何提高程序健壮性是个很大的问题

出200入657汤圆

 楼主| 发表于 2015-9-26 21:20:42 | 显示全部楼层
Ray______ 发表于 2015-9-26 16:08
然后定义一个指针,遍历寻找帧头,找到头后直接判断尾是否相等,帧尾正确则找到了数据入口,就可以用数组处 ...

原理当然不复杂
复杂的是 当找到了帧头继续找帧尾时,到了数据缓冲区最后一个接收字节还没找到时,该怎么处理?
针对定义的帧协议,优化处理方式就很多了
如何在复杂度以及尽量少丢帧之间折衷,并不是那么容易的事

这个程序编不好很容易死掉,
如何提高程序健壮性是个很大的问题

出200入657汤圆

 楼主| 发表于 2015-9-26 21:45:01 | 显示全部楼层
Ray______ 发表于 2015-9-26 16:08
然后定义一个指针,遍历寻找帧头,找到头后直接判断尾是否相等,帧尾正确则找到了数据入口,就可以用数组处 ...

原理当然不复杂
复杂的是 当找到了帧头继续找帧尾时,到了数据缓冲区最后一个接收字节还没找到时,该怎么处理?
针对定义的帧协议,优化处理方式就很多了
如何在复杂度以及尽量少丢帧之间折衷,并不是那么容易的事

这个程序编不好很容易死掉,
如何提高程序健壮性是个很大的问题

出200入657汤圆

 楼主| 发表于 2015-9-26 21:45:55 | 显示全部楼层
Ray______ 发表于 2015-9-26 16:08
然后定义一个指针,遍历寻找帧头,找到头后直接判断尾是否相等,帧尾正确则找到了数据入口,就可以用数组处 ...

原理当然不复杂
复杂的是 当找到了帧头继续找帧尾时,到了数据缓冲区最后一个接收字节还没找到时,该怎么处理?
针对定义的帧协议,优化处理方式就很多了
如何在复杂度以及尽量少丢帧之间折衷,并不是那么容易的事

这个程序编不好很容易死掉,
如何提高程序健壮性是个很大的问题

出0入0汤圆

发表于 2015-9-26 22:03:36 | 显示全部楼层
Ray______ 发表于 2015-9-26 16:06
我是这样的,定义一个缓冲区,大小至少为数据一帧的两倍,

2倍够干啥的,分分钟灭趴下;我一般会计算整个程序运行的最大时间,然后根据这个时间和一帧字节去反推缓冲区多大,再加20%余量即可;

出0入0汤圆

发表于 2015-9-26 22:09:26 | 显示全部楼层
ziruo2002ab 发表于 2015-9-26 21:45
原理当然不复杂
复杂的是 当找到了帧头继续找帧尾时,到了数据缓冲区最后一个接收字节还没找到时,该怎么 ...

如何贴代码,不会弄啊,我本来想贴一个的;晕倒;

出0入0汤圆

发表于 2015-9-26 22:10:37 | 显示全部楼层
本帖最后由 kinsno 于 2015-9-26 22:13 编辑

/**
  * @brief  void TFT_Scan()
  * @param  None
  * @retval None
  * @备注: TFT_Scan,主程20ms定时扫描;
                        按照115200bit->15200byte/s -> 15byte/ms ->
                        我们定义的队列长度为512, 512/15 = 34ms,近乎一半为20ms;
                       
帧结构 : 0x55 + 帧长(1byte) + COM(1byge) + databuf(n byte) + check + 0xaa       
0x55 5 [1 2 3 4 5] 6 7       
  *******************************************************************
  
**/
//TFT返回消息扫描 , 迪文屏比较二货的机制,不适合这种高密度的发描,完全是大炮大蚊子;
//非得让我在中断使用傻瓜式方法,漏帧是在所难免的!傻瓜式代码名称为TFT_ISR();
void TFT_scan(void)
{

    uint8_t         readlen = 0,Data_Temp = 0;
        uint16_t        StAddr = 0,Key_Temp = 0,QueueCounter = 0;       

       
        //1.保留指针
        StAddr = Queue_GetCurRead(&tftRevQue);
        memset(tftBuff,0,FRAME_BUFF_LEN);
       
        //最小帧长不合格,数据未收全
        QueueCounter = Queue_GetCounter(&tftRevQue);
        if(QueueCounter < Frame_Len)
                return ;       
                       
        //2. 帧头和帧长
    if(Queue_Peek_Mult(&tftRevQue,tftBuff,2,StAddr))   
        {       
               
                if((tftBuff[0] == 0xAA)&&(tftBuff[1] == 0x78))//帧头为AA 78
                {       
                       
                        //继续侦测
                        readlen = Frame_Len;                       
                        if(Queue_Peek_Mult(&tftRevQue,tftBuff,readlen,StAddr))       
                        {
                                if(        (tftBuff[4] == 0xcc) && //帧尾正确
                                        (tftBuff[5] == 0x33) &&
                                        (tftBuff[6] == 0xc3) &&
                                        (tftBuff[7] == 0x3c)
                                  )
                                  {
                                                memset(tftBuff,0,sizeof(tftBuff));
                                                Queue_Read_Mult(&tftRevQue,tftBuff,readlen);
                                                Key_Temp = tftBuff[2];
                                                Key_Temp*= 256;
                                                Key_Temp += tftBuff[3];
                                                //抛出事件
                                                SchMsg.post(tTouch,Key_Temp);
                                  }
                                  else
                                          Queue_Read_Single(&tftRevQue,&Data_Temp);//改变读指针!
                        }
                        else
                                Queue_Read_Single(&tftRevQue,&Data_Temp);//改变读指针!
                }
                else
                        Queue_Read_Single(&tftRevQue,&Data_Temp);//改变读指针!                       
        }
        else
                Queue_Read_Single(&tftRevQue,&Data_Temp);//改变读指针!       
}

出20入25汤圆

发表于 2015-9-26 22:52:31 | 显示全部楼层
kinsno 发表于 2015-9-26 22:10
/**
  * @brief  void TFT_Scan()
  * @param  None

你这个漏不漏帧,一帧数据没发全怎么处理?

出0入0汤圆

发表于 2015-9-27 04:05:36 来自手机 | 显示全部楼层
ziruo2002ab 发表于 2015-9-26 21:45
原理当然不复杂
复杂的是 当找到了帧头继续找帧尾时,到了数据缓冲区最后一个接收字节还没找到时,该怎么 ...

首先纠正一点,是判断帧尾,不是寻找帧尾。你一帧数据格式难道没规律?判断正确有何难,错误就扔掉

出0入0汤圆

发表于 2015-9-27 04:06:42 来自手机 | 显示全部楼层
kinsno 发表于 2015-9-26 22:03
2倍够干啥的,分分钟灭趴下;我一般会计算整个程序运行的最大时间,然后根据这个时间和一帧字节去反推缓 ...

你怎么不说8位机算啥,64位机直接秒杀…客观点看

出0入0汤圆

发表于 2015-9-27 04:20:14 | 显示全部楼层
因为只有手机,就简单表达下自己的思想吧
uint8_t *dat;
if(ReceiveComplete)
{
  while(!cheakHead)
{
     dat++;
  }
if(checkEnd)
  {
    //u can check data here or return ok
  }
else
{
   //find next or return error
}
}

出0入0汤圆

发表于 2015-9-27 09:58:18 | 显示全部楼层
本帖最后由 kinsno 于 2015-9-27 10:00 编辑
chenchaoting 发表于 2015-9-26 22:52
你这个漏不漏帧,一帧数据没发全怎么处理?


一帧没发全:因为你是有方法可以判断帧尾的,不管是定长的还是不定长的帧,你就一直找帧尾,如果找不着,就直接return掉(但不是移动帧头哦),当然还要考虑溢出的情况,比如你一帧最长也就是100个字节,结果你从帧头找了100个字节还没有找到帧尾,这明显就得移动帧头了,对吧;

出0入0汤圆

发表于 2015-9-27 09:59:12 | 显示全部楼层
Ray______ 发表于 2015-9-27 04:06
你怎么不说8位机算啥,64位机直接秒杀…客观点看

呵呵,我们所说根本不在一条线上;

出0入0汤圆

发表于 2015-9-27 14:32:29 | 显示全部楼层
kinsno 发表于 2015-9-27 09:59
呵呵,我们所说根本不在一条线上;

既然你知道不在一条线上你还妄加评论?关键字眼麻烦看好
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-6-6 20:36

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表