🎀 文章作者:二土电子
🐸 期待大家一起学习交流!
一、功能简介
最近尝试用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返回的数据。