GDT + HMI Entegrasyonu: Modbus ile Ağırlık Gösterimi ve Kontrol

Amazeng Teknik Ekip
9 dakika okuma
GDTHMIModbusLoadcellTartım SistemiTouchscreen

Giriş

Tartım sistemlerinde loadcell verilerinin görselleştirilmesi için geleneksel olarak dedike ağırlık göstergeleri kullanılır. Ancak modern uygulamalarda, touchscreen HMI panelleri ile GDT Dijital Transmitter entegrasyonu, çok daha esnek ve güçlü bir çözüm sunar.

Bu yazımızda, Modbus RTU/TCP protokolü üzerinden GDT'den HMI'ya veri aktarımı, kullanıcı arayüzü tasarımı ve gerçek dünya uygulamalarını inceliyoruz.

Neden GDT + HMI?

Geleneksel Çözüm: Dedike Gösterge

Loadcell → GDT → 4-20mA → Ağırlık Göstergesi (LED Display)

Sınırlamalar:

  • Sadece sayısal gösterim
  • Grafik yok
  • Tare butonu fiziksel
  • Uzaktan erişim yok
  • Loglama yok

Modern Çözüm: GDT + HMI

Loadcell → GDT → Modbus TCP/RTU → HMI (7"/10.1" Touchscreen)

Avantajlar: ✅ Grafik trend gösterimi
✅ Touchscreen tare/zero
✅ Çoklu tank görüntüleme
✅ Alarm threshold ayarları
✅ SD karta veri kaydı
✅ MQTT ile bulut entegrasyonu

Modbus Haberleşme Mimarisi

Seçenek 1: Modbus TCP (Ethernet)

┌─────────────┐         ┌─────────────┐
│   GDT-TCP   │  TCP/IP │ HMI Panel   │
│ 192.168.1.10├────────►│ 192.168.1.20│
│   Port 502  │  LAN    │   Qt/QML    │
└─────────────┘         └─────────────┘

Avantajları:

  • Hızlı (tipik 10-20ms response time)
  • Uzun mesafe (100m+ CAT5e kablo)
  • Network üzerinde birden fazla HMI aynı GDT'yi okuyabilir

Seçenek 2: Modbus RTU (RS485)

┌─────────────┐         ┌─────────────┐
│   GDT-RTU   │  RS485  │ HMI Panel   │
│  Slave ID:1 ├────────►│   Master    │
│   (A, B)    │ 2-wire  │  (A, B, G)  │
└─────────────┘         └─────────────┘

Avantajları:

  • Elektriksel izolasyon
  • Gürültüye dayanıklı (diferansiyel sinyal)
  • Daisy-chain: Tek RS485 hattına çoklu GDT

Kablo Bağlantısı:

GDT         HMI
A    ────   A (RS485+)
B    ────   B (RS485-)
GND  ────   G (Ground - opsiyonel)

Terminasyon Direnci:
Hat ucunda 120Ω direnç ekleyin (yansıma önleme).

GDT Modbus Register Map

GDT Dijital Transmitter aşağıdaki Modbus registerlarını sunar:

Holding Registers (Function Code 03 - Read)

RegisterVeriTypeUnitAçıklama
0-1Channel 1 RawFloatmV/VKanal 1 ham değer
2-3Channel 2 RawFloatmV/VKanal 2 ham değer
4-5Channel 3 RawFloatmV/VKanal 3 ham değer
6-7Channel 4 RawFloatmV/VKanal 4 ham değer
8-9Net WeightFloatkgTare düşülmüş net ağırlık
10-11Gross WeightFloatkgBrüt ağırlık
12-13Tare ValueFloatkgAktif tare değeri
14StatusUint16-Durum bayrakları
15AlarmUint16-Alarm bayrakları

Float Format: IEEE 754 (Big-endian, 2 register)

Coils (Function Code 05 - Write Single Coil)

