hello, robot

바퀴 로봇이 정해진 경로를 따라가게 만들 때 가장 먼저 정해야 하는 값이 look-ahead 거리예요. 경로 위에서 지금 위치보다 얼마나 앞선 지점을 목표로 삼을지의 거리인데, 이 값 하나로 주행 품질이 크게 달라져요.

감으로 정하면 근거가 남지 않으니 값만 바꾸고 나머지를 고정한 통제 실험으로 설계했어요. 작을수록 정밀해지지도, 클수록 안정되지도 않는 U자가 나왔어요.

알고리즘Pure Pursuit (기하 기반 경로 추종) 경로한 변 1 m 사각 · 5 cm 조밀화 지표cross-track error RMS · 최대 · 완주 시간 통제시드 고정 · 슬립 4% · 회전 오차 ±6%
최적 look-ahead
0.20
m · 경로 한 변의 20%
이탈 RMS
4.0
cm · 0.10 m 대비 −72%
완주 시간
28.4
초 · 최악의 0.42배
0.45 m 시행
14.8
m 이탈 · 발산
실물 yaw
−1338
도 · 정지 상태 누적

전방의 한 점만 좇는 제어

경로 추종에 쓰는 방법은 크게 셋이에요. 기하로 푸는 Pure Pursuit, 앞바퀴 기준의 횡오차와 헤딩오차를 함께 줄이는 Stanley, 미래 구간을 예측해 최적화로 푸는 MPC예요. 이번에 쓴 건 첫 번째고, 고른 이유는 튜닝 파라미터가 look-ahead 거리 하나로 압축되기 때문이에요. 값 하나만 움직이면 통제 실험이 그대로 성립해요.

동작은 단순해요. 로봇 앞쪽 경로 위에 목표점 하나를 잡고, 현재 위치에서 그 점을 지나는 원호를 그린 뒤 그 곡률만큼 조향해요. 목표점이 계속 앞으로 밀려나므로 로봇은 영원히 닿지 않는 점을 좇게 되고, 그 결과로 경로를 따라가요.

Pure Pursuit이 매 주기에 하는 일
목표점까지의 직선이 아니라, 목표점을 지나는 원호의 곡률이 조향량이 돼요.
계획 경로look-ahead 반경로봇이탈 오차목표점경로 위에서 앞으로만 이동이 원호의 곡률만큼 조향해요거리가 작으면곡률이 커져 경로 주변을 지그재그로 진동거리가 크면코너 안쪽을 잘라 크게 이탈

곡률을 계산한다고 하지만 실질은 cross-track error, 즉 경로와의 횡방향 거리에 대한 비례 제어에 가까워요. 목표점이 옆으로 벌어질수록 원호가 급해지고 조향이 세지니까요. Nav2의 기본 컨트롤러인 Regulated Pure Pursuit도 같은 계열이라, 여기서 익힌 감각이 그대로 이어져요.

이 로봇에 맞춘 변형
이번에 붙인 로봇은 연속 속도 명령을 받지 못하고 "몇 카운트 전진", "몇 도 회전" 같은 이산 명령만 받아요. 그래서 조향각을 연속으로 주는 대신 각도 오차가 12도를 넘으면 먼저 회전하고, 그보다 작으면 한 스텝 전진하는 방식으로 바꿨어요. 원래의 연속 제어는 아니지만 목표점을 고르는 논리는 동일해서 look-ahead의 효과는 그대로 측정돼요.

값을 바꿔가며 오차를 재요

한 변 1미터 사각 경로를 5센티미터 간격으로 조밀화하고, 매 제어 주기마다 현재 위치에서 가장 가까운 경로점까지의 거리를 cross-track error 표본으로 모았어요. 주행 조건은 난수 시드를 고정해 슬립 4%, 회전 오차 ±6%가 매 시행 동일하게 재현되도록 했고요.

look-ahead이탈 RMS최대완주 시간판정
0.10 m14.3 cm17.9 cm68.5 초진동
0.15 m7.0 cm11.9 cm39.2 초개선
0.20 m4.0 cm8.3 cm28.4 초최적
0.30 m9.3 cm18.5 cm41.2 초코너 절삭
look-ahead 거리에 따른 이탈 오차
RMS와 최대 이탈이 같은 자리에서 함께 최소가 돼요. 완주 시간도 같은 지점이 최소예요.
051015200.100.150.200.30look-ahead 거리 (m)이탈 오차 (cm)최적 0.20 m17.911.98.318.514.37.04.09.3최대 이탈이탈 RMS

0.20미터에서 오차와 시간이 동시에 최소예요. 0.10에서 0.20으로 옮기면 RMS가 72% 줄고, 거기서 0.30으로 더 키우면 다시 2.3배로 늘어요.

왜 U자가 되는지

너무 가까운 점을 좇으면 조향이 과해져요. 목표점이 코앞이라 조금만 벗어나도 원호의 곡률이 커지고, 그 조향이 다시 반대쪽 이탈을 만들면서 경로 주변을 지그재그로 진동해요. 0.10미터에서 완주 시간이 최적의 2.4배로 늘어난 게 그 흔적이에요. 같은 거리를 가는데 훨씬 많이 꺾었다는 뜻이니까요.

