Gazebo / Ros2 시뮬레이터

이제 명령을 내리기위한 컨트롤 용도로 Ros2를 사용, Gazebo 시뮬레이터를 사용해 시뮬레이션 환경에서 제어를 적용해 볼 것이다.

Ros2는 사용해본 경험이 있지만, Gazebo는 경험이 없어서 설치부터 환경세팅까지 정리 한 후 로봇을 모델링했다.

로봇 모델링은 아래와 같은 모델 코드로 구현했고, 외장 디자인과 바퀴등은 인터넷 자료를 활용했다.

image.png

활용한 인터넷 자료 디자인

활용한 인터넷 자료 디자인

모델 제작 과정

모델 코드 model.sdf
<?xml version='1.0'?>
    <sdf version='1.7'>
      <model name='mobilebot'>
        <link name="base_link">
          <inertial>
            <mass>102</mass>
            <inertia>
                <ixx>3.3668</ixx>
                <ixy>0</ixy>
                <ixz>0</ixz>
                <iyy>5.0881</iyy>
                <iyz>0</iyz>
                <izz>7.8412</izz>
            </inertia>
          </inertial>
          <collision name="collision_base">
            <geometry>
              <box>
    			<size>0.6 0.4 0.05</size>
              </box>
            </geometry>
          </collision>
    	    <visual name="visual_base">
            <geometry>
              <box>
    			<size>0.6 0.4 0.05</size>
              </box>
            </geometry>
            <material>
              <ambient>0.8 0.8 0.8 1</ambient>
              <diffuse>0.3 0.3 0.3 1</diffuse>
              <specular>0.1 0.1 0.1 1</specular>
              <emissive>0.0 0.0 0.0 1</emissive>
            </material>
          </visual>
    	  </link>
    
        <link name="front_right_hinge">
    			<pose>0.0 -0.215 0.025 0 0 0</pose>
    		  <inertial>
    				<mass>0.1</mass>
    				<inertia>
    					<ixx>0.01</ixx>
    					<ixy>0</ixy>
    					<ixz>0</ixz>
    					<iyy>0.01</iyy>
    					<iyz>0</iyz>
    					<izz>0.01</izz>
    				</inertia>
    			</inertial>
    			<visual name="visual_front_right_hinge">
    				<geometry>
    					<box>
    						<size>0.03 0.03 0.1</size>
    					</box>
    				</geometry>
    				<material>
    					<ambient>0.8 0.8 0.8 1</ambient>
    					<diffuse>0.6 0.6 0.6 1</diffuse>
    					<specular>0.1 0.1 0.1 1</specular>
    					<emissive>0.0 0.0 0.0 1</emissive>
    				</material>
    			</visual>
    	  </link>
    
        <joint name="front_right_axis_joint" type="fixed">
    			<child>front_right_hinge</child>
    			<parent>base_link</parent>
    		</joint> 
    
        <link name="front_left_hinge">
    			<pose>0.0 0.215 0.025 0 0 0</pose>
    		  <inertial>
    				<mass>0.1</mass>
    				<inertia>
    					<ixx>0.01</ixx>
    					<ixy>0</ixy>
    					<ixz>0</ixz>
    					<iyy>0.01</iyy>
    					<iyz>0</iyz>
    					<izz>0.01</izz>
    				</inertia>
    			</inertial>
    			<visual name="visual_front_left_hinge">
    				<geometry>
    					<box>
    						<size>0.03 0.03 0.1</size>
    					</box>
    				</geometry>
    				<material>
    					<ambient>0.8 0.8 0.8 1</ambient>
    					<diffuse>0.6 0.6 0.6 1</diffuse>
    					<specular>0.1 0.1 0.1 1</specular>
    					<emissive>0.0 0.0 0.0 1</emissive>
    				</material>
    			</visual>
    		</link>
    
            <joint name="front_left_axis_joint" type="fixed">
    			<child>front_left_hinge</child>
    			<parent>base_link</parent>
    		</joint> 
    
    <link name="front_left_wheel">
    		  <pose>0.0 0.25 0.03 1.57079 0 0</pose>
    		  <inertial>
    				<mass>4.0</mass>
    				<inertia>
    					<ixx>0.0064</ixx>
    					<ixy>0</ixy>
    					<ixz>0</ixz>
    					<iyy>0.0064</iyy>
    					<iyz>0</iyz>
    					<izz>0.0128</izz>
    				</inertia>
    			</inertial>
    		  <collision name="collision_front_left_wheel">
    		  	<pose>0 0 0 0 0 0</pose>
    		    <geometry>
    		      <cylinder>
    		        <length>0.05</length>
    						<radius>0.08</radius>
    		      </cylinder>
    		    </geometry>
    			  <!--max_contacts>1</max_contacts-->
    				<surface>
    		      <friction>
    		        <ode>
    		          <mu>20.0</mu>
    		          <mu2>20.0</mu2>
    		          <fdir1>1 0 0</fdir1>
    		          <slip1>0</slip1>
    		          <slip2>0.001</slip2>		          
    		        </ode>
    		      </friction>
    		      <contact>
                <ode>
                  <kp>1000000000000000000000000000.0</kp>
                  <kd>1000000000000000000000000000.0</kd>
                </ode>
              </contact>
    			  </surface>
    		  </collision>
    			<visual name="visual_front_left_wheel">
       			<pose>0.0 0.0 0.0 1.57079 0 0</pose>
    		    <geometry>
    		      <mesh>
    		        <uri>model://mobilebot/meshes/wheel.dae</uri>
    		        <scale>0.45 0.45 0.45</scale>
    		      </mesh>
    		    </geometry>
    		  </visual>
    		</link>
    
        <joint name="front_left_wheel_joint" type="revolute">
    			<child>front_left_wheel</child>
    			<parent>front_left_hinge</parent>
    			<axis>
    				<xyz>0 0 1</xyz>
    			</axis>
    			<limit>
    				<effort>2.0</effort>
    				<velocity>3.0</velocity>			
    			</limit>
    			<joint_properties>
    				<damping>0.0</damping>
    				<friction>0.001</friction>				
    			</joint_properties>			
    		</joint>
    
        <link name="front_right_wheel">
    			<pose>0.0 -0.25 0.03 1.57079 0 0</pose>
    		  <inertial>
    				<mass>4.0</mass>
    				<inertia>
    					<ixx>0.0064</ixx>
    					<ixy>0</ixy>
    					<ixz>0</ixz>
    					<iyy>0.0064</iyy>
    					<iyz>0</iyz>
    					<izz>0.0128</izz>
    				</inertia>
    			</inertial>
    			<collision name="collision_front_right_wheel">
    			  <pose>0 0 0 0 0 0</pose>
    			  <geometry>
    		      <cylinder>
    		        <length>0.05</length>
    						<radius>0.08</radius>
    		      </cylinder>
    		    </geometry>
    		    <!--max_contacts>1</max_contacts-->
    				<surface>
    		      <friction>
    		        <ode>
    		          <mu>20.0</mu>
    		          <mu2>20.0</mu2>
    		          <fdir1>1 0 0</fdir1>
    		          <slip1>0</slip1>
    		          <slip2>0.001</slip2>		          
    		        </ode>
    		      </friction>
    		      <contact>
                <ode>
                  <!--min_depth>0.001</min_depth-->
                  <kp>1000000000000000000000000000.0</kp>
                  <kd>1000000000000000000000000000.0</kd>
                </ode>
              </contact>
    			  </surface>
    			</collision>
    			<visual name="visual_front_right_wheel">
       			<pose>0.0 0.0 0.0 1.57079 0 0</pose>			
    		    <geometry>
    		      <mesh>
    		        <uri>model://mobilebot/meshes/wheel.dae</uri>
    		        <scale>0.45 0.45 0.45</scale>
    		      </mesh>
    		    </geometry>
    			</visual>
    		</link>
    
        <joint name="front_right_wheel_joint" type="revolute">
    			<child>front_right_wheel</child>
    			<parent>front_right_hinge</parent>
    			<axis>
    				<xyz>0 0 1</xyz>
    			</axis>
    			<limit>
    				<effort>2.0</effort>
    				<velocity>3.0</velocity>				
    			</limit>
    			<joint_properties>
    				<damping>0.0</damping>
    				<friction>0.001</friction>				
    			</joint_properties>			
    		</joint>
    
        <link name="front_yaw_link">
    			<pose>0.25 0 -0.04 0 1.5707 1.5707</pose>			
    			<visual name="visual_front_yaw_link">
    		    <geometry>
    		      <cylinder>
    		        <length>0.005</length>
    		        <radius>0.01</radius>
    		      </cylinder>
    		    </geometry>
    			</visual>
    			<collision name="collision_front_yaw_link">
    		    <geometry>
    		      <cylinder>
    		        <length>0.005</length>
    		        <radius>0.01</radius>
    		      </cylinder>
    		    </geometry>
    			</collision>
    			<inertial>
    				<mass>0.01</mass>
    				<inertia>
    					<ixx>5.145833333333334e-06</ixx>
    					<ixy>0</ixy>
    					<ixz>0</ixz>
    					<iyy>5.145833333333334e-06</iyy>
    					<iyz>0</iyz>
    					<izz>1.0125000000000003e-04</izz>
    				</inertia>
    			</inertial>
    		</link>
    		
        <joint name="front_yaw_joint" type="revolute">
          <child>front_yaw_link</child>
    			<parent>base_link</parent>
    			<axis>
    				<xyz>0 0 1</xyz>
    			</axis>
    			<limit>
    				<effort>1000.0</effort>
    				<velocity>100.0</velocity>				
    			</limit>
    			<dynamics>
    				<damping>0.0</damping>
    				<friction>0.001</friction>				
    			</dynamics>
        </joint>
    
    <link name="front_roll_link">
    			<pose>0.25 0 -0.04 0 1.5707 1.5707</pose>			
    			<visual name="visual_front_roll_link">
    		    <geometry>
    		      <cylinder>
    		        <length>0.005</length>
    		        <radius>0.01</radius>
    		      </cylinder>
    		    </geometry>
    			</visual>
    			<collision name="collision_front_roll_link">
    		    <geometry>
    		      <cylinder>
    		        <length>0.005</length>
    		        <radius>0.01</radius>
    		      </cylinder>
    		    </geometry>
    			</collision>
    			<inertial>
    				<mass>0.01</mass>
    				<inertia>
    					<ixx>5.145833333333334e-06</ixx>
    					<ixy>0</ixy>
    					<ixz>0</ixz>
    					<iyy>5.145833333333334e-06</iyy>
    					<iyz>0</iyz>
    					<izz>1.0125000000000003e-04</izz>
    				</inertia>
    			</inertial>
    		</link>
    		
        <joint name="front_roll_joint" type="revolute">
          <child>front_roll_link</child>
    			<parent>front_yaw_link</parent>
    			<axis>
    				<xyz>1 0 0</xyz>
    			</axis>
    			<limit>
    				<effort>1000.0</effort>
    				<velocity>100.0</velocity>				
    			</limit>
    			<dynamics>
    				<damping>0.0</damping>
    				<friction>0.001</friction>				
    			</dynamics>
        </joint>
    		
    		<link name="front_pitch_link">
    			<pose>0.25 0 -0.04 0 1.5707 1.5707</pose>			
    			<visual name="visual_front_pitch_link">
    		    <geometry>
    		      <sphere>
    		        <length>0.005</length>
    		        <radius>0.01</radius>
    		      </sphere>
    		    </geometry>
    			</visual>
    			<collision name="collision_front_pitch_link">
    		    <geometry>
    		      <sphere>
    		        <radius>0.01</radius>
    		      </sphere>
    		    </geometry>
    			</collision>
    			<inertial>
    				<mass>0.01</mass>
    				<inertia>
    					<ixx>5.145833333333334e-06</ixx>
    					<ixy>0</ixy>
    					<ixz>0</ixz>
    					<iyy>5.145833333333334e-06</iyy>
    					<iyz>0</iyz>
    					<izz>1.0125000000000003e-04</izz>
    				</inertia>
    			</inertial>
    		</link>
    		
        <joint name="front_pitch_joint" type="revolute">
          <child>front_pitch_link</child>
    			<parent>front_roll_link</parent>
    			<axis>
    				<xyz>0 1 0</xyz>
    			</axis>
    			<limit>
    				<effort>1000.0</effort>
    				<velocity>100.0</velocity>				
    			</limit>
    			<dynamics>
    				<damping>0.0</damping>
    				<friction>0.001</friction>				
    			</dynamics>
        </joint>
    
    <link name="back_yaw_link">
    			<pose>-0.25 0 -0.04 0 1.5707 1.5707</pose>			
    			<visual name="visual_back_yaw_link">
    		    <geometry>
    		      <cylinder>
    		        <length>0.005</length>
    		        <radius>0.01</radius>
    		      </cylinder>
    		    </geometry>
    			</visual>
    			<collision name="collision_back_yaw_link">
    		    <geometry>
    		      <cylinder>
    		        <length>0.005</length>
    		        <radius>0.01</radius>
    		      </cylinder>
    		    </geometry>
    			</collision>
    			<inertial>
    				<mass>0.01</mass>
    				<inertia>
    					<ixx>5.145833333333334e-06</ixx>
    					<ixy>0</ixy>
    					<ixz>0</ixz>
    					<iyy>5.145833333333334e-06</iyy>
    					<iyz>0</iyz>
    					<izz>1.0125000000000003e-04</izz>
    				</inertia>
    			</inertial>
    		</link>
    		
        <joint name="back_yaw_joint" type="revolute">
          <child>back_yaw_link</child>
    			<parent>base_link</parent>
    			<axis>
    				<xyz>0 0 1</xyz>
    			</axis>
    			<limit>
    				<effort>1000.0</effort>
    				<velocity>100.0</velocity>				
    			</limit>
    			<dynamics>
    				<damping>0.0</damping>
    				<friction>0.001</friction>				
    			</dynamics>
        </joint>
    		
    		<link name="back_roll_link">
    			<pose>-0.25 0 -0.04 0 1.5707 1.5707</pose>			
    			<visual name="visual_back_roll_link">
    		    <geometry>
    		      <cylinder>
    		        <length>0.005</length>
    		        <radius>0.01</radius>
    		      </cylinder>
    		    </geometry>
    			</visual>
    			<collision name="collision_back_roll_link">
    		    <geometry>
    		      <cylinder>
    		        <length>0.005</length>
    		        <radius>0.01</radius>
    		      </cylinder>
    		    </geometry>
    			</collision>
    			<inertial>
    				<mass>0.01</mass>
    				<inertia>
    					<ixx>5.145833333333334e-06</ixx>
    					<ixy>0</ixy>
    					<ixz>0</ixz>
    					<iyy>5.145833333333334e-06</iyy>
    					<iyz>0</iyz>
    					<izz>1.0125000000000003e-04</izz>
    				</inertia>
    			</inertial>
    		</link>
    		
        <joint name="back_roll_joint" type="revolute">
          <child>back_roll_link</child>
    			<parent>back_yaw_link</parent>
    			<axis>
    				<xyz>1 0 0</xyz>
    			</axis>
    			<limit>
    				<effort>1000.0</effort>
    				<velocity>100.0</velocity>				
    			</limit>
    			<dynamics>
    				<damping>0.0</damping>
    				<friction>0.001</friction>				
    			</dynamics>
        </joint>
    		
    		<link name="back_pitch_link">
    			<pose>-0.25 0 -0.04 0 1.5707 1.5707</pose>			
    			<visual name="visual_back_pitch_link">
    		    <geometry>
    		      <sphere>
    		        <length>0.005</length>
    		        <radius>0.01</radius>
    		      </sphere>
    		    </geometry>
    			</visual>
    			<collision name="collision_back_pitch_link">
    		    <geometry>
    		      <sphere>
    		        <radius>0.01</radius>
    		      </sphere>
    		    </geometry>
    			</collision>
    			<inertial>
    				<mass>0.01</mass>
    				<inertia>
    					<ixx>5.145833333333334e-06</ixx>
    					<ixy>0</ixy>
    					<ixz>0</ixz>
    					<iyy>5.145833333333334e-06</iyy>
    					<iyz>0</iyz>
    					<izz>1.0125000000000003e-04</izz>
    				</inertia>
    			</inertial>
    		</link>
    		
        <joint name="back_pitch_joint" type="revolute">
          <child>back_pitch_link</child>
    			<parent>back_roll_link</parent>
    			<axis>
    				<xyz>0 1 0</xyz>
    			</axis>
    			<limit>
    				<effort>1000.0</effort>
    				<velocity>100.0</velocity>				
    			</limit>
    			<dynamics>
    				<damping>0.0</damping>
    				<friction>0.001</friction>				
    			</dynamics>
        </joint>
        
        <link name="cover">
    			<pose>0.0 -0.0 -0.02 0 0 1.57079</pose>
    			<inertial>
    				<mass>0.0001</mass>
    				<inertia>
    					<ixx>0.00001</ixx>
    					<ixy>0</ixy>
    					<ixz>0</ixz>
    					<iyy>0.00001</iyy>
    					<iyz>0</iyz>
    					<izz>0.00001</izz>
    				</inertia>
    			</inertial>
    		  <visual name="visual_cover">
    		    <geometry>
    		      <mesh>
    		        <uri>model://mobilebot/meshes/robot.dae</uri>
    		        <scale>0.26 0.25 0.32</scale>
    		      </mesh>
    		    </geometry>
    		  </visual>
    		</link>
    		<joint name="cover_joint" type="fixed">
    			<child>cover</child>
    			<parent>base_link</parent>
    			<axis>
    				<xyz>0 0 1</xyz>
    			</axis>
    		</joint>
    
        <plugin name="libgazebo_ros_p3d" filename="libgazebo_ros_p3d.so">
          <ros>
            <namespace>robot</namespace>
            <remapping>odom:=odom</remapping>
          </ros>
          <frame_name>map</frame_name>
          <body_name>base_link</body_name>
          <update_rate>30.0</update_rate>
          <gaussian_noise>10</gaussian_noise>
          <xyzOffsets>0 0 0</xyzOffsets>
    		  <rpyOffsets>0 0 0</rpyOffsets>
        </plugin>
    
        <plugin name="left_motor" filename="libgazebo_ros_dc_motor.so">
          <motor_shaft_joint>front_left_wheel_joint</motor_shaft_joint>
          <motor_wrench_frame>front_left_wheel</motor_wrench_frame>
          
          <parameter type="bool" name="publish_encoder">true</parameter>
          <parameter type="bool" name="publish_velocity">true</parameter>
          <parameter type="bool" name="publish_current">false</parameter>
          <parameter type="bool" name="publish_motor_joint_state">true</parameter>
          <parameter type="bool" name="publish_load">false</parameter>
    
          <parameter type="double" name="update_rate">1000</parameter>
          <parameter type="double" name="motor_nominal_voltage">48</parameter>
          <parameter type="double" name="moment_of_inertia">0.00001</parameter>
          <parameter type="double" name="armature_damping_ratio">0.0001</parameter>
          <parameter type="double" name="electromotive_force_constant">0.0322</parameter>
          <parameter type="double" name="electric_resistance">4.18</parameter>
          <parameter type="double" name="electric_inductance">0.012</parameter>
          <parameter type="double" name="gear_ratio">25.0</parameter>
          <parameter type="double" name="encoder_ppr">100</parameter>
          <parameter type="double" name="velocity_noise">0.0</parameter>
          <parameter type="double" name="kp_v">2.0</parameter>
          <parameter type="double" name="ki_v">0.0</parameter>
        </plugin>
        
        <plugin name="right_motor" filename="libgazebo_ros_dc_motor.so">    
          <motor_shaft_joint>front_right_wheel_joint</motor_shaft_joint>
          <motor_wrench_frame>front_right_wheel</motor_wrench_frame>
    
          <parameter type="bool" name="publish_encoder">true</parameter>
          <parameter type="bool" name="publish_velocity">true</parameter>
          <parameter type="bool" name="publish_current">false</parameter>
          <parameter type="bool" name="publish_motor_joint_state">true</parameter>
          <parameter type="bool" name="publish_load">false</parameter>
    
          <parameter type="double" name="update_rate">1000</parameter>
          <parameter type="double" name="motor_nominal_voltage">48</parameter>
          <parameter type="double" name="moment_of_inertia">0.00001</parameter>
          <parameter type="double" name="armature_damping_ratio">0.0001</parameter>
          <parameter type="double" name="electromotive_force_constant">0.0322</parameter>
          <parameter type="double" name="electric_resistance">4.18</parameter>
          <parameter type="double" name="electric_inductance">0.012</parameter>
          <parameter type="double" name="gear_ratio">25.0</parameter>
          <parameter type="double" name="encoder_ppr">100</parameter>
          <parameter type="double" name="velocity_noise">0.0</parameter>
          <parameter type="double" name="kp_v">2.0</parameter>
          <parameter type="double" name="ki_v">0.0</parameter>
        </plugin>
        
        <static>0</static>
        <allow_auto_disable>1</allow_auto_disable>
      </model>
    </sdf>
    
  • 위 모델 코드를 사용해서 각 link별로 크기와 위치를 선정
  • 각 link와 몸체 base_link를 joint를 사용해서 연결

