관리 메뉴

데이터 과학

ROS 2와 Gazebo를 이용한 사족보행 로봇 시뮬레이션 환경 구축 본문

인공지능/피지컬 AI

ROS 2와 Gazebo를 이용한 사족보행 로봇 시뮬레이션 환경 구축

티에스윤 2026. 9. 28. 17:21

Ubuntu 26.04에서 ROS 2와 Gazebo를 이용한 PuppyPi 사족보행 로봇 시뮬레이션 환경 구축

1. 개요

Hiwonder PuppyPi는 4개의 다리와 총 8개의 관절을 사용하는 사족보행 로봇입니다. 실제 PuppyPi를 직접 제어하기 전에 Gazebo 환경에서 로봇의 구조와 관절 움직임을 구현하면 보행 알고리즘, ROS 2 제어, 강화학습 등의 실험을 보다 안전하게 진행할 수 있습니다.

본 실습에서는 Ubuntu 26.04 환경에서 ROS 2와 Gazebo를 이용하여 PuppyPi의 가상 모델을 구성하고, 4개의 다리와 8개의 관절을 제어할 수 있는 기본 시뮬레이션 환경을 구축합니다.

전체 구조는 다음과 같습니다.

Ubuntu 26.04
      │
      └── ROS 2 Lyrical
              │
              ├── robot_state_publisher
              │
              ├── ros2_control
              │
              └── ros_gz
                      │
                      ▼
                 Gazebo Jetty
                      │
                      ▼
               PuppyPi 가상 모델
                      │
               4 Legs / 8 DOF
                      │
                      ▼
                 Joint Control

Ubuntu 26.04에서는 ROS 2 Lyrical과 Gazebo Jetty 조합을 사용하는 것이 적절합니다.


2. 개발 환경

본 실습 환경은 다음과 같습니다.

운영체제 : Ubuntu 26.04
ROS 2    : Lyrical
Gazebo   : Jetty
Robot    : Hiwonder PuppyPi
DOF      : 8

먼저 Ubuntu 버전을 확인합니다.

lsb_release -a

ROS 2 배포판을 확인합니다.

printenv ROS_DISTRO

정상적으로 설정되어 있다면 다음과 같이 출력됩니다.

lyrical

Gazebo 버전을 확인합니다.

gz sim --versions

ROS 2 환경을 활성화합니다.

source /opt/ros/lyrical/setup.bash

3. ROS 2와 Gazebo 연동 패키지 설치

ROS 2와 Gazebo 사이의 통신을 위해 ros_gz 패키지를 설치합니다.

sudo apt update
sudo apt install ros-lyrical-ros-gz

추가로 필요한 패키지를 설치합니다.

sudo apt install \
ros-lyrical-xacro \
ros-lyrical-robot-state-publisher \
ros-lyrical-joint-state-publisher \
ros-lyrical-joint-state-publisher-gui

ros2_control 관련 패키지도 설치합니다.

sudo apt install \
ros-lyrical-ros2-control \
ros-lyrical-ros2-controllers

Gazebo와 ros2_control을 연결하기 위해 gz_ros2_control도 필요합니다.

sudo apt install \
ros-lyrical-gz-ros2-control

만약 해당 패키지가 apt 저장소에서 제공되지 않는 경우에는 소스 빌드 방식으로 설치해야 합니다.


4. PuppyPi Workspace 생성

PuppyPi 전용 ROS 2 workspace를 생성합니다.

mkdir -p ~/puppypi_ws/src
cd ~/puppypi_ws/src

PuppyPi description 패키지를 생성합니다.

ros2 pkg create \
--build-type ament_cmake \
puppypi_description

필요한 폴더를 생성합니다.

cd ~/puppypi_ws/src/puppypi_description
mkdir -p \
urdf \
launch \
worlds \
config \
meshes

최종 디렉터리 구조는 다음과 같습니다.

puppypi_ws
└── src
    └── puppypi_description
        ├── config
        │   └── controllers.yaml
        ├── launch
        │   └── gazebo.launch.py
        ├── meshes
        ├── urdf
        │   └── puppypi.urdf.xacro
        ├── worlds
        │   └── puppy_world.sdf
        ├── CMakeLists.txt
        └── package.xml

