Embedded Linux ile HMI Geliştirme: Yocto ve Qt ile Özel Arayüz Tasarımı
Giriş
Endüstriyel HMI sistemlerinde Windows CE ve proprietary çözümlerin hakimiyeti son yıllarda Embedded Linux lehine hızla değişiyor. HMI Endüstriyel Ekran Çözümlerimiz, tam da bu paradigma değişiminin merkezinde: Yocto Project ile özelleştirilmiş Linux dağıtımı ve Qt/QML ile modern arayüz geliştirme.
Bu yazımızda, sıfırdan bir embedded Linux HMI'nın nasıl oluşturulacağını, blog yazımızda anlattığımız Yocto altyapısı üzerine Qt entegrasyonunu ve gerçek dünya uygulamalarını inceliyoruz.
Neden Embedded Linux + Qt?
Geleneksel HMI Çözümlerinin Sınırlamaları
1. Windows CE / Windows Embedded
- Lisans maliyetleri (cihaz başına $100+)
- Güvenlik güncellemeleri sonlandı (Windows CE 2018'de EOL)
- Donanım gereksinimleri yüksek (en az 512MB RAM)
2. Proprietary Çözümler (Siemens WinCC, Rockwell FactoryTalk)
- Vendor lock-in (üretici bağımlılığı)
- Sınırlı özelleştirme imkanı
- Yüksek yazılım maliyeti
3. Arduino/MCU Tabanlı Basit Ekranlar
- Düşük grafik performansı
- Sınırlı networking (TCP/IP stack'i zayıf)
- Karmaşık uygulamalar için yetersiz
Embedded Linux + Qt'nin Avantajları
✅ Açık Kaynak & Ücretsiz: Hiçbir lisans maliyeti yok
✅ Tam Kontrol: İşletim sisteminin her katmanını özelleştirme
✅ Güçlü Grafik: Qt Quick (QML) ile hardware-accelerated UI
✅ Networking: Native TCP/IP, MQTT, Modbus desteği
✅ OTA Updates: A/B partition ile güvenli yazılım güncellemesi
✅ Uzun Ömür: Linux kernel ve Qt, 10+ yıl destek garantisi
Yocto Project: Özelleştirilmiş Linux İmajı
Yocto Nedir?
Yocto Project, embedded cihazlar için özelleştirilmiş Linux dağıtımları oluşturmaya yarayan bir build sistemidir. Standart bir Ubuntu/Debian'dan farkı:
- Minimal footprint: Sadece ihtiyaç duyulan paketler dahil (~50MB rootfs)
- Cross-compilation: Geliştirme PC'sinde (x86) ARM işlemciler için derleme
- Reproducible builds: Her build aynı sonucu verir
HMI İçin Yocto Layer Yapısı
meta-hmi-amazeng/
├── recipes-core/
│ └── images/
│ └── hmi-image.bb # Ana imaj tarifi
├── recipes-graphics/
│ └── qt5/
│ └── qtbase_%.bbappend # Qt özelleştirmeleri
├── recipes-connectivity/
│ └── modbus/
│ └── libmodbus_git.bb # Modbus kütüphanesi
├── recipes-kernel/
│ └── linux/
│ └── linux-%.bbappend # Touchscreen driver'ları
└── recipes-app/
└── hmi-app/
└── hmi-app_git.bb # HMI uygulaması
Örnek HMI Image Tarifi
# hmi-image.bb
DESCRIPTION = "Amazeng HMI Linux Image"
LICENSE = "MIT"
IMAGE_FEATURES += " \
splash \
ssh-server-dropbear \
hwcodecs \
"
IMAGE_INSTALL = " \
packagegroup-core-boot \
qtbase \
qtdeclarative \
qtquickcontrols2 \
qtgraphicaleffects \
qtsvg \
qtwebsockets \
libmodbus \
hmi-app \
ca-certificates \
tzdata \
"
inherit core-image
Qt/QML ile Modern HMI Arayüzü
Qt Quick (QML) vs Qt Widgets
| Özellik | Qt Widgets (C++) | Qt Quick (QML) |
|---|---|---|
| Performans | CPU-based | GPU-accelerated |
| Geliştirme Hızı | Yavaş (compile) | Hızlı (interpreted) |
| Animasyon | Karmaşık | Çok kolay |
| Touchscreen | Mouse event'leri | Native touch |
| Tasarımcı Desteği | Qt Designer | Qt Design Studio |
HMI uygulamaları için Qt Quick (QML) kesinlikle tercih edilmelidir.
Basit HMI Uygulaması: Loadcell Okuyucu
Bir GDT Dijital Transmitter'dan Modbus TCP ile ağırlık okuyup gösteren HMI:
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 gradient
Rectangle {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.0; color: "#2C3E50" }
GradientStop { position: 1.0; color: "#34495E" }
}
}
ColumnLayout {
anchors.centerIn: parent
spacing: 30
// Başlık
Text {
text: "Tank 1 - Net Ağırlık"
font.pixelSize: 36
font.bold: true
color: "#ECF0F1"
Layout.alignment: Qt.AlignHCenter
}
// Ağırlık göstergesi
Rectangle {
width: 600
height: 200
color: "#1ABC9C"
radius: 20
Text {
anchors.centerIn: parent
text: modbusBackend.weight.toFixed(2) + " kg"
font.pixelSize: 72
font.bold: true
color: "white"
}
}
// Kontrol butonları
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 20
Button {
text: "TARE"
font.pixelSize: 24
implicitWidth: 150
implicitHeight: 80
onClicked: modbusBackend.tare()
}
Button {
text: "SIFIRLA"
font.pixelSize: 24
implicitWidth: 150
implicitHeight: 80
onClicked: modbusBackend.zero()
}
}
}
}
modbus_backend.h (C++)
#ifndef MODBUSBACKEND_H
#define MODBUSBACKEND_H
#include <QObject>
#include <QTimer>
#include <modbus/modbus.h>
class ModbusBackend : public QObject
{
Q_OBJECT
Q_PROPERTY(double weight READ weight NOTIFY weightChanged)
public:
explicit ModbusBackend(QObject *parent = nullptr);
~ModbusBackend();
double weight() const { return m_weight; }
Q_INVOKABLE void tare();
Q_INVOKABLE void zero();
signals:
void weightChanged();
private slots:
void readModbusData();
private:
modbus_t *m_modbusCtx;
QTimer *m_timer;
double m_weight = 0.0;
};
#endif
modbus_backend.cpp
#include "modbus_backend.h"
#include <QDebug>
ModbusBackend::ModbusBackend(QObject *parent)
: QObject(parent)
{
// GDT Modbus TCP bağlantısı (IP: 192.168.1.100, Port: 502)
m_modbusCtx = modbus_new_tcp("192.168.1.100", 502);
if (modbus_connect(m_modbusCtx) == -1) {
qCritical() << "Modbus bağlantısı başarısız:" << modbus_strerror(errno);
modbus_free(m_modbusCtx);
return;
}
// Her 100ms'de bir veri oku
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, &ModbusBackend::readModbusData);
m_timer->start(100);
}
void ModbusBackend::readModbusData()
{
uint16_t registers[2];
// GDT holding register 0: Net ağırlık (IEEE 754 float, 2 register)
if (modbus_read_registers(m_modbusCtx, 0, 2, registers) == 2) {
// Float'a dönüştür
union {
uint32_t u32;
float f32;
} converter;
converter.u32 = (registers[0] << 16) | registers[1];
m_weight = converter.f32;
emit weightChanged();
}
}
void ModbusBackend::tare()
{
// GDT coil 0: Tare komutu
modbus_write_bit(m_modbusCtx, 0, 1);
}
void ModbusBackend::zero()
{
// GDT coil 1: Zero komutu
modbus_write_bit(m_modbusCtx, 1, 1);
}
Gerçek Dünya Uygulaması: Çoklu Tank İzleme
Senaryo
3 adet süt tankı, her birinde GDT Dijital Transmitter + 4 loadcell. Merkezi bir 10.1" HMI ile tüm tankları izleme.
Mimari
┌──────────────┐ Modbus TCP ┌──────────────┐
│ Tank 1 GDT │◄─────────────────►│ │
│ 192.168.1.101│ │ │
└──────────────┘ │ │
│ HMI Panel │
┌──────────────┐ Modbus TCP │ (10.1 inch) │
│ Tank 2 GDT │◄─────────────────►│ Qt/QML │
│ 192.168.1.102│ │ │
└──────────────┘ │ │
│ │
┌──────────────┐ Modbus TCP │ │
│ Tank 3 GDT │◄─────────────────►│ │
│ 192.168.1.103│ └──────────────┘
└──────────────┘
QML Layout (3 Tank Grid)
GridLayout {
columns: 3
columnSpacing: 20
rowSpacing: 20
Repeater {
model: [
{ name: "Tank 1", ip: "192.168.1.101" },
{ name: "Tank 2", ip: "192.168.1.102" },
{ name: "Tank 3", ip: "192.168.1.103" }
]
TankWidget {
tankName: modelData.name
ipAddress: modelData.ip
Layout.preferredWidth: 300
Layout.preferredHeight: 400
}
}
}
Yocto + Qt Geliştirme İş Akışı
1. Geliştirme Ortamı Kurulumu
# Yocto SDK kurulumu
./poky-sdk-installer.sh
# SDK'yı aktive et
source /opt/poky/environment-setup-cortexa7-poky-linux-gnueabi
# Qt Creator'da cross-compiler ayarla
qmake -query
2. Hızlı Geliştirme Döngüsü
Lokal Test (PC'de):
qmake hmi-app.pro
make
./hmi-app
Hedef Cihazda Test:
# SCP ile HMI paneline kopyala
scp hmi-app [email protected]:/usr/bin/
# SSH ile çalıştır
ssh [email protected]
systemctl stop hmi-app
/usr/bin/hmi-app
3. Yocto Image Build
cd yocto-build
bitbake hmi-image
# SD karta yaz
sudo dd if=tmp/deploy/images/hmi-board/hmi-image.wic of=/dev/sdb bs=4M
OTA (Over-The-Air) Güncelleme Stratejisi
A/B Partition Şeması
/dev/mmcblk0p1: boot
/dev/mmcblk0p2: rootfs-a (aktif)
/dev/mmcblk0p3: rootfs-b (yedek)
/dev/mmcblk0p4: data (kullanıcı verileri)
Güncelleme Süreci
- Yeni imaj
rootfs-b'ye download edilir - Bootloader flag değiştirilir
- Reboot sonrası
rootfs-baktif olur - Uygulama başarılı başlarsa
rootfs-bkalıcı olur - Hata varsa, reboot ile
rootfs-a'ya geri döner (rollback)
Performans Optimizasyonu
1. Boot Hızı (Target: < 5 saniye)
# hmi-image.bb içinde
IMAGE_FEATURES_remove = "debug-tweaks package-management"
EXTRA_IMAGE_FEATURES = "read-only-rootfs"
2. Qt Quick Animasyon Optimizasyonu
// CPU yerine GPU kullan
layer.enabled: true
layer.effect: ShaderEffect { }
// Gereksiz render'ları önle
visible: opacity > 0
opacity: enabled ? 1.0 : 0.0
Behavior on opacity { NumberAnimation { duration: 200 } }
3. Modbus Polling Optimizasyonu
// Her değişken için farklı update rate
QTimer *fastTimer = new QTimer(this); // 100ms - ağırlık
QTimer *slowTimer = new QTimer(this); // 1000ms - sıcaklık
Bulut Entegrasyonu: HMI + AWS IoT
HMI paneli aynı zamanda bir IoT gateway olarak da çalışabilir. Cloud & IoT Veri Toplama çözümümüzde, HMI:
- Modbus RTU/TCP ile ZMA ve GDT cihazlarından veri toplar
- MQTT ile AWS IoT Core'a gönderir
- Hem lokal arayüzde gösterir, hem de Grafana dashboard'da
Qt MQTT Client Örneği:
#include <QtMqtt/QMqttClient>
QMqttClient *mqttClient = new QMqttClient(this);
mqttClient->setHostname("xxxxx.iot.eu-west-1.amazonaws.com");
mqttClient->setPort(8883);
mqttClient->connectToHost();
// GDT'den okunan veriyi publish et
QByteArray payload = QString("{\"tank_id\":1,\"weight\":%1}").arg(weight).toUtf8();
mqttClient->publish("amazeng/tank/data", payload);
Sonuç ve Öneriler
Embedded Linux + Qt kombinasyonu, modern endüstriyel HMI'lar için en esnek ve güçlü çözümdür. HMI Endüstriyel Ekran Çözümlerimiz, bu teknoloji stack'i üzerine inşa edilmiştir ve:
✅ Proprietary çözümlerden %70 daha ekonomiktir
✅ Tam özelleştirme özgürlüğü sunar
✅ 10+ yıl uzun ömürlü destek garantisi
✅ Cloud ve IoT entegrasyonuna hazır
İlgili Kaynaklar
- Yocto ile Embedded Linux
- Zephyr RTOS Neden Kullanıyoruz (alternatif RTOS yaklaşımı)
- HMI Endüstriyel Ekran Çözümleri
- Cloud & IoT Veri Toplama