[PX4 ROS 2 Programming] 2편: ROS 2 프로그래밍 필수 주의사항 (QoS, 좌표계, 시간 동기화)

안녕하세요! 로봇 공학과 자율 비행에 관심을 가지고 계신 여러분을 환영합니다.

지난 1편에서는 새롭게 도입된 uXRCE-DDS 미들웨어의 개념을 알아보고, Ubuntu 22.04 환경에서 ROS 2 Humble과 PX4의 작업 공간(Workspace)을 빌드하는 과정까지 함께 진행해 보았습니다. 환경 구축을 무사히 마치셨다면, 이제 본격적으로 C++ 코드를 작성하여 드론을 제어해 볼 차례입니다.

하지만 많은 대학생과 초보 연구자분들이 이 단계에서 이른바 **’통곡의 벽’**을 마주하곤 합니다. 분명히 문법에 맞게 ROS 2 노드 코드를 작성했고 에러 로그도 없는데, “센서 데이터가 전혀 들어오지 않거나”, “이륙 명령을 내리자마자 드론이 땅으로 곤두박질치는” 황당한 상황을 겪기 때문입니다.

이러한 문제들의 십중팔구는 ROS 2와 PX4 시스템 간의 설정 및 규약 차이를 제대로 이해하지 못해서 발생합니다. 따라서 본격적인 제어 노드 작성에 앞서, 이번 2편에서는 여러분의 디버깅 시간을 획기적으로 줄여줄 세 가지 핵심 개념인 **QoS(Quality of Service), 좌표계(Frames), 그리고 시간 동기화(Time Sync)**에 대해 아주 상세하고 친절하게 파헤쳐 보겠습니다.


1. 첫 번째 주의사항: QoS (Quality of Service) 프로필 설정

가장 빈번하게 올라오는 질문 중 하나는 다음과 같습니다. “터미널에서 ros2 topic echo 명령어로는 데이터가 잘 보이는데, 제가 만든 C++ 구독자(Subscriber) 노드의 콜백(Callback) 함수는 전혀 호출되지 않아요!”

이 현상의 원인은 QoS(Quality of Service) 설정의 불일치입니다.

QoS(Quality of Service)란 무엇일까요? ROS 2에서는 노드 간 통신을 할 때, 데이터의 성격에 맞춰 통신의 품질을 결정할 수 있습니다. 예를 들어, 한 번의 데이터 손실도 치명적인 ‘이륙 명령어’ 데이터와, 중간에 몇 번 유실되더라도 최신 데이터를 지연 없이 빠르게 받아야 하는 ‘IMU 센서’ 데이터는 통신 전략(신뢰성 보장 여부 등)이 달라야 합니다.

문제의 원인: PX4 내부에서 uXRCE-DDS를 통해 ROS 2로 발행(Publish)되는 토픽들은 대부분 센서 데이터에 적합한 QoS 프로필(Best Effort)을 사용합니다. 하지만 여러분이 ROS 2 구독자를 생성할 때 아무런 설정을 하지 않으면, ROS 2의 **기본(Default) QoS 설정(Reliable)**이 적용됩니다. 안타깝게도 ROS 2의 기본 QoS 설정과 PX4의 센서 QoS 설정은 서로 호환되지 않습니다. 통신 규약이 맞지 않으니 ROS 2 구독자는 발행자가 보내는 데이터를 아예 무시하게 되어, 콜백 함수가 실행되지 않는 것입니다.

해결 방법: rmw_qos_profile_sensor_data 사용 여러분의 ROS 2 구독자 코드에 센서 데이터용 QoS 프로필을 명시적으로 적용해야 합니다. 다음은 PX4의 SensorCombined (가속도 및 자이로 센서) 데이터를 받아오기 위해 올바르게 QoS를 적용한 C++ 구독자 예제 코드입니다.

C++
#include <rclcpp/rclcpp.hpp>
#include <px4_msgs/msg/sensor_combined.hpp> // PX4 메시지 헤더 포함

class SensorCombinedListener : public rclcpp::Node
{
public:
    explicit SensorCombinedListener() : Node("sensor_combined_listener")
    {
        // 1. 센서 데이터용 QoS 프로필 구조체 가져오기 (이 부분이 핵심입니다!)
        rmw_qos_profile_t qos_profile = rmw_qos_profile_sensor_data;
        
        // 2. rclcpp::QoS 객체로 초기화 (히스토리 깊이는 5로 설정하여 최신 5개 유지)
        auto qos = rclcpp::QoS(rclcpp::QoSInitialization(qos_profile.history, 5), qos_profile);

        // 3. 구독자 생성 시 qos 설정을 인자로 전달
        subscription_ = this->create_subscription<px4_msgs::msg::SensorCombined>(
            "/fmu/out/sensor_combined", 
            qos,  // <--- 호환되는 QoS 프로필 적용
            [this](const px4_msgs::msg::SensorCombined::UniquePtr msg) {
                // 데이터 수신 시 실행될 콜백 함수
                RCLCPP_INFO(this->get_logger(), "가속도 X: %f", msg->accelerometer_m_s2);
            });
    }

private:
    rclcpp::Subscription<px4_msgs::msg::SensorCombined>::SharedPtr subscription_;
};

