GDT + HMI Integration: Real-Time Weight Display with Modbus Communication

Amazeng Technical Team
9 min read
GDTHMIModbusLoadcellQtIndustrial WeighingQML

Introduction

In industrial weighing systems, real-time weight display and recording are critical requirements. Our GDT Digital Transmitter communicates with HMI Industrial Display Solutions via Modbus TCP or Modbus RTU protocol to meet these needs.

In this article, we examine in detail how to read, display, and record weight data from GDT on an HMI panel.

System Architecture

┌──────────────────────────────────────────────────────┐
│         Loadcell Platform (4-channel)                │
│                                                      │
│  ┌────────┐  ┌────────┐  ┌────────┐  ┌────────┐   │
│  │LC #1   │  │LC #2   │  │LC #3   │  │LC #4   │   │
│  │2mV/V   │  │2mV/V   │  │2mV/V   │  │2mV/V   │   │
│  └───┬────┘  └───┬────┘  └───┬────┘  └───┬────┘   │
│      │           │           │           │        │
└──────┼───────────┼───────────┼───────────┼────────┘
       │           │           │           │
       └───────────┴───────────┴───────────┘
                     │
              ┌──────▼──────┐
              │  GDT-410M   │  ← 24-bit ADC, Modbus TCP
              │  Transmitter│
              └──────┬──────┘
                     │ Ethernet (Modbus TCP)
                     │ Port 502
              ┌──────▼──────┐
              │  HMI Panel  │  ← 10.1" Touchscreen
              │  Qt/QML UI  │     Embedded Linux
              └─────────────┘

GDT Modbus Register Map

RegisterAddressTypeUnitDescription
Gross Weight40001-40002Float32kgTotal weight (4 channels)
Net Weight40003-40004Float32kgGross - Tare
Tare Value40005-40006Float32kgContainer weight
Channel 140011-40012Float32mV/VLoadcell 1 raw data
Channel 240013-40014Float32mV/VLoadcell 2 raw data
Channel 340015-40016Float32mV/VLoadcell 3 raw data
Channel 440017-40018Float32mV/VLoadcell 4 raw data
Status40101Uint16-Device status
Zero Command40201Uint16-Write 1 = Zero
Tare Command40202Uint16-Write 1 = Tare

HMI Application: Qt + QML Interface

C++ Modbus Backend

ModbusReader.h

#ifndef MODBUSREADER_H
#define MODBUSREADER_H

#include <QObject>
#include <QModbusTcpClient>
#include <QTimer>

class ModbusReader : public QObject
{
    Q_OBJECT
    Q_PROPERTY(float grossWeight READ grossWeight NOTIFY weightChanged)
    Q_PROPERTY(float netWeight READ netWeight NOTIFY weightChanged)
    Q_PROPERTY(float tare READ tare NOTIFY weightChanged)
    Q_PROPERTY(bool connected READ connected NOTIFY connectionChanged)

public:
    explicit ModbusReader(QObject *parent = nullptr);
    ~ModbusReader();

    float grossWeight() const { return m_grossWeight; }
    float netWeight() const { return m_netWeight; }
    float tare() const { return m_tare; }
    bool connected() const { return m_connected; }

    Q_INVOKABLE void connectToDevice(const QString &ip, int port = 502);
    Q_INVOKABLE void zero();
    Q_INVOKABLE void setTare();

signals:
    void weightChanged();
    void connectionChanged();
    void errorOccurred(const QString &error);

private slots:
    void onStateChanged(QModbusDevice::State state);
    void onReadReady();
    void readRegisters();

private:
    QModbusTcpClient *m_modbusClient;
    QTimer *m_readTimer;

    float m_grossWeight = 0.0f;
    float m_netWeight = 0.0f;
    float m_tare = 0.0f;
    bool m_connected = false;

    float registersToFloat(quint16 reg1, quint16 reg2);
};

#endif

ModbusReader.cpp

#include "ModbusReader.h"
#include <QModbusDataUnit>
#include <QDebug>

ModbusReader::ModbusReader(QObject *parent)
    : QObject(parent)
{
    m_modbusClient = new QModbusTcpClient(this);

    connect(m_modbusClient, &QModbusClient::stateChanged,
            this, &ModbusReader::onStateChanged);

    // Read every 200ms
    m_readTimer = new QTimer(this);
    connect(m_readTimer, &QTimer::timeout, this, &ModbusReader::readRegisters);
}

