GDT + HMI Entegrasyonu: Modbus ile Ağırlık Gösterimi ve Kontrol
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)
| Register | Veri | Type | Unit | Açıklama |
|---|---|---|---|---|
| 0-1 | Channel 1 Raw | Float | mV/V | Kanal 1 ham değer |
| 2-3 | Channel 2 Raw | Float | mV/V | Kanal 2 ham değer |
| 4-5 | Channel 3 Raw | Float | mV/V | Kanal 3 ham değer |
| 6-7 | Channel 4 Raw | Float | mV/V | Kanal 4 ham değer |
| 8-9 | Net Weight | Float | kg | Tare düşülmüş net ağırlık |
| 10-11 | Gross Weight | Float | kg | Brüt ağırlık |
| 12-13 | Tare Value | Float | kg | Aktif tare değeri |
| 14 | Status | Uint16 | - | Durum bayrakları |
| 15 | Alarm | Uint16 | - | Alarm bayrakları |
Float Format: IEEE 754 (Big-endian, 2 register)
Coils (Function Code 05 - Write Single Coil)
| Coil | Fonksiyon |
|---|---|
| 0 | Tare (Net ağırlığı sıfırla) |
| 1 | Zero (Brüt ağırlığı sıfırla) |
| 2 | Kalibrasyon Modu ON |
| 3 | Kalibrasyon 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:
- 1x GDT-4CH (4 loadcell bağlı)
- 1x 7" HMI Panel
HMI Fonksiyonları
- Hedef ağırlık girişi: Operatör touchscreen'den 25kg girer
- Dolum izleme: Anlık ağırlık artışı gösterilir
- Otomatik durdurma: 24.5kg'da valf kapanır
- Tare: Her yeni çuval için otomatik tare
- 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.