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

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

关于串口传输,个人觉得采用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;
}

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

时间: 2024-10-12 20:09:56

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

MINA粘包断包专题研究

一.前述: 近期做项目用到了MINA,其中遇到了一个断包与粘包的问题,困扰了我一天一夜,经过一天一夜的思索与查看其他大牛分享的资料,现将我在解决这一问题过程中的一些心得与解决问题的方法记录下来,供广大IT兄弟姐妹们参考,如有不对或欠妥之处,请指证.请不要吝惜分享您的技术,作为中国IT软件工程师,一定要想到多一个人掌握IT技术,不会给你增加一个竞争对手,如果认为会给你增加竞争对手,这种想法是非常狭隘的,自私自利的.只有分享,大家共同的技术提高了,才能激发出更多的思维解决更加棘手的技术难点,希望大家

c#网络通信框架networkcomms内核解析之七 数据包创建器(PacketBuilder)

PacketBuilder 数据包创建器,用于辅助创建数据包. 程序把Tcp连接上收到的二进制数据暂时存储在 packetBuilder中,如果收到的数据足够多,程序会把数据包包头解析出来,并根据数据包包头中的数据,解析出数据包大小,根据数据包大小,从PacketBuilder中截取相应的二进制数据,把这部分数据以内存流(MemoryStream)的形式,加上数据包包头一起交给NetworkComms.CompleteIncomingItemTask()方法进行处理. PacketBuilder

c#网络通信框架networkcomms内核解析之八 数据包的核心处理器

我们先回顾一个 c#网络通信框架networkcomms内核解析之六 处理接收到的二进制数据 中,主程序把PacketBuilder 中的数据交给核心处理器处理的过程 //创建优先级队列项目 PriorityQueueItem item = new PriorityQueueItem(priority, this, topPacketHeader, packetBuilder.ReadDataSection(packetHeaderSize, topPacketHeader.PayloadPac

友盟消息推送新版 SDK 为什么要支持多包名推送?

友盟消息推送 Android SDK 升级至 v 1.4.1 版本了!那问题来了,v 1.4.1 版本有什么新功能呢? “支持多包名推送”!新版 SDK 下载地址 开发者都知道,对于各种 Android 应用,特别是 Android 游戏而言,针对不同的渠道定制版本.使用不同的包名是运营中常见的一环,但是这样会导致后续使用消息推送时工作量过大,每个包都要单独推送,费时费力,还难以做到精准推送,效率低下……在不改变使用多包名的现状下,该如何提升消息推送效率? 友盟消息推送团队新推出的“多包名推送”

nginx防盗链+访问控制+限制指定目录运行php+解析支持php+现在user_agent

nginx防盗链 作用:防止其他网站引用本web站图片与视频资源,导致本站流量过大,从而造成不必要的经济开支:比如:本网站test.com有图片文件1.gif,而B网站使用test.com/1.gif 引用我们的图片,那么本网站的图片访问就会上升,但是带宽会增加,访问test.com的用户量却没有增加,出口带宽成本缺增加了: 编辑虚拟配置文件 vim /usr/local/nginx/conf/vhost/test.com.conf 增加代码 location ~* ^.+\.(gif|jpg|

2018最新mfc作为上位机接收硬件端USB或串口数据显示成图片 解决串口接收数据丢字节丢包问题

本文用的是VS2013MFC写串口数据接收: 第一步:首先建立一个MFC工程,成功后会跳出一个对话框,直接在对话框上点击右键->点击插入ACTIVAE控件->选择MicrosoftCommunications Control, version 6.0 成功后会显示一个电话的图标在对话框上,运行起来不会显示的 不用担心这个美观问题.如果没有这个插件的话,可能是版本太低  可以自己下载一个补上 第二步:大概的窗体搞好:   那个显示图片的大框是PICTURE控件变量 然后就要项目->类向导中

微信公众号开发之解析xml数据包

在上次开发的基础上(链接在这里:https://www.cnblogs.com/segho/p/11654719.html) 我们来对用户发送过来的消息进行解析: 需要用到dom4j.jar,自行百度下载资源 我们将xml文件解析文map键值对, 将其直观的反映在console控制台上 WxService中的parseRequest方法代码如下: 1 public static Map<String, String> parseRequest(InputStream is) { 2 Map&l

基于bootstrap的上传插件fileinput实现ajax异步上传功能(支持多文件上传预览拖拽)

首先需要导入一些js和css文件 ? 1 2 3 4 5 6 <link href="__PUBLIC__/CSS/bootstrap.css" rel="external nofollow" rel="stylesheet"> <link type="text/css" rel="stylesheet" href="__PUBLIC__/CSS/fileinput.css&qu

web大文件上传解决方案支持分片断点上传

一. 功能性需求与非功能性需求 要求操作便利,一次选择多个文件和文件夹进行上传:支持PC端全平台操作系统,Windows,Linux,Mac 支持文件和文件夹的批量下载,断点续传.刷新页面后继续传输.关闭浏览器后保留进度信息. 支持文件夹批量上传下载,服务器端保留文件夹层级结构,服务器端文件夹层级结构与本地相同. 支持大文件批量上传(20G)和下载,同时需要保证上传期间用户电脑不出现卡死等体验:支持文件夹上传,文件夹中的文件数量达到1万个以上,且包含层级结构. 支持断点续传,关闭浏览器或刷新浏览