前言:
最近一段时间在做有关串口通讯的Qt项目,其中与MCU STM32那部分的通讯比较令人头疼,因为MCU处理的是HEX16进制数,而Qt应用更多的倾向于对字符串的处理,经过这段时间的痛苦而又煎熬的摸索,也算是有所新的认识。在这中秋佳节即将来临之际,也得空写点关于这方面的心得,算是一个记录笔记吧。
介绍:
项目情况
MCU STM32控制端:
几个控制按键挂在MCU上,随着按键的动作相应地发出指定数据帧,通过串口RX/TX传给ARM板端,Qt应用作出相应动作。
ARM Qt显示应用端:
需要Qt的应用能够接受到MCU 发出的数据帧,并且解析出有用的数据,进行相应动作。
MCU那边的串口通讯我就不说了,应该比较常见。我着重讲一下Qt这边怎么处理一个完整的数据帧。
相关知识点
一个完整的数据帧,包括帧头、标识符、数据长度、数据块、校验和帧尾等,比如:
帧头 | 各种标识 | ... | 数据长度 | 数据块 | 校验 | 帧尾 |
FF | EE | ... | 12 | 数据长度个字节 | ** | AA |
串口通讯中,以商议好的数据帧形式发送和接受数据,能够有效的避免数据接受不完整,接受错误。使用指定形式的数据帧是一种有效、准确的通讯方式。
校验:
数据在传输过程中,可能会存在数据出错的情况。为了保证数据传输的正确性,因此会采取一些方法来判断数据是否正确,或者在数据出错的时候及时发现进行改正。常用的几种数据校验方式有奇偶校验、CRC校验、LRC校验、格雷码校验、和校验、异或校验等。
一般串口通讯中常用到的校验有CRC32、CRC16、和校验或是异或校验等。如果大家有兴趣可以多找一找这方面的资料看看,我在这里就不多做赘述。讲的比较详细:
https://blog.csdn.net/zhengqijun_/article/details/53150749
划重点————————————————————————————————————————————————————
Qt 解析完整数据帧
MCU发出的是16进制数据帧,Qt善于处理QString的字符串。
我先说一下Qt 解析部分大概思路:1. 串口读取为二进制字节组---QByteArray;2. 将QByteArray 写入数据流QDataStream,而后从中按一个一个字节读取出,直到结束;3. 按数据帧协议处理一个字节一个字节,环环相扣,引入状态机机制,解析完整帧。
直接上代码。Talk is cheap, show me the code.
头文件声明:
QString buffer;//串口接受数据缓冲区
QString bufferdatalen;//数据长度两字节字符串缓存
QString frameData;//
uint8_t checkSum, SendBuf[200];//校验异或和
ushort TxNum;
int num;
int state_machine;//协议解析状态机
int lencnt, datalen;
bool stopped;
bool ok;
CPP文件解析函数一览:
//读取串口数据帧并处理
void SerialPortA::readMyComA(QByteArray temp)
{
QDataStream out(&temp, QIODevice::ReadWrite);
// qDebug()<<"MyComA temp hex"<<temp.toHex();
while (!out.atEnd()) {
qint8 outChar = 0;
out>>outChar;
QString byte = QString("%1").arg(outChar&0xFF, 2, 16, QLatin1Char('0'));
// qDebug()<<"byte"<<byte;
byte = byte.toUpper();
if(state_machine == 0)//协议解析状态机
{
if(byte == INC1)//起始符1
state_machine = 1;
else
state_machine = 0;//状态机复位
}
else if(state_machine == 1)
{
if(byte == INC2)//起始符2
state_machine = 2;
else
state_machine = 0;//状态机复位
}
else if(state_machine == 2)
{
if(byte == INC3)//起始符3
state_machine = 3;
else
state_machine = 0;//状态机复位
}
else if(state_machine == 3)
{
if(byte == FRAMESTAR)//帧头
state_machine = 4;//
else
state_machine = 0;
}
else if(state_machine == 4)
{
if(byte == FRAMETYPE)//帧类型
{
state_machine = 5;
buffer = byte;
}
else
state_machine = 0;
}
/×××××××××中间的部分按照各自的协议数据帧进行处理×××××××××××××/
else if(state_machine == 12)//第一字节数据长度
{
buffer += byte;
bufferdatalen = byte;
state_machine = 13;
}
else if(state_machine == 13)//第二字节数据长度
{
buffer += byte;
bufferdatalen += byte;
lencnt = 0;//接收数据计数器
datalen = bufferdatalen.toInt(&ok, 16);//接收数据长度
// qDebug()<<"datalen is :"<<datalen<<"bufferdatalen"<<bufferdatalen;
state_machine = 14;
}
else if((state_machine == 14) || (state_machine == 15))//接收一定长度的数据块
{
frameData += byte;//数据保存
buffer += byte;
if(lencnt == (datalen-1))
state_machine = 16;
else
{
state_machine = 15;
++lencnt;
}
}
else if(state_machine == 16)//进行校验
{
TxNum = 0;
if(buffer.isEmpty()) return;
// buffer += "AA";//加上帧尾
// qDebug()<<"用于计算校验位的字符串: "<<buffer;
QByteArray temp;
temp = QByteArray::fromHex(buffer.toLatin1().data());//获取buffer数据为字符串,需要转换成16进制
QDataStream out(&temp, QIODevice::ReadWrite);//将字节组读入
while(!out.atEnd())
{
qint8 outchar = 0;
out >> outchar;//每字节填充一次,直到结束
SendBuf[TxNum] = (uint8_t)(outchar & 0xff);
TxNum++;
}
checkSum = (uint8_t)(myAlgorithm->Sum_Calculate(SendBuf, TxNum));//求帧头以外所有数据的校验和
QString strSum = QString("%1").arg(checkSum&0xFF, 2, 16, QLatin1Char('0'));
strSum = strSum.toUpper();
byte = byte.toUpper();
// qDebug()<<"Check SUM is :"<<strSum<<"Byte is:"<<byte;
if(byte == strSum)//判断校验XOR是否相等
{
state_machine = 17;
// qDebug()<<"!!!!!!!!!!!!!!!!!!!!!!!";
sendOneFrame(CORRECT_RESPONSE);//校验无异常
}
else
{
state_machine = 0;
sendOneFrame(ERROR_RESPONSE);//校验异常
}
}
else if(state_machine == 17)
{
// qDebug()<<"*************"<<byte;
if((byte == "AA") || (byte == "aa")) //判断是否接收到帧尾结束符
{
// qDebug()<<"frame data :"<<frameData<<"buffer"<<buffer;
// qDebug()<<"MyComA Receive Frame End AA!!!";
/****************数据块8字节分析*********************/
byte8Analysis(frameData);
/*****************数据块8字节分析********************/
}
frameData = "";
buffer = "";
state_machine = 0;//状态机复位
}
}
}
校验计算函数:
uint8_t Algorithm::Sum_Calculate(uint8_t *puchMsg, uint16_t usDataLen)
{
uint8_t CRCValue=0;
uint16_t i=0;
for ( i=0; i<usDataLen; i++ )
{
CRCValue = puchMsg[i] ^ CRCValue;//进行异或校验取值
}
return CRCValue;
}
解析部分,按照自身协商的数据帧,实际情况进行处理分析。大概的思路已经给出。
在此,也感特别谢@yuxiangs的转发博客给予的思路:
https://blog.csdn.net/yilongdashi/article/details/80449315#comments
以上仅是个人对qt中串口使用的大概了解,各位有补充的欢迎留言指导及斧正,谢谢大家,预祝大家中秋佳节快乐。