文章目录
- 一、功能简介
 - 二、串口助手实现
 - 2.1 创建UI
 - 2.2 扫描可用串口
 - 2.3 配置波特率
 - 2.4 配置数据位
 - 2.5 配置停止位
 - 2.6 配置校验位
 - 2.7 打开/关闭串口
 - 2.8 刷新串口
 - 2.9 发送新行
 - 2.10 串口发送
 - 2.11 串口接收显示
 - 2.12 清空接收窗口
 - 2.13 定时发送
 - 2.14 固定窗口大小
 
- 三、总结
 - 3.1 将信号与槽函数关联
 - 3.2 优化方向
 
一、功能简介
最近尝试用QT实现了一个串口调试驻守于,作为一个刚接触QT的小白,当然是站在巨人的肩膀上完成了这个小项目。在实现过程中学习了许多内容,这里简单总结一下。
本次设计的串口调试助手只实现了一些基本功能
- 打开/关闭串口
 - 刷新串口
 - 串口收/发
 - 定时发送
 - 清空接收窗口
 - 选择串口,配置波特率,数据位,停止位和校验位
 
实现效果如下

二、串口助手实现
下面首先再明确一下功能。打开串口时会提示“串口打开成功”或者“串口打开失败”。打开成功后会禁用选择串口,刷新串口,配置波特率,数据位,停止位和校验位的功能。关闭串口后恢复使用。

由于使能和失能控件的方法比较简单,就在这介绍,不再单独介绍了。
- 失能控件
 
ui->控件名->setEnabled(false);
 
- 使能控件
 
ui->控件名->setEnabled(true);
 
2.1 创建UI
在开始程序编写前,先创建UI界面。创建UI时使用的控件如果和博主的不同的话,程序可能会不能直接复用,需要注意一下。本项目主要用到了这些控件
- QLabel
该控件是标签,用来提示,比如UI中的“串口”,“波特率”等字样,就是用的QLabel控件。 - QComboBox
该控件为多选下拉框。UI中可选串口号,波特率,数据为,停止位,校验位这些都是使用的该控件。双击控件可以编辑下拉框成员。 - QPushButton
该控件为按钮。UI中的发送,打开/关闭串口等,都是使用的该控件。 - QLineEdit
QLineEdit是一个单行文本编辑器,UI中定时时间输入,发送内容输入框都是使用的该控件。 - QCheckBox
QCheckBox为复选框控件,UI中“定时发送”和“发送新行”使用的就是该控件。该控件可以添加一个“stateChange(int)”槽函数。可以通过int变量的值来判断复选框是否被选中。如果复选框被选中,int变量的值为2,未被选中,int变量的值为0。 - QTextBroswer
该控件是一个文本阅读器,UI中串口接收内容的显示用到了该控件。 
2.2 扫描可用串口
扫描可用串口的方法是
    // 搜索所有可用串口
    foreach (const QSerialPortInfo &inf0, QSerialPortInfo::availablePorts()) {
        serialNamePort<<inf0.portName();
    }
 
扫描可用串口用在两个地方,首先是用在刚打开软件时,扫描当前可用串口。扫描到可用串口后,需要将可用串口号显示到UI的控件上。
    // 将可用串口显示到serialBox
    ui->serialBox->addItems(serialNamePort);
 
其次用到扫描可用串口的地方就是“刷新串口”按键,后面会有介绍。
2.3 配置波特率
打开串口时,会根据UI界面波特率的下拉框中选择的值配置波特率。配置方法是直接获取下拉框中的字符,将其转换成int型变量,然后配置波特率。
        // toInt将baudrateBox中的字符串转换成整型数
        serialPort->setBaudRate(ui->baudrateBox->currentText().toInt());   // 波特率
 
2.4 配置数据位
配置数据位时,无法像配置波特率一样,直接获取下拉框中的字符串,转换成int型直接赋值。这里选择先将数据位下拉框内容转换成int型,然后用switch语句配置数据位
        // 用switch语句设置数据位
        switch (ui->databitBox->currentText().toInt())
        {
        case 5:
            serialPort->setDataBits(QSerialPort::Data5);   // 5位
            break;
        case 6:
            serialPort->setDataBits(QSerialPort::Data6);   // 6位
            break;
        case 7:
            serialPort->setDataBits(QSerialPort::Data7);   // 7位
            break;
        case 8:
            serialPort->setDataBits(QSerialPort::Data8);   // 8位
            break;
        default:
            serialPort->setDataBits(QSerialPort::Data8);   // 8位
            break;
        }
 
