一  前言

之前用MFC开发了一款串口调试助手,今天又冒出来一个想法,使用Qt也开发一款并与之通信,看看Qt是否比MFC开发更简洁呢?MFC串口调试助手博客:

Qt版本:Qt 5.9.6              Author:WSG              Date:2018/07/23

先来看看两款软件之间的通信吧!

串口调试助手centos版本_串口调试助手centos版本

由于时间的限制,现在快到凌晨1点了,今天还要上班......没有做得很完全,实现了基本功能。接下来老规矩,分享源码!!!

 

二  头文件 SerialPort.h

#ifndef SERIALPORT_H
#define SERIALPORT_H

#include <QMainWindow>
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>

#include <QLabel>
#include <QPushButton>
#include <QCloseEvent>

namespace Ui {
class SerialPort;
}

class SerialPort : public QMainWindow
{
    Q_OBJECT

public:
    explicit SerialPort(QWidget *parent = 0);
    ~SerialPort();

    /* 初始状态栏 */
    void InitStatusBar();   // 初始化状态栏
    void SetSerState();     // 设置状态栏串口状态
    void SetRecvNum();      // 设置接收字节数
    void SetSendNum();      // 设置发送字节数

    /* 初始化CombBox控件 */
    void InitCommCmb();         // 初始化CombBox控件
    void SetPortNumCmb();       // 设置串口号
    QStringList GetEnableCommPortQt();      // 获取计算机可用串口 Qt方式
    QStringList GetEnableCommPortWin();     // 获取计算机可用串口 注册表方式
    QString GetComm(int nIndex, bool bValue = true);
    void SetBaudCmb();          // 设置波特率
    void SetDPaityCmb();        // 设置校验位
    void SetDataBCmb();         // 设置数据位
    void SetStopBCmb();         // 设置停止位
    void SetStreamCmb();        // 设置流控制

    bool SetSerialPortParam(QSerialPort *serial);   // 设置串口参数,失败返回false,成功返回true

    /* 父类函数重写 */
    void closeEvent(QCloseEvent *event);

private slots:
    void on_baudRCmb_currentIndexChanged(int index);    // 自定义波特率
    void on_OnOffBtn_clicked();                         // 打开/断开串口
    void on_ReflushSerPortBtn_clicked();    // 刷新串口,重新扫描计算机可用串口
    void slot_RecvPortData();               // 接收串口数据
    void on_SendBtn_clicked();              // 发送数据
    void on_ClearRecvBtn_clicked();         // 清空接收区
    void slot_ResetNumBtn_clicked();        // 复位计数

private:
    Ui::SerialPort *ui;

    /* 状态栏控件 */
    QLabel *m_SerStateLbl;        // 串口状态
    QLabel *m_RecvNumLbl;         // 接收字节数
    QLabel *m_SendNumLbl;         // 发送字节数
    QPushButton *m_ResetNumBtn;   // 复位计数按钮

    /* 发送、接收字节数 */
    uint m_nRecvNum;              // 接收字节数
    uint m_nSendNum;              // 发送字节数

    bool m_bOpen;                 // 标识串口状态

    QSerialPort *m_serial;        // 串口通信类对象
    qint64 m_nReadBuffSize;       // 串口缓冲区大小
};

#endif // SERIALPORT_H

Qt串口编程主要用到的就是QSerialPort和QSerialPortInfo这两个类了,注意要在pro文件里添加 QT += serialport

 

三  实现源代码 SerialPort.cpp

/* 串口编程四步走:
* 1.设置串口参数
* 2.打开串口
* 3.读/写串口
* 4.关闭串口
* By WSG 2018/07/22
*/

#include "SerialPort.h"
#include "ui_SerialPort.h"

#include <QSettings>
#include <qt_windows.h>
#include <QMessageBox>
#include <QDebug>

SerialPort::SerialPort(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::SerialPort)
{
    ui->setupUi(this);

    this->setWindowTitle(tr("串口调试助手 Qt版 V1.0"));
    this->setFixedSize(this->width(), this->height());

    m_nRecvNum = 0;
    m_nSendNum = 0;
    m_bOpen = false;

    InitStatusBar();
    InitCommCmb();

    ui->OnOffBtn->setIcon(QIcon(":/pic/res/OFF.png"));
    ui->RecvDataEdt->setReadOnly(true);

    m_serial = new QSerialPort;
    connect(m_serial, SIGNAL(readyRead()), this, SLOT(slot_RecvPortData()));
    m_nReadBuffSize = 64;

}

SerialPort::~SerialPort()
{
    delete ui;
    delete m_SerStateLbl;
    delete m_RecvNumLbl;
    delete m_SendNumLbl;
    delete m_ResetNumBtn;
}

