前言:
本节将在前面学习的基础上逐步制作一个简易的串口助手,不过基于前面学习的内容还是不够我们完成制作一个简易串口助手的,所以我们将边继续学习边完成这个简易串口助手
之前我们都是通过纯代码来设置程序页面,所以可以发现主界面程序非常拥挤、看起来很乱。从现在开始,我们给每个页面都创建一个类(做法可以参考我们的QDiaLog类(对话框、弹窗类)),然后在主界面通过切换面板(QTabWidget 类)将这些子窗口添加到切换面板中,实现主窗口负责组装、每个页面类各管自己的界面设计和功能实现:
| 做法 | 问题 |
|---|---|
| 全塞进 Widget 的 .ui | 主窗口会变成几百行的"大杂烩",找一个控件要滚半天 |
| 每页一个类 | 各管各的,主窗口只负责组装;以后做升级页、日志页照抄这个模式 |
现在我们开始创建我们的串口助手类,这次我们不再使用纯代码来设计界面和功能,而是利用ui设计器来设计界面,代码实行功能,这样能减轻我们不少负担,流程如下:
创建串口助手类
(1.1)创建串口助手类文件
1.Qt Creator 右键项目 → 添加新文件

2.Qt → Qt Designer Form Class

3.模板选 Widget,类名 SerialPage


(1.2)通过ui编辑器设计串口助手页面
我们要设计的串口界面如下:

涉及的类和布局
设计这个界面用到的类有:QPushButton(按键)、QComboBox(选择下拉框)、QTableWidget(表格控件)、QTetxEdit(文本编辑)、QTextBrowser(文本浏览框)
布局思路:主界面采用垂直布局,内部再放两个水平布局:
- 上方水平布局:左侧为多个选择下拉框,右侧为文本浏览框;
- 下方水平布局:左侧为文本编辑框,右侧为「发送」和「清除发送」按钮。
添加QComboBox选择下拉框的流程如下:
双击QComboBox控件:


点击左下角+号

添加完成
关于这些属性的参数我们可以看帮助手册,如下:


(1.3)编辑串口助手的功能代码:
首先我们要在项目文件添加serialport模块,如下

接着我们要添加#include 头文件(这个是串口信息类的头文件),我们需要该类的一个成员函数获取本机可用的串口的列表,代码如下:
cpp
//扫描本机的串口,并添加到comboBox的下拉列表
foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
ui->comboBox->addItem(info.portName());
}
涉及的函数,帮助手册描述如下:
1.foreach

这个宏用来实现 Qt 的 foreach 循环。variable 是变量名 / 变量定义;container 是 Qt 容器,容器内元素类型要和变量匹配。
2.availablePorts()函数(获取当前设备所有可用串口)

返回系统上所有可用串口的列表
所以通过这几个函数我们就可以将本节可用的串口添加到comboBox选择框下
3.编辑点击"打开串口"按键后的槽函数:
该函数实行的功能有:设置我们选择的串口助手参数、打开串口助手
涉及的函数主要有:
(1)QSerialPort::setPortName函数(设置串口名)

- 串口名字可以传简短名称(Windows:
"COM3"),也可以传系统完整路径 - 配套读接口:
portName()获取当前串口名 - 配合
QSerialPortInfo用来枚举扫描电脑上所有串口
(2)QComboBox::currentText()(保存当前显示的文本)

我们可以通过这个函数来获取对应的参数选择框(comboBox)的文本,然后设置串口的属性
(3)QSerialPort::setBaudRate函数(设置串口波特率)

可以看到帮助手册还教我们如何使用,可以注意到这里面填入的参数为32位类型的,所以我们还要将currentText()获取的文本转成32位类型的(即currentText().toInt())
(4)QSerialPort::setStopBits函数(设置串口停止位)

注意这里的参数要传入QSerialPort::StopBits的枚举类型,所以我们还要将currentText()获取的文本转换成该枚举类的(即QSerialPort::StopBits(ui->comboBox_3->currentText().toInt()))
(5)QSerialPort::setDataBits函数(设置串口数据位)

这里不再赘述,跟(4)一样
(6)QSerialPort::setParity函数(设置串口奇偶校验)

注意串口奇偶校验选择框我们填入的文字,这跟QSerialPort类的奇偶校验枚举类型都匹配不上,所以这里我们要改成QComboBox::currentIndex()函数(获取当前选择框索引),然后通过switch来配置串口的奇偶校验
代码如下:
cpp
/*设置奇偶校验*/
switch(ui->comboBox_5->currentIndex()) //获取comboBox_5下拉框的当前索引
{
case 0:
serialport->setParity(QSerialPort::NoParity); //无校验位
break;
case 1:
serialport->setParity(QSerialPort::OddParity); //奇校验
break;
case 2:
serialport->setParity(QSerialPort::EvenParity); //偶校验
break;
default:
break;
}
(7)QSerialPort::setFlowControl函数(涉及串口流控模式)

