【从零开始的ROS四轴机械臂控制(二)】

 
    • 四、urdf文件及gazebo仿真
      • 1.simple_arm示例
        • (1)config文件夹
        • (2)launch文件夹
        • (3)meshes文件夹
        • (4)urdf文件夹
        • (5)worlds文件夹
        • (6)文件间联系
      • 2.根据simple_arm示例改进arm0文件夹
        • (1)urdf文件夹
          • ①arm1.gazebo.xacro
          • ②arm1.urdf.xacro
            • (2)config文件夹
            • (3)launch文件夹
              • ①robot_control.xml
              • ②robot_description.xml
              • ③gazebo.launch
            • (4)scripts文件夹
 

四、urdf文件及gazebo仿真

  首先先忽略硬件部分,来看一下gazebo仿真部分。这一个部分的目标是,实现使用arm_mover节点和look_away服务节点来控制gazebo中的机器人。   在这里插入图片描述   本部分完成后如上图所示,使用simple_mover控制机械臂移动。   我找了很多关于urdf资料后发现,很多人讲urdf说的比较模糊,而且没有实际例子来验证。或者介绍的比较模糊,并不是很能理解从Solidworks导出urdf文件后如何进行实际的应用。很多博客都是以导入到rviz中能使用作为结束,但是这样的话对我们这个项目来说是远远不够的。   所以为了能顺利地进行项目,所以重点来研究一下urdf文件和包。 我在改进我的arm0的包的时候主要参考了simple_arm这个示例,如果不想看simple_arm示例可以直接跳到四、2根据simple_arm 示例改进arm0文件夹,其主要详细说明了四轴机械臂项目的arm0包。  

1.simple_arm示例

 
有关于工作区可以参考博客【Udacity机器人软件工程师课程笔记(九)-ROS-Catkin包、工作空间和目录结构】   有关simple_arm和gazebo可以参考【Udacity机器人软件工程师课程笔记(十)-ROS-Catkin-包(package)和gazebo】  
以下是从github仓库克隆的 simple_arm 的包。   在这里插入图片描述   接下来要针对每一个文件来分析其作用。当时我在学习使用simple_arm这个包时,没有很好的研究其文件内容。但是现在要搭建针对于自己项目的gazebo文件,还是很有必要好好的看一下其中文件的结构及内容的。  

(1)config文件夹

  config文件夹:配置文件   在这里插入图片描述 controllers.yaml  
simple_arm:
    #list of controllers
    joint_state_controller:
      type: joint_state_controller/JointStateController
      publish_rate: 50
    joint_1_position_controller:
      type: effort_controllers/JointPositionController
      joint: joint_1
      pid: {p: 100.0, i: 0.01, d: 10.0}
    joint_2_position_controller:
      type: effort_controllers/JointPositionController
      joint: joint_2
      pid: {p: 100.0, i: 0.01, d: 10.0}
 

(2)launch文件夹

  launch:提供更自动化的启动节点的方式 在这里插入图片描述   robot_control.xml:  
<launch>
  <!--将关节控制器加载到param服务器-->
  <rosparam file="$(find simple_arm)/config/controllers.yaml" command="load"/>
  <!--Load controllers-->
  <node name="spawner" pkg="controller_manager" type="spawner" respawn="false"
    output="screen" ns="/simple_arm" args="joint_state_controller
    joint_1_position_controller
    joint_2_position_controller"/>
</launch>
```xml
robot_description.xml:
  ``` robot_spawn.launch ```xml```  

(3)meshes文件夹

  在这里插入图片描述   meshes中包含了相机的3d模型文件。   meshes 中文件可以包括meshes(.dae(Collada)或.stl(STereoLithography)格式的CAD文件)。  

(4)urdf文件夹

  urdf:通用机器人描述文件。   在这里插入图片描述   simple_arm.gazebo.xacro: 定义gazebo中的的模型,其中包括  
  • (1)定义gazebo
  • (2)定义link
    • 1)base link
    • 2)link_1
    • 3)link_2
    • 4)camera_link
  • (3)定义摄像头
   
