21.6.3.1. ScheduleOnInterval()의 타이머 인터럽트 활용
드론의 제어 루프는 심장 박동과 같다. 1초에 100번 뛰기로 했다면, 정확히 10밀리초(10,000 마이크로초)마다 한 번의 오차도 없이 맥박이 뛰어야만 기체가 안정적으로 하늘을 날 수 있다.
이 기계적인 정주기 맥박을 만들어내기 위해 CustomApp 모듈의 초기화 부분(start() 또는 init())에서 우리는 부모 클래스의 메서드인 **ScheduleOnInterval()**을 호출하여 영구적인 스케줄링 예약을 걸어둔다.
// CustomApp.cpp
int CustomApp::start() {
// 100Hz(10ms = 10000us) 주기로 내 Run() 함수를 무한 반복 실행해 줘!
ScheduleOnInterval(10_ms);
return PX4_OK;
}
이 단 한 줄의 코드가 실행되는 순간, 내부적으로는 픽스호크 프로세서(STM32)의 **하드웨어 타이머 인터럽트(Hardware Timer Interrupt)**와 NuttX 커널 스케줄러 간의 숨 가쁜 협잡이 시작된다.
1. ScheduleOnInterval()의 내부 타이머 메커니즘
usleep() 함수가 단순히 스레드를 기절시키고 시간이 흐르길 기다리는 바보 같은 방법이라면, ScheduleOnInterval()은 스마트 알람 시계를 맞춰놓고 내 할 일(다른 스레드 연산)을 하러 떠나는 방식과 같다.
- 타이머 세팅:
ScheduleOnInterval(10000)이 호출되면, PX4 커널은 현재의 절대 시간(hrt_absolute_time)을 확인하고, 그로부터 정확히 10,000us 뒤의 시간에 도달하면 알람이 울리도록 하드웨어 타이머 레지스터를 세팅한다. - 스레드 양보: 알람을 세팅한 보따리(
work_s)는 버스에 타지 않고 대합실(Timer Queue)에서 조용히 대기한다. 그동안 빈 버스는 다른 모듈들의 화물을 실어나르며 CPU를 100% 알뜰하게 사용한다. - 인터럽트 발생 (ISR): 정확히 10,000us가 흐른 순간, STM32 칩 내부의 하드웨어 클럭이 “띠링!” 하고 **인터럽트(Hardware Interrupt)**를 발생시킨다.
- 워크 큐 삽입: 인터럽트 서비스 루틴(ISR)은 하던 모든 일을 0.1us 만에 중단하고, 대합실에 있던 내 작업 보따리(
work_s)를 낚아채어 내가 타기로 예약했던 진짜 버스(예:wq_rate_ctrl)의 운전석 옆(Ready Queue)에 강제로 꽂아 넣어버린다. Run()함수 발동: 버스가 출발(Context Switch)하는 즉시 내 C++ 객체의Run()함수가 실행된다.
2. 마이크로초(us) 단위의 정밀함 (HRT)
여기서 가장 놀라운 점은 이 알람의 정밀도가 밀리초(ms) 단위의 엉성한 소프트웨어력이 아니라, **HRT(High Resolution Timer)**라는 픽스호크 전용 하드웨어 마이크로초 단위 카운터를 기반으로 굴러간다는 사실이다.
우리가 10_ms (10,000us)를 예약했다면, 이 알람은 10,001us도 아니고 9,999us도 아닌 최대한 정확히 10,000us 근방에서 하드웨어를 때려버린다.
이 미친듯한 하드웨어 인터럽트 연결고리 덕분에, 멀티 코어도 아닌 단일 코어 픽스호크 프로세서에서 50개가 넘는 모듈들이 서로 “내가 먼저 돌겠다“며 싸우지 않고, 각자의 정밀한 주파음(Hz)에 맞춰 오케스트라처럼 동작할 수 있는 것이다.
하지만 이 알람 시계(타이머 큐)에 객체를 밀어 넣을 때 PX4 커널이 사용하는 기괴한 hrt_absolute_time() 절대 시간 꼼수가 하나 숨어있다. 다음 장(21.6.3.1.1)에서 큐 버퍼에 타이머를 밀어 넣는 진짜 수학 공식을 확인해 보자.