5. PuppyPi 관절 구조

PuppyPi는 4개의 다리와 8개의 관절을 갖는 구조로 구성합니다.

각 다리는 hip 관절과 knee 관절로 구성합니다.

                    FRONT

          FL                     FR

       Hip Joint              Hip Joint
          │                      │
       Upper Leg              Upper Leg
          │                      │
       Knee Joint             Knee Joint
          │                      │
       Lower Leg              Lower Leg


        ┌────────────────────────┐
        │                        │
        │       base_link        │
        │                        │
        └────────────────────────┘


          │                      │
       Hip Joint              Hip Joint
          │                      │
       Upper Leg              Upper Leg
          │                      │
       Knee Joint             Knee Joint
          │                      │
       Lower Leg              Lower Leg

          RL                     RR

                    REAR

8개의 관절 이름은 다음과 같이 정의합니다.

fl_hip_joint
fl_knee_joint

fr_hip_joint
fr_knee_joint

rl_hip_joint
rl_knee_joint

rr_hip_joint
rr_knee_joint

6. PuppyPi URDF/Xacro 작성

URDF/Xacro 파일을 생성합니다.

nano \
~/puppypi_ws/src/puppypi_description/urdf/puppypi.urdf.xacro

다음 내용을 입력합니다.

 

<?xml version="1.0"?>

