İçeriğe geç

Teknik Yazılar

Gömülü Sistemler··7 dk okuma

Companion Computer Nedir? MAVLink ile Uçuş Kontrolcüsüne Bağlanmak

Tespit modeliniz hedefi buldu — peki bu bilgiyi uçuş kontrolcüsüne nasıl söyleyeceksiniz? UART kablosundan GUIDED mod komutuna kadar tüm zincir.

Jetson üzerinde YOLO modeliniz çalışıyor, kameradan gelen karede hedefi 15-20 FPS ile buluyorsunuz. Sonra şu soruyla karşılaşıyorsunuz: bu tespiti uçuş kontrolcüsüne nasıl bildireceğiz? Aracın o koordinata gitmesini, orada bekleyip yükü bırakmasını kim söyleyecek?

Bizim ilk refleksimiz “her şeyi uçuş kontrolcüsüne yükleyelim” olmuştu. Pixhawk sınıfı bir kartın üzerindeki mikrodenetleyici saniyede yüzlerce kez IMU okuyup motor çıkışı hesaplamakla meşgul. Oraya bir sinir ağı koymak mümkün değil; koysanız bile kontrol döngüsünü bozarsınız.

Çözüm ikiye bölmek: gerçek zamanlı kontrol uçuş kontrolcüsünde (FC — flight controller) kalır, ağır hesaplama yanına takılan ikinci bir bilgisayara taşınır. Bu ikinci bilgisayara companion computer (eşlikçi bilgisayar) deniyor; ArduPilot bunu MAVLink protokolüyle FC’ye bağlanıp uçuş sırasında karar veren bir birim olarak tanımlıyor. Bu yazıda o iki kutuyu bağlayan kabloyu, protokolü ve çalışan kodu anlatıyoruz.

Görev bölüşümü neden bu şekilde?

Uçuş kontrolcüsünün işi katı gerçek zamanlıdır: stabilizasyon döngüsü kaçırılmaz, işletim sistemi bile yoktur (ArduPilot ChibiOS üzerinde koşar). Companion computer’ın işi ise “yumuşak” gerçek zamanlıdır — bir kareyi geç işlemek görevi geciktirir ama aracı düşürmez. Bu ayrım aynı zamanda bir güvenlik sınırıdır: companion çökse bile FC uçmaya devam eder, failsafe devreye girer ve araç RTL (return to launch — kalkış noktasına dönüş) yapar. Tersi doğru değildir.

Tipik companion görevleri: nesne tespiti, gimbal kontrolü, harita üretimi, yer istasyonuna video aktarımı, görev mantığının üst seviyesi. Bizim SUAS 2026 aracımızda Jetson Orin NX 16GB bu rolde: SIYI A8 mini kameradan RTSP akışını alıyor, YOLO11m ile tespit yapıyor ve sonucu MAVLink üzerinden FC’ye konum komutu olarak çeviriyor.

Fiziksel bağlantı: UART, USB, Ethernet

Üç seçenek var ve çoğu takım için doğru cevap birincisi.

Yöntem Ayar Ne zaman tercih edilir
UART (TELEM1/TELEM2) SERIAL2_PROTOCOL=2, SERIAL2_BAUD=921 Varsayılan seçim. Sadece 3 kablo (TX, RX, GND), FC yeniden başlasa da bağlantı adı değişmez
USB (SERIAL0) Kabloyu tak, /dev/ttyACM0 Tezgah üstü test ve parametre yazma. Uçuşta kullanmayın: konnektör titreşimde çıkar, port adı numaralanma sırasına göre kayar
Ethernet NET_ENABLE=1 + NET_P1_* parametreleri ArduPilot 4.5 ve üzeri, Ethernet MAC’i olan kartlarda (Pixhawk6X, CubeRed). ROS 2/DDS ve yüksek veri hacmi için

ArduPilot’ta seri portlar SERIAL0 = USB, SERIAL1 = TELEM1, SERIAL2 = TELEM2, SERIAL3 = GPS şeklinde eşlenir. SERIALx_PROTOCOL değeri 1 ise MAVLink1, 2 ise MAVLink2 konuşulur — companion için 2 kullanın. SERIALx_BAUD parametresi baud’un binde biridir: 57=57600, 115=115200, 921=921600.

Kablolama çaprazdır: FC TX → companion RX, FC RX → companion TX, GND → GND. Pixhawk UART’ları 3.3 V mantık seviyesindedir; 5 V TTL bir kartı doğrudan bağlamayın ve FC’yi companion’ın USB’sinden beslemeyin.

