Qt 使用modbus协议

发布于:2024-12-18 ⋅ 阅读:(16) ⋅ 点赞:(0)

Qt 框架下 使用modbus协议

一,使用Qt原生的 QModbusClient ,比如QModbusTcpClient

1,因为modbus的读写 需要在同一个线程中,所以需要在主线程中利用moveToThread的方式,将业务逻辑封装到 子线程中。

2,modbus封装

#pragma once
#include <QObject>
#include <QModbusTcpClient>
#include <QCoreApplication>
#include <qdebug.h>

namespace Device {
    class Modbus:public QObject{
        Q_OBJECT
    public:
        Modbus(QObject*parent): QObject(parent){

        }

    protected:
        bool modbusRead(QModbusTcpClient * modbusMaster,int serverAddress, QModbusDataUnit::RegisterType type, int newStartAddress, quint16 newValueCount, QList<quint16> &array, bool warning){
            QModbusDataUnit readUnit(type, newStartAddress, newValueCount);
            QModbusReply *reply = modbusMaster->sendReadRequest(readUnit, serverAddress);

            if(reply == nullptr)
            {
                qDebug()<<QStringLiteral("modbusRead 无效请求");
                return false;
            }

            while (!reply->isFinished())
            {
                QCoreApplication::processEvents();
            }
            if (reply->error() == QModbusDevice::NoError)
            {
                QModbusDataUnit resultUnit = reply->result();
                for (int i = 0; i < static_cast<int>(resultUnit.valueCount()); ++i) {
                    array.append(resultUnit.value(i));
                }
                return true;
            }
            else
            {
                qDebug()<<QStringLiteral("modbusRead reply %1").arg(reply->errorString());
                return false;
            }
        }
        bool modbusWrite(QModbusTcpClient * modbusMaster,int serverAddress, QModbusDataUnit::RegisterType type, int newStartAddress, quint16 newValueCount, QList<quint16> array, bool warning){
            QModbusDataUnit writeUnit(type, newStartAddress, newValueCount);
            for(int i = 0; i < newValueCount; i++)
                writeUnit.setValue(i, array.value(i));

            QModbusReply *reply = modbusMaster->sendWriteRequest(writeUnit, serverAddress);
            if(reply == nullptr)
            {
                qDebug()<<QStringLiteral("modbusWrite 无效请求");
                return false;
            }

            while (!reply->isFinished())
            {
                QCoreApplication::processEvents();
            }

            if (reply->error() == QModbusDevice::NoError)
            {
                return true;
            }
            else
            {
                qDebug()<<QStringLiteral("modbusWrite reply %1").arg(reply->errorString());
                return false;
            }
        }
    };
}

3,继承上述类,封装业务逻辑,以压力传感器为例。将功能放到槽函数中。

class ModbusWorkerPressure:public Modbus{
Q_OBJECT
public:
    explicit ModbusWorkerPressure(Modbus*parent= nullptr);
    ~ModbusWorkerPressure() override;

public slots:
    void open();
    void close();
    void zero();

signals:
    void siStatus(int type,int result);

private:
    QModbusTcpClient *modbusClient{nullptr};
    bool bOpened{false};
    bool bExit{false};
};
ModbusWorkerPressure::ModbusWorkerPressure(Modbus *parent): Modbus(parent)  {

}

ModbusWorkerPressure::~ModbusWorkerPressure() {

}

void ModbusWorkerPressure::close() {
    bExit = true;
    DELAY(500);
    if (bOpened) {
        modbusClient->disconnectDevice();
    }
    if (modbusClient != nullptr) {
        modbusClient->deleteLater();
    }
}

void ModbusWorkerPressure::open() {
    modbusClient = new QModbusTcpClient(this);
    modbusClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, "10.10.10.2");
    modbusClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, 502);
    modbusClient->setTimeout(500);
    if (modbusClient->connectDevice()) {
        DELAY(500);
        while (!bExit) {
            QList<quint16> list;
            int32_t pressure=0;
            if(modbusRead(modbusClient,1,QModbusDataUnit::HoldingRegisters,0x0000,0x0002,list,false)){
                int result = 0;
                result =static_cast<int32_t>(list[1])<<16|list[0];
                pressure = result;
                //单位g
                DataManager::Instance().setCurPressure(pressure);

                //能读出数据 认为连接成功
                static bool execute = false;
                if (!execute) {
                    bOpened = true;
                    emit siStatus(0, 0);
                    execute = true;
                }
            }
            DELAY(5);
        }
    } else {
        emit siStatus(0, -1);
        bOpened = false;
    }
}

