28.3.1.1. updateInitialize(), update(), activate() 가상 함수(Virtual Function)의 생명주기
FlightTask 프레임워크가 템플릿 메서드 패턴(Template Method Pattern)으로 뼈대를 잡았다면, 그 뼈대를 타고 흐르는 ’혈류’는 바로 가상 함수(Virtual Function)들의 생명주기(Lifecycle)다.
상위 모듈인 FMM(Flight Mode Manager)은 팩토리 패턴으로 찍어낸 자식 비행 객체를 마치 다마고치 키우듯 엄격한 순서대로 깨우고, 먹이고, 운동시킨다. 이 3단계 인터페이스 생명주기는 모든 PX4 커스텀 비행 모드 개발자가 달달 외워야 할 객체 지향 제어의 절대적인 십계명과 같다.
1. 생명의 탄생과 거부권: activate()
수동 모드에서 자동 미션 모드로 파일럿이 스위치를 튕기는 찰나, 램(RAM)에는 새로운 FlightTaskAutoMission 객체가 new 연산자를 통해 태어난다. 하지만 태어났다고 해서 곧바로 궤적 계산 루프에 투입되는 것은 아니다.
객체가 힙 영역에 할당된 직후, FMM 데몬은 제일 처음으로 activate() 가상 함수를 단 1회 호출하여 ’면접’을 본다.
// FlightTask의 자식 클래스 내부에 오버라이딩된 activate() 의사 코드
bool activate() override {
if (!GPS_is_valid) {
return false; // 나 일 못해.
}
reset_integrators();
set_initial_target_to_current_position();
return true; // 정상 부팅 완료
}
- 초기화의 골든 타임: 이 함수는 모드가 전환되어 최초로 활성화될 때, 딱 한 번 실행되는 황금 같은 초기화 구역이다. 적분기(Integrator)에 쌓여있는 쓰레기 값들을 0으로 청소하거나, “현재 내가 떠 있는 위치를 첫 번째 목표 궤적(
Setpoint)으로 락온(Lock-on)하라” 같은 초기 스냅샷 고정(Snapshot Hold) 작업을 여기서 수행한다. - 자기 부정권 (Self-Rejection): 가장 중요한 특징은 반환형이
bool이라는 점이다. 만약 자동 미션 객체가 태어났는데 GPS가 끊겨 있다면,activate()는 스스로false를 내뱉고 자살(생성 거부)을 선언한다. FMM은 이를 보고 즉각Fallback 모드(고도 유지 등) 객체를 새로 뽑아내는 비상 플랜을 가동한다.
2. 매 루프의 전처리 밥상 차리기: updateInitialize()
activate() 면접을 가뿐히 통과(true)한 객체는 비로소 FMM의 무한 궤도 Run() 루프 레이스에 탑승하게 된다. 하지만 메인 계산식인 update()가 돌기 직전, 매 틱(Tick)마다 어김없이 먼저 실행되는 프롤로그(Prologue) 함수가 바로 updateInitialize()다.
- 부모 주도의 인프라 구축: 앞 절에서 설명했듯, 이 함수는 주로 부모 클래스(
FlightTask) 단에서 구현되어 있다. (물론 자식 클래스가 원한다면override해서 기능을 추가할 수 있다.) 매 루프마다 uORB 미들웨어에서 최신 관성 센서(IMU), GPS 데이터, 수동 스틱 조작량 등을 쭈욱 빨아들여 와서 클래스 내부의 읽기 전용 구조체 변수들에 캐싱(Caching)해 놓는다. - 현재의 찰나 포착: “나는 지금 상공 50.2m에, 동쪽으로 3m/s 속도로 날고 있고, 조종인은 스틱을 50% 밀고 있다“는 현재 기체의 물리적 실체(Ground Truth)를 사진 찍듯 메모리에 정지(Freeze)시켜, 잠시 뒤 실행될 수학 계산식이 요동치는 센서값에 흔들리지 않도록 방파제 역할을 해준다.
3. 심장 박동과 수학의 결정체: update()
밥상(데이터)이 완벽하게 차려지면, FMM은 마침내 호루라기를 불어 순수 가상 함수인 update()를 매 루프(약 400Hz 속도)마다 맹렬하게 호출한다.
- 순수한 수학적 샌드박스: 이 블록 안에는 uORB 통신이나 하드웨어 타이머 같은 더러운 인프라 코드가 끼어들 틈이 없다. 오직 제어공학과 선형대수학만이 숨 쉰다. 자식 비행 모드 개발자는
updateInitialize()가 차려놓은 현재 위치/속도 센서 변수들을 주물러서, “다음 0.0025초 뒤에 우리 드론은 어떤 속도로 어느 방향을 향해 날아가야 하는가?“라는 목표 수치(Trajectory Setpoint)를 죽어라 산출해 낸다. - 에러 항의 누적 (Integration):
dt(델타 타임)가 적용되는 미적분 연산도 여기서 이루어진다. 현재 목표 좌표와 실제 좌표의 오차가 계산되어 PID 제어기의 I-term 에러 변수에 차곡차곡 쌓이며 기체의 바람 저항을 수학적으로 이겨낸다.
이 update() 함수가 산출물을 뱉어내고 종료되면, FMM은 그 갓 짜낸 신선한 궤적 목표값(Setpoint)을 낚아채서 하위 자세 제어기(Attitude Controller)에게 던져주고 잠에 빠진다. 그리고 다음 틱(Tick)이 오면 다시 updateInitialize() -> update() 순서로 톱니바퀴를 맹렬하게 굴리며, 이 3단계 생명주기 프레임워크야말로 수십 가지 모드가 엉키지 않고 우아하게 병렬 생존할 수 있는 PX4 아키텍처의 심장 박동(Heartbeat)이다.