image.jpg

image.jpg

image.jpg

image.jpg

Ros2 통신 과정

  • 플러그인은 Gazebo 물리 엔진과 ROS2 노드를 이어주는 역할을 한다.
  • 하드웨어 대신 Gazebo에서 동일한 인터페이스를 위해 Gazebo 플러그인을 사용했다.
    • 구체적으로, 이동 로봇의 구동부는 플러그인이 /cmd_vel 토픽으로 들어오는 geometry_msgs/Twist 메시지를 속도로 변환해주고, 센서 플러그인은 Gazeb0에서 계산된 상태를 ROS2 토픽으로 퍼블리시한다.
  • 로봇의 현재 상태 정보를 얻기 위한 P3D 플러그인을 적용

      <plugin name="libgazebo_ros_p3d" filename="libgazebo_ros_p3d.so">
            <ros>
              <namespace>robot</namespace>
              <remapping>odom:=odom</remapping>
            </ros>
            <frame_name>map</frame_name>
            <body_name>base_link</body_name>
            <update_rate>30.0</update_rate>
            <gaussian_noise>0.01</gaussian_noise>
            <xyzOffsets>0 0 0</xyzOffsets>
      		  <rpyOffsets>0 0 0</rpyOffsets>
          </plugin>
    
    • 몸체 link의 위치와 자세를 읽어서 ROS2로 Pose/Odometry를 퍼블리시 해주는 플러그인이다.
    • <frame_name>map</frame_name> 를 통해 map 기준으로 몸체 link의 위치를 퍼블리시 했다.
      • Gazebo는 기본적으로 world 프레임이 있는데, 그걸 ROS2 기준의 프레임으로 대응시켰다.
    • <update_rate>30.0</update_rate> 현실적인 센서 업데이트 주기 중 일반적인 수준으로 맞췄다.
    • <gaussian_noise>0.01</gaussian_noise> 퍼블리시 되는 위치나 자세 값에 랜덤오차를 0.01 정도 표준편차를 주었다.
      • 0은 너무 이상적이라고 생각했다. 실세 노이즈를 어느 정도 반영한 상태에서 제어기를 설계하고 싶었다.
      • 정지 상태에서는 실제 위치 x는 같다. 측정된 위치는 실제 위치에 임의로 노이즈를 더하기 때문에 값이 위아래로 튄다.
      • $v = \frac{x_k - x_{k-1}}{\Delta t}$ 에서 $x_k = x + n_k$ 이 k 시점의 실제 위치에 노이즈를 더한 것이고, $v = \frac{n_k - n_{k-1}}{\Delta t}$ 가 되기 때문에 속도는 노이즈의 변화율이 된다.
      • 분모의 $\Delta t$ 가 매우 작기 때문에 노이즈의 변화량이 증폭되어 정지상태여도 빠른 주행중인 것처럼 보인다.
      • 따라서 위치 기반 속도 추정은 노이즈에 매우 민감하고 우리는 휠 오도메트리를 사용해서 속도를 직접 측정하면 이 증폭을 피할 수 있다. 속도를 직접 측정하니까 미분 과정이 없어서 노이즈가 순수 노이즈 크기만큼만 반영이 된다.
      • 소프트웨어적으로는 위치 센서를 위치 미분해서 얻지 않고 엔코터나 IMU 쪽에서만 얻게하거나, 필터링이나 스무딩을 한다.
  • DC 모터에 파라미터를 할당하고 제어를 위한 오픈소스 플러그인을 적용

      <plugin name="left_motor" filename="libgazebo_ros_dc_motor.so">
            <motor_shaft_joint>front_left_wheel_joint</motor_shaft_joint>
            <motor_wrench_frame>front_left_wheel</motor_wrench_frame>
              
            <parameter type="bool" name="publish_encoder">true</parameter>
            <parameter type="bool" name="publish_velocity">true</parameter>
            <parameter type="bool" name="publish_current">false</parameter>
            <parameter type="bool" name="publish_motor_joint_state">true</parameter>
            <parameter type="bool" name="publish_load">false</parameter>
        
            <parameter type="double" name="update_rate">1000</parameter>
            <parameter type="double" name="motor_nominal_voltage">48</parameter>
            <parameter type="double" name="moment_of_inertia">0.00001</parameter>
            <parameter type="double" name="armature_damping_ratio">0.0001</parameter>
            <parameter type="double" name="electromotive_force_constant">0.0322</parameter>
            <parameter type="double" name="electric_resistance">4.18</parameter>
            <parameter type="double" name="electric_inductance">0.012</parameter>
            <parameter type="double" name="gear_ratio">25.0</parameter>
            <parameter type="double" name="encoder_ppr">100</parameter>
            <parameter type="double" name="velocity_noise">0.0</parameter>
            <parameter type="double" name="kp_v">2.0</parameter>
            <parameter type="double" name="ki_v">0.0</parameter>
          </plugin>
            
          <plugin name="right_motor" filename="libgazebo_ros_dc_motor.so">    
            <motor_shaft_joint>front_right_wheel_joint</motor_shaft_joint>
            <motor_wrench_frame>front_right_wheel</motor_wrench_frame>
        
            <parameter type="bool" name="publish_encoder">true</parameter>
            <parameter type="bool" name="publish_velocity">true</parameter>
            <parameter type="bool" name="publish_current">false</parameter>
            <parameter type="bool" name="publish_motor_joint_state">true</parameter>
            <parameter type="bool" name="publish_load">false</parameter>
        
            <parameter type="double" name="update_rate">1000</parameter>
            <parameter type="double" name="motor_nominal_voltage">48</parameter>
            <parameter type="double" name="moment_of_inertia">0.00001</parameter>
            <parameter type="double" name="armature_damping_ratio">0.0001</parameter>
            <parameter type="double" name="electromotive_force_constant">0.0322</parameter>
            <parameter type="double" name="electric_resistance">4.18</parameter>
            <parameter type="double" name="electric_inductance">0.012</parameter>
            <parameter type="double" name="gear_ratio">25.0</parameter>
            <parameter type="double" name="encoder_ppr">100</parameter>
            <parameter type="double" name="velocity_noise">0.0</parameter>
            <parameter type="double" name="kp_v">2.0</parameter>
            <parameter type="double" name="ki_v">0.0</parameter>
          </plugin>
    
    • 모터 스펙은 앞서 정의한 값을 넣어줬다.
    • update_rate 는 1000HZ로 설정했다.
      • DC 모터는 동특성이 로봇 움직임보다 훨씬 빠르게 변하는 시스템이므로 외부 제어 주기보다 훨씬 높은 주파수로 작동하도록 했다.
      • 미세하게 힘을 조절해서 정밀한 제어가 가능한 장점도 있다.
    • moment_of_inertia = 0.00001, armature_damping_ratio = 0.0001 모터가 관성 모멘트가 없어서 무한대 가속도가 나오는 것을 방지했다.
      • 입력과 동시에 바로 최고 속도가 나오지 않아야 실제 응답과 비슷하다고 생각했다.

