평행 그리퍼(Gripper) 장착

The Elder Node·2026년 9월 5일

평행 그리퍼 URDF 작성과 mimic 조인트

이제 물체를 집을 그리퍼가 필요하다. 상용 그리퍼의 URDF를 가져다 적용할 수도 있겠지만 단순히 물체를 잡는 목적이라면 URDF 작성법에 익숙해질 겸해서 직접 작성하여 사용하는 것이 더 빠를 것으로 생각되어 이 방향으로 진행한다.

1. 손가락 형상

tool0을 부모 링크로 하여 상자 모양 손가락 두 개를 prismatic 관절(일직선으로 미끄러지는 선형 관절)로 붙인다.

src/arm_description/urdf/arm_gripper.urdf.xacro

핵심적인 설정 내용은 다음과 같다.

  • axis를 Y축을 기준으로 서로 반대로 설정. 같은 방향이면 관절 값을 증가시킬 때 두 손가락이 나란히 한쪽으로 밀린다. 반대로 줘야 중심을 향해 모인다.
  • 관절 값 0을 완전히 열린 상태로 정의한다. 손가락 원점을 Y축 상의 ±0.03 m로, 두께를 0.01 m로 설정한다. 원점이 손가락 링크의 중심에 있으므로 두께 0.01 m의 절반인 0.005 m를 제외하면 안쪽 면의 실제 위치는 ±0.025 m가 된다. 따라서 두 손가락이 중심을 향해 0.025 m만큼 움직였을 때 그리퍼가 완전히 맞닿게 된다. URDF에서는 joint의 <limit> 태그 속성을 lower=0.0, upper=0.025로 지정한다.
  • finger2_joint에 <mimic> 속성을 부여. 평행 그리퍼는 두 축이 물리적으로 연동되므로 한쪽만 제어하면 된다. 조인트에 mimic 속성을 주어 finger1_joint가 움직인 만큼 axis 속성에 따라 움직이도록 만든다.

물체를 잡는 지점으로 쓸 tool center point인 tcp_link도 고정 관절(fixed joint)로 연결하여 추가한다. tool0은 UR 로봇의 공구 장착면이고, 손가락 관절 원점이 거기서 z=0.05, 손가락 길이가 0.05이므로 그리퍼의 중심 좌표의 z축 위치는 z=0.075 m다. tcp_link를 그 위치에 둔다.

이렇게 설계한 그리퍼가 파지할 테스트 대상물로 두께 5 mm, 가로 20 mm, 세로 40 mm 크기의 직육면체를 가정했다. 손가락이 맞닿는 가로 폭(20 mm)을 그리퍼의 최대 개방 폭(50 mm)보다 충분히 좁게 설정하여 안정적인 파지 여유를 두었다.

2. 그리퍼 제어 인터페이스

UR 로봇의 ROS 2 패키지인 ur_description 내부의 매크로를 참고하여, 그리퍼용 매크로를 같은 형태로 하나 더 만든다.

src/arm_description/urdf/gripper_joint_control.xacro

finger2_joint에는 <command_interface>를 두지 않는다. mimic joint는 명령을 받는 대상이 아니라 상태만 보고하는 대상이다. 여기에 command_interface를 달면 관절을 움직이는 주체가 둘이 되어 동작에 문제가 발생한다. gz_ros2_control의 GazeboSimSystem 플러그인은 URDF 내의 조인트에 <mimic> 태그가 있으면 이를 읽어서 mimic 쌍으로 등록한다.

생성한 gripper_joint_control.xacro 매크로를 arm_study.ros2_control.xacro에서 포함시키고 시뮬레이션용 Gazebo 플러그인을 하드웨어로 설정한다.

<xacro:include filename="$(find arm_description)/urdf/gripper_joint_control.xacro"/>

3. 그리퍼 전용 컨트롤러

처음에는 그리퍼가 목표 위치로 이동해야 하므로 finger1_joint를 팔의 joint_trajectory_controller에 넣어서 MoveIt이 tcp_link의 위치를 계산해야 한다고 생각했다. 하지만, 살펴보니 tcp_link는 tool0에 fixed 조인트로 부착된 링크이며, MoveIt의 계획 그룹인 ur_manipulator에는 UR 로봇의 6축 관절만 포함되어 있어 그리퍼는 제외된다.
또한, 하나의 command interface는 한 컨트롤러만 점유할 수 있다. joint_trajectory_controller가 finger1_joint의 position 명령을 점유하고 있으면 그리퍼를 여닫기 위한 그리퍼용 컨트롤러가 해당 인터페이스를 제어할 수 없다.
(tcp_link를 목표 위치로 설정하는 것과 관련된 내용은 다음에 다시 정리하도록 하겠다.)

우선, 그리퍼 전용 컨트롤러를 새로 만들어 준다.

