MAVSDK-Python 프로그래밍 완벽 가이드 [제7편]: OFFBOARD 모드의 핵심 원리 이해하기
안녕하세요! 자율비행 로봇과 드론 제어 알고리즘 연구에 매진하고 계신 대학생 및 연구원 여러분. MAVSDK-Python 연재 블로그의 일곱 번째 시간에 오신 것을 환영합니다.
지금까지 우리는 MAVSDK의 기본 클래스와 비동기(asyncio) 프로그래밍을 익히고, 드론을 이착륙시키며 목적지(Waypoint)로 날려 보내는 방법들을 배웠습니다. 하지만 강화학습을 이용한 자율비행 알고리즘, 카메라 비전 기반의 실시간 객체 추적, 군집 비행 등을 연구하는 대학원생이나 연구원분들에게 기존의 단순한 ‘임무(Mission)’ 모드나 ‘이동(Goto)’ 모드는 정밀도와 반응성 면에서 한계가 명확합니다.
이러한 한계를 극복하고 기체의 움직임을 내 마음대로, 그것도 초당 수십 번씩 정밀하게 제어하기 위해 반드시 정복해야 하는 산이 바로 **’OFFBOARD(오프보드) 모드’**입니다.
이번 **[제7편: OFFBOARD 모드의 핵심 원리 이해하기]**에서는 드론 제어의 꽃이라 불리는 오프보드 모드의 개념부터, 까다로운 전제 조건, 그리고 위치/속도/피드포워드 제어 코드까지 아주 친절하고 상세하게 파헤쳐 보겠습니다.
1. OFFBOARD 모드란 무엇인가요?
일반적으로 드론은 조종기의 스틱 입력(RC 제어)을 받거나, 사전에 입력된 GPS 기반의 경로(Mission)를 따라 비행합니다. 하지만 OFFBOARD 모드에서는 기체 내부의 비행 제어기(FC)가 아닌, ‘외부(Off-board)’에 있는 컴패니언 컴퓨터(예: 라즈베리파이, 젯슨 나노)나 지상국 PC가 드론의 조종간을 직접 잡게 됩니다.
외부 컴퓨터에 탑재된 여러분의 파이썬 알고리즘이 위치, 속도, 가속도, 자세, 추력 등의 목표값(Setpoint)을 계산하여 MAVLink 또는 ROS 2를 통해 드론에게 실시간으로 쏘아주면, 드론은 오직 그 명령만을 맹목적으로 추종하게 됩니다.