<robot
  xmlns:xacro="http://www.ros.org/wiki/xacro"
  name="puppypi">

  <!-- 기본 크기 설정 -->

  <xacro:property
    name="body_length"
    value="0.28"/>

  <xacro:property
    name="body_width"
    value="0.14"/>

  <xacro:property
    name="body_height"
    value="0.06"/>

  <xacro:property
    name="upper_leg_length"
    value="0.10"/>

  <xacro:property
    name="lower_leg_length"
    value="0.11"/>


  <!-- 본체 -->

  <link name="base_link">

    <visual>
      <geometry>
        <box size="${body_length} ${body_width} ${body_height}"/>
      </geometry>
    </visual>

    <collision>
      <geometry>
        <box size="${body_length} ${body_width} ${body_height}"/>
      </geometry>
    </collision>

    <inertial>

      <mass value="1.5"/>

      <inertia
        ixx="0.003"
        iyy="0.010"
        izz="0.012"
        ixy="0"
        ixz="0"
        iyz="0"/>

    </inertial>

  </link>


  <!-- 다리 생성 Macro -->

  <xacro:macro
    name="leg"
    params="prefix x y">

    <link name="${prefix}_upper_leg">

      <visual>

        <origin
          xyz="0 0 -${upper_leg_length/2}"/>

        <geometry>
          <cylinder
            radius="0.015"
            length="${upper_leg_length}"/>
        </geometry>

      </visual>

      <collision>

        <origin
          xyz="0 0 -${upper_leg_length/2}"/>

        <geometry>
          <cylinder
            radius="0.015"
            length="${upper_leg_length}"/>
        </geometry>

      </collision>

      <inertial>

        <origin
          xyz="0 0 -${upper_leg_length/2}"/>

        <mass value="0.12"/>

        <inertia
          ixx="0.00012"
          iyy="0.00012"
          izz="0.000025"
          ixy="0"
          ixz="0"
          iyz="0"/>

      </inertial>

    </link>


    <joint
      name="${prefix}_hip_joint"
      type="revolute">

      <parent link="base_link"/>

      <child link="${prefix}_upper_leg"/>

      <origin
        xyz="${x} ${y} 0"
        rpy="0 0 0"/>

      <axis xyz="0 1 0"/>

      <limit
        lower="-1.2"
        upper="1.2"
        effort="20"
        velocity="5"/>

      <dynamics
        damping="0.15"
        friction="0.05"/>

    </joint>


    <link name="${prefix}_lower_leg">

      <visual>

        <origin
          xyz="0 0 -${lower_leg_length/2}"/>

        <geometry>
          <cylinder
            radius="0.012"
            length="${lower_leg_length}"/>
        </geometry>

      </visual>

      <collision>

        <origin
          xyz="0 0 -${lower_leg_length/2}"/>

        <geometry>
          <cylinder
            radius="0.012"
            length="${lower_leg_length}"/>
        </geometry>

      </collision>

      <inertial>

        <origin
          xyz="0 0 -${lower_leg_length/2}"/>

        <mass value="0.08"/>

        <inertia
          ixx="0.00008"
          iyy="0.00008"
          izz="0.000015"
          ixy="0"
          ixz="0"
          iyz="0"/>

      </inertial>

    </link>


    <joint
      name="${prefix}_knee_joint"
      type="revolute">

      <parent
        link="${prefix}_upper_leg"/>

      <child
        link="${prefix}_lower_leg"/>

      <origin
        xyz="0 0 -${upper_leg_length}"
        rpy="0 0 0"/>

      <axis xyz="0 1 0"/>

      <limit
        lower="-2.0"
        upper="0.2"
        effort="20"
        velocity="5"/>

      <dynamics
        damping="0.15"
        friction="0.05"/>

    </joint>

  </xacro:macro>


  <!-- Front Left -->

  <xacro:leg
    prefix="fl"
    x="0.11"
    y="0.075"/>


  <!-- Front Right -->

  <xacro:leg
    prefix="fr"
    x="0.11"
    y="-0.075"/>


  <!-- Rear Left -->

  <xacro:leg
    prefix="rl"
    x="-0.11"
    y="0.075"/>


  <!-- Rear Right -->

  <xacro:leg
    prefix="rr"
    x="-0.11"
    y="-0.075"/>


  <!-- ros2_control -->

  <ros2_control
    name="GazeboSimSystem"
    type="system">

    <hardware>

      <plugin>
        gz_ros2_control/GazeboSimSystem
      </plugin>

    </hardware>


    <joint name="fl_hip_joint">
      <command_interface name="position"/>
      <state_interface name="position"/>
      <state_interface name="velocity"/>
    </joint>

    <joint name="fl_knee_joint">
      <command_interface name="position"/>
      <state_interface name="position"/>
      <state_interface name="velocity"/>
    </joint>

    <joint name="fr_hip_joint">
      <command_interface name="position"/>
      <state_interface name="position"/>
      <state_interface name="velocity"/>
    </joint>

    <joint name="fr_knee_joint">
      <command_interface name="position"/>
      <state_interface name="position"/>
      <state_interface name="velocity"/>
    </joint>

    <joint name="rl_hip_joint">
      <command_interface name="position"/>
      <state_interface name="position"/>
      <state_interface name="velocity"/>
    </joint>

    <joint name="rl_knee_joint">
      <command_interface name="position"/>
      <state_interface name="position"/>
      <state_interface name="velocity"/>
    </joint>

    <joint name="rr_hip_joint">
      <command_interface name="position"/>
      <state_interface name="position"/>
      <state_interface name="velocity"/>
    </joint>

    <joint name="rr_knee_joint">
      <command_interface name="position"/>
      <state_interface name="position"/>
      <state_interface name="velocity"/>
    </joint>

  </ros2_control>


  <!-- Gazebo ros2_control Plugin -->

  <gazebo>

    <plugin
      filename="libgz_ros2_control-system.so"
      name="gz_ros2_control::GazeboSimROS2ControlPlugin">

      <robot_param>
        robot_description
      </robot_param>

      <robot_param_node>
        robot_state_publisher
      </robot_param_node>

      <parameters>
        $(find puppypi_description)/config/controllers.yaml
      </parameters>

    </plugin>

  </gazebo>