위의 코드처럼 create_subscription의 두 번째 인자로 rmw_qos_profile_sensor_data 기반의 qos 객체를 넘겨주면, 그동안 막혀있던 데이터가 시원하게 들어오는 것을 확인하실 수 있습니다. (참고로, ROS 2에서 PX4로 제어 명령을 보낼 때는 ROS 2 기본 설정과 PX4 설정이 호환되므로 특별히 QoS를 변경하지 않아도 됩니다).


2. 두 번째 주의사항: 좌표계 (Frames) 규약의 늪

통신에 성공해서 드론에게 “위로 5m 올라가라”고 위치 제어 명령(z = 5.0)을 내렸는데, 드론이 땅을 파고들려 하거나 엉뚱한 방향으로 날아간다면 좌표계(Frames) 불일치를 반드시 확인해야 합니다.

ROS 2와 PX4는 서로 완전히 다른 기본 좌표계 규약을 사용합니다. 이 둘 사이를 변환하지 않고 데이터를 그대로 전송하면 시스템은 완전히 반대 방향으로 명령을 해석하게 됩니다.

(1) World 좌표계 (전역 위치)

  • PX4 (NED): 북-동-하 (North, East, Down) 시스템을 사용합니다. 기체 기준 앞으로 가는 것이 북쪽(X), 오른쪽이 동쪽(Y)이며, 아래로 향하는 것이 양(+)의 Z축입니다. 고도가 높아질수록 Z값은 음수(-)가 됩니다.
  • ROS 2 (ENU): 동-북-상 (East, North, Up) 시스템을 표준으로 사용합니다. 오른쪽이 동쪽(X), 앞으로 가는 것이 북쪽(Y)이며, 위로 향하는 것이 양(+)의 Z축입니다.

(2) Body 좌표계 (기체 기준 자세 및 추력)

  • PX4 (FRD): 전-우-하 (Forward, Right, Down) 시스템입니다. 기체의 앞쪽이 X, 오른쪽이 Y, 아래쪽이 Z입니다.
  • ROS 2 (FLU): 전-좌-상 (Forward, Left, Up) 시스템입니다. 기체의 앞쪽이 X, 왼쪽이 Y, 위쪽이 Z입니다.

어떻게 변환할까요?

PX4의 모든 토픽은 메시지 정의에 명시적으로 다른 좌표계를 쓴다고 적혀있지 않은 이상 기본적으로 NED 및 FRD 규약을 따릅니다. 따라서 데이터를 주고받을 때 명시적인 수학적 회전(Rotation)을 통해 좌표계를 맞추어 주어야 합니다.

  • ENU (ROS) NED (PX4) 변환 (World 변환): 목표 위치를 지시하는 TrajectorySetpoint 메시지를 보낼 때 사용합니다. 먼저 Z축(위쪽)을 기준으로 90도(π/2) 회전한 뒤, 새로운 X축(이전 동쪽/새로운 북쪽)을 기준으로 180도(π) 회전해야 합니다.
  • FLU (ROS) FRD (PX4) 변환 (Body 변환): 추력을 제어하는 VehicleThrustSetpoint 메시지를 보낼 때 사용합니다. X축(앞쪽)을 기준으로 180도(π) 회전하면 됩니다.

💡 개발 팁: 매번 삼각함수와 쿼터니언을 써서 직접 계산하기는 매우 번거롭고 실수가 발생하기 쉽습니다. 이를 해결하기 위해 px4_ros_com 패키지 내부에는 frame_transforms라는 유용한 공유 라이브러리가 포함되어 있습니다. C++ 코드 상단에 이 라이브러리를 Include 하여 제공되는 헬퍼 함수들을 사용하면 벡터나 쿼터니언(Quaternion, 자세 데이터)의 변환을 안전하고 간편하게 수행할 수 있습니다.


3. 세 번째 주의사항: 시간 동기화 (Time Synchronization)