플러그인 적용 후

실제 로봇이 없어도 Gazebo 환경에서 로봇이 구동되며 실시간으로 값을 받아볼 수 있게 되었다.

image.png

  • 모터에 대한 정보도 좌/우 따로 토픽으로 받을 수 있게 되었다.

image.gif

image.gif

  • 한 쪽 바퀴에만 $1rad/s$ 에 해당하는 각속도를 넣어서 테스트 했다.

Turtlebot3 패키지 연동

터틀봇3 패키지 내에 들어있는 teleop node를 활용해서 키보드로 로봇을 제어해봤다.

image.png

  • 키보드 값을 입력 받아서 topic으로 /cmd_vel 로 쏘면, Control node가 구독해서 모바일봇으로 다시 퍼블리시 해주는 구조이다.

image.gif

[cmd] linear :  3.0  angular :  0.0
[cur] linear :  6.957660564507382  angular :  -4.336003365866704
[cmd] linear :  3.0  angular :  0.0
[cur] linear :  -11.48909999033403  angular :  -2.4905442053146474
[cmd] linear :  3.0  angular :  0.0
[cur] linear :  15.354914321095018  angular :  3.460247668695481
[cmd] linear :  3.0  angular :  0.0
[cur] linear :  -2.017019213756047  angular :  4.682560264096943
[cmd] linear :  3.0  angular :  0.0
[cur] linear :  17.38343493116724  angular :  3.7183501743370213
[cmd] linear :  3.0  angular :  0.0
[cur] linear :  -1.796576020986112  angular :  9.611881336718003
[cmd] linear :  3.0  angular :  0.0
[cur] linear :  -3.5824107926800606  angular :  -13.074454957071762
[cmd] linear :  3.0  angular :  0.0
[cur] linear :  -13.872253149371469  angular :  -0.642756694489865
[cmd] linear :  3.0  angular :  0.0
[cur] linear :  2.166972600360915  angular :  7.851183712573045
[cmd] linear :  3.0  angular :  0.0
[cur] linear :  -19.907223824591874  angular :  20.055677520612377
[cmd] linear :  3.0  angular :  0.0
[cur] linear :  3.1564870765572213  angular :  -9.889990927134317

  • 정상적인 주행 상황에서 가끔씩 값이 크게 튀는 로그가 발견되었다. 실제 로봇은 부드럽게 움직였지만 로그의 속도와 각속도는 그렇지 않았다.

