串口网口数据帧解析(支持连包、断传、错误数据过滤)

1 篇文章 0 订阅
1 篇文章 0 订阅
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/hwb_1988/article/details/45872379

        嵌入式系统中,关于数据接受部分确实思考了很多,下面总结下个人经验。

        关于串口传输,个人觉得采用modbus协议来接受数据是比较合理的,采用3.5char字符的超时机制,接受的时候如果判断超时,就当作一帧数据进行处理,所以这种情况,帧格式没有那么讲解,发送和超时机制弄好就行。

        第二种网口用的比较多,串口也用的上,什么情况下用的上呢,当发送的数据没有固定的格式和长度,而且发送的时间也无特别的讲究。一般用的比较普遍的格式为:

        帧头 + 长度 + 数据 + 校验 + 帧尾,这种格式可以算的上一种万能的数据处理格式啦,几乎都可以套用。

        贴出来的代码主要有以下几个功能:

        一是处理没用的数据格式,假如帧头为0xa5,如果发送不是该0xa5的字符,统统过滤,如果不过滤的话,循环队列的缓冲会被浪费。

        二是处理了断续传输,假如一帧数据在传输过程中,一部分先达到,一部分后达到,我采用的机制就是一帧数据没有接受完,那就不处理。

        三是当多帧数据一起接受时,此时就有粘包了,这个时候需要做的是把接受到的所有数据按照帧格式分析提取,处理完后的数据就是有效的单帧数据。我这里面采用的回调机制,你只要把你的帧格式处理函数注册一下,当数据处理完毕后,会自动调用你注册函数。

        四是只能处理单包数据不超过6K的ram,如果长度标志超过6K,这些数据数据会被过滤,当然这个不是固定的你可以修改,程序没有采用宏定义方式。

        采用C++写的,当然你只要稍微的修改下,就可以支持C语言了,只要一个头文件即可

        新建Queue.h文件


 
 
  1. #ifndef QUEUE_H
  2. #define QUEUE_H
  3. #define MAXSIZE 10240 //10K的RAM作为缓冲区
  4. #define START_CHAR 0xa5
  5. #define END_CHAR 0x5a
  6. typedef unsigned char uint8_t;
  7. typedef unsigned long uint32_t;
  8. typedef enum {
  9. RES_OK = 0,
  10. RES_ERROR
  11. } STATUS;
  12. typedef enum {
  13. RECEIVED_START,
  14. NO_RECEIVED_START
  15. } RECV_FSM;
  16. typedef void (*AnalysisFun)(uint8_t *, int len); //回调函数类型
  17. class Queue {
  18. private:
  19. uint32_t front;
  20. uint32_t rear;
  21. uint8_t data[MAXSIZE];
  22. AnalysisFun anaFun;
  23. public:
  24. Queue(AnalysisFun cb)
  25. {
  26. front = 0;
  27. rear = 0;
  28. anaFun = cb;
  29. }
  30. ~Queue()
  31. {
  32. }
  33. STATUS EnQueue(uint8_t e)
  34. {
  35. if ((rear + 1) % MAXSIZE == front)
  36. {
  37. return RES_ERROR;
  38. }
  39. data[rear] = e;
  40. rear = (rear + 1) % MAXSIZE;
  41. return RES_OK;
  42. }
  43. int QueueLength()
  44. {
  45. return (rear - front + MAXSIZE) % MAXSIZE;
  46. }
  47. uint8_t GetQueue( int index)
  48. {
  49. return data[(front + index) % MAXSIZE];
  50. }
  51. void HandleData()
  52. {
  53. uint32_t frame_len, frame_startpos = 0;
  54. uint8_t frame_check, c;
  55. RECV_FSM frame_state = NO_RECEIVED_START;
  56. uint32_t len = (rear - front + MAXSIZE) % MAXSIZE; //循环队列中数据长度
  57. for ( uint32_t i = 0; i < len; i++)
  58. {
  59. if (frame_state == NO_RECEIVED_START)
  60. {
  61. if (GetQueue(i) == START_CHAR) //接受到0xa5
  62. {
  63. frame_check = 0;
  64. frame_state = RECEIVED_START; //切换,进而处理帧数据分析
  65. }
  66. else
  67. {
  68. frame_startpos = i + 1; //标记缓冲队列需要释放的位置
  69. }
  70. }
  71. else if (frame_state == RECEIVED_START)
  72. {
  73. if (i + 4 <= len) //长度4个字节
  74. {
  75. frame_len = (( uint32_t)GetQueue(i++));
  76. frame_len |= (( uint32_t)GetQueue(i++)) << 8;
  77. frame_len |= (( uint32_t)GetQueue(i++)) << 16;
  78. frame_len |= (( uint32_t)GetQueue(i++)) << 24;
  79. if (frame_len > 6137) //不支持单包数据超过6K
  80. {
  81. frame_state = NO_RECEIVED_START;
  82. frame_startpos = i; //标志需要释放的位置
  83. continue;
  84. }
  85. if (i + frame_len + 2 <= len) //数据长度+校验+帧尾
  86. {
  87. uint8_t *p = new uint8_t[frame_len + 2]; //分配空间,把循环队列数据转移到新分配的空间
  88. if (!p)
  89. {
  90. return;
  91. }
  92. for ( uint32_t k = 0; k < frame_len; k++)
  93. {
  94. c = GetQueue(i++); //取出循环队列一个数据
  95. p[k] = c; //转移
  96. frame_check += c; //校验求和
  97. }
  98. c = GetQueue(i++); //取出校验码和求和的内容进行对比
  99. if (c == frame_check && GetQueue(i) == END_CHAR) //如何比对内容一致且帧尾也无误
  100. {
  101. anaFun(p, frame_len); //回调处理
  102. }
  103. frame_state = NO_RECEIVED_START; //切换到重新等待0xa5状态
  104. frame_startpos = i + 1; //重新标记缓冲队列需要释放的位置
  105. delete[] p; //释放空间
  106. }
  107. else
  108. {
  109. break;
  110. }
  111. }
  112. else
  113. {
  114. break;
  115. }
  116. }
  117. }
  118. front = (front + frame_startpos) % MAXSIZE; //释放缓冲区
  119. }
  120. };
  121. #endif