2.5 配置停止位
配置停止位也无法直接使用数字,这里使用另一个方法。获取停止位下拉框中的字符,利用if语句完成停止位的配置。
        // 设置停止位,直接用字符做判断
        if (ui->stopbitBox->currentText() == "1")
        {
            serialPort->setStopBits(QSerialPort::OneStop);   // 1位停止位
        }
        else if (ui->stopbitBox->currentText() == "1.5")
        {
            serialPort->setStopBits(QSerialPort::OneAndHalfStop);   // 1.5位停止位
        }
        else if (ui->stopbitBox->currentText() == "2")
        {
            serialPort->setStopBits(QSerialPort::TwoStop);   // 2位停止位
        }
        else   // 默认1位停止位
        {
            serialPort->setStopBits(QSerialPort::OneStop);   // 1位停止位
        }
 
2.6 配置校验位
配置校验位与配置停止位的方法相同,也是使用if语句。
        // 设置校验位,也用if直接判断字符串
        if (ui->checkBox->currentText() == "None")
        {
            serialPort->setParity(QSerialPort::NoParity);   // 无校验
        }
        else if (ui->checkBox->currentText() == "Odd")
        {
            serialPort->setParity(QSerialPort::OddParity);   // 奇校验
        }
        else if (ui->checkBox->currentText() == "Even")
        {
            serialPort->setParity(QSerialPort::EvenParity);   // 偶校验
        }
        else   // 默认无校验
        {
            serialPort->setParity(QSerialPort::NoParity);   // 无校验
        }
 
2.7 打开/关闭串口
打开串口和关闭串口使用的是同一个按钮。刚打开是,按钮为“打开串口”。打开串口成功后,将按钮上显示的字符修改为“关闭串口”。点击按钮时,根据按钮上的字符来判断是执行打开串口还是关闭串口。实现框架如下
    // 打开串口
    if (ui->openButton->text() == "打开串口")
    {
    	// 打开串口
    }
    else   // 关闭串口
    {
    	// 关闭串口
    }
 
当然,也可以使用标志位实现。
打开串口和关闭串口的函数为
    bool open(OpenMode mode) override;
    void close() override;
 
2.8 刷新串口
刷新串口的功能是重新扫描当前可用串口,然后将当前可用串口号显示到串口的下拉框中。扫描方法上面已经介绍过了,需要注意的是,点击刷新串口,扫描完当前可用串口后,需要将之前串口下拉框中的显示内容清除掉再重新显示。
// 刷新串口
void MainWindow::on_refrashButton_clicked()
{
    QStringList serialNamePort;
    // 先清除一下之前串口号中的显示内容
    ui->serialBox->clear();
    // 搜索所有可用串口
    foreach (const QSerialPortInfo &inf0, QSerialPortInfo::availablePorts()) {
        serialNamePort<<inf0.portName();
    }
    // 将可用串口显示到serialBox
    ui->serialBox->addItems(serialNamePort);
}
 
2.9 发送新行
发送新行是使用复选框实现的。在发送前会先检测是否勾选了发送新行,判断方法如下
    // 如果发送新行被勾选
    if (ui->newLineBox->isChecked())
    {
    	// 发送新行
    }
 
2.10 串口发送
按下“串口发送”按钮,首先获取输入的内容,然后根据是否需要发送新行来判断是否需要在结尾增加换行。“串口发送”按钮的槽函数如下。
// 发送按钮
void MainWindow::on_sendButton_clicked()
{
    // 如果发送新行被勾选
    if (ui->newLineBox->isChecked())
    {
        // 多行文本框用sendEdit()获取内容
        // QLineExit内容用text()获取内容
        serialPort->write(ui->sendEdit->text().toLatin1() + "\r\n");      // 串口发送数据
    }
    else
    {
        serialPort->write(ui->sendEdit->text().toLatin1());      // 串口发送数据
    }
}
 
2.11 串口接收显示
上位机需要显示串口接收到的内容。有一个reayRead信号,一旦上位机接收到数据,就会想应该信号。将该信号绑定到显示接收内容的槽函数,一旦接收到数据,就开始显示。绑定方法如下
    // 将readyRead信号链接到Read_Data函数
    connect(serialPort, &QSerialPort::readyRead, this, &MainWindow::Read_Data);
 
显示数据的槽函数如下。
// 读取接收到的数据并显示
void MainWindow::Read_Data()
{
    QByteArray buf;
    // 读取全部接收数据
    buf = serialPort->readAll();
	
	// 如果接收内容非空
    if(!buf.isEmpty())
    {
        QString str = ui->receiveBrowser->toPlainText();
        str += tr(buf);
        ui->receiveBrowser->clear();
        ui->receiveBrowser->append(str);
    }
    buf.clear();
}
 
2.12 清空接收窗口
// 清除窗口
void MainWindow::on_clearButton_clicked()
{
    ui->receiveBrowser->clear();
}
 
2.13 定时发送
定时发送需要用到定时器,需要先添加定时器的头文件
// 定时器头文件
#include<QTimer>
 
然后添加一个定时发送时间的类成员
    // 定时发送时间间隔
    QTimer *timSend;
 
将倒计时结束信号关联到串口发送槽函数
    // 定时发送
    timSend = new QTimer;
    timSend->setInterval(1000);   // 设置默认值1000ms
    // 定时发送与发送按键函数关联
    connect(timSend,&QTimer::timeout,this,[=]()
    {
         on_sendButton_clicked();
    });
 
定时发送功能时使用复选框实现的。关于复选框上面介绍了,可以根据一个int型变量的值来判断选中状态。定时发送勾选后需要失能定时时间编辑和发送按钮。取消勾选后,恢复定时器时间编辑和发送按钮的使用。定时发送函数如下
// 定时发送
void MainWindow::on_timeSendBox_stateChanged(int arg1)
{
    // 获取复选框选中状态
    // 选中值为2,未选中值为0
    if (arg1 == 0)
    {
        // 结束计时
        timSend->stop();
        // 恢复定时时间可编辑
        ui->timeEdit->setEnabled(true);
        // 恢复发送按钮
        ui->sendButton->setEnabled(true);
    }
    else
    {
        // 根据设置时间开始计时
        timSend->start(ui->timeEdit->text().toInt());
        // 禁用定时时间可编辑
        ui->timeEdit->setEnabled(false);
        // 禁用发送按钮
        ui->sendButton->setEnabled(false);
    }
}
 
2.14 固定窗口大小
为了防止窗口大小改变,这里选择禁用最大化按钮,固定窗口大小,防止被鼠标拖动放大。程序实现方法如下
    // 禁用最大化按钮
    setWindowFlags(windowFlags()&~Qt::WindowMaximizeButtonHint);
    // 禁止拖动窗口大小
    setFixedSize(673,620);
 
窗口大小可以在UI文件中查看,选中窗口,查看右侧属性栏。

三、总结
3.1 将信号与槽函数关联
之前一直使用的是右键控件,选择转到槽来实现的关联。这里学习到了利用“connect”关联信号与槽函数。connect基本格式如下
connect(控件名, SIGNAL(要关联的信号), this, SLOT(槽函数));
 
也可以像上面关联readyRead信号时那样写
connect(serialPort, &QSerialPort::readyRead, this, &MainWindow::Read_Data);
 
或者像关联定时结束信号时那样写
    connect(timSend,&QTimer::timeout,this,[=]()
    {
         on_sendButton_clicked();
    });
 
3.2 优化方向
正如最开始所说,本文只是实现了简单的串口助手的功能,后续可以优化的方向还有很多。比如
- 支持显示中文
 - 支持保存接收内容
 - 下方显示接收到的内容字符数
 - 增加时间戳等
 
但是针对本人平时的使用已经足够了,因此暂时并不考虑优化,后续使用过程中遇到问题再考虑。有意思的是,本人使用该串口助手访问API时,能够看到API返回来的中文。下图是利用WIFI模块访问心知天气API返回的数据。


