튀는 로그 분석

cmd 는 정상적으로 3과 0으로 유지되는데 cur 값은 한 번씩 값이 크게 튀는 현상이 있었다. 노이즈 값이 크게 반영되었거나 속도이기 때문에 위치를 시간으로 나누는 과정에서 값이 튀었을 것으로 예상하고 원인을 찾아보았다.

  • 후보
    • 차분의 분모 dt가 작아져서 값이 순간적으로 크게 보이는 경우
      • now() 로 dt를 잡고, 데이터는 stamp 기반일때 시간축이 섞여서
        • 측정이 찍힌 시간은 stamp로, 콜백이 실행된 시간을 now()로 얻게 되면 위치변화는 stamp 흐름을 따라가는데 dt는 현재 시간으로 재면, 두 시간축이 같은 리듬이 아니기 때문에 dt 범위가 달라져서 변동이 심할 수 있다. 이럴 경우 stamp 기준으로 계산하는 것이 안정적이다.
      • 메세지가 네트워크/큐 때문에 실제 간격과 다르게 한꺼번에 연속으로 콜백되어서 어느 순간에 거의 2개가 동시 처리되면 dt 감소
        • dt를 콜백 호출 간격 기반으로 계산하면 어느 순간 한 번에 빠르게 실행되는 경우 dt가 1회성으로 매우 커질 수 있다.
    • 플러그인의 실험을 위해 가우시안 노이즈를 <gaussian_noise>10</gaussian_noise> 10으로 설정해두어서
  • 결론
    • 코드를 직접 확인한 결과,
    • cur 값은 control_robot.py 에서 출력하는데, 실제로는 control_robot.py 로 구독한 /robot/odom 의 Odometry.twist 를 그대로 읽어온 값이다. 이 노드 안에서는 dt로 나눠서 속도를 계산하는 코드가 없다. 따라서 dt 관련 후보는 문제가 아니다.
    • /robot/odm 은 모델 SDF에서 P3D 플러그인이 퍼블리시 하는데,
    • 플러그인 설정의 가우시간 노이즈가 테스트를 위해 10으로 높여 놓았는데, 되돌리지 않아서 표준 편차가 10으로 한 번씩 크게 튀는 값이 나오게 되었다.
  • 테스트
    • 가우시안 노이즈 값을 0.01로 두고 다시 제어를 시작해봤다.

        [cmd] linear :  -1.5  angular :  0.0
        [cur] linear :  0.07788550724647324  angular :  -0.01895389213057958
        [cmd] linear :  -2.0  angular :  0.0
        [cur] linear :  0.1195186484726249  angular :  -0.0062722198927030355
        [cmd] linear :  -2.0  angular :  0.0
        [cur] linear :  0.1530365989391994  angular :  -0.00861613546351606
        [cmd] linear :  -2.5  angular :  0.0
        [cur] linear :  0.1709270954028069  angular :  -0.003100883572690438
        [cmd] linear :  -3.0  angular :  0.0
        [cur] linear :  0.20240741083392372  angular :  -0.009781222284346372
        [cmd] linear :  -3.0  angular :  0.0
        [cur] linear :  0.23607689382020272  angular :  -0.003795920321737263
        [cmd] linear :  -3.0  angular :  0.0
        [cur] linear :  0.23694622727228343  angular :  -0.014323942045737757
        [cmd] linear :  -3.4999999999999996  angular :  0.0
        [cur] linear :  0.2519330011736734  angular :  0.014088135430624676
        [cmd] linear :  -3.9999999999999996  angular :  0.0
        [cur] linear :  0.2424767872855163  angular :  -0.014139599319290787
        [cmd] linear :  -4.0  angular :  0.0
        [cur] linear :  0.31398173326248  angular :  0.010898859279758845
        [cmd] linear :  -4.0  angular :  0.0
        [cur] linear :  0.3217806859406334  angular :  0.0033362062504576884
        [cmd] linear :  -4.0  angular :  0.0
        [cur] linear :  0.3175554819382802  angular :  -0.005200905808602651
        [cmd] linear :  -4.0  angular :  0.0
        [cur] linear :  0.3348905921220147  angular :  0.010606165652519546
        [cmd] linear :  -4.0  angular :  0.0
        [cur] linear :  0.3207792354489992  angular :  -0.00294466189502181
        [cmd] linear :  -4.0  angular :  0.0
        [cur] linear :  0.31710844723395365  angular :  -6.023304307044801e-05
        [cmd] linear :  -4.0  angular :  0.0
        [cur] linear :  0.33231914549753977  angular :  -0.019434537821900286
        [cmd] linear :  -4.0  angular :  0.0
        [cur] linear :  0.3261831338532742  angular :  0.004577647788463024
        [cmd] linear :  -4.0  angular :  0.0
        [cur] linear :  0.31274331797214155  angular :  -0.00504699603976933
        [cmd] linear :  -4.0  angular :  0.0
        [cur] linear :  0.3017519289845812  angular :  0.011928417773003512
        [cmd] linear :  -4.0  angular :  0.0
        [cur] linear :  0.31792136174318825  angular :  -0.00200434917624646
        [cmd] linear :  -4.0  angular :  0.0
        [cur] linear :  0.3153826457173677  angular :  -0.0027232532408877717
        [cmd] linear :  -4.0  angular :  0.0
        [cur] linear :  0.3140751681233642  angular :  -0.0161248175875797
      
    • angular 값이 0.01~0.02 사이에서 움직이는 정상적인 범위가 되었다.
    • cmd/cmd_vel을 휠 속도 명령으로 바꾸기 위해 임의 게인을 곱한 ‘제어입력’이고, cur은 Gazebo가 계산한 ‘실제 차체 속도’ 라서 단위나 의미 자체가 다르기 때문에 값이 같게 보일 필요가 없다.
      • Gazebo에서 차량을 제어할 때 휠 반지름/차폭으로 변환식을 써야하지만, 차량 속도가 느려 체감 속도를 맞추려고 임의의 게인을 곱해 cmd를 계산했다.
    • 현실에서는 관성이나 마찰 때문에 명령을 즉시 그대로 못 따라가기 때문에 매 순간 cur = cmd가 성립하지는 않는다.

