Vaka Çalışması · Mekatronik

Tarayıcıda çalışan bir PLC simülatörü nasıl yazılır?

Ladder tarama motorundan robot kinematiğine, sahayı canlandıran fizik döngüsünden bulduğum iki ciddi hataya kadar — Tomris PLC Akademi'nin mühendislik anlatısı.

RolTasarım · Geliştirme · Test
AlanEndüstriyel Otomasyon
ÇıktıTek dosya, kurulumsuz
Durum● Yayında

İçindekiler

  1. Problem: neden bir simülatör daha?
  2. Mimari: üç katmanlı ayrım
  3. Kalp: ladder tarama motoru
  4. Bulduğum hata: PLC bobinleri ne zaman yazılır?
  5. Saha: fizik ve sensör modeli
  6. Tomris: kinematik ve hareket kalitesi
  7. Test: örnek çözümleri gerçekten çalıştırmak
  8. Sonuç ve öğrendiklerim

Problem

Factory I/O ve TIA Portal harika araçlar — ama ikisi de Windows, lisans ve kurulum istiyor. Bir öğrencinin ya da işe dönen bir mühendisin "hemen şimdi ladder deneyeyim" demesinin önünde ciddi bir sürtünme var.

Hedefim netti: tarayıcıda açılan, kurulum istemeyen, ama oyuncak olmayan bir öğrenme aracı. "Oyuncak olmamak" burada kritik — çünkü asıl mesele ladder sembollerini çizebilmek değil, PLC'nin nasıl düşündüğünü kavramak: tarama döngüsü, mühürleme, kenar tetikleme, el sıkışma sinyalleri, güvenli duruş.

Bunun için tek bir tasarım kararı her şeyi belirledi:

Temel Tasarım Kararı

Saha ölü doğar. Konveyör dönmez, robot kıpırdamaz, hiçbir şey çalışmaz — ta ki kullanıcının yazdığı ladder programı çıkışları sürene kadar. Sahne bir animasyon değil, programın sonucudur. Yanlış mantık yazarsan kutular yanlış yere gider ve bunu 3B olarak görürsün.

Mimari

Sistem üç katmana ayrıldı ve bu katmanlar birbirini yalnızca tag tablosu üzerinden görür. Gerçek bir tesiste PLC ile saha arasındaki tek köprü de I/O kartlarıdır — mimariyi kasten buna benzettim.

Ladder Motoru plcScan(dt) NO/NC · P/N · TON/TOF CTU · SET/RST · OR dalları TAG TABLOSU tek gerçeklik kaynağı I0.0 Fotosel I2.0 Robot Hazır I3.0 ARIZA Q0.0 Bant Motoru Q1.0 Robot Çevrim Q1.2 Alarm Kornası M · T · C bellek · zaman · sayıcı 3B Saha physics(dt) konveyör · itici · tank Tomris robotları HMI / SCADA buton · lamba · alarm sürükle-bırak tasarımcı Q yaz I oku Q oku I yaz
Ladder motoru sahayı hiç tanımaz; saha da ladder'ı. İkisi yalnızca tag tablosu üzerinden konuşur.

Bu ayrımın pratik kazancı büyük oldu: yeni bir senaryo eklemek için tarama motoruna hiç dokunmadım. Trafik ışığı, dolum istasyonu ve arıza senaryosu — üçü de sadece yeni sahne kurucuları ve yeni tag'ler. Motor aynı kaldı.

Ladder Tarama Motoru

Bir PLC saniyede yüzlerce kez aynı şeyi yapar: girişleri oku → mantığı yukarıdan aşağı çöz → çıkışları yaz. Motorun tamamı bu döngüyü taklit eder.

Veri modeli kasten yalın tutuldu. Bir network (rung), paralel dalların listesi ve bir bobinden oluşur. Dallar arasında OR, dal içinde AND vardır — ladder mantığının tamamı budur:

// veri modeli
rung = {
  branches: [               // dallar arası: OR
    [ {k:'NO', tag:'I1.0'}, {k:'NC', tag:'I1.1'} ],   // dal içi: AND
    [ {k:'NO', tag:'M0.0'}, {k:'NC', tag:'I1.1'} ]    // ← mühürleme dalı
  ],
  coil: {k:'OUT', tag:'M0.0'}
}

Yukarıdaki tek network, elektrik panolarındaki klasik seal-in (mühürleme) devresidir: START'a basınca birinci dal iletir, bobin çeker; parmağını çekince ikinci dal (bobinin kendi kontağı) devreyi ayakta tutar. STOP her iki dalı da keser.

Tarama fonksiyonunun özü:

// plcScan — her karede çalışır
for (const rung of program) {
  let rungPow = false;
  for (const br of rung.branches) {
    let pow = true;
    for (const el of br) {
      const v = tagVal(el.tag);
      if (el.k === 'NO')  pow = pow && v;
      if (el.k === 'NC')  pow = pow && !v;
      if (el.k === 'P') {          // yükselen kenar
        const fire = v && !el._pv;  // yalnızca 0→1 anında iletir
        el._pv = v;
        pow = pow && fire;
      }
      // TON / TOF: dt burada tüketilir
    }
    rungPow = rungPow || pow;   // dallar OR'lanır
  }
  writeCoil(rung.coil, rungPow);
}
Neden Kenar Kontağı Şart?

Bir kutu fotoseli 0,4 saniye kesiyorsa ve tarama 20 ms'de bir dönüyorsa, düz bir kontakla sayaç 20 kez artar. Öğrencinin bunu kendi gözüyle görmesi, "P kontağı"nı anlatan on slayttan daha etkili. Görev 2 tam olarak bu tuzağın üzerine kuruldu.

Bulduğum Hata: Bobin Ne Zaman Yazılır?

Bu, projenin en öğretici anıydı — ve simülatörün doğruluğunu doğrudan ilgilendiren gerçek bir mühendislik hatasıydı.

İlk yazdığım motorda OUT bobinleri bir ara tabloda biriktiriliyor, tarama bittikten sonra tag tablosuna işleniyordu. "Zaten aynı kare içinde, ne fark eder" diye düşünmüştüm. Ta ki çift robot senaryosunu test edene kadar.

Görev 8'de iki robot aynı paleti paylaşıyor. Çarpışmayı önlemek için klasik interlock yazılır — ikinci robotun başlama şartına "birincinin komutu aktif DEĞİL" koşulu eklenir:

// Network 6
I0.3 · I2.0 · /I2.3  ──────────( Q1.0 )   // Tomris-1 çevrim başlat

// Network 7
I0.3 · I2.2 · /I2.1 · /Q1.0  ───( Q1.1 )   // Tomris-2 — Q1.0 kilidi
⚠ Hata

Testte iki robot da aynı anda başladı. Sebep: Network 7, /Q1.0 kontağını okurken Q1.0 henüz tag tablosuna yazılmamıştı — bir önceki taramanın değerini (false) görüyordu. Kilit hiçbir zaman tutmadı.

Gerçek bir PLC'de durum böyle değildir. Süreç görüntüsü (process image) girişte bir kez okunur ve çıkışta bir kez yazılır — ama program içinde bir bobin çektiği anda o bit güncellenir ve programın devamındaki kontaklar yeni değeri görür. Ladder'ın yukarıdan aşağı, sıralı yürütülmesinin bütün anlamı budur. Benim motorum bu semantiği ihlal ediyordu.

✓ Çözüm

Bobinleri yazıldıkları anda tag tablosuna işledim. Aynı tag'e birden fazla OUT bobini konursa (çift bobin hatası) bunları OR'layarak yeni başlayanlara tolerans bıraktım — ama sıralı görünürlük artık gerçek PLC ile birebir aynı.

const written = {};                       // bu taramada yazılan tag'ler

if (coil.k === 'OUT') {
  if (!written[coil.tag]) {
    TAGS[coil.tag].v = rungPow;           // ANINDA yaz →
    written[coil.tag] = true;             // sonraki network'ler görür
  } else {
    TAGS[coil.tag].v = TAGS[coil.tag].v || rungPow;  // çift bobin → OR
  }
}