Linux tarafında portun seri konsol olarak kullanılmadığından emin olun. Raspberry Pi’de raspi-config içinden “login shell over serial” kapatılır, donanım UART’ı açık bırakılır; port /dev/serial0 olur. Jetson’da genellikle /dev/ttyTHS1’dir ve nvgetty servisinin kapalı olması gerekir.

MAVLink’i üç kavramda anlamak

Mesaj. MAVLink paket tabanlıdır. Her paket bir System ID, Component ID, Message ID ve veri alanları taşır; MAVLink1’de en fazla 263, MAVLink2’de 280 bayttır. Araç varsayılan olarak system ID 1’dir (MAV_SYSID), yer istasyonları geleneksel olarak 255 kullanır.

Heartbeat. Bağlantı açıldığında her taraf 1 Hz hızında HEARTBEAT (mesaj ID 0) yollar. Bu mesaj hem “hayattayım” sinyalidir hem de aracın uçuş modunu (custom_mode) ve arm durumunu taşır. Kod yazarken ilk yapacağınız şey heartbeat beklemektir, çünkü hedef sistem numarasını oradan öğrenirsiniz.

Stream rate. FC her mesajı kendiliğinden yollamaz; hangisini kaç Hz istediğinizi söylemeniz gerekir. Eski yöntem REQUEST_DATA_STREAM ile mesaj gruplarını istemektir. ArduPilot 4.0 ve üzerinde tercih edilen yöntem MAV_CMD_SET_MESSAGE_INTERVAL (komut ID 511) ile tek tek mesaj istemektir — aralık mikrosaniye cinsindendir, 10 Hz için 100000 yazarsınız.

Sık kullanılan mesajlar ve birimleri:

Mesaj ID İçerik Birim tuzağı
HEARTBEAT 0 Mod, arm durumu custom_mode araç tipine özgüdür
GLOBAL_POSITION_INT 33 Konum, irtifa, hız lat/lon degE7, irtifa mm, hız cm/s
ATTITUDE 30 roll/pitch/yaw radyan, derece değil
SYS_STATUS 1 Batarya, sensör sağlığı gerilim mV, akım cA

pip install pymavlink ile kurulur. Aşağıdaki blok tek başına çalışır; UDP satırını yorumdan çıkarırsanız SITL simülasyonuna da bağlanır.

import time
from pymavlink import mavutil

# Gerçek donanım: Jetson UART'ı üzerinden TELEM2'ye
master = mavutil.mavlink_connection('/dev/ttyTHS1', baud=921600)
# SITL / mavlink-router çıkışı için alternatif:
# master = mavutil.mavlink_connection('udpin:0.0.0.0:14550')

# İlk HEARTBEAT gelene kadar bekle. target_system/target_component burada dolar.
master.wait_heartbeat()
print(f"Bağlandı -> system: {master.target_system}, "
      f"component: {master.target_component}")

def set_message_rate(msg_id, hz):
    """Tek bir mesajın gönderim hızını ayarla (ArduPilot 4.0+)."""
    master.mav.command_long_send(
        master.target_system, master.target_component,
        mavutil.mavlink.MAV_CMD_SET_MESSAGE_INTERVAL,  # 511
        0,                    # confirmation
        msg_id,               # param1: mesaj ID
        int(1_000_000 / hz),  # param2: aralık, MİKROSANİYE
        0, 0, 0, 0, 0)

set_message_rate(mavutil.mavlink.MAVLINK_MSG_ID_GLOBAL_POSITION_INT, 10)
set_message_rate(mavutil.mavlink.MAVLINK_MSG_ID_ATTITUDE, 20)

t_end = time.time() + 10
while time.time() < t_end:
    msg = master.recv_match(type='GLOBAL_POSITION_INT',
                            blocking=True, timeout=2)
    if msg is None:
        print("Telemetri gelmiyor: baud/port/PROTOCOL ayarlarını kontrol et")
        continue
    print(f"lat={msg.lat / 1e7:.7f} lon={msg.lon / 1e7:.7f} "
          f"rel_alt={msg.relative_alt / 1000.0:.1f} m "
          f"vz={msg.vz / 100.0:+.2f} m/s")

