쿼드콥터 자세제어기를 PID로 짜고, 최근에는 자이로스코프 센서 모델에 바이어스 랜덤워크 + 노이즈를 반영하는 작업을 했다. rk4를 이용해서 매 스텝마다 현재 각속도를 다시 측정해서 넣어주는 작업을 반복했는데 이 과정이 closed loop system과 연관이 있어 정리해본다.

(출처: https://www.newport.com/n/open-vs-closed-loop-picomotor)
기준 입력만으로 제어 신호를 만들어서 출력을 제어하는 방식이다. 제어기는 지금 실제로 뭐가 일어나고 있는지를 전혀 모른 채 미리 정해둔 입력만 내보낸다.
출력 신호가 제어 동작에 직접적인 영향을 주는 시스템이다. 입력 신호와 피드백 신호의 차이(오차)를 활용한다. 이 오차가 controller에 전달되어 오차를 줄이는 방향으로 제어 신호를 계속 갱신하여 최종적으로 시스템 출력이 원하는 값에 도달하도록 한다.
드론의 자세제어를 파이썬으로 실습해보고 있다. 지금까지 완성된 자세제어기(AttitudeController.compute_torque)는 목표 자세만 받는 게 아니라 현재 자세(q_current)와 현재 각속도(omega)를 매번 인자로 받는다.
def compute_torque(
self, q_current: np.ndarray, q_target: np.ndarray, omega: np.ndarray, dt: float
) -> np.ndarray:
e = attitude_error(q_current, q_target)
self._integral += e * dt
self._integral = np.clip(self._integral, -self.integral_limit, self.integral_limit)
torque = self.kp * e + self.ki * self._integral - self.kd * omega
return torque
만약 이게 open loop였다면, 목표 자세 하나만 놓고 "이 자세로 가려면 이 정도 토크를 몇 초간 주면 된다"는 식으로 시간에 대한 토크 프로파일을 미리 계산해서 그대로 흘려보냈을 것이다. 문제는 쿼드콥터에는 모델이 완벽히 잡아내지 못하는 것들이 항상 있다는 점이다. 질량/관성 추정 오차, 모터 응답 편차, 외란(바람) 같은 것을 open loop 제어기는 전혀 모르기 때문에, 실제 자세가 계획과 조금이라도 어긋나면 그 오차를 절대 스스로 줄이지 못한다.
실제로 시뮬레이션 루프(simulate_attitude_stabilization)를 보면 매 스텝마다 이 순환이 일어난다.
for i in range(int(duration/dt)):
true_omega = state_current[10:13]
omega_for_control = gyroscope.measure(true_omega, dt) if gyroscope is not None else true_omega
torque = ctrl.compute_torque(state_current[6:10], state_target[6:10], omega_for_control, dt)
...
state_current = rb.rk4_step(state_current, thrust, torque, dt, params)
현재 자세/각속도를 측정 → 오차 계산 → 토크 산출 → 동역학에 적용해서 다음 상태 생성 → 그 다음 상태를 다시 측정, 이 순환이 매 dt마다 반복된다. 이 "다시 측정해서 다시 넣는다"는 부분이 바로 폐루프라고 할 수 있다.
여기서 omega_for_control은 실제 각속도(true_omega)가 아니라 gyroscope.measure()를 거친 값이다. 즉 바이어스 랜덤워크와 노이즈가 낀 측정값이다. 그래서 이번에 자이로 노이즈를 배선한 작업을 통해 폐루프가 이상적인 피드백이 아니라 노이즈 낀 피드백을 받아도 여전히 오차를 줄여나가는지를 확인하고 싶었다.
다음 단계로는 이 노이즈 낀 피드백을 그대로 컨트롤러에 흘려보내는 대신, EKF 같은 상태추정기를 거쳐서 필터링된 값을 피드백으로 사용해 볼 예정이다.