1. 강의 목표
이번 글에서는 ROS 2 모바일 로봇 시뮬레이션에서 반드시 이해해야 하는 두 가지 물리 설정을 다룹니다.
- Friction 설정
- 바퀴가 바닥에서 미끄러지는 이유
- mu, mu2, slip1, slip2의 의미
- Gazebo에서 모바일 로봇 바퀴 마찰을 설정하는 방법
- Inertia 설정
- 로봇 링크에 질량과 관성 모멘트를 넣어야 하는 이유
- URDF/Xacro에서 <inertial> 태그 작성법
- 박스형 바디와 원통형 바퀴의 관성 계산법
2. 왜 Friction과 Inertia가 중요한가?
ROS 2에서 로봇 모델을 만들 때 단순히 모양만 보이게 하는 것은 어렵지 않습니다. visual 태그만 있어도 RViz에서는 로봇이 그럴듯하게 보입니다. 하지만 Gazebo 같은 물리 시뮬레이터에서는 이야기가 다릅니다.
Gazebo에서 로봇이 정상적으로 움직이려면 각 링크에 다음 정보가 필요합니다.
- 충돌 형상: <collision>
- 질량: <mass>
- 관성 모멘트: <inertia>
- 바닥과 접촉하는 부분의 마찰: <friction>
URDF를 Gazebo에서 사용하려면 각 링크에 <inertia> 요소가 제대로 지정되어야 합니다. 또한 URDF 자체는 마찰 같은 시뮬레이션 전용 속성을 충분히 표현하지 못하기 때문에 Gazebo 확장 태그나 SDF 설정을 함께 사용합니다.
즉, 모바일 로봇이 다음처럼 이상하게 움직인다면 대부분 Friction 또는 Inertia 설정 문제입니다.
- 바퀴가 도는데 로봇이 앞으로 안 감
- 제자리에서 계속 미끄러짐
- 가만히 있어도 로봇이 덜덜 떨림
- 회전 명령을 줬는데 이상하게 직진함
- 바퀴가 땅에 닿았는데 접지력이 없음
- 로봇이 너무 쉽게 뒤집힘
- 시뮬레이션이 불안정하게 튐
3. 기본 구조: URDF, Xacro, Gazebo, SDF 관계
ROS 2 모바일 로봇 모델은 보통 URDF 또는 Xacro로 작성합니다.
robot_description/ ├── urdf/ │ └── mobile_robot.urdf.xacro ├── launch/ │ └── display.launch.py └── package.xml
URDF는 ROS에서 로봇의 링크와 조인트를 표현하는 표준 형식입니다. 하지만 Gazebo는 내부적으로 SDF 형식을 사용합니다. Gazebo Classic 문서에 따르면 URDF를 Gazebo에서 사용할 때 Gazebo가 내부적으로 URDF를 SDF로 변환합니다.
정리하면 다음과 같습니다.
Xacro ↓ 변환 URDF ↓ Gazebo가 내부 변환 SDF ↓ Gazebo 물리 시뮬레이션
4. Inertia란?
Inertia는 한국어로 관성 또는 관성 모멘트라고 합니다.
쉽게 말하면 다음과 같습니다.
| 질량 mass | 물체가 직선 운동 변화를 얼마나 버티는가 |
| 관성 모멘트 inertia | 물체가 회전 운동 변화를 얼마나 버티는가 |
예를 들어 같은 5kg 물체라도 질량이 중심에 몰려 있으면 회전하기 쉽고, 질량이 바깥쪽에 퍼져 있으면 회전하기 어렵습니다. 이것을 수치로 표현한 것이 관성 모멘트입니다.
Gazebo 물리 엔진은 로봇의 움직임을 계산할 때 질량과 관성 모멘트를 사용합니다. 모바일 로봇의 바디, 바퀴, 센서 링크에 관성값이 없거나 말이 안 되는 값이 들어가면 시뮬레이션이 불안정해집니다.
5. URDF의 <inertial> 기본 형식
URDF에서 관성 정보는 각 <link> 안에 작성합니다.
<link name="base_link">
<visual>
...
</visual>
<collision>
...
</collision>
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="5.0"/>
<inertia
ixx="0.1" ixy="0.0" ixz="0.0"
iyy="0.1" iyz="0.0"
izz="0.1"/>
</inertial>
</link>
각 항목의 의미는 다음과 같습니다.
태그설명
| <origin> | 질량 중심 위치. 보통 링크 중심과 같게 둠 |
| <mass> | 링크 질량, 단위 kg |
| ixx | x축 회전에 대한 관성 모멘트 |
| iyy | y축 회전에 대한 관성 모멘트 |
| izz | z축 회전에 대한 관성 모멘트 |
| ixy, ixz, iyz | 관성곱. 대칭 형상에서는 보통 0 |
모바일 로봇 강의에서는 처음부터 복잡한 관성곱을 다루지 않아도 됩니다. 대부분의 박스형 바디, 원통형 바퀴는 중심축에 맞춰 모델링하면 ixy, ixz, iyz를 0으로 둘 수 있습니다.
관성계산식 정리 자료 :
6. 박스형 로봇 바디의 관성 계산
모바일 로봇의 본체를 직육면체라고 가정합니다.
가로 x = 0.40 m 세로 y = 0.30 m 높이 z = 0.15 m 질량 m = 5.0 kg
직육면체의 중심 기준 관성 모멘트는 다음과 같습니다.