/***************************************************************************/
/*                              初始化状态栏                                 */
/***************************************************************************/

void SerialPort::InitStatusBar()
{
    m_SerStateLbl = new QLabel();
    m_RecvNumLbl = new QLabel();
    m_SendNumLbl = new QLabel();
    m_ResetNumBtn = new QPushButton();
    connect(m_ResetNumBtn, SIGNAL(clicked()), this, SLOT(slot_ResetNumBtn_clicked()));

    m_SerStateLbl->setMinimumSize(180, 20);
    m_RecvNumLbl->setMinimumSize(180, 20); // 标签最小尺寸
    m_SendNumLbl->setMinimumSize(180, 20);

    ui->statusBar->addWidget(m_SerStateLbl);
    ui->statusBar->addWidget(m_RecvNumLbl);
    ui->statusBar->addWidget(m_SendNumLbl);
    ui->statusBar->addWidget(m_ResetNumBtn);

    SetSerState();
    SetRecvNum();
    SetSendNum();
    m_ResetNumBtn->setText(tr("复位计数"));
}

void SerialPort::SetSerState()
{
    QString strState;
    if ( m_bOpen )
        strState = tr("打开");
    else
        strState = tr("关闭");

    m_SerStateLbl->setText(tr("串口状态:%1").arg(strState));
}

void SerialPort::SetRecvNum()
{
    QString strRecvNum = QString::number(m_nRecvNum);
    m_RecvNumLbl->setText(tr("接收字节数:%1").arg(strRecvNum));
}

void SerialPort::SetSendNum()
{
    QString strSendNum = QString::number(m_nSendNum);
    m_SendNumLbl->setText(tr("发送字节数:%1").arg(strSendNum));
}

/***************************************************************************/
/*                            初始化CombBox                                 */
/***************************************************************************/

void SerialPort::InitCommCmb()
{
    SetPortNumCmb();    // 串口号
    SetBaudCmb();       // 波特率
    SetDPaityCmb();     // 校验位
    SetDataBCmb();      // 数据位
    SetStopBCmb();      // 停止位
    SetStreamCmb();     // 流控制
}

// 设置串口号
void SerialPort::SetPortNumCmb()
{
    QStringList commPortList = GetEnableCommPortQt(); // 或者GetEnableCommPortWin(); 两种方式皆能正确获取计算机可用串口号
    if ( !commPortList.isEmpty() )
        ui->PortNumCmb->addItems(commPortList);
}

// 获取计算机可用串口 QSerialPort QSerialPortInfo类
QStringList SerialPort::GetEnableCommPortQt()
{
    QStringList CommPortList;

    foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
    {
        QSerialPort serial;
        serial.setPort(info);
        if (serial.open(QIODevice::ReadWrite))
        {
            CommPortList.append(serial.portName());
            serial.close();
        }
    }

    return CommPortList;
}

// 获取计算机可用串口 从注册表中读取
QStringList SerialPort::GetEnableCommPortWin()
{
    QStringList CommPortList;

    QString strCommPath = tr("HKEY_LOCAL_MACHINE\\HARDWARE\\DEVICEMAP\\SERIALCOMM");
    QSettings *settings = new QSettings(strCommPath, QSettings::NativeFormat);
    QStringList keyList = settings->allKeys();

    if ( !keyList.isEmpty() )
    {
        for (int i = 0; i < keyList.count(); i++)
            CommPortList.append(GetComm(i));
    }

    return CommPortList;
}

// nIndex为索引号 bValue选择返回值为key还是value,默认为value
QString SerialPort::GetComm(int nIndex, bool bValue)
{
    QString strCommRet = tr("");
    HKEY hKey;

    // RegOpenKeyEx,打开注册表 windows Api 成功返回0 需要加<qt_windows.h>头文件
    if (::RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("HARDWARE\\DEVICEMAP\\SERIALCOMM"), 0, KEY_READ, &hKey) != 0)
    {
        QMessageBox::warning(this, tr("注册表"), tr("无法打开注册表!"), QMessageBox::Ok);
        return tr(""); // 无法打开注册表
    }

    QString strKeyMsg;      // 键名
    QString strValueMsg;    // 键值
    wchar_t keyName[256];   // 键名数组
    char keyValue[256];     // 键值数组

    ulong nKeySize = sizeof(keyName);
    ulong nValueSize = sizeof(keyValue);
    ulong nType;

    if (::RegEnumValue(hKey, nIndex, keyName, &nKeySize, 0, &nType, (BYTE*)keyValue, &nValueSize) == 0) // 列举键名
    {
        // 读取键名
        for (uint i = 0; i < nKeySize; i++)
        {
            if (keyName[i] != 0x00)
                strKeyMsg.append(keyName[i]);
        }

        // 读取键值
        for (uint j = 0; j < nValueSize; j++)
        {
            if (keyValue[j] != 0x00)
                strValueMsg.append(keyValue[j]);
        }

        if ( bValue )
            strCommRet = strValueMsg;
        else
            strCommRet = strKeyMsg;
    }//if
    else
    {
        ::RegCloseKey(hKey); // 关闭注册表
        return "";  // 无可用串口
    }

    return strCommRet;
}

 // 设置波特率