2. 연구자가 반드시 알아야 할 3가지 필수 전제 조건 (안전장치)
오프보드 모드는 외부 컴퓨터가 조종권을 완전히 가져가기 때문에, 통신이 끊기거나 코드가 멈추면 기체가 그대로 추락할 수 있는 매우 위험한 모드입니다. 따라서 PX4는 오프보드 모드 진입과 유지에 매우 엄격한 3가지 조건을 요구합니다.
① 2Hz 이상의 지속적인 생존 신호 (Heart-beat)
PX4는 외부 컨트롤러(여러분의 파이썬 코드)가 정상적으로 작동하고 있다는 증거로, 최소 2Hz(초당 2회) 이상의 속도로 지속적인 설정점(Setpoint) 메시지 스트림을 수신해야 합니다. 만약 코드가 에러로 멈추거나 네트워크 지연으로 인해 이 속도가 2Hz 아래로 떨어지면, PX4는 COM_OF_LOSS_T 파라미터에 설정된 시간 초과 이후 오프보드 모드를 강제로 해제해 버립니다. 통신이 끊겼을 때의 안전 조치는 COM_OBL_RC_ACT 파라미터에 의해 결정되며, 보통 제자리 비행(Hold)을 하거나 착륙(Land) 또는 복귀(RTL)를 시도합니다.
② 사전 설정점 주입 (Initial Setpoint)
드론이 비행 중 오프보드 모드로 전환될 때, 갑자기 어디로 가야 할지 모르면 기체가 요동칠 수 있습니다. 따라서 오프보드 모드를 켜기 전, 반드시 최소 1초 이상 설정점 메시지 스트림을 먼저 전송하고 있어야 진입이 허가됩니다. 이 때문에 모든 MAVSDK 오프보드 코드에는 drone.offboard.start()를 호출하기 전, 초기값을 세팅하는 코드가 필수적으로 들어갑니다.
③ 신뢰할 수 있는 위치/자세 센서 정보
오프보드 모드는 정밀한 제어를 요구하므로 GPS, Optical-Flow, VIO(Visual Inertial Odometry), 또는 Mocap(모션 캡처) 센서로부터 신뢰할 수 있는 위치나 자세 정보가 안정적으로 수신되고 있어야만 작동합니다. GPS가 없는 실내 환경이라면 비전 센서를 통한 위치 추정 세팅이 선행되어야 합니다.
3. 헷갈리기 쉬운 좌표계 완벽 정리 (NED vs BODY)
코드를 작성하기 전, 방향을 지시할 기준 좌표계를 이해해야 합니다. 연구 시나리오에 따라 적절한 좌표계를 선택하는 것이 핵심입니다.
- Local NED (North-East-Down) 좌표계: 지구의 방위를 기준으로 합니다. 드론이 어느 쪽을 보고 있든 무관하게 앞(X)은 북쪽, 오른쪽(Y)은 동쪽, 아래(Z)는 지면을 향합니다. 주의할 점은 Z축(고도)이 **Down(아래)**이므로, 드론을 10m 고도로 띄우고 싶다면 Z값에 반드시 **
-10.0**을 입력해야 합니다. - BODY 좌표계: 드론의 현재 기수(Nose) 방향을 기준으로 합니다. **앞(Forward), 우측(Right), 아래(Down)**로 구성됩니다. 카메라가 드론 정면에 달려있고, 비전 인식으로 추적 대상이 화면 중앙에 오도록 제어할 때 매우 직관적이고 필수적인 좌표계입니다.
4. MAVSDK-Python 오프보드 실전 예제 분석
이제 배운 이론이 코드에 어떻게 녹아들어 있는지 직접 확인해 보겠습니다. MAVSDK-Python에서는 OffboardError 예외 처리가 매우 중요하므로 항상 try...except로 감싸주어야 합니다.
예제 1: OFFBOARD 위치 제어 (NED 기준)
가장 기본적인 형태로, 특정 좌표(북쪽 Xm, 동쪽 Ym, 고도 Zm)로 이동시킵니다.
import asyncio
from mavsdk import System
from mavsdk.offboard import (OffboardError, PositionNedYaw)
async def run():
drone = System()
await drone.connect(system_address="udp://:14540")
# 1. 아밍(Arming)
await drone.action.arm()
# 2. 💡 핵심: 오프보드 시작 전 EKF Origin(0, 0, 0) 초기화 스트림 전송
print("-- EKF origin 초기화 (Initial Setpoint)")
await drone.offboard.set_position_ned(PositionNedYaw(0.0, 0.0, 0.0, 0.0))
# 3. 오프보드 모드 시작
try:
await drone.offboard.start()
except OffboardError as error:
print(f"오프보드 시작 실패: {error._result.result}")
return
# 4. Local NED 좌표계 기준으로 이동
print("-- 북쪽으로 5m, 동쪽으로 10m, 고도는 5m 상공으로 이동 (기수는 동쪽 90도)")
await drone.offboard.set_position_ned(PositionNedYaw(5.0, 10.0, -5.0, 90.0))
# 이동할 수 있는 충분한 시간(15초)을 부여. 비동기 환경이므로 asyncio.sleep 사용
await asyncio.sleep(15)
print("-- 오프보드 종료")
await drone.offboard.stop()
if __name__ == "__main__":
asyncio.run(run())