CoilFonksiyon
0Tare (Net ağırlığı sıfırla)
1Zero (Brüt ağırlığı sıfırla)
2Kalibrasyon Modu ON
3Kalibrasyon Modu OFF

HMI Uygulaması (Qt/QML)

Modbus Client Backend (C++)

modbusClient.h

#ifndef MODBUSCLIENT_H
#define MODBUSCLIENT_H

#include <QObject>
#include <QTimer>
#include <QModbusClient>
#include <QModbusTcpClient>
#include <QModbusRtuSerialMaster>

class ModbusClient : public QObject
{
    Q_OBJECT
    Q_PROPERTY(float netWeight READ netWeight NOTIFY netWeightChanged)
    Q_PROPERTY(float grossWeight READ grossWeight NOTIFY grossWeightChanged)
    Q_PROPERTY(float tareValue READ tareValue NOTIFY tareValueChanged)
    Q_PROPERTY(bool connected READ isConnected NOTIFY connectionChanged)

public:
    explicit ModbusClient(QObject *parent = nullptr);

    // Getters
    float netWeight() const { return m_netWeight; }
    float grossWeight() const { return m_grossWeight; }
    float tareValue() const { return m_tareValue; }
    bool isConnected() const { return m_connected; }

    // Q_INVOKABLE methods (QML'den çağrılabilir)
    Q_INVOKABLE void connectTcp(const QString &host, int port);
    Q_INVOKABLE void connectRtu(const QString &port, int baudrate);
    Q_INVOKABLE void disconnect();
    Q_INVOKABLE void sendTare();
    Q_INVOKABLE void sendZero();

signals:
    void netWeightChanged();
    void grossWeightChanged();
    void tareValueChanged();
    void connectionChanged();
    void errorOccurred(const QString &error);

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

private:
    QModbusClient *m_modbusDevice = nullptr;
    QTimer *m_pollTimer = nullptr;

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

    int m_serverAddress = 1; // Modbus slave ID

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

#endif

modbusClient.cpp (Ana Fonksiyonlar)

#include "modbusClient.h"
#include <QModbusDataUnit>

ModbusClient::ModbusClient(QObject *parent) : QObject(parent)
{
    m_pollTimer = new QTimer(this);
    connect(m_pollTimer, &QTimer::timeout, this, &ModbusClient::readRegisters);
}

void ModbusClient::connectTcp(const QString &host, int port)
{
    if (m_modbusDevice) {
        m_modbusDevice->disconnectDevice();
        delete m_modbusDevice;
    }

    m_modbusDevice = new QModbusTcpClient(this);
    m_modbusDevice->setConnectionParameter(
        QModbusDevice::NetworkAddressParameter, host);
    m_modbusDevice->setConnectionParameter(
        QModbusDevice::NetworkPortParameter, port);

    connect(m_modbusDevice, &QModbusDevice::stateChanged,
            this, &ModbusClient::onStateChanged);

    if (!m_modbusDevice->connectDevice()) {
        emit errorOccurred(m_modbusDevice->errorString());
    }
}

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

    if (m_connected) {
        m_pollTimer->start(100); // Her 100ms'de bir oku
    } else {
        m_pollTimer->stop();
    }
}

void ModbusClient::readRegisters()
{
    if (!m_modbusDevice || !m_connected) return;

    // Register 8-13 arası oku (Net, Gross, Tare)
    auto *reply = m_modbusDevice->sendReadRequest(
        QModbusDataUnit(QModbusDataUnit::HoldingRegisters, 8, 6),
        m_serverAddress);

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

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

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

        // Net Weight (reg 8-9)
        m_netWeight = registersToFloat(unit.value(0), unit.value(1));
        emit netWeightChanged();

        // Gross Weight (reg 10-11)
        m_grossWeight = registersToFloat(unit.value(2), unit.value(3));
        emit grossWeightChanged();

        // Tare Value (reg 12-13)
        m_tareValue = registersToFloat(unit.value(4), unit.value(5));
        emit tareValueChanged();
    }

    reply->deleteLater();
}