recv_match bloklayıcıdır; ana görev döngünüzü bloklamak istemiyorsanız telemetriyi ayrı bir thread’de okuyup paylaşılan bir sözlüğe yazın. Biz Jetson’da tam olarak bunu yapıyoruz: tespit döngüsü ile MAVLink döngüsü ayrı thread’lerde koşuyor.

Komut göndermek: mod değiştirme ve GUIDED ile konuma gitme

GUIDED, aracın dışarıdan gelen konum/hız hedeflerini takip ettiği moddur. ArduPilot dokümantasyonu bu modun companion computer ve Lua betikleriyle kullanılabileceğini açıkça belirtir. Ön koşul GPS kilidi ve — dikey eksende hedef veriyorsanız — aracın havada olmasıdır.

POS_ONLY = 0b110111111000  # sadece lat/lon/alt kullan; hız, ivme, yaw yok sayılsın

def set_mode(name, timeout=5):
    """Mod değiştir ve HEARTBEAT'ten doğrula."""
    mode_id = master.mode_mapping()[name]   # ör. 'GUIDED'
    master.mav.set_mode_send(
        master.target_system,
        mavutil.mavlink.MAV_MODE_FLAG_CUSTOM_MODE_ENABLED,
        mode_id)
    t_end = time.time() + timeout
    while time.time() < t_end:
        hb = master.recv_match(type='HEARTBEAT', blocking=True, timeout=1)
        if hb and hb.custom_mode == mode_id:
            return True
    return False

def arm(timeout=10):
    master.mav.command_long_send(
        master.target_system, master.target_component,
        mavutil.mavlink.MAV_CMD_COMPONENT_ARM_DISARM,  # 400
        0, 1, 0, 0, 0, 0, 0, 0)                        # param1=1 -> arm
    ack = master.recv_match(type='COMMAND_ACK', blocking=True, timeout=timeout)
    return ack is not None and ack.result == mavutil.mavlink.MAV_RESULT_ACCEPTED

def goto(lat_deg, lon_deg, rel_alt_m):
    """Kalkış noktasına göreli irtifada bir GPS hedefine git."""
    master.mav.set_position_target_global_int_send(
        0,                                   # time_boot_ms (kullanılmıyor)
        master.target_system, master.target_component,
        mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT,
        POS_ONLY,
        int(lat_deg * 1e7), int(lon_deg * 1e7), float(rel_alt_m),
        0, 0, 0,        # vx, vy, vz   (yok sayılıyor)
        0, 0, 0,        # afx, afy, afz (ArduPilot bunları zaten yok sayar)
        0, 0)           # yaw, yaw_rate

if set_mode('GUIDED') and arm():
    goto(39.9250000, 32.8360000, 25.0)   # hedefi kendi sahanızla değiştirin
else:
    print("Mod veya arm reddedildi — arm ön koşullarını kontrol et")

type_mask hangi alanların dikkate alınacağını belirler: bit 0 ise kullanılır, 1 ise yok sayılır. 0b110111111000 “sadece konum” demektir. Bu maskeyi yanlış kurmak en sık yaşanan sessiz hatadır — araç irtifayı değiştirir ama yatayda kıpırdamaz.

Konum hedefi araç oraya varana kadar geçerlidir. Hız veya ivme hedefi gönderiyorsanız GUID_TIMEOUT (varsayılan 3 saniye) içinde yenisini göndermelisiniz, aksi halde araç yavaşlayıp durur. Yatay hızı WP_SPD sınırlar (varsayılan 10 m/s).

MAVROS ve ROS 2 alternatifi

pymavlink tek başına yeterli değilse ROS katmanı devreye girer.

Yaklaşım Ne zaman seçilir
pymavlink Tek bir Python süreci, birkaç mesaj tipi, hızlı prototip. Bağımlılık yükü en düşük seçenek
MAVROS Zaten ROS ekosistemindesiniz; hazır node’lar, TF ağacı ve mesaj dönüşümleri işinize yarıyor. MAVLink köprüsü üzerinden çalışır
AP_DDS (native DDS) ArduPilot 4.5 ve üzeri; ROS 2 ile XRCE-DDS üzerinden doğrudan konuşur, aradaki köprü kalkar. Ethernet bağlantısıyla birlikte tercih edilir

ROS 1’in ömrü 2025’te dolduğu için yeni projelerde ROS 2 tarafına bakmak mantıklı (2026 itibarıyla). Biz simülasyonda Gazebo + SITL + ROS 2 kullanıyoruz, ancak uçuş yazılımının kritik yolunda hâlâ doğrudan pymavlink var: daha az katman, daha az sürpriz.