Ixx = (1/12) * m * (y² + z²) Iyy = (1/12) * m * (x² + z²) Izz = (1/12) * m * (x² + y²)
계산하면 다음과 같습니다.
Ixx = (1/12) * 5.0 * (0.30² + 0.15²)
= 0.046875
Iyy = (1/12) * 5.0 * (0.40² + 0.15²)
= 0.0760417
Izz = (1/12) * 5.0 * (0.40² + 0.30²)
= 0.104167
URDF/Xacro에서는 다음처럼 작성합니다.
<link name="base_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="0.40 0.30 0.15"/>
</geometry>
</visual>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="0.40 0.30 0.15"/>
</geometry>
</collision>
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="5.0"/>
<inertia
ixx="0.046875" ixy="0.0" ixz="0.0"
iyy="0.0760417" iyz="0.0"
izz="0.104167"/>
</inertial>
</link>
7. 원통형 바퀴의 관성 계산
모바일 로봇 바퀴는 보통 원통형으로 모델링합니다.
예시 조건:
바퀴 반지름 r = 0.05 m 바퀴 폭 L = 0.03 m 질량 m = 0.2 kg
바퀴가 y축을 중심으로 회전한다고 가정하면 원통의 관성은 다음과 같이 둘 수 있습니다.

Iyy = (1/2) * m * r² Ixx = Izz = (1/12) * m * (3r² + L²)
계산하면 다음과 같습니다.
Iyy = 0.5 * 0.2 * 0.05²
= 0.00025
Ixx = Izz = (1/12) * 0.2 * (3 * 0.05² + 0.03²)
= 0.00014
URDF 예시는 다음과 같습니다.
<link name="left_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="1.5708 0 0"/>
<geometry>
<cylinder radius="0.05" length="0.03"/>
</geometry>
</visual>
<collision>
<origin xyz="0 0 0" rpy="1.5708 0 0"/>
<geometry>
<cylinder radius="0.05" length="0.03"/>
</geometry>
</collision>
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="0.2"/>
<inertia
ixx="0.00014" ixy="0.0" ixz="0.0"
iyy="0.00025" iyz="0.0"
izz="0.00014"/>
</inertial>
</link>
주의할 점은 바퀴의 회전축입니다. URDF에서 cylinder 기본 축은 z축입니다. 하지만 모바일 로봇 바퀴는 보통 y축 또는 x축을 기준으로 회전해야 하므로 visual, collision, joint axis 방향을 반드시 맞춰야 합니다.
8. Xacro 매크로로 Inertia 재사용하기
강의에서는 같은 구조를 반복해서 쓰지 않는 것이 좋습니다. Xacro 매크로를 쓰면 훨씬 깔끔합니다.
1) 박스 관성 매크로
<xacro:macro name="box_inertia" params="m x y z">
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="${m}"/>
<inertia
ixx="${(1.0/12.0) * m * (y*y + z*z)}"
ixy="0.0"
ixz="0.0"
iyy="${(1.0/12.0) * m * (x*x + z*z)}"
iyz="0.0"
izz="${(1.0/12.0) * m * (x*x + y*y)}"/>
</inertial>
</xacro:macro>
사용 예:
<link name="base_link"> ... <xacro:box_inertia m="5.0" x="0.40" y="0.30" z="0.15"/> </link>
2) 원통 관성 매크로
<xacro:macro name="cylinder_inertia_y_axis" params="m r l">
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="${m}"/>
<inertia
ixx="${(1.0/12.0) * m * (3*r*r + l*l)}"
ixy="0.0"
ixz="0.0"
iyy="${0.5 * m * r*r}"
iyz="0.0"
izz="${(1.0/12.0) * m * (3*r*r + l*l)}"/>
</inertial>
</xacro:macro>
사용 예:
<link name="left_wheel_link"> ... <xacro:cylinder_inertia_y_axis m="0.2" r="0.05" l="0.03"/> </link>
9. Friction이란?
Friction은 마찰입니다. 모바일 로봇에서는 특히 바퀴와 바닥 사이의 마찰이 중요합니다.
마찰이 너무 낮으면 다음 문제가 생깁니다.
바퀴는 도는데 로봇이 앞으로 안 감 회전 시 로봇이 미끄러짐 오도메트리와 실제 위치가 크게 달라짐
마찰이 너무 높으면 다음 문제가 생깁니다.
회전이 너무 뻑뻑함 메카넘/옴니휠의 횡방향 움직임이 막힘 접촉 힘이 과하게 커져 시뮬레이션이 튐
Gazebo 물리 튜토리얼은 ODE 물리 엔진 기준으로 solver, constraint, friction 같은 물리 파라미터를 조정하는 방법을 설명합니다. Gazebo Classic의 기본 ODE 설정에서는 max_step_size, real_time_update_rate, solver 반복 횟수 같은 값도 마찰 안정성에 영향을 줍니다.
10. mu와 mu2의 의미
Gazebo/SDF에서 바퀴 마찰을 설정할 때 자주 쓰는 값은 다음입니다.
<surface>
<friction>
<ode>
<mu>1.0</mu>
<mu2>1.0</mu2>
</ode>
</friction>
</surface>
파라미터의미
| mu | 첫 번째 접선 방향의 마찰 계수 |
| mu2 | 첫 번째 방향과 수직인 두 번째 접선 방향의 마찰 계수 |
| fdir1 | 첫 번째 마찰 방향 지정 |
| slip1 | 첫 번째 방향의 인공 미끄러짐 |
| slip2 | 두 번째 방향의 인공 미끄러짐 |
mu와 mu2는 단순히 “앞뒤 마찰”, “좌우 마찰”로 고정되는 값이 아닙니다. 접촉면의 접선 방향 두 개에 대한 마찰 계수입니다. fdir1을 명시하면 첫 번째 마찰 방향을 지정할 수 있습니다. fdir1은 첫 번째 마찰 방향을 정의하는 벡터이며 접촉 법선에 수직인 접선 방향이어야 합니다.
두 물체가 접촉하면 물리 엔진은 먼저 다음 정보를 계산합니다.
접촉점 p
접촉 법선 n
첫 번째 접선 방향 t1
두 번째 접선 방향 t2
이 세 방향은 서로 직교하는 접촉 좌표계를 구성합니다.
n: 접촉 법선
↑
│
│
●────→ t1: 첫 번째 마찰 방향
/
/
t2: 두 번째 마찰 방향
각 방향의 역할은 다음과 같습니다.
n : 두 물체가 서로 파고들지 않도록 반발력이 작용하는 방향
t1 : mu가 적용되는 첫 번째 마찰 방향
t2 : mu2가 적용되는 두 번째 마찰 방향
ODE는 접촉 법선에 수직인 두 방향으로 마찰력을 적용합니다. fdir1은 첫 번째 방향인 t1을 지정하고, 두 번째 방향은 접촉 법선과 fdir1에 모두 수직이 되도록 계산됩니다.
11. URDF에서 Gazebo 마찰 설정하기
URDF 자체는 마찰 설정 표현이 제한적입니다. 그래서 Gazebo 확장 태그를 사용합니다. Gazebo 문서도 URDF에 Gazebo 전용 <gazebo> 요소를 추가해 SDF에 있는 시뮬레이션 속성을 지정할 수 있다고 설명합니다.
예를 들어 left_wheel_link에 마찰을 주려면 다음처럼 작성합니다.
<gazebo reference="left_wheel_link"> <mu1>1.0</mu1> <mu2>1.0</mu2> <kp>1000000.0</kp> <kd>100.0</kd> <minDepth>0.001</minDepth> <maxVel>1.0</maxVel> </gazebo>
Gazebo Classic URDF 확장에서는 보통 mu1, mu2 형식으로 많이 사용합니다. SDF 원문에서는 mu, mu2 구조가 쓰입니다.
12. SDF 형식의 Friction 예시
SDF에서는 다음처럼 더 명확하게 작성합니다.
<collision name="wheel_collision">
<geometry>
<cylinder>
<radius>0.05</radius>
<length>0.03</length>
</cylinder>
</geometry>
<surface>
<friction>
<ode>
<mu>1.0</mu>
<mu2>1.0</mu2>
<slip1>0.0</slip1>
<slip2>0.0</slip2>
</ode>
</friction>
</surface>
</collision>
SDFormat는 로봇, 물체, 환경, 물리 엔진, 조명, 센서 등을 표현하기 위한 XML 형식입니다. Gazebo 계열 시뮬레이터에서 시뮬레이션용 모델을 표현할 때 핵심 형식으로 사용됩니다.
13. 모바일 로봇 바퀴별 추천 Friction 시작값
처음 강의 실습에서는 아래 값을 시작점으로 쓰면 좋습니다. 단, 실제 값은 로봇 크기, 질량, 바퀴 반지름, 바닥 재질, 물리 엔진 설정에 따라 조정해야 합니다.
로봇 종류 mu1 mu2 설명
| 일반 2륜 차동구동 | 1.0 ~ 2.0 | 1.0 ~ 2.0 | 기본적인 고무 바퀴 느낌 |
| 4륜 차동구동 | 1.0 ~ 2.0 | 1.0 ~ 2.0 | 직진 안정성 우선 |
| 캐스터 포함 로봇 | 구동 바퀴 1.0 이상 | 캐스터는 낮게 | 캐스터가 끌리지 않게 조정 |
| 메카넘 휠 | 방향별로 다르게 조정 | 방향별로 다르게 조정 | 횡방향 미끄러짐 허용 필요 |
| 옴니휠 | 일부 방향 낮게 | 일부 방향 높게 | 롤러 방향 특성 반영 |
초보 강의에서는 먼저 mu1=1.0, mu2=1.0으로 시작하고, 로봇이 미끄러지면 값을 올리는 방식이 좋습니다.
14. ROS 2 모바일 로봇에 Inertia와 Friction 넣기
1) 패키지 생성
cd ~/gazebo_ws/src
ros2 pkg create --build-type ament_cmake mobile_robot_description