void ModbusReader::connectToDevice(const QString &ip, int port)
{
    m_modbusClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, ip);
    m_modbusClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, port);
    m_modbusClient->setTimeout(1000);
    m_modbusClient->setNumberOfRetries(3);

    m_modbusClient->connectDevice();
}

void ModbusReader::onStateChanged(QModbusDevice::State state)
{
    m_connected = (state == QModbusDevice::ConnectedState);
    emit connectionChanged();

    if (m_connected) {
        qDebug() << "✓ Connected to GDT";
        m_readTimer->start(200);  // 5 Hz update rate
    } else {
        m_readTimer->stop();
    }
}

void ModbusReader::readRegisters()
{
    if (!m_modbusClient || !m_connected) return;

    // Read holding registers 40001-40006 (6 registers = 3 floats)
    QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters, 0, 6);

    auto *reply = m_modbusClient->sendReadRequest(readUnit, 1);  // Unit ID = 1

    if (reply) {
        if (!reply->isFinished()) {
            connect(reply, &QModbusReply::finished, this, &ModbusReader::onReadReady);
        } else {
            delete reply;
        }
    }
}

void ModbusReader::onReadReady()
{
    auto *reply = qobject_cast<QModbusReply *>(sender());
    if (!reply) return;

    if (reply->error() == QModbusDevice::NoError) {
        const QModbusDataUnit unit = reply->result();

        // Parse Float32 values (2 registers each)
        m_grossWeight = registersToFloat(unit.value(0), unit.value(1));
        m_netWeight = registersToFloat(unit.value(2), unit.value(3));
        m_tare = registersToFloat(unit.value(4), unit.value(5));

        emit weightChanged();
    } else {
        emit errorOccurred(reply->errorString());
    }

    reply->deleteLater();
}

float ModbusReader::registersToFloat(quint16 reg1, quint16 reg2)
{
    // IEEE 754 big-endian conversion
    quint32 raw = (static_cast<quint32>(reg1) << 16) | reg2;
    float value;
    memcpy(&value, &raw, sizeof(float));
    return value;
}

void ModbusReader::zero()
{
    // Write 1 to register 40201
    QModbusDataUnit writeUnit(QModbusDataUnit::HoldingRegisters, 200, 1);
    writeUnit.setValue(0, 1);

    m_modbusClient->sendWriteRequest(writeUnit, 1);
    qDebug() << "Zero command sent";
}

void ModbusReader::setTare()
{
    // Write 1 to register 40202
    QModbusDataUnit writeUnit(QModbusDataUnit::HoldingRegisters, 201, 1);
    writeUnit.setValue(0, 1);

    m_modbusClient->sendWriteRequest(writeUnit, 1);
    qDebug() << "Tare command sent";
}

QML User Interface

main.qml

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtCharts 2.15
import com.amazeng 1.0  // ModbusReader