<?xml version="1.0"?>
<robot>
  <!-- ros_control插件 -->
  <gazebo>
    <plugin name="gazebo_ros_control" filename="libgazebo_ros_control.so">
      <robotNamespace>/simple_arm</robotNamespace>
      <robotSimType>gazebo_ros_control/DefaultRobotHWSim</robotSimType>
    </plugin>
  </gazebo>

  <!--base_link-->
  <gazebo reference="base_link">
    <material>Gazebo/White</material>
  </gazebo>

  <!--link_1-->
  <gazebo reference="link_1">
    <mu1>0.2</mu1>
    <mu2>0.2</mu2>
    <material>Gazebo/Orange</material>
  </gazebo>

  <!--link_2-->
  <gazebo reference="link_2">
    <mu1>0.2</mu1>
    <mu2>0.2</mu2>
    <material>Gazebo/White</material>
  </gazebo>

  <!--camera_link-->
  <gazebo reference="camera_link">
    <material>Gazebo/Grey</material>
  </gazebo>

  <!-- 摄像头-->
  <gazebo reference="camera_link">
    <sensor type="camera" name="rgb_camera">
      <update_rate>30.0</update_rate>
      <camera name="rgb_camera">
        <horizontal_fov>1.4</horizontal_fov>
        <image>
          <width>640</width>
          <height>480</height>
          <format>R8G8B8</format>
        </image>
        <clip>
          <near>0.02</near>
          <far>300</far>
        </clip>
      </camera>
      <plugin name="camera_controller" filename="libgazebo_ros_camera.so">
        <alwaysOn>true</alwaysOn>
        <updateRate>0.0</updateRate>
        <cameraName>rgb_camera</cameraName>
        <imageTopicName>image_raw</imageTopicName>
        <cameraInfoTopicName>camera_info</cameraInfoTopicName>
        <frameName>camera_link</frameName>
        <hackBaseline>0.0</hackBaseline>
        <distortionK1>0.0</distortionK1>
        <distortionK2>0.0</distortionK2>
        <distortionK3>0.0</distortionK3>
        <distortionT1>0.0</distortionT1>
        <distortionT2>0.0</distortionT2>
      </plugin>
    </sensor>
  </gazebo>

</robot>
  simple_arm.urdf.xacro:其中...为省略部分:   这个部分定义link、joint和transmission  
<?xml version="1.0"?>
<robot name="simple_arm" xmlns:xacro="http://www.ros.org/wiki/xacro">

  <!--Define constants-->
  <xacro:property name="link_type" value="cuboidal" />
  <xacro:property name="PI" value="3.14159"/>
  ...

  <!--Import gazebo elements-->
  <xacro:include filename="$(find simple_arm)/urdf/simple_arm.gazebo.xacro" />

  <!--Links-->
  <link name="world"/>

  <link name="base_link">
    <visual>
      <origin xyz="0 0 ${length1/2}" rpy="0 0 0"/>
      <geometry>
        <cylinder length="${length1}" radius="${radius1}"/>
      </geometry>
      <material name="white">
        <color rgba="1.0 1.0 1.0 1.0"/>
      </material>
    </visual>
    <collision>
      <origin xyz="0 0 ${length1/2}" rpy="0 0 0"/>
      <geometry>
        <cylinder length="${length1}" radius="${radius1}"/>
      </geometry>
    </collision>
    <inertial>
      <origin xyz="0 0 ${length1/2}" rpy="0 0 0"/>
      <mass value="${mass1}"/>
      <inertia
        ixx="${mass1 / 12.0 * (2*radius1*2*radius1 + length1*length1)}" ixy="0.0" ixz="0.0"
        iyy="${mass1 / 12.0 * (length1*length1 + 2*radius1*2*radius1)}" iyz="0.0"
        izz="${mass1 / 12.0 * (2*radius1*2*radius1 + 2*radius1*2*radius1)}"/>
    </inertial>
  </link>
  
  ...
  
  <!--camera_link-->
  <link name="camera_link">
    <collision>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <!--mesh filename="package://simple_arm/meshes/camera.dae"/-->
        <sphere radius="${camera_size}"/>
      </geometry>
    </collision>
    <visual>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <!--mesh filename="package://simple_arm/meshes/camera.dae"/-->
        <sphere radius="${camera_size}"/>
      </geometry>
      <material name="red">
        <color rgba="1.0 0 0 1.0"/>
      </material>
    </visual>
    <inertial>
      <mass value="1e-5" />
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <inertia ixx="1e-6" ixy="0" ixz="0" iyy="1e-6" iyz="0" izz="1e-6" />
    </inertial>
  </link>

  <!--Joints-->
  <joint name="fixed_base_joint" type="fixed">
    <parent link="world"/>
    <child link="base_link"/>
  </joint>

  ...
  
  <joint name="camera_joint" type="fixed">
    <axis xyz="0 0 1" />
    <origin xyz="0 0 ${length3}" rpy="0 -${PI/2} 0"/>
    <parent link="link_2"/>
    <child link="camera_link"/>
  </joint>

  <!--转换和执行器-->
  <transmission name="tran1">
    <type>transmission_interface/SimpleTransmission</type>
    <joint name="joint_1">
      <hardwareInterface>EffortJointInterface</hardwareInterface>
    </joint>
    <actuator name="motor1">
      <hardwareInterface>EffortJointInterface</hardwareInterface>
      <mechanicalReduction>1</mechanicalReduction>
    </actuator>
  </transmission>

  <transmission name="tran2">
    <type>transmission_interface/SimpleTransmission</type>
    <joint name="joint_2">
      <hardwareInterface>EffortJointInterface</hardwareInterface>
    </joint>
    <actuator name="motor2">
      <hardwareInterface>EffortJointInterface</hardwareInterface>
      <mechanicalReduction>1</mechanicalReduction>
    </actuator>
  </transmission>
