AEGLOSLAB Arduino UNO Q 완전 가이드

제8부 실시간 시스템 응용

8.5 STM32에서 PX4/ArduPilot 실시간 제어 — MAVLink 오프보드·가이디드 스트리밍

이 장은 제8부에서 가장 오해하기 쉬운 주제를 다룬다. 먼저 경계부터 명확히 규정한다 — 이 장이 설명하는 것은 "UNO Q가 드론을 비행시킨다"가 아니라 "UNO Q의 MCU가 드론의 비행 컨트롤러에게 결정론적으로 명령을 전송한다"는, 훨씬 좁고 정확한 역할이다. 이 경계를 흐리면 위험하다 — 실제 비행체에 적용하기 전에 이 장을 끝까지 읽어야 한다.

이 장 전체에 실물 미검증(검증 필요) 라벨이 붙는다. 여기 제시하는 배선·코드·절차는 MAVLink·PX4·ArduPilot 공식 문서와 STM32U585·UNO Q 공식 사양을 근거로 구성한 참조 설계이지, 실제 비행체에서 검증된 완성 절차가 아니다. 비행체에 적용하려면 지상에서 프로펠러를 제거한 상태로 충분히 검증한 뒤, 단계적으로(먼저 시뮬레이터, 다음 지상 고정, 마지막 실비행) 진행해야 한다.

8.5.1 경계 설정 — STM32U585는 flight controller가 될 수 없다

8.5.1.1 결론부터

Arduino UNO Q는 PX4나 ArduPilot 펌웨어 자체를 구동하는 flight controller(FC)가 될 수 없다. 이것은 소프트웨어를 조금 수정하면 해결되는 문제가 아니라, MCU 자체의 급이 다른 하드웨어 경계다.

8.5.1.2 근거

  • STM32U585는 STM32U5 시리즈다. ST가 이 시리즈를 분류하는 표현 자체가 "Ultra-low-power"(초저전력) — 배터리 수명을 최우선으로 설계된 라인이다. Cortex-M33 코어에 160MHz, 2MB 플래시, 786KB SRAM을 갖췄다(2.1, 8.1).
  • PX4·ArduPilot이 실제로 요구하는 급은 F4/F7/H7 계열이다. ArduPilot의 ChibiOS HAL 보드 정의(hwdef) 스크립트 디렉터리에는 STM32F427xx.py, STM32F745xx.py, STM32H743xx.py 같은 파일이 있고, ArduPilot 개발 문서가 제시하는 "부팅 가능한" 최소 요구사항도 "STM32H7(F4/F7도 가능)" 급 MCU, 1MB 이상 플래시, 1MB 이상 RAM 권장이다. 이 책 집필 시점 검색으로는 STM32U5 계열을 대상으로 하는 ArduPilot hwdef 스크립트나 정식 지원 보드가 확인되지 않는다.
  • PX4 쪽도 마찬가지다. PX4는 NuttX RTOS 위에서 동작하며, NuttX 자체는 STM32L5의 포팅을 바탕으로 STM32U5 아키텍처 지원 코드를 갖고 있다(즉 "NuttX가 이 칩 위에서 부팅할 수 있는가"라는 질문에는 원리적으로 가능성이 있다). 하지만 이것은 RTOS 커널 수준의 아키텍처 지원이지, PX4가 실제로 STM32U5 기반 보드를 flight controller 타깃으로 지원한다는 뜻이 아니다. 이 책 집필 시점 검색으로 STM32U5 기반의 공식·비공식 PX4 FC 보드는 확인되지 않는다.
  • UNO Q 자체가 FC용 하드웨어를 갖추고 있지 않다. Pixhawk급 FC는 진동·전자기 간섭에 강한 방식으로 마운트된 리던던트(이중·삼중) IMU를 SPI로 수백 Hz~8kHz급 속도로 읽고, 별도 기압계·전원 이중화·안전 스위치 회로를 갖춘다. UNO Q의 STM32U585는 Arduino 실드 폼팩터의 범용 GPIO·PWM·통신 핀으로 배선되어 있고(2.2), 이런 FC 전용 센서 하네스가 없다.

