【发布时间】:2021-12-12 17:02:55
【问题描述】:
我有一个单线程 QQuick 应用程序,它有一个主窗口和一个处理 Modbus 写/读功能的类。到目前为止一切正常,但是当我在我的 qml 窗口中放置 BusyIndicator 以显示总线繁忙时,我得到 CRC 不匹配和响应超时,例如:
- “丢弃带有错误 CRC 的响应,收到:64580,计算出的 CRC:55067”
- “读取响应错误:响应超时。(代码:0x5)” - qt.modbus:(RTU 客户端)无法将响应与打开的请求匹配,忽略
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "modbusinterface.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
ModbusInterface modbus;
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
engine.rootContext()->setContextProperty("modbus", &modbus);
return app.exec();
}
modbusinterface.h
#ifndef MODBUSINTERFACE_H
#define MODBUSINTERFACE_H
#include <QObject>
#include <QSerialPort>
#include <QModbusRtuSerialMaster>
#include <QModbusDevice>
#include <QModbusClient>
#include <QVariant>
#include <QDebug>
class ModbusInterface : public QObject
{
Q_OBJECT
Q_PROPERTY(bool busBusy READ busBusy NOTIFY busBusyChanged)
public:
explicit ModbusInterface(QObject *parent = nullptr);
bool busBusy(void) {return m_busBusy;}
Q_INVOKABLE bool read(int deviceId, int startAddress, quint16 count);
public slots:
void readReady();
signals:
void busBusyChanged();
private:
bool m_busBusy = false;
QModbusReply *m_lastRequest = nullptr;
QModbusClient *m_client = nullptr;
};
#endif // MODBUSINTERFACE_H
modbusinterface.cpp
#include "modbusinterface.h"
ModbusInterface::ModbusInterface(QObject *parent) : QObject(parent)
{
m_client = new QModbusRtuSerialMaster();
m_client->setConnectionParameter(QModbusDevice::SerialPortNameParameter, "ttyUSB0");
m_client->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, QSerialPort::Baud19200);
m_client->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, QSerialPort::Data8);
m_client->setConnectionParameter(QModbusDevice::SerialParityParameter, QSerialPort::NoParity);
m_client->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, QSerialPort::OneStop);
m_client->setTimeout(1000);
m_client->setNumberOfRetries(1);
if (!m_client->connectDevice()) {
qDebug() << "Connect failed: " << m_client->errorString();
} else {
qDebug() << "Modbus Client is Connected";
}
}
bool ModbusInterface::read(int deviceId, int startAddress, quint16 count)
{
QModbusDataUnit RxData;
if(startAddress>=40000) RxData.setRegisterType(QModbusDataUnit::HoldingRegisters);
else RxData.setRegisterType(QModbusDataUnit::InputRegisters);
RxData.setStartAddress(startAddress);
RxData.setValueCount(count);
if (!m_client) {
qDebug() << "!m_client";
return false;
}
if (m_client->state() != QModbusDevice::ConnectedState){
qDebug() << "Modbus Client is not Connected in read section";
return false;
}
if (auto *reply = m_client->sendReadRequest(RxData, deviceId))
{
if (!reply->isFinished()){
connect(reply, &QModbusReply::finished, this, &ModbusInterface::readReady);
m_lastRequest = reply;
m_busBusy = true;
emit busBusyChanged();
} else {
delete reply;
}
return true;
}
return false;
}
void ModbusInterface::readReady()
{
auto reply = qobject_cast<QModbusReply *>(sender());
if (!reply) return;
if( reply == m_lastRequest){
m_busBusy = false;
emit busBusyChanged();
}
reply->deleteLater();
if (reply->error() == QModbusDevice::NoError)
{
qDebug() << reply;
}
else if (reply->error() == QModbusDevice::ProtocolError)
{
qDebug() << QString("Read response error: %1 (Mobus exception: 0x%2)").
arg(reply->errorString()).
arg(reply->rawResult().exceptionCode(), -1, 16);
} else {
qDebug() << QString("Read response error: %1 (code: 0x%2)").
arg(reply->errorString()).
arg(reply->error(), -1, 16);
}
}
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.5
Window {
width: 640
height: 480
visible: true
title: qsTr("ModbusTrial")
Button{
id: button
anchors.centerIn: parent
text: "read Modbus"
onClicked: {
for(var i=1; i<=10; i++){
modbus.read(i, 30001, 1)
}
}
}
BusyIndicator{
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: button.bottom
visible: modbus.busBusy //false
}
}
在这个最小的工作示例中,我通过我的 modbusinterface 类的 qml/c++ 接口触发/排队 10 个读取请求。当我访问不需要知道哪个服务器地址的设备时,我需要扫描多个 ID 以获取有效响应,并希望在此期间显示一个 busyIndicator。这会导致上述超时/CRC 错误。 如果我的窗口中没有运行动画,则接收到的数据没有错误。
- 能否通过使用单独的线程运行 modbus 读/写方法来解决此问题,我将如何实现?还是我只会通过将串行函数放在单独的线程中来增加误读?
据我所知,由于我的应用程序在单线程中运行,GUI 的持续更新在某种程度上干扰了串行数据的接收。
我使用了 linux 命令行工具“stress”来查看在高 cpu 负载下我是否也会丢失数据,但事实并非如此。
有趣的是,我得到的第一个响应总是有效的。那么像我这样排队请求会不会有问题呢?在文档中它说:
注意:QModbusClient 将它收到的请求排队。并行执行的请求数取决于协议。例如,桌面平台上的 HTTP 协议为一个主机/端口组合发出 6 个并行请求。
亲切的问候
【问题讨论】:
-
没有minimal reproducible example,我们无法直接回答您的问题。但我肯定会尝试一个单独的线程,看看是否能解决它。
-
请提供足够的代码,以便其他人更好地理解或重现问题。
-
“公交车很忙”是什么意思?
-
我更新了我的代码示例。总线很忙,我的意思是我发送了多个“readRequests”,在处理它们的时间里——有时直到超时发生——我认为总线很忙
-
如果你正在读取连续寄存器,更改
count会比读取10个单个寄存器更好。
标签: c++ multithreading qt qml modbus