找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 5852|回复: 5
收起左侧

51单片机串口接收数据完毕检测程序

[复制链接]
ID:675145 发表于 2020-4-29 15:03 | 显示全部楼层 |阅读模式
这两天买了个蓝牙模块研究,发现串口接收到的数据没有规律,不好检测到底接收完了没有。查阅了些资料发现了比较好的方法,为了帮助跟我一样的萌新可以更好的学习,我决定发个帖子,也算是给自己做个笔记。
1、IDLE中断
IDLE就是串口收到一帧数据后,发生的中断。什么是一帧数据呢?比如说给单片机一次发来1个字节,或者一次发来8个字节,这些一次发来的数据,就称为一帧数据,也可以叫做一包数据。
如何判断一帧数据结束,就是我们今天讨论的问题。因为很多项目中都要用到这个,因为只有接收到一帧数据以后,你才可以判断这次收了几个字节和每个字节的内容是否符合协议要求。

看到这个第一反应就是“好东西”,可惜51单片机好像没有,所以只是做了粗略的了解,有兴趣的各位可以去找些资料看看,随便查一下资料还挺多的
2、自定义结束符
在一帧数据尾部添加一个字符,比如传输的每一帧数据尾部都是“#”,当单片机接收到“#”后,说明已经接收完毕,然后立个Flag,就可以开始处理这些接收到的数据了,如下:
  1. u8 xdata RxLen=0;                                //接收计数
  2. u8 xdata RxFlag=0;                                //接收完毕标志位
  3. u8 xdata Uart4_Rx_Buffer[33];        //接收到的数据

  4. void Uart4_Init()        //蓝牙串口4,选择定时器2为波特率发生器,波特率57600
  5. {
  6.         S4CON = 0x10;                //8位数据,可变波特率
  7.         S4CON &= 0xBF;                //串口4选择定时器2为波特率发生器
  8.         AUXR |= 0x04;                //定时器2时钟为Fosc,即1T
  9.         T2L = 0x7E;                        //设定定时初值
  10.         T2H = 0xFF;                        //设定定时初值
  11.         AUXR |= 0x10;                //启动定时器2
  12.         
  13.         IE2|=0X10;                        //打开串口中断4
  14.         EA=1;                                //打开总中断
  15. }
  16. void  S4_Routine() interrupt 18        //串口4中断函数
  17. {               
  18.         if(S4CON&0x01)                //接收中断
  19.         {
  20.                 S4CON&=0xFE;        //清除接收中断
  21.                 Uart4_Rx_Buffer[RxLen]=S4BUF;
  22.                 if(Uart4_Rx_Buffer[RxLen] == '#')        //是结束符,说明接收完毕,就可以拿着RxFlag出去搞事情了
  23.                 {
  24.                         RxFlag=1;
  25.                 }
  26.                 else                                                                //不是结束符,老实接收下一个数据
  27.                 {
  28.                         RxLen++;
  29.                 }
  30.         }                        
  31. }
  32. void BLE_Allot()        //数据分配
  33. {
  34.         if(RxFlag)
  35.         {
  36.                 RxFlag=0;
  37.                 RxLen=0;
  38.                 /*干一些你想干的事情*/
  39.         }
  40. }
复制代码
3、用另外一个定时器检测

具体的思路是,因为每一位传输的时间间隔由波特率决定都是一样,所以每一个字节的传输时间也是一样的。那么我们每传输一个字节时,把定时器初值复位,以保证定时器不会溢出。定时器时间取传输一个字节的1.5倍,这样一来,当定时器超时时候,就意味着串口接收数据停止了。此时同样立一个flag开始处理这些接收到的数据,如下:
PS:大佬用的是滴答定时器,我发现51单片机还是没有,欲哭无泪,所以只好自己做一个了
  1. u8 xdata RxLen=0;                                //接收计数
  2. u8 xdata RxFlag=0;                                //接收完毕标志位
  3. u8 xdata Uart4_Rx_Buffer[33];        //接收到的数据
  4. void Timer1_Init()        //定时器1初始化
  5. {
  6.         AUXR |= 0x40;        //定时器时钟1T模式
  7.         TMOD &= 0x0F;        //设置定时器模式
  8.         TL1 = 0x82;                //设置定时初值
  9.         TH1 = 0xE7;                //设置定时初值
  10.         TF1 = 0;                //清除TF1标志
  11.         TR1 = 0;                //这里先不开始计时,等传输信号的时候开始计时
  12.         
  13.         ET1=1;
  14.         EA=1;
  15. }
  16. void Timer0_Rountine() interrupt 3
  17. {
  18.         TR1 = 0;        //定时器1停止计时
  19.         TF1 = 0;        //清除TF1标志        
  20.         RxFlag=1;        //串口接收完毕
  21. }
  22. void Uart4_Init()        //蓝牙串口4,选择定时器2为波特率发生器,波特率57600
  23. {
  24.         S4CON = 0x10;                //8位数据,可变波特率
  25.         S4CON &= 0xBF;                //串口4选择定时器2为波特率发生器
  26.         AUXR |= 0x04;                //定时器2时钟为Fosc,即1T
  27.         T2L = 0x7E;                        //设定定时初值
  28.         T2H = 0xFF;                        //设定定时初值
  29.         AUXR |= 0x10;                //启动定时器2
  30.         
  31.         IE2|=0X10;                        //打开串口中断4
  32.         EA=1;                                //打开总中断
  33. }
  34. void  S4_Routine() interrupt 18        //串口4中断函数
  35. {               
  36.         if(S4CON&0x01)                //接收中断
  37.         {
  38.                 S4CON&=0xFE;        //清除接收中断
  39.                 Uart4_Rx_Buffer[RxLen]=S4BUF;        //将数据放到数组中
  40.                 RxLen++;                //下次进来数据存放地址+1
  41.                 TR1=0;                        //定时器1停止计时(这里搞了好久,一定要先停止,不然会出现错误)               
  42.                 TL1=0x82;                //设置定时初值
  43.                 TH1=0xE7;                //设置定时初值
  44.                 TR1=1;                        //定时器1开始计时               
  45.         }        
  46. }
  47. void BLE_Allot()        //数据分配
  48. {
  49.         if(RxFlag)
  50.         {
  51.                 RxFlag=0;        //标志清零
  52.                 RxLen=0;        //计数清零
  53.                 /*干一些你想干的事情*/
  54.         }
  55. }
