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左下角的"运行",最后运行结果如下:
在这里插入图片描述

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