</robot>
   

(5)worlds文件夹

  在这里插入图片描述 willow_garage.world是用于Gazebo仿真环境的XML文件,如果不定义可以使用空白世界。所以这个文件为可选文件。  

(6)文件间联系

  屏幕截图 2020-12-21 124000   从框图中我们可以比较清楚的的除了文件的框架结构。所有的文件都指向到robot_spawn.launch文件。要实现搭建自己的包,就要按照框图一步步的修改从solidworks导出的urdf文件。  
各文件夹功能如下所示   • script(python可执行文件) • src(C ++源文件) • msg(用于自定义消息定义) • srv(用于服务消息定义) • include - >作为依赖项所需的头文件/库 • config - >配置文件 • launch - >提供更自动化的启动节点的方式   其他文件夹可能包括   • urdf(通用机器人描述文件) • meshes(.dae(Collada)或.stl(STereoLithography)格式的CAD文件) • worlds(用于Gazebo仿真环境的XML文件)  

2.根据simple_arm示例改进arm0文件夹

  由于某些原因(导出了多次urdf文件),我的文件夹实际名称为arm1,在此特别说明一下,在接下来也会以arm1作为例子。   在这里插入图片描述   以上是arm1包中的所有内容,我已经将它上传至github,其版本号定为Robot arm 0.1.1,功能是通过simple_mover.py实现了最基本机械臂的运动 。   arm1包(arm version 0.1.1)结构如下  
arm1
  |-config
  |  |-controllers.yaml
  |-launch
  |  |-gazebo.launch
  |  |-robot_control.xml
  |  |-robot_description.xml
  |-meshes(未更改)
  |  |-base_link.STL
  |  |-link1.STL
  |  |-link2.STL
  |  |-link3.STL
  |  |-link4.STL
  |-scripts
  |  |-simple_mover
  |-urdf
  |  |-arm1.urdf.xacro
  |  |-arm1.gazebo.xacro
  |-worlds
  |  |-willow_garage.world
  |-CMakeLists.txt(未更改)
  |-export.log(未更改)
  |-package.xml(未更改)
    其中 CMakeLists.txt、export.log、package.xml和meshes文件夹中内容为solidworks转出的自带内容,其它文件都进行了更改。  

(1)urdf文件夹

  这部分使用Xacro(XML宏)。   Xacro是一种XML宏语言。使用xacro,可以使用扩展较大的XML表达式的宏来构造更短,更易读的XML文件。  
有关xacro的更多信息可以参考ROS Wiki
①arm1.gazebo.xacro
  arm1.gazebo.xacro的主要功能是,添加gazebo_ros_control插件,将ros和gazebo连接起来。  
<?xml version="1.0"?>
<robot>
  <!-- ros_control plugin -->
  <gazebo>
    <plugin name="gazebo_ros_control" filename="libgazebo_ros_control.so">
      <robotNamespace>/arm1</robotNamespace>
      <robotSimType>gazebo_ros_control/DefaultRobotHWSim</robotSimType>
    </plugin>
  </gazebo>
  <!--base_link-->
  <gazebo reference="base_link">
    <material>Gazebo/White</material>
  </gazebo>
  <!--link_1-->
  <gazebo reference="link1">
    <mu1>0.2</mu1>
    <mu2>0.2</mu2>
    <material>Gazebo/Orange</material>
  </gazebo>
  <!--link_2-->

  <gazebo reference="link2">
    <mu1>0.2</mu1>
    <mu2>0.2</mu2>
    <material>Gazebo/White</material>
  </gazebo>
  <!--link_3-->
  <gazebo reference="link3">
    <mu1>0.2</mu1>
    <mu2>0.2</mu2>
    <material>Gazebo/Orange</material>
  </gazebo> 
 <!--link_4-->
  <gazebo reference="link4">
    <mu1>0.2</mu1>
    <mu2>0.2</mu2>
    <material>Gazebo/White</material>
  </gazebo>
