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

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

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

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

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

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

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

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

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

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

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

        新建Queue.h文件

#ifndef QUEUE_H
#define QUEUE_H
#define MAXSIZE	10240								//10K的RAM作为缓冲区
#define START_CHAR	0xa5
#define END_CHAR	0x5a
typedef unsigned char uint8_t;
typedef unsigned long uint32_t;
typedef enum {
	RES_OK = 0,
	RES_ERROR
} STATUS;

typedef enum {
	RECEIVED_START,
	NO_RECEIVED_START
} RECV_FSM;

typedef void (*AnalysisFun)(uint8_t *, int len);	//回调函数类型
class Queue {
private:
	uint32_t front;
	uint32_t rear;
	uint8_t data[MAXSIZE];
	AnalysisFun anaFun;
public:
	Queue(AnalysisFun cb)
	{
		front = 0;
		rear = 0;
		anaFun = cb;
	}
	~Queue()
	{
	}
	STATUS EnQueue(uint8_t e)
	{
		if ((rear + 1) % MAXSIZE == front)
		{
			return RES_ERROR;
		}
		data[rear] = e;
		rear = (rear + 1) % MAXSIZE;
		return RES_OK;  
	}
	int QueueLength()
	{
		return (rear - front + MAXSIZE) % MAXSIZE;
	}
	uint8_t GetQueue(int index)
	{
		return data[(front + index) % MAXSIZE];
	}
	void HandleData()
	{
		uint32_t frame_len, frame_startpos = 0;
		uint8_t frame_check, c;
		RECV_FSM frame_state = NO_RECEIVED_START;
		uint32_t len = (rear - front + MAXSIZE) % MAXSIZE;	//循环队列中数据长度
		for (uint32_t i = 0; i < len; i++)
		{
			if (frame_state == NO_RECEIVED_START)
			{
				if (GetQueue(i) == START_CHAR)				//接受到0xa5
				{
					frame_check = 0;
					frame_state = RECEIVED_START;			//切换,进而处理帧数据分析
				}
				else
				{
					frame_startpos = i + 1;					//标记缓冲队列需要释放的位置
				}
			}
			else if (frame_state == RECEIVED_START)
			{
				if (i + 4 <= len)							//长度4个字节
				{
					frame_len = ((uint32_t)GetQueue(i++));
					frame_len |= ((uint32_t)GetQueue(i++)) << 8;
					frame_len |= ((uint32_t)GetQueue(i++)) << 16;
					frame_len |= ((uint32_t)GetQueue(i++)) << 24;
					if (frame_len > 6137)					//不支持单包数据超过6K
					{
						frame_state = NO_RECEIVED_START;
						frame_startpos = i;					//标志需要释放的位置
						continue;
					}
					if (i + frame_len + 2 <= len)			//数据长度+校验+帧尾
					{
						uint8_t *p = new uint8_t[frame_len + 2];//分配空间,把循环队列数据转移到新分配的空间
						if (!p)
						{
							return;
						}
						for (uint32_t k = 0; k < frame_len; k++)
						{
							c = GetQueue(i++);				//取出循环队列一个数据
							p[k] = c;						//转移
							frame_check += c;				//校验求和			
						}
						c = GetQueue(i++);					//取出校验码和求和的内容进行对比
						if (c == frame_check && GetQueue(i) == END_CHAR)	//如何比对内容一致且帧尾也无误
						{
							anaFun(p, frame_len);			//回调处理
						}
						frame_state = NO_RECEIVED_START;	//切换到重新等待0xa5状态
						frame_startpos = i + 1;				//重新标记缓冲队列需要释放的位置
						delete[] p;							//释放空间
					}
					else
					{
						break;
					}
				}
				else
				{
					break;
				}
			}
		}
		front = (front + frame_startpos) % MAXSIZE;			//释放缓冲区
	}
};
#endif


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

// Thread.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Queue.h"
using namespace std;

void Print(uint8_t *str, int len)									//回调函数处理的内容,这里只是打印
{
	for (int i = 0; i < len; i++)
	{
		printf("%d=%02x\r\n", i, str[i]);
	}
}

int _tmain(int argc, _TCHAR* argv[])
{
	Queue queue(Print);				
	uint8_t t[] = {
		0xa5, 0x02, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x5a,		//过滤掉了,数据超过了6K
		0xa5, 0x02, 0x00, 0x00, 0x00, 0x02, 0x03, 0x05, 0x5a,		//打印
		0xa5, 0x02, 0x00, 0x00, 0x00, 0x04, 0x05, 0x00, 0x5a,		//校验错了,过滤
		0xaa, 0xa1, 0x33,											//过滤,这些数据没有出现0xa5
		0xa5, 0x03, 0x00,											//有0xa5,等待续传,不能过滤
	};
	for (int i = 0; i < sizeof(t); i++)
	{
		if (queue.EnQueue(t[i]))
		{
			cout << "error"<< endl;
		}
	}
	queue.HandleData();
	printf("len = %d\r\n", queue.QueueLength());
	getchar();
	return 0;
}

 

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

 

 



展开阅读全文

没有更多推荐了,返回首页