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原生的好用