ApplicationWindow {
    visible: true
    width: 1024
    height: 600
    title: "GDT Weighing System"

    ModbusReader {
        id: modbusReader
        Component.onCompleted: {
            connectToDevice("192.168.1.100", 502)
        }
    }

    Rectangle {
        anchors.fill: parent
        color: "#1E1E1E"

        ColumnLayout {
            anchors.fill: parent
            anchors.margins: 20
            spacing: 20

            // Header
            Rectangle {
                Layout.fillWidth: true
                height: 60
                color: "#2D2D2D"
                radius: 8

                RowLayout {
                    anchors.fill: parent
                    anchors.margins: 10

                    Text {
                        text: "GDT-410M Weighing Station"
                        font.pixelSize: 28
                        font.bold: true
                        color: "#FFFFFF"
                    }

                    Item { Layout.fillWidth: true }

                    Rectangle {
                        width: 20
                        height: 20
                        radius: 10
                        color: modbusReader.connected ? "#2ECC71" : "#E74C3C"
                    }

                    Text {
                        text: modbusReader.connected ? "Connected" : "Disconnected"
                        font.pixelSize: 16
                        color: "#FFFFFF"
                    }
                }
            }

            // Main weight display
            Rectangle {
                Layout.fillWidth: true
                Layout.preferredHeight: 250
                color: "#27AE60"
                radius: 12

                ColumnLayout {
                    anchors.centerIn: parent
                    spacing: 10

                    Text {
                        text: "NET WEIGHT"
                        font.pixelSize: 24
                        color: "white"
                        Layout.alignment: Qt.AlignHCenter
                    }

                    Text {
                        text: modbusReader.netWeight.toFixed(2)
                        font.pixelSize: 96
                        font.bold: true
                        color: "white"
                        Layout.alignment: Qt.AlignHCenter
                    }

                    Text {
                        text: "kg"
                        font.pixelSize: 36
                        color: "white"
                        Layout.alignment: Qt.AlignHCenter
                    }
                }
            }

            // Gross and Tare
            RowLayout {
                Layout.fillWidth: true
                spacing: 20

                Rectangle {
                    Layout.fillWidth: true
                    height: 120
                    color: "#34495E"
                    radius: 8

                    ColumnLayout {
                        anchors.centerIn: parent
                        spacing: 5

                        Text {
                            text: "Gross"
                            font.pixelSize: 18
                            color: "#BDC3C7"
                            Layout.alignment: Qt.AlignHCenter
                        }

                        Text {
                            text: modbusReader.grossWeight.toFixed(2) + " kg"
                            font.pixelSize: 32
                            color: "white"
                            Layout.alignment: Qt.AlignHCenter
                        }
                    }
                }

                Rectangle {
                    Layout.fillWidth: true
                    height: 120
                    color: "#34495E"
                    radius: 8

                    ColumnLayout {
                        anchors.centerIn: parent
                        spacing: 5

                        Text {
                            text: "Tare"
                            font.pixelSize: 18
                            color: "#BDC3C7"
                            Layout.alignment: Qt.AlignHCenter
                        }

                        Text {
                            text: modbusReader.tare.toFixed(2) + " kg"
                            font.pixelSize: 32
                            color: "white"
                            Layout.alignment: Qt.AlignHCenter
                        }
                    }
                }
            }

            // Control buttons
            RowLayout {
                Layout.fillWidth: true
                spacing: 20

                Button {
                    text: "ZERO"
                    Layout.fillWidth: true
                    Layout.preferredHeight: 80
                    font.pixelSize: 24
                    onClicked: modbusReader.zero()

                    background: Rectangle {
                        color: parent.pressed ? "#E67E22" : "#F39C12"
                        radius: 8
                    }

                    contentItem: Text {
                        text: parent.text
                        font: parent.font
                        color: "white"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                    }
                }

                Button {
                    text: "TARE"
                    Layout.fillWidth: true
                    Layout.preferredHeight: 80
                    font.pixelSize: 24
                    onClicked: modbusReader.setTare()

                    background: Rectangle {
                        color: parent.pressed ? "#2980B9" : "#3498DB"
                        radius: 8
                    }

                    contentItem: Text {
                        text: parent.text
                        font: parent.font
                        color: "white"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                    }
                }
            }
        }
    }
}

Real-World Application: Automatic Packaging Line

Scenario

Products are weighed and automatically recorded in a packaging line.

Requirements:

  1. Target Weight: 25 kg ± 0.1 kg
  2. Automatic Recording: Product over threshold → Save to database
  3. Tare: Automatic tare after every 10 weighing operations

Implementation

Data Recording (SQLite):

// DatabaseManager.cpp
void DatabaseManager::saveWeighing(float weight, const QString &batchCode)
{
    QSqlQuery query(m_db);
    query.prepare("INSERT INTO weighings (timestamp, weight, batch_code) "
                  "VALUES (datetime('now'), :weight, :batch)");
    query.bindValue(":weight", weight);
    query.bindValue(":batch", batchCode);
    query.exec();
}

GDT + HMI + AWS Integration

Our GDT + HMI combination can also send data to the cloud. Details in our Cloud & IoT Data Collection solution:

GDT (Modbus) → HMI (Gateway) → MQTT → AWS IoT Core

HMI panel:

  • Displays weight locally (Qt/QML interface)
  • Publishes to AWS IoT Core (MQTT client)
  • Stores in database (SQLite/PostgreSQL)

Conclusion

Our GDT Digital Transmitter and HMI Industrial Display Solutions integration provides:

✅ Real-time weight monitoring with 200ms update rate
✅ Modern touchscreen interface with Qt/QML
✅ Flexible communication with Modbus TCP/RTU
Cloud integration ready (AWS, Azure)