QT-ModbusTCP之QModbusTcpClient的使用

本文介绍了如何使用QModbusTcpClient进行Modbus TCP通信,该类在C++中实现了后台线程处理,保证了读取的同步性和断线重连机制。内容包括类的设计、连接、读写寄存器及线圈操作,并提供了程序下载链接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

使用QModbusTcpClient来做ModbusTCP来通讯,原滋原味。使用方便并且稳定,虽然网上有很多开源的,当时有时候应用起来,出了问题,真的头大。这里介绍QModbusTcpClient的使用,已经完全满足了应用程序的基本开发,并且在原有的基础上了,做了后台线程的处理,保证了读取寄存器相对同步获取,以及服务端掉线能自动重新连接等机制。


一、效果演示

请添加图片描述

二、通讯类

1.头文件

#pragma once

#include <QObject>
#include <QModbusTcpClient>
#include <QModbusReply>
#include <QThread>
#include <QHash>

class CModbusClient : public QThread
{
	Q_OBJECT

public:
	CModbusClient(QObject *parent = 0);
	~CModbusClient();

signals:
	void signalConnectDevice();
	void signalReadRegisterData(int, int, int);
	void signalWriteRegisterData(int, uint16_t, int);

private slots:
	void slotErrorOccurred(QModbusDevice::Error error);
	void slotStateChanged(QModbusDevice::State state);
	void slotConnectDevice();
	void slotReadyRead(); 
	void slotReadRegisterData(int nStartAddress, int nNum, int type = QModbusDataUnit::HoldingRegisters);
	void slotWriteRegisterData(int nStartAddress, uint16_t uValue, int type = QModbusDataUnit::HoldingRegisters);

public:
	bool connect(QString strIp, int nPort);
	bool isConnected();
	void release(); 

	bool getBit(uint16_t uAddr, uint16_t uBit);
	bool readRegister16(uint16_t uAddr, uint16_t &uValue);
	bool readRegister32(uint32_t uAddr, uint32_t &uValue);
	bool readCoil(uint16_t uAddr, uint16_t &uValue);

	bool setBit(uint16_t uAddr, uint16_t uBit, bool bState);
	bool writeRegister16(uint16_t uAddr, uint16_t uValue);
	bool writeRegister32(uint32_t uAddr, uint32_t uValue);
	bool writeCoil(uint16_t uAddr, uint16_t uValue);

protected:
	void run() override;

private:
	QModbusTcpClient *m_pClient;
	// 连接状态
	bool m_bConnected;
	
	// 程序退出,用来退出线程
	bool m_bAppClose;

	// 寄存器
	QHash<uint16_t, uint16_t> m_readValueHoldingRegistersHash;
	QHash<uint16_t, uint16_t> m_readAddrHoldingRegistersHash;

	// 线圈
	QHash<uint16_t, uint16_t> m_readValueCoilsHash;
	QHash<uint16_t, uint16_t> m_readAddrCoilsHash;

};


2.源文件

#include "CModbusClient.h"
#include <QVariant>
#include <QDebug>
#include <QTime>
#include <QVector>

#define  SERVER_ID         100

// 自定义的定时器
struct sModbusTimer
{
	QTime time;
	uint32_t interval;

	void start(uint32_t t)
	{
		interval = t;
		time.start();
	};

	bool isTimeOut()
	{
		return time.elapsed() > interval;
	};
};


CModbusClient::CModbusClient(QObject *parent)
	: QThread(parent)
	,m_bConnected(false)
	,m_bAppClose(false)
	,m_pClient(new QModbusTcpClient())
{
	// 信号槽关联
	QObject::connect(this, &CModbusClient::signalConnectDevice, this, &CModbusClient::slotConnectDevice);
	QObject::connect(this, &CModbusClient::signalReadRegisterData, this, &CModbusClient::slotReadRegisterData);
	QObject::connect(this, &CModbusClient::signalWriteRegisterData, this, &CModbusClient::slotWriteRegisterData);
}

CModbusClient::~CModbusClient()
{
// 退出程序
	m_bAppClose = true;

	QThread::msleep(100);

	if (m_bConnected)
		m_pClient->disconnect();

}

// 连接
bool CModbusClient::connect(QString strIp, int nPort)
{
	if (m_bConnected)
		return true;

	m_pClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, QVariant::fromValue(nPort));
	m_pClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter,QVariant::fromValue(strIp));
	m_pClient->setTimeout(1000);
	m_pClient->setNumberOfRetries(3);

	m_bConnected = m_pClient->connectDevice();
	if (m_bConnected)
	{
		QObject::connect(m_pClient, &QModbusTcpClient::stateChanged, this, &CModbusClient::slotStateChanged);
		QObject::connect(m_pClient, &QModbusTcpClient::errorOccurred, this, &CModbusClient::slotErrorOccurred);
	}
	else
		qDebug() << m_pClient->errorString() << "\n";

	if (!this->isRunning())
		this->start();

	return m_bConnected;
}

