PX4 MAVSDK – C++ Programming [9편] 정밀한 드론 제어: 오프보드(Offboard) 모드

안녕하세요! 마케팅팀의 에이든(Aiden)입니다. 지난 연재들을 통해 우리는 드론의 센서 데이터를 수신하고(Telemetry), 기본적인 이착륙을 수행하며(Action), 다수의 웨이포인트를 순회하는 자율 비행(Mission)까지 알아보았습니다.

하지만 카메라(Vision) 기반의 장애물 회피, 인공지능(AI) 표적 추적, 다수 기체의 군집 비행a(Swarm)과 같은 첨단 로보틱스 연구를 수행하려면, 정해진 경로를 따라가는 것만으로는 부족합니다. 드론의 두뇌 역할을 하는 컴패니언 컴퓨터(Companion Computer)가 밀리초(ms) 단위로 상황을 판단하고, 기체의 속도와 자세를 직접 제어해야 합니다.

이번 9편에서는 MAVSDK C++을 활용한 자율 비행 연구의 하이라이트, 오프보드(Offboard) 제어 모드의 완벽한 개념과 실전 적용 방법을 아주 상세하게 파헤쳐 보겠습니다.


오프보드(Offboard) 모드란 무엇인가?

오프보드(Offboard) 모드는 조종기의 스틱 입력이나 사전 입력된 미션 경로 대신, 외부의 보조 컴퓨터(Companion Computer, 예: Raspberry Pi, NVIDIA Jetson 등)가 직접 위치, 속도, 자세 등의 목표치(Setpoint)를 계산하여 비행 제어기(FCU)로 전송해 기체를 조종하는 방식입니다.

이 모드는 충돌 회피, 비주얼 서보잉(Visual Servoing)과 같이 컴퓨터 비전 알고리즘 연산 결과가 즉각적으로 비행 궤적에 반영되어야 하는 연구에 필수적으로 사용됩니다.

⚠️ 연구원을 위한 핵심: Failsafe 안전 메커니즘과 20Hz 자동 전송

오프보드 모드는 매우 강력하지만 그만큼 위험하기도 합니다. 만약 보조 컴퓨터의 프로그램이 멈추거나 통신 케이블이 끊어져서 명령이 전달되지 않으면 어떻게 될까요? 안전을 위해 PX4 오토파일럿은 일정 시간(보통 0.5초) 이상 새로운 제어 명령이 수신되지 않으면 즉시 ‘페일세이프(Failsafe)’ 모드로 전환되어 기체를 강제 정지시키거나 귀환(RTL)시킵니다.

이를 방지하기 위해 PX4는 최소 2Hz 이상의 주기로 설정값을 계속 보내줄 것을 요구합니다. 다행히도 MAVSDK C++의 Offboard 플러그인은 개발자의 편의를 위해 마지막으로 입력된 설정값을 내부적으로 초당 20번(20Hz)씩 자동 재전송해 줍니다. 따라서 우리는 알고리즘에 따라 방향을 바꿀 때만 새로운 목표치를 setpoint 메서드로 업데이트해 주면 됩니다.


제어의 기준점: NED 좌표계와 Body(차체) 좌표계

오프보드로 속도를 제어하기 위해서는 어떤 방향으로 움직일 것인지 좌표계를 명확히 해야 합니다. MAVSDK는 지구를 기준으로 하는 NED 프레임과, 기체 자체를 기준으로 하는 Body 프레임을 모두 제공합니다.

1) NED 프레임 (North-East-Down)

글로벌 나침반 방향을 기준으로 하는 절대 좌표계입니다.

  • North (북쪽): 양수(+)를 주면 북쪽으로, 음수(-)를 주면 남쪽으로 이동.
  • East (동쪽): 양수(+)를 주면 동쪽으로, 음수(-)를 주면 서쪽으로 이동.
  • Down (아래쪽): 양수(+)를 주면 하강하고, 음수(-)를 주면 상승합니다. (드론 제어에서 가장 많이 헷갈리는 부분이니 꼭 기억하세요!)

나침반 방향으로 일정하게 움직이거나, 특정 방향을 바라보게(Yaw) 할 때 유용합니다.

2) Body 프레임 (Forward-Right-Down)