src/arm_bringup/config/arm_controllers.yaml

controller_manager:
  ros__parameters:
    gripper_controller:
      type: parallel_gripper_action_controller/GripperActionController

gripper_controller:
  ros__parameters:
    joint: finger1_joint
    allow_stalling: true       # 물체를 붙잡아 목표까지 못 가도 성공으로 처리
    stall_velocity_threshold: 0.001
    stall_timeout: 0.5
    goal_tolerance: 0.002 

joint에는 finger1_joint만 적는다. finger2_joint는 mimic이라 컨트롤러가 직접 다루지 않는다.

allow_stalling: true가 핵심이다. 물체를 잡으면 손가락이 목표 위치까지 가지 못한 채 멈추는데, 이를 실패가 아닌 성공으로 처리하라는 뜻이다. 물체를 잡았을 때 성공 신호는 stalled=true가 된다.

4. 작업 중 겪은 문제

RViz에 로봇 모델이 표시되지 않음

형상 확인용으로 joint_state_publisher_gui + RViz 런치를 따로 만들었는데 로봇이 제대로 그려지지 않았다.
RViz에서 로봇이 정상적으로 그려지지 않는 상황

이미지에서 보이듯이 RobotModel의 상태가 'Error'로 표시되고 "No transform from [//base] to [base_link]"처럼 모든 링크에 대한 TF를 받지 못하고 있는 상태이다.
원인은 URDF가 아니라 RViz 설정 파일 문제였다. RobotModel 디스플레이의 TF Prefix가 /로 남아 있어 모든 링크 이름 앞에 /를 붙여 TF를 찾다가 실패하고 있었다. 좌측 Display에서 TF Prefix 항목의 '/'를 삭제하면 정상적으로 나타난다.

ResourceStorage: already existing key

실행 시 모든 관절에서 중복 등록 에러가 발생했다.

ResourceStorage: ... already existing key

arm_study.ros2_control.xacro를 ur5e.urdf.xacro와 arm_gripper.urdf.xacro 양쪽에서
include 하고 있었다. 이 파일의 <ros2_control> 블록은 매크로로 감싸여 있지 않아 include 횟수만큼 그대로 펼쳐진다. 그리퍼 관절만이 아니라 모든 관절이 두 번씩 등록된 것이다. 매크로가 아닌 xacro 파일은 include 지점을 한 곳으로 유지해야 한다.

mimic을 인식했다는 로그는 찍히는데 follower가 움직이지 않음

<mimic> 태그를 달고 follower의 인터페이스도 정리한 상태에서, gz_ros2_control은 mimic 쌍을 인식했다는 로그를 정확히 출력했다.

Joint 'finger2_joint' is mimicking joint 'finger1_joint' with multiplier: 1 and offset: 0

그런데 Gazebo에서 finger1_joint를 움직여도 finger2_joint는 따라오지 않았다. 인식은 하고 있으니 URDF나 ros2_control 설정의 문제는 아니었다.

원인은 물리 엔진이었다. gz_ros2_control은 mimic 관계를 파악한 뒤, 두 관절을 묶어 달라고 물리 엔진에 제약(constraint) 생성을 요청한다. 관절을 실제로 연동시키는 주체는 ROS 계층이 아니라 물리 엔진인 것이다. 그런데 Gazebo의 기본 엔진인 dartsim은 이 제약을 구현하고 있지 않다.

같은 증상이 업스트림에도 보고되어 있다 —
Error mimic_joints [ROS Jazzy + Gazebo Harmonic] #340.

다행히 bullet-featherstone이라는 시뮬레이션 엔진이 이를 구현하고 있고 Gazebo에 이미 포함되어 있어서, 런치의 gz_args에 엔진을 변경하여 지정하면 되었다.

src/arm_bringup/launch/arm_study_bringup.launch.py

"gz_args": IfElseSubstitution(
    gazebo_gui,
    if_value=[" -r -v 4 --physics-engine gz-physics-bullet-featherstone-plugin ", world_file],
    else_value=[" -s -r -v 4 --physics-engine gz-physics-bullet-featherstone-plugin ", world_file],
)

이 변경으로 두 손가락이 대칭으로 움직이기 시작했다.

====== 2026.10 변경 사항 =======
이 당시에는 물리 엔진 교체로 모든 문제가 해결된 것으로 보았으나, 반복 테스트에서 그리퍼가 오동작하는 경우가 계속 발생하였다. 원인은 제어기 명령과 물리 엔진 제약이 서로 충돌하면서 발생하는 오류였다. 이 내용은 뒤에 나올 「Mimic Joint 동작 문제」 글에서 다룬다.

profile
무선/임베디드 엔지니어의 ROS2 & AI 개척기

0개의 댓글