이번 단계 목표: 03편에서는 한 자세를 ‘유지’만 했습니다. 이번엔 목표 자세를 시간에 따라 바꿔서 동작을 만듭니다. PD 제어는 그대로, ‘목표값’만 움직이면 동작이 된다는 걸 체험합니다.
핵심 아이디어 — “목표를 움직이면 동작이 된다”
03편에서 배운 PD 제어는 “정해진 목표 각도로 관절을 데려가는” 장치였습니다. 그럼 그 목표 각도 자체를 매 순간 조금씩 바꾸면 어떻게 될까요? 관절이 그 목표를 쫓아가면서 움직임(동작) 이 만들어집니다. 애니메이션이 정지 그림을 빠르게 넘겨 움직임을 만드는 것과 같습니다.
for i in range(n_steps):
t = i * dt
target = pose_at(t) # 시각 t에 따라 목표 자세를 계산
robot.control_dofs_position(target, motor_dofs)
scene.step()
pose_at(t) 함수가 이 단계의 전부입니다. “1~5초엔 스쿼트, 5~8초엔 팔 흔들기”처럼 시간표대로 목표 자세를 돌려주는 함수입니다.
부드럽게 움직이는 비결 — 보간(interpolation). 목표를 갑자기 확 바꾸면 로봇이 휘청합니다. 그래서 코사인 곡선
0.5*(1-cos(...))으로 목표를 서서히 바꿉니다. 0에서 1까지 천천히 출발해 천천히 멈추는, 자연스러운 가감속 곡선입니다.
실행(스크립트와 그림 파일의 번호는 글 번호보다 하나 작습니다. 이 글은 04편이고 스크립트는 03_으로 시작합니다):
python scripts/03_motion_squat_wave.py # 헤드리스 + mp4 저장
python scripts/03_motion_squat_wave.py --vis # 실시간으로 보기
결과 — 스쿼트 2회 + 한 팔 흔들기
| 스쿼트(앉기) | 팔 흔들기 |
|---|---|
![]() |
![]() |
전체 동작은 영상으로:
스쿼트 2회 + 팔 흔들기 동작 완료
스쿼트 최저점 몸통 높이: 0.747 m (서 있을 때 ≈0.79 m)
동작 후 몸통 높이: 0.790 m
넘어지지 않고 동작 수행!
회고 — 이 단계에서 제대로 한 수 배웠습니다
블로그에 정직하게 남기려고, 넘어졌던 시도들을 그대로 적습니다. 여기서 얻은 직관이 뒤(보행·RL)에서 계속 쓰입니다.
① “보기 좋은 깊은 스쿼트”는 넘어졌다
처음엔 무릎을 깊게(0.95rad) 굽혀 멋진 풀스쿼트를 시도 → 그대로 주저앉음. 깊이 앉을수록 무게중심이 발 뒤/밖으로 벗어나기 쉽고, 모터가 버텨야 할 힘도 급격히 커집니다. 얕게(무릎 0.7rad, 약 40°) 하되, 무릎·고관절·발목을 같은 비율로 굽혀 무게중심을 발 위에 유지하니 성공.
② 팔 흔들기에서 계속 앞으로 고꾸라졌다 — 그리고 결정적 깨달음
팔을 앞으로 들어 흔들게 했더니, 천천히/작게 들어도 번번이 앞으로 넘어졌습니다. 구간별 높이를 찍어 보니 스쿼트는 멀쩡한데 팔을 드는 순간(5초)부터 무너졌습니다.
그래서 4가지 팔 동작을 실험해 봤습니다(표로 정리):
| 동작 | 결과 |
|---|---|
| 양팔을 옆으로 들기 | 안정 |
| 오른 팔뚝만 흔들기 | 안정 |
| 오른팔을 옆으로 들고 흔들기 | 안정 |
| 양팔을 앞으로 들기 | 넘어짐 |
패턴이 보였습니다: ‘앞으로’는 넘어지고 ‘옆으로’는 안 넘어진다.
왜? 로봇의 발은 앞뒤로 짧고, 두 발은 좌우로 벌어져 있습니다. 그래서 무게중심이 머물 수 있는 안전지대(지지면, support polygon)가 앞뒤로는 좁고 좌우로는 넓습니다. 팔을 앞으로 들면 무게중심이 좁은 앞쪽 경계(발끝)를 금방 넘어 고꾸라지지만, 옆으로 들면 넓은 좌우 범위 안에 머물러 버팁니다. 그래서 최종 동작은 팔을 옆으로 들어 흔드는 것으로 정했습니다.
③ 이게 왜 중요한가 — 다음 단계의 복선
지금 우리 로봇은 미리 정한 목표 자세를 그대로 따라갈 뿐, 스스로 균형을 잡지 못합니다. 그래서 무게중심이 조금만 벗어나도 회복하지 못하고 넘어집니다. “넘어지려 하면 발목으로 버티고 팔을 휘저어 균형을 되찾는” 능력이 없습니다. 이게 바로 다음 두 단계의 주제입니다.
재검증 노트 (2026-08-31 추가)
10편에서 잘 걷는 듯한 영상이 실은 잔발이었음을 확인하고 “영상은 검증이 아니다”를 배운 뒤, 이 모션도 같은 눈으로 재검증했습니다. 이번엔 걸음이 아니라 “실제 G1 모터로 재현 가능한 동작인가”가 질문입니다. 스쿼트+팔 흔들기 8초 동안 관절별로 실제 가한 토크를 기록해 실물 토크 한계(URDF effort, 즉 URDF에 적힌 관절별 최대 토크: 무릎 139Nm·엉덩이 88Nm· 발목 35Nm·어깨/팔꿈치 25Nm)와 비교한 결과:
- 합격. 최대 토크가 발목 17Nm(한계의 49%), 무릎 11Nm(8%), 팔꿈치도 여유 — 전 구간 포화 0%(포화: 모터가 한계 토크에 닿은 시간 비율). 추종 오차(목표 각도와 실제 각도의 차이)도 최대 14.5°(빠른 팔 흔들기 구간)로 준수합니다. 토크로 보면 실물 모터 한계 안에 드는 얌전한 궤적입니다 (실물 시험은 하지 않았습니다).
- 스쿼트 중 앞기울임 12.8°는 버그가 아니라 설계였습니다. 목표 자세(엉덩이 −31°+무릎 40°+ 발목 −19°)를 합치면 골반이 약 −10° 숙여지게 되어 있고, 사람 스쿼트의 상체 숙임과 같은 성격입니다. 좌우 흔들림(roll)은 0.0° — 완전히 안정적이었습니다.
- 솔직한 고백 하나: ‘스쿼트’라기엔 얕습니다(몸통 하강 4.3cm, 무릎 40° — 쿼터 스쿼트). 깊은 스쿼트는 무릎 90° 이상이 필요한데, 그건 03편에서 배운 “깊게 굽히면 못 버틴다” 제약과 다시 만나는 문제라 이 단계에선 여기까지가 적절한 절충이었습니다.
재검증에 쓴 도구: Genesis의
get_dofs_control_force()(실제 가한 토크)와get_dofs_force_range()(토크 클램프 범위 — Genesis는 URDF effort 한계에서 토크를 자동으로 잘라냄. 이렇게 한계값에서 잘라내는 것을 클램프라고 합니다).
다음 단계
균형이 이렇게 까다로운데, 하물며 걷기는 어떨까요? 한 발로 서는 순간이 반복되는 보행을 손으로 직접 짜보고, 왜 그렇게 어려운지 확인합니다 → 05. 보행 도전

