前言
上位机的简单编写可以帮我们测试并完善平台,QT作为一款跨平台的GUI开发框架,提供了非常丰富的常用串口api。本文先从最简单的串口调试助手开始,编写平台软件的串口控制界面
工程配置
QT 串口通信基于QT的QSerialPort类,先在项目文件pro中添加QT += serialport。
避免默认的widget类和ui命名,将串口部分的ui命名为ui_serial,类命名为serial
界面设置
在UI界面可以直接搭建我们想要的界面显示方案,并自动生成相应代码添加头文件,用起来还是非常方便的,注意不要和代码里手动添加的混淆就好了
需要配置的可以选择QComboBox,添加常用的配置项,比如串口号,波特率,数据校验停止位等
添加led指示灯QLabel,可以用来美化界面
点击选中的可以选择QRadioButton,用以单选,直观的给以感受
收发的界面选择文本框QPlainTextEdit,可以多行显示
手动加的短文本框可以用QLineEdit,可以单行显示
其余的用最基础的pushbutton就可以了
为了界面不会被放大缩小导致排版混乱,可以添加弹簧或者直接写死窗口大小
设计完成后,会保存为对应名称比如serial.ui
构造函数里需要将有交互的部分connect起来,将界面按键的初始值做设置
我们可以通过继承QSerialPort类,也可以直接继承Qwidget类,在里面申明Qserialport
class serial : public QWidget
{
Q_OBJECT
public:
explicit serial(QWidget *parent = 0);
QSerialPort *serialPort;
~serial();
private slots:
void serialPortReadyRead_Slot();
void on_OpenBt_clicked();
void on_SendBt_clicked();
void on_SaveBt_clicked();
void on_ClearBt_clicked();
void on_pushButton_clicked();
private:
QPushButton *m_button;
void LED(bool changeColor); //串口连接指示灯
void closeEvent(QCloseEvent *event); /***关闭***/
void on_comStatus(QString name, bool flag);
Ui::serial *ui_serial;
};
serial::serial(QWidget *parent) :
QWidget(parent),
ui_serial(new Ui::serial)
{
ui_serial->setupUi(this);
setWindowTitle("串口控制界面-HX");
this->setFixedSize(980, 600); // 固定窗口的大小
/***************定义接收格式数据按钮********************/
QButtonGroup *btnGroupRev=new QButtonGroup(this);
btnGroupRev->addButton(ui_serial->ASCII_Receive_Box,0); // 将给定的按钮添加到按钮组
btnGroupRev->addButton(ui_serial->HEX_Receive_Box,1);
ui_serial->ASCII_Receive_Box->setChecked(true); // 设置默认模式
/***************定义发送格式数据按钮********************/
QButtonGroup *btnGroupSend=new QButtonGroup(this);
btnGroupSend->addButton(ui_serial->ASCII_Send_Box,0); // 将给定的按钮添加到按钮组
btnGroupSend->addButton(ui_serial->HEX_Send_Box,1);
ui_serial->ASCII_Send_Box->setChecked(true); // 设置默认模式
serialPort = new QSerialPort(this);
connect(serialPort, SIGNAL(readyRead()), this, SLOT(serialPortReadyRead_Slot()));
/***************串口热插拔********************/
ComChange::getInstance()->setHWND((HWND)this->winId());
connect(ComChange::getInstance(), &ComChange::comStatus, this, &serial::on_comStatus);
QStringList strName = ComChange::getAvailablePort(); // 获取所有可用串口
ui_serial->SerialCb->addItems(strName);
/*连接rtsp界面*/
// m_button = findChild<QPushButton*>("pushButton_3"); // 查找已有的pushButton3对象
// qDebug()<<"m_button is :"<<m_button;
// connect(this, &serial::buttonClicked_ctrl_left, this, &serial::on_pushButton_3_clicked);
// 连接信号与槽//
}
考虑到热插拔的问题封装getAvailablePort获取当前可用串口
ComChange* ComChange::m_comChange = nullptr;
ComChange *ComChange::getInstance()
{
if(m_comChange == nullptr)
{
static QMutex mutex; //实例互斥锁。
QMutexLocker locker(&mutex); //加互斥锁。
if(m_comChange == nullptr)
{
m_comChange = new ComChange();
}
}
return m_comChange;
}
/**
* @brief 获取系统中所有可用的串口名
* @return
*/
QStringList ComChange::getAvailablePort()
{
QStringList strName;
foreach(const QSerialPortInfo& info, QSerialPortInfo::availablePorts())
{
QSerialPort port(info);
if(port.open(QIODevice::ReadWrite))
{
strName << info.portName();
port.close();
}
}
return strName;
}
串口的热插拔
/*
函 数:on_comStatus
描 述:串口热插拔操作。有comchange的h文件、cpp文件和该函数,才能支持串口热插拔操作,缺一不可。移植请注意。
输 入:串口名称:name, 连接标志:flag
输 出:无
*/
void serial::on_comStatus(QString name, bool flag)
{
if(flag) ui_serial->SerialCb->addItem(name); // 串口插入时自动添加串口名
else
{
ui_serial->SerialCb->removeItem(ui_serial->SerialCb->findText(name)); // 串口拔出时自动移除串口名
LED(false); //红色LED 表示关闭串口
serialPort->close(); //关闭串口
ui_serial->OpenBt->setText("打开串口");
}
}
串口的初始化
在点击打开串口的时候做初始化,使用open(QIODevice::ReadWrite)
用ReadWrite 的模式尝试打开串口,打开成功后设置串口通信的波特率,校验方式等配置。(打开方式有多种,只读(r/o)、只写(w/o)或读写(r/w)模式)
直接使用serialPort的相关API
setPortName
将当前串口的名字设置为系统可用串口的名字
setBaudRate
设置波特率
setDataBits
设置数据位
setStopBits
设置停止位
setParity
设置校验位
close
关闭串口
注意:串口始终以独占访问方式打开(即没有其他进程或线程可以访问已打开的串口)。
/*
函 数:on_OpenBt_clicked
描 述:打开串口时初始化串口
输 入:无
输 出:无
*/
void serial::on_OpenBt_clicked()
{
if(ui_serial->OpenBt->text()=="打开串口")
{
serialPort->setPortName(ui_serial->SerialCb->currentText()); // 将当前串口的名字设置为系统可用串口的名字
qint32 baudrate = ui_serial->BaundCb->currentText().toInt(); // 获取期望的波特率
serialPort->setBaudRate(baudrate); // 设置波特率
//设置数据位
switch(ui_serial->DataCb->currentText().toInt())
{
case 8:
serialPort->setDataBits(QSerialPort::Data8); break;
case 7:
serialPort->setDataBits(QSerialPort::Data7); break;
case 6:
serialPort->setDataBits(QSerialPort::Data6); break;
case 5:
serialPort->setDataBits(QSerialPort::Data5); break;
}
//设置停止位
if(ui_serial->StopCb->currentText() == "1")
{
serialPort->setStopBits(QSerialPort::OneStop);
}
else if(ui_serial->StopCb->currentText() == "1.5")
{
serialPort->setStopBits(QSerialPort::OneAndHalfStop);
}
else if(ui_serial->StopCb->currentText() == "2")
{
serialPort->setStopBits(QSerialPort::TwoStop);
}
//设置校验位
if(ui_serial->CheckCb->currentText() == "None")
{
serialPort->setParity(QSerialPort::NoParity);
}
else if(ui_serial->CheckCb->currentText() == "Even")
{
serialPort->setParity(QSerialPort::EvenParity);
}
else if(ui_serial->CheckCb->currentText() == "Odd")
{
serialPort->setParity(QSerialPort::OddParity);
}
LED(true); //绿色LED 表示打开串口
//串口连接失败提示
if(serialPort->open(QIODevice::ReadWrite) == false)
{
QMessageBox::critical(this, "提示", "串口连接失败");
LED(false); //红色LED 表示关闭串口
}
ui_serial->OpenBt->setText("关闭串口");
}
else
{
LED(false); //红色LED 表示关闭串口
serialPort->close(); //关闭串口
ui_serial->OpenBt->setText("打开串口");
}
}
串口的收发
根据hex和ASCII在收发时做判断serialPort->readAll();
可以接收所有信息,serialPort->write(SendTextByte)
; /可以通过串口将数据发送出去
/*
函 数:serialPortReadyRead_Slot
描 述:上位机接收数据
输 入:无
输 出:无
*/
void serial::serialPortReadyRead_Slot()
{
QByteArray buf = serialPort->readAll(); //从串口读取信息
if(ui_serial->ASCII_Receive_Box->isChecked()) //如果设置接收ASCII
{
ui_serial->ReceiveEdit->insertPlainText(QString::fromLocal8Bit(buf)); // 对串口接收的数据进行编码
}
else if(ui_serial->HEX_Receive_Box->isChecked()) //如果设置接收HEX
{
QDataStream out(&buf, QIODevice::ReadWrite); //读取数据
while(!out.atEnd()) //读取是否完成
{
qint8 outChar = 0;
out >> outChar;
QString str = QString("%1").arg(outChar&0xFF,2,16,QLatin1Char('0')); //转换16进制
ui_serial->ReceiveEdit->insertPlainText(str+" "); //每显示一次后面加一个空格
}
}
}
/*
函 数:on_SendBt_clicked
描 述:上位机发送数据
输 入:无
输 出:无
*/
void serial::on_SendBt_clicked()
{
QString sendstr=ui_serial->SendEdit->toPlainText(); //获取将要发送数据
if(ui_serial->ASCII_Send_Box->isChecked()) // 如果发送ASCII模式
{
QByteArray SendTextByte = sendstr.toLocal8Bit(); //将发送的数据转换格式
serialPort->write(SendTextByte); // 通过串口将数据发送出去
}
else if(ui_serial->HEX_Send_Box->isChecked()) // 如果发送HEX
{
QByteArray SendTextByte = QByteArray::fromHex(sendstr.toLatin1()); // 转换数据格式
serialPort->write(SendTextByte); // 通过串口将数据发送出去
}
}
接收区清除
为了直观的读取接收区数据,也需要增加清除当前数据的功能
/*
函 数:on_ClearBt_clicked
描 述:清空接收区的数据
输 入:无
输 出:无
*/
void serial::on_ClearBt_clicked()
{
ui_serial->ReceiveEdit->clear();
}
接收数据的保存
数据量大的时候经常需要保存接收数据的log,所以存log的功能也是必不可少的
file.open(QFile::WriteOnly | QFile::Text)
可以将文本数据框取出并按行排列
/*
函 数:on_SaveBt_clicked
描 述:以TXT格式保存接收区数据
输 入:无
输 出:无
*/
void serial::on_SaveBt_clicked()
{
QString textFile = QFileDialog::getSaveFileName(this,tr("Save txt"),
"",tr("text (*.txt)")); //选择路径
//将文本框数据取出并按行排列
QFile file(textFile);//文件命名
if (!file.open(QFile::WriteOnly | QFile::Text)) //检测文件是否打开
{
QMessageBox::information(this, "Error Message", "Please Select a Text File!");
return;
}
QTextStream out(&file); //分行写入文件
out << ui_serial->ReceiveEdit->toPlainText();
}
指示灯的使用
为了美化界面,直观显示串口是否正常连接,可以加个指示灯,设置绿色表示开红色表示关
/*
函 数:closeEvent
描 述:关闭窗口时若未关闭串口,则要关闭串口
输 入:无
输 出:无
*/
void serial::closeEvent(QCloseEvent *event)
{
if(serialPort->isOpen()) // 串口处于打开状态
{
LED(false); //红色LED 表示关闭串口
serialPort->close(); // 串口关闭
event->accept(); // 界面关闭
}
}
/*
函 数:LED
描 述:串口指示灯
输 入:bool changeColor
输 出:无
*/
void serial::LED(bool changeColor)
{
if(changeColor == true)
{
// 显示绿色
ui_serial->LED->setStyleSheet("background-color: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0 rgba(0, 229, 0, 255), stop:1 rgba(255, 255, 255, 255));border-radius:9px;");
}
else
{
// 显示红色
ui_serial->LED->setStyleSheet("background-color: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0 rgba(255, 0, 0, 255), stop:1 rgba(255, 255, 255, 255));border-radius:9px;");
}
}
窗口退出
为了避免退出的时候占用串口,关闭窗口的时候需要关闭串口
/*
函 数:closeEvent
描 述:关闭窗口时若未关闭串口,则要关闭串口
输 入:无
输 出:无
*/
void serial::closeEvent(QCloseEvent *event)
{
if(serialPort->isOpen()) // 串口处于打开状态
{
LED(false); //红色LED 表示关闭串口
serialPort->close(); // 串口关闭
event->accept(); // 界面关闭
}
}
具体功能
剩下的就是根据协议规定点击pushbutton发送相应的内容了