新建main.cpp,我这个工程是在VS2010新建的:


 
 
  1. // Thread.cpp : 定义控制台应用程序的入口点。
  2. #include "stdafx.h"
  3. #include <iostream>
  4. #include "Queue.h"
  5. using namespace std;
  6. void Print(uint8_t *str, int len) //回调函数处理的内容,这里只是打印
  7. {
  8. for ( int i = 0; i < len; i++)
  9. {
  10. printf( "%d=%02x\r\n", i, str[i]);
  11. }
  12. }
  13. int _tmain( int argc, _TCHAR* argv[])
  14. {
  15. Queue queue(Print);
  16. uint8_t t[] = {
  17. 0xa5, 0x02, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x5a, //过滤掉了,数据超过了6K
  18. 0xa5, 0x02, 0x00, 0x00, 0x00, 0x02, 0x03, 0x05, 0x5a, //打印
  19. 0xa5, 0x02, 0x00, 0x00, 0x00, 0x04, 0x05, 0x00, 0x5a, //校验错了,过滤
  20. 0xaa, 0xa1, 0x33, //过滤,这些数据没有出现0xa5
  21. 0xa5, 0x03, 0x00, //有0xa5,等待续传,不能过滤
  22. };
  23. for ( int i = 0; i < sizeof(t); i++)
  24. {
  25. if ( queue.EnQueue(t[i]))
  26. {
  27. cout << "error"<< endl;
  28. }
  29. }
  30. queue.HandleData();
  31. printf( "len = %d\r\n", queue.QueueLength());
  32. getchar();
  33. return 0;
  34. }

 

调试内容,当然希望广大朋友测试,我这里面的校验采用是求和校验

 

 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值