폴더를 만듭니다.
cd ~/gazebo_ws/src/mobile_robot_description
mkdir urdf launch rviz

2) package.xml 확인
package.xml에 최소한 다음 의존성을 넣습니다.
<depend>robot_state_publisher</depend> <depend>joint_state_publisher</depend> <depend>joint_state_publisher_gui</depend> <depend>rviz2</depend> <depend>xacro</depend> <export> <build_type>ament_cmake</build_type> </export>

ament_cmake 패키지는 package.xml의 <export> 안에 다음이 있어야 합니다.
<build_type>ament_cmake</build_type>
없으면 빌드나 패키지 인식에서 문제가 생길 수 있습니다.
3) CMakeLists.txt 설정
cmake_minimum_required(VERSION 3.8)
project(mobile_robot_description)
find_package(ament_cmake REQUIRED)
install(
DIRECTORY urdf launch rviz
DESTINATION share/${PROJECT_NAME}
)
ament_package()

4) 모바일 로봇 Xacro 전체 예제
파일 생성:
touch urdf/mobile_robot.urdf.xacro

urdf/mobile_robot.urdf.xacro에 다음 코드를 입력합니다.
<?xml version="1.0"?>
<robot
name="simple_mobile_robot"
xmlns:xacro="http://www.ros.org/wiki/xacro">
<!-- ======================================== -->
<!-- Parameters -->
<!-- ======================================== -->
<xacro:property name="base_length" value="0.40"/>
<xacro:property name="base_width" value="0.30"/>
<xacro:property name="base_height" value="0.15"/>
<xacro:property name="base_mass" value="5.0"/>
<xacro:property name="wheel_radius" value="0.05"/>
<xacro:property name="wheel_width" value="0.03"/>
<xacro:property name="wheel_mass" value="0.2"/>
<xacro:property name="wheel_y" value="0.18"/>
<xacro:property name="wheel_z" value="-0.05"/>
<!-- ======================================== -->
<!-- Inertia Macros -->
<!-- ======================================== -->
<xacro:macro name="box_inertia" params="m x y z">
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="${m}"/>
<inertia
ixx="${(1.0 / 12.0) * m * (y*y + z*z)}"
ixy="0.0"
ixz="0.0"
iyy="${(1.0 / 12.0) * m * (x*x + z*z)}"
iyz="0.0"
izz="${(1.0 / 12.0) * m * (x*x + y*y)}"/>
</inertial>
</xacro:macro>
<xacro:macro
name="cylinder_inertia_y_axis"
params="m r l">
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="${m}"/>
<inertia
ixx="${(1.0 / 12.0) * m * (3*r*r + l*l)}"
ixy="0.0"
ixz="0.0"
iyy="${0.5 * m * r*r}"
iyz="0.0"
izz="${(1.0 / 12.0) * m * (3*r*r + l*l)}"/>
</inertial>
</xacro:macro>
<!-- ======================================== -->
<!-- Base Link -->
<!-- ======================================== -->
<link name="base_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box
size="${base_length}
${base_width}
${base_height}"/>
</geometry>
<material name="blue">
<color rgba="0.1 0.2 0.8 1.0"/>
</material>
</visual>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box
size="${base_length}
${base_width}
${base_height}"/>
</geometry>
</collision>
<xacro:box_inertia
m="${base_mass}"
x="${base_length}"
y="${base_width}"
z="${base_height}"/>
</link>
<!-- ======================================== -->
<!-- Wheel Macro -->
<!-- ======================================== -->
<xacro:macro name="wheel" params="name y_pos">
<link name="${name}_wheel_link">
<visual>
<!-- Cylinder 기본 축 z를 y축 방향으로 회전 -->
<origin xyz="0 0 0" rpy="1.5708 0 0"/>
<geometry>
<cylinder
radius="${wheel_radius}"
length="${wheel_width}"/>
</geometry>
<material name="black">
<color rgba="0.02 0.02 0.02 1.0"/>
</material>
</visual>
<collision>
<origin xyz="0 0 0" rpy="1.5708 0 0"/>
<geometry>
<cylinder
radius="${wheel_radius}"
length="${wheel_width}"/>
</geometry>
</collision>
<xacro:cylinder_inertia_y_axis
m="${wheel_mass}"
r="${wheel_radius}"
l="${wheel_width}"/>
</link>
<joint
name="${name}_wheel_joint"
type="continuous">
<parent link="base_link"/>
<child link="${name}_wheel_link"/>
<origin
xyz="0 ${y_pos} ${wheel_z}"
rpy="0 0 0"/>
<axis xyz="0 1 0"/>
</joint>
<!-- Gazebo wheel friction -->
<gazebo reference="${name}_wheel_link">
<mu1>1.0</mu1>
<mu2>1.0</mu2>
<kp>1000000.0</kp>
<kd>100.0</kd>
<minDepth>0.001</minDepth>
<maxVel>1.0</maxVel>
</gazebo>
</xacro:macro>
<!-- ======================================== -->
<!-- Left and Right Wheels -->
<!-- ======================================== -->
<xacro:wheel
name="left"
y_pos="${wheel_y}"/>
<xacro:wheel
name="right"
y_pos="${-wheel_y}"/>
<!-- ======================================== -->
<!-- Caster -->
<!-- ======================================== -->
<link name="caster_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<sphere radius="0.025"/>
</geometry>
<material name="gray">
<color rgba="0.5 0.5 0.5 1.0"/>
</material>
</visual>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<sphere radius="0.025"/>
</geometry>
</collision>
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="0.05"/>
<inertia
ixx="0.0000125"
ixy="0.0"
ixz="0.0"
iyy="0.0000125"
iyz="0.0"
izz="0.0000125"/>
</inertial>
</link>
<joint name="caster_joint" type="fixed">
<parent link="base_link"/>
<child link="caster_link"/>
<origin
xyz="-0.15 0 -0.075"
rpy="0 0 0"/>
</joint>
<!-- 캐스터는 낮은 마찰 사용 -->
<gazebo reference="caster_link">
<mu1>0.1</mu1>
<mu2>0.1</mu2>
<kp>1000000.0</kp>
<kd>100.0</kd>
</gazebo>
<!-- ======================================== -->
<!-- Gazebo ROS Differential Drive Plugin -->
<!-- ======================================== -->
<gazebo>
<plugin
name="gazebo_ros_diff_drive"
filename="libgazebo_ros_diff_drive.so">
<ros>
<remapping>cmd_vel:=/cmd_vel</remapping>
<remapping>odom:=/odom</remapping>
</ros>
<update_rate>50.0</update_rate>
<left_joint>
left_wheel_joint
</left_joint>
<right_joint>
right_wheel_joint
</right_joint>
<wheel_separation>
${2.0 * wheel_y}
</wheel_separation>
<wheel_diameter>
${2.0 * wheel_radius}
</wheel_diameter>
<max_wheel_torque>
20.0
</max_wheel_torque>
<max_wheel_acceleration>
1.0
</max_wheel_acceleration>
<!--
1: Gazebo 실제 위치 기반
0: 바퀴 회전 적분 기반
-->
<odometry_source>1</odometry_source>
<odometry_frame>odom</odometry_frame>
<robot_base_frame>base_link</robot_base_frame>
<publish_odom>true</publish_odom>
<publish_odom_tf>true</publish_odom_tf>
<publish_wheel_tf>true</publish_wheel_tf>
</plugin>
</gazebo>
</robot>

