嵌入式系统中,关于数据接受部分确实思考了很多,下面总结下个人经验。
关于串口传输,个人觉得采用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