使用Qt自带的库开发,添加相关头文件
#include <QModbusTcpClient>
#include <QModbusReply>
#include <QSerialPort>
#include <QModbusDataUnit>
#include <QModbusRtuSerialMaster>
一、寄存器说明
Modbus寄存器的操作包括读写和只读。具体如下:
enum RegisterType {Invalid,DiscreteInputs,Coils,InputRegisters,HoldingRegisters};
分别叫做:
- 离散输入寄存器(只读,通常为开关量输入)
- 线圈寄存器(读写,一般为继电器的控制)
- 输入寄存器(只读,一般为模拟量输入)
- 保持寄存器(读写,一般状态参数控制)
二、同步读取
QT采用事件处理机制,由于Modbus读取过程通常有时延,Qt机制不适合采用while延时等待读取的方式。网上大多采用的是基于ModbusReply的Finished信号,做异步处理。导致在获取寄存器数据时候比较麻烦。因此,可以考虑使用事件循环做同步处理。
读寄存器数据的接口:
QVector<quint16>
MainWindow::readModbusTcpUnit(QModbusDataUnit::RegisterType type, int startAddr, int numbers, int serverID, bool *isOK)
- type:指明读取的类型,由RegisterType类型定义
- startAddr:起始地址
- numbers:读取的数量
- serverID:服务器的ID
- isOK:当前操作是否成功
- 返回值:QVector<quint16> 类型,读取的数据放在向量中
以下是具体实现:
QVector<quint16>
MainWindow::readModbusTcpUnit(QModbusDataUnit::RegisterType type, int startAddr, int numbers, int serverID, bool *isOK)
{QVector<quint16> results;results.clear();if (mModbusClient->state() != QModbusDevice::ConnectedState) {*isOK = false;return results;}QModbusDataUnit readUnit(type, startAddr, static_cast<quint16>(numbers));auto *reply = mModbusClient->sendReadRequest(readUnit, serverID);if (!reply->isFinished()) {QEventLoop loop;connect(reply, &QModbusReply::finished,&loop,&QEventLoop::quit);eventLoop.exec();}if (reply->error() == QModbusDevice::NoError) {const QModbusDataUnit unit = reply->result();QString strType;switch (type){case QModbusDataUnit::Coils: strType = "Coils"; break;case QModbusDataUnit::DiscreteInputs: strType = "DiscreteInputs"; break;case QModbusDataUnit::HoldingRegisters: strType = "HoldingRegisters"; break;case QModbusDataUnit::InputRegisters: strType = "InputRegisters"; break;}qDebug()<<"read "<<strType<< " startAddr = "<<startAddr<<" numbers = "<<numbers<<" values = " <<unit.values();*isOK = true;results = unit.values();} else {*isOK = false;}delete reply;return results;
}
使用方法:
/** bool isOK;* QVector<quint16> readUnit = readModbusTcpUnit(QModbusDataUnit::Coils, 0, 10, SERVER_ID, &isOK);* QVector<quint16> readUnit = readModbusTcpUnit(QModbusDataUnit::DiscreteInputs, 0, 10, SERVER_ID, &isOK);* QVector<quint16> readUnit = readModbusTcpUnit(QModbusDataUnit::HoldingRegisters, 0, 10, SERVER_ID, &isOK);* QVector<quint16> readUnit = readModbusTcpUnit(QModbusDataUnit::InputRegisters, 0, 10, SERVER_ID, &isOK);
*/
三、异步写
写寄存器只有Coils和HoldingRegisters可以操作,写操作使用异步执行。
接口如下:
bool
MainWindow::writeModbusTcpCoils(QVector<quint16> coilsValues, int startAddr, int numbers, int serverID)
- coilsValues:向量,需要写入的值;
- startAddr:起始地址
- numbers:需要写寄存器的数量
- serverID:服务器的ID
以写线圈为例,实现如下:
void
MainWindow::writeModbusTcpCoils(QVector<quint16> coilsValues, int startAddr, int numbers, int serverID)
{if (coilsValues.size() < numbers){qDebug()<<"error : coilsValue size < numbers";return;}if (mModbusClient->state() != QModbusDevice::ConnectedState) {qDebug()<<"error : disConnectedState";return;}QModbusDataUnit writeUnit(QModbusDataUnit::Coils, startAddr, static_cast<quint16>(numbers));for (int valueIdx = 0; valueIdx < writeUnit.valueCount(); ++valueIdx) {writeUnit.setValue(valueIdx, coilsValues.at(valueIdx));}auto *reply = mModbusClient->sendWriteRequest(writeUnit, serverID);if (!reply->isFinished()) {connect(reply, &QModbusReply::finished, this, [this, reply]() {if (reply->error() == QModbusDevice::ProtocolError) {qDebug() << “ProtocolError”;} else if (reply->error() != QModbusDevice::NoError) {qDebug() << “Error”;}});}reply->deleteLater();
}
使用方法:
/** QVector<quint16> values;* values.push_back(1);* values.push_back(0);* values.push_back(1);* writeModbusTcpCoils(values, 0, 3, SERVER_ID);* writeModbusTcpCoils(values, 3, 2, SERVER_ID);
*/
写保持寄存器和写线圈类似,不在赘述。