GÖREV KAYDI

GÖREV KAYDI

KAYIT AÇIK

ATILIM UAV · YER KONTROL İSTASYONU

GÖREV KAYDI / DETAY

Arşiv

CANLI · OKUMA MODU

Gömülü Sistemler

Companion Computer Nedir? Uçuş Kontrolcünüzle MAVLink Üzerinden Konuşmak

Companion Computer Nedir? Uçuş Kontrolcünüzle MAVLink Üzerinden Konuşmak

ŞEK. 01

KAYIT KÜNYESİ

Kategori

Gömülü Sistemler

TARİH

TÜM KAYITLAR

YOLO modeliniz Jetson üzerinde çalışıyor ve hedefi kamera karesinde 15-20 FPS ile buluyor. Hemen ardından bir sonraki soru geliyor: bu tespiti uçuş kontrolcüsüne nasıl ulaştıracağız? Uçağa o koordinatlara gitmesini, orada beklemesini ve yükü bırakmasını kim söyleyecek?

İlk refleksimiz "her şeyi uçuş kontrolcüsüne koyalım" olmuştu. Pixhawk sınıfı bir karttaki mikrodenetleyici, saniyede yüzlerce kez IMU okuyup motor çıkışlarını hesaplamakla zaten meşgul. Orada bir sinir ağı çalıştırmak mümkün değil; başarsanız bile kontrol döngüsünü mahvedersiniz.

Çözüm işi ikiye bölmek: gerçek zamanlı kontrol uçuş kontrolcüsünde (FC) kalır, ağır hesaplama ise onun yanına monte edilen ikinci bir bilgisayara taşınır. Bu ikinci bilgisayarın adı companion computer (yardımcı bilgisayar); ArduPilot bunu, FC'ye MAVLink protokolü üzerinden bağlanan ve uçuş sırasında karar veren bir birim olarak tanımlıyor. Bu yazıda o iki kutu arasındaki kabloyu, üzerinden akan protokolü ve gerçekten çalışan kodu ele alıyoruz.

İş neden böyle bölünüyor?

Uçuş kontrolcüsünün işi katı gerçek zamanlıdır: stabilizasyon döngüsü asla kaçırılmaz ve ortada bir 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 uçağı düşürmez. Bu ayrım aynı zamanda bir güvenlik sınırıdır: companion çökerse FC uçmaya devam eder, failsafe (güvenli-mod) devreye girer ve uçak RTL (kalkış noktasına dönüş) uygular. Tersi geçerli değildir.

Tipik companion işleri: nesne tespiti, gimbal kontrolü, haritalama, yer istasyonuna video akışı ve üst seviye görev mantığı. SUAS 2026 uçağımızda bu rolü Jetson Orin NX 16GB üstleniyor: SIYI A8 mini kameradan RTSP akışını çekiyor, YOLO11m ile tespit yapıyor ve sonucu MAVLink üzerinden FC'ye bir konum komutuna ç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 kullanılır

UART (TELEM1/TELEM2)

SERIAL2_PROTOCOL=2, SERIAL2_BAUD=921

Varsayılan tercih. Yalnızca 3 kablo (TX, RX, GND) ve FC yeniden başladığında port adı değişmiyor

USB (SERIAL0)

Kabloyu tak, /dev/ttyACM0

Tezgâh testi ve parametre yazma. Uçuşta kullanmayın: konnektör titreşimde yerinden çıkar ve port adı numaralandırma sırasına göre kayar

Ethernet

NET_ENABLE=1 ile NET_P1_* parametreleri

ArduPilot 4.5 ve sonrası, Ethernet MAC'i olan kartlarda (Pixhawk6X, CubeRed). ROS 2/DDS ve yüksek veri hacimleri için