Bu düzeltmeden sonra interlock ilk denemede çalıştı ve iki robot T-1 → T-2 → T-1 → T-2 düzeninde sırayla devretmeye başladı. Aynı testte ikinci bir hata da yakalandı: trafik ışığı sekansı zinciri sıfırlarken bir tarama boyunca tüm lambaları söndürüyordu. Kırmızı bobinine paralel bir dal eklenerek giderildi.

Saha: Fizik ve Sensör Modeli

Saha katmanı, tag tablosundaki Q bitlerini okur; kutuları hareket ettirir; sonucu I bitleri olarak geri yazar. Arada başka hiçbir bağ yoktur.

Sensör modeli kasten fiziksel tutuldu — "kutu şu koordinata gelince bit 1 olsun" değil, "ışın demeti kesildi mi?" sorusu soruluyor:

// Fotosel: ışın demetinin üzerinde bir kutu var mı?
TAGS['I0.0'].v = boxes.some(b => aktif(b) && yakin(b.x, gateX, 0.34));

// Yükseklik fotoseli: yalnızca UZUN kutular demeti keser
TAGS['I0.1'].v = boxes.some(b => aktif(b) && b.tall && yakin(b.x, gateX, 0.34));

Bu yaklaşımın bedeli var — her karede kutu listesi taranıyor — ama kazancı çok daha büyük: sinyal süresi kutunun hızına ve boyuna bağlı hale geliyor. Bant hızlanınca sinyal kısalıyor, kenar tetikleme problemi kendiliğinden doğuyor. Bu, yapay olarak üretilemeyecek bir öğrenme fırsatı.

Senaryo çeşitliliği

SenaryoÖğrettiği kavramCeza mekanizması
MühürlemeSeal-in, paralel dal, latch
SayıcıP kenarı, CTU, parti kontrolü5. kutu geçerse hata
Robot el sıkışmaHazır/meşgul/başlat protokolü
Ayırma hücresiBellek biti, TON, entegrasyonYanlış kasa = hata
Trafik ışığıZincirleme TON sekansıKırmızıda geçiş = hata
Dolum istasyonuSeviye kontrolü, chattering önlemeTank taşması = hata
Arıza yönetimiAlarm biti, güvenli duruş3 sn içinde durmazsan ihlal
Çift robotInterlock, sıra bitiÇarpışma = hata
Tasarım Notu

Her senaryoda bir ceza mekanizması olması tesadüf değil. Hatasız çalışan bir simülasyon, öğrenciye yanlış yaptığını asla söylemez. Kutunun yanlış kasaya düştüğünü görmek, kırmızı bir hata mesajından çok daha kalıcıdır.

Tomris: Kinematik ve Hareket Kalitesi

Robot ilk versiyonda "çalışıyordu" ama bir çizgi filmden fırlamış gibiydi. Üç müdahale onu inandırıcı kıldı.

1 · Bilek telafisi

İlk sürümde kol eğildikçe tutucu da eğiliyordu — gerçek bir 6 eksen robotta bu asla olmaz, çünkü bilek eksenleri uç efektörü sabit tutar. Tek satırlık bir telafi, sahnenin inandırıcılığını tamamen değiştirdi:

// tutucu, omuz ve dirsek ne yaparsa yapsın dik kalır
wrist.rotation.z = pose.sh - pose.el;

2 · Hareket profili

Lineer interpolasyon, servo motorların yapamayacağı ani hız değişimleri üretiyordu. Quintic smootherstep ile ivme profili yumuşatıldı — hareket yavaş başlıyor, hızlanıyor, hedefe yaklaşırken yavaşlıyor:

const smooth = k => k*k*k*(k*(k*6 - 15) + 10);
// birinci VE ikinci türevi uçlarda sıfır → sıçramasız ivme

3 · Boşta nefes alma

Bekleyen robot tamamen donuk duruyordu. Servo tutuş salınımını taklit eden çok küçük bir sinüs hareketi (~0,02 radyan) eklendi. Fark eden olmuyor — ama olmadığında herkes fark ediyor.