5) Xacro 변환 확인
cd ~/gazebo_ws
ros2 run xacro xacro src/mobile_robot_description/urdf/mobile_robot.urdf.xacro

파일로 저장해서 확인하려면:
ros2 run xacro xacro \ src/mobile_robot_description/urdf/mobile_robot.urdf.xacro \ > /tmp/mobile_robot.urdf
URDF 문법 검사:
check_urdf /tmp/mobile_robot.urdf

6) RViz 확인용 Launch 파일
파일 생성:
touch launch/display.launch.py

내용:
import os
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.substitutions import Command
from launch_ros.substitutions import FindPackageShare
from launch_ros.parameter_descriptions import ParameterValue
def generate_launch_description():
pkg_share = FindPackageShare('mobile_robot_description').find('mobile_robot_description')
xacro_file = os.path.join(pkg_share, 'urdf', 'mobile_robot.urdf.xacro')
robot_description = ParameterValue(
Command(['xacro ', xacro_file]),
value_type=str
)
robot_state_publisher = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{
'robot_description': robot_description
}]
)
joint_state_publisher_gui = Node(
package='joint_state_publisher_gui',
executable='joint_state_publisher_gui'
)
rviz2 = Node(
package='rviz2',
executable='rviz2',
output='screen'
)
return LaunchDescription([
robot_state_publisher,
joint_state_publisher_gui,
rviz2
])