ArduPilot'ta seri portlar şöyle eşlenir: SERIAL0 = USB, SERIAL1 = TELEM1, SERIAL2 = TELEM2, SERIAL3 = GPS. Bir SERIALx_PROTOCOL değeri 1 ise MAVLink1, 2 ise MAVLink2 konuşulur. Companion için 2 kullanın. SERIALx_BAUD parametresi ise baud hızının bine bölünmüş hâlidir: 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 lojik seviyede çalışır; 5 V TTL bir kartı doğrudan bunlara 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 "login shell over serial" seçeneğini raspi-config içinden kapatın, donanım UART'ını açık bırakın; port o zaman /dev/serial0 olarak görünür. Jetson'da genelde /dev/ttyTHS1 olur ve nvgetty servisinin devre dışı bırakılması gerekir.

Üç kavramda MAVLink

Mesajlar. MAVLink paket tabanlıdır. Her paket bir System ID, bir Component ID, bir Message ID ve veri alanlarını taşır; azami boyut MAVLink1'de 263 bayt, MAVLink2'de 280 bayttır. Araç varsayılan olarak system ID 1 kullanır (MAV_SYSID), yer istasyonları için ise geleneksel olarak 255 kullanılır.

Heartbeat. Bağlantı kurulduktan sonra her iki taraf da 1 Hz'de bir HEARTBEAT (mesaj ID 0) gönderir. Bu hem "hayattayım" sinyalidir hem de aracın uçuş modunu (custom_mode) ve arm durumunu taşır. Kodda yapacağınız ilk şey bir heartbeat beklemektir, çünkü hedef sistem numarasını oradan öğrenirsiniz.

Akış hızları. FC her mesajı kendiliğinden göndermez; hangilerini hangi hızda istediğinizi söylemeniz gerekir. Eski yaklaşım, mesaj gruplarını REQUEST_DATA_STREAM ile istemektir. ArduPilot 4.0 ve sonrasında tercih edilen yaklaşım ise mesajları tek tek MAV_CMD_SET_MESSAGE_INTERVAL (komut ID 511) ile istemektir: aralık mikrosaniye cinsindendir, yani 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

pymavlink ile bağlanmak ve telemetri okumak

Kurulumu pip install pymavlink ile yapılır. Aşağıdaki blok tek başına çalışır; UDP satırının yorumunu kaldırırsanız bunun yerine bir SITL simülasyonuna bağlanır.

import time
from pymavlink import mavutil

# Real hardware: Jetson UART to TELEM2
master = mavutil.mavlink_connection('/dev/ttyTHS1', baud=921600)
# Alternative for SITL / mavlink-router output:
# master = mavutil.mavlink_connection('udpin:0.0.0.0:14550')

# Wait for the first HEARTBEAT. target_system/target_component get filled in here.
master.wait_heartbeat()
print(f"Connected -> system: {master.target_system}, "
      f"component: {master.target_component}")