void ModbusWorkerPressure::zero() {
    QList<quint16> list{0x0001};
    if (!modbusWrite(modbusClient,1, QModbusDataUnit::HoldingRegisters, 0x0002, 0x0001, list, false)) {
        LOGE(u8"压力传感器  清零失败!");
    }
}

4, 主线程 使用moveToThread 将上述业务线程进行封装,然后主线程中 用信号,进行触发

bool PressureSensor::open()
    {
        workerThread = new QThread();
        modbusWorker = new ModbusWorkerPressure();
        modbusWorker->moveToThread(workerThread);
        QObject::connect(workerThread, &QThread::started, modbusWorker, &ModbusWorkerPressure::open);
        QObject::connect(this, &PressureSensor::siClose, modbusWorker, &ModbusWorkerPressure::close);
        QObject::connect(this, &PressureSensor::siZero, modbusWorker, &ModbusWorkerPressure::zero);
        QObject::connect(modbusWorker, &ModbusWorkerPressure::siStatus, [&](int type, int result) {
            if (type == 0) {
                if (result == 0) {
                    bOpened = true;
                } else {
                    bOpened = false;
                }
            }
        });
        workerThread->start();
        return true;
    }

    bool PressureSensor::zero() {
        if(!bOpened)
            return false;

        emit siZero();
        return true;
    }

5,此种方式 优点就是Qt原生框架,但是缺点是 这种方式是异步的方式,想要做到同步调用,比如轴系运动中,需要自己去同步,试过 QEventLoop的方式,但是不行,会丢失事件。

二,第三方库 libmodbus

1,编译及下载

Libmodbus在win11下的编译与VS2019下的运行_libmodbus vs2019-CSDN博客

2,写bool

 auto home  = [&](bool flag){
            const int read_regAddress = 55;
            const int numBits = 1;
            uint16_t coilStatus[numBits];

            int rc;
            {
                QMutexLocker locker(&mutex);
                rc = modbus_read_registers(modbusContext,read_regAddress,numBits,coilStatus);
            }
            if (rc == -1) {
                LOGE(QString("goHome Failed to read Modbus coils %1").arg(modbus_strerror(errno)).toUtf8());
            }
            else {
                uint16_t value = coilStatus[0];
                if(flag){
                    value|=0x80;
                }else{
                    value&=0xFF7F;
                }

                {
                    QMutexLocker locker(&mutex);
                    rc = modbus_write_register(modbusContext, read_regAddress, value);
                }
                if (rc == -1) {
                    LOGE(QString("goHome Failed to write Modbus coil %1").arg(modbus_strerror(errno)).toUtf8());
                }
            }
        };
        home(false);
        DELAY(100);
        home(true);

3,读取double

            while (!bExit){
                const int read_regAddress = 1104;
                const int numBits = 4;
                uint16_t coilStatus[numBits];
                int rc;
                {
                    QMutexLocker locker(&mutex);
                    rc = modbus_read_registers(modbusContext,read_regAddress,numBits,coilStatus);
                }
                if (rc == -1) {
                    LOGE(QString("getPos Failed to read Modbus coils %1").arg(modbus_strerror(errno)).toUtf8());
                }
                else {
                    ::uint64_t combined  =  static_cast<uint64_t>(coilStatus[3]) << 48 |static_cast<uint64_t>(coilStatus[2]) << 32|static_cast<uint64_t>(coilStatus[1]) << 16| coilStatus[0];
                    double realValue;
                    std::memcpy(&realValue, &combined, sizeof(realValue));
                    DataManager::Instance().setCurZ(realValue*1000);
                }
                DELAY(5);
            }

4,写double

//位置
        const int write_regAddress_pos = 1150;
        const int numReg = 4;
        ::uint64_t  rawPos = *reinterpret_cast<::uint64_t*>(&ptpPos);
        ::uint16_t listPos[4]{static_cast<uint16_t>(rawPos&0xFFFF),static_cast<uint16_t>((rawPos>>16)&0xFFFF),static_cast<uint16_t>((rawPos>>32)&0xFFFF),static_cast<uint16_t>((rawPos>>48)&0xFFFF)};
        int rc;
        {
            QMutexLocker locker(&mutex);
            rc = modbus_write_registers(modbusContext, write_regAddress_pos,numReg, listPos);
        }

        if (rc == -1) {
            LOGE(QString("setPos Failed to write Modbus coil %1").arg(modbus_strerror(errno)).toUtf8());
        }

5,注意

modbus_t 并不是线程安全的,因此在使用的地方 需要加锁。亲测这个 比Qt原生的好用