매트랩과 simulink에서 만든 제어기를 Gazebo에 적용하기 위해서 UDP 통신 환경을 구축했다.

Instrument Control Toolbox 애드온을 적용하여 UDP recieve/send 블록을 사용한다.

  • UDP(User Datagram Protocol) 통신이란
    • 인터넷에서 데이터를 주고받는 전송 계층 프로토콜로 TCP와 다르게 비연결형 방식으로 상대방이 수신중이라는 응답을 하지 않아도 ros2의 토픽처럼 패킷을 그냥 던지는 방식이다.
    • 중간에 패킷이 손실되거나 순서가 바뀌거나 중복이 될 수도 있지만 보내는 사람은 알 수 없다.
    • 하지만 전송 속도가 매우 빠르기 때문에 조금 유실 되어도 크게 문제가 없다.
    • 하나의 IP 안에서도 여러 프로그램이 동시에 UDP를 쓸 수 있도록 포트 번호로 구분한다.

image.png

image.png

image.png

  • Solver가 Auto 방식이면
    • 보통 가변 스텝 솔버를 선택하기 때문에 신호의 변화가 적을 때는 step을 크게 건너뛰고, 변화가 급격할 때만 잘게 쪼갠다.
    • 계산이 단순할 때, 10초 분량의 데이터를 몇 번의 계산으로 끝내기 때문에 결과를 한 번에 뿌려준다.
  • Solver가 Fixed-step 방식이면
    • 정해진 간격마다 무조건 멈추기 때문에 10초 시뮬레이션이라면 step이 0.01 일때 총 1000번의 계산을 강제로 수행한다.
    • 매 step마다 데이터를 주고받는 과정에서 대기 시간이 발생해서 시뮬레이션 속도가 물리적인 실제 시간과 비슷하게 느려지게 된다.
    • 완전한 실시간을 사용하고 싶으면 simulate - run 아래 화살표 - simulation pacing 기능을 켜면 실제 시간과 동기화 된다.
  • IP 주소 설정은
    • 외부 pc가 아닌 내 컴퓨터로 데이터를 보내기 때문에 로컬을 의미하는 127.0.0.1을 사용했다.
    • 내 pc에 어떤 통로로 들어오든 포트 7777로 오는 것은 다 받게 하기 위해 로컬 주소를 0.0.0.0을 사용했다.
  • Byte-order는
    • 숫자를 저장하거나 보낼 때 큰 단위부터 차례대로 보내는 방식이다.
    • 123을 1-2-3 순서로 보냈으면 받을 때도 1-2-3 순서로 받기 위해서 big-endian으로 맞춰주었다.
    • 이 설정이 서로 다르다면 데이터가 깨지거나 말도 안 되는 다른 값이 나오게 된다.