void SerialPort::SetBaudCmb()
{
    QStringList baudRList;
    baudRList.append(tr("110"));
    baudRList.append(tr("300"));
    baudRList.append(tr("600"));
    baudRList.append(tr("1200"));
    baudRList.append(tr("2400"));
    baudRList.append(tr("4800"));
    baudRList.append(tr("9600"));
    baudRList.append(tr("14400"));
    baudRList.append(tr("19200"));
    baudRList.append(tr("38400"));
    baudRList.append(tr("56000"));
    baudRList.append(tr("57600"));
    baudRList.append(tr("115200"));
    baudRList.append(tr("128000"));
    baudRList.append(tr("256000"));
    baudRList.append(tr("自定义"));

    ui->baudRCmb->addItems(baudRList);
    ui->baudRCmb->setCurrentIndex(6);
}

// 设置校验位
void SerialPort::SetDPaityCmb()
{
    QStringList DPaityList;
    DPaityList.append(tr("NONE"));
    DPaityList.append(tr("ODD"));
    DPaityList.append(tr("EVEN"));
    DPaityList.append(tr("MARK"));
    DPaityList.append(tr("SPACE"));

    ui->DPaityCmb->addItems(DPaityList);
}

 // 设置数据位
void SerialPort::SetDataBCmb()
{
    for (int i = 5; i <= 8; i++)
    {
        QString strDataB = QString::number(i);
        ui->DataBCmb->addItem(strDataB);
    }
    ui->DataBCmb->setCurrentIndex(3);
}

// 设置停止位
void SerialPort::SetStopBCmb()
{
    ui->StopBCmb->addItem(tr("1"));
    ui->StopBCmb->addItem(tr("1.5"));
    ui->StopBCmb->addItem(tr("2"));
}

// 流控制
void SerialPort::SetStreamCmb()
{
    ui->FlowCtrlCmb->addItem(tr("NO"));
    ui->FlowCtrlCmb->addItem(tr("RTS/CTS"));
    ui->FlowCtrlCmb->addItem(tr("XON/XOFF"));
}

/***************************************************************************/
/*                                 槽函数                                   */
/***************************************************************************/
// 波特率自定义
void SerialPort::on_baudRCmb_currentIndexChanged(int index)
{
    uint nCount = ui->baudRCmb->count();
    if ((unsigned)index == nCount - 1)
    {
        ui->baudRCmb->setEditable(TRUE);
        ui->baudRCmb->setItemText(index, tr(""));
    }
    else
    {
        ui->baudRCmb->setEditable(FALSE);
        ui->baudRCmb->setItemText(nCount-1, tr("自定义"));
    }
}

// 刷新串口
void SerialPort::on_ReflushSerPortBtn_clicked()
{
    ui->PortNumCmb->clear();
    SetPortNumCmb();
}

// 复位计数
void SerialPort::slot_ResetNumBtn_clicked()
{
    m_nSendNum = 0;
    m_nRecvNum = 0;
    SetSendNum();
    SetRecvNum();
}

// 清空接受区
void SerialPort::on_ClearRecvBtn_clicked()
{
    ui->RecvDataEdt->setText(tr(""));
}

/***************************************************************************/
/*                             串口通信                                     */
/***************************************************************************/
// 打开/关闭串口
void SerialPort::on_OnOffBtn_clicked()
{
    if (m_serial->isOpen()) // 已经处于打开状态,则关闭串口
    {
        m_serial->close();
        ui->OnOffBtn->setText(tr("打开"));
        ui->OnOffBtn->setIcon(QIcon(":/pic/res/OFF.png"));
        m_bOpen = false;
        SetSerState();
    }
    else // 串口处于关闭状态,打开串口
    {
        if ( !SetSerialPortParam(m_serial) )
        {
            QMessageBox::critical(this, tr("Error"), tr("串口错误!"), QMessageBox::Ok);
            return;
        }

        // 打开串口
        if ( !m_serial->open(QIODevice::ReadWrite) ) // 打开失败
        {
            QMessageBox::critical(this, tr("Error"), tr("串口不存在或者被其它程序占用!"), QMessageBox::Ok);
            // QString strRecv = ui->RecvDataEdt->toPlainText();
            // strRecv += tr("\n【Error】Can't Open COM Port!");
            ui->RecvDataEdt->append(tr("\n【Error】Can't Open COM Port!"));
            return;
        }

        // 设置串口缓冲区大小
        m_serial->setReadBufferSize(m_nReadBuffSize);

        ui->OnOffBtn->setText(tr("断开"));
        ui->OnOffBtn->setIcon(QIcon(":/pic/res/ON.png"));
        m_bOpen = true;
        SetSerState();
    }
}