</robot>
 
②arm1.urdf.xacro
  为了方便阅读,我在其中删除了一部分的内容,完整部分可以查看github。   这个文件主要定义了arm的urdf文件。其中包括link,joint和Transmission。link和joint 在solidworks转换出来的文件中已经给出了,在这里不在赘述。   主要注意的有三个地方。   一、我们在urdf文件中,需要对joint打上gazebo使用的transmission标签,transmission把TF(ros)的连接关系与仿真平台上的驱动设备(gazebo)联系在了一起,有了执行器,gazebo就可以在物理层面上对模型进行驱动了。
更多关于transmission标签可以参考此处
  二、我们需要定义world link,然后再将base link 和world link连接起来,这样模型才能在gazebo世界中固定。   三、需要导入arm1.gazebo.xacro文件。  
<?xml version="1.0" encoding="utf-8"?>
<!-- This URDF was automatically created by SolidWorks to URDF Exporter! Originally created by Stephen Brawner (brawner@gmail.com) 
     Commit Version: 1.5.1-0-g916b5db  Build Version: 1.5.7152.31018
     For more information, please see http://wiki.ros.org/sw_urdf_exporter -->
<robot
  name="arm1" 
  xmlns:xacro="http://www.ros.org/wiki/xacro">
  <!--Import gazebo elements-->
  <xacro:include filename="$(find arm1)/urdf/arm1.gazebo.xacro" />
  <!--Links and joints-->
  <link name="world"/>
  <link name="base_link">
    <inertial>
      <origin
        xyz="0.000877732518704125 -6.1202787414609E-05 0.0136416983965608"
        rpy="0 0 0" />
      <mass
        value="0.0149906128541691" />
      <inertia
        ixx="9.0485354260676E-07"
        ixy="9.67790693248534E-12"
        ixz="-1.26661641226237E-08"
        iyy="1.3035966603116E-06"
        iyz="-4.46725888473771E-12"
        izz="1.62723345073819E-06" />
    </inertial>
    <visual>
      <origin
        xyz="0 0 0"
        rpy="0 0 0" />
      <geometry>
        <mesh
          filename="package://arm1/meshes/base_link.STL" />
      </geometry>
      <material
        name="">
        <color
          rgba="1 1 1 1" />
      </material>
    </visual>
    <collision>
      <origin
        xyz="0 0 0"
        rpy="0 0 0" />
      <geometry>
        <mesh
          filename="package://arm1/meshes/base_link.STL" />
      </geometry>
    </collision>
  </link>
  <!--this joint used to fix model-->
  <joint name="fixed_base_joint" type="fixed">
    <parent link="world"/>
    <child link="base_link"/>
  </joint>
  ...(省略部分)
  <!--Transmission and actuators-->
  <transmission name="tran1">
  ...(省略部分)
  </transmission>
</robot>
 

(2)config文件夹

  为每个joint添加控制器controller,保存为controllers.yaml文件。  
arm1:
    #list of controllers
    joint_state_controller:
      type: joint_state_controller/JointStateController
      publish_rate: 50
    joint1_position_controller:
      type: effort_controllers/JointPositionController
      joint: joint1
      pid: {p: 1, i: 0.001, d: 0.002}
    joint2_position_controller:
      type: effort_controllers/JointPositionController
      joint: joint2
      pid: {p: 1, i: 0.001, d: 0.002}
    joint3_position_controller:
      type: effort_controllers/JointPositionController
      joint: joint3
      pid: {p: 1, i: 0.001, d: 0.002}
    joint4_position_controller:
      type: effort_controllers/JointPositionController
      joint: joint4
      pid: {p: 1, i: 0.001, d: 0.002}
 

(3)launch文件夹

 
①robot_control.xml
  该xml文件负责加载控制器。  
<launch>
  <!--Load the joint controllers to param server-->
  <rosparam file="$(find arm1)/config/controllers.yaml" command="load"/>
  <!--Load controllers-->
  <node name="spawner" pkg="controller_manager" type="spawner" respawn="false"
    output="screen" ns="/arm1" args="joint_state_controller
    joint1_position_controller
    joint2_position_controller
    joint3_position_controller
    joint4_position_controller"/>
</launch>
 