빌드:
cd ~/gazebo_ws
colcon build --packages-select mobile_robot_description
source install/setup.bash

실행:
ros2 launch mobile_robot_description display.launch.py

7) Gazebo 실행용 Launch 파일
파일을 생성합니다.
cd ~/gazebo_ws/src/mobile_robot_description
touch launch/gazebo.launch.py

launch/gazebo.launch.py를 다음과 같이 작성합니다.
import os
from ament_index_python.packages import (
get_package_share_directory
)
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import (
PythonLaunchDescriptionSource
)
from launch.substitutions import Command
from launch_ros.actions import Node
from launch_ros.parameter_descriptions import ParameterValue
def generate_launch_description():
package_name = 'mobile_robot_description'
package_share = get_package_share_directory(
package_name
)
gazebo_share = get_package_share_directory(
'gazebo_ros'
)
xacro_file = os.path.join(
package_share,
'urdf',
'mobile_robot.urdf.xacro'
)
robot_description = ParameterValue(
Command([
'xacro',
' ',
xacro_file
]),
value_type=str
)
gazebo = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(
gazebo_share,
'launch',
'gazebo.launch.py'
)
)
)
robot_state_publisher = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
output='screen',
parameters=[{
'robot_description': robot_description,
'use_sim_time': True
}]
)
spawn_robot = Node(
package='gazebo_ros',
executable='spawn_entity.py',
output='screen',
arguments=[
'-topic',
'robot_description',
'-entity',
'simple_mobile_robot',
'-x',
'0.0',
'-y',
'0.0',
'-z',
'0.11'
]
)
return LaunchDescription([
gazebo,
robot_state_publisher,
spawn_robot
])

