오도메트리는 바퀴가 얼마나 돌았는지를 계속 더해서 내가 어디쯤 왔는지 추정하는 일이에요. 외부 기준을 전혀 보지 않고 매 순간의 이동량만 시간에 따라 적분해요.
여기서 이 방식의 성격이 결정돼요. 이동량을 더하기만 하므로 결과는 절대 위치가 아니라 적분을 시작한 지점 기준의 상대 위치예요. 로봇 전원이 켜진 자리가 원점이고 거기서부터의 누적 이동이 현재 자세죠. 대신 좋은 보장이 하나 있어요. 값이 연속적이고 매끄러워요. 외부 보정을 안 받으니 갑자기 점프하는 일이 없어서 제어기가 믿고 미분할 수 있는 신호예요.
지도 기준 좌표와의 관계는 한 줄로 정리돼요. 지도 좌표는 정확하지만 보정 순간 점프할 수 있고, 오도메트리는 매끄럽지만 흘러가요. 그래서 위치 추정기는 로봇 자세를 직접 고쳐 쓰는 대신 두 좌표계 사이의 변환을 갱신하는 방식으로 누적 오차를 흡수해요.
틱에서 미터로
엔코더가 주는 원재료는 최소 눈금이 몇 칸 지나갔나라는 정수예요. 거리로 바꾸는 건 세 단계의 사슬이에요.
틱 수 ÷ (틱/모터 1회전) = 모터 회전수
모터 회전수 ÷ 기어비 = 바퀴 회전수 # 감속 30:1이면 모터 30바퀴 = 바퀴 1바퀴
바퀴 회전수 × (π × 지름) = 이동 거리숫자를 넣어 볼게요. 엔코더가 모터 1회전에 13틱을 내고 4체배로 52틱, 기어비 30:1이면 바퀴 1회전은 1,560틱이에요. 바퀴 지름 65mm면 둘레가 약 0.2042m라서 틱 하나가 약 0.13mm예요. 이 “틱당 거리” 상수 하나로 환산이 끝나요.
실전에서는 펌웨어가 20ms마다 좌우 바퀴의 누적 틱을 올려 보내고, 받는 쪽이 이전 값과의 차이를 거리로 바꿔요. 증분이 아니라 누적을 보내는 이유는 패킷이 하나 유실돼도 다음 패킷에서 차이가 저절로 복구되기 때문이에요.
두 바퀴 속도가 몸의 운동이 되는 식
차동구동은 좌우 바퀴를 따로 구동하는 구조예요. 조향 장치가 없고 두 바퀴의 속도 차이만으로 방향을 바꿔요. 이 구조에서 몸의 운동은 딱 두 숫자로 표현돼요. 전진 속도와 회전 각속도요.
v = (vR + vL) / 2 # 두 바퀴의 평균 → 몸의 전진
ω = (vR − vL) / L # 두 바퀴의 차이 ÷ 트랙 폭 → 몸의 회전L은 좌우 바퀴 접지점 사이의 거리예요. 분모에 있다는 게 직관과 맞아요. 같은 속도 차라도 바퀴 사이가 좁으면 더 홱 돌고 넓으면 완만하게 돌아요. 양쪽이 같으면 직진, 정반대면 제자리 회전, 한쪽이 조금 빠르면 원호를 그리고 그 반지름은 v/ω예요.
반대 방향도 같은 한 쌍이에요.
vL = v − ω·L/2 # 안쪽 바퀴는 느리게
vR = v + ω·L/2 # 바깥 바퀴는 빠르게이게 속도 명령이 바퀴 명령이 되는 경로예요. 텔레옵이나 경로 추종 컨트롤러는 전진 속도와 각속도를 실어 보낼 뿐 바퀴를 몰라요. 컨트롤러가 이 식으로 좌우 목표 속도를 만들고, 바퀴별 제어기가 그 목표를 추종해요. 같은 한 쌍의 식이 양방향으로 쓰이는 셈이에요. 한쪽은 측정을 해석하고 다른 쪽은 의도를 배분해요.
순서가 규칙이에요
이제 매 주기의 속도를 자세에 쌓아요.
x ← x + v·cos(θ)·dt # 지금 바라보는 방향으로 전진분을 분해
y ← y + v·sin(θ)·dt
θ ← wrap(θ + ω·dt) # 방향 갱신은 이동 반영 뒤에, 그리고 랩핑이동은 이번 스텝을 시작할 때의 방향으로 반영하고 방향은 그다음에 갱신해요. 그리고 각도는 매번 −π에서 π 구간으로 감아 줘요. 랩핑을 안 하면 각도가 몇십 라디안까지 자라는데, 삼각함수에 넣는 건 문제없지만 각도끼리 비교하는 모든 계산이 깨져요. 목표 방향과의 차이, 최단 회전 방향 판단이 전부 두 각의 차인데, 한쪽은 6.3이고 한쪽은 0.02인 상태로는 그 차가 의미를 잃거든요.
이 갱신식은 오일러 적분이에요. 그 시간 동안 방향이 변하지 않는다고 가정하고 직선으로 잇는 방식이라, 직진에서는 정확하지만 곡선에서는 스텝마다 조금씩 어긋나요. 실제 궤적은 호인데 계산은 현으로 대체하니 곡선을 다각형으로 근사하는 셈이에요. 스텝당 오차가 주기의 제곱에 비례해서, 갱신이 성길수록 곡선 주행에서 위치가 눈에 띄게 틀어져요. 그래서 오도메트리 갱신은 제어 주기만큼 촘촘하게 돌리는 게 기본이에요.
한 주기로 묶으면 사슬이 한눈에 보여요.
void on_tick_report(long ticks_l, long ticks_r) { // 20ms마다 누적 틱 도착
double dl = to_meters(ticks_l - prev_l_); // Δ틱 → 좌바퀴 이동
double dr = to_meters(ticks_r - prev_r_);
prev_l_ = ticks_l; prev_r_ = ticks_r;
double v = (dr + dl) / (2.0 * dt_); // 순기구학
double w = (dr - dl) / (track_ * dt_);
x_ += v * std::cos(th_) * dt_; // 자세 적분
y_ += v * std::sin(th_) * dt_;
th_ = wrap(th_ + w * dt_); // 갱신 후 랩핑
}오차에 지우개가 없어요
이 주제의 핵심입니다. 적분이라는 구조 자체가 매 스텝의 결과를 다음 스텝의 출발값으로 쓰기 때문에, 한 번 들어온 오차는 이후의 모든 값에 실려 다녀요.
오차의 뿌리는 두 부류예요. 먼저 파라미터가 실물과 다른 경우입니다. 바퀴 유효 지름은 공기압·마모·적재 하중으로 도면값과 어긋나고, 좌우가 서로 다르게 어긋나요.
좌우 지름이 1%만 달라도 결과가 커요. 직진 명령을 줘도 실제 바퀴 속도가 1% 차이 나니 유령 회전이 생겨요. 0.3m/s에 트랙 폭 0.16m면 각속도 약 0.019rad/s, 반지름 약 16m의 큰 원을 그려요. 5m를 “직진”하고 나면 방향이 18도 틀어지고 옆으로 0.78m 흘러 있어요.
트랙 폭도 마찬가지예요. 바퀴 접지가 점이 아니라 면이라 유효 폭이 도면과 다르고, 그게 틀리면 회전량 추정이 그 비율만큼 틀려요.
둘 중에서도 방향 오차가 최악이에요. 위치 오차는 그 크기만큼으로 끝나지만, 방향 오차는 이후의 모든 이동을 통째로 회전시켜서 위치 오차가 이동 거리에 비례해 자라요. 18도 틀어진 채 10m를 가면 3m가 어긋나는 식이에요. 대책이 대부분 방향을 지키는 쪽에 몰려 있는 이유예요.
실측으로 잡는 두 절차
파라미터 오차는 재서 잡을 수 있어요.
① 직진 1m — 지름 보정
바닥에 1m를 표시하고 직진시킨다.
오도메트리가 1m를 보고할 때 실제 이동을 줄자로 잰다.
실측 ÷ 보고값을 바퀴 둘레에 곱해 넣는다.
직진 중 옆으로 흐른 양은 좌우 지름 비율의 몫 → 좌우를 따로 보정한다.
② 제자리 360° × 5회 — 트랙 폭 보정
테이프로 시작 방향을 표시하고 제자리 회전 다섯 바퀴를 명령한다.
보고된 총 회전각과 실제 회전각을 비교한다.
보정 폭 = 도면 폭 × (보고각 ÷ 실측각)회전을 여러 바퀴 돌리는 건 작은 비율 오차를 크게 증폭해 읽기 위해서예요. 한 바퀴만 돌면 몇 도 차이가 눈에 안 들어오지만 다섯 바퀴면 그 다섯 배로 벌어져요.
반면 미끄러짐이나 바닥 요철은 이렇게 못 잡아요. 상수 보정으로 지워지지 않고, 특히 미끄러지는 순간 바퀴는 돌았는데 몸은 안 움직였으니 오도메트리는 확신을 갖고 틀려요. 그래서 출구는 바깥 센서예요. 관성 센서가 방향을 받쳐 주고, 라이다 매칭이 좌표계 변환 보정으로 누적분을 흡수해요.
결과가 담기는 자리
계산 전부가 메시지 하나로 실려 나가요. 자리 배치만 알면 낯설 게 없어요.
├── header # 아래 자세가 어느 좌표계 기준인지
├── child_frame_id # 아래 속도가 어느 좌표계 기준인지
├── pose
│ ├── position # 적분 결과 x, y
│ ├── orientation # 방향을 쿼터니언으로
│ └── covariance # 불확실성
└── twist
├── linear.x # 전진 속도
└── angular.z # 회전 각속도자세는 적분 결과이고 속도는 지금 어떻게 움직이나예요. 기준 좌표계가 서로 다릅니다. 자세는 오도메트리 프레임 기준이고 속도는 로봇 몸 기준이에요.
불확실성 항목은 이 값을 얼마나 믿어도 되는가예요. 오도메트리는 갈수록 흘러가니 소비자에게 그걸 함께 알려줘야 여러 센서를 가중치로 섞는 융합 필터가 제 일을 해요.
바퀴가 도는데도 로봇이 직진하지 않는 하드웨어 쪽 이야기는 좌우 바퀴 편차 보정 — 같은 지령으로 직진하지 않는 이유에 있어요. 여기 나온 자세를 입력으로 받아 경로를 따라가는 층은 경로 추종의 전방 주시 거리와 이탈 오차의 최적점고요.