이 세 가지(칩 등급, 소프트웨어 보드 지원, 하드웨어 구성)가 겹쳐서 "이론적으로 언젠가 누군가 포팅할 수도 있다"가 아니라 "지금 이 보드로는 안 되고, 그렇게 설계되지도 않았다"는 결론이 된다. BOOK.md(BK-04)에 명시된 이 책의 핵심 정직성 원칙이 바로 이 지점을 위한 것이다.

8.5.1.3 그럼 이 장은 무엇을 하는가

STM32U585가 FC가 될 수 없다고 해서 드론과 무관해지는 것은 아니다. UNO Q의 MCU는 별도의 실제 FC에 "연결되어" 그 FC를 실시간으로 조종하는 외부 제어기가 될 수 있다. 이것이 이 장의 실제 주제다.

할 수 없음 할 수 있음 UNO Q MCU STM32U585 PX4/ArduPilot 펌웨어를 직접 구동 별도 Pixhawk급 FC에 UART로 연결해 MAVLink setpoint 스트리밍

8.5.2 대안 아키텍처 — 결정론적 외부 제어기

실제 구성은 두 개의 서로 다른 보드가 UART 한 가닥으로 연결되는 것이다.

UART TELEM2/3 · GPS2 등 Bridge · 비동기 3.4 · 8.3 별도 Pixhawk급 FC PX4 또는 ArduPilot 펌웨어 구동 UNO Q MCU STM32U585 · c_library_v2 UNO Q MPU 미션 계획 · 비전
  • FC(Pixhawk 계열): PX4는 TELEM2 포트를, ArduPilot도 통상 TELEM2/TELEM3 또는 GPS2 같은 여분 UART 포트를 컴패니언 컴퓨터·외부 제어기 연결용으로 열어둔다. PX4 공식 문서는 컴패니언 컴퓨터 연결에 UART(TELEM2)를 표준 경로로 제시하며, USB-시리얼 어댑터로 우회하는 방법도 함께 안내한다. 비행체 배선에서는 USB보다 UART 직결이 지연·지터 면에서 권장된다 — USB는 호스트 쪽 드라이버 스택·열거(enumeration) 과정을 거치는 반면 UART는 두 UART 페리퍼럴이 직접 프레임을 주고받는다.
  • UNO Q MCU(STM32U585): FC와 UART로 물리 연결되어, MAVLink 하트비트와 setpoint 메시지를 정해진 주기로 결정론적으로 내보낸다(8.1~8.2의 결정론이 여기서 그대로 쓰인다).

8.5.3 MAVLink c_library_v2를 Zephyr 기반 MCU에 이식하는 근거

MAVLink 공식 C 레퍼런스 라이브러리(GitHub mavlink/c_library_v2)는 mavgen이 생성하는 헤더 온리(header-only) 라이브러리다. 특정 운영체제 API에 의존하지 않고, 순수 C로 메시지를 패킹·언패킹하는 함수만 제공한다 — 통신 매체(UART, USB, 무선 등)에 실제로 바이트를 실어 보내는 부분은 라이브러리 밖, 사용자 코드의 몫이다. 이 성질 덕분에:

  • ARM7·ATmega·dsPIC·STM32 등 다양한 MCU에서 이미 field-proven 상태로 쓰이고 있다.
  • 메모리가 제한된 환경을 위해 MAVLINK_COMM_NUM_BUFFERS 같은 매크로로 버퍼 수를 줄이는 등 리소스 제약 환경에 맞춘 설정 옵션을 제공한다.
  • OS 비의존적이므로 Zephyr 기반 Arduino Core(3.2) 위에도 원리적으로 이식 가능하다 — Zephyr 전용 API를 요구하지 않고, malloc/스레드 같은 OS 서비스에도 의존하지 않는다.

