Chapter 21. 사용자 정의 앱(Custom Application) 및 모듈 개발
- 21.1. PX4 사용자 정의 애플리케이션 및 모듈 아키텍처 심층 분석
- 21.1.1. PX4 소프트웨어 스택 내 사용자 정의 코드의 계층적 위치
- 21.1.1.1. 응용 계층(Application Layer)과 미들웨어 계층(Middleware Layer)의 경계
- 21.1.1.1.1. POSIX API 기반 독립 프로세스와 PX4 내부 스레드의 메모리 공간 공유(Flat Memory) 특성
- 21.1.1.1.2. NuttX RTOS 플랫(Flat) 빌드와 보호(Protected) 빌드 모드에서의 커스텀 모듈 동작 차이 분석
- 21.1.1.2. 애플리케이션, 모듈, 드라이버 컴포넌트 간의 컨텍스트(Context) 분리
- 21.1.1.2.1. 비동기식 I/O 처리를 전담하는 백그라운드 데몬(Daemon)의 생명주기
- 21.1.1.2.2. 하드 리얼타임(Hard Real-time) 요구사항을 가지는 인루프(In-loop) 제어 모듈의 제약 사항
- 21.1.2. 기존 NuttX Task 방식과 최신 Work Queue 방식의 스케줄링 패러다임 전환
- 21.1.2.1. 독립 태스크(
task_create) 방식의 구조적 한계점
- 21.1.2.1.1. 개별 태스크 스택(Stack) 할당으로 인한 SRAM 메모리 파편화 및 고갈 문제
- 21.1.2.1.2. 선점형(Preemptive) 컨텍스트 스위칭으로 인한 분기 예측 실패 및 CPU 캐시 미스(Cache Miss) 증가
- 21.1.2.2. 협력적(Cooperative) Work Queue 기반 런루프(Run-loop)의 이점
- 21.1.2.2.1. 스레드 풀(Thread Pool) 공유를 통한 메모리 풋프린트(Footprint) 최소화 원리
- 21.1.2.2.2. 블로킹(Blocking) API 호출 금지 원칙과 이를 위반했을 때 발생하는 큐 지연(Queue Starvation) 현상
- 21.2. 커스텀 모듈의 빌드 시스템(CMake/Kconfig) 통합 로직 상세
- 21.2.1. PX4 Kconfig 트리(Tree) 구조 내 커스텀 모듈 주입
- 21.2.1.1.
src/modules/custom_app/Kconfig 파일의 구문론적 설계
- 21.2.1.1.1.
menuconfig CUSTOM_MODULES 블록 생성을 통한 GCS 및 빌드 도구에서의 가시성 확보
- 21.2.1.1.2.
depends on 지시어를 활용한 하드웨어 아키텍처(예: ARCH_CHIP_STM32H7) 및 타 모듈 의존성 검증
- 21.2.2.
px4_add_module() 매크로의 내부 동작 메커니즘
- 21.2.2.1.
CMakeLists.txt 내 컴파일러 지시자 및 링커 옵션 제어
- 21.2.2.1.1.
MODULE 속성과 바이너리 심볼 테이블(Symbol Table) 네이밍 룰의 상관관계
- 21.2.2.1.2.
STACK_MAIN 속성에 따른 NuttX TCB(Task Control Block) 스택 사이즈 런타임 할당 로직
- 21.2.2.1.3.
PRIORITY 파라미터가 SCHED_FIFO 및 SCHED_RR 스케줄링 정책에 미치는 영향
- 21.2.2.2. 정적 라이브러리 링크 및 uORB 헤더 의존성 결합
- 21.2.2.2.1.
DEPENDS에 uORB_msgs를 명시하여 메시지 생성 전처리(Pre-processing) 순서를 보장하는 원리
- 21.2.3. 타겟 보드 구성 파일(
.px4board)을 통한 펌웨어 컴파일
- 21.2.3.1.
boards/ 디렉토리 하위의 default.px4board 파싱(Parsing) 과정
- 21.2.3.1.1.
CONFIG_MODULES_CUSTOM_APP=y 구문이 CMake의 add_subdirectory()를 호출하는 트리거 로직
- 21.2.3.2. Out-of-tree 빌드 디렉토리(
build/px4_fmu-vX_default/) 내 중간 생성물 분석 및 캐시 클리어(make clean)
- 21.3. C/C++ 기반 최소 단위 모듈(Hello World) 소스 코드 해부
- 21.3.1. 모듈 엔트리 포인트(Entry Point)의 C 링키지(Linkage) 설계
- 21.3.1.1.
extern "C" __EXPORT 지시자의 바이너리 레벨 역할
- 21.3.1.1.1. C++ 네임 맹글링(Name Mangling) 방지를 통한 NSH(NuttShell) 심볼 테이블 검색 기능 지원
- 21.3.1.2.
<module>_main 함수의 인자 전달 메커니즘
- 21.3.1.2.1. NSH에서 입력된 문자열을
argc, argv 배열로 토크나이징(Tokenizing)하는 NuttX 내부 로직
- 21.3.2. POSIX
getopt() 기반 커맨드라인 파서(Parser) 구현
- 21.3.2.1. 옵션 플래그 파싱 및 유효성 검사 루틴 작성
- 21.3.2.1.1.
while ((myopt = getopt(argc, argv, "ht:v:")) != EOF) 루프의 포인터 제어
- 21.3.2.1.2.
optarg 전역 변수 메모리 안전성 확보 및 정수/실수형 변환(strtol, strtof) 에러 핸들링
- 21.3.3. 시뮬레이션 환경(SITL) 컴파일 및 로깅 테스트
- 21.3.3.1.
make px4_sitl jmavsim 환경에서의 모듈 실행 컨텍스트
- 21.3.3.1.1. 호스트 OS(Linux/macOS) 내 POSIX 스레드(pthreads)로 매핑되는 과정
- 21.3.3.2. 백그라운드 실행 연산자(
&)의 처리 로직 및 프로세스 ID(PID) 반환 확인
- 21.4.
ModuleBase 템플릿 기반 PX4 표준 모듈 설계 기법
- 21.4.1. CRTP(Curiously Recurring Template Pattern) 다형성 최적화
- 21.4.1.1.
ModuleBase<MyModule> 선언의 컴파일 타임 타입 검증 원리
- 21.4.1.1.1. 가상 함수 테이블(vtable) 룩업 오버헤드를 제거하고 인라인(Inline) 확장을 유도하는 템플릿 메타프로그래밍
- 21.4.2. 모듈 생명주기 관리 및 원자적(Atomic) 상태 제어
- 21.4.2.1. 상태 변이 로직 스레드 안전성(Thread-safety) 확보
- 21.4.2.1.1. 내부
_task_id 변수에 대한 동시 접근을 막기 위한 Mutex 및 스핀락(Spinlock) 적용
- 21.4.2.2. 필수 오버라이드 메서드 구현 디테일
- 21.4.2.2.1.
start(): instantiate() 호출 전후의 메모리 힙(Heap) 검사 및 동적 할당 실패에 대한 Fallback 시퀀스
- 21.4.2.2.2.
stop(): request_stop() 호출 이후 워커 스레드가 안전하게 종료될 때까지 대기하는 조건 변수(Condition Variable) 사용법
- 21.4.2.2.3.
status(): PX4_INFO 매크로를 이용해 NSH에 현재 실행 주기, 지연 시간(Latency) 등 통계 데이터 출력 포맷팅
- 21.4.3. 커스텀 명령어 라우터(Router) 구현
- 21.4.3.1.
custom_command() 내부의 다중 분기 로직
- 21.4.3.1.1.
strcmp 최적화를 통한 서브 커맨드 처리 및 print_usage() 연동을 통한 잘못된 명령 피드백 제어
- 21.5.
ModuleParams를 활용한 시스템 파라미터 결합(Binding) 기술
- 21.5.1. 매개변수 클래스 다중 상속 및 초기화 메커니즘
- 21.5.1.1.
public ModuleParams 상속 구조와 생성자 초기화 리스트(Initializer List)
- 21.5.1.1.1.
ModuleParams(nullptr) 호출을 통한 전역 파라미터 리스트 연결 원리
- 21.5.2.
DEFINE_PARAMETERS 매크로의 메타데이터 변환 과정
- 21.5.2.1.
ParamInt 및 ParamFloat 템플릿의 메모리 바인딩
- 21.5.2.1.1. 매크로에 정의된
px4::params::CUSTOM_APP_MAX 심볼이 펌웨어 빌드 시 XML 메타데이터로 추출되는 파이프라인
- 21.5.2.1.2. C API (
param_get) 대비 C++ 래퍼 클래스가 제공하는 포인터 타입 캐스팅 오류 사전 방지 효과
- 21.5.3. 런타임(Runtime) 파라미터 동적 업데이트 설계
- 21.5.3.1.
parameter_update_s 토픽 구독 모델 구현
- 21.5.3.1.1. 제어 루프 진입 시
updated() 플래그의 비트 마스킹(Bit Masking) 고속 검사
- 21.5.3.1.2. 파라미터 업데이트 발생 시
updateParams()를 호출하여 클래스 멤버 변수를 락-프리(Lock-free) 방식으로 동기화하는 기법
- 21.6. Work Queue 스레드 풀 및 스케줄러(Scheduler) 제어
- 21.6.1.
px4::WorkItem 상속 구조와 내부 데이터 추상화
- 21.6.1.1.
WorkItem 객체 생성 시 NuttX 커널 큐 연동
- 21.6.1.1.1.
work_s 구조체의 함수 포인터와 Run() 메서드 주소 간의 바인딩 원리
- 21.6.2. 타겟 스레드 풀(Thread Pool)의 전략적 선택
- 21.6.2.1. 제어 주파수 및 지연성(Latency) 요구에 따른 분류
- 21.6.2.1.1.
wq_rate_ctrl (초고우선순위): 1kHz 이상 자이로/가속도 데이터 처리용 스택 할당
- 21.6.2.1.2.
wq_hp_default / wq_lp_default: 50Hz~250Hz 위치 제어 및 비동기 I/O 로직용 워커 분배
- 21.6.3. 타임스탬프 기반 주기적 실행(Scheduled Work) 로직 구현
- 21.6.3.1.
ScheduleOnInterval()의 타이머 인터럽트 활용
- 21.6.3.1.1.
hrt_absolute_time() 기반 마이크로초(us) 단위 절대 시간 스케줄링 큐 삽입
- 21.6.3.1.2. 실제 실행된 타임스탬프와의 편차(Jitter)를 계산하여 루프 실행 주기를 동적으로 보정하는 알고리즘
- 21.6.4. 블로킹 회피 메커니즘(Non-blocking Execution)
- 21.6.4.1.
Run() 메서드 내부의 파일 입출력 및 폴링(Polling) 제한
- 21.6.4.1.1. 디스크 I/O 대기 발생 시 Work Queue 전체 스레드가 멈추는 데드락(Deadlock) 시나리오 분석
- 21.6.4.1.2. 장시간 연산이 필요한 경우 유한 상태 기계(FSM)를 쪼개어 여러 프레임에 걸쳐 연산하는 타임 슬라이싱(Time-slicing) 기법
- 21.7. uORB 비동기 메시징 및 이벤트 기반 실행(Event-driven) 최적화
- 21.7.1. C++ 래퍼(
uORB::Subscription)를 활용한 메모리 복사 제어
- 21.7.1.1.
SubscriptionData<T> 템플릿의 객체 내장 특성
- 21.7.1.1.1. 동적 할당을 회피하고 모듈 스택 내에 메시지 버퍼를 미리 확보하여 페이지 폴트(Page Fault)를 방지하는 구조
- 21.7.1.2. 다중 인스턴스 센서 데이터의 선택적 구독
- 21.7.1.2.1.
instance 매개변수를 조정하여 3중 리던던시(Triple Redundancy) IMU 중 특정 인덱스의 데이터만 필터링하는 방법
- 21.7.2. 인터럽트 핸들러 레벨의 콜백 기반 스케줄링 트리거
- 21.7.2.1.
SubscriptionCallbackWorkItem 클래스의 이벤트 바인딩
- 21.7.2.1.1. 센서 드라이버가 토픽을 발행(Publish)하는 즉시 콜백이 호출되어
ScheduleNow()로 해당 모듈을 스케줄러 큐 최상단에 푸시(Push)하는 지연 시간 제로화(Zero-latency) 기법
- 21.7.3. Lock-Free 방식의 토픽 데이터 발행(Publication)
- 21.7.3.1.
uORB::Publication<T> 초기화 및 광고(Advertisement) 로직
- 21.7.3.1.1.
orb_advert_t 핸들을 획득할 때 발생하는 파일 기술자(File Descriptor) 테이블 락(Lock) 우회 로직
- 21.7.3.1.2. 멀티 코어(Multi-core) H7 프로세서 환경에서 데이터를 기록할 때 발생하는 메모리 배리어(Memory Barrier) 및 캐시 코히런시(Cache Coherency) 문제 해결
- 21.7.4. 폴링(Polling)을 활용한 다중 토픽 동기화 융합
- 21.7.4.1.
px4_pollfd_struct_t 배열 구축 및 동기화 대기
- 21.7.4.1.1.
poll() 타임아웃을 이용하여 GPS, Baro, IMU 데이터의 타임스탬프를 동기화하고 누락(Drop)된 프레임을 보간(Interpolation)하는 처리 방법
- 21.8. 응용 설계: 다중 상태(FSM) 기반 페이로드 자동 제어 모듈 개발
- 21.8.1. 실시간 입력 토픽과 상태 천이 설계
- 21.8.1.1.
vehicle_local_position_s 및 vehicle_status_s 토픽 교차 분석
- 21.8.1.1.1. 네비게이션 상태(Nav State)가 Mission 혹은 Offboard 모드일 때만 활성화되도록 인터록(Interlock) 설정
- 21.8.2. 유한 상태 기계(FSM) 아키텍처 코딩
- 21.8.2.1.
enum class AppState { INIT, CLIMB, TRIGGER, CONFIRM_ACK, RTL } 상태 정의
- 21.8.2.1.1.
switch-case 블록 내에서 current_state 변이 전, 조건 평가와 진입 액션(Entry Action), 퇴장 액션(Exit Action)을 분리하는 구조체 패턴 구현
- 21.8.3. 센서 노이즈 필터링 및 이벤트 발동(Trigger) 조건식
- 21.8.3.1. 고도 데이터의 로우 패스 필터(Low Pass Filter, LPF) 적용
- 21.8.3.1.1. 기압계 돌풍(Gust)에 의한 급격한 고도 스파이크를 오인식하지 않도록 1차 IIR(Infinite Impulse Response) 필터 구현 및 알파(Alpha) 값 튜닝
- 21.8.4. 제어 명령(
vehicle_command) 발행 및 MAVLink 응답 검증
- 21.8.4.1.
COMMAND_LONG 메시지 패키징 규칙
- 21.8.4.1.1. 페이로드 투하 명령을 위한
param1 ~ param7 포맷팅 및 타겟 시스템/컴포넌트 ID 주입 로직
- 21.8.4.2.
vehicle_command_ack_s 구독을 통한 트랜잭션 종료 판단
- 21.8.4.2.1. ACK 수신 실패 타임아웃 계산 및 최대 재시도(Max Retries) 도달 시 GCS에
STATUSTEXT 에러 로깅을 브로드캐스팅하는 예외 처리 루틴
- 21.9. 펌웨어 부트 시퀀스 연동 및 프로세스 데몬화(Daemonization) 로직
- 21.9.1. ROMFS/init.d 부트 스크립트 실행 트리(Execution Tree) 분석
- 21.9.1.1.
rcS 메인 스크립트 내 파라미터 로딩 및 마운트 프로세스
- 21.9.1.1.1. 하드웨어 초기화(
rc.board_defaults) 이후 센서 앱 구동 시점 사이에 커스텀 모듈을 삽입하기 위한 스크립트 실행 순서(Order of Execution) 파악
- 21.9.2.
SYS_AUTOSTART 기반 기체 프로파일 연동 시작
- 21.9.2.1.
ROMFS/px4fmu_common/init.d/rc.mc_apps 내 조건부 실행 로직 구성
- 21.9.2.1.1.
param compare CUSTOM_APP_EN 1 쉘 스크립트 구문을 사용하여 파라미터 값 활성화 시 모듈의 start 명령을 호출하는 분기문 작성
- 21.9.3. 안전한 백그라운드 전환 및 시스템 셧다운(Shutdown) 대응
- 21.9.3.1. 모듈의 데몬 분리 및 I/O 리다이렉션
- 21.9.3.1.1. 표준 입출력(stdout, stderr) 파이프를 끊고
PX4_INFO 매크로를 통한 uORB 로거 파일(ULog)로의 출력 전환 기법
- 21.9.3.2. 기체 전원 차단 전 이벤트 수신 및 리소스 완전 반환
- 21.9.3.2.1.
VEHICLE_CMD_PREFLIGHT_REBOOT_SHUTDOWN 커맨드 수신 시 힙 메모리 해제 및 진행 중인 워커 스레드를 강제 인터럽트하여 안전하게 종료하는 클린업(Cleanup) 시퀀스
- 21.10. 커스텀 모듈의 딥(Deep) 성능 분석, 프로파일링 및 GDB 트러블슈팅
- 21.10.1.
perf_counter API를 활용한 마이크로(Micro) 단위 지연 시간 측정
- 21.10.1.1. 성능 카운터 셋업 및 NSH
perf 명령어 추적
- 21.10.1.1.1.
perf_alloc(PC_ELAPSED, "module_run_latency") 카운터 할당 후 perf_begin(), perf_end() 매크로를 이용해 Run() 루프의 실행 소요 시간을 나노초 단위로 측정하는 방법
- 21.10.2. NSH 환경에서의 하드웨어 리소스 및 메모리 릭(Leak) 추적
- 21.10.2.1.
top, free, work_queue 출력 데이터 분석을 통한 병목 도출
- 21.10.2.1.1.
top 출력 시 특정 모듈의 스택 가용량(Stack Margin)이 임계치(예: 100 bytes) 이하로 떨어질 때 스택 오버플로우를 예방하기 위한 CMake STACK_MAIN 재설정 기준
- 21.10.2.1.2.
work_queue status 명령어에서 큐 대기 시간(Waiting Queue Time)이 길어지는 워커를 찾아 별도의 독립 스레드로 분리(Offloading)하는 리팩토링 전략
- 21.10.3. 하드 폴트(Hard Fault) 분석 및 크래시(Crash) 로그 덤프 복원
- 21.10.3.1. 마이크로컨트롤러 크래시 덤프 구조 파악
- 21.10.3.1.1. SD 카드에 기록된
fault_*.log의 레지스터 정보(PC, LR, SP) 추출
- 21.10.3.1.2.
arm-none-eabi-addr2line 유틸리티를 활용하여 PC(Program Counter) 주소를 타겟 C++ 파일의 라인 넘버로 역추적(Backtrace)하여 Null Pointer 오류 발생 지점 색출
- 21.10.4. JTAG/SWD 하드웨어 디버거 및 GDB 연동 타겟 디버깅
- 21.10.4.1. SEGGER J-Link 하드웨어 타겟 연결 및 GDB 서버 세션 수립
- 21.10.4.1.1.
.gdbinit 스크립트에 target extended-remote :2331 매핑 후, 특정 센서 데이터가 수신되는 순간(Watchpoint) 실행을 정지시키는 메모리 브레이크포인트 심화 기법
- 21.10.4.2. VSCode의
launch.json 설정을 통한 Cortex-Debug 익스텐션 그래픽 환경 디버깅 연동 및 스텝 인(Step-in) 변수 조사(Variable Inspection)