-z 0.11은 로봇을 지면보다 약간 위에서 생성하기 위한 값입니다.
이 모델에서 바퀴와 캐스터의 최저점은 base_link 기준 약 -0.10m이므로 0.11m 위치에서 생성하면 지면 위로 자연스럽게 내려앉습니다.
8) Gazebo 빌드 및 실행
터미널 1에서 다음 명령을 실행합니다.
cd ~/gazebo_ws
colcon build --symlink-install --packages-select mobile_robot_description
source install/setup.bash
ros2 launch mobile_robot_description gazebo.launch.py



정상적으로 실행되면 다음 상태가 됩니다.
- Gazebo 실행
- 바닥 평면 표시
simple_mobile_robot모델 생성robot_state_publisher실행- DiffDrive 플러그인 로드
/cmd_vel구독/odom발행/tf발행/clock발행
9) 키보드로 로봇 조종하기
다음 명령을 실행합니다.
source turtlebot3_ws/install/setup.bash
ros2 run turtlebot3_teleop teleop_keyboard
키보드 입력으로 직진, 후진, 회전 및 정지 동작을 시험할 수 있습니다.

15. 실험 1: 마찰을 매우 낮게 설정하기
바퀴의 Gazebo 설정을 다음과 같이 변경합니다.
<gazebo reference="${name}_wheel_link">
<mu1>0.01</mu1>
<mu2>0.01</mu2>
<kp>1000000.0</kp>
<kd>100.0</kd>
<minDepth>0.001</minDepth>
<maxVel>1.0</maxVel>
</gazebo>


