赞
踩
目前国内关于ROS2的教程比较少,我记录一下再ROS2中使用Gazebo进行仿真的过程,也算是总结这一段时间关于ROS2的学习。
使用差分小车是为了控制小车转弯,left wheel和right wheel是驱动轮,caster wheel负责保持稳定。在开始构建之前,为了可视化小车,需要了解
模型时如何展示的。
首先创建工作空间及功能包
mkdir -p car_ws/src
cd car_ws/src
ros2 pkg create --build-type ament_cmake car_description
colcon build
记得在.bashrc 中添加 source ~/car_ws/install/setup.bash
创建 launch urdf config meshes scripts等文件夹。然后配置CMakeLists.txt和package.xml
整个小车xacro文件分成Color、Base_Link、Chassis_Link、LEFT WHEEL_Link、RIGHT WHEEL_Link、CASTER WHEEL_Link,6个模块组成
5个link中每一个都是由Visual、Collision、 Inertia以及对应的joint组成,在编写的过程中可以实时使用rviz来显示.
在launch文件夹中编写一个launch文件,直接运行:ros2 launch car_description car.launch.py
即可显示(注意不要同时使用joint_state_publisher_gui和joint_state_publisher)
Gazebo的文件配置较为麻烦。先简单分析一下:
在Gazebo中进行仿真,需要use_sim_time,使用仿真时间统一所有节点的时间
使用 ros2 launch gazebo_ros gazebo.launch.py 开启一个新Gazebo世界
使用 ros2 run gazebo_ros spawn_entity.py -topic robot_description -entity car 。可以将robot_description这个话题中的模型加载到Gazebo世界,取名为car
因此可以编写一个launch文件,一键启动:ros2 launch car_description car_gazebo.launch.py
直接将robot_description加载到gazebo中,颜色信息无法体现,也无法控制小车运动,因此需要在原本的xacro文件中添加gazebo tag
关于颜色的tag
Gazebo/White
在每一个Link后面加上这个tag
当我们想要使用ROS与Gazebo进行交互时,需要使用插件。例如ros2_control或者将要用的gazebo_ros_diff_drive(自带的),这样的插件可以通过C++来编写,具体可以参考 编写ros插件。
<gazebo> <plugin name="diff_drive" filename="libgazebo_ros_diff_drive.so"> <!-- Wheel Information --> <left_joint>left_wheel_joint</left_joint> <right_joint>right_wheel_joint</right_joint> <wheel_separation>0.35</wheel_separation> <wheel_diameter>0.1</wheel_diameter> <!-- Limits --> <max_wheel_torque>200</max_wheel_torque> <max_wheel_acceleration>10.0</max_wheel_acceleration> <!-- Output --> <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>
libgazebo_ros_diff_drive.so需要传入轮子名称、尺寸、限制,以及输出选择等
与前文小车控制流程不一样的是,现在小车的关节角由Gazebo插件发布,而不是通过GUI伪造。 gazebo_ros_diff_drive接受/cmd_vel这个话题的数据,发布需要的关节数据,并广播一个从名为"odom"的新坐标系(类似于世界坐标系的起始位置)到"base_link"的变换,以便让其他代码知道当前机器人的位置。
而运行ros2 run teleop_twist_keyboard teleop_twist_keyboard
可以直接用键盘来输入geometry_msgs/msg/Twist这种消息和小车对接。
为了避免不稳定的情况,给caster wheel添加一个很小的摩擦系数:
Gazebo/Black
小车的位置通过订阅/odom可以获得,控制小车的话题是/cmd_vel,通过下面的代码可以实现对小车的位置控制:
#! /usr/bin/env python3 import rclpy import math import numpy as np from rclpy.node import Node from geometry_msgs.msg import Twist from nav_msgs.msg import Odometry def euler_from_quaternion(quaternion): """ Converts quaternion (w in last place) to euler roll, pitch, yaw quaternion = [x, y, z, w] """ x = quaternion.x y = quaternion.y z = quaternion.z w = quaternion.w sinr_cosp = 2 * (w * x + y * z) cosr_cosp = 1 - 2 * (x * x + y * y) roll = np.arctan2(sinr_cosp, cosr_cosp) sinp = 2 * (w * y - z * x) pitch = np.arcsin(sinp) siny_cosp = 2 * (w * z + x * y) cosy_cosp = 1 - 2 * (y * y + z * z) yaw = np.arctan2(siny_cosp, cosy_cosp) return roll, pitch, yaw class CarController(Node): def __init__(self): super().__init__('car_controller') # 目标位置 self.target_x = -2.0 # 目标 x 坐标 self.target_y = 2.0 # 目标 y 坐标 self.current_x = 0.0 # 当前 x 坐标 self.current_y = 0.0 # 当前 y 坐标 self.roll=0.0 self.pitch=0.0 self.yaw=0.0 # 订阅当前位置 self.odom_sub = self.create_subscription( Odometry, '/odom', self.odom_callback, 10 ) # 发布控制指令 self.cmd_vel_pub = self.create_publisher(Twist, '/cmd_vel', 10) # 标记当前步骤 self.current_step = 1 # 角速度 self.angular_velocity = math.pi / 2 # 90 度/秒 self.timer=None self.first_yaw =None def odom_callback(self, odom_msg): self.current_x = odom_msg.pose.pose.position.x self.current_y = odom_msg.pose.pose.position.y orientation=odom_msg.pose.pose.orientation self.roll, self.pitch, self.yaw = euler_from_quaternion(orientation) self.get_logger().info('Current Position: x={:.2f}, y={:.2f}, roll={:.2f}, pitch={:.2f}, yaw={:.2f}'.format(self.current_x, self.current_y, self.roll, self.pitch, self.yaw)) if self.current_step == 1: # 走完 x 轴 if abs(self.current_x - self.target_x) < 0.05: self.get_logger().info('X direction target position reached!') self.current_step = 2 # 进入第二步:旋转 self.stop_car() return cmd_vel = Twist() if self.current_x < self.target_x: cmd_vel.linear.x = 0.5 # 正向线速度,可根据需要进行调整 else: cmd_vel.linear.x = -0.5 # 反向线速度,可根据需要进行调整 self.cmd_vel_pub.publish(cmd_vel) elif self.current_step == 2: # 根据 y 轴坐标差选择旋转方向 dy = self.target_y - self.current_y if dy > 0: angle = math.pi / 2 # 向左旋转 90 度 else : angle = -math.pi / 2 # 向右旋转 90 度 if not self.first_yaw: self.first_yaw=self.yaw+angle if abs(self.first_yaw-self.yaw)>0.05: cmd_vel = Twist() cmd_vel.linear.x = 0.0 cmd_vel.angular.z = angle self.cmd_vel_pub.publish(cmd_vel) else: cmd_vel = Twist() self.cmd_vel_pub.publish(cmd_vel) self.current_step = 3 # if self.get_clock().now().nanoseconds/1e9-self.first_time<1.0: # cmd_vel = Twist() # cmd_vel.linear.x = 0.0 # cmd_vel.angular.z = angle # self.cmd_vel_pub.publish(cmd_vel) # else: # cmd_vel = Twist() # self.cmd_vel_pub.publish(cmd_vel) # self.current_step == 3 elif self.current_step == 3: # 走完 y 轴 if abs(self.current_y - self.target_y) < 0.05: self.get_logger().info('Target position reached!') self.stop_car() return cmd_vel = Twist() cmd_vel.linear.x = 0.5 # 前进线速度,可根据需要进行调整 self.cmd_vel_pub.publish(cmd_vel) def stop_car(self): cmd_vel = Twist() self.cmd_vel_pub.publish(cmd_vel) def move_to_target(self): while rclpy.ok(): rclpy.spin_once(self) def main(args=None): rclpy.init(args=args) car_controller = CarController() car_controller.move_to_target() if __name__ == '__main__': main()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。