</robot>


  

  

  

  

  

  


  

  

    
      
        
      
    

    
      
        
      
    

    

      

      

    

  


  

  

    

      

        

        
          
        

      

      

        

        
          
        

      

      

        

        

        

      

    


    

      

      

      

      

      

      

    


    

      

        

        
          
        

      

      

        

        
          
        

      

      

        

        

        

      

    


    

      

      

      

      

      

      

    

  


  

  


  

  


  

  


  

  


  

  

    

      
        gz_ros2_control/GazeboSimSystem
      

    


    
      
      
      
    

    
      
      
      
    

    
      
      
      
    

    
      
      
      
    

    
      
      
      
    

    
      
      
      
    

    
      
      
      
    

    
      
      
      
    

  


  

  

    

      
        robot_description
      

      
        robot_state_publisher
      

      
        $(find puppypi_description)/config/controllers.yaml

7. URDF/Xacro 문법 확인

작성한 Xacro 파일이 정상적으로 URDF로 변환되는지 확인합니다.

cd ~/puppypi_ws
source /opt/ros/lyrical/setup.bash
xacro \
src/puppypi_description/urdf/puppypi.urdf.xacro \
> /tmp/puppypi.urdf

오류가 없다면 정상적으로 변환된 것입니다.

관절이 모두 포함되어 있는지 확인합니다.

grep joint /tmp/puppypi.urdf

다음 8개의 관절이 확인되어야 합니다.

fl_hip_joint
fl_knee_joint

fr_hip_joint
fr_knee_joint

rl_hip_joint
rl_knee_joint

rr_hip_joint
rr_knee_joint

8. ros2_control Controller 설정

Controller 설정 파일을 생성합니다.

nano \
~/puppypi_ws/src/puppypi_description/config/controllers.yaml

다음 내용을 입력합니다.

controller_manager:

  ros__parameters:

    update_rate: 100

    joint_state_broadcaster:

      type:
        joint_state_broadcaster/JointStateBroadcaster

    puppy_joint_controller:

      type:
        joint_trajectory_controller/JointTrajectoryController


puppy_joint_controller:

  ros__parameters:

    joints:

      - fl_hip_joint
      - fl_knee_joint

      - fr_hip_joint
      - fr_knee_joint

      - rl_hip_joint
      - rl_knee_joint

      - rr_hip_joint
      - rr_knee_joint

    command_interfaces:

      - position

    state_interfaces:

      - position
      - velocity

joint_state_broadcaster는 각 관절의 상태를 ROS 2 Topic으로 전달합니다.

joint_trajectory_controller는 각 관절의 목표 위치를 전달하는 데 사용합니다.


9. Gazebo World 생성

Gazebo에서 사용할 기본 평면 환경을 생성합니다.

nano \
~/puppypi_ws/src/puppypi_description/worlds/puppy_world.sdf

다음 내용을 입력합니다.

<?xml version="1.0" ?>

<sdf version="1.10">

  <world name="puppy_world">

    <physics
      name="physics"
      type="ignored">

      <max_step_size>
        0.001
      </max_step_size>

      <real_time_factor>
        1.0
      </real_time_factor>

    </physics>


    <plugin
      filename="gz-sim-physics-system"
      name="gz::sim::systems::Physics"/>


    <plugin
      filename="gz-sim-user-commands-system"
      name="gz::sim::systems::UserCommands"/>


    <plugin
      filename="gz-sim-scene-broadcaster-system"
      name="gz::sim::systems::SceneBroadcaster"/>


    <light
      type="directional"
      name="sun">

      <pose>
        0 0 10 0 0 0
      </pose>

      <diffuse>
        0.8 0.8 0.8 1
      </diffuse>

      <direction>
        -0.5 0.1 -0.9
      </direction>

    </light>


    <model name="ground_plane">

      <static>true</static>

      <link name="link">

        <collision name="collision">

          <geometry>

            <plane>

              <normal>
                0 0 1
              </normal>

              <size>
                20 20
              </size>

            </plane>

          </geometry>

        </collision>


        <visual name="visual">

          <geometry>

            <plane>

              <normal>
                0 0 1
              </normal>

              <size>
                20 20
              </size>

            </plane>

          </geometry>

        </visual>

      </link>

    </model>

  </world>

</sdf>

10. Gazebo Launch 파일 생성

Gazebo 실행과 PuppyPi 생성, ros2_control Controller 실행을 하나의 launch 파일로 구성합니다.