즉 STM32U585가 FC 펌웨어(PX4/ArduPilot 자체)는 구동할 수 없어도, MAVLink 프로토콜 자체를 말하는 것은 헤더 하나를 포함하는 문제이지 OS 이식성 문제가 아니다. 이 구분이 이 장 전체를 가능하게 하는 핵심이다.

8.5.4 배선

FC의 TELEM2(또는 여분 UART 포트)와 UNO Q MCU의 UART(예: 2.2 핀아웃의 D0/D1, Serial1)를 다음과 같이 연결한다.

FC 쪽UNO Q MCU 쪽비고
TELEM2 TXRX(예: D0)교차 연결 — TX는 상대 RX로
TELEM2 RXTX(예: D1)교차 연결
GNDGND반드시 공통 접지
(전원은 별도)—FC와 UNO Q를 같은 배터리에서 각자 규격에 맞게 급전. 전원선을 함께 묶지 않는다

전압 레벨을 반드시 확인한다. UNO Q의 UART 핀은 3.3V 로직이다(2.2). Pixhawk 계열 FC의 TELEM 포트도 통상 3.3V지만, 사용하는 FC 모델의 실제 로직 레벨은 그 FC의 데이터시트로 재확인한다 — 레벨이 다르면 레벨 시프터를 넣어야 한다(확인 필요, FC 모델별로 다름). 보율(baud rate)도 FC 쪽 파라미터(예: SER_TEL2_BAUD류, ArduPilot/PX4 파라미터명은 버전별로 다를 수 있음 — 확인 필요)와 MCU 쪽 Serial1.begin() 값을 일치시켜야 한다. MAVLink 로그 스트리밍처럼 대역폭이 필요한 용도에는 921600bps 이상이 권장된다는 것이 PX4 문서에서 확인되지만, setpoint 스트리밍만 목적이라면 57600~115200bps대로도 충분한 경우가 많다 — 실제 필요 대역폭은 메시지 종류·주기에 따라 계산해서 정한다.

8.5.5 하트비트·setpoint 인코딩 — 의사코드

MAVLink 연결이 성립하려면 양쪽 모두 하트비트(HEARTBEAT, 보통 1Hz)를 지속적으로 보내야 한다. FC는 상대가 하트비트를 보내지 않으면 유효한 시스템으로 인식하지 않는다.

// ===== 개념 예시 — c_library_v2 헤더(common/mavlink.h)를 Zephyr 기반
// Arduino Core 프로젝트에 포함시켰다고 가정. 실제 API 시그니처는
// c_library_v2 버전에 따라 세부가 다를 수 있어 헤더 원문으로
// 재확인 필요(확인 필요). 실물 미검증.

#include <common/mavlink.h>

HardwareSerial &fcSerial = Serial1;  // FC와 연결된 UART

const uint8_t SYSTEM_ID = 42;     // 이 외부 제어기의 MAVLink 시스템 ID
const uint8_t COMPONENT_ID = MAV_COMP_ID_ONBOARD_COMPUTER;

unsigned long lastHeartbeatMs = 0;
unsigned long lastSetpointMs = 0;
const unsigned long HEARTBEAT_PERIOD_MS = 1000;   // 1Hz
const unsigned long SETPOINT_PERIOD_MS = 50;      // 20Hz — PX4 오프보드 최소 2Hz를 충분히 상회

void sendHeartbeat() {
  mavlink_message_t msg;
  uint8_t buf[MAVLINK_MAX_PACKET_LEN];

  mavlink_msg_heartbeat_pack(
      SYSTEM_ID, COMPONENT_ID, &msg,
      MAV_TYPE_ONBOARD_CONTROLLER, MAV_AUTOPILOT_INVALID,
      0, 0, MAV_STATE_ACTIVE);

  uint16_t len = mavlink_msg_to_send_buffer(buf, &msg);
  fcSerial.write(buf, len);
}