차량(드론)의 현재 주행 방향을 기준으로 하는 상대 좌표계입니다.

  • Forward (앞): 기수 방향 전진(+), 후진(-)
  • Right (우): 기체 우측으로 이동(+), 좌측으로 이동(-)
  • Down (아래): NED와 동일하게 하강(+), 상승(-)

장애물이 나타나 “현재 가던 길에서 오른쪽으로 3m/s 회피해”와 같은 상대적인 제어나, 나선형 탐색(Spiral Search), 원주 비행(Circle) 등을 구현할 때 주로 사용됩니다.


Offboard 모드 시작을 위한 엄격한 규칙

오프보드 API를 사용할 때 가장 많이 겪는 오류는 “명령 거부(Command Denied)”입니다. PX4는 오프보드 모드를 시작(Start)하기 전에, 기체가 당장 실행할 초기 설정값(Setpoint)이 최소 1개 이상 미리 입력되어 있어야만 모드 전환을 허락합니다.

올바른 실행 흐름:

  1. 초기 설정값 생성 및 전송 (예: 제자리 정지 명령)
  2. offboard.start() 호출
  3. 원하는 제어(속도/위치/자세) 명령 전송
  4. 임무 종료 시 offboard.stop() 호출 (기체는 Hold 모드로 자동 전환됨)


실전 C++ 예제: 속도 및 자세 제어 완벽 구현

앞서 배운 개념을 종합하여, 이륙 후 (1) NED 좌표계 기반의 속도 제어, (2) Body 좌표계 기반의 회전 비행, (3) 기체를 직접 기울이는 Attitude(자세) 제어를 순차적으로 수행하는 C++ 예제 코드를 분석해 보겠습니다.

(이 코드는 SITL 시뮬레이션 환경에서 실행하는 것을 강력히 권장합니다.)

C++
#include <iostream>
#include <thread>
#include <chrono>
#include <cmath>
#include <mavsdk/mavsdk.h>
#include <mavsdk/plugins/action/action.h>
#include <mavsdk/plugins/offboard/offboard.h>
#include <mavsdk/plugins/telemetry/telemetry.h>

using namespace mavsdk;
using std::chrono::seconds;
using std::this_thread::sleep_for;

int main() {
    Mavsdk mavsdk{Mavsdk::Configuration{ComponentType::GroundStation}};
    mavsdk.add_any_connection("udpin://0.0.0.0:14540"); // SITL 연결 [14]

    auto system = mavsdk.first_autopilot(3.0);
    if (!system) return 1;

    auto action = Action{system.value()};
    auto offboard = Offboard{system.value()};
    auto telemetry = Telemetry{system.value()};

    // 1. 이륙 대기 및 비행 준비 [15, 16]
    while (!telemetry.health_all_ok()) {
        sleep_for(seconds(1));
    }
    action.arm();
    action.takeoff();
    sleep_for(seconds(8)); // 이륙 완료 대기 [17]

    // ----------------------------------------------------
    // [Phase 1] NED 좌표계 제어: 북쪽으로 사인파 비행 [11, 18]
    // ----------------------------------------------------
    std::cout << "[Phase 1] NED 좌표계 속도 제어 시작\n";
    
    // 오프보드 시작 전 필수: 초기 설정값(정지 상태) 전송 [11]
    offboard.set_velocity_ned(Offboard::VelocityNedYaw{});
    
    if (offboard.start() == Offboard::Result::Success) {
        // 사인파 형태로 북쪽-남쪽 왕복 비행 [18]
        for (unsigned i = 0; i < 628; ++i) { // 2*PI 사이클
            float vx = 5.0f * sinf(i * 0.01f); // 북쪽 방향 속도 변화 [18]
            Offboard::VelocityNedYaw ned_velocity{};
            ned_velocity.north_m_s = vx; 
            ned_velocity.yaw_deg = 90.0f; // 기수는 항상 동쪽을 유지 [18]
            offboard.set_velocity_ned(ned_velocity);
            std::this_thread::sleep_for(std::chrono::milliseconds(10)); // 100Hz 루프 [8]
        }
    }

    // ----------------------------------------------------
    // [Phase 2] Body 좌표계 제어: 원 그리기(Circle) [19, 20]
    // ----------------------------------------------------
    std::cout << "[Phase 2] Body 좌표계 제어 시작 (원주 비행)\n";
    
    Offboard::VelocityBodyYawspeed circle{};
    circle.forward_m_s = 5.0f;       // 기체 앞으로 5m/s 전진 [21]
    circle.yawspeed_deg_s = 30.0f;   // 초당 30도씩 시계 방향으로 회전 [21]
    offboard.set_velocity_body(circle);
    
    sleep_for(seconds(12)); // 12초간 원을 그리며 비행 [21]

    // ----------------------------------------------------
    // [Phase 3] Attitude(자세) 제어: 롤(Roll) 각도 30도 비행 [22]
    // ----------------------------------------------------
    std::cout << "[Phase 3] Attitude(자세) 제어 시작\n";
    
    Offboard::Attitude roll_cmd{};
    roll_cmd.roll_deg = 30.0f;       // 오른쪽으로 30도 기울기 [22]
    roll_cmd.thrust_value = 0.6f;    // 고도 유지를 위한 적절한 추력(0.0 ~ 1.0) [22]
    offboard.set_attitude(roll_cmd); [22]
    
    sleep_for(seconds(3)); [23]

    // ----------------------------------------------------
    // 안전한 종료 및 착륙 [24]
    // ----------------------------------------------------
    std::cout << "오프보드 모드 종료 및 착륙\n";
    offboard.stop(); // 오프보드를 종료하면 드론은 Hold(제자리 호버링) 모드로 바뀜 [6]
    
    action.land(); // 착륙 수행 [24]
    while (telemetry.in_air()) {
        sleep_for(seconds(1)); [24, 25]
    }
    std::cout << "비행 완료!\n";
    return 0;
}


