基于Qt5的串口助手(自动刷新串口支持热插拔,附带源码)
qextserialport库下载链接:
http://code.google.com/p/qextserialport/
由于即将读研,暑假长而无聊,开始学习QT开发桌面软件。这是我学习过程学的第一个小项目,算demo吧,参考了正点原子的XCOM和友善串口助手的优点。项目本身很简单,但由于实在没有经验花了一定时间。
1.添加源码到工程
将qextserialport-1.2rc.zip解压,将解压后的src目录拷贝到项目里的子目录SerialSrc下
在项目pro文件中增加下面这行:include(./serialSrc/src/qextserialport.pri)
2.读取在线串口
qextserialport库本身带一个有实现获取串口的函数,由于我做的时候没有注意,花了大量时间学了下如何读取windows的注册表来获取串口,也可以实现同样的功能。源码如下:
getSerialCommNames此函数用于获取串口
void MainWindow::getSerialCommNames(QStringList &list)
{
list.clear();
QSettings settings("HKEY_LOCAL_MACHINE\\HARDWARE\\DEVICEMAP\\SERIALCOMM",QSettings::NativeFormat);
QStringList keyList = settings.allKeys();
HKEY hroot;//打开的注册表键句柄
DWORD type = REG_SZ;
DWORD size = MAX_PATH;
wchar_t data[MAX_PATH];// 使用宽字节
RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("HARDWARE\\DEVICEMAP\\SERIALCOMM"), 0, KEY_READ, &hroot);
for (int i = 0; i < keyList.count(); i++)
{
QString key = keyList.at(i);
key.replace('/', '\\');
RegQueryValueExW(hroot, key.toStdWString().c_str(), 0, &type, (LPBYTE)data, &size);
QString str_name = QString::fromStdWString(data);
list.append(str_name);
}
RegCloseKey(hroot);
list.sort();
return;
}
3.实现热插拔更新串口
如果要支持热插拔的话,需要一个事件函数nativeEvent,设备状态改变发送信号WM_DEVICECHANGE。此函数如下:
bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, long* result)
{
MSG* msg = reinterpret_cast<MSG*>(message);
if (msg->message == WM_DEVICECHANGE)//发生硬件热插拔事件,更新可用串口号
{
QStringList list;
getSerialCommNames(list);
ui->cboxPortName->clear();
ui->cboxPortName->addItems(list);
}
return false;
}
4.串口库相关使用
串口库的使用官方没有给出具体文档,需要参考example例程来学习
串口读取模式
EventDriven( 事件驱动方式):
使用事件处理串口的读取,一旦有数据到来,就会发出**readyRead()**信号,我们可以关联该信号来读取串口的数据。在事件驱动的方式下,串口的读写是异步的,调用读写函数会立即返回,它们不会冻结调用线程。
Polling (查询方式) :
读写函数是同步执行的,信号不能工作在这种模式下,而且有些功能也无法实现。但是这种模式下的开销较小。我们需要自己建立定时器来读取串口的数据。
在Windows下支持以上两种模式,而在Linux下只支持Polling模式。
我这个Demo中所使用的是Polling方式
QextSerialPort类:
用来描述具体的一个端口,可以通过它的成员函数,来获取/设置该端口的波特率,名称,停止位等,也可以通过该类来打开/关闭某个端口
5.示例-使用EventDriven事件驱动模式制作串口助手
效果展示:
5.1 创建UI
这个UI界面做的优点拉跨,界面美化学习教程还希望各位大佬们给我分享分享…
但是功能还是很完善的,后续还会继续修改。
5.2 头文件
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <qextserialport/qextserialport.h>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
protected:
void initComboBoxs();
void initSerial();
void Change_btn_isOn(bool ison);
protected:
bool nativeEvent(const QByteArray& eventType, void* message, long* result) override;//监控Windows系统事件(硬件热插拔)
private:
QextSerialPort *com; //串口通信对象
QTimer *timerRead; //定时读取串口数据
void readData();
void getSerialCommNames(QStringList &list);
private slots:
void on_btnOpen_clicked();
void on_btnSend_clicked();
void on_btnClear_clicked();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
5.4 源文件
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QSettings>
#include <QString>
#include <QThread>
#include <QTimer>
#include <qdebug.h>
#include <qstring.h>
#include <qt_windows.h>
#include <qthread.h>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->txtReceive->setReadOnly(true);
initComboBoxs();
//创建定时器对象
timerRead= new QTimer();
timerRead->setInterval(100);
connect(timerRead, &QTimer::timeout, this, &MainWindow::readData);
void(QComboBox::*recodactivatedsignal)(int)=&QComboBox::activated;
connect(ui->recordBox,recodactivatedsignal,this,[=](int num){
if(num==ui->recordBox->count()-1)
{
ui->recordBox->clear();
ui->recordBox->addItem(QString::fromLocal8Bit("清除记录"));
}
else
{
ui->txtSend->clear();
ui->txtSend->appendPlainText(ui->recordBox->currentText());
}
});
}
//初始化comboBoxs控件
void MainWindow::initComboBoxs()
{
BaudRateType RateTypes[12]={
BAUD1200,BAUD2400 ,BAUD4800,BAUD9600 ,
BAUD14400,BAUD19200,BAUD38400,BAUD56000,
BAUD57600,BAUD115200,BAUD128000, BAUD256000};
DataBitsType BitsTypes[4]={DATA_5,DATA_6, DATA_7, DATA_8};
for(int i=0;i<12;i++)
{
ui->cboxBaudRate->addItem(QString("%1").arg((int)RateTypes[i]),RateTypes[i]);
}
ui->cboxBaudRate->setCurrentIndex(9); //设置默认值
for(int i=0;i<4;i++)
{
ui->cboxDataBit->addItem(QString("%1").arg((int)BitsTypes[i]),BitsTypes[i]);
}
ui->cboxDataBit->setCurrentIndex(3); //设置默认值8位数据
ui->cboxParity->addItem(QString::fromLocal8Bit("无"),PAR_NONE);
ui->cboxParity->addItem(QString::fromLocal8Bit("奇校验"),PAR_ODD);
ui->cboxParity->addItem(QString::fromLocal8Bit("偶校验"),PAR_EVEN);
ui->cboxStopBit->addItem("1",STOP_1);
ui->cboxStopBit->addItem("1.5",STOP_1_5);
ui->cboxStopBit->addItem("2",STOP_2);
QStringList SerialPortNameList;
getSerialCommNames(SerialPortNameList);
ui->cboxPortName->addItems(SerialPortNameList);
ui->recordBox->addItem(QString::fromLocal8Bit("清除记录"));
ui->recordBox->setCurrentIndex(-1);
}
void MainWindow::getSerialCommNames(QStringList &list)
{
list.clear();
QSettings settings("HKEY_LOCAL_MACHINE\\HARDWARE\\DEVICEMAP\\SERIALCOMM",QSettings::NativeFormat);
QStringList keyList = settings.allKeys();
HKEY hroot;//打开的注册表键句柄
DWORD type = REG_SZ;
DWORD size = MAX_PATH;
wchar_t data[MAX_PATH];// 使用宽字节
RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("HARDWARE\\DEVICEMAP\\SERIALCOMM"), 0, KEY_READ, &hroot);
for (int i = 0; i < keyList.count(); i++)
{
QString key = keyList.at(i);
key.replace('/', '\\');
RegQueryValueExW(hroot, key.toStdWString().c_str(), 0, &type, (LPBYTE)data, &size);
QString str_name = QString::fromStdWString(data);
list.append(str_name);
}
RegCloseKey(hroot);
list.sort();
return;
}
void MainWindow::readData()
{
if (com->bytesAvailable() <= 0) {
return;
}
QByteArray data = com->readAll();
int dataLen = data.length();
if (dataLen <= 0) {
return;
}
QString buffer;
buffer = QString::fromLocal8Bit(data);
QTextCursor tc = ui->txtReceive->textCursor();
tc.movePosition(QTextCursor::End);
tc.insertText(buffer);
}
/****************槽函数***************/
void MainWindow::on_btnOpen_clicked()
{
static bool comstatu;
if (ui->btnOpen->text() == QString::fromLocal8Bit("打开串口"))
{
com = new QextSerialPort(ui->cboxPortName->currentText(), QextSerialPort::Polling);
comstatu = com->open(QIODevice::ReadWrite);
if (comstatu)
{
ui->btnOpen->setText(QString::fromLocal8Bit("关闭串口"));
//清空缓冲区
com->flush();
//设置波特率
com->setBaudRate((BaudRateType)ui->cboxBaudRate->currentText().toInt());
//设置数据位
com->setDataBits((DataBitsType)ui->cboxDataBit->currentText().toInt());
//设置校验位
com->setParity((ParityType)ui->cboxParity->currentIndex());
//设置停止位
com->setStopBits((StopBitsType)ui->cboxStopBit->currentIndex());
com->setFlowControl(FLOW_OFF);
com->setTimeout(10);
timerRead->start(); //初始化定时器
}
}
else {
timerRead->stop();
com->close();
com->deleteLater();
ui->btnOpen->setText(QString::fromLocal8Bit("打开串口"));
comstatu = false;
}
}
void MainWindow::on_btnSend_clicked()
{
QString sendstring=ui->txtSend->toPlainText();
sendstring.replace("\n","\r\n");
if(sendstring.length()<=0)
{
return;
}
QByteArray sendbytes=sendstring.toLocal8Bit();
com->write(sendbytes);
int num_items=ui->recordBox->count();
for(int i=0;i<num_items;i++)
{
if(sendstring==ui->recordBox->itemText(i))
break;
else if(i==ui->recordBox->count()-1)
{
ui->recordBox->insertItem(0,sendstring);
}
}
}
void MainWindow::on_btnClear_clicked()
{
ui->txtReceive->clear();
}
/*****************************事件函数*****************************/
bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, long* result)
{
MSG* msg = reinterpret_cast<MSG*>(message);
if (msg->message == WM_DEVICECHANGE)//发生硬件热插拔事件,更新可用串口号
{
QStringList list;
getSerialCommNames(list);
ui->cboxPortName->clear();
ui->cboxPortName->addItems(list);
}
return false;
}
MainWindow::~MainWindow()
{
delete ui;
}