// PX4 오프보드용 — SET_POSITION_TARGET_LOCAL_NED (속도 제어 예시)
void sendPX4OffboardVelocitySetpoint(float vx, float vy, float vz, float yawRate) {
  mavlink_message_t msg;
  uint8_t buf[MAVLINK_MAX_PACKET_LEN];

  // type_mask: 위치·가속도·yaw는 무시하고 속도(vx,vy,vz)와 yaw_rate만 사용
  uint16_t type_mask = 0b0000011111000111;  // 정확한 비트 배치는 MAVLink
                                              // 공식 문서로 재확인(확인 필요)

  mavlink_msg_set_position_target_local_ned_pack(
      SYSTEM_ID, COMPONENT_ID, &msg,
      millis(), /* time_boot_ms */
      1, 1,     /* target_system, target_component — FC 쪽 ID */
      MAV_FRAME_LOCAL_NED,
      type_mask,
      0, 0, 0,           /* x, y, z position — 무시됨 */
      vx, vy, vz,        /* velocity */
      0, 0, 0,           /* acceleration — 무시됨 */
      0, yawRate);       /* yaw, yaw_rate */

  uint16_t len = mavlink_msg_to_send_buffer(buf, &msg);
  fcSerial.write(buf, len);
}

// ArduPilot 가이디드 모드용 — 메시지 구조는 동일(SET_POSITION_TARGET_LOCAL_NED),
// ArduPilot이 이 메시지를 Guided 모드에서 해석하는 방식으로 재사용된다.
void sendArduPilotGuidedVelocitySetpoint(float vx, float vy, float vz) {
  sendPX4OffboardVelocitySetpoint(vx, vy, vz, 0);  // 실제로는 FC별 세부 차이 확인 필요
}

void loop() {
  unsigned long now = millis();

  if (now - lastHeartbeatMs >= HEARTBEAT_PERIOD_MS) {
    lastHeartbeatMs = now;
    sendHeartbeat();
  }

  if (now - lastSetpointMs >= SETPOINT_PERIOD_MS) {
    lastSetpointMs = now;
    // targetVx/Vy/Vz는 MPU가 Bridge로 내려준 미션 계획값을
    // 로컬 변수에 반영한 것 — 8.3.5, 8.4.5와 같은 패턴
    sendPX4OffboardVelocitySetpoint(targetVx, targetVy, targetVz, 0);
  }

  // FC로부터 오는 MAVLink 메시지(상태·ACK 등)도 폴링해서 파싱한다.
  // 워치독 판단(8.5.7)에 필요하다.
}

이 코드가 실제로 컴파일·동작하려면 c_library_v2를 Arduino Core(Zephyr 기반) 프로젝트에 라이브러리로 포함하는 절차, HardwareSerial/Serial1 API의 정확한 사용법, type_mask 비트 배치의 정확한 값이 모두 실물·공식 문서로 재확인되어야 한다. 이 절 전체가 검증 필요다.

8.5.6 모드 진입 절차

8.5.6.1 PX4 — 오프보드(Offboard) 모드

PX4는 오프보드 모드를 "외부 제어기가 동작 중이다"라는 증거로 setpoint 스트림 자체를 사용한다.

  1. 오프보드 모드로 전환하거나 무장(arm)하기 최소 1초 전부터 setpoint(또는 ROS 2 OffboardControlMode) 스트림을 최소 2Hz로 먼저 보내기 시작한다. 위 예제는 20Hz로 여유를 둔다.
  2. MAV_CMD_DO_SET_MODE(또는 QGroundControl 등에서 동일한 명령)로 오프보드 모드 진입을 요청한다.
  3. 진입 후에도 setpoint 스트림을 계속 유지한다 — 끊기면 페일세이프가 발동한다(8.5.7).