// 是否连接
bool CModbusClient::isConnected()
{
	return m_bConnected;
}

// 资源释放
void  CModbusClient::release()
{
	m_bAppClose = true;
	// 延时确保后台线程全部退出
	QThread::msleep(200);
	m_pClient->disconnect();
}

// 错误触发
void CModbusClient::slotErrorOccurred(QModbusDevice::Error error)
{
	qDebug() << "ModbusClient Error Num:" << error << "\n";
	qDebug() << "ModbusClient Error Info:" << m_pClient->errorString() << "\n";
}

// 连接状态变化
void CModbusClient::slotStateChanged(QModbusDevice::State state)
{
	switch (state)
	{
	case QModbusDevice::UnconnectedState:
	{
		m_bConnected = false;
	}break;
	case QModbusDevice::ConnectedState:
	{
		m_bConnected = true;
	}break;
	case QModbusDevice::ClosingState:
	{
		m_bConnected = false;
	}break;
	default:
		break;
	}

	qDebug() << "ModbusClient StateChanged:" << state << "\n";
}

// 连接设备
void CModbusClient::slotConnectDevice()
{
	if (!m_bConnected)
		m_pClient->connectDevice();
}

// 返回读取的值
void CModbusClient::slotReadyRead()
{
	auto reply = qobject_cast<QModbusReply*>(sender());
	if (reply)
	{
		if (reply->error() == QModbusDevice::NoError)
		{
			const QModbusDataUnit unit = reply->result();
			uint16_t nStartAddr = unit.startAddress();

			auto valueList = unit.values();

			// 将读取的返回值直接插入到值哈希表里面
			if (unit.registerType() == QModbusDataUnit::RegisterType::HoldingRegisters)
			{
				for (int i = 0; i < valueList.size(); i++)
				{
					m_readValueHoldingRegistersHash[nStartAddr + i] = valueList[i];
					qDebug() << QString("Read HoldingRegisters Value[%1]:%2").arg(i + nStartAddr).arg(valueList[i]) << "\n";
				}
			}
			else if (unit.registerType() == QModbusDataUnit::RegisterType::Coils)
			{
				for (int i = 0; i < valueList.size(); i++)
				{
					m_readValueHoldingRegistersHash[nStartAddr + i] = valueList[i];
					qDebug() << QString("Read Coils Value[%1]:%2").arg(i + nStartAddr).arg(valueList[i]) << "\n";
				}
			}

		}
		else
		{
			qDebug() << "ReadyRead Error:" << reply->errorString() << "\n";
		}

		reply->deleteLater();
	}

}

// 后台线程
void CModbusClient::run()
{
	sModbusTimer timeout;
	int nStep = 0;
	while (true)
	{
		QThread::msleep(30);

		// 线程退出
		if (m_bAppClose)
			return;


		switch (nStep)
		{
		case 0:
		{
			if (!m_bConnected)
			{
				timeout.start(3 * 1000);
				nStep = 1;
			}
			else
				nStep = 2;

		}break;

		case 1:
		{
			if (timeout.isTimeOut() && !m_bConnected)
			{
				// 通过发送信号,再次连接设备,不同线程需要用信号发送
				emit signalConnectDevice();
				nStep = 0;
			}
			else if (m_bConnected)
				nStep = 0;

		}break;

		case 2:
		{
			// 后台不断刷新读取
			if (true)
			{
				static int nIndex = 0;
				auto keys = m_readAddrHoldingRegistersHash.keys();

				if (nIndex < keys.size())
				{
					uint16_t uAddr = keys[nIndex];

					// 发送信号,读寄存器请求,不同线程需要用信号发送
					emit signalReadRegisterData(uAddr, m_readAddrHoldingRegistersHash[uAddr],(int)QModbusDataUnit::RegisterType::HoldingRegisters);

					nIndex++;
				}
				else
					nIndex = 0;
			}

			if (true)
			{
				static int nIndex = 0;
				auto keys = m_readAddrCoilsHash.keys();

				if (nIndex < keys.size())
				{
					uint16_t uAddr = keys[nIndex];

					// 发送信号,读寄存器请求,不同线程需要用信号发送
					emit signalReadRegisterData(uAddr, m_readAddrCoilsHash[uAddr], (int)QModbusDataUnit::RegisterType::Coils);

					nIndex++;
				}
				else
					nIndex = 0;
			}


			nStep = 0;
		}break;

		default:
			break;
		}
	}

}

