21.7.4.1. `px4_pollfd_struct_t` 배열 구축 및 동기화 대기

21.7.4.1. px4_pollfd_struct_t 배열 구축 및 동기화 대기

다중 토픽을 폴링(Polling)하기 위한 첫걸음은, 내가 감시하고 싶은 uORB 토픽들의 파일 디스크립터(File Descriptor)들을 모아 하나의 구조체 배열로 예쁘게 포장하는 것이다.
이때 사용되는 뼈대 구조체가 바로 PX4가 제공하는 px4_pollfd_struct_t 이다.

1. 폴링 배열(Poll Array)의 셋업

자이로 센서(sensor_gyro)와 기압계(sensor_baro)의 데이터를 융합하는 간단한 필터 모듈의 Run() 함수 진입부를 작성해 보자.

#include <px4_platform_common/posix.h> // px4_poll 사용을 위한 헤더

void SimpleFusionModule::Run() {

    // 1. 내가 감시할 토픽들의 파일 디스크립터들을 변수로 빼놓는다.
    // (이 FD들은 보통 클래스 생성자나 start() 함수에서 orb_subscribe()로 미리 발급받아 둔다)
    int gyro_fd = _gyro_sub_fd;
    int baro_fd = _baro_sub_fd;

    // 2. 뼈대 배열 선언 (감시 대상이 2개이므로 크기는 2)
    px4_pollfd_struct_t fds[2];

    // 3. 배열 0번 방: 자이로 센서 세팅
    fds[0].fd = gyro_fd;                // 감시할 파일 번호
    fds[0].events = POLLIN;             // "새 데이터가 들어왔는지(POLLIN)만 감시해라!"

    // 4. 배열 1번 방: 기압계 센서 세팅
    fds[1].fd = baro_fd;
    fds[1].events = POLLIN;

이렇게 세팅된 fds 배열은 이제 커널에게 던져줄 완벽한 ’감시 대상 명단’이 되었다.

2. px4_poll 호출: 깊은 잠에 빠지다

이제 대망의 px4_poll 함수를 호출할 차례다. 이 함수가 호출되는 순간, 이 필터 모듈의 스레드(Task)는 하던 일을 완전히 멈추고 커널의 대기열(Wait Queue)로 넘어가 깊은 잠(Blocked State) 에 빠진다.

    // 5. 커널아, 이 두 센서 중 하나라도 데이터가 올 때까지 나를 깨우지 마라!
    //    단, 1000ms(1초)가 넘도록 아무것도 안 오면 강제로 깨워줘. (타임아웃 설정)
    int poll_ret = px4_poll(fds, 2, 1000); 

    // ---- [이 시점에서 스레드는 정지되고, 버스는 다른 모듈에게 양보됨] ----

px4_poll은 매우 영리하다. 저 2개의 토픽 중 단 하나라도 orb_publish()가 발생하는 그 나노초(ns)의 찰나에 하드웨어 인터럽트를 타고 나의 스레드를 깨워준다. 앞서 배운 이벤트 기반 콜백(SubscriptionCallbackWorkItem)과 동일한 제로-레이턴시(Zero-latency) 기상 효과를 다중 채널에서 누릴 수 있는 것이다.

3. 폴링의 결과 분석과 수거

스레드가 다시 깨어났다면, px4_poll의 반환값(poll_ret)을 분석하여 나를 깨운 범인이 누구인지, 혹은 1초가 넘도록 아무도 안 와서 강제 기상(Timeout)된 것인지 추궁해야 한다.

    // 6. 결과 분석
    if (poll_ret == 0) {
        // [타임아웃 방어]: 1초 동안 자이로도, 기압계도 아무런 데이터가 오지 않았다.
        // 센서가 죽었거나 케이블이 뽑혔을 확률이 높다! (Failsafe 발동)
        printf("WARNING: 센서 데이터 1초간 유실됨!\n");
        return;

    } else if (poll_ret < 0) {
        // 에러 발생
        return;

    } else {
        // [성공]: 1개 이상의 센서에 데이터가 도착했다!

        // 7-1. 자이로 우체통에 편지가 왔나?
        if (fds[0].revents & POLLIN) {
            sensor_gyro_s gyro_data;
            orb_copy(ORB_ID(sensor_gyro), gyro_fd, &gyro_data);
            // 자이로 데이터 처리 로직...
        }

        // 7-2. 기압계 우체통에 편지가 왔나?
        if (fds[1].revents & POLLIN) {
            sensor_baro_s baro_data;
            orb_copy(ORB_ID(sensor_baro), baro_fd, &baro_data);
            // 기압계 데이터 처리 로직...
        }
    }
}

이 구조는 아름답고 고전적이지만, EKF 같은 정밀 센서 융합기에서는 여전히 치명적인 결점 하나가 남는다.
위 코드처럼 짜면 기압계(20Hz)가 먼저 도착해서 깨어났을 때, 아직 자이로 데이터(250Hz)의 최신 값이 도달하지 않아 두 센서의 **타임스탬프(TimeStamp)가 미묘하게 엇나가는 현상(Phase Shift)**이 발생한다.

이 파편화된 시간의 간극을 어떻게 메워야 서로 다른 주파수를 쏘아대는 4개의 센서를 오차 없이 하나의 시공간으로 정렬(Time Alignment)시킬 수 있을까?
poll() 함수의 타임아웃(Timeout) 꼼수를 활용한 데이터 점 보간(Interpolation) 기술을 다음 장(21.7.4.1.1)에서 뜯어보자.