Qt实现串口通信示例

前言:以下串口通信示例,参考了现有网上前辈们的资源,最后结合部分个人的思想,所以下述博客会将实现的原理及代码的案例进行公开。

这里我们先上效果图:

一、串口通信简介

串口通信是上下位机进行通信的一种常用的通信协议,大部分单片机中都有一到多个串口资源经过简单的配置就可以实现上下位机的通信,下图是串口通信协议中的一种形式。如果你不是用硬件描述语言去实现一个串口,这部分了解下即可。常用的是8位数据位加起始位加停止位,因此串口是只能发送0-255区间的数据。因此想要发送int或者float型的数据需要按高地位或者到内存中取出数据来发送,此时就需要通信协议来确保数据的准确性,接下来将依次进行介绍。

二、串口通信各参数的含义

了解完串口通信的大致含义,我们再编程时候比较关心的还是串口通信中波特率、奇偶校验、数据位以及停止位的含义。下面我们引用一位前辈作出的解释,有关本文中涉及到的引用会在本文最后附上原创的出处,为了美观,不在相应的地方附上版权声明了。

简介

串口是一种非常通用的设备通信的协议(不要与通用串行总线Universal Serial Bus(USB)混淆)。大多数计算机包含两个基于RS232的串口。串口同时也是仪器仪表设备通用的通信协议;很多GPIB兼容的设备也带有RS-232口。同时,串口通信协议也可以用于获取远程采集设备的数据。
串口通信的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。比如IEEE488定义并行通行状态时,规定设备线总长不得超过20米,并且任意两个设备间的长度不得超过2米;而对于串口而言,长度可达1200米。
典型地,串口用于ASCII码字符的传输。通信使用3根线完成:(1)地线,(2)发送,(3)接收。由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据。其他线用于握手,但不是必须的。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通行的端口,这些参数必须匹配:
波特率

这是一个衡量符号传输速率的参数。它表示每秒钟传送的符号的个数。例如300波特表示每秒钟发送300个符号。当我们提到时钟周期时,我们就是指波特率,例如如果协议需要4800波特率,那么时钟是4800Hz。这意味着串口通信在数据线上的采样率为4800Hz。通常电话线的波特率为14400,28800和36600。波特率可以远远大于这些值,但是波特率和距离成反比。高波特率常常用于放置的很近的仪器间的通信,典型的例子就是GPIB设备的通信。
数据位

这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据不会是8位的,标准的值是5、6、7和8位。如何设置取决于你想传送的信息。比如,标准的ASCII码是0~127(7位)。扩展的ASCII码是0~255(8位)。如果数据使用简单的文本(标准 ASCII码),那么每个数据包使用7位数据。每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。由于实际数据位取决于通信协议的选取,术语“包”指任何通信的情况。
停止位

用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
奇偶校验位

在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。例如,如果数据是011,那么对于偶校验,校验位为0,保证逻辑高的位数是偶数个。如果是奇校验,校验位为1,这样就有3个逻辑高位。高位和低位不是真正的检查数据,简单置位逻辑高或者逻辑低校验。这样使得接收设备能够知道一个位的状态,有机会判断是否有噪声干扰了通信或者是否传输和接收数据是否不同步。
2.补充

比特率
在数字信道中,比特率是数字信号的传输速率,它用单位时间内传输的二进制代码的有效位(bit)数来表示,其单位为每秒比特数bit/s(bps)、每秒千比特数(Kbps)或每秒兆比特数(Mbps)来表示(此处K和M分别为1000和1000000,而不是涉及计算机存储器容量时的1024和1048576)。
波特率
波特率指数据信号对载波的调制速率,它用单位时间内载波调制状态改变次数来表示,其单位为波特(Baud)。 波特率与比特率的关系为:比特率=波特率X单个调制状态对应的二进制位数。
显然,两相调制(单个调制状态对应1个二进制位)的比特率等于波特率;四相调制(单个调制状态对应2个二进制位)的比特率为波特率的两倍;八相调制(单个调制状态对应3个二进制位)的比特率为波特率的三倍;依次类推。
RS232是要用在近距离传输上最大距离为30M
RS485用在长距离传输最大距离1200M

三、Qt中的串口通信

qt中集成了QSerialport类,可以直接使用该类实现串口通信。这部分内容看到网上有的说在建立工程的时候要选择Qt Project  Setting ,不过我在使用Qt5.12建立工程的时候仍然按照普通项目工程建立(这里不再额外叙述怎么New 一个Project了),如果Qt版本低的话,工程失败情况下可以另行找工程建立方法。