// 发送寄存器读取请求
void CModbusClient::slotReadRegisterData(int nStartAddress, int nNum, int  type)
{
	QModbusDataUnit readUnit((QModbusDataUnit::RegisterType)type, nStartAddress, nNum);
	if (auto* reply = m_pClient->sendReadRequest(readUnit, SERVER_ID))
	{
		if (!reply->isFinished())
			QObject::connect(reply, &QModbusReply::finished, this, &CModbusClient::slotReadyRead);
		else
			delete reply;
	}
	else
		qDebug() << m_pClient->errorString();
}

// 发送寄存器写入请求
void CModbusClient::slotWriteRegisterData(int nStartAddress, uint16_t uValue, int  type)
{
	if (!m_bConnected)
		return;

	const int nLength = 1;
	QModbusDataUnit writeUnit((QModbusDataUnit::RegisterType)type, nStartAddress, nLength);
	writeUnit.setValue(0, uValue);

	auto *reply = m_pClient->sendWriteRequest(writeUnit, SERVER_ID);
	if (reply != nullptr)
	{
		if (!reply->isFinished())
		{
			QObject::connect(reply, &QModbusReply::finished, this, [this, reply]()
			{
				if (reply->error() != QModbusDevice::NoError)
					qDebug() << "write response error:" << reply->errorString();
			});
		}

		reply->deleteLater();
	
	}
	else
		qDebug() << "write request error:" << m_pClient->errorString();

}

// 写入16位值
bool CModbusClient::readRegister16(uint16_t uAddr, uint16_t &uValue)
{
	if (!m_bConnected)
		return false;
	
	// 一次设置读取长度为100
	const int nReadLength = 100;

	// 如果值读取哈希链表没有找到,需要插入到地址哈希链表,让它在后台读取
	auto itFind = m_readValueHoldingRegistersHash.find(uAddr);
	if (itFind == m_readValueHoldingRegistersHash.end())
	{
		m_readAddrHoldingRegistersHash[uAddr] = nReadLength;
		return false;
	}
	else
	{
		// 找到值,直接返回
		uValue = m_readValueHoldingRegistersHash[uAddr];
	}

	return true;
}

// 读取32位值
bool CModbusClient::readRegister32(uint32_t uAddr, uint32_t &uValue)
{
	if (!m_bConnected)
		return false;

	bool bRet = false;
	uint16_t uData16[2] = { 0 };
	bRet = readRegister16(uAddr, uData16[0]);
	bRet = readRegister16(uAddr + 1, uData16[1]);

	uValue = uData16[0]  | (uData16[1] << 16);

	return bRet;
}

// 获取位值
bool CModbusClient::getBit(uint16_t uAddr, uint16_t uBit)
{
	if (!m_bConnected)
		return false;

	uint16_t uValue = 0;
	readRegister16(uAddr, uValue);

	return uValue & (1 << (uBit % 16));
}

// 读取线圈值
bool CModbusClient::readCoil(uint16_t uAddr, uint16_t &uValue)
{
	const int nReadLength = 100;
	auto itFind = m_readValueCoilsHash.find(uAddr);
	if (itFind == m_readValueCoilsHash.end())
	{
		m_readAddrCoilsHash[uAddr] = nReadLength;
		return false;
	}
	else
		uValue = m_readValueCoilsHash[uAddr];

	return true;
}

// 设置位值
bool CModbusClient::setBit(uint16_t uAddr, uint16_t uBit, bool bState)
{
	bool bRet = false;

	uint16_t uValue = 0;
	bRet = readRegister16(uAddr, uValue);

	if (bRet)
	{
		if (bState)
			uValue |= (1 << (uBit % 16));	 // 把某位置1
		else
			uValue &= ~(1 << (uBit % 16));	 // 把某位置0

		writeRegister16(uAddr, uValue);
	}

	return bRet;
}

// 写入16位值
bool CModbusClient::writeRegister16(uint16_t uAddr, uint16_t uValue)
{
	bool bRet = true;
	signalWriteRegisterData(uAddr, uValue, (int)QModbusDataUnit::RegisterType::HoldingRegisters);
	return bRet;
}

// 写入32位值
bool CModbusClient::writeRegister32(uint32_t uAddr, uint32_t uValue)
{
	bool bRet = false;

	uint16_t uData16[2] = {0};
	uData16[0] = uValue & 0Xffff;
	uData16[1] = (uValue >> 16) & 0Xffff;

	bRet = writeRegister16(uAddr, uData16[0]);
	bRet = writeRegister16(uAddr+1, uData16[1]);

	return bRet;
}