def set_message_rate(msg_id, hz):
    """Set the send rate of a single message (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: message ID
        int(1_000_000 / hz),  # param2: interval, MICROSECONDS
        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("No telemetry: check the baud rate, port and PROTOCOL settings")
        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")
import time
from pymavlink import mavutil

# Real hardware: Jetson UART to TELEM2
master = mavutil.mavlink_connection('/dev/ttyTHS1', baud=921600)
# Alternative for SITL / mavlink-router output:
# master = mavutil.mavlink_connection('udpin:0.0.0.0:14550')

# Wait for the first HEARTBEAT. target_system/target_component get filled in here.
master.wait_heartbeat()
print(f"Connected -> system: {master.target_system}, "
      f"component: {master.target_component}")

def set_message_rate(msg_id, hz):
    """Set the send rate of a single message (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: message ID
        int(1_000_000 / hz),  # param2: interval, MICROSECONDS
        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("No telemetry: check the baud rate, port and PROTOCOL settings")
        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ü tıkamasını istemiyorsanız telemetriyi ayrı bir thread'de okuyup paylaşılan bir sözlüğe yazın. Jetson'da tam olarak bunu yapıyoruz: tespit döngüsü ile MAVLink döngüsü ayrı thread'lerde çalışıyor.

Komut göndermek: mod değiştirmek ve GUIDED ile bir konuma uçmak

GUIDED, aracın dışarıdan verilen konum ve hız hedeflerini takip ettiği moddur. ArduPilot belgeleri bu modun bir companion computer veya Lua betikleriyle kullanılabileceğini açıkça belirtiyor. Ön koşullar: GPS kilidi ve dikey eksende hedef veriyorsanız aracın hâlihazırda havada olması.

POS_ONLY = 0b110111111000  # use lat/lon/alt only; ignore velocity, acceleration, yaw

def set_mode(name, timeout=5):
    """Change mode and confirm it from the HEARTBEAT."""
    mode_id = master.mode_mapping()[name]   # e.g. '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):
    """Fly to a GPS target at an altitude relative to the launch point."""
    master.mav.set_position_target_global_int_send(
        0,                                   # time_boot_ms (unused)
        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   (ignored)
        0, 0, 0,        # afx, afy, afz (ArduPilot ignores these anyway)
        0, 0)           # yaw, yaw_rate

if set_mode('GUIDED') and arm():
    goto(39.9250000, 32.8360000, 25.0)   # swap in coordinates for your own field
else:
    print("Mode change or arm rejected — check the arming prerequisites")
POS_ONLY = 0b110111111000  # use lat/lon/alt only; ignore velocity, acceleration, yaw

def set_mode(name, timeout=5):
    """Change mode and confirm it from the HEARTBEAT."""
    mode_id = master.mode_mapping()[name]   # e.g. '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):
    """Fly to a GPS target at an altitude relative to the launch point."""
    master.mav.set_position_target_global_int_send(
        0,                                   # time_boot_ms (unused)
        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   (ignored)
        0, 0, 0,        # afx, afy, afz (ArduPilot ignores these anyway)
        0, 0)           # yaw, yaw_rate

if set_mode('GUIDED') and arm():
    goto(39.9250000, 32.8360000, 25.0)   # swap in coordinates for your own field
else:
    print("Mode change or arm rejected — check the arming prerequisites")

type_mask hangi alanların dikkate alınacağını belirler: 0 olan bir bit alanın kullanıldığı, 1 olan bit ise yok sayıldığı anlamına gelir. 0b110111111000 "yalnızca konum" demektir. Bu maskeyi yanlış kurmak en sık görülen sessiz hatadır: araç irtifa değiştirir ama yatayda hiç kıpırdamaz.

Bir konum hedefi, araç oraya varana kadar geçerli kalır. Hız veya ivme hedefi gönderiyorsanız GUID_TIMEOUT (varsayılan 3 saniye) içinde yenisini göndermeniz gerekir; aksi hâlde araç yavaşlar ve durur. Yatay hız WP_SPD ile sınırlıdır (varsayılan 10 m/s).

MAVROS ve ROS 2 alternatifi

Tek başına pymavlink yetmediğinde devreye bir ROS katmanı giriyor.

Yaklaşım

Ne zaman tercih edilir

pymavlink

Tek bir Python süreci, birkaç mesaj tipi, hızlı prototipleme. Bağımlılık açısından en hafif seçenek

MAVROS

Zaten ROS ekosistemindesiniz ve hazır düğümler, TF ağacı ile mesaj dönüşümleri emeğinin karşılığını veriyor. Bir MAVLink köprüsü üzerinden çalışır

AP_DDS (yerleşik DDS)

ArduPilot 4.5 ve sonrası; arada köprü olmadan XRCE-DDS üzerinden doğrudan ROS 2 ile konuşur. En iyi Ethernet bağlantısıyla eşleşir

ROS 1 2025'te destek ömrünü tamamladı; dolayısıyla yeni projelerde (2026 itibarıyla) ROS 2 tarafına bakmak mantıklı. Simülasyonda Gazebo + SITL + ROS 2 kullanıyoruz, ama uçuş yazılımının kritik yolu hâlâ doğrudan pymavlink konuşuyor: daha az katman, daha az sürpriz.

Sık düşülen hatalar

  • Seri konsolu açık bırakmak. Linux /dev/serial0 üzerinde bir login shell çalıştırıyorsa açılış mesajları MAVLink verisine karışır ve tek bir paket bile ayrıştırılamaz.

  • Aynı portu iki uygulamadan açmak. Bir seri portu aynı anda yalnızca tek bir süreç tutabilir. Hem kendi kodunuzun hem de MAVProxy ya da bir yer kontrol istasyonunun bağlanması gerekiyorsa mavlink-router kurun ve portu birden fazla UDP uç noktasına dağıtın.

  • Komut göndermeden önce şunu çağırmayı unutmak: wait_heartbeat(). O ana kadar master.target_system 0'dır; komutlarınız hiçbir yere gitmez ve hata bile almazsınız.

  • Birimleri karıştırmak. Enlem/boylam degE7 (10⁷ ile ölçeklenmiş tam sayı), irtifa mm, hız cm/s, açılar radyan, SET_MESSAGE_INTERVAL ise mikrosaniye cinsindendir. Bunlardan birini karıştırırsanız ortaya çıkan hata sessizdir.

  • Akış hızlarını sonuna kadar açmak. 57600 bps'lik bir telemetri bağlantısında her mesajı 50 Hz'de istemek bant genişliğini doldurur ve paket kaybına yol açar. Yalnızca ihtiyacınız olan mesajları, gerçekten ihtiyaç duyduğunuz hızda isteyin.

  • Companion'a yer istasyonuyla aynı system ID'yi vermek. Companion'ın heartbeat'i, FC'nin bağlantıyı hâlâ ayakta sanmasına yol açarak GCS failsafe'ini susturabilir; yer istasyonu bağlantısı gerçekten kopsa bile failsafe hiç devreye girmez.

Güvenlik notları

FC bir arm komutunu reddedebilir; bu bir hata değil, bir koruma katmanıdır. ARMING_CHECK kontrolleri GPS kalitesi, EKF tutarlılığı, pusula sağlığı ve batarya durumu gibi alanları kapsar. Kodunuz COMMAND_ACK sonucunu incelemeli ve komut reddedildiğinde göreve başlamayı reddetmelidir.

Devrede bir companion varken GCS failsafe'ini ayrıca düşünün: FS_GCS_ENABLE açıkken FC, birincil yer istasyonundan (MAV_GCS_SYSID ile tanımlanan sistem) gelen heartbeat'i izler ve FS_GCS_TIMEOUT (varsayılan 5 saniye) boyunca sessizlik olursa failsafe'i tetikler. Bu korumanın çalışmaya devam etmesi için companion'ınıza farklı bir system ID verin.

Son olarak, pilotun mod anahtarı her zaman companion'ın komutlarını geçersiz kılabilmelidir. ~20 m'den iki hedefi tespit edip otonom bırakma testimizin arkasında haftalarca süren SITL tekrarları var; hiçbir kod parçası sahada ilk kez çalışmadı.

Pratik özet

  1. TELEM2 kullanın: SERIAL2_PROTOCOL=2, SERIAL2_BAUD=921, kablolama çapraz TX↔RX, ortak GND. Uçuşta USB'ye güvenmeyin.

  2. Her bağlantıda wait_heartbeat() çağırın, ardından yalnızca ihtiyacınız olan mesajları şununla isteyin: MAV_CMD_SET_MESSAGE_INTERVAL.

  3. Komutlarınızı doğrulayın: mod değişikliklerini HEARTBEAT.custom_mode ile, arm ve diğer komutları COMMAND_ACK.result ile doğrulayın. "Gönderdim" ile "kabul edildi" aynı şey değildir.

  4. Şunları iki kez kontrol edin: type_mask ve birimler — degE7, mm, cm/s, radyan, mikrosaniye.

  5. Companion'a rağmen failsafe'i çalışır tutun: ayrı bir system ID kullanın, pilot müdahalesini asla engellemeyin ve sahaya çıkmadan önce senaryoyu SITL'de prova edin.

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

©2026 ATILIM UAV TEAM

©2026 ATILIM UAV TEAM