반대로 목표점이 너무 멀면 코너에서 안쪽을 크게 잘라요. 로봇 입장에서는 저 멀리 있는 점을 향해 직선에 가깝게 가는 셈이라, 그 사이의 굽은 경로를 무시하게 돼요.

경로 크기 대비 비율로 보세요
한 변 1미터에서 0.20미터면 20%인데, 시험 삼아 0.45미터(45%)로 돌렸더니 완전히 발산해서 14.8미터까지 이탈했어요. look-ahead가 코너의 곡률 반경보다 커지면 목표점이 반대편 변으로 튀어 버리기 때문이에요. 절대값이 아니라 경로 곡률 대비로 잡아야 다른 코스에 옮겨도 성립해요.

실험 환경이 만든 오차 셋

측정값이 시행마다 널뛰어서 원인을 좁히는 데 시간이 더 걸렸어요. 잡고 보니 알고리즘이 아니라 실험 환경 쪽 문제가 대부분이었어요.

1

로봇이 경로를 거꾸로 감

원인목표점을 "남은 경로에서 look-ahead 이상 떨어진 첫 점"으로 골랐는데, 사각 경로는 시작점과 끝점이 가까워 이미 지나온 점이 다시 선택돼요
조치경로를 조밀화한 뒤 최근접점 인덱스를 앞으로만 증가시키게 고정
2

시작 헤딩이 79도로 고정됨

원인이전 시행의 시뮬레이터 노드가 안 죽고 같은 /odom을 계속 발행. 새 시행의 추종기가 옛 로봇의 자세를 읽고 있었어요
조치시행마다 ROS 도메인과 포트를 분리
3

도메인을 나눴더니 노드끼리 서로를 못 찾음

원인ROS_DOMAIN_ID는 0~101만 안전해요. 그 위 값은 DDS가 계산한 포트가 운영체제의 임의 할당 범위와 겹쳐요
증상노드는 멀쩡히 뜨는데 서로를 발견하지 못하고 에러 로그가 안 남아요. 알고리즘 문제로 오해하기 쉬운 종류예요

셋 다 알고리즘을 한 줄도 안 건드렸는데 측정값이 달라졌어요. 통제 실험은 값을 재기 전에 무엇이 고정돼 있는지부터 재야 성립해요.

실물에서는 회전이 측정되지 않았어요

같은 실험을 실제 로봇에서 먼저 시도했는데, 추종기가 제자리 회전만 반복했어요. 로그를 보니 원인이 분명했어요.

기준 yaw -1338.8도
회전 H80 (헤딩오차 +80도, yaw -3도)
회전 H64 (헤딩오차 +64도, yaw -4도)

정지 상태에서 누적 yaw가 1338도까지 폭주해 있고, 80도 회전 명령을 보내도 yaw는 1도씩 드리프트만 해요. 로봇은 실제로 도는데 자이로가 그 회전을 반영하지 못하니, 추종기 입장에서는 “아직 방향이 안 맞았다”는 판단이 계속 유지되고 같은 명령을 무한히 내보내게 돼요.

회전 방향 부호를 뒤집어도 증상이 같았던 게 단서였어요. 부호 문제라면 반대로 바꿨을 때 달라져야 하니까요.

측정되지 않는 상태는 제어할 수 없어요
그래서 연속 회전이 6회를 넘으면 스스로 멈추고 원인 후보를 로그로 남기도록 안전장치를 넣었어요. 자이로 대신 좌우 바퀴 회전량 차이로 헤딩을 적분하는 경로도 열어 뒀고요. 제어기가 무한 루프에 빠지는 것보다 멈추고 이유를 남기는 쪽이 다음 시행을 살려요.

남는 기준

파라미터 하나를 정하는 일이 왜 실험 설계 문제가 되는지가 이번의 요점이에요. 0.20이라는 값 자체는 이 경로에서만 쓰는 숫자고, 옮겨 갈 수 있는 건 경로 곡률 대비 비율로 잡는다RMS·최대·시간을 함께 본다는 절차예요. 셋 중 하나만 보면 진동하는 주행도 정밀해 보일 수 있어요.

실물 검증은 센서를 복구한 뒤로 미루고, 그 사이 알고리즘과 실험 절차는 시뮬레이터에서 완결시켰어요. 하드웨어가 막혀 있어도 진행할 수 있는 부분을 분리해 두면 시간을 덜 잃어요.

같은 스크립트로 실물에서 다시 돌려 시뮬레이터와 오차를 나란히 놓는 것이 다음 차례예요. 그 대조표가 나오면 파라미터 하나를 왜 그 값으로 정했는지, 그리고 시뮬과 실물이 어디서 갈라지는지를 같은 지표로 말할 수 있게 돼요. joint_states와 robot_description, 그리고 서보 두 점 캘리브레이션에서 만든 오도메트리가 여기서 위치 피드백으로 쓰여요.