float ModbusClient::registersToFloat(quint16 reg1, quint16 reg2)
{
    // IEEE 754 float dönüşümü (Big-endian)
    quint32 value = (static_cast<quint32>(reg1) << 16) | reg2;
    float result;
    memcpy(&result, &value, sizeof(float));
    return result;
}

void ModbusClient::sendTare()
{
    if (!m_modbusDevice || !m_connected) return;

    // Coil 0 = Tare komutu
    m_modbusDevice->sendWriteRequest(
        QModbusDataUnit(QModbusDataUnit::Coils, 0, 1),
        m_serverAddress);
}

void ModbusClient::sendZero()
{
    if (!m_modbusDevice || !m_connected) return;

    // Coil 1 = Zero komutu
    m_modbusDevice->sendWriteRequest(
        QModbusDataUnit(QModbusDataUnit::Coils, 1, 1),
        m_serverAddress);
}

QML Kullanıcı Arayüzü

main.qml

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15

ApplicationWindow {
    visible: true
    width: 1024
    height: 600
    title: "GDT Loadcell Monitor"

    // Arka plan
    Rectangle {
        anchors.fill: parent
        gradient: Gradient {
            GradientStop { position: 0.0; color: "#34495E" }
            GradientStop { position: 1.0; color: "#2C3E50" }
        }
    }

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

        // Başlık ve bağlantı durumu
        RowLayout {
            Layout.fillWidth: true

            Text {
                text: "GDT Tartım Sistemi"
                font.pixelSize: 36
                font.bold: true
                color: "#ECF0F1"
            }

            Item { Layout.fillWidth: true }

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

            Text {
                text: modbusClient.connected ? "Bağlı" : "Bağlantı Yok"
                font.pixelSize: 18
                color: "#ECF0F1"
            }
        }

        // Ana ağırlık göstergesi
        Rectangle {
            Layout.fillWidth: true
            Layout.preferredHeight: 250
            color: "#1ABC9C"
            radius: 15

            ColumnLayout {
                anchors.centerIn: parent
                spacing: 10

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

                Text {
                    text: modbusClient.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
                }
            }
        }

        // Brüt ve Tare göstergeleri
        RowLayout {
            Layout.fillWidth: true
            spacing: 20

            // Brüt Ağırlık
            Rectangle {
                Layout.fillWidth: true
                Layout.preferredHeight: 120
                color: "#3498DB"
                radius: 10

                ColumnLayout {
                    anchors.centerIn: parent

                    Text {
                        text: "Brüt"
                        font.pixelSize: 18
                        color: "white"
                        Layout.alignment: Qt.AlignHCenter
                    }

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

            // Tare Değeri
            Rectangle {
                Layout.fillWidth: true
                Layout.preferredHeight: 120
                color: "#E67E22"
                radius: 10

                ColumnLayout {
                    anchors.centerIn: parent

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

                    Text {
                        text: modbusClient.tareValue.toFixed(2) + " kg"
                        font.pixelSize: 32
                        font.bold: true
                        color: "white"
                        Layout.alignment: Qt.AlignHCenter
                    }
                }
            }
        }

        // Kontrol butonları
        RowLayout {
            Layout.fillWidth: true
            Layout.alignment: Qt.AlignHCenter
            spacing: 20

            Button {
                text: "TARE"
                font.pixelSize: 24
                implicitWidth: 200
                implicitHeight: 80

                background: Rectangle {
                    color: parent.pressed ? "#16A085" : "#1ABC9C"
                    radius: 10
                }

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

                onClicked: modbusClient.sendTare()
            }

            Button {
                text: "SIFIRLA"
                font.pixelSize: 24
                implicitWidth: 200
                implicitHeight: 80

                background: Rectangle {
                    color: parent.pressed ? "#C0392B" : "#E74C3C"
                    radius: 10
                }

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

                onClicked: modbusClient.sendZero()
            }
        }
    }

    Component.onCompleted: {
        // GDT'ye Modbus TCP bağlantısı
        modbusClient.connectTcp("192.168.1.10", 502)
    }
}

Gerçek Dünya Uygulaması: Paketleme Hattı

Senaryo

Bir gıda fabrikasında ürünler 25kg'lık çuvallara paketleniyor. Her dolum istasyonunda:

HMI Fonksiyonları

  1. Hedef ağırlık girişi: Operatör touchscreen'den 25kg girer
  2. Dolum izleme: Anlık ağırlık artışı gösterilir
  3. Otomatik durdurma: 24.5kg'da valf kapanır
  4. Tare: Her yeni çuval için otomatik tare
  5. Sayaç: Toplam paket sayısı (SD karta yazılır)

QML - Paketleme Modu

Rectangle {
    property real targetWeight: 25.0
    property int packageCount: 0

    ColumnLayout {
        // Hedef ağırlık göstergesi
        ProgressBar {
            from: 0
            to: targetWeight
            value: modbusClient.netWeight

            background: Rectangle {
                color: "#BDC3C7"
                radius: 5
            }

            contentItem: Item {
                Rectangle {
                    width: parent.width * (parent.value / parent.to)
                    height: parent.height
                    color: parent.value >= targetWeight ? "#2ECC71" : "#3498DB"
                    radius: 5
                }
            }
        }

        Text {
            text: modbusClient.netWeight.toFixed(2) + " / " + targetWeight + " kg"
            font.pixelSize: 48
        }

        // Paket sayacı
        Text {
            text: "Toplam Paket: " + packageCount
            font.pixelSize: 32
        }

        Button {
            text: "YENİ PAKET (TARE)"
            onClicked: {
                modbusClient.sendTare()
                packageCount++
            }
        }
    }

    // Otomatik durdurma logic
    Connections {
        target: modbusClient
        function onNetWeightChanged() {
            if (modbusClient.netWeight >= targetWeight * 0.98) {
                // Valf kapatma sinyali (Modbus coil write)
                // ...
            }
        }
    }
}

Hata Yönetimi ve Diagnostics

Modbus Timeout

void ModbusClient::onReadReady()
{
    auto *reply = qobject_cast<QModbusReply *>(sender());

    if (reply->error() == QModbusDevice::TimeoutError) {
        emit errorOccurred("GDT cevap vermiyor! Kablo bağlantısını kontrol edin.");

        // HMI'da görsel uyarı
        m_connected = false;
        emit connectionChanged();
    }
}

CRC Hatası (Modbus RTU)

RS485 hattında elektriksel gürültü varsa CRC hataları oluşabilir:

  • Kablo kalitesini artırın (shielded CAT5)
  • Terminasyon direnci ekleyin
  • Baudrate'i düşürün (19200 → 9600)

Performans Optimizasyonu

Polling Rate Seçimi

// Hızlı: 100ms (10 Hz) - Dinamik tartım
m_pollTimer->start(100);

// Orta: 500ms (2 Hz) - Normal tartım
m_pollTimer->start(500);

// Yavaş: 1000ms (1 Hz) - Tank izleme
m_pollTimer->start(1000);

Kural: Statik tartımda (tank) yavaş polling yeterli, dinamik tartımda (konveyör) hızlı polling gerekli.

Sonuç

GDT Dijital Transmitter ile HMI Panel entegrasyonu, tartım sistemlerine şunları kazandırır:

Esneklik: UI'yı istediğiniz gibi özelleştirin
Fonksiyonellik: Grafik, alarm, loglama
Maliyet: Dedike göstergelerden ucuz
Genişleyebilirlik: Bulut entegrasyonu hazır

Benzer bir çözüm için iletişime geçin.

İlgili Kaynaklar