29.6.2.2. 내부 제어기 초기화: 모드 전환 감지 시 각속도 제어기 내의 PID 객체 내부 누적 상태 및 이전 오차 변수를 클리어하는 초기화 함수 호출 추적

29.6.2.2. 내부 제어기 초기화: 모드 전환 감지 시 각속도 제어기 내의 PID 객체 내부 누적 상태 및 이전 오차 변수를 클리어하는 초기화 함수 호출 추적

1. 개요 및 제어기 내부 메모리의 위험성

드론의 자세 제어와 각속도 제어를 담당하는 비례-적분-미분(PID) 제어기는 과거의 상태를 기억하는 상태 의존적(Stateful) 알고리즘이다. 특히 적분기(I-Term)는 오랜 시간 축적된 과거의 오차를 메모리(_torque_int 등)에 보관하며, 미분기(D-Term)는 속도 변화율을 계산하기 위해 직전 사이클의 오차나 센서 필터값(_angular_accel 등)을 보관한다.

만약 조종자가 비행 모드를 수평 유지(Stabilized)에서 위치 고정(Position)으로 변경하거나, 그 반대로 전환할 때 이 PID 객체가 가진 과거의 ’기억’을 강제로 지워주지 않는다면 어떻게 될까? 과거의 모드에서 바람에 맞서느라 잔뜩 누적해둔 적분기(Windup) 값이, 새로운 모드가 시작되자마자 모터로 한꺼번에 쏟아져 들어가 기체를 뒤집어버리는(Flip) 끔찍한 오작동이 발생하게 된다.

최신 PX4-Autopilot 아키텍처는 이러한 유령(Ghost) 제어 출력을 막기 위해 모드 전환 엣지(Edge)에서 각속도 제어기 및 하위 제어 변수들을 완벽하게 영점화(Zeroing)하는 초기화 파이프라인을 가동한다.

2. 초기화 함수 호출 파이프라인 (Call Trace)

모드 전환 시 제어기 내부 상태 변수가 클리어(Clear)되는 과정은 상위(Commander)에서 시작되어 최하위(Rate Controller)까지 계층적으로 전달된다.

  1. State Machine Helper: 조종기 스위치 변동을 감지하고 vehicle_status uORB 토픽의 main_state를 갱신한다.
  2. Flight Mode Manager (flight_mode_manager.cpp): 상태 변화를 감지하고, 기존에 돌고 있던 태스크를 종료시킨 후 새로운 FlightTask(예: FlightTaskManualAltitude)의 인스턴스를 찾아 activate() 메서드를 호출한다.
  3. Attitude Controller (mc_att_control): 모드 매니저의 상태 변화나 플라이트 태스크의 리셋 플래그를 감지하면, 자신이 감싸고 있는 래퍼(Wrapper) 로직을 초기화한다.
  4. Rate Controller (RateControl.cpp): 실질적인 PID 연산 객체로서, 외부에서 reset() 함수가 명시적으로 호출되면 내부의 적분 행렬과 가속도 필터 상태를 즉시 무효화한다.

3. 메모리 클리어 아키텍처 (Mermaid 설계도)

시간의 흐름에 따라 활성화된 PID 제어기 내부 메모리가 스위칭 이벤트에 의해 어떻게 소거되는지 나타낸 상태 흐름도이다.

graph TD
    A[시간 t-1: 이전 비행 모드 작동 중] --> B[(PID 내부 메모리)]
    B --> |바람 저항으로 인한 누적| C[적분 오차_torque_int: <br>Roll 0.1, Pitch -0.2]
    B --> |D항 노이즈 필터| D[과거 가속도_angular_accel: <br>Roll 0.05, Pitch 0.01]
    
    E[시간 t0: 조종자 비행 모드 전환!] --> F[FlightTask::activate() 트리거]
    F --> G[mc_att_control 전환 플래그 캡처]
    G --> H[RateControl::reset() 함수 호출]
    
    H --> I[행렬 제로잉 함수 .zero() 수행]
    
    I -.-> |클리어| C
    I -.-> |클리어| D
    
    C --> J[적분 오차_torque_int: 0, 0, 0]
    D --> K[과거 가속도_angular_accel: 0, 0, 0]
    
    J --> L[시간 t1: 신규 비행 모드 <br> 깨끗한 상태로 기동 시작]
    K --> L