nano \
~/puppypi_ws/src/puppypi_description/launch/gazebo.launch.py

다음 내용을 입력합니다.

import os
import xacro

from launch import LaunchDescription

from launch.actions import (
    IncludeLaunchDescription,
    TimerAction
)

from launch.launch_description_sources import (
    PythonLaunchDescriptionSource
)

from launch_ros.actions import Node

from ament_index_python.packages import (
    get_package_share_directory
)


def generate_launch_description():

    pkg_share = (
        get_package_share_directory(
            'puppypi_description'
        )
    )

    ros_gz_sim_share = (
        get_package_share_directory(
            'ros_gz_sim'
        )
    )

    xacro_file = os.path.join(
        pkg_share,
        'urdf',
        'puppypi.urdf.xacro'
    )

    robot_description = (
        xacro.process_file(
            xacro_file
        ).toxml()
    )

    world_file = os.path.join(
        pkg_share,
        'worlds',
        'puppy_world.sdf'
    )


    gazebo = IncludeLaunchDescription(

        PythonLaunchDescriptionSource(

            os.path.join(
                ros_gz_sim_share,
                'launch',
                'gz_sim.launch.py'
            )

        ),

        launch_arguments={

            'gz_args':
                f'-r {world_file}'

        }.items()

    )


    robot_state_publisher = Node(

        package=
            'robot_state_publisher',

        executable=
            'robot_state_publisher',

        name=
            'robot_state_publisher',

        output=
            'screen',

        parameters=[

            {

                'robot_description':
                    robot_description,

                'use_sim_time':
                    True

            }

        ]

    )


    spawn_robot = Node(

        package=
            'ros_gz_sim',

        executable=
            'create',

        arguments=[

            '-name',
            'puppypi',

            '-topic',
            'robot_description',

            '-x',
            '0.0',

            '-y',
            '0.0',

            '-z',
            '0.27'

        ],

        output='screen'

    )


    joint_state_broadcaster = Node(

        package=
            'controller_manager',

        executable=
            'spawner',

        arguments=[

            'joint_state_broadcaster',

            '--controller-manager',

            '/controller_manager'

        ],

        output='screen'

    )


    puppy_joint_controller = Node(

        package=
            'controller_manager',

        executable=
            'spawner',

        arguments=[

            'puppy_joint_controller',

            '--controller-manager',

            '/controller_manager'

        ],

        output='screen'

    )


    return LaunchDescription([

        gazebo,

        robot_state_publisher,

        spawn_robot,

        TimerAction(

            period=3.0,

            actions=[
                joint_state_broadcaster
            ]

        ),

        TimerAction(

            period=5.0,

            actions=[
                puppy_joint_controller
            ]

        )

    ])

11. CMakeLists.txt 수정

다음 내용을 CMakeLists.txt에 추가합니다.

install(
  DIRECTORY

    launch
    urdf
    worlds
    config
    meshes

  DESTINATION

    share/${PROJECT_NAME}
)

12. package.xml 의존성 설정

package.xml에는 다음과 같은 의존성을 추가합니다.

<exec_depend>xacro</exec_depend>

<exec_depend>robot_state_publisher</exec_depend>

<exec_depend>ros_gz_sim</exec_depend>

<exec_depend>controller_manager</exec_depend>

<exec_depend>joint_state_broadcaster</exec_depend>

<exec_depend>joint_trajectory_controller</exec_depend>

<exec_depend>gz_ros2_control</exec_depend>

13. PuppyPi 패키지 빌드

Workspace 루트로 이동합니다.

cd ~/puppypi_ws

ROS 2 환경을 활성화합니다.

source /opt/ros/lyrical/setup.bash

의존성을 설치합니다.

rosdep install \
--from-paths src \
--ignore-src \
-r -y

빌드합니다.

colcon build \
--symlink-install

빌드가 완료되면 환경을 다시 불러옵니다.

source install/setup.bash

14. Gazebo에서 PuppyPi 실행

다음 명령을 실행합니다.

ros2 launch \
puppypi_description \
gazebo.launch.py