연구원을 위한 실무 가이드 및 주의사항

실제 로보틱스 논문(ICRA, IROS 등)을 위한 실험 환경을 구축할 때 명심해야 할 몇 가지 팁을 드립니다.

  1. 제어 루프(Control Loop)의 주파수 최적화: 오프보드 제어 명령(setpoint)은 10Hz에서 50Hz 사이로 전송하는 것이 가장 안정적입니다. 200Hz 이상의 너무 잦은 명령 전송은 MAVLink 통신 대역폭을 낭비하고 큐(Queue)를 포화시킬 위험이 있습니다. 위 예제의 사인파 비행처럼 미세한 조절이 필요할 때만 고빈도(100Hz) 루프를 돌리고, 일반적인 주행에서는 낮은 빈도로 업데이트해도 MAVSDK가 자동으로 20Hz를 유지해 줍니다.
  2. 모드 이탈 모니터링 (is_active()): 연구 도중 지상국(QGroundControl)에서 조종사가 강제로 개입하여 스위치를 변경하면 기체는 오프보드 모드를 벗어나게 됩니다. 이 경우 offboard.is_active() 값은 false로 변하며, MAVSDK는 더 이상 자동 재전송을 하지 않습니다. 코드 내에서 이 상태를 모니터링하여 제어 루프를 안전하게 정지하는 로직을 추가하는 것이 좋습니다.
  3. 지연 시간(Latency) 최소화: 무거운 비전 연산(예: YOLO 등)을 Telemetry 콜백 안에서 절대 실행하지 마세요. 비전 처리는 별도의 작업 스레드(Worker thread)에서 돌리고, 산출된 최종 속도/위치 타겟값만 오프보드 제어 스레드로 넘겨주는 아키텍처가 통신 지연을 막는 가장 중요한 설계 철학입니다.


이번 9편을 통해 우리는 드론을 단순한 ‘장난감’에서 외부 컴퓨터가 실시간으로 조종하는 ‘첨단 자율 로봇’으로 탈바꿈시키는 오프보드(Offboard) 모드의 핵심 기술에 대해 알아보았습니다.

좌표계를 자유자재로 다루며 기체의 속도, 위치, 자세까지 직접 컨트롤하는 이 기술이야말로 장애물 회피, 비주얼 서보잉, 군집 비행 등을 구현하기 위한 열쇠입니다.

다음 [10편: 사용자 정의 로깅 및 통합 테스트(gtest)]에서는 우리가 지금까지 작성한 복잡한 C++ 비행 코드가 실제 비행 전 완벽하게 작동하는지 검증하는 구글 테스트(Google Test) 프레임워크와 로깅 시스템에 대해 알아보겠습니다.


작성자: 에이든(Aiden), 쿼드(QUAD) 드론연구소 마케팅팀

기고일: 2026.03.19

Similar Posts

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다