4. 소스 코드 깊은 분석 (RateControl::reset())

가장 하위 단위에서 모터 토크를 결정짓는 각속도 제어기의 핵심 C++ 클래스는 src/modules/mc_rate_control/RateControl.cpp에 정의되어 있다. 이 클래스가 제공하는 reset() 메서드가 바로 튐(Jerking)을 방어하는 최후의 보루이다.

// src/modules/mc_rate_control/RateControl.cpp 발췌

void RateControl::reset()
{
    // 1. 적분기(Integral) 리셋
    // 이전 모드에서 지속적으로 쌓여왔던 P-Error의 누적 합을 3차원 벡터(Roll, Pitch, Yaw) 모두 0으로 덮어쓴다.
    // matrix::Vector3f 타입이므로 .zero() 메서드를 활용한다.
    _torque_int.zero();

    // 2. 미분기(Derivative) 노이즈 필터 리셋
    // 물리적인 자이로 센서의 노이즈를 억제하기 위해 각가속도를 로우패스 필터링(LPF)하여 
    // 저장해두던 과거 상태값을 0으로 리셋한다. 
    // 만약 이것을 지우지 않으면, 전환 순간 델타 타임(dt) 내에 비정상적인 스파이크가 발생하여 D 토크가 폭증한다.
    _angular_accel.zero();
}

RateControl::reset()을 호출하는 주체는 주로 상위 래핑 앱인 MulticopterRateControl (또는 PX4 버전에 따라 mc_att_control) 모듈이다. 상위 모듈은 조종기 스위치 변동 이벤트를 폴링(Polling)하거나 uORB 구독을 통해 파악한 즉시, 위 초기화 코드를 비행 제어 루프 진입 직전에 단 1회 삽입하여 실행한다.

5. Ardupilot 대비 아키텍처 구현 차이

새로운 제어로 돌입할 때 PID 메모리를 초기화해야 한다는 제어 공학적 결론은 동일하나, 객체를 다루는 방법에서 두 오픈소스는 뚜렷한 차이를 보인다.

  • P4X-Autopilot:
    모든 물리량 연산에 수학적 행렬(matrix::Vector3f) 라이브러리를 적극 도입하여, Roll, Pitch, Yaw의 세 가지 축(Axis) 상태 연산을 단일 행렬식 객체의 .zero() 메서드로 우아하게 통합 처리한다. 이는 코드의 가독성을 높이고 SIMD 최적화 등 하드웨어 가속에 매우 유리한 현대적 C++ 11/14 구조를 대변한다.
  • Ardupilot:
    Ardupilot의 경우 AC_PI_2DAC_PID라는 축 단위 제어 라이브러리를 광범위하게 사용하며, 각 모드 클래스 내의 init() 메서드를 시작할 때 명시적으로 reset_I()를 호출하여 적분기를 비운다. 하지만 Roll/Pitch/Yaw 등 각 축에 독립된 PID 인스턴스가 생성되어 있어 각각의 헬퍼 메서드를 개별적으로 호출(pitch_pid.reset_I(), roll_pid.reset_I())해야 하는 다소 전통적인 스칼라(Scalar) 기반 객체지향 처리의 차이를 보여준다.

결론적으로, PX4의 범플리스 트랜스퍼 철학은 내부 PID 제어기의 기억 상실 메커니즘(_torque_int.zero())과 EKF 목표점 동기화라는 강력한 양방향 보완 정책이 결합하여, 어떠한 가혹한 상태 전환 요구에서도 기체의 평정심을 유지하는 공학적 방어 체계를 완성하게 된다.