QT (1) 編寫Qt類庫來設計上下位機串口通信系統的上位機

前言

前一篇文章講到了利用C#窗體應用程序來設計上位機的界面軟件,但是這樣的界面軟件還不夠通用,只能定製化運行,圖形界面的代碼和上位機初步處理數據的代碼耦合性較高。所以這篇文章講了利用QT Creator將上位機部分的代碼編寫成QT類庫,從而和圖形界面解耦合,如果使用不同的軟件開發圖形界面,都可以使用到這個類庫。

創建一個QT程序

  • 首先我們需要創建一個QT程序來當做這個類庫的主窗體,打開Qt Creator後,選擇工具欄"文件"->“新建文件或項目”->“項目”->“Application”->“Qt Widgets Application”;
    在這裏插入圖片描述
  • 單擊"Choose"後,重新命名,選擇文件存放路徑,點擊"下一步";
    在這裏插入圖片描述
  • 根據自己需要選擇"Kits",點擊"下一步";然後命名各個文件名和基類名,點擊"下一步";最後點擊"完成"就可以生成一個Qt主程序了。
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述

創建QT類庫

  • 首先右鍵項目欄中的QT項目名,選擇"添加新文件",接着選擇"C++"->“C++ Class”,點擊"Choose";
    在這裏插入圖片描述
  • 給類名取名,選擇要包含的基類,點擊"下一步";
    在這裏插入圖片描述
  • 可以看到該C++類就被添加到主項目裏了,最後點擊"完成"。頭文件就被放到主項目的Headers文件夾下,.cpp文件就放到Sources文件夾下了。
    在這裏插入圖片描述

編寫QT類庫

  • 我們在剛創建的頭文件的類裏創建各個變量和函數,以光開關模塊爲例,代碼如下所示。
#ifndef BOTDR_H
#define BOTDR_H

#include <QMainWindow>
#include <QObject>                        //Qt鏈接函數所用
#include <QtSerialPort/QSerialPort>       //Qt串口類
#include <QtSerialPort/QSerialPortInfo>
#include <QDebug>                        //qDebug所用
#include <QtDebug>
#include <QThread>                      //Qt多進程所用

class BOTDR : public QThread
{
    Q_OBJECT

public:
    explicit BOTDR();
    ~BOTDR();
    bool ComOpen(int BaudRate);                    //Qt串口打開函數         
    bool ComClose();	                           //Qt串口關閉函數  
    bool SendData(int *frame, int length);         //Qt串口發送數據函數 
    bool OpticalSwitchSetting(int ChannelNumber);  //光開關下發命令函數 
	int CalBIP8(int *data);                        //異或校驗碼函數      
	 
    struct QTARGC        //用來顯示下位機參數的結構體
    {
        int Module;       //各個模塊的標識碼
        int Optical_Rx;   //存放下位機返回的通道號參數
        bool IsSuccess;
        bool IsUpdate;
        int Error;
        int Warning;
        int ErrorExtense;
    };
    QTARGC QtArgc;


signals:
    void readyRead();     //信號函數
    void DataRead();

public slots:
    void ReceivedData();  //Qt串口接收數據函數,也是事件函數,由信號函數觸發

private:
    QSerialPort* MySerial;       //實例化一個私有串口
    QByteArray txdata;           //串口接收的QByteArray

    const int OpticalSwitch = 0x03;

protected:
    void run();
};


#endif // BOTDR_H

  • 接着我們在創建的.cpp文件裏編寫類先關的函數。
#include "botdr.h"

BOTDR::BOTDR()
{
    //構造函數初始化串口和參數
    MySerial = new QSerialPort();  
    QtArgc.Optical_Rx = 0;
}

BOTDR::~BOTDR()
{
    //析構函數關閉串口
    BOTDR::ComClose();
}


bool BOTDR::ComOpen(int BaudRate)
{
    //查詢當前PC內有多少可用的串口,全部放入列表Ports
    QList<QSerialPortInfo> Ports = QSerialPortInfo::availablePorts();
    int a = Ports.length();
    if (a>1)
    {
        //如果有新插入的設備則MySerial選擇對應的端口
        MySerial->setPortName(Ports[a-1].portName()); 
    }
    else
    {
        MySerial->setPortName(Ports[0].portName()) ;    //默認條件下則選擇端口1
    }
    MySerial->setBaudRate(BaudRate,QSerialPort::AllDirections);//設置波特率和讀寫方向
    MySerial->setDataBits(QSerialPort::Data8);      //數據位爲8位
    MySerial->setFlowControl(QSerialPort::NoFlowControl);//無流控制
    MySerial->setParity(QSerialPort::NoParity); //無校驗位
    MySerial->setStopBits(QSerialPort::OneStop); //一位停止位
    //按照以上設置打開MySerial串口
    if (MySerial->open(QIODevice::ReadWrite))
    {
        qDebug() <<MySerial->portName()<<"串口打開成功!";
        return true;
    }
    else
    {
        qDebug() <<"串口打開失敗!";
        return false;
    }
}


bool BOTDR::ComClose()
{
    if(MySerial->isOpen()==true)
    {
        //清除串口數據並關閉
        MySerial->clear();
        MySerial->close();
    }
    return true;
}


bool BOTDR::SendData(int* frame, int length)
{
    //將原本的int數組內的數據轉換到QByteArray內
    for(int i=0;i<length;i++)
    {
        txdata[i] = frame[i];
    }
    //串口發送數據
    if(MySerial->write(txdata)== length)
    {
        qDebug()<<"命令發送成功!";
        //串口發出數據後,串口如果準備完畢,就鏈接到數據接收函數,時刻監聽串口
        QObject::connect(MySerial,SIGNAL(readyRead()), this,SLOT(ReceivedData()));
        return true;
    }
    else
        return false;
}


