QT项目实战之串口调试助手

目录

一、前言

1、关于字符集编码的延伸阅读

2、关于字符编码的几点总结

二、功能与效果

1、效果

2、功能

三、实现过程

1、创建QMainWindow窗口与UI设计

2、代码实现

1、信号和槽的关联方式

2、文件发送和文件保存

3、配置信息的保存

4、字符与16进制编码字符的互相转换

3、工程打包发布

1、使用QT自带的windeployqt.exe打包

2、使用HM NIS Edit编辑器与NSIS编译器更深层次打包

四、功能测试


一、前言

给大家安利一款自己编写的串口调试助手小软件,使用QT5.5编写,在windows下可正常使用,Linux环境下没测试过,可能会出现乱码,因为windows环境下的字符编码是ANSI(在中文系统中ANSI就是GBK编码),而Linux环境下字符编码是UTF-8,调试助手中发送和接收的数据也都是按照GBK编码转换的。比如:

字符

GBK编码

Unicode编码

UTF-8

"123学习"

31 32 33 D1 A7 CF B0

31 32 33 5B 66 4E 60

31 32 33 E5 AD A6 E4 B9 A0

1、关于字符集编码的延伸阅读

1、汉字字符集编码查询

2、查看字符编码(UTF-8)

3、unicode和utf-8互转

4、ANSI是什么编码?

5、QT串口中文显示问题,Unicode转GBK

6、QT字符编码

2、关于字符编码的几点总结

1、字符就是一堆文字符号,每个国家都会有自己的语言文字,这些字符在计算机中怎么存储呢?计算机中都是以01二进制存储的,把这些字符转成其对应且唯一的二进制码不就行了。也就出现了各种编码,比如英文的ASCII、中文的GDB编码、日文的Shift-JIS编码等等

2、ASCII码:上个世纪60年代, 美国制定了一套字符编码, 对英语字符与二进制位之间的关系, 做了统一规定,这被称为ASCII码,ASCII码一共规定了128个字符的编码。只占用了一个字节的后面7位, 最前面的1位统一规定为0

3、GBK码:英语用128个符号编码就够了, 但是用来表示其他语言, 128个符号是不够的,于是就使用两个字节表示一个汉字, 所以理论上最多可以表示256x256=65536个符号

4、Unicode:世界上存在着多种编码方式, 同一个二进制数字可以被解释成不同的符号,要想打开一个文本文件, 就必须知道它的编码方式, 否则用错误的编码方式解读, 就会出现乱码。为了解决不统一的问题,有个国际组织就出来制定了包含世界上所有语言字符的编码,这就是Unicode编码。Unicode通常用两个字节表示一个字符,原有的英文编码从单字节变成双字节,只需要把高字节全部填为0就可以。但是 Unicode只是一个符号集, 它只规定了符号的二进制代码, 却没有规定这个二进制代码应该如何存储,如果用Unicode来表示1字节的ASCII优点浪费内存,而且也出现了不同存储方式的Unicode

5、UTF-8编码 :UTF-8就是在互联网上使用最广的一种unicode的实现方式. 其他实现方式还包括UTF-16和UTF-32。UTF-8最大的一个特点, 就是它是一种变长的编码方式. 它可以使用1~6个字节表示一个符号, 根据不同的符号而变化字节长度.

二、功能与效果

1、效果

废话不多说,先看效果

2、功能

1、记录和保存串口的配置信息,便于下次打开时直接使用上次的配置

2、保存串口接收区的数据(可以是串口接收的的也可以是串口发送的)

3、一键清除接收窗口和发送窗口的数据

4、接收时,可以选择16进制显示还是字符显示,可以选择显示当前的时间和发送的数据

5、发送时,可以选择定时发送以及发送新行,可以发送文件数据。需要注意的是,选择或不选择16进制发送只是在发送区字符显示的切换,发送的数据都是ANSI编码的字节数据;勾选16进制时就将字符转成ANSI编码(这里是GBK编码)显示,不勾选16进制就显示字符或是将16进制数据根据ANSI编码转成字符

6、可以显示当前的串口状态、发送的字节数、接收的字节数、当前的时间

三、实现过程

1、创建QMainWindow窗口与UI设计

创建后在 .pro 工程管理文件中添加 QT += serialport,这样才能使用串口

2、代码实现

源码连接:https://github.com/denghengli/qt_study/tree/master/17_SerialPort

划重点:

1、信号和槽的关联方式

方法一:

0)如果是自定义的信号,需要在.h中声明

1)在.h中声明槽函数

2)在.cpp中实现槽函数

3)使用connect将信号和槽函数进行连接:

QObject::connect(ui->pushbutton,SIGNAL( clicked() ),this,SLOT( on_pushBuuton_clicked() );

方法二:

在.ui文件中,右击按钮,选择''go to slot''选项,选择信号"clicked",单机确定按钮。系统自动生成槽函数声明和定义,并建立内部映射。只需在槽函数体内编写槽函数体内编写槽函数的功能即可,添加语句如方法一中的步骤2.

方法三:

在.ui界面,选择"改变信号/槽"选项,单击"+"添加新的槽函数,确定完成槽函数的添加,最后重复方法一中的步骤1和步骤2完成槽函数声明和定义.

2、文件发送和文件保存

通过QFileDialog类提供的文件对话框,来选择要发送的文件和新建保存的文件

openfile_path = QFileDialog::getOpenFileName(this, "打开文件", "", "Text File(*.txt)"); 
savefile_path = QFileDialog::getSaveFileName(this,"另存为", savefile_path, "Text File(*.txt)");

3、配置信息的保存

每一次在配置完串口、波特率、停止位等后,打开串口时,通过QSettings类将这些配置信息以key-value的形式保存到预先规定的文件中;在每次打开软件时也先读取这个配置文件的数据,再设置串口

//创建ini配置文件
void SerialPort::configiniInit()
{
    iniPath = new QDir;
    iniFilename = iniPath->currentPath() + "/SerialPort.ini";
    //构造一个QSettings对象,用于访问存储在名为iniFilename的文件中的设置。如果文件不存在,就创建它
    configini = new QSettings(iniFilename, QSettings::IniFormat);
}

//读取ini配置文件,设置下拉框的项目
void SerialPort::configiniRead()
{
    configini->beginGroup("SETUP");
    int i_port = configini->value("COM").toInt();
    int i_baudrate = configini->value("baudrate").toInt();
    int i_databit = configini->value("databit").toInt();
    int i_checkbit = configini->value("checkbit").toInt();
    int i_stopbit = configini->value("stopbit").toInt();
    configini->endGroup();

    ui->CB_port->setCurrentIndex(i_port);
    ui->CB_baudrate->setCurrentIndex(i_baudrate);
    ui->CB_databit->setCurrentIndex(i_databit);
    ui->CB_checkbit->setCurrentIndex(i_checkbit);
    ui->CB_stopbit->setCurrentIndex(i_stopbit);
}

//写ini配置文件,将下拉框的项目索引写入ini文件
void SerialPort::configiniWrite()
{
    configini->beginGroup("SETUP");
    configini->setValue("COM", ui->CB_port->currentIndex());
    configini->setValue("baudrate", ui->CB_baudrate->currentIndex());
    configini->setValue("databit", ui->CB_databit->currentIndex());
    configini->setValue("checkbit", ui->CB_checkbit->currentIndex());
    configini->setValue("stopbit", ui->CB_stopbit->currentIndex());
    configini->endGroup();
}

4、字符与16进制编码字符的互相转换

不要弄混了,是字符ANSI(这里是GBK)16进制编码字符的互相转换;

例如:“123学习” <----> "31 32 33 D1 A7 CF B0"

原因是,textEdit控件显示的是QString字符,在显示时需要在字符和16进制之间切换。

“123学习” ----> "31 32 33 D1 A7 CF B0" 步骤:

//1、获取发送数据 QString = "123学习"
QString sendstr = ui->textEdit_tx->toPlainText();

//2、"123学习" --> GBK编码:31 32 33 D1 A7 CF B0
QByteArray sendarray = sendstr.toLocal8Bit();//"123学习"在QT中是UTF8编码的,所以这里需要使用toLocal8Bit转成Winds中的GBK编码
//3、GBK编码:31 32 33 D1 A7 CF B0 --> GBK编码字符:"31 32 33 D1 A7 CF B0"
QDataStream out(&sendarray,QIODevice::ReadWrite);    //将字节数组读入
while(!out.atEnd())
{
    qint8 outChar = 0;
    out>>outChar;   //每字节填充一次,直到结束
    QString str = QString("%1").arg(outChar&0xFF,2,16,QLatin1Char('0'));
    str = str.toUpper();
    ui->textEdit_tx->insertPlainText(str + " ");//在当前光标处插入文本
    //返回表示当前可见游标的QTextCursor副本。对返回游标的更改不会影响QTextEdit的游标;使用setTextCursor()更新可见的游标
    QTextCursor cursor = ui->textEdit_tx->textCursor();
    cursor.movePosition(QTextCursor::End);//将光标移动到文档的最后
    ui->textEdit_tx->setTextCursor(cursor);//更新光标
}

"31 32 33 D1 A7 CF B0" ----> “123学习” 步骤:

//1、获取发送数据 QString = "31 32 33 D1 A7 CF B0"
QString sendstr = ui->textEdit_tx->toPlainText();
QByteArray sendarray;
//2、GBK编码字符:"31 32 33 D1 A7 CF B0" --> GBK编码:31 32 33 D1 A7 CF B0
QStringtoHex(sendarray, sendstr);
//3、GBK编码:31 32 33 D1 A7 CF B0 --> Unicode编码 --> "123学习"
QString str1 = QString::fromLocal8Bit(sendarray);//实现了从本地字符集GBK到Unicode的转换,解决中文显示乱码问题
ui->textEdit_tx->setText(str1);

可以看到 QStringtoHex(QByteArray& sendData,QString str)函数比较关键,但是实现起来也比较简单

char SerialPort::ConvertHexChar(char c)
{
    if(c>='a'&&c<='f'){
        return c-'a'+10;
    }
    else if(c>='A'&&c<='F'){
        return c-'A'+10;
    }
    else if(c>='0'&&c<='9'){
        return c-'0';
    }
    else{
        return -1;
    }
}
//"31 32 33 D1 A7 CF B0" --> 31 32 33 D1 A7 CF B0
void SerialPort::QStringtoHex(QByteArray& sendData,QString str)
{
    char hstr,lstr,hdata,ldata;
    int len = str.length();
    int sendnum = 0;
    QByteArray temp;
    temp.resize(len/2);//设置大小,len/2会大于实际16进制字符
    
    for(int i=0;i<len;)
    {
        //QString转QByteArray的方法,Latin1代表ASCII。'3' --> 3
        hstr = str[i].toLatin1();
        if(hstr == ' '){
            ++i; continue;
        }
        if(++i >= len) break;
        lstr = str[i].toLatin1();

        hdata = ConvertHexChar(hstr);
        ldata = ConvertHexChar(lstr);
        if(-1 == hdata || -1 == ldata)
            break;
        ++i;
        temp[sendnum] = hdata<<4|ldata;
        sendnum++;
    }
    sendData.reserve(sendnum);//重新调整大小
    sendData = temp.left(sendnum);//返回一个 字节数组,其中包含这个字节数组最左边的len字节。去掉多余字符
}

3、工程打包发布

1、使用QT自带的windeployqt.exe打包

1、选择 release,构建工程

2、将 .exe文件拷贝到任意路径下,将其进行打包,这样在任意一台没有安装QT环境的电脑也可以运行,打包前首先要确保QT的安装目录下有 windeployqt.exe

3、打开终端,进入到.exe目录下(快捷方式:在文件夹中,点击空白处,shift+右键,选择“在此处打开窗口”),输入 windeployqt.exe 文件

4、生成成功后,会在当前目录下生成一些文件,将此文件夹拷贝到任意没有QT环境的电脑上也能运行

2、使用HM NIS Edit编辑器与NSIS编译器更深层次打包

1、先使用QT自带的windeployqt.exe打包,生成可执行文件和需要的动态库

2、下载安装 NSISNIS Edit 这两个软件

3、根据 NIS Edit安装向导的使用 步骤,将windeployqt.exe 打包后的文件再打包生成一个 Setup.exe 的安装向导文件;使用NIS EDIT安装时,有个需要注意的地方,在选择应用程序的文件时,要选择需要打包的文件夹

完成后就可以通过这个Setup.exe来安装并使用串口调试助手了。在安装 Setup.exe 的时候根据提示下一步下一步就可以了。这样逼格是不是都提高了 哈哈

四、功能测试

1、下载一个虚拟串口的工具,我下载的是 VSDP ,当然其他的也行,只要能在本地虚拟出串口就行,如下图,COM1和COM2就可以互联了;"Add pair "添加新的虚拟串口

2、用serialport打开COM2,随便用一个串口调试助手(我这里用的XCOM)打开COM1,测试互相发送数据都是ok的

猜你喜欢

转载自blog.csdn.net/m0_37845735/article/details/108655012