정상적으로 실행되면 Gazebo 안에 PuppyPi 가상 모델이 생성됩니다.

           FRONT

       FL          FR
       │            │
       │            │

    ┌──────────────────┐
    │                  │
    │     PuppyPi      │
    │                  │
    └──────────────────┘

       │            │
       │            │
       RL          RR

           REAR

현재 모델은 실제 PuppyPi의 외형을 그대로 재현한 모델이 아니라, 관절 제어와 보행 알고리즘 실습을 위한 단순화된 8-DOF 모델입니다.


15. Controller 동작 확인

새로운 터미널을 실행합니다.

source /opt/ros/lyrical/setup.bash
source ~/puppypi_ws/install/setup.bash

Controller 상태를 확인합니다.

ros2 control list_controllers

정상적으로 실행되고 있다면 다음과 유사하게 나타납니다.

joint_state_broadcaster
    joint_state_broadcaster/JointStateBroadcaster
    active

puppy_joint_controller
    joint_trajectory_controller/JointTrajectoryController
    active

관절 상태도 확인할 수 있습니다.

ros2 topic echo /joint_states

8개의 관절 이름과 위치 값이 출력되면 정상입니다.


16. PuppyPi의 다리 움직이기

이제 ros2_control을 이용하여 8개의 관절을 움직여 봅니다.

ros2 topic pub --once \
/puppy_joint_controller/joint_trajectory \
trajectory_msgs/msg/JointTrajectory \
"{
joint_names: [
'fl_hip_joint',
'fl_knee_joint',
'fr_hip_joint',
'fr_knee_joint',
'rl_hip_joint',
'rl_knee_joint',
'rr_hip_joint',
'rr_knee_joint'
],
points: [
{
positions: [
0.2, -0.8,
0.2, -0.8,
0.2, -0.8,
0.2, -0.8
],
time_from_start: {
sec: 1,
nanosec: 0
}
}
]
}"

PuppyPi의 네 다리가 동시에 움직인다면 ros2_control 연결이 정상적으로 완료된 것입니다.


17. 앞다리의 자세 변경하기

다음과 같이 각 관절에 서로 다른 값을 전달할 수 있습니다.

ros2 topic pub --once \
/puppy_joint_controller/joint_trajectory \
trajectory_msgs/msg/JointTrajectory \
"{
joint_names: [
'fl_hip_joint',
'fl_knee_joint',
'fr_hip_joint',
'fr_knee_joint',
'rl_hip_joint',
'rl_knee_joint',
'rr_hip_joint',
'rr_knee_joint'
],
points: [{
positions: [
0.5, -1.0,
-0.2, -0.6,
0.2, -0.8,
0.2, -0.8
],
time_from_start: {
sec: 1,
nanosec: 0
}
}]
}"

앞쪽 두 다리의 움직임이 다르게 나타난다면 각 관절을 독립적으로 제어할 수 있는 상태입니다.


18. 현재까지 완성된 구조

현재까지 구성한 전체 시스템은 다음과 같습니다.

                ROS 2 Lyrical

                     │

          robot_state_publisher

                     │
                     ▼

               PuppyPi URDF

                     │
                     ▼

                Gazebo Jetty

                     │

              gz_ros2_control

                     │
                     ▼

             controller_manager

               ┌─────┴─────┐
               │           │
               ▼           ▼

       joint_state      joint_trajectory
       broadcaster       controller

               │           │
               └─────┬─────┘
                     │
                     ▼

                  8 joints

        FL                  FR
       hip                 hip
       knee                knee

              PuppyPi

       hip                 hip
       knee                knee
        RL                  RR

19. WSL에서 Gazebo 실행하기

WSL2에서 Gazebo를 사용하는 경우 WSLg를 통해 Linux GUI 프로그램을 Windows 화면에 표시할 수 있습니다.

먼저 Gazebo가 정상적으로 실행되는지 확인합니다.

gz sim

Gazebo GUI가 나타나면 기본 설정은 정상입니다.

화면이 검게 나타나거나 렌더링 오류가 발생하는 경우 다음을 확인합니다.

