- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
🐊🐊🐊🐊🐊好多小鳄鱼 。
Qt的确有自己的串口通信类,就是QSerialPort,但是我们在使用过程中因为要更加定制化的使用串口通信类减小开发的难度,所以我们会提供一个串口通信类,也就是这个SerialPortHelper类.
首先我们要知道什么是串口,串口通信就是机器和系统之间的一个通信协议,你可以将它理解为共享内存,可以根据需要向其中写入内容,然后在需要的时候从中读取数据。不过需要注意的是,在Qt的封装下,你不需要知道串口内的数据是否是给你的,还是你发的,因为统统都是你的.
大概了解了一下什么是串口通信,那么我们来看一下串口通信的通信手册大概是什么样.
由上我们可以看到,串口通信消息大概就是一串16进制的字符按照特定的规定,然后向串口中写入这些消息就可以了。比如前面这三个 41 54 64 这三个数字就是固定写死的,而 36 则是这个协议的通信号,是用于区分不同消息码的,比如上述这条指令的消息码是36,另外一条消息 。
然后后续标了颜色的内容就是实际上这条消息码中具体携带的数据,这个就不过多介绍了.
不过需要注意的是,硬件返回的数据或者向硬件发送的数据不一定都是像人一样的从左到右,有些命令特别是偏长的命令很多都是要求从右到左,也就是常说的小端序,低字节在前高字节在后,比如:
给出两端函数示例(实际上这两个函数在Qt中都有,这里只是展示一下具体是什么意思而已):
//将接收到的小端字节序数据转换为无符号整数
QString SerialPortHelper::getLittleEnd(const QByteArray& data)
{
if (data.size() > 8) return "";
qulonglong result = 0;
for (int i = 0; i < data.size(); ++i)
{
qulonglong tmp = (uchar)data[i];
result += tmp <<= i * 8;
}
return QString::number(result);
}
//大端字节序数据转换为浮点数
QString SerialPortHelper::getBigEndFlt(const QByteArray& data)
{
const int fltLen = 4;
if (data.size() != fltLen) return "null";
float result = 0;
uchar fltArr[fltLen];
for (int i = 0; i < fltLen; ++i)
{
fltArr[fltLen - i - 1] = data.at(i);
}
return QString::number(*(float*)fltArr, 'f', 9);
}
ok,接下来还有四位数字,这个要分开来说前两位和后两位.
其中前两位是CRC校验码,这个是需要对前面的数字进行一个基本的校验,具体我就不太懂了,这里提供一个函数,供参考:
void SerialPortHelper::CRC16_2(const QByteArray& ba, uchar* crcBuf)
{
int pos, i;
uchar* buf = (uchar*)ba.data();
int len = ba.size();
unsigned int crc = 0xFFFF;
for (pos = 0; pos < len; pos++)
{
crc ^= (unsigned int)buf[pos]; // XOR byte into least sig. byte of crc
for (i = 8; i != 0; i--) // Loop over each bit
{
if ((crc & 0x0001) != 0) // If the LSB is set
{
crc >>= 1; // Shift right and XOR 0xA001
crc ^= 0xA001;
}
else // Else LSB is not set
{
crc >>= 1; // Just shift right
}
}
}
//高低字节转换
crc = ((crc & 0x00ff) << 8) | ((crc & 0xff00) >> 8);
//qDebug() << QString().sprintf("CRC:%04x", crc);
crcBuf[0] = crc >> 8;
crcBuf[1] = crc;
}
最后两位就是固定的了,用两个固定搭配来分割各种字符段 。
我们一般使用串口类,主要流程如下:
1.获得基本参数: 我们需要获得这个串口的一些基本参数,其中包含内容如下:
QString portName = "NULL";
int baudRate = 921600;
QSerialPort::DataBits dataBits = QSerialPort::Data8;
QSerialPort::StopBits stopBits = QSerialPort::OneStop;
QSerialPort::Parity parity = QSerialPort::NoParity;
我们需要在启动这个端口调用的时候设置好这些属性,才能获取正确的COM口消息和发送消息,给出一个启动串口的示例:
serialPort = new QSerialPort();
serialPort->setPortName(param.portName);
if (serialPort->open(QIODevice::ReadWrite))
{
serialPort->setBaudRate(param.baudRate);
serialPort->setDataBits(param.dataBits);
serialPort->setStopBits(param.stopBits);
serialPort->setParity(param.parity);
//通知串口接收到消息的信号函数
connect(serialPort, &QSerialPort::readyRead, this, &CarControlModel::dataReceive);
qDebug() << "connect:" << param.portName;
emit checkConnableRetSig(true);
setStartTime();
//emit comConnStatus(true);
return true;
See?其实很简单的的,这个对象实际上就做了一件事,通知你现在来数据了。注意,这个readyRead 并不是发给你接到的数据,而是通知你现在串口接到消息了,而且这个消息还很有可能不是一条一条的,可能是一段一段的,就是消息可能不完整,这里给出一个示例.
void CarControlModel::dataReceive()
{
if (serialPort != nullptr && serialPort->isOpen())
{
QByteArray buffer = serialPort->readAll();
analysisData(buffer);
}
}
void CarControlModel::analysisData(const QByteArray& dataArr)
{
//消息断开的情况
qDebug() << "recv data: " << dataArr.toHex(' ');
dataBuff.append(dataArr);
QList<QByteArray> dataList;
for (;;)
{
int index = dataBuff.indexOf(QByteArray(gEnd, gEndLen));
if (index == -1) break;
dataList.append(dataBuff.mid(0, index + gEndLen));
dataBuff = dataBuff.right(dataBuff.size() - index - gEndLen);
}
if (dataList.size() == 0) return;
。。。。
也就是说,接到串口指令之后可能还需要你做一个解析,把接到的消息拆分出来处理.
发送指令的话,就比较简单了,比如:
qint64 CarControlModel::startCentralCMD()
{
QByteArray allArr, funDataArr;
funDataArr.clear();
funDataArr.append(0x32);
funDataArr.append(0x01); //0x01启动中心码盘
code(allArr, funDataArr);
return writeData(allArr);
}
//传入功能码数据区,组合成完整的指令存入数组
void CarControlModel::code(QByteArray& allArr, const QByteArray& funDataArr)
{
allArr.clear();
allArr.append(gId, gIdLen);
allArr.append(gAddress, gAddressLen);
allArr.append(funDataArr);
uchar crcBuf[gCrcLen];
CRC16_2(allArr, crcBuf);
allArr.append((char*)crcBuf, gCrcLen);
allArr.append(gEnd, gEndLen);
}
//串口发送消息出去
qint64 CarControlModel::writeData(const QByteArray& data)
{
qDebug() << "send data: " << data.toHex(' ');
qint64 ret = -1;
if (serialPort != nullptr && serialPort->isOpen())
{
ret = serialPort->write(data);
if (!serialPort->waitForBytesWritten(10000))
{
//emit sendWarningSig("发送失败:" + data.toHex(' '));
qDebug() << QString("发送失败:" + data.toHex(' '));
}
}
else
{
emit sendWarningSig("工控主串口未连接");
}
return ret;
}
最后此篇关于[Qt开发]一口气搞懂串口通信的文章就讲到这里了,如果你想了解更多关于[Qt开发]一口气搞懂串口通信的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
HTTP缓存相关的问题好像是前端面试中比较常见的问题了,上来就会问什么cache-control字段有哪些,有啥区别啥的。嗯……说实话,我觉得至少在本篇来说,HTTP缓存还算不上复杂,只是字段稍
代理,其实全称应该叫做代理服务器,它是客户端与服务器之间得中间层,本质上来说代理就是一个服务器,在HTTP的链路中插入的一个中间环节,就是代理服务器啦。所谓的代理服务就是指:服务本身不生产内容,
我们在前两篇的内容中分别学习了缓存和代理,大致了解了缓存有哪些头字段,代理是如何服务于服务器和客户端的,那么把两者结合起来,代理缓存,也就是说代理服务器也可以缓存,当客户端请求数据的时候,未必一
在前面的章节,我们把HTTP/1.1的大部分核心内容都过了一遍,并且给出了基于Node环境的一部分示例代码,想必大家对HTTP/1.1已经不再陌生,那么HTTP/1.1的学习基本上就结束了。这两
我们前一篇学习了HTTP/2,相比于HTTP/1,HTTP/2在性能上有了大幅的改进,但是HTTP/2因为底层还是基于TCP协议的,虽然HTTP/2在应用层引入了流的概念,利用多路复用解决了队头
前面我们花了很大的篇幅来讲HTTP在性能上的改进,从1.0到1.1,再到2.0、3.0,HTTP通过替换底层协议,解决了一直阻塞性能提升的队头阻塞问题,在性能上达到了极致。 那么,接下
上一篇噢,我们搞明白了什么是安全的通信,这个很重要,特别重要,敲黑板!! 然后,我们还学了HTTPS到底是什么,以及HTTPS真正的核心SSL/TLS是什么。最后我们还聊了聊TLS的实
经过前两章的学习,我们知道了通信安全的定义以及TLS对其的实现~有了这些知识作为基础,我们现在可以正式的开始研究HTTPS和TLS协议了。嗯……现在才真正开始。 我记得之前大概聊过,当
这一篇文章,我们核心要聊的事情就是HTTP的对头阻塞问题,因为HTTP的核心改进其实就是在解决HTTP的队头阻塞。所以,我们会讲的理论多一些,而实践其实很少,要学习的头字段也只有一个,我会在最开始
我们在之前的文章中介绍HTTP特性的时候聊过,HTTP是无状态的,每次聊起HTTP特性的时候,我都会回忆一下从前辉煌的日子,也就是互联网变革的初期,那时候其实HTTP不需要有状态,就是个浏览页面
前面几篇文章,我从纵向的空间到横向的时间,再到一个具体的小栗子,可以说是全方位,无死角的覆盖了HTTP的大部分基本框架,但是我聊的都太宽泛了,很多内容都是一笔带过,再加上一句后面再说就草草结束了。
大家好,我是煎鱼。 在 Go 语言中总是有一些看上去奇奇怪怪的东西,咋一眼一看感觉很熟悉,但又不理解其在 Go 代码中的实际意义,面试官却爱问... 今天要给大家介绍的是 SliceHead
我是一名优秀的程序员,十分优秀!