8.5.6.2 ArduPilot — 가이디드(Guided) 모드

  1. MAV_CMD_DO_SET_MODE(또는 RC 스위치)로 GUIDED 모드로 전환한다.
  2. 무장(arm)한다.
  3. SET_POSITION_TARGET_LOCAL_NED 등으로 위치/속도/가속도 setpoint를 스트리밍한다.
  4. ArduPilot은 GUID_TIMEOUT 파라미터(기본값 3초)로 마지막 명령 수신 후 경과 시간을 감시한다 — 이 시간 안에 새 명령이 오지 않으면 속도·가속도 명령을 받고 있었다면 서서히 정지하고, 자세 명령을 받고 있었다면 수평 호버로 전환한다.

두 FC 소프트웨어 모두 "계속 스트리밍되는 것 자체가 안전장치의 일부"라는 설계 철학을 공유한다 — 이것이 다음 절 워치독 설계의 출발점이다.

8.5.7 워치독·페일세이프 설계

UNO Q MCU 쪽에서 반드시 구현해야 하는 안전장치:

  • 스트림 자체 감시: sendPX4OffboardVelocitySetpoint() 같은 함수가 실제로 매 주기 호출되고 있는지, UART 전송이 막히지 않았는지(버퍼 풀 상태 등)를 감시한다. 8.2.3의 하드 스케줄(타이머 인터럽트)로 setpoint 전송 주기 자체를 결정론적으로 보장하는 것이 1차 안전 수단이다.
  • MPU-MCU 연결 감시: setpoint의 목표값이 MPU의 미션 계획(Bridge 경유, 8.3)에서 온다면, Bridge 연결이 끊기거나 갱신이 멈췄을 때를 별도로 감지해야 한다. 8.3.5에서 다룬 패턴 그대로 — 마지막 유효값을 얼마나 오래 신뢰할지, 타임아웃 후 "제자리에서 호버" 또는 "속도 0" 같은 안전 setpoint로 전환할지를 명시적으로 코드에 넣는다. 이 판단을 하지 않고 마지막 명령을 무한히 반복 전송하면, MPU가 멈춘 상태를 FC가 "정상적으로 동작 중인 외부 제어기"로 오인해 위험한 명령을 계속 실행할 수 있다.
  • FC 쪽 응답 확인: FC가 보내는 HEARTBEAT, COMMAND_ACK 등을 MCU가 파싱해 FC가 실제로 오프보드/가이디드 모드에 진입했는지, 무장 상태인지 확인한다. 모드 전환이 실패했는데 setpoint만 계속 보내는 것은 무의미하다.
  • 이중 타임아웃 계층을 둔다 — ① MCU가 MPU로부터 유효한 목표값을 못 받으면 안전 setpoint(정지·호버)로 대체, ② 그래도 MCU 자체가 정지하거나 UART가 끊기면 FC 쪽의 자체 페일세이프(COM_OF_LOSS_T/COM_OBL_RC_ACT, GUID_TIMEOUT)가 최종 안전 수단으로 동작 — 이렇게 이중으로 설계한다. 한쪽만 믿지 않는다.
  • 실비행 전 지상 테스트: 프로펠러를 제거한 상태에서 MCU 전원을 강제로 끊거나 UART 케이블을 분리해 보며 FC가 의도한 페일세이프 동작을 실제로 수행하는지 확인한다. 이 검증 없이 실비행에 들어가지 않는다.
Bridge 3.4 · 8.3 갱신 끊김 감지 예 아니오 UART 자체 페일세이프 COM_OF_LOSS_T · GUID_TIMEOUT MPU 미션 계획 MCU 로컬 setpoint 버퍼 타임아웃? 안전 setpoint로 대체 정지 · 호버 MAVLink setpoint 결정론적 스트리밍 FC · PX4/ArduPilot 2차 안전 수단

8.5.8 MPU 컴패니언 컴퓨터 방식과의 비교

PX4 공식 문서가 안내하는 "일반적인" 컴패니언 컴퓨터 구성은 이 장의 구성과 다르다 — 그 구성은 MPU급 컴퓨터(예: Raspberry Pi)가 직접 FC의 TELEM2에 연결되어 MAVSDK·MAVROS 같은 상위 라이브러리로 미션 로직·비전을 처리하며 MAVLink를 주고받는 방식이다.

