串口数据出现分包如何正确完整接收
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
串口通信在QT上位机以及单片机或者安卓串口等使用情况下,经常容易出现一包数据分成几包的情况下,如何快速将这些分散的数据拼成完整一包相信很多单片机以及串口相关的开发人员都会遇到,可能很多简单的方式都能实现,但是一个有效耐得住考验少丢数据的方法也是很重要的。`
一、设计思路
1.数据的格式是头0XA4 0X4A 尾部是0X3C 0X3C,里面包含数据数据长度和帧校验等相关定义,本文只从头尾如何顺利提取到每一包。
2.思路1是每一次数据到来将数据保存起来,然后识别里面是否有尾部,如果没有,等待下一次,这样数据缓冲会越来越多,索引尾部多少会有点慢.
3.现在改为每来一次数据,识别当前数据是否有尾部,如果有,则认为当前数据是一包,则再将缓冲数据进行头部识别,识别不到头,证明是错误数据,不进行处理。识别成功,则调用相应处理函数。若数据中没有尾部,则将数据放入缓存。若数据最后一位是0X3C,则做好标记,下一包数据来临时优先判断第一个字节是不是0X3C。
若单次接收包含了多包数据也应能处理。
4、关于数据体中出现头或者尾的问题说明:
(1)本接收方式是以包进行读取,大部分情况下串口数据都是完整的一包数据,或者一包数据由于不连续分成两包三包。
(2)出现尾部为数据体中的尾,并非真实的尾—该情况需要满足1个极低概率的条件:恰好数据分包在数据体中含有尾部标志的地方。如 A4 4A 01 02 03 04 3C 3C (分包)02 3C 3C;
(3)数据体中有头部–头部的索引是从缓存的前面往后面搜索的,如果是数据体中出现头部是没有关系的,会忽略数据体中的头部。唯一可能的情况就是恰好分包的未知在数据体中的A4 4A之前,如: A4 4A 01 (分包)A4 4A 02 03 04 3C 3C 02 3C 3C;
(4)以上两种情况都是有概率会出现,但是都是极低,低到不可能会发生(源自于数据是按照包来的,并非单个字节出现的概率,即数据必须恰好分包在恰好重复数据的位置)。笔者认为可以不做考虑,如若为了更高级的安全性能,一般是对数据体加一个累加和校验或者CRC校验即可,当然出现此类情况,数据肯定是要丢弃了。
二、代码参考
QT代码参考
代码如下(示例):
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
void setupPlot();
void setCVMode();
void setCCMode();
void initActionsConnections();
private slots:
void updateView();
void updateLCDNumber();
void handleTimeout();
void showStatusMessage(const QString &message);
void about();
void openSerialPort();
void closeSerialPort();
void writeData(const QByteArray &data);
void readData();
void SerialRecv_solt();
void process_serial_recv(char *pBuf,int len);
void ComSend( unsigned char cmd1, int var1);
int get_tail(unsigned char *pbuf,int len);
int get_head(unsigned char *pbuf,int len);
int send_copy_from_read(unsigned char *pbuf,int len);
int process_pack(unsigned char* pbuf,int len);
void on_doubleSpinBox_vol_valueChanged(double arg1);
void on_doubleSpinBox_cur_valueChanged(double arg1);
void on_radioButton_cv_clicked();
void on_radioButton_cc_clicked();
void on_pushButton_verify_clicked();
void on_verticalSlider_vol_valueChanged(int value);
void on_verticalSlider_cur_valueChanged(int value);
void on_actionCtrl_triggered(bool checked);
void on_actionUpdate_triggered();
private:
Ui::MainWindow *ui;
QSerialPort *serial;
SettingsDialog *settings;
QVector<double> vol, cur;
QTimer *m_pTimer;
unsigned char data_recv[1024];
int len_pack=0;
int maybe_tail;//疑似拿到了尾部一个字节
};
int MainWindow::get_tail(unsigned char *pbuf,int len)
{
int i=0;
int pos;
for(i=0;i<len;i++)
{
if(pbuf[i]==0xc3)
{
if(i==(len-1))
{
return 0;//疑似拿到了尾部一个字节
}
else
{
if(pbuf[i+1]==0xc3)
pos=i+2;//第几个字节出现了尾部
return pos;//完整的拿到了尾部
}
}
}
return -1;//没有拿到尾部任何信息
}
/**
* @brief 获取头的位置
* @param pbuf [in/out]
* @param len [in/out]
* @return int
*/
int MainWindow::get_head(unsigned char *pbuf,int len)
{
int i=0;
for(i=0;i<len;i++)
{
if((pbuf[i]==0xA5)&&(i<(len-1)))
{
if(pbuf[i+1]==0x5A)
return i;
}
}
return -1;//没有头部信息
}
int MainWindow:: process_pack(unsigned char* pbuf,int len)
{
int pos_head=0;
memcpy(data_recv+len_pack,pbuf,len);
len_pack+=len;
pos_head=get_head(data_recv,len_pack);
if(pos_head>=0)//找到了头
{
send_copy_from_read(data_recv+pos_head,len_pack-pos_head);
qDebug() << "hello";
}
//相关标志位清零
maybe_tail=0;
len_pack=0;
}
/*------------------------ slot ----------------------------------------------*/
/**
******************************************************************************
* @brief 串口数据接收处理
******************************************************************************
**/
void MainWindow::SerialRecv_solt()
{
QByteArray data;
data.resize (serial->bytesAvailable());
serial->read (data.data(),data.size());
//int pos_tail=0;
//int pos_head=0;
int size_now=0;
int pos_data=0;
int data_num=0;
size_now=data.size();
if((maybe_tail==1)&&((unsigned char )data.data()[0]==0xc3))//识别到结束位
{
process_pack((unsigned char *)data.data(),1);
//将当前包剩余的数据赋值给下一包
if(data.size()>1)
{
memcpy(data_recv+len_pack,&data.data()[1],(data.size()-1));
len_pack+=(data.size()-1);
}
}
else
{
maybe_tail=0;
//size_now--剩余字节数
while(size_now>0)
{
pos_data=data.size()-size_now;
data_num=get_tail((unsigned char *)data.data()+pos_data,size_now);
if(data_num>0)//识别到结束位
{
process_pack((unsigned char *)data.data()+pos_data,data_num);
size_now=data.size()-data_num;
}
else//没有识别到结束位
{
if(data_num==0)
maybe_tail=1;
if((len_pack+data.size())<1024)//限制缓冲大小为1024个字节
{
memcpy(data_recv+len_pack,data.data()+pos_data,size_now);
len_pack+=size_now;
}
else
{
len_pack=0;//从头开始
}
break;
}
}
}
}
总结
该代码测试之后可以经过任意几包数据混合,断开发送,使用串口测试过。