前言
1.一直都想要做一个Qt上位机,趁着这个周末有时间,动手写一下
2.comboBox没有点击的信号,所以做了一个触发的功能
3.Qt的数据类型很奇怪,转来转去的我也搞得很迷糊
4.给自己挖个坑,下一期做一个查看波形的上位机
有纰漏请指出,转载请说明。
学习交流请发邮件 1280253714@qq.com
串口功能
波特率设置
串口开关
串口异常检测
字符串/HEX收发
定时发送
接收数据分隔
协议组包
协议拆包
源代码
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QtSerialPort/QSerialPort> // 提供访问串口的功能
#include <QtSerialPort/QSerialPortInfo> // 提供系统中存在的串口信息
#include <QTime>
#include <QtCharts>
#include <QTimer>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
//用于表格设置
enum FieldColNum {
colTime,
colCmd1,
colCmd2,
colData,
};
enum CellType {
ctTime,
ctCmd1,
ctCmd2,
ctData,
};
public:
int tableRowCnt = 0;
public:
Widget(QWidget *parent = nullptr);
~Widget();
void serialPortInit(); //串口初始化
void windowInit(); //显示窗口初始化
void refreshCom(); //刷新串口
void tableInit(); //表格初始化
void createItemsARow(int rowNum, QByteArray *protocalData); //表格新建一行
QString ByteArrayToHexString(QByteArray &ba);
private:
Ui::Widget *ui;
QSerialPort* serialPort;
public slots:
void comboBoxClicked(); //comboBox被点击
private slots:
void sendData(); //发送串口数据
void receiveData(); //接收串口数据
void openSerialport(); //串口开启
void closeSerialport(); //串口关闭
void setBuad(int); //设置波特率
void clearRcv(); //清楚接收缓存
void on_btnConvert_clicked(); //转换按钮被点击
void on_btnClear_clicked(); //清楚转化的按钮被点击
void sendProtocalHexData(); //以hex格式发送串口数据
void handleSerialError(QSerialPort::SerialPortError serialPortErr); //串口异常捕获
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include "newcombobox.h"
#include <algorithm>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//创建一个定时器用来定时发送
QTimer *timer1 = new QTimer();
timer1->start(1000);
connect(timer1,&QTimer::timeout,[=](){
int timed = ui->comboBox_timedSend->currentText().toInt();
timer1->start(timed);
if(ui->checkBox_timedSend->isChecked() == true)
{
sendData();
}
});
windowInit();
tableInit();
serialPort = new QSerialPort();
serialPortInit();
connect(serialPort,SIGNAL(readyRead()),this,SLOT(receiveData()));
connect(serialPort,SIGNAL(errorOccurred(QSerialPort::SerialPortError)),this,SLOT(handleSerialError(QSerialPort::SerialPortError)));
connect(ui->pushButton_sendData,SIGNAL(clicked()),this,SLOT(sendData()));
connect(ui->pushButton_openSerialPort,SIGNAL(clicked()),this,SLOT(openSerialport()));
connect(ui->pushButton_closeSerialPort,SIGNAL(clicked()),this,SLOT(closeSerialport()));
connect(ui->comboBox_chooseCom,SIGNAL(clicked()),this,SLOT(comboBoxClicked()));
connect(ui->pushButton_clearRcv,SIGNAL(clicked()),this,SLOT(clearRcv()));
connect(ui->pushButton_convert,SIGNAL(clicked()),this,SLOT(on_btnConvert_clicked()));
connect(ui->pushButton_clearConvertData,SIGNAL(clicked()),this,SLOT(on_btnClear_clicked()));
connect(ui->pushButton_sendProtocalData,SIGNAL(clicked()),this,SLOT(sendProtocalHexData()));
connect(serialPort,SIGNAL(errorOccurred(QSerialPort::SerialPortError)),this,SLOT(handleSerialError(QSerialPort::SerialPortError)));
connect(ui->comboBox_setBuad,SIGNAL(activated(int)),this,SLOT(setBuad(int)));
}
Widget::~Widget()
{
delete ui;
}
//串口异常捕获
void Widget::handleSerialError(QSerialPort::SerialPortError serialPortErr)
{
if(serialPortErr == QSerialPort::ResourceError)
{
QMessageBox::critical(NULL, "critical", "设备拔出", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
closeSerialport();
}
if(serialPortErr == QSerialPort::DeviceNotFoundError)
{
QMessageBox::critical(NULL, "critical", "找不到串口", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
closeSerialport();
}
}
void Widget::comboBoxClicked()
{
refreshCom();
}
void Widget::windowInit()
{
ui->pushButton_closeSerialPort->setEnabled(false);
ui->pushButton_openSerialPort->setEnabled(false);
ui->pushButton_sendData->setEnabled(false);
setWindowTitle(tr("串口收发"));
ui->comboBox_timedSend->addItem("10");
ui->comboBox_timedSend->addItem("100");
ui->comboBox_timedSend->addItem("1000");
ui->comboBox_timedSend->setCurrentIndex(2);
}
void Widget::refreshCom()
{
//显示串口列表
ui->comboBox_chooseCom->clear();
foreach(QSerialPortInfo portInfo, QSerialPortInfo::availablePorts())
ui->comboBox_chooseCom->addItem(portInfo.portName()+":"+portInfo.description());
ui->pushButton_openSerialPort->setEnabled(ui->comboBox_chooseCom->count()>0); //
}
void Widget::serialPortInit(){
refreshCom();
ui->comboBox_setBuad->addItem("9600");
ui->comboBox_setBuad->addItem("115200");
ui->comboBox_setBuad->addItem("921600");
ui->comboBox_setBuad->setCurrentIndex(1);
}
void Widget::tableInit()
{
QStringList headerText;
headerText<<"时间"<<"命令1"<<"命令2"<<"数据";
ui->tableWidget->setColumnCount(headerText.size()); //设置表格列数
ui->tableWidget->resizeColumnsToContents();
for (int i=0;i<ui->tableWidget->columnCount();i++)
{
QTableWidgetItem *headerItem=new QTableWidgetItem(headerText.at(i));
QFont font=headerItem->font(); //获取原有字体设置
font.setBold(true); //设置为粗体
font.setPointSize(10); //字体大小
headerItem->setForeground(QBrush(Qt::black)); //设置文字颜色
headerItem->setFont(font); //设置字体
ui->tableWidget->setHorizontalHeaderItem(i,headerItem); //设置表头单元格的item
}
ui->tableWidget->setColumnWidth(0, 60);
ui->tableWidget->setColumnWidth(1, 50);
ui->tableWidget->setColumnWidth(2, 50);
ui->tableWidget->setColumnWidth(3, 250);
}
//为一行的单元格创建 Items
void Widget::createItemsARow(int rowNum, QByteArray *protocalData)
{
uchar preFix = 0xA5;
uchar crc = 0;
uchar temp = 0;
temp = static_cast<uchar>(protocalData->at(0));
if(static_cast<uchar>(protocalData->at(0)) == preFix)
{
for(int i=1; i<protocalData->length()-2; i++)
{
temp = static_cast<uchar>(protocalData->at(i));
crc += static_cast<uchar>(protocalData->at(i));
}
temp = static_cast<uchar>(protocalData->at(protocalData->length()-2));
if(crc != static_cast<uchar>(protocalData->at(protocalData->length()-2)))
{
return;
}
uchar len = protocalData->at(1);
uchar cmd1 = protocalData->at(2);
uchar cmd2 = protocalData->at(3);
QByteArray data = protocalData->mid(4,len-6);
QDateTime curTime = QDateTime::currentDateTime();//获取系统现在的时间
QString time = curTime.toString("hh:mm:ss"); //设置显示格式
uint8_t str1 = static_cast<uint8_t>(cmd1);
QString hexStr1 = QString("%1").arg(str1, 2, 16, QLatin1Char('0')).toUpper();
uint8_t str2 = static_cast<uint8_t>(cmd2);
QString hexStr2 = QString("%1").arg(str2, 2, 16, QLatin1Char('0')).toUpper();
QString testdata = ByteArrayToHexString(data).toLatin1().toUpper();
QTableWidgetItem *item = new QTableWidgetItem(time, ctTime);
ui->tableWidget->setItem(rowNum, colTime, item);
item = new QTableWidgetItem(hexStr1, ctCmd1);
ui->tableWidget->setItem(rowNum, colCmd1, item);
item = new QTableWidgetItem(hexStr2, ctCmd2);
ui->tableWidget->setItem(rowNum, colCmd2, item);
item = new QTableWidgetItem(testdata, ctData);
ui->tableWidget->setItem(rowNum, colData, item);
}
auto lastRowIndex = ui->tableWidget->rowCount()-1; // 最后一行的索引
auto lastModelIndex = ui->tableWidget->model()->index(lastRowIndex, 0);
ui->tableWidget->scrollTo(lastModelIndex); // 滚动到最后一行
}
QString Widget::ByteArrayToHexString(QByteArray &ba)
{
QDataStream out(&ba,QIODevice::ReadWrite); //将str的数据 读到out里面去
QString buf;
while(!out.atEnd())
{
qint8 outChar = 0;
out >> outChar; //每次一个字节的填充到 outchar
QString str = QString("%1").arg(outChar&0xFF,2,16,QLatin1Char('0')).toUpper() + QString(" "); //2 字符宽度
buf += str;
}
return buf;
}
void Widget::sendData()
{
QString message = ui->lineEdit_sendData->text();
if(ui->checkBox_hexSend->isChecked() == true)
{
serialPort->write(QByteArray::fromHex(message.toLatin1()));
}
else
{
serialPort->write(message.toLatin1());
}
}
void Widget::receiveData()
{
QByteArray message;
QString hexMsg;
message.append(serialPort->readAll());
QDateTime time = QDateTime::currentDateTime(); //获取系统现在的时间
QString date = time.toString("hh:mm:ss"); //设置显示格式
if(ui->checkBox_hexRcv->isChecked() == true)
{
tableRowCnt++;
ui->tableWidget->setRowCount(tableRowCnt);
createItemsARow(tableRowCnt-1,&message);
hexMsg = ByteArrayToHexString(message).toLatin1();
ui->textEdit_RecData->append(date+QString("-> ")+hexMsg.toUpper());
}
else
{
ui->textEdit_RecData->append(date+QString("-> ")+message);
}
}
void Widget::openSerialport()
{
ui->pushButton_closeSerialPort->setEnabled(true);
ui->pushButton_openSerialPort->setEnabled(false);
QList<QSerialPortInfo> comList = QSerialPortInfo::availablePorts();
QSerialPortInfo portInfo = comList.at(ui->comboBox_chooseCom->currentIndex());
serialPort->setPort(portInfo); //设置使用哪个串口
if(serialPort->open(QIODevice::ReadWrite) == false)
{
QMessageBox::critical(NULL, "critical", "找不到串口/串口被占用", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
closeSerialport();
}
else
{
serialPort->setBaudRate(QSerialPort::Baud115200);
serialPort->setDataBits(QSerialPort::Data8);
serialPort->setParity(QSerialPort::NoParity);
serialPort->setStopBits(QSerialPort::OneStop);
serialPort->setFlowControl(QSerialPort::NoFlowControl);
ui->pushButton_sendData->setEnabled(true);
}
}
void Widget::closeSerialport()
{
if(serialPort->isOpen()){
serialPort->clear();
serialPort->close();
}
ui->pushButton_closeSerialPort->setEnabled(false);
ui->pushButton_openSerialPort->setEnabled(true);
}
void Widget::setBuad(int buad)
{
QString str = ui->comboBox_setBuad->currentText();
serialPort->setBaudRate(str.toInt());
}
void Widget::clearRcv()
{
ui->textEdit_RecData->clear();
}
void Widget::on_btnClear_clicked()
{
ui->lineEdit_protocalData->clear();
}
void Widget::on_btnConvert_clicked()
{
ui->lineEdit_protocalData->clear();
bool ok;
QString str = "A5";
int val1= ui->lineEditCmd1->text().toInt(&ok,16); //以十六进制数读入
QString str1 = QString("%1").arg(val1, 2, 16, QLatin1Char('0'));
int val2= ui->lineEditCmd2->text().toInt(&ok,16); //以十六进制数读入
QString str2 = QString("%1").arg(val2, 2, 16, QLatin1Char('0'));
if ((str1.isEmpty())||(str2.isEmpty()))
return;
int val3= ui->lineEditData->text().toInt(&ok,16); //以十六进制数读入
QString str3 = QString("%1").arg(val3, 2, 16, QLatin1Char('0'));
uint8_t len = 6 + static_cast<uint8_t>(str3.length()/2);
QString hexStr = QString("%1").arg(len, 2, 16, QLatin1Char('0'));
str.append(hexStr);
str.append(str1);
str.append(str2);
str.append(str3);
uint8_t crc;
QString tmp;
for(int i=0; i<str3.length(); i+=2)
{
tmp = ui->lineEditData->text()[i];
tmp += ui->lineEditData->text()[i+1];
crc+= tmp.toInt(&ok,16);
tmp = "";
}
crc += len;
crc += val1;
crc += val2;
QString hexCrc= QString("%1").arg(crc, 2, 16, QLatin1Char('0'));
str.append(hexCrc);
str.append("5A");
str = str.toUpper();
ui->lineEdit_protocalData->insert(str);
}
void Widget::sendProtocalHexData()
{
QString message = ui->lineEdit_protocalData->text();
serialPort->write(QByteArray::fromHex(message.toLatin1()));
}
ui
代码框架概览
演示视频
串口上位机(基本设置/协议收发)演示_哔哩哔哩_bilibili
串口上位机(基本设置/协议收发)演示