串口通信是我们经常会遇到的问题。很多时候当我们设计一个串口应用时,我们希望有一个简便的、可视的方式来验证它。这一篇中我们就来基于QT设计一个串口调试工具。
1、概述
在开始软件设计之前,我们来简略地分析一下这样一个小软件其要包含的主要内容有哪些。我们认为软件需要如下几个方面的内容:
- 串口参数的配置,我们希望串口号能够自动搜索,而相应的配置参数我们可以选择。
- 发送数据的输入,对于本软件我们需要输入相应的数据以实现命令及消息的发送,所以我们需要设计数据的输入区域以及发送交互按钮等。
- 接收信息的显示,作为调试工具,我们肯定希望能够一目了然地看到接收到目标设备发送过来的消息,所以我们需要一个显示区域来对接收的区域进行显示。
- 运行状态的显示, 我们希望对操作的状态进行反馈以指示操作的动作是否执行,所以我们需要状态栏来实现这一需求。
- 其它辅助功能, 还有如发送计数、接收计数、数据存储等功能有时候也是需要的,所以我们一并考虑。
对于串口工具其实网上就有不少,我们之所以要自己实现这么一个串口调试工具,主要的原因有两点。一是,网上找到的相应工具某一个单独的工具有时候不能完全满足我们的需求,所以我们根据自己的需求设计这个工具能更好的满足我们串口调试的需要。二是,通过这样一个工具的实现,我们能够加深对串口通讯相关知识的理解。
2、界面设计
根据上一节中分析的需求,我们先来设计软件的界面。我们在QT中基于QMainWindow类生成一个操作界面,包括菜单栏、工具栏和状态栏以满足需求中对状态显示及操作命令的要求。
而在中间显示区域,我们将其划分为3行2列。在左边的一列从上到下设置:串口配置操作区域、接收配置区域以及发送配置区域。在右侧的一列从上到下设置:动态曲线显示区域、信息接收显示区域以及信息发送输入区域。具体的界面设置如下图所示:
完成如上图的布局后,我们可以选择在属性中配置空间的参数,也可以在代码中添加相关的参数,本人习惯于在代码中完成。完成整个布局后我们先试着运行程序,正常运行则出现如下的界面:
上图就是完成布局后的运行界面,不过我们还没有实现相应的编码,所以目前还不能实现我们第一节中所提出来的功能。
3、编码实现
接下来这一小节,我们将来编码实现相应的功能。我们主要将功能分为参数设置与操作功能、数据的输入与发送功能以及数据的接收与显示功能三个部分来实现。
3.1、参数设置与操作功能
对于参数的配置除了串口号以外都可以直接使用ComboBox控件的相应函数添加。串口号这块,我们希望搜索电脑安装的串口并添加到控件中。具体的实现方式如下:
//搜索可用的串口,并添加到串口组合框
void MainWindow::SearchSerialPorts()
{
ui->comboBoxPort->clear();
foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts())
{
ui->comboBoxPort->addItem(info.portName());
}
}
配置好串口参数后,我们可以打开串口以建立连接。需要说明的是我们打开串口间离连接时,我们需要将该串口的数据接收与我们的数据接收和处理函数建立信号槽连接。具体实现如下:
//打开串口
void MainWindow::on_actionConnect_triggered()
{
serialPort->setPortName(ui->comboBoxPort->currentText());
if(serialPort->open(QIODevice::ReadWrite)) //打开串口成功
{
serialPort->setBaudRate(ui->comboBoxBaud->currentText().toInt()); //设置波特率
switch(ui->comboBoxData->currentIndex()) //设置数据位数
{
case 1:serialPort->setDataBits(QSerialPort::Data8);break;
default: break;
}
switch(ui->comboBoxParity->currentIndex()) //设置奇偶校验
{
case 0: serialPort->setParity(QSerialPort::NoParity);break;
default: break;
}
switch(ui->comboBoxStop->currentIndex()) //设置停止位
{
case 1: serialPort->setStopBits(QSerialPort::OneStop);break;
case 2: serialPort->setStopBits(QSerialPort::TwoStop);break;
default: break;
}
serialPort->setFlowControl(QSerialPort::NoFlowControl); //设置流控制
//连接槽函数
QObject::connect(serialPort, &QSerialPort::readyRead, this, &MainWindow::ReadSerialData);
// 设置控件可否使用
ui->actionConnect->setEnabled(false);
ui->actionClose->setEnabled(true);
ui->actionRefresh->setEnabled(false);
}
else //打开失败提示
{
QMessageBox::information(this,tr("错误"),tr("打开串口失败!"),QMessageBox::Ok);
}
}
同样的,我们除了要打开串口建立连接外,还需要关闭串口断开连接,具体的代码如下:
//关闭串口
void MainWindow::on_actionClose_triggered()
{
serialPort->clear();
serialPort->close();
// 设置控件可否使用
ui->actionConnect->setEnabled(true);
ui->actionClose->setEnabled(false);
ui->actionRefresh->setEnabled(true);
}
3.2、数据的输入与发送功能
数据的输入与发送,我们设计了5条命令,每条命令可以通过后面的按钮手动发送,也可以自动循环发送。自动循环发送时,将对每条选中的命令以设定的时间间隔轮询发送。
首先我们来看看定时周期发送的过程。我们定义了一个计时器,以我们设定的时间周期触发发送命令,每次发送复选框被选中的命令一条,依次循环直到人为停止循环发送为止。具体的代码如下:
//定时周期发送
void MainWindow::CycleSendData()
{
QCheckBox* cbSend;
while(true)
{
snIndex=snIndex>=6?1:snIndex;
cbSend=ui->groupBoxMessage->findChild<QCheckBox*>(QString("checkBoxSendEnable%1").arg(QString::number(snIndex)));
if(cbSend->isChecked())
{
WriteSerialData(snIndex);
snIndex++;
break;
}
snIndex++;
}
}
手动单次发送则判断是哪一个按钮触发的动作则操作对应的数据输入框,将其中的内容以指定的格式发送出去。具体的操作代码如下:
//按钮触发发送
void MainWindow::SingleSendData()
{
// 判断如果Sender是QPushButton就执行
if (QPushButton* btn = dynamic_cast<QPushButton*>(sender()))
{
QString senderName;
int sn=0;
senderName = btn->objectName();
sn = senderName.replace("pushButtonSend", "").toInt();
if((0<sn) && (sn<6))
{
WriteSerialData(sn);
}
}
}
3.3、数据的接收与现实功能
在我们的设计中,数据的接收相对要简单一些。当串口接收到数据后就会触发我们的接收数据处理函数,并将以我们设定的格式显示出来,具体的实现代码如下:
//从串口接收数据
void MainWindow::ReadSerialData()
{
QByteArray rxDatas;
QString context;
rxDatas=serialPort->readAll();
if(!rxDatas.isNull())
{
if(ui->checkBoxRecieve->isChecked()) //十六进制显示
{
context = rxDatas.toHex(' ');
context=context.toUpper();
}
else //ASCII显示
{
context = rxDatas;
}
QString timeStrLine="["+QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")+"][接收]: ";
context = timeStrLine+context+"\n\r";
QString content = "<span style=\" color:blue;\">"+context+"</span>";
ui->textBrowser->append(content);
receivedBytes=receivedBytes+rxDatas.size();
ui->lcdNumberRecieve->display(receivedBytes);
ui->statusbar->showMessage(tr("成功读取%1字节数据").arg(rxDatas.size()));
}
rxDatas.clear();
}
4、小结
完成了编码调试后,我们来对开发的这一工具进行一些测试。首先我们安装一个虚拟串口软件用以虚拟我们用于测试的串口。如果有硬件接口最好,但是在我的电脑上没有串口,所以我们使用虚拟串口来模拟一对串口。具体的配置如下图所示:
我们使用另一个串口工具来实现与我们开发的这一工具实现通讯验证。我们使用access port来实现与这一工具的通讯。access port使用COM12,SerialMaster使用COM12,相应的串口参数配置为一致。具体的配置如下图所示:
我们使用access port发送数据,SerialMaster接收到并在显示区显示为蓝色字符。同样我们通过SerialMaster手动发送一条信息,可以在access port看到相应的数据并且在SerialMaster的显示区域显示为红色字符。如下图所示:
接着我们试验自动循环发送。我们将发送取得三条命令输入,并将对应的复选框选择为选中。并将自动发送复选框选中,就会按设定的时间间隔发送相应的命令。如下图所示:
到这里我们想要的串口调试工具就基本实现了,当然,后续我们还可以更具需要修改或添加一些功能以适应不同的应用寻求,甚至可以嵌入到一些应用中以实现必要的测试功能。我们将代码发布到Gitee,欢迎下载和交流。
完整代码下载地址:https://gitee.com/ErichMoonan/serial-master