echo $DISPLAY

OpenGL 상태도 확인합니다.

glxinfo | grep OpenGL

glxinfo가 설치되어 있지 않은 경우 다음 명령을 사용합니다.

sudo apt install mesa-utils

WSL 환경에서는 Gazebo 시뮬레이션 자체와 GUI 렌더링 문제가 별도로 발생할 수 있으므로, Gazebo 단독 실행을 먼저 확인한 뒤 ROS 2 launch 파일을 실행하는 것이 좋습니다.


20. 실제 PuppyPi와 Gazebo의 관계

현재 구성은 실제 PuppyPi의 Raspberry Pi에 Gazebo를 직접 설치하는 방식이 아닙니다.

Gazebo는 PC 또는 WSL 환경에서 실행합니다.

Windows PC
    │
   WSL
    │
Ubuntu 26.04
    │
ROS 2 Lyrical
    │
Gazebo Jetty
    │
Virtual PuppyPi

실제 PuppyPi에서는 별도의 ROS 2 제어 시스템이 동작합니다.

PuppyPi
    │
Raspberry Pi
    │
ROS 2
    │
puppy_control
    │
Gait Controller
    │
Inverse Kinematics
    │
8 Servos

따라서 Gazebo에서는 PuppyPi의 가상 모델을 이용하여 보행과 제어 알고리즘을 검증하고, 이후 실제 PuppyPi의 제어 시스템으로 해당 알고리즘을 연결하는 방식으로 확장할 수 있습니다.


21. 다음 단계

현재 단계에서는 다음과 같은 기능까지 구성한 상태입니다.

Ubuntu 26.04
        ↓
ROS 2 Lyrical
        ↓
Gazebo Jetty
        ↓
PuppyPi URDF/Xacro
        ↓
4 Legs / 8 Joints
        ↓
gz_ros2_control
        ↓
JointTrajectoryController
        ↓
관절 제어

다음 단계에서는 PuppyPi 가상 모델에 센서를 추가할 수 있습니다.

PuppyPi
   │
   ├── IMU
   ├── Contact Sensor
   ├── Camera
   └── LiDAR

보행 기능은 다음과 같은 순서로 확장할 수 있습니다.

Standing
   ↓
Walking
   ↓
Trot
   ↓
Turning
   ↓
Slope Walking

최종적으로는 다음과 같은 구조를 구현할 수 있습니다.

Gazebo PuppyPi
       ↓
Gait Generator
       ↓
Inverse Kinematics
       ↓
ROS 2 Control
       ↓
4족 보행

이후 PPO와 같은 강화학습 알고리즘을 연결하면 PuppyPi의 보행 안정성, 경사면 이동, 장애물 회피 등의 실험으로 확장할 수 있습니다.


정리

이번 실습에서는 Ubuntu 26.04, ROS 2 Lyrical 및 Gazebo Jetty 환경에서 PuppyPi 사족보행 로봇의 기본 시뮬레이션 환경을 구축합니다.

PuppyPi를 4개의 다리와 8개의 회전 관절로 구성하고, Xacro를 이용하여 반복적인 다리 구조를 정의합니다. 또한 gz_ros2_control을 이용하여 Gazebo와 ROS 2의 controller manager를 연결하고, joint_trajectory_controller를 통해 각 관절의 목표 각도를 제어합니다.

이 과정을 통해 PuppyPi의 기본적인 관절 구조와 제어 방법을 이해할 수 있으며, 이후 보행 알고리즘, 역기구학, IMU 기반 자세 제어, LiDAR 기반 이동 및 강화학습 실험으로 확장할 수 있습니다.

다만 현재 사용한 링크 길이, 질량, 관성값과 관절 범위는 시뮬레이션 구성을 위한 근사값입니다. 실제 PuppyPi와 동일한 동작을 구현하려면 이후 실제 기체의 링크 길이, 서보 위치, 질량 분포 및 관절 제한값을 반영하여 URDF 모델을 보정하는 과정이 필요합니다.

 

 

chatGTP 정리