Ros2 통신 연결

이제 Ros2에서도 UDP 통신을 사용하여 Server와 Client 노드를 만들어서 서로 데이터를 잘 주고 받는지 환경 테스트를 해야한다.

image.png

  • 간단하게 udp_server 노드와 udp_client 노드를 만들어서 둘 사이에 통신이 잘 되는지 확인했다.
    • 테스트로 0.11과 0.22를 송신하고, 서버로부터 그 절반인 0.06과 0.11이 계산되어 나오는 것을 볼 수 있다.
    • 클라이언트와 서버 모두 송수신이 정상적으로 작동한다.

Ros2 상호간에 통신이 잘 되는 것을 확인하였고, 우리의 현재 흐름은 아래와 같다.

명령(입력) 경로

  • MATLAB 제어 알고리즘 → UDP로 (linear, angular) 송신 → ROS2 udp_server가 수신 → ROS2 /cmd_vel(Twist)로 publish → control_robot가 /cmd_vel subscribe → 휠 명령 publish → Gazebo 모터 플러그인 → 로봇이 움직임

상태(출력) 경로

  • Gazebo → gazebo_ros_p3d가 /robot/odom publish → ROS2에서 /robot/odom subscribe → UDP로 상태($v$, $w$, pose)를 MATLAB으로 송신 → MATLAB이 UDP receive

Updated: