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

2023-05-16

                                                     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;   //停止位
    // 波特率    //波特率默认选择下拉第三项:9600
    baudList<<"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;
               else
                   hexdata = 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_OBJECT

public:
    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;   //停止位
    // 波特率    //波特率默认选择下拉第三项:9600
    baudList<<"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;
               else
                   hexdata = 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

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 机器学习算法五:随机森林(Random Forest)

    集成学习 xff1a 通过构建并结合多个学习器来完成学习任务 xff1b 集成学习中主要包括boosting算法 和bagging算法 xff1b boosting算法 xff1a xff08 线性集成 xff09 关注于降低偏差 xff1
  • 怎样利用VNC远程连接LINUX桌面

    关于显示等问题都有 xff0c 比较详细 http blog csdn net zhouyunjie archive 2008 11 27 3396824 aspx 先要修改 vnc xstartup文件 bin sh Uncomment
  • VNC多用户

    1 为vnc建立用户 newuser 2 拷贝 root vnc 里的xstartup文件到用户目录 home newuser 3 修改xstartup文件 xff0c 在最后增加 xff1a gnome session amp 4 以ne
  • 【环境配置】初试使用mitmproxy搭建网络代理

    初试使用mitmproxy搭建网络代理 1 参考文章 老版本的很多功能已经不能用了 xff0c 用新的版本研究了一下 参考文章 https mitmproxy org http www freebuf com sectool 76361 h
  • 关于Keil 的快速注释功能,并为其添加快捷键

    原地址 xff1a http blog sina com cn s blog 6859cadf0101i3p4 html Keil版本uVision 4 03 1 在Keil gt Edit gt Advanced中有两项 Comment
  • 【OpenCV】ArUco Marker

    1 创建 span class token keyword import span cv2 span class token keyword as span cv span class token keyword import span n
  • 非常实用,华为、新华三、锐捷交换机的配置命令分享

    干弱电这一行难免会接触到交换机 xff0c 华为 新华三锐捷交换机又是最常见的交换机 xff0c 关于他们的命令配置很容易弄混 xff0c 而且在实际项目配置中也很容易出错 xff0c 因此 xff0c 本期我们将来介绍这三家交换机的基础配
  • 打飞机小游戏

    设计目标 xff1a 高质量的代码要有这些优点 复用性好 扩展性好 维护性好 可移植性好 健壮性好 效率好 可读性好 所以设计代码的时候不要只想到功能的实现 xff0c 还要考虑功能的扩展及代码复用等 设计规则 xff1a 需求分析抽取共性
  • windows 安装 Navicat Premiun

    Navicat Premiun 中文网站 Navicat Premiun 官网下载 简介 Navicat Premium 是一套数据库管理工具 xff0c 结合其它Navicat 成员 xff0c 支持单一程序同时连接到 MySQL Mar
  • 论文排版中MathType的使用(论文投稿必备)

    使用mathtype往论文中插入公式时 xff0c 若需要编号 xff0c 则点击 右编号 xff08 若只需插入文本行内 xff0c 则点 内联 xff09 由于论文是分栏格式 xff0c 单行公式太长导致编号串行 xff0c 因此需要将
  • python语言中变量的共享引用及原处修改

    代码段一 xff1a 代码段1 gt gt gt a 61 6 gt gt gt b 61 a gt gt gt a b 输出 xff1a 6 6 上述代码段一中 xff0c 第一行我们创建了对象6 xff0c 并将变量a 与之相关联 xf
  • 通用环形缓冲区 LwRB 使用指南

    什么是 LwRB xff1f LwRB 是一个开源 通用环形缓冲区库 xff0c 为嵌入式系统进行了优化 源码点击这里 Github LwRB 特性 使用 ANSI C99 编写FIFO xff08 先进先出 xff09 无动态内存分配 x
  • Windows 环境下的 Socket 编程 3 - 基于 TCP 的服务器/客户端

    基于 TCP 的服务器端 客户端 绝大多数 TCP 服务器端都按照如下顺序调用 xff1a 在 Windows 环境下 xff0c 代码表示为 xff1a WSADATA wsaData span class token punctuati
  • 随想006:帮忙的时机

    我一直不理解发生在我身边的一个现象 从我第一次发现到现在已经几年了 xff0c 在一个又一个人身上 xff0c 不断地出现 在编程界 xff0c 重复 可能是软件中一切邪恶的根源 xff0c 我为此吃过苦头 让我记忆尤深的是 xff0c 当
  • 仿真软件 LTspice XVII 操作方法

    原理图编辑 与其它软件不同 xff0c LTspice XVII 软件先选择动作 xff0c 然后选择对象 比如要移动一个元件时 xff0c 首先选择 移动 xff08 move xff09 命令 xff0c 然后单击一个元件或框住一系列元
  • LwIP IP 层常用函数和宏

    1 将 4 字节数据组成 IP 地址 头文件 xff1a ip addr h span class token function IP ADDR4 span span class token punctuation span ipaddr
  • 测试驱动的嵌入式开发 001:VSCode + CMake + CppUTest 环境搭建

    本文是对 测试驱动的嵌入式 C 语言开发 第二章的实践 对资源受限的嵌入式项目如何进行测试驱动开发的所有疑问 xff0c 都因为本书第二章最开始的一句话而消解 xff0c 当我读到这句话时 xff0c 脑袋里有闪电掠过 这句话是 xff1a
  • 测试驱动的嵌入式开发 002:VSCode + CMake + Unity 环境搭建

    本文是对 测试驱动的嵌入式 C 语言开发 第二章的实践 搭建 VSCode 这部分参考博文 基于Windows 的 VS Code C C 43 43 编译环境搭建 安装 CMake 安装 CMake xff0c 在官网下载最新安装包 安装
  • 原理图与 PCB 绘制备忘

    原理图绘制 导出 BOM 点击菜单 Reports Bill of Materials xff0c 在弹出的生成 BOM 界面中 xff0c 点击 OK 按钮导出 BOM 批量修改器件标识 xff08 比如型号 xff0c 阻值 xff09
  • Windows 10 安装 MySQL

    确认是否已安装 MySQL xff08 1 xff09 按 win 43 r 快捷键打开运行 xff1b xff08 2 xff09 输入 services msc xff1b xff08 3 xff09 点击 确定 xff0c 在打开的服

随机推荐

  • lwIP 细节之四:ARP 相关知识

    ARP 表项的最大存活时间与宏 ARP MAXAGE 相关 xff0c 默认为 240 x 5000ms 61 20 分钟 当 ARP 表项还有最后一分钟 xff08 由宏 ARP AGE REREQUEST USED 控制 xff09 存
  • Windows 环境下的 Socket 编程 4 - 基于 UDP 的服务器/客户端

    在 TCP 中 xff0c 套接字之间应该是一对一的关系 若向 10 个客户端提供服务 xff0c 则除了守门的服务器套接字外 xff0c 还需要 10 个服务器端套接字 但在 UDP 中 xff0c 不管是服务器端还是客户端都只需要 1
  • ADS1120 备忘

    ADS1120 是一个小型 低功耗 16 bit 模数转换器 xff08 ADC xff09 xff1a 内置 PGA xff08 1 128 xff09 内置参考基准 xff08 2 048V xff09 内置温度传感器内置 2 个已配对
  • Windows 环境下的 Socket 编程 5 - 套接字的可选项

    套接字具有多种特性 xff0c 这些特性可以更改 更改和读取可选项函数 span class token keyword int span span class token function getsockopt span span cla
  • 使能中断与禁止中断策略比较

    本文描述的内容仅适用于 Cortex M3 M4 架构 Keil MDK 编译环境 在阅读 RT Thread 源码和 FreeRTOS 源码时 xff0c 发现二者使能中断和禁止中断的方式不同 xff0c 再加上 CMSIS 也提供了使能
  • FreeModbus RTU 移植指南

    FreeModbus 简介 FreeModbus 是一个免费的软件协议栈 xff0c 实现了 Modbus 从机功能 xff1a 纯 C 语言支持 Modbus RTU ASCII支持 Modbus TCP 本文介绍 Modbus RTU
  • 随想007:模块化代码

    你一定不止一次的听说过模块化代码 理想的模块化代码高内聚低耦合 逻辑清晰 经过严格测试 xff0c 易于复用 嵌入式 C 编程界到处流传着它的大名 在学校 在公司 在各种技术书籍中 xff0c 你总能找到它的身影 它被描述的像是无所不能 x
  • VS Code 用作嵌入式开发编辑器

    使用 Keil MDK 进行嵌入式开发时 xff0c Keil 的编辑器相对于主流编辑器而言有些不方便 xff0c 比如缺少暗色主题 缺少智能悬停感知 xff08 鼠标停在一个宏上 xff0c 能自动展开最终的宏结果 xff09 代码补全不
  • CSDN Marddown 编辑器语法备忘

    原文链接 xff1a https blog csdn net blogdevteam article details 103478461 本文对其二次加工 xff0c 增加渲染样式 补充例程 添加未收录的常用语法 CSDN Markdown
  • 随想008:烂摊子

    我看到过很多离谱的现象 比如 xff1a 程序 代码重复 命名随意 逻辑混乱 甚至对齐都不一致 xff0c 当我询问代码为什么这样写时 xff0c 他们告诉我 xff1a 我接手时就是这样 xff01 原理图参数错误 器件老旧 xff0c
  • MySQL —— Database initialization failed 错误处理

    安装 MySQL 过程中遇到的错误 xff0c 记录一下 xff0c 避免下次又遇到同样的错误 安装 MySQL 8 0 33 到最后一步时 xff0c 出现了小红点无法往下执行了 找了网上的方法解决了问题 xff0c 为了避免下次遇到同样
  • 随想009:读书

    我很懊恼的意识到 xff0c 直到过了 30 岁 xff0c 我才明白如何读书 我们在网上浏览新闻 阅读小说 xff0c 这些让人愉悦的阅读不叫读书 xff0c 顶多叫消遣 xff1b 我们在学校背诵历史 地理 xff0c 记住数学公式和化
  • 找文心一言问了几个嵌入式软件开发的问题

    百度终于通知我可以试用文心一言了 xff0c 试验了一番 xff0c 总体满意 xff0c 记录几个文心一言回答的问题 我是一个嵌入式软件开发者 xff0c 你能帮我做什么工作以提高我的开发效率 作为一个嵌入式软件开发者 xff0c 以下是
  • JavaScript常用数组属性和方法

    本文给出了js xff08 JavaScript xff09 数组 xff08 Array xff09 操作所涉及到的所有常用内置函数说明及用法 xff0c 希望对大家有所帮助 数组的属性和方法 属性length属性 xff1a 方法1 把
  • FreeRTOS三种数据结构区别(StreamBuffer,MessageBuffer,Queue)

    转载自博客 xff1a https blog zh123 top p 61 308 Queue队列是最基本的数据结构 xff0c 在FreeRTOS v10 0后提供了另外两种高级数据结构为Streambuffer和MessageBuffe
  • MySQL知识点(详细)

    结构化查询语言SQL 结构化查询语言SQL xff08 Structured Query Language xff09 xff1a 是在关系数据库上执行数据操作 xff0c 检索及维护所使用的标准语言 xff0c 可以用来查询数据 定义数据
  • linux驱动系列学习之Framebuffer子系统(三)

    一 Framebuffer子系统简介 Framebuffer xff08 帧缓冲 xff09 时Linux系统位显示设备提供的一个接口 属于偏底层的显示接口 它将显示缓冲区抽象 xff0c 屏蔽图像硬件的底层差异 xff0c 允许上层应用程
  • Linux系统通过远程终端修改分辨率

    最近的工作是测试服务器的板载GPU xff0c 由于一切处于开发阶段 xff08 没有USB驱动 xff0c 无法通过键鼠操作 xff09 xff0c 切换桌面分辨率成了一项难题 用谷歌 用百度 xff0c 总算找到了一个在远程终端修改分辨
  • ubuntu安装WPS字体缺失的解决办法

    作者最近新安装的ubuntu系统 xff0c 所以缺少了很多的办公软件 xff0c 之前安装了WPS xff0c 但是Linux系统的wps缺少了windows的字库 xff0c 因此使用起来不太方便 xff0c 所以作者就整理了这样的一份
  • Qt实现串口通信(C++实现串口通信小助手)---串口收发及串口数据解码、串口通信模拟器。

    Qt实现串口通信示例 前言 xff1a 以下串口通信示例 xff0c 参考了现有网上前辈们的资源 xff0c 最后结合部分个人的思想 xff0c 所以下述博客会将实现的原理及代码的案例进行公开 这里我们先上效果图 xff1a 一 串口通信简