예상 결과는 다음과 같습니다.
- 바퀴가 쉽게 미끄러짐
- 바퀴 회전력이 바닥에 충분히 전달되지 않음
- 로봇이 명령한 거리만큼 이동하지 않음
- 회전 중 자세가 불안정함
- 바퀴 회전과 실제 이동 거리의 차이가 커짐
직진 명령으로 확인합니다.
ros2 topic pub \
--rate 10 \
/cmd_vel \
geometry_msgs/msg/Twist \
"{
linear: {
x: 0.2,
y: 0.0,
z: 0.0
},
angular: {
x: 0.0,
y: 0.0,
z: 0.0
}
}"
마찰이 너무 낮으면 모터가 회전해도 접지력이 부족하므로 로봇이 정상적으로 움직이지 않습니다.
16. 실험 2: 일반적인 마찰값 사용하기
바퀴 마찰을 다음과 같이 설정합니다.
<gazebo reference="${name}_wheel_link">
<mu1>1.0</mu1>
<mu2>1.0</mu2>
<kp>1000000.0</kp>
<kd>100.0</kd>
<minDepth>0.001</minDepth>
<maxVel>1.0</maxVel>
</gazebo>


예상 결과는 다음과 같습니다.
- 일반적인 차동구동 로봇처럼 이동함
- 직진 주행이 비교적 안정적임
- 제자리 회전이 가능함
- 바퀴의 회전력이 지면에 전달됨
초기 시험에서는 mu1=1.0, mu2=1.0부터 시작하는 것이 좋습니다.
그다음 다음 요소에 따라 조정합니다.
- 로봇 질량
- 바퀴 반지름
- 바퀴 폭
- 지면 재질
- 목표 가속도
- 물리 엔진 설정
17. 실험 3: 마찰을 지나치게 높이기
바퀴 마찰을 다음과 같이 변경합니다.
<gazebo reference="${name}_wheel_link">
<mu1>100.0</mu1>
<mu2>100.0</mu2>
<kp>1000000.0</kp>
<kd>100.0</kd>
<minDepth>0.001</minDepth>
<maxVel>1.0</maxVel>
</gazebo>