// 写入线圈值
bool CModbusClient::writeCoil(uint16_t uAddr, uint16_t uValue)
{
	bool bRet = true;
	signalWriteRegisterData(uAddr, uValue, (int)QModbusDataUnit::RegisterType::Coils);
	return bRet;
}


程序下载

https://download.youkuaiyun.com/download/u013083044/86784777

<think>好的,用户想了解Qt5的Modbus TCP,我需要先回忆一下相关知识,确保信息准确。Qt5中的Modbus模块主要用于工业通信,支持主从设备交互。Modbus TCP是基于TCP/IP的协议,常用于工业自动化。 首先,要确认Qt5的Modbus库属于Qt SerialBus模块,用户需要在项目配置中添加相应的模块。可能用户会遇到如何安装或启用的问题,需要提醒他们检查是否安装了Qt SerialBus组件。 然后,Modbus TCP的主站(客户端)和从站(服务器)的实现步骤。主站需要创建QModbusClient实例,设置连接参数,比如IP和端口,然后发送请求。从站则需要处理请求,可能需要继承QModbusServer并重写处理函数。 需要提到常用的类,比如QModbusClient、QModbusServer、QModbusDataUnit等,以及它们的作用。此外,数据读写流程和错误处理也是关键点,用户可能需要知道如何处理超时或连接失败的情况。 还要注意Qt5 Modbus的一些限制,比如对某些Modbus功能的支持不完全,或者性能方面的考量。最后,示例代码片段会帮助用户快速上手,但需要确保语法正确,并说明需要在.pro文件中添加serialbus模块。 总结时,应用场景和注意事项能帮助用户判断是否适合他们的项目。需要保持回答结构清晰,分步骤解释,避免使用Markdown,用自然的中文口语化表达。</think>以下是对Qt5 Modbus TCP的逐步介绍: $\textcolor{green}{\textbf{1. 基本概念}}$ $$ \text{Modbus TCP} = \text{Modbus RTU协议} + \text{TCP/IP传输层} $$ - 基于TCP/IP的工业通信协议(默认端口502) - 采用客户端/服务器架构(主站Master/从站Slave) $\textcolor{green}{\textbf{2. Qt5支持模块}}$ - 位于Qt SerialBus模块中(Qt 5.8+) - 需在.pro文件添加: ```qmake QT += serialbus ``` $\textcolor{green}{\textbf{3. 核心类说明}}$ ```cpp QModbusTcpClient // TCP客户端(主站) QModbusTcpServer // TCP服务器(从站) QModbusDataUnit // 数据单元(保持寄存器/输入寄存器等) QModbusReply // 异步操作响应对象 ``` $\textcolor{green}{\textbf{4. 典型工作流程(主站)}}$ ① 创建客户端实例: ```cpp QModbusTcpClient *modbusClient = new QModbusTcpClient; modbusClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, 502); modbusClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, "192.168.1.100"); ``` ② 建立连接: ```cpp if (!modbusClient->connectDevice()) { qDebug() << "连接失败:" << modbusClient->errorString(); } ``` ③ 发送读请求: ```cpp QModbusDataUnit request(QModbusDataUnit::HoldingRegisters, 100, 10); // 读取保持寄存器100-109 if (auto *reply = modbusClient->sendReadRequest(request, 1)) { // 1是从站地址 connect(reply, &QModbusReply::finished, [=]() { if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit result = reply->result(); // 处理数据... } reply->deleteLater(); }); } ``` $\textcolor{green}{\textbf{5. 数据映射(从站实现)}}$ 需继承QModbusServer并重写: ```cpp bool readData(QModbusDataUnit *unit) override; bool writeData(const QModbusDataUnit &unit) override; ``` $\textcolor{green}{\textbf{6. 错误处理}}$ ```cpp connect(modbusClient, &QModbusClient::errorOccurred, [](QModbusDevice::Error error) { qDebug() << "错误类型:" << error; }); ``` $\textcolor{green}{\textbf{7. 注意事项}}$ - 超时设置:默认3000ms,可通过setTimeout()调整 - 字节序处理:需自行处理数据的大小端转换 - 线程安全:建议在主线程创建QModbusClient实例 - 性能限制:单客户端建议请求频率不超过100次/秒 $\textcolor{green}{\textbf{8. 应用场景}}$ - 工业PLC控制 - 传感器数据采集 - SCADA系统 - 能源管理系统 $\textcolor{red}{\textbf{注意:}}$ Qt Modbus模块目前仅实现基础功能,复杂场景可能需要扩展: - 不支持Modbus TCP安全协议(MBAP) - 有限的数据分帧处理能力 - 调试建议使用Modbus Poll/Simulator等工具配合测试
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的大海贼

联系博主,为您提供有价值的资源

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值