Jog ve Teach — smartPAD Deneyimi

Sonradan eklenen Jog modu, robotu eksen eksen elle sürmeyi sağlıyor ve o sırada PLC komutlarını bloke ediyor (I2.0 = 0) — gerçek bir robot manuel moddayken tam olarak böyle davranır. Teach modu ise elle konumlandırılan noktaları kaydedip PTP bir program olarak oynatıyor. Endüstriyel robot programlamanın temeli budur ve tarayıcıda deneyimlenebilir hale geldi.

Test: Örnek Çözümleri Gerçekten Çalıştırmak

Bir eğitim aracının en utanç verici hatası, "örnek çözüm" düğmesinin çalışmayan bir program yüklemesidir. Bunu garanti altına almanın tek yolu var: örnekleri test etmek.

Ladder motorunu 3B sahneden bağımsız olarak Node.js altında başsız (headless) çalıştıran bir test koşucusu yazdım. Her görevin örnek çözümü yükleniyor, sanal saniyeler ilerletiliyor ve çıkışlar doğrulanıyor:

// test çıktısı
=== GÖREV 5 TRAFİK SEKANS TESTİ ===
   0.0s -> K          // kırmızı
   4.9s -> Y          // yeşil
  10.0s -> S          // sarı
  11.9s -> K          // döngü kapandı, karanlık kare yok ✓

=== GÖREV 7 ARIZA TESTİ ===
  Normal çalışma  -> Q0.0(bant): true  | Q1.2(korna): false
  ARIZA geldi     -> Q0.0(bant): false | Q1.2(korna): true
  Sonuç: GÜVENLİ DURUŞ ✓

=== GÖREV 8 SIRALI DEVRETME TESTİ ===
  Çevrim sırası: T-1 → T-2 → T-1 → T-2 → T-1 → T-2
  Sonuç: Robotlar sırayla çalışıyor, çarpışma yok ✓

Yukarıda anlattığım bobin yazma hatası da, trafik ışığındaki karanlık kare de bu test tarafından yakalandı — ikisi de gözle bakarak fark edilebilecek şeyler değildi. Test yazmasaydım simülatör "çalışıyor gibi" görünecek, ama yanlış bir PLC modeli öğretecekti.

Buradan Çıkan Ders

Otomasyonda devreye alma (commissioning) da böyledir: sistem "dönüyor" diye doğru çalıştığı sonucu çıkmaz. Kenar durumlarını kasten tetikleyip beklenen çıkışı doğrulamak gerekir. Simülatörü test etme biçimim, bir sahayı devreye alma biçimimin aynısıydı.

Sonuç

9Görev
11Ladder komutu
2Yakalanan hata
0Kurulum adımı

Ortaya çıkan şey, tek bir HTML dosyasında çalışan; ladder yazabildiğin, HMI ekranı tasarlayabildiğin, robotu elle sürüp nokta öğretebildiğin ve OEE'sini izleyebildiğin bir eğitim hücresi oldu. Hiçbir kurulum, lisans veya işletim sistemi bağımlılığı yok.

Kullanılan teknolojiler

Ladder Logic PLC Tarama Döngüsü Three.js WebGL Vanilla JS Robot Kinematiği HMI / SCADA Web Audio API Node.js (test) localStorage

Bundan sonrası

Neden Bu Proje?

Otomasyon alanına dönerken, "TIA Portal biliyorum" demenin kanıtı bir sertifika değil, çalışan bir sistem olmalı diye düşündüm. Bu simülatör bir PLC'nin nasıl düşündüğünü baştan inşa etmemi gerektirdi — tarama döngüsü, süreç görüntüsü, kenar tetikleme, el sıkışma protokolleri, güvenli duruş. Bir aracı kullanmayı öğrenmenin en iyi yolu, o aracın bir benzerini yazmakmış.

Simülatörü kendin dene

Kurulum yok, kayıt yok. Tarayıcında açılır, ilerlemen kaydedilir.