复制代码
最后,感谢大佬在网上发布的好资料能给我们参考,也希望大佬能进来多讨论,多给点意见和建议



评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

回复

使用道具 举报

ID:675145 发表于 2020-4-29 15:21 | 显示全部楼层
第三种方法定时器传输时间计算方法:比如波特率为57600,那么每秒钟传输的字节数为:57600/8=7200B/S,那么一个字节需要的时间约为:1s/7200B=139uS,所以一个字节的1.5倍时间约为:209uS
回复

使用道具 举报

ID:424977 发表于 2020-5-2 21:34 | 显示全部楼层
感谢大佬分享 学习到了
回复

使用道具 举报

ID:876099 发表于 2021-10-11 17:08 | 显示全部楼层
使用了你的程序,感谢分享
回复

使用道具 举报

ID:65956 发表于 2021-10-12 08:39 | 显示全部楼层
我说问问,如果用你这个,当一帧来时存在着两个23(#的十六进制数是23),那怎么办?所以说这个并不是最终的好办法
void  S4_Routine() interrupt 18        //串口4中断函数
{               
        if(S4CON&0x01)                //接收中断
        {
                S4CON&=0xFE;        //清除接收中断
                Uart4_Rx_Buffer[RxLen]=S4BUF;
                if(Uart4_Rx_Buffer[RxLen] == '#')        //是结束符,说明接收完毕,就可以拿着RxFlag出去搞事情了
                {
                        RxFlag=1;
                }
                else                                                                //不是结束符,老实接收下一个数据
                {
                        RxLen++;
                }
        }                        
}
第2种你用定时器0作接收超时的想法是对的,但应是这样的才不用这说的那么麻烦
/*******串口1接收中断程序******************************************************
//接收时打开T0定时计数器(通过主函数再判断是否接收超时)
//*****************************************************************************/
void Uart_Isr() interrupt 4 //using 1
{
        if(RI)
        {
                 RI = 0;
                //---------------------------------------
                TL0 = 0x00;                                        //设置定时初值
                TH0 = 0x4C;                                        //设置定时初值
                TF0 = 0;                                        //清除TF0标志
                ET0 = 0;                                            //关闭T0的溢出中断
                TR0 = 1;                                        //定时器0开始计时
                //---------------------------------------
                 S1_busy = 1;                                        //忙
                S1_Rec_data = SBUF;
                S1_Rectemp[S1_number] = S1_Rec_data;
                S1_number++;                                        //个数加1
                if(S1_number>=100);                                //判断是否超出设置的最大缓存区
                {
                        S1_number = 0;
                }
                step1 = S1_number;                                 
        }
        S1_busy = 0;
}
这是我之前项目作的,当然不只这种方法,还有很多,这个方法我现在都不用了,只是看你这么写,给参考一下
回复

使用道具 举报

ID:624769 发表于 2021-10-12 23:33 | 显示全部楼层
张小不懂 发表于 2020-4-29 15:21
第三种方法定时器传输时间计算方法:比如波特率为57600,那么每秒钟传输的字节数为:57600/8=7200B/S,那么 ...

更正一下, 串口传输时,无效验的话,10位为一个字节,有效验的话,11位为一个字节,因为有起始和停止位。
另外,计算超时其实不用那么麻烦,按增强型51大多是4分波特率的,所以,你定时器2既然做了波特率发生器,那么,串口接受完成一个字节后,被RI触发后,只要开T2中断, 然后赋值超时计数(比如:Time_Out) = 105; 别问我这105怎么来的,反正有人算过加上N次试验,105是最佳值,反正按4分波特率算就是2个半字节的超时吧,然后T2中断,最低优先级,每次--, 减到0了,置标志位就可以了。这样做的好处是,不用刻意用多一个定时器,而且,不管你波特率变成多少,超时判断的字节数是不变的。
当然,标准51会累点,16分波特率,得减420次,有点累……
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

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