②robot_description.xml
  机器人描述文件,其每个部分的功能见注释。  
<launch>
  <!--Load urdf to param server-->
  <param name="robot_description" command="$(find xacro)/xacro --inorder '$(find arm1)/urdf/arm1.urdf.xacro'"/>

  <!--GUI used to send fake joint values-->
  <node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher">
    <param name="use_gui" value="false"/>
  </node>
  <!--Publish robot state to TF-->
  <node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher">
    <remap from="/joint_states" to="/arm1/joint_states" />
  </node>
</launch>
 
③gazebo.launch
  最终的运行文件,加载gazebo和相关节点。  
<launch>
  <include file="$(find arm1)/launch/robot_description.xml"/>
  <include file="$(find arm1)/launch/robot_control.xml"/>

  <!--Launch a gazebo world-->
  <include file="$(find gazebo_ros)/launch/empty_world.launch"/>
    <arg name="world_name" value="$(find arm1)/worlds/willow_garage.world"/>
    <arg name="paused" value="false"/>
    <arg name="use_sim_time" value="true"/>
    <arg name="gui" value="true"/>
    <arg name="headless" value="false"/>
    <arg name="debug" value="false"/>

  <node 
     name="urdf_spawner" 
     pkg="gazebo_ros" 
     type="spawn_model" 
     respawn="false" output="screen"
     args="-urdf -param robot_description -x 0 -y 0 -z 0 -R 0 -P 0 -Y 0 -model arm1"/>

</launch>
  打开一个新的终端  
source ~/catkin_ws/devel/setup.bash
roslaunch arm1 gazebo.launch
  然后我们就可以看到gazebo成功打开了,模型成功加载如下。   在这里插入图片描述   若gazebo中模型出现抖动,则考虑controllers.yaml中的pid参数的设置。我当时按照pid默认值来运行gazebo模型,结果造成了非常严重的模型抖动。猜测可能是因为模型尺寸和simple_arm的尺寸相差较大而导致的。   确定了只是因为pid参数的问题之后,那就需要调参了。调参可是门艺术啊。我调整完的参数已经在controllers.yaml中给出了,其效果比默认值好上很多了,但是还是有抖动。   每次调参数,需要重新开启新的终端加载gazebo。如果有必要,可以输入以下命令,来关闭所有的gazevo服务。  
$ killall gzserver
 

(4)scripts文件夹

  scripts文件夹中存放python的可执行文件,我们需要把我们的simple_mover放到这里边。 若不存在scripts文件夹,则首先创建它
$ cd ~/catkin_ws/src/arm1/
$ mkdir scripts
$ cd ~/catkin_ws/src/arm1/scripts 
  创建simple_mover 文件
$ sudo touch simple_mover 
  使文件可执行
$ sudo chmod 777 simple_mover 
  编写simple_mover文件,也可以直接打开文件进行编辑。
nano simple_mover
  以下内容可以参考博客【Udacity机器人软件工程师课程笔记(十一)-ROS-编写ROS节点】  
#!/usr/bin/env python

import math
import rospy
from std_msgs.msg import Float64

def mover():
    pub_j1 = rospy.Publisher('/arm1/joint1_position_controller/command',
                             Float64, queue_size=10)
    pub_j2 = rospy.Publisher('/arm1/joint2_position_controller/command',
                             Float64, queue_size=10)
    pub_j3 = rospy.Publisher('/arm1/joint3_position_controller/command',
                             Float64, queue_size=10)
    pub_j4 = rospy.Publisher('/arm1/joint4_position_controller/command',
                             Float64, queue_size=10)
    rospy.init_node('arm_mover')
    rate = rospy.Rate(10)
    start_time = 0

    while not start_time:
        start_time = rospy.Time.now().to_sec()

    while not rospy.is_shutdown():
        elapsed = rospy.Time.now().to_sec() - start_time
        pub_j1.publish(math.sin(2*math.pi*0.1*elapsed)*(math.pi/2))
        pub_j2.publish(math.sin(2*math.pi*0.1*elapsed)*(math.pi/2))
		pub_j3.publish(math.sin(2*math.pi*0.1*elapsed)*(math.pi/2))
		pub_j4.publish(math.sin(2*math.pi*0.1*elapsed)*(math.pi/2))
        rate.sleep()

if __name__ == '__main__':
    try:
        mover()
    except rospy.ROSInterruptException:
	pass
  在gazebo运行的条件下,新建一个终端。  
source ~/catkin_ws/devel/setup.bash
rosrun arm1 simple_mover
  运行如下:   在这里插入图片描述