// 设置串口参数,失败返回false,成功返回true
bool SerialPort::SetSerialPortParam(QSerialPort *serial)
{
    // 设置串口号
    QString strPortNum = ui->PortNumCmb->currentText();
    if (strPortNum == tr(""))
        return false;
    serial->setPortName(strPortNum);

    // 设置波特率
    qint32 nBaudRate = ui->baudRCmb->currentText().toInt();
    serial->setBaudRate(nBaudRate);

    // 设置奇偶校验
    int nParityType = ui->DPaityCmb->currentIndex();
    switch (nParityType)
    {
    case 0:
        serial->setParity(QSerialPort::NoParity);
        break;
    case 1:
        serial->setParity(QSerialPort::OddParity);
        break;
    case 2:
        serial->setParity(QSerialPort::EvenParity);
        break;
    case 3:
        serial->setParity(QSerialPort::MarkParity);
        break;
    case 4:
        serial->setParity(QSerialPort::SpaceParity);
        break;
    default:
        serial->setParity(QSerialPort::UnknownParity);
        break;
    }

    // 设置数据位
    int nDataBits = ui->DataBCmb->currentIndex();
    switch (nDataBits)
    {
    case 0:
        serial->setDataBits(QSerialPort::Data5);
        break;
    case 1:
        serial->setDataBits(QSerialPort::Data6);
        break;
    case 2:
        serial->setDataBits(QSerialPort::Data7);
        break;
    case 3:
        serial->setDataBits(QSerialPort::Data8);
        break;
    default:
        serial->setDataBits(QSerialPort::UnknownDataBits);
        break;
    }

    // 设置停止位
    int nStopBits = ui->StopBCmb->currentIndex();
    switch (nStopBits)
    {
    case 0:
        serial->setStopBits(QSerialPort::OneStop);
        break;
    case 1:
        serial->setStopBits(QSerialPort::OneAndHalfStop);
        break;
    case 2:
        serial->setStopBits(QSerialPort::TwoStop);
        break;
    default:
        serial->setStopBits(QSerialPort::UnknownStopBits);
        break;
    }

    // 流控制
    int nFlowCtrl = ui->FlowCtrlCmb->currentIndex();
    switch (nFlowCtrl)
    {
    case 0:
        serial->setFlowControl(QSerialPort::NoFlowControl);
        break;
    case 1:
        serial->setFlowControl(QSerialPort::HardwareControl);
        break;
    case 2:
        serial->setFlowControl(QSerialPort::SoftwareControl);
        break;
    default:
        serial->setFlowControl(QSerialPort::UnknownFlowControl);
        break;
    }

    return true;
}

// 槽函数,接收串口数据
void SerialPort::slot_RecvPortData()
{
    QByteArray bytes = m_serial->readAll();
    if ( !bytes.isEmpty() )
    {
        QString strRecv = QString::fromLocal8Bit(bytes);
        ui->RecvDataEdt->append(strRecv);

        m_nRecvNum += bytes.count();
        SetRecvNum();
    }
    else
        ui->RecvDataEdt->setText(tr("接收数据出错!"));
}

// 发送数据,写串口
void SerialPort::on_SendBtn_clicked()
{
    // 串口未打开
    if ( !m_bOpen )
    {
        QMessageBox::warning(this, tr("Error"), tr("串口未打开,发送失败!"), QMessageBox::Ok);
        return;
    }

    QByteArray SendBytes = ui->SendDataEdt->toPlainText().toLocal8Bit();
    if ( !SendBytes.isEmpty() )
    {
        m_serial->write(SendBytes);
        m_nSendNum += SendBytes.count();
        SetSendNum();
    }
}

/***************************************************************************/
/*                                函数重写                                  */
/***************************************************************************/
// 窗口关闭事件,如果窗口关闭前串口未关闭,则先关闭串口
void SerialPort::closeEvent(QCloseEvent *event)
{
    if (m_serial->isOpen())
        m_serial->close();

    event->accept();
}

 获取可用串口时,Qt封装的QSerialPortInfo类是要比Windows API简单啊,几行代码就搞定了。代码注释应该还算详细哈,有点基础的应该都能看懂,读者若有不懂的地方,欢迎评论区留言,看到了会及时回复的。