MPU가 직접 컴패니언 컴퓨터 (일반적 구성)이 장의 구성 — MCU가 실시간 외부 제어기
FC와 직결되는 프로세서MPU(Linux, 범용 OS)MCU(Zephyr, 결정론적)
setpoint 스트리밍 주기의 안정성Linux 스케줄링 지터에 노출(8.1.3)하드웨어 타이머로 결정론적 유지(8.2)
잘하는 일미션 계획, 경로 계획, 비전·AI 추론, 상위 로직정확한 주기의 저지연 명령 스트리밍
구현 난이도MAVSDK/MAVROS 등 성숙한 라이브러리 활용 가능c_library_v2 직접 통합, 저수준 구현 필요(8.5.5)

UNO Q에서는 이 둘을 배타적으로 고르지 않고 조합할 수 있다. MPU가 카메라·센서로 상황을 인식하고 미션 계획(예: "다음 목표 지점", "목표 속도 벡터")을 세우면, 그 결과를 Bridge(3.4·8.3)로 MCU에 넘기고, MCU가 그 값을 받아 FC로 결정론적으로 스트리밍하는 구성이다 — 8.5.2의 다이어그램에서 점선(Bridge, 비동기)과 실선(UART, 결정론적)의 구분이 정확히 이 역할 분담을 나타낸다. MPU는 "무엇을 할지"를, MCU는 "언제 정확히 실행할지"를 맡는다는 8.1.5의 원칙이 여기서도 그대로 적용된다.

8.5.9 정직성 종합 고지

이 장에서 확인된 사실(공식 문서·데이터시트 기반)과 검증 필요(실물 미검증 또는 공식 확인 안 됨) 항목을 나눠 정리한다.

항목상태
STM32U585 = STM32U5(초저전력) 시리즈, Cortex-M33 160MHz확인됨(ST 데이터시트)
ArduPilot이 요구하는 급은 F4/F7/H7 계열, hwdef 스크립트에 STM32U5 계열 미확인확인됨(이 책 집필 시점 검색 기준)
PX4 NuttX가 STM32U5 아키텍처 자체는 포팅되어 있으나 FC 보드 타깃은 아님확인됨(이 책 집필 시점 검색 기준)
c_library_v2가 헤더 온리·OS 비의존확인됨(공식 GitHub 저장소)
PX4 오프보드 2Hz 최소 스트림, COM_OF_LOSS_T/COM_OBL_RC_ACT 페일세이프확인됨(PX4 공식 문서)
ArduPilot GUID_TIMEOUT 기본 3초확인됨(ArduPilot 공식 문서)
PX4 TELEM2가 컴패니언 연결의 표준 경로, UART가 USB보다 권장확인됨(PX4 공식 문서 — USB 대비 정량적 지연 수치까지는 확인되지 않음)
8.5.5 코드의 type_mask 비트값, mavlink_msg_*_pack 정확한 시그니처검증 필요 — c_library_v2 헤더 원문으로 재확인
Arduino Core(Zephyr 기반)에 c_library_v2를 통합하는 정확한 절차검증 필요 — 실물 미검증
FC 모델별 TELEM 포트 전압·보율 파라미터명검증 필요 — FC 모델마다 다름
이 장 전체의 실비행 검증미검증 — 반드시 지상 테스트를 먼저 거칠 것

8.5.10 제8부를 마치며

8.1에서 시작한 "왜 결정론이 필요한가"라는 질문은 이 장에서 "결정론이 실제로 생명과 안전에 직결되는 응용(비행체 실시간 제어)"까지 이어졌다. 제9부(AI·엣지 응용)는 다시 MPU 쪽 — 비전·추론이라는 soft real-time 영역으로 이동한다. 두 영역을 하나의 보드 안에서 어떻게 나누고 잇는지가 UNO Q라는 제품의 핵심이라는 점을 이 부 전체가 보여준다.