예상 결과는 다음과 같습니다.
- 접지력은 강해짐
- 제자리 회전이 부자연스러울 수 있음
- 접촉 힘이 커질 수 있음
- 로봇이 떨리거나 튈 수 있음
- 물리 계산이 불안정해질 수 있음
마찰 계수는 높다고 무조건 좋은 것이 아닙니다.
실제 로봇과 유사한 움직임을 만들 수 있는 범위에서 조정해야 합니다.
18. 실험 4: 질량을 너무 작게 설정하기
바디 질량을 다음과 같이 변경합니다.
<xacro:property
name="base_mass"
value="0.001"/>
또는 직접 다음과 같이 설정할 수 있습니다.
<mass value="0.001"/>
예상되는 문제는 다음과 같습니다.
- 작은 충돌에도 로봇이 튀어 오름
- 바퀴 접촉이 불안정해짐
- 가속도가 비정상적으로 커짐
- 충돌 시 로봇이 멀리 날아감
- 수치 계산이 불안정해짐
링크 질량은 실제 로봇의 예상 질량과 비슷하게 설정해야 합니다.
19. 실험 5: 관성값을 너무 작게 설정하기
바디 관성값을 다음과 같이 변경합니다.
<inertia
ixx="0.000001"
ixy="0.0"
ixz="0.0"
iyy="0.000001"
iyz="0.0"
izz="0.000001"/>
예상되는 문제는 다음과 같습니다.
- 작은 힘에도 급격히 회전함
- 자세가 계속 떨림
- 충돌 시 각속도가 크게 증가함
- 시뮬레이션이 불안정해짐
관성값은 임의로 작은 값을 입력하는 것이 아니라 링크의 질량과 형상을 이용해 계산해야 합니다.
20. 실험 6: 형상 기반 관성값 사용하기
박스 바디에는 다음 매크로를 사용합니다.
<xacro:box_inertia
m="${base_mass}"
x="${base_length}"
y="${base_width}"
z="${base_height}"/>
바퀴에는 다음 매크로를 사용합니다.
<xacro:cylinder_inertia_y_axis
m="${wheel_mass}"
r="${wheel_radius}"
l="${wheel_width}"/>
예상 결과는 다음과 같습니다.
- 로봇의 움직임이 자연스러워짐
- 회전 반응이 안정됨
- 충돌 후 비정상적인 진동이 감소함
- 바퀴 접촉이 안정됨
- 물리 엔진의 계산이 안정적임
21. 마찰 실험 결과 기록표
실험 결과를 다음과 같이 기록할 수 있습니다.
| 실험 | mu1 | mu2 | 직진 상태 | 회전 상태 | 진동 | 미끄러짐 |
| 낮은 마찰 | 0.01 | 0.01 | 불안정 | 불안정 | 낮음 또는 불규칙 | 매우 큼 |
| 일반 마찰 | 1.0 | 1.0 | 안정 | 비교적 안정 | 낮음 | 적음 |
| 높은 마찰 | 100.0 | 100.0 | 강한 접지 | 회전이 뻑뻑함 | 발생 가능 | 매우 적음 |
22. 관성 실험 결과 기록표
| 실험 | 바디 질량 | 관성값 | 예상 결과 |
| 지나치게 작은 질량 | 0.001kg | 기존값 | 충돌과 가속에 과민 반응 |
| 지나치게 작은 관성 | 5.0kg | 0.000001 | 회전과 진동이 불안정 |
| 형상 기반 관성 | 5.0kg | 수식 계산 | 비교적 안정적인 물리 운동 |