로봇 시스템, 특히 빠르게 날아다니며 센서 데이터를 융합해야 하는 드론에 있어서 시간(Time)은 생명과도 같습니다. 데이터가 정확히 ‘언제’ 생성되었는지(Timestamp) 알아야 올바른 제어 알고리즘을 구동할 수 있기 때문입니다. 시뮬레이션 환경이든 실제 기체 제어든 이 시간 동기화 원리를 명확히 이해해야 합니다.

시나리오 A: OS 시간(운영체제 시계)을 메인으로 사용하는 경우 (일반적인 상황)

우리가 흔히 사용하는 대부분의 표준적인 ROS 2 노드는 컴퓨터의 OS 시스템 시간을 사용합니다. uXRCE-DDS 미들웨어는 매우 똑똑하게 설계되어 있어서, 비행 제어기(PX4 클라이언트)와 ROS 2 에이전트 사이의 시간 오프셋, 시간 지연(Drift), 통신 레이턴시 등을 스스로 계산하고 자동으로 시간을 동기화해 줍니다. 이러한 동기화 상태는 /fmu/out/timesync_status 토픽을 통해 모니터링할 수 있으며, 개발자는 시뮬레이션의 실시간 배수(Real Time Factor)가 1에 가까운 일반적인 상황이라면 코드에 별도의 추가 작업 없이 편하게 시스템 시간을 활용하면 됩니다.

시나리오 B: Gazebo 시뮬레이터 클럭을 사용하는 경우 (특수 상황)

하지만 연구 목적상 시뮬레이션을 실제 시간보다 빠르게 혹은 느리게 돌려야 하는 경우(실시간 배수가 1이 아닐 때)나, ROS 2가 Gazebo 시뮬레이터와 직접 상호작용해야 하는 복잡한 환경에서는 OS 시간 동기화 방식이 제대로 동작하지 않습니다.

이럴 때는 ROS 2 노드와 PX4가 OS 시간이 아닌 Gazebo 시뮬레이터의 내부 시간(/clock 토픽)을 기준으로 삼아야 합니다. 이를 위한 3단계 절차는 다음과 같습니다.

  1. ROS 2 Gazebo 브릿지 연결: ros_gz_bridge 패키지의 parameter_bridge 노드를 사용하여 Gazebo의 시계 데이터를 ROS 2의 /clock 토픽으로 가져옵니다.
  2. ROS 2 파라미터 변경: 여러분이 작성한 ‘모든’ ROS 2 노드가 OS 시간이 아닌 시뮬레이션 시간을 쓰도록 use_sim_time 파라미터를 true로 설정해야 합니다.
  3. PX4 파라미터 변경: PX4 내부적으로도 uXRCE-DDS가 독자적으로 OS 시간을 맞추려는 시도를 멈추도록, PX4의 시스템 파라미터인 UXRCE_DDS_SYNCT 값을 false로 변경해야 합니다.

이렇게 설정하면 Gazebo가 전체 시스템의 완벽한 ‘마스터 시계’ 역할을 하게 되어, 시뮬레이션을 일시정지하면 ROS 2 노드의 제어 로직 시간도 정확히 같이 멈추는 동기화를 이룰 수 있습니다.


마치며

이번 2편에서는 ROS 2로 코드를 본격적으로 작성하기 전에 반드시 머릿속에 담아두어야 할 세 가지 핵심 기둥에 대해 자세히 알아보았습니다.

  • 호환성 문제로 데이터가 수신되지 않는 현상을 막기 위한 QoS(rmw_qos_profile_sensor_data) 설정
  • 드론의 정확한 방향 및 추력 제어를 위한 좌표계(ENU NED, FLU FRD) 변환
  • 시뮬레이션 환경 및 목적에 따른 정확한 시간 동기화(Time Sync) 전략

이 세 가지 개념만 완벽히 숙지하신다면, 앞으로 겪게 될 알 수 없는 오류와 디버깅 시간의 90%를 절약하실 수 있을 것이라 확신합니다! 이론적인 내용이 많아 머리가 조금 복잡해지셨을 수도 있지만, 코드로 직접 구현해 보면 금방 체화하실 수 있습니다.

다음 **3편: ROS 2 기초 노드 작성 (Listener & Advertiser)**에서는 오늘 배운 지식을 활용하여 SensorCombined 토픽을 수신하여 터미널에 출력해 보고, 더 나아가 DebugVect 토픽을 통해 ROS 2에서 PX4 네트워크로 디버깅 데이터를 쏘아보는 실제 C++ 예제 코드를 한 줄 한 줄 뜯어보겠습니다. 다음 편에서 뵙겠습니다!


YouTube 강좌

재생


Author: maponarooo, CEO of QUAD Drone Lab

Date: February 27, 2026

Similar Posts

답글 남기기

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