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)에서 뜯어보자.