Sık düşülen tuzaklar

  • Seri konsolu kapatmamak. Linux /dev/serial0 üzerinde login shell çalıştırıyorsa MAVLink verisinin içine boot mesajları karışır ve hiçbir paket parse edilmez.
  • Tek portu iki uygulamayla açmak. Bir seri portu aynı anda yalnızca bir süreç açabilir. Hem kendi kodunuz hem MAVProxy/yer istasyonu bağlanacaksa mavlink-router kurun ve portu birden fazla UDP endpoint’e dağıtın.
  • wait_heartbeat() çağırmadan komut göndermek. master.target_system o ana kadar 0’dır; komutlarınız hiçbir yere gitmez, hata da almazsınız.
  • Birim karıştırmak. Enlem/boylam degE7 (10⁷ ile çarpılmış tam sayı), irtifa mm, hız cm/s, açı radyan, SET_MESSAGE_INTERVAL mikrosaniye. Bu dördü karıştırıldığında ortaya çıkan hata sessizdir.
  • Stream rate’i sonuna kadar açmak. 57600 bps’lik bir telemetri linkinde her mesajı 50 Hz istemek bandı doldurur ve paket kaybına yol açar. Sadece ihtiyacınız olan mesajları, ihtiyacınız olan hızda isteyin.
  • Companion’a yer istasyonuyla aynı system ID’yi vermek. Companion’ın heartbeat’i GCS failsafe’ini “hâlâ bağlıyım” sanarak susturabilir; gerçekte yer istasyonu bağlantısı kopmuş olsa bile failsafe tetiklenmez.

Güvenlik notları

Arm komutu FC tarafından reddedilebilir; bu bir hata değil, koruma katmanıdır. ARMING_CHECK denetimleri GPS kalitesi, EKF tutarlılığı, pusula sağlığı ve batarya gibi başlıkları kapsar. Kodunuz COMMAND_ACK sonucunu kontrol etmeli, reddedildiğinde görevi başlatmamalıdır.

GCS failsafe’ini companion mimarisinde ayrıca düşünün: FS_GCS_ENABLE açıkken FC, birincil yer istasyonundan (MAV_GCS_SYSID ile belirlenen sistem) gelen heartbeat’i izler ve FS_GCS_TIMEOUT süresi (varsayılan 5 saniye) boyunca sessizlik olursa failsafe tetikler. Companion’ınıza farklı bir system ID verin ki bu koruma çalışmaya devam etsin.

Son olarak, pilotun mod anahtarı her zaman companion’ın komutlarını geçersiz kılabilmelidir. Bizim ~20 m irtifadan iki hedefi tespit edip otonom bırakma testimizin arkasında haftalarca süren SITL tekrarları var; sahada ilk kez çalıştırılan kod diye bir şey olmadı.

Pratik özet

  1. TELEM2’yi kullanın: SERIAL2_PROTOCOL=2, SERIAL2_BAUD=921, kablolama TX↔RX çapraz, ortak GND. Uçuşta USB’ye güvenmeyin.
  2. Her bağlantıda wait_heartbeat() çağırın, sonra MAV_CMD_SET_MESSAGE_INTERVAL ile yalnızca ihtiyacınız olan mesajları isteyin.
  3. Komutları doğrulayın: mod değişimini HEARTBEAT.custom_mode ile, arm ve diğer komutları COMMAND_ACK.result ile teyit edin. “Gönderdim” ile “kabul edildi” aynı şey değildir.
  4. type_mask ve birimleri iki kez kontrol edin — degE7, mm, cm/s, radyan, mikrosaniye.
  5. Failsafe’i companion’a rağmen çalışır bırakın: ayrı system ID kullanın, pilot müdahalesini engellemeyin, sahaya çıkmadan önce senaryoyu SITL’de tekrarlayın.

Kaynaklar: ArduPilot dev/copter dokümantasyonu (Companion Computers, Telemetry Port Setup, MAVLink Basics, Requesting Data, Guided Mode, Network Setup) ve mavlink.io common mesaj tanımları.

  • companion-computer
  • mavlink
  • pymavlink
  • ardupilot
  • jetson

Zor bir mühendislik probleminiz mi var?

Yapılabilir mi, ne kadar sürer, nasıl kurulur: konuşarak başlayalım.

Projenizi Konuşalım