예제 2: OFFBOARD 속도 제어 (BODY 기준)
로봇 비전이나 장애물 회피 알고리즘에서 가장 많이 사용되는 기체 기준의 속도 제어입니다. 목표물이 오른쪽으로 벗어나면 기체를 우측으로 이동시키라는 명령을 쉽게 내릴 수 있습니다.
from mavsdk.offboard import (OffboardError, VelocityBodyYawspeed)
# ...(연결 및 초기화 과정 생략)...
# 💡 속도 제어의 초기 설정점은 0m/s 로 초기화합니다.
await drone.offboard.set_velocity_body(VelocityBodyYawspeed(0.0, 0.0, 0.0, 0.0))
await drone.offboard.start()
print("-- 앞(Forward)으로 5m/s 속도로 전진하며 기수를 초당 30도씩 시계방향으로 회전 (원주 비행)")
await drone.offboard.set_velocity_body(VelocityBodyYawspeed(5.0, 0.0, 0.0, 30.0))
await asyncio.sleep(15) # 15초 동안 해당 속도와 회전 유지
# 💡 이동이 끝나면 속도를 다시 0으로 만들어 정지(Hold)시킵니다.
await drone.offboard.set_velocity_body(VelocityBodyYawspeed(0.0, 0.0, 0.0, 0.0))
await asyncio.sleep(5)

예제 3: 연구 성능을 극대화하는 위치+속도 제어 (Feed-Forward)
드론이 목표 위치로 이동할 때 단순히 목표점(Position)만 지시하면, 오차가 발생한 후 그것을 수정하는 ‘피드백(Feedback)’ 제어에만 의존하여 이동이 굼뜨거나 목표지점에서 진동(오버슈트)이 발생할 수 있습니다.
이를 개선하기 위해 PX4는 ‘피드포워드(Feed-Forward)’ 기법을 지원합니다. 위치와 함께 필요한 속도를 미리 예측하여 제어 신호에 더해줌으로써, 드론이 훨씬 더 빠르고 정밀하게 목표 위치와 속도에 도달하게 하는 고급 제어 방식입니다.
from mavsdk.offboard import (PositionNedYaw, VelocityNedYaw)
# 초기화 생략...
print("-- 북쪽 50m 위치로 이동하되, 이동 속도(Feed-Forward)를 북쪽 방향 1.0m/s로 미리 지시함")
# set_position_velocity_ned()를 사용하여 위치와 속도 지령치를 동시에 전송
await drone.offboard.set_position_velocity_ned(
PositionNedYaw(50.0, 0.0, -10.0, 0.0), # 목표 위치 (북쪽 50m, 고도 10m)
VelocityNedYaw(1.0, 0.0, 0.0, 0.0) # 목표 속도 (북쪽 1m/s)
)
await asyncio.sleep(20)
이 피드포워드 제어를 동적 환경에서 효과적으로 사용하려면 시스템의 특성을 잘 파악하고 신중하게 값을 세팅해야 높은 성능을 이끌어낼 수 있습니다.

마치며
이번 제7편에서는 자율비행 연구의 최종 병기라고 할 수 있는 OFFBOARD 모드의 통신 원리, 필수 전제 조건, 그리고 위치 및 속도를 다루는 실전 예제들을 깊이 있게 분석해 보았습니다.
처음 접하시는 분들은 PositionNedYaw나 VelocityBodyYawspeed 같은 긴 클래스 이름과, DOWN 방향이 - (마이너스)라는 사실에 잦은 실수를 범하곤 합니다. 따라서 실제 드론을 날리기 전에 반드시 SITL(가상 시뮬레이터) 환경에서 기체가 의도한 좌표계와 방향으로 정확히 움직이는지 충분히 검증하는 습관을 들이시기 바랍니다.
다음 **[제8편]**에서는 이번 오프보드 지식을 바탕으로, ‘Keyboard 입력을 이용한 수동 조종 및 기체 이동 프로그램’을 직접 구현해 보며 실시간 제어의 재미를 만끽해 보도록 하겠습니다.
오늘 배운 오프보드 피드포워드 개념이나 코드에 궁금한 점이 있으시다면 언제든 편하게 댓글을 남겨주세요. 연구원 여러분의 멋진 알고리즘 비행을 응원합니다! 감사합니다.
YouTube 강좌

Author: maponarooo, CEO of QUAD Drone Lab
Date: March 5, 2026