在qt中使用串口通信仍然比较简单,只需要在.pro文件中加入下述代码就完成了。

QT       += core gui serialport

然后在工程的头文件中引用相关的头文件。

#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>

接着在类中将串口类进行实例化。

    QSerialPort *serialPort;

以上的过程,我们就已经准备好了串口通信所需要的必要条件,接下来要使用串口通信,少不了的要对串口实例化的对象进行参数的设定,下面我们对串口进行初始化。

void Serial_port::InitPort()
{const auto infos = QSerialPortInfo::availablePorts();for(const QSerialPortInfo &info : infos){QSerialPort serial;serial.setPort(info);if(serial.open(QIODevice::ReadWrite)){ui->PortBox->addItem(info.portName());serial.close();}}QStringList baudList;   //波特率QStringList parityList; //校验位QStringList dataBitsList;   //数据位QStringList stopBitsList;   //停止位// 波特率    //波特率默认选择下拉第三项:9600baudList<<"1200"<<"2400"<<"4800"<<"9600"<<"14400"<<"19200"<<"38400"<<"56000"<<"57600"<<"115200";ui->BaudBox->addItems(baudList);ui->BaudBox->setCurrentIndex(3);// 校验      //校验默认选择无parityList<<"无"<<"奇"<<"偶";ui->ParityBox->addItems(parityList);ui->ParityBox->setCurrentIndex(0);// 数据位      //数据位默认选择8位dataBitsList<<"5"<<"6"<<"7"<<"8";ui->DataBox->addItems(dataBitsList);ui->DataBox->setCurrentIndex(3);// 停止位      //停止位默认选择1位stopBitsList<<"1"<<"2";ui->StopBox->addItems(stopBitsList);ui->StopBox->setCurrentIndex(0);
}

其中初始化函数 中首先获取计算机中有效的端口号,然后将端口号的名称给端口选择控件。相应的波特率等也是赋予选择控件相应的值。setCurrentIndex()是给控件设定默认下拉项的index。

经过对串口对象指针的初始化,就已经完成串口通信所要求的各参数配置,一个完整的通信当然也少不了串口的收数了,下面给出串口数据的收报函数。

