嵌入式系统中,关于数据接受部分确实思考了很多,下面总结下个人经验。
关于串口传输,个人觉得采用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;
-
}
调试内容,当然希望广大朋友测试,我这里面的校验采用是求和校验