使用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);
*/
写保持寄存器和写线圈类似,不在赘述。