void Serial_port::readData()
{QByteArray buf;if (serialPort){buf = serialPort->readAll();if (!buf.isEmpty()){receBytes += buf.size();QString redata = QString("received:%1").arg(QString::number(receBytes));ui->sendlabel->setText(redata);QString myStrTemp = QString::fromLocal8Bit(buf); //支持中文显示if(ui->reDisplay->isChecked()){QString str = ui->textBrowser->toPlainText();str +=myStrTemp;ui->textBrowser->clear();ui->textBrowser->append(str);}}buf.clear();}}

其实,从上面的函数中我们就能发现qt中串口的收报函数很简单,只是需要下面的一行命令,就能够完成了串口的收数功能。

    buf = serialPort->readAll();

而相应的我们并不会满足这样单调的功能,所以在收报函数中加入接收总字节数显示以及显示收到的内容,这样的功能。

        receBytes += buf.size();QString redata = QString("received:%1").arg(QString::number(receBytes));ui->sendlabel->setText(redata);
            ui->textBrowser->append(str);

说完了串口通信的收报,我们先不提发报的事情,从相关的串口助手上我们都知道,在显示接收的数据时候,很多情况下要求显示收到报文的16进制形式,所以这里我们再写另外一种收报函数(以16进制接收并显示)。

void Serial_port::readToHex()
{QByteArray temp = serialPort->readAll();auto isShow = ui->reDisplay->isChecked();         //接收显示?QDataStream out(&temp,QIODevice::ReadOnly);    //将字节数组读入while(!out.atEnd()){qint8 outChar = 0;out>>outChar;   //每字节填充一次,直到结束//十六进制的转换QString str = QString("%1").arg(outChar&0xFF,2,16,QLatin1Char('0'));if(isShow){ui->textBrowser->insertPlainText(str.toUpper());//大写ui->textBrowser->insertPlainText(" ");//每发送两个字符后添加一个空格ui->textBrowser->moveCursor(QTextCursor::End);}}}

最后让我们完成串口通信的发报功能,这部分其实很简单,但为了追求发报功能的完整性,所以我们丰富这部分函数的功能,同样的将发送功能分为文本发送和以16进制形式的发送。

void Serial_port::on_sendButton_clicked()
{//Latin1是ISO-8859-1的别名,有些环境下写作Latin-1。ISO-8859-1编码是单字节编码,向下兼容ASCII//其编码范围是0x00-0xFF,0x00-0x7F之间完全和ASCII一致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号。QString str = ui->lineEdit->text();if(!str.isEmpty()){auto isHexSend = ui->sHexRadio->isChecked();int len = str.length();if(len%2 == 1){str = str.insert(len-1,'0');}QByteArray senddata;if(isHexSend){StringToHex(str,senddata);serialPort->write(senddata);if(serialPort->write(senddata)<0){QMessageBox::critical(this, tr("Error"), serialPort->errorString());}}else{if(serialPort->write(ui->lineEdit->text().toLocal8Bit())<0){QMessageBox::critical(this, tr("Error"), serialPort->errorString());}}}}

字符转换为16进制函数如下:

void Serial_port::StringToHex(QString str, QByteArray &senddata)
{int hexdata,lowhexdata;int hexdatalen = 0;int len = str.length();senddata.resize(len/2);char lstr,hstr;for(int i=0; i<len; ){//char lstr,hstr=str[i].toLatin1();if(hstr == ' '){i++;continue;}i++;if(i >= len)break;lstr = str[i].toLatin1();hexdata = convertHexChart(hstr);lowhexdata = convertHexChart(lstr);if((hexdata == 16) || (lowhexdata == 16))break;elsehexdata = hexdata*16+lowhexdata;i++;senddata[hexdatalen] = (char)hexdata;hexdatalen++;}senddata.resize(hexdatalen);}
char Serial_port::convertHexChart(char ch)
{if((ch >= '0') && (ch <= '9'))return ch-0x30;  // 0x30 对应 ‘0’else if((ch >= 'A') && (ch <= 'F'))return ch-'A'+10;else if((ch >= 'a') && (ch <= 'f'))return ch-'a'+10;//        else return (-1);else return ch-ch;//不在0-f范围内的会发送成0}

四、解码

对于串口通信来说,收报是重要的一步但也不是最后的一步,在进行开发时,我们不但要完成数据的接收,同样要根据协议双方的约定完成报文的解码,再进行相应算法的开发,解码的复杂程度依赖于协议的复杂程度,这里我们仅仅提供一种简单的解码校验方式。

假设上图是我们接收的一个完整报的形式,其中ox55和0x53表示报文头,中间的校验位等暂不考虑,最后的sum表示和校验位。和校验即通过数据的累加和去验证数据的正确与否。上图是一帧需要发送的数据可以看从左到右分别为帧头,功能字,数据帧与和校验,还可以加上数据位数。帧头和功能字可以根据自己的需求进行定制,数据位即需要发送的数据,最后的SUM即为无符号char型数据,将SUM之前的数据求和(不用管数据的溢出)即可。下位机按这种方式将数据发送上来上位机只要找到帧头即可进行解码并且对数据进行验证,这里附一个简单的上位机解码的程序,之前用来解码下位机发送来的欧拉角数据使用的程序。

void uart::ReadData()
{ui.buffSize->setText(QString::number(serialPort.bytesAvailable()));int buffersize = ui.bufferSize->value();if (serialPort.bytesAvailable()>buffersize){   //更改过滤个数,提高通信速率requestData = serialPort.readAll().toHex();//转成 hex}if (!requestData.isEmpty() ){QByteArray temp = requestData.mid(QString(requestData).indexOf("55"), 22);unsigned char buffer[11] = { 0 };unsigned char sum = 0;for (int i = 0; i < 11; i++){buffer[i] = (ByteArrayToHexchar(temp.at((i << 1) )) << 4 )+ ByteArrayToHexchar(temp.at((i << 1) + 1 ));if (i<10)sum = sum + buffer[i];}//sum = buffer[0] + buffer[1] + buffer[2] + buffer[3] + buffer[5] + buffer[6];if ((buffer[0] == 0x55) && (buffer[1] == 0x53))if (sum == buffer[10]){float x, y, z;x = (buffer[3] << 8 | buffer[2]);y = (buffer[5] << 8 | buffer[4]);z = (buffer[7] << 8 | buffer[6]);x = x / 32768 * 180;y = y / 32768 * 180;z = z / 32768 * 180;ui.x->setText(QString::number(x, 'f', 2));ui.y->setText(QString::number(y, 'f', 2));ui.z->setText(QString::number(z, 'f', 2));//ui.textEdit->append(QString::number(z, 'f', 2));requestData.clear();}}if (!timer.isActive())timer.start(5000);//500ms定时器
}

当然如果不考虑和校验位置的话就更简单了,假设我们的协议规定了一个完整报的长度为40字节,报文头为ox03和0x66,然后我们要从第15位和第16位解码出一个int数据,这里将去掉大部分个人的内容。

        if(temp.size()==40){if((recvdata[0]==0x03)&&(recvdata[1]==0x66)){target_data.e=(int)(recvdata[15]+recvdata[16]*256);}}

五、发报模拟器

这里我们嵌入到demo中作为其中的一个小功能了,其中路径后面的输入框,在进行单击时候会打开文件系统框要求你去选取txt文件,然后点击播放按钮将读取txt中的内容(这里我的txt所有的数据都在一行,如果是分行的txt需要你自己去更改相应的代码)然后我这里每次模拟发报40个字节。

void Serial_port::handleLineEditClicked()
{//QString curPath = QDir::currentPath();QString curPath = "../QSerial_port";//QFile file;QString aFileName = QFileDialog::getOpenFileName(this,QString("选择文件"),curPath,QString("TEXT(*.txt)"));ui->m_txt->setText(aFileName);}
void Serial_port::on_Playbutton_clicked()
{QString path = ui->m_txt->text();//qDebug()<<"是否成功获取"<<path_ce->text();if(path.isEmpty())return;QFile aFile(path);if(!aFile.exists())     //文件不存在qDebug() <<"the file not exist!!";//return;if(!aFile.open(QIODevice::ReadOnly | QIODevice::Text))qDebug() <<"文件无法打开";//return;QTextStream aStream(&aFile);    //用文件流读取文件while (!aStream.atEnd()){QString qstr = aStream.readLine();   //读取文件的一行文本//qDebug()<<qstr.length();int len = qstr.length();if(len%2 == 1){qstr = qstr.insert(len-1,'0');}QByteArray senddata;StringToHex(qstr,senddata);//qDebug()<<senddata.size();for(int i=0;i<senddata.size();i++){QByteArray tmpsenddata=senddata.mid(i,40);i=i+39;//qDebug()<<tmpsenddata.length();serialPort->write(tmpsenddata);if(!serialPort->waitForBytesWritten())   //这一句很关键,决定是否能发送成功{qDebug()<<"serial write error";}Sleep(1000);}}}

到这里基本就完成了串口通信的全部内容(上述代码设计到了工程的ui控件,请自行修改),另外额外提供一点,在模拟器中如果循环对串口数据进行发报,我们都知道,核心的代码就是

            serialPort->write(tmpsenddata);

但是当我们在进行发报的时候,会发现哪怕程序执行了write功能,收报方仍然无法收到,这里有人提出qt中的串口通信在发送后必须要进行发送是否成功的判断才能够正常工作,当然,这一点在循环中执行write是最明显看到的,单个命令可能是正常的,所以这里我们必须要加上判断。

            if(!serialPort->waitForBytesWritten())   //这一句很关键,决定是否能发送成功{qDebug()<<"serial write error";}

好了到此Qt的串口通信就完全结束了,为了更直观的看到各功能的实现,下面给出完整的代码,如果还不明白,点击最后链接下载完整工程唠!

Serial_port.h

#ifndef SERIAL_PORT_H
#define SERIAL_PORT_H#include <QMainWindow>
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>
#include <QLabel>
#include <QTimer>
#include <windows.h>
#include <QString>
#include <QDebug>
#include <dbt.h>
#include <devguid.h>
#include <setupapi.h>
#include <initguid.h>
#include <list>
using namespace std;namespace Ui {
class Serial_port;
}class Serial_port : public QMainWindow
{Q_OBJECTpublic:explicit Serial_port(QWidget *parent = nullptr);void InitPort();~Serial_port();private slots:void readData();    //读取数据void timeToSend();  //定时发送void on_OpenButton_clicked();void on_r_clearButton_clicked();void on_s_clearButton_clicked();void on_sendButton_clicked();void StringToHex(QString str,QByteArray &senddata); //发送时进制转换char convertHexChart(char ch);  //16进制转换void readToHex();   //将读取的数据以16进制显示void Mdisplay();//void pasingData(QByteArray &array); //解析串口数据void handleLineEditClicked();void on_Playbutton_clicked();private:Ui::Serial_port *ui;QSerialPort *serialPort;QTimer *time;static   int sendBytes;static   int receBytes;
};
#endif // SERIAL_PORT_H

Serial_port.cpp

#include "Serial_port.h"
#include "ui_Serial_port.h"
#include <QMessageBox>
#include "m_lineEdit.h"
#include <QDebug>
#include <QFileDialog>Serial_port::Serial_port(QWidget *parent) :QMainWindow(parent),ui(new Ui::Serial_port)
{ui->setupUi(this);time = new QTimer(this);InitPort();//设置默认值ui->PortBox->setCurrentIndex(1);ui->BaudBox->setCurrentIndex(7);ui->StopBox->setCurrentIndex(0);ui->DataBox->setCurrentIndex(3);ui->ParityBox->setCurrentIndex(0);ui->sendButton->setEnabled(false);ui->sHexRadio->setEnabled(false);ui->sTextRadio->setChecked(true);ui->sTextRadio->setEnabled(false);ui->rTextRadio->setChecked(true);ui->rHexRadio->setChecked(false);ui->reDisplay->setChecked(true);connect(ui->reSendCheck, &QCheckBox::stateChanged,this, &Serial_port::timeToSend);         //自动重发connect(time, &QTimer::timeout, this, &Serial_port::on_sendButton_clicked);//connect(ui->rHexRadio, &QRadioButton::toggled, this, &Serial_port::Mdisplay);connect(ui->m_txt, SIGNAL(clicked()), this, SLOT(handleLineEditClicked()));}int Serial_port::sendBytes = 0;
int Serial_port::receBytes = 0;void Serial_port::InitPort()
{const auto infos = QSerialPortInfo::availablePorts();for(const QSerialPortInfo &info : infos){QSerialPort serial;serial.setPort(info);if(serial.open(QIODevice::ReadWrite)){ui->PortBox->addItem(info.portName());//qDebug()<<info.portName();serial.close();}}QStringList baudList;   //波特率QStringList parityList; //校验位QStringList dataBitsList;   //数据位QStringList stopBitsList;   //停止位// 波特率    //波特率默认选择下拉第三项:9600baudList<<"1200"<<"2400"<<"4800"<<"9600"<<"14400"<<"19200"<<"38400"<<"56000"<<"57600"<<"115200";ui->BaudBox->addItems(baudList);ui->BaudBox->setCurrentIndex(3);// 校验      //校验默认选择无parityList<<"无"<<"奇"<<"偶";ui->ParityBox->addItems(parityList);ui->ParityBox->setCurrentIndex(0);// 数据位      //数据位默认选择8位dataBitsList<<"5"<<"6"<<"7"<<"8";ui->DataBox->addItems(dataBitsList);ui->DataBox->setCurrentIndex(3);// 停止位      //停止位默认选择1位stopBitsList<<"1"<<"2";ui->StopBox->addItems(stopBitsList);ui->StopBox->setCurrentIndex(0);
}Serial_port::~Serial_port()
{delete serialPort;delete time;delete ui;
}void Serial_port::readData()
{QByteArray buf;if (serialPort){buf = serialPort->readAll();if (!buf.isEmpty()){receBytes += buf.size();QString redata = QString("received:%1").arg(QString::number(receBytes));ui->sendlabel->setText(redata);QString myStrTemp = QString::fromLocal8Bit(buf); //支持中文显示if(ui->reDisplay->isChecked()){QString str = ui->textBrowser->toPlainText();str +=myStrTemp;ui->textBrowser->clear();ui->textBrowser->append(str);}}buf.clear();}}void Serial_port::timeToSend()
{if(ui->reSendCheck->isChecked()){if(time->isActive()){return;}else{int ms = ui->spinBox->value();time->start(ms);}}else{if(time->isActive()){time->stop();}else{return;}}}void Serial_port::on_OpenButton_clicked()
{if (ui->OpenButton->text() == tr("打开串口")){serialPort = new QSerialPort;serialPort->setPortName(ui->PortBox->currentText());if(serialPort->open(QIODevice::ReadWrite)){//qDebug()<<ui->BaudBox->currentIndex();switch (ui->BaudBox->currentIndex()) {case 0:serialPort->setBaudRate(QSerialPort::Baud1200);break;case 1:serialPort->setBaudRate(QSerialPort::Baud2400);break;case 2:serialPort->setBaudRate(QSerialPort::Baud4800);break;case 3:serialPort->setBaudRate(QSerialPort::Baud9600);break;case 4:serialPort->setBaudRate(QSerialPort::Baud19200);break;case 5:serialPort->setBaudRate(QSerialPort::Baud38400);break;case 6:serialPort->setBaudRate(QSerialPort::Baud57600);break;case 7:serialPort->setBaudRate(QSerialPort::Baud115200);break;default:break;}switch (ui->StopBox->currentIndex()) {case 0:serialPort->setStopBits(QSerialPort::OneStop);break;case 1:serialPort->setStopBits(QSerialPort::TwoStop);break;default:break;}switch (ui->DataBox->currentIndex()) {case 0:serialPort->setDataBits(QSerialPort::Data5);break;case 1:serialPort->setDataBits(QSerialPort::Data6);break;case 2:serialPort->setDataBits(QSerialPort::Data7);break;case 3:serialPort->setDataBits(QSerialPort::Data8);break;default:break;}switch (ui->ParityBox->currentIndex()) {case 0:serialPort->setParity(QSerialPort::NoParity);break;case 1:serialPort->setParity(QSerialPort::OddParity);break;case 2:serialPort->setParity(QSerialPort::EvenParity);break;default:break;}ui->OpenButton->setText(tr("关闭串口"));ui->PortBox->setEnabled(false);ui->BaudBox->setEnabled(false);ui->StopBox->setEnabled(false);ui->DataBox->setEnabled(false);ui->ParityBox->setEnabled(false);ui->sendButton->setEnabled(true);ui->sTextRadio->setEnabled(true);ui->sHexRadio->setEnabled(true);connect(serialPort, &QSerialPort::readyRead, this, &Serial_port::readData);connect(serialPort, &QSerialPort::readyRead, this, &Serial_port::readToHex);}else{QMessageBox::critical(this, tr("Error"), serialPort->errorString());}}else{serialPort->clear();serialPort->close();serialPort->deleteLater();ui->sendButton->setEnabled(false);ui->OpenButton->setText(tr("打开串口"));ui->PortBox->setEnabled(true);ui->BaudBox->setEnabled(true);ui->StopBox->setEnabled(true);ui->DataBox->setEnabled(true);ui->ParityBox->setEnabled(true);ui->sHexRadio->setEnabled(false);ui->sTextRadio->setEnabled(false);}}void Serial_port::on_r_clearButton_clicked()
{ui->textBrowser->clear();}void Serial_port::on_s_clearButton_clicked()
{ui->lineEdit->clear();
}void Serial_port::on_sendButton_clicked()
{//Latin1是ISO-8859-1的别名,有些环境下写作Latin-1。ISO-8859-1编码是单字节编码,向下兼容ASCII//其编码范围是0x00-0xFF,0x00-0x7F之间完全和ASCII一致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号。QString str = ui->lineEdit->text();if(!str.isEmpty()){auto isHexSend = ui->sHexRadio->isChecked();int len = str.length();if(len%2 == 1){str = str.insert(len-1,'0');}QByteArray senddata;if(isHexSend){StringToHex(str,senddata);serialPort->write(senddata);if(serialPort->write(senddata)<0){QMessageBox::critical(this, tr("Error"), serialPort->errorString());}}else{if(serialPort->write(ui->lineEdit->text().toLocal8Bit())<0){QMessageBox::critical(this, tr("Error"), serialPort->errorString());}}}}void Serial_port::StringToHex(QString str, QByteArray &senddata)
{int hexdata,lowhexdata;int hexdatalen = 0;int len = str.length();senddata.resize(len/2);char lstr,hstr;for(int i=0; i<len; ){//char lstr,hstr=str[i].toLatin1();if(hstr == ' '){i++;continue;}i++;if(i >= len)break;lstr = str[i].toLatin1();hexdata = convertHexChart(hstr);lowhexdata = convertHexChart(lstr);if((hexdata == 16) || (lowhexdata == 16))break;elsehexdata = hexdata*16+lowhexdata;i++;senddata[hexdatalen] = (char)hexdata;hexdatalen++;}senddata.resize(hexdatalen);}char Serial_port::convertHexChart(char ch)
{if((ch >= '0') && (ch <= '9'))return ch-0x30;  // 0x30 对应 ‘0’else if((ch >= 'A') && (ch <= 'F'))return ch-'A'+10;else if((ch >= 'a') && (ch <= 'f'))return ch-'a'+10;//        else return (-1);else return ch-ch;//不在0-f范围内的会发送成0}void Serial_port::readToHex()
{QByteArray temp = serialPort->readAll();auto isShow = ui->reDisplay->isChecked();         //接收显示?QDataStream out(&temp,QIODevice::ReadOnly);    //将字节数组读入while(!out.atEnd()){qint8 outChar = 0;out>>outChar;   //每字节填充一次,直到结束//十六进制的转换QString str = QString("%1").arg(outChar&0xFF,2,16,QLatin1Char('0'));if(isShow){ui->textBrowser->insertPlainText(str.toUpper());//大写ui->textBrowser->insertPlainText(" ");//每发送两个字符后添加一个空格ui->textBrowser->moveCursor(QTextCursor::End);}}}void Serial_port::Mdisplay()
{if(ui->rHexRadio->isChecked()){disconnect(serialPort, &QSerialPort::readyRead, this, &Serial_port::readData);connect(serialPort, &QSerialPort::readyRead, this, &Serial_port::readToHex);}else{connect(serialPort, &QSerialPort::readyRead, this, &Serial_port::readData);disconnect(serialPort, &QSerialPort::readyRead, this, &Serial_port::readToHex);}}void Serial_port::handleLineEditClicked()
{//QString curPath = QDir::currentPath();QString curPath = "../QSerial_port";//QFile file;QString aFileName = QFileDialog::getOpenFileName(this,QString("选择文件"),curPath,QString("TEXT(*.txt)"));ui->m_txt->setText(aFileName);}void Serial_port::on_Playbutton_clicked()
{QString path = ui->m_txt->text();//qDebug()<<"是否成功获取"<<path_ce->text();if(path.isEmpty())return;QFile aFile(path);if(!aFile.exists())     //文件不存在qDebug() <<"the file not exist!!";//return;if(!aFile.open(QIODevice::ReadOnly | QIODevice::Text))qDebug() <<"文件无法打开";//return;QTextStream aStream(&aFile);    //用文件流读取文件while (!aStream.atEnd()){QString qstr = aStream.readLine();   //读取文件的一行文本//qDebug()<<qstr.length();int len = qstr.length();if(len%2 == 1){qstr = qstr.insert(len-1,'0');}QByteArray senddata;StringToHex(qstr,senddata);//qDebug()<<senddata.size();for(int i=0;i<senddata.size();i++){QByteArray tmpsenddata=senddata.mid(i,40);i=i+39;//qDebug()<<tmpsenddata.length();serialPort->write(tmpsenddata);if(!serialPort->waitForBytesWritten())   //这一句很关键,决定是否能发送成功{qDebug()<<"serial write error";}Sleep(1000);}}}

最后提示一点,ui中的lineedit为自定义控件,需要提升相应功能,及添加对应类。

六、虚拟串口工具

对于程序来说,最方便的测验程序的时候不需要接硬件就能测试,这里介绍给大家一款能够创建虚拟串口的工具Virtual Serial Port Driver,以及串口小助手,当然咱们自己写的就是小助手,为了严重功能确实好用,那也可以下载个网上的,或者运行两个,有关工具的介绍这里就不提了,百度上讲的已经很好了,具体的资源也很容易百度到,这里也不提供了,百度对该工具的使用介绍链接(点击)。

七、参考文献

1.文中第一部分,串口通信的简介、和校验解码、串口通信图片及报文示图片来源于CSDN博主「WAmani」的原创文章,链接为:https://blog.csdn.net/wamani/article/details/52849043

2.文中第二部分,串口通信各参数的含义来源于CSDN博主「guomutian911」的原创文章,链接为:https://blog.csdn.net/guomutian911/article/details/47044603/

3.文中一些功能的实现来源于CSDN博主「苍雨流痕」的原创文章,链接为https://blog.csdn.net/hellolru/article/details/80838824

八、资源下载

点击下载。或者访问链接:https://download.csdn.net/download/zijinmu69/12376049

Qt实现串口通信(C++实现串口通信小助手)---串口收发及串口数据解码、串口通信模拟器。相关推荐

  1. C#开发串口小助手(可更改波特率、串口号,可实现数据以及字符的收发)

    0. 前言 前置项目为:VS 2019中利用C#语言在.Net Framework 4.5框架上开发简易串口发送数据软件 本项目的全部代码已经放置至码云:https://gitee.com/jiawe ...

  2. #串口通信超时处理_【学院推荐】划重点!LabVIEW中串口参数需注意的几点

    串口可以说是我们最容易见到,也最容易接触到的一种总线,台式机上一般都有二个,而现在很多下位机.仪器等很多都还是使用串口通信的. 1,串口扩展的问题 先说一下串口的扩展问题,一般的台式机或工控机上都至少 ...

  3. mfc如何将一个数组中的字节数据用串口发送出去_RS232串口多机通信

    一.基本原理 1.主从多机通信拓扑图 2.主从多机通信的具体过程 1)使所有的从机的SM2位置1,以便接收主机发来的地址: 2)主机发出一帧地址信息,其中包括8位需要与之通信的从机地址,第9位为1: ...

  4. 单片机红外通信c语言,基于C语言的计算机与多单片机红外无线串口通信的实现.doc...

    基于C语言的计算机与多单片机红外无线串口通信的实现 基于C语言的计算机与多单片机红外无线 串口通信的实现 黄文亮 信息学院 电子信息工程专业 指导教师 刘传菊 摘要:计算机与一台或多台单片机的通信系统 ...

  5. 串口通信中一些常用的小工具

    文章目录 1.逻辑分析仪下的串口数据 2.数字转字符串函数 3.字符串转整数 4.字符串转浮点数 5.测试转换方法 6.空闲中断DMA接收不定长数据 7.高频数据收发测试 本文总结下串口通信的一些小技 ...

  6. 树莓派已经通过网络连接通过串口通信在串口调试小助手打印与操作

    在树莓派编译运行 树莓派已经通过网络连接通过串口通信在串口调试小助手打印C gcc xxx.c -lwiringPi 源码 #include <wiringSerial.h> #inclu ...

  7. php接收232通讯接口数据,RS232串口通信的传输格式和接收过程

    串口是计算机上一种非常通用设备通信的协议(不要与通用串行总线Universal Serial Bus或者USB混淆).大多数计算机包含两个基于RS232的串口.串口同时也是仪器仪表设备通用的通信协议: ...

  8. 315Mhz RF射频解码 串口输出方案(支持2262/1527多种编码方式)

    315Mhz RF射频解码 串口输出方案 一.简介 二.使用前准备 三.测试方法 四.测试现象 总结 原文链接:https://www.yourcee.com/newsinfo/2929023.htm ...

  9. stm32与linux通过protobuf打包数据并进行通信

    一.利用protobuf通信原理 最近项目中需要用到stm32与Orange Pi(移植了linux)进行数据交互,H6端是用C++编写的串口底层驱动,与stm32的串口连接并通信.串口间的通信协议定 ...

最新文章

  1. 想成为优秀的技术人员你必须做到的几件事情
  2. 页面异常反dump 及 内存访问异常hook
  3. 【小白学PyTorch】14.tensorboardX可视化教程
  4. 息系统项目管理师知识点:区块链
  5. 知道这些用于数据科学和机器学习的GitHub存储库和Reddit主题吗?
  6. 西北农林科技大学计算机老师 癌症,西北农林科大攻克苹果树“癌症”
  7. 众人帮蚂蚁帮任务平台修复版源码
  8. Equivalent String
  9. php barcode设置黑条宽度,打印机字体的安装问题 BarCode Pro 6.0修改线条宽度 BarCode pro打EAN13条形码...
  10. matlab 菲涅尔衍射,基于Matlab及菲涅尔衍射仿真.doc
  11. win10 Network Location Awareness 错误:1068依赖服务或组无法启动
  12. 使用 jszip 实现.zip文件解压后上传
  13. 计算机删除内置用户,如何删除windows的内置账户,administrator
  14. linux运行minecraft,如何在DeepinLinux下运行Minecraft光影整合包进行游戏
  15. RabbitMQ实现即时通讯
  16. 一升的眼泪剧情和图片(转贴)
  17. 如何在Windows上为代码签名创建自签名证书
  18. 谷歌学术+SCI-HUB一键下载SCI文献
  19. 送给去IT培训机构的猿们的一些毒鸡汤
  20. 【转】FAL_CLIENT和FAL_SERVER参数详解

热门文章

  1. 《中国人工智能学会通讯》——1.23 国际评测
  2. JSch - Examples - Exec.java
  3. 开眼 | 盘点那些改变世界的代码
  4. 【Algorithm】算法设计与分析(第二版)- 王红梅 - JAVA / C++实现:3.14 变位词
  5. php 飞信接口 2013,php飞信每日自动天气预报 v1.0
  6. python爬虫爬取58同城租房信息(使用动态IP)输出Excel文件
  7. C语言:memcpy、memmove等函数的了解,使用以及模拟实现
  8. Matlab实现支持向量机算法(附上完整仿真源码)
  9. 短信猫 测试软件,因教室门窗忘关被扣分 高二女生发明监测小设备
  10. 线性代数笔记2——向量1(向量简介)