void BOTDR::ReceivedData()
{
    QtArgc.IsSuccess =false;
    QtArgc.IsUpdate = false;
    QtArgc.Module = 0;
    QByteArray data = MySerial->readAll(); //將串口緩衝區內所有數據放入QByteArray類型的data內
    int length = data.length();
    if(length>0)   //當數據不爲空時
    {
        //數據轉化爲無符號十六進制(因爲QByteArray會把每個字節數據當成有符號的byte型,但我這裏接收到數據是無符號的byte,所以要做個轉換,不然接收到的數據就是錯誤的;所以請根據自行需求加不加這段代碼)
        int buff[30] ={0};
        for(int i=0;i<length;i++)
        {
            if(data[i]&128)
            {
                buff[i]= 128+(data[i]&127);
            }
            else
            {
                buff[i] = data[i];
            }
        }
		
		//根據接收到的數據進行初步的處理
        if((buff[0]-65+'A') =='D')
        {
            //標識本次數據更新屬於哪個模塊
            QtArgc.Module = buff[1];
            if(buff[1]==OpticalSwitch)
            {
                QtArgc.Optical_Rx = buff[2];
                QtArgc.IsUpdate = true;
            }
        }
        else if((buff[0]-65+'A')=='S')
        {
            QtArgc.IsSuccess = true;
        }
        else if((buff[0]-65+'A')=='W')
        {
            QtArgc.IsSuccess = false;
            QtArgc.Warning = buff[1];
        }
        else if((buff[0]-65+'A')=='E')
        {
            QtArgc.IsSuccess = false;
            QtArgc.Error = buff[1];
            if(length > 2)
            {
                QtArgc.ErrorExtense = buff[2];
            }
        }
    }
//    else
//        qDebug()<<"數據接收失敗!";
}


//光開關命令下發函數
bool BOTDR::OpticalSwitchSetting(int ChannelNumber)
{
    if(MySerial->isOpen())  //檢查串口是否打開
    {
        if(ChannelNumber>=0 && ChannelNumber<5)
        {
            int frame[8] = { 0xfb, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0xfe };
            frame[4] = ChannelNumber;
            frame[1] = BOTDR::CalBIP8(frame);
            //發送數據命令幀
            if(BOTDR::SendData(frame,8)==true)
            {
                return true;
            }
            else
                return false;
        }
        else
            return false;
    }
    else
        return false;
}


//異或校驗函數
int BOTDR::CalBIP8(int *data)
{
    int bip16 = char(data[0] ^ (data[1] & 0x00) ^ data[2] ^ data[3] ^ data[4] ^ data[5] ^ data[6] ^ data[7]);
    int bip8 = char(((bip16 & 0xf0) >> 4) ^ (bip16 & 0x0f));
    return bip8;
}


void BOTDR::run()
{
    //只有當更新和成功標誌位置true後才發射信號函數
    while(QtArgc.IsUpdate!=true && QtArgc.IsSuccess !=true)
    {}
    emit DataRead();   //發射信號函數,進而觸發讀取通道號的函數
}

調用QT類庫

  • 類庫裏的函數和變量設置好了之後,我們就可以使用類庫了,這裏我們不在UI界面做設計,只在mainwindow.cpp裏簡單調用類庫。
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

	//調用類庫
    com = new BOTDR();  //示例化類
    connect(com, SIGNAL(DataRead()),SLOT(DisplayArgc()));
    com->start();
    //串口打開,光開關設置參數
    if(com->ComOpen(9600) == true)
    {
        com->OpticalSwitchSetting(3);
    }
}


void MainWindow::DisplayArgc()
{
    if(com->QtArgc.IsSuccess == true)
    {
        qDebug()<<"本次操作成功";
    }
    else if(com->QtArgc.IsUpdate == true)
    {
        switch(com->QtArgc.Module)
        {
            case 0x03:
            {
                //OpticalSwitch
                qDebug()<<"當前通道號爲"<<com->QtArgc.Optical_Rx;
                break;
            }
            default:
                break;
        }
    }
    else
    {
        qDebug()<<"差錯代碼:"<<com->QtArgc.Error;
        qDebug()<<"差錯代碼擴展:"<<com->QtArgc.ErrorExtense;
        qDebug()<<"告警代碼:"<<com->QtArgc.Warning;
    }
}
  • 串口打開後,光開關設置參數,調用類庫裏相應的函數,就數據命令幀下發到下位機,下位機接收後由外部設備光開關操作後,返回當前通道號給上位機,上位機接收函數ReceivedData()接收後,將其參數存放對應位置,並更新標誌位,等待界面軟件讀取。

優化

爲了進一步優化讀取通道號的邏輯,可進行Qt線程的鏈接函數。

  • 在頭文件的類裏新增信號函數void DataRead();調用類庫時新增語connect(com,SIGNAL(DataRead()),SLOT(DisplayArgc()));,即通過信號函數DataRead()來觸發讀取通道號的顯示函數DisplayArgc(),該函數定義在mainwindow類裏,後續可由界面軟件函數替代。
  • 那麼信號函數DataRead()什麼時候開始運行,可以由一個Qt線程來控制。在頭文件里加一個受保護的線程函數,protected: void run();,並由調用類庫時的語句com->start();來控制該線程運行;接着在.cpp文件裏編寫該函數,只有當串口數據接收函數ReceivedData()內的標誌位更新了才發射信號函數(這裏有需求的可在run()裏增加等待時間跳出)
  • 讀取通道號的函數DisplayArgc()被觸發後,就會根據標誌位來顯示當前通道號

通道號設置爲3,點擊Qt左下角的"運行",最後運行結果如下:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章