Qt实现串口调试助手
1.目标
实现串口调试助手,界面风格简明,可配置关键通信参数包括,波特率、停止位、数据位、校验位;可以以16进制发送数据,以16进制或者ascii码接收数据,支持将接收数据保存至本地txt。
注:查看FPGA下位机串口代码实现,请查看FPGA串口通信
下载FPGA串口通信工程文件和代码,请查看FPGA串口通信vivado工程
2.步骤
2.1.pro当中添加,串口模块。
该模块Qt5支持,Qt4不支持;
QT += serialport
2.2串口相关类介绍
#include <QtSerialPort/QSerialPortInfo>
#include <QtSerialPort/QSerialPort>
QSerialPortInfo这个类将会获取串口的信息,如下:
qDebug()<<"serialPortName:"<<info.portName();
qDebug() << "Description : " << info.description();
qDebug() << "Manufacturer: " << info.manufacturer();
qDebug() << "Serial Number: " << info.serialNumber();
qDebug() << "System Location: " << info.systemLocation();
QSerialPort提供了多种方法,如基本的open、close、read、write方法,以及错误处理、设置串口参数等多个方法,将协调工作,共同完成串口收发的操作。
2.3界面原型搭建
一些界面布局细节,排版原则在下面无限保真度的界面原型中全部体现,这个搭建不需要手工代码。
2.4界面逻辑
(1)调用Init()函数,初始化函数将完成以下功能
上来先查询当前在线的串口设备,并将其写入下拉框;
初始化波特率选择下拉框,以及校验位、停止位、数据位;
设置默认以16进制显示;
清除接收缓冲区(黑色区域)。
(2)做按钮功能连接
”扫描串口“按钮重新扫描当前串口设备,并将其状态更新到对应下拉框中;
“打开串口”按钮将下发串口配置打开串口,添加打开失败模态提示用户,添加打开成功该按钮变为“关闭串口”;
"关闭串口"按钮将clear串口并关闭串口;
“清除接收”将textEdit clear掉;
“保存窗口”将当前接收框中内容保存至工程目录下的txt,并且清空当前接收框;
“发送”将进行数据处理操作并发送至打开的串口,当然会有串口失败的提示;
“清除发送”将清除当前的发送框;
(3)调整界面逻辑细节
“串口选择”QFrame固定大小,不随界面的大小变化;
“串口选择”中水平元素水平布局之后总体垂直布局;
“接收框”为只读模式;
设置“接收框”样式表,黑色背景,绿色字体;
“发送框”默认显示本人logo;
2.5底层逻辑
(1)实现获取当前配置函数,返回值为枚举类型,方便“打开串口操作”时调用,以获取校验位当前配置为例,如下:
enum QSerialPort::Parity MainWindow::getParity()
{
QString currentParityBit = ui->checkBit_Sel->currentText();
if (currentParityBit == "None")
return QSerialPort::NoParity;
else if (currentParityBit == "Odd")//奇数
return QSerialPort::OddParity;
else if (currentParityBit == "Even")//偶校验
return QSerialPort::EvenParity;
else
return QSerialPort::UnknownParity;
}
(2)接收数据,串口数据缓冲区收到数据会发出信号&QSerialPort::readyRead,故用户只需写槽函数并将其链接。
notice:
(2.1)比如对方发送 0x11、0x22、0x33、0x44、0x55,的数据,很可能会触发很多次 ReadSerialData() 调用,并不是收完这 5 个数据,才会触发一次,但是读走的数据,下次不会重复读到,这点可以放心;
(2.2)这里需要去判断读取的 QByteArray 的 buffer 是不是空,调试的时候遇到过,空数据,也触发了一次进入这个槽函数;
(2.3)接收(发送)数据类型为QByteArray的类型,接收侧需要做数据类型转换方便显示处理
str.append(QString::number((ary[i]&0xff),16));//QByteArray-->QString
(2.4)接收操作read、readAll,以及发送操作write都是非阻塞操作,即为函数执行,无论数据是否被全部接收(发送),函数都会立即返回,程序将会继续执行。
(2.5)支持16进制的显示、Ascii码的显示(两种数据类型处理)
(3)发送数据
默认按照16进制的方式发送数据,因此将只允许16进制相关的字符输入,其他的字符将不允许输入。
3.代码
3.1.cpp文件代码
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QMessageBox>
#include <QFile>
#include <QTextStream>
#include <QByteArray>
#include <QTextDecoder>
#include <QTextCodec>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//QAbstractNativeEventFilter QT系统事件过滤器,例如串口的插拔可以识别
//即中断的方式实时检测设备状态
Init();//初始化函数,作用:扫描在线串口;填充波特率、校验位、数据位、停止位窗口部件
//重新扫描在线串口(显示当前设备状态)
connect(ui->scanBtn,&QPushButton::clicked,[=](){
ui->comboBox->clear();
foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts())
{
ui->comboBox->addItem(info.portName());
}
});
//打开串口按钮
connect(ui->openBtn,&QPushButton::clicked,[=](){
if(ui->openBtn->text()=="打开串口")
{
// qDebug()<<getBaud()<<getParity()<<getdataBits()<<getstopBits();
mySerialPort->setPortName(ui->comboBox->currentText());
mySerialPort->setBaudRate(getBaud());
mySerialPort->setParity(getParity());
mySerialPort->setDataBits(getdataBits());
mySerialPort->setStopBits(getstopBits());
// mySerialPort->setFlowControl(QSerialPort::NoFlowControl); // TODO: FlowControl
// mySerialPort->setReadBufferSize(0); //缓冲区无限大
if(!(mySerialPort->open(QSerialPort::ReadWrite)))
{
//标准对话框提示打开串口失败
QMessageBox::warning(this,"警告","打开串口失败");
}
else
{
ui->openBtn->setText("关闭串口");
}
}
else
{
mySerialPort->clear();
mySerialPort->close();
ui->openBtn->setText("打开串口");
}
});
//保存按钮
connect(ui->saveBtn,&QPushButton::clicked,[=](){
QFile file("UART_Rec.txt");
file.open(QIODevice::Append);
//使用QTextStream写入文件
QTextStream tsm(&file);
tsm<<ui->reci_Edit->toPlainText();
ui->reci_Edit->clear();
file.close();
});
//清除接收按钮
connect(ui->clearRecBtn,&QPushButton::clicked,[=](){
ui->reci_Edit->clear();
});
//清除发送按钮
connect(ui->clearXferBtn,&QPushButton::clicked,[=](){
ui->xferEdit->clear();
});
//发送按钮
connect(ui->xferBtn, &QPushButton::clicked, this, [=](){
if(!mySerialPort->isOpen())
{
QMessageBox::information(this,"提示","请先打开串口");
}
else
{
// //获取发送框字符
QString str = ui->xferEdit->document()->toPlainText();
if (str.isEmpty())
{
QMessageBox::information(this,"提示","请先输入发送内容");
}
//默认16进制发送,过滤无用字符 包括0x
else
{
char ch;
bool flag = false;
uint32_t i, len;
//去掉无用符号
str = str.replace(' ',"");
str = str.replace(',',"");
str = str.replace('\r',"");
str = str.replace('\n',"");
str = str.replace('\t',"");
str = str.replace("0x","");
str = str.replace("0X","");
str = str.replace('\\',"");
str = str.replace(':',"");
str = str.replace('?',"");
str = str.replace(';',"");
//判断数据合法性
for(i = 0, len = str.length(); i < len; i++)
{
ch = str.at(i).toLatin1();
if (ch >= '0' && ch <= '9')
{
flag = false;
}
else if (ch >= 'a' && ch <= 'f')
{
flag = false;
}
else if (ch >= 'A' && ch <= 'F')
{
flag = false;
}
else
{
flag = true;
}
}
if(flag)
{
QMessageBox::warning(this,"警告","输入内容包含非法16进制字符");
}
else
{
//QString转QByteArray
QByteArray xferAry = str.toUtf8();
mySerialPort->write(xferAry);
}
}
}
});
//接收Buffer
connect(mySerialPort,&QSerialPort::readyRead,[=](){
if(ui->hex_Rtn->isChecked())
{
//以16进制显示
QByteArray ary = mySerialPort->readAll();
qDebug()<<ary;
QString str;
str.clear();
for (int i = 0;i<ary.size()-2 ;i++ ) {
str.append(QString::number((ary[i]&0xff),16));//将16进制原样输出
// str.append(" ");
}
ui->reci_Edit->append(str);
// ui->reci_Edit->append("\n");
}
else
{
//以Ascii码显示 Ascii码指的是16进制所对应的字符,就是ascii规定的一个16进制和字符之间的映射
ui->reci_Edit->append(mySerialPort->readAll());
ui->reci_Edit->append("\n");
}
});
}
MainWindow::~MainWindow()
{
if(mySerialPort->isOpen())
{
mySerialPort->clear();
mySerialPort->close();
}
delete mySerialPort;
delete ui;
}
enum QSerialPort::Parity MainWindow::getParity()
{
QString currentParityBit = ui->checkBit_Sel->currentText();
if (currentParityBit == "None")
return QSerialPort::NoParity;
else if (currentParityBit == "Odd")//奇数
return QSerialPort::OddParity;
else if (currentParityBit == "Even")//偶校验
return QSerialPort::EvenParity;
else
return QSerialPort::UnknownParity;
}
enum QSerialPort::BaudRate MainWindow::getBaud()
{
bool ok;
enum QSerialPort::BaudRate ret;
switch (ui->baud_Sel->currentText().toLong(&ok, 10))
{
case 1200:
ret = QSerialPort::Baud1200;
break;
case 2400:
ret = QSerialPort::Baud2400;
break;
case 4800:
ret = QSerialPort::Baud4800;
break;
case 9600:
ret = QSerialPort::Baud9600;
break;
case 19200:
ret = QSerialPort::Baud19200;
break;
case 38400:
ret = QSerialPort::Baud38400;
break;
case 57600:
ret = QSerialPort::Baud57600;
break;
case 115200:
ret = QSerialPort::Baud115200;
break;
default:
ret = QSerialPort::UnknownBaud;
break;
}
return ret;
}
enum QSerialPort::StopBits MainWindow::getstopBits()
{
QString currentStopBits = ui->stopBit_Sel->currentText();
if (currentStopBits == "1")
return QSerialPort::OneStop;
else if (currentStopBits == "1.5")
return QSerialPort::OneAndHalfStop;
else if (currentStopBits == "2")
return QSerialPort::TwoStop;
else
return QSerialPort::UnknownStopBits;
}
enum QSerialPort::DataBits MainWindow::getdataBits()
{
QString currentDataBits = ui->dataBit_Sel->currentText();
if(currentDataBits == "5")
return QSerialPort::Data5;
else if (currentDataBits == "6")
return QSerialPort::Data6;
else if (currentDataBits == "7")
return QSerialPort::Data7;
else if (currentDataBits == "8")
return QSerialPort::Data8;
else
return QSerialPort::UnknownDataBits;
}
void MainWindow::Init()
{
//上来先扫描一遍当前串口,初始化COM口选择组件
QStringList myPortName;
foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts())
{
myPortName << info.portName();
qDebug()<<"serialPortName:"<<info.portName();
qDebug() << "Description : " << info.description();
qDebug() << "Manufacturer: " << info.manufacturer();
qDebug() << "Serial Number: " << info.serialNumber();
qDebug() << "System Location: " << info.systemLocation();
}
myPortName.sort();
ui->comboBox->addItems(myPortName);
//初始化波特率组件
QStringList myBaud;
myBaud.clear();
myBaud<<"1200"<<"2400"<<"4800"<<"9600"<<"19200"<<"38400"<<"57600"<<"115200";
ui->baud_Sel->addItems(myBaud);
ui->baud_Sel->setCurrentText("9600");
//初始化停止位
QStringList mystopBits;
mystopBits.clear();
mystopBits<<"1"<<"1.5"<<"2";
ui->stopBit_Sel->addItems(mystopBits);
ui->stopBit_Sel->setCurrentText("1");
//初始化校验位
QStringList mycheckBits;
mycheckBits.clear();
mycheckBits<<"None"<<"Even"<<"Odd";
ui->checkBit_Sel->addItems(mycheckBits);
ui->checkBit_Sel->setCurrentText("None");
//初始化数据位
QStringList mydataBits;
mydataBits.clear();
mydataBits<<"5"<<"6"<<"7"<<"8";
ui->dataBit_Sel->addItems(mydataBits);
ui->dataBit_Sel->setCurrentText("8");
ui->hex_Rtn->setChecked(true);//设置默认是16进制显示
ui->reci_Edit->clear();
}
3.2.h代码
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QtSerialPort/QSerialPortInfo>
#include <QtSerialPort/QSerialPort>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
enum QSerialPort::Parity getParity();
enum QSerialPort::BaudRate getBaud();
enum QSerialPort::StopBits getstopBits();
enum QSerialPort::DataBits getdataBits();
void Init();
//创建一个串口对象
QSerialPort *mySerialPort = new QSerialPort();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
4.效果
使用虚拟串口软件VSPD虚拟串口,使用成熟软件XCOM V2.0(正点原子串口调试助手)与自己的软件通信,可以清晰的看到,软件收发正常。VSPD软件的获取与安装请查看工具类–虚拟串口工具
5.思考
两个想法,1.USB属于热插拔设备,系统可以动态的检测设备的插入与断开,那么如何让自己的软件动态的查看串口设备的连接状态呢?实际上,Qt提供了“中断”接口,可以做做升级版;
2.现在只支持16进制发送,支持16进制、ascii码的接收,那么是否可以考虑利用串口传输汉字呢?实际上,Qt支持多种字符集,可以尝试一番。