这里我们默认选择无流控就行了
(8)QSerialPort::open函数(打开串口,参数:选择打开模式)

这里的参数与我们打开File文件是一样的
(9)QComboBox::setEnabled函数(设置QComboBox控件的选择框)

当我们传入false时,就打不开该控件的选择框了
(10)QSerialPort::close函数(关闭串口)

(11)代码如下:
serialpage.cpp:
cpp
#include "serialpage.h"
#include "ui_serialpage.h"
#include <QMessageBox>
SerialPage::SerialPage(QWidget *parent) :
QWidget(parent),
ui(new Ui::SerialPage)
{
serialport =new QSerialPort(this);
ui->setupUi(this);
//扫描本机的串口,并添加到comboBox的下拉列表
foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
ui->comboBox->addItem(info.portName());
}
}
SerialPage::~SerialPage()
{
delete ui;
}
void SerialPage::on_pushButton_3_toggled(bool checked)
{
/*如果按键按下即串口打开*/
if(checked)
{
/*设置要打开的串口名字*/
serialport->setPortName(ui->comboBox->currentText());
/*设置选择的波特率,要转成32位类型*/
serialport->setBaudRate(ui->comboBox_2->currentText().toInt());
/*设置选择的停止位,要转成StopBits枚举类型*/
serialport->setStopBits(QSerialPort::StopBits(ui->comboBox_3->currentText().toInt()));
/*设置选择的数据位,要转成DataBits枚举类型*/
serialport->setDataBits(QSerialPort::DataBits(ui->comboBox_4->currentText().toInt()));
/*设置奇偶校验*/
switch(ui->comboBox_5->currentIndex()) //获取comboBox_5下拉框的当前索引
{
case 0:
serialport->setParity(QSerialPort::NoParity); //无校验位
break;
case 1:
serialport->setParity(QSerialPort::OddParity); //奇校验
break;
case 2:
serialport->setParity(QSerialPort::EvenParity); //偶校验
break;
default:
break;
}
/*默认设置无流控*/
serialport->setFlowControl(QSerialPort::NoFlowControl);
/*打开串口,模式选择读写模式*/
if(serialport->open(QIODevice::ReadWrite))
{
/*打开串口后关闭串口配置选择,即关闭下拉框*/
ui->comboBox->setEnabled(false);
ui->comboBox_2->setEnabled(false);
ui->comboBox_3->setEnabled(false);
ui->comboBox_4->setEnabled(false);
ui->comboBox_5->setEnabled(false);
ui->pushButton_3->setText("关闭串口");
}
else
{
QMessageBox::warning(this,"提示","串口打开失败:"+ serialport->errorString());
ui->pushButton_3->setChecked(false); //串口打开失败,恢复按键未选中状态
return;
}
}
else
{
/*打开串口后开启串口配置选择,即打开下拉框*/
ui->comboBox->setEnabled(true);
ui->comboBox_2->setEnabled(true);
ui->comboBox_3->setEnabled(true);
ui->comboBox_4->setEnabled(true);
ui->comboBox_5->setEnabled(true);
/*关闭串口*/
serialport->close();
ui->pushButton_3->setText("打开串口");
}
}
效果如下:


4.文本浏览框和文本编辑代码
涉及函数:
(1)QSerialPort::readyRead函数(这是QSerialPort继承的信号)

当设备当前读通道有新数据可以读取时,触发这个信号。只有再来新数据才会再次触发;如果你在它绑定的槽函数里再次进入事件循环、或者调用 waitForReadyRead(),不会递归再次发出 readyRead。继承 QIODevice 写子类:有新数据到达才 emit readyRead,不能只是缓冲区还有旧数据没读完就发这个信号。
所以我们将这个信号与我们自定义的槽函数连接(该槽函数实行的功能就是读取从串口接收的信息,然后写到浏览框上)
(2)QTextBrowser::insertPlainText()函数

该函数与sertPlainText()函数不同,它不会覆盖旧内容
(3)QSerialPort::errorOccurred函数(QSerialPort的信号,参数 error:错误类型枚举值,代表是什么错误。)

所以我们通过自定义一个槽函数(该槽函数的参数也是QSerialPort::SerialPortError error),通过这个error我们就能判断串口有没有出错
(4)QTextCodec::codecForName函数

遍历所有已安装的编码器,返回与参数名称匹配度最高的编码器;匹配时不区分大小写 。如果找不到,则返回 nullptr。
当你指定了编码名称时,Qt 会直接返回对应的编解码器,不会自动猜测编码。例如:
cpp
QTextCodec::codecForName("GBK")
就是明确使用 GBK 进行解码。由于 Keil/STM32 里的中文字符串默认采用 GB2312/GBK 编码,所以接收数据时我们要按 GBK 格式解码;同理,发送数据时也要按 GBK 格式进行编码。
(5)QTextCodec::fromUnicode函数

