基于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事件驱动模式制作串口助手

效果展示:

Android 串口热插拔 串口是否支持热插拔_串口

5.1 创建UI

这个UI界面做的优点拉跨,界面美化学习教程还希望各位大佬们给我分享分享…

但是功能还是很完善的,后续还会继续修改。


Android 串口热插拔 串口是否支持热插拔_qt_02

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;
}