把 Unicode 的 QString,按照当前解码器对应的编码编码 ,返回字节数组 QByteArray。
(6)最终代码如下:
serialpage.h
cpp
#ifndef SERIALPAGE_H
#define SERIALPAGE_H
#include <QWidget>
#include <QSerialPort> //串口类
#include <QSerialPortInfo> //串口信息类
namespace Ui {
class SerialPage;
}
class SerialPage : public QWidget
{
Q_OBJECT
public:
explicit SerialPage(QWidget *parent = nullptr);
~SerialPage();
private slots:
void on_pushButton_3_toggled(bool checked);
void serialport_error(QSerialPort::SerialPortError error);
void read_data();
void on_pushButton_2_clicked();
void on_pushButton_4_clicked();
private:
Ui::SerialPage *ui;
QSerialPort *serialport;
};
#endif // SERIALPAGE_H
serialpage.cpp
cpp
#include "serialpage.h"
#include "ui_serialpage.h"
#include <QMessageBox>
#include <QDebug>
#include <QTextCodec>
SerialPage::SerialPage(QWidget *parent) :
QWidget(parent),
ui(new Ui::SerialPage)
{
serialport =new QSerialPort(this);
ui->setupUi(this);
//扫描本机的串口,并添加到comboBox的下拉列表
foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
ui->comboBox->addItem(info.portName());
}
connect(serialport,&QSerialPort::readyRead,this,&SerialPage::read_data);
connect(serialport, &QSerialPort::errorOccurred, this, &SerialPage::serialport_error);
}
SerialPage::~SerialPage()
{
delete ui;
}
/*打开串口槽*/
void SerialPage::on_pushButton_3_toggled(bool checked)
{
/*如果按键按下即串口打开*/
if(checked)
{
/*设置要打开的串口名字*/
serialport->setPortName(ui->comboBox->currentText());
/*设置选择的波特率,要转成32位类型*/
serialport->setBaudRate(ui->comboBox_2->currentText().toInt());
/*设置选择的停止位,要转成StopBits枚举类型*/
serialport->setStopBits(QSerialPort::StopBits(ui->comboBox_3->currentText().toInt()));
/*设置选择的数据位,要转成DataBits枚举类型*/
serialport->setDataBits(QSerialPort::DataBits(ui->comboBox_4->currentText().toInt()));
/*设置奇偶校验*/
switch(ui->comboBox_5->currentIndex()) //获取comboBox_5下拉框的当前索引
{
case 0:
serialport->setParity(QSerialPort::NoParity); //无校验位
break;
case 1:
serialport->setParity(QSerialPort::OddParity); //奇校验
break;
case 2:
serialport->setParity(QSerialPort::EvenParity); //偶校验
break;
default:
break;
}
/*默认设置无流控*/
serialport->setFlowControl(QSerialPort::NoFlowControl);
/*打开串口,模式选择读写模式*/
if(serialport->open(QIODevice::ReadWrite))
{
/*打开串口后关闭串口配置选择,即关闭下拉框*/
ui->comboBox->setEnabled(false);
ui->comboBox_2->setEnabled(false);
ui->comboBox_3->setEnabled(false);
ui->comboBox_4->setEnabled(false);
ui->comboBox_5->setEnabled(false);
ui->pushButton_3->setText("关闭串口");
}
else
{
QMessageBox::warning(this,"提示","串口打开失败:"+ serialport->errorString());
ui->pushButton_3->setChecked(false); //串口打开失败,恢复按键未选中状态
return;
}
}
else
{
/*打开串口后开启串口配置选择,即打开下拉框*/
ui->comboBox->setEnabled(true);
ui->comboBox_2->setEnabled(true);
ui->comboBox_3->setEnabled(true);
ui->comboBox_4->setEnabled(true);
ui->comboBox_5->setEnabled(true);
/*关闭串口*/
serialport->close();
ui->pushButton_3->setText("打开串口");
}
}
void SerialPage::serialport_error(QSerialPort::SerialPortError error)
{
if(error!=0)
{
qDebug()<<"串口出错:"<<error<<endl;
}
}
/*读取接收的数据并写入文本浏览框*/
void SerialPage::read_data()
{
QByteArray text=serialport->readAll();
/*转成GBK类型*/
QTextCodec *gbk = QTextCodec::codecForName("GBK");
ui->textBrowser->insertPlainText(gbk->toUnicode(text)); //insertPlainText不会覆盖原数据
}
/*点击串口发送槽*/
void SerialPage::on_pushButton_2_clicked()
{
/*获取文本框的数据并转成字节数组发送*/
QTextCodec *gbk = QTextCodec::codecForName("GBK");
serialport->write(gbk->fromUnicode(ui->textEdit->toPlainText()));
}
void SerialPage::on_pushButton_4_clicked()
{
/*清除浏览框*/
ui->textBrowser->clear();
}
效果如下:

(7)代码路线:
枚举串口 → 配置参数 → open/close → readyRead + readAll → 编码(GBK)→ errorOccurred