赞
踩
本篇用来记录一次作业的学习例程,错误之处敬请谅解,后续修改
作业要求:
写两个ROS节点,一个节点发布连续变化(可以按sin曲线变化)的7自由度的关节角信息;另一个节点订阅第一个节点发布的关节角信息,然后通过正运动学(可以用DH法建模)计算出最终的末端位姿,并把这个末端位姿在rviz中显示出来。机器人模型可以考虑用KUKA,其他的也可以。
创建工作空间及src文件夹,src文件夹下 init 使它变成工作空间的属性
mkdir -p ~/catkin_ws/src
cd ~/catkin_wc/src
catkin_init_workspace
在src文件夹下创建lwr_description功能包,依赖std_msgs、roscpp、message_generation、joint_state_controller、robot_state_publisher库
catkin_create_pkg lwr_description message_generation std_msgs roscpp joint_state_controller robot_state_publisher
lwr_description功能包中包含以下文件夹:
urdf:用于存放机器人模型的URDF或xacro文件
meshes: 用于放置URDF中引用的模型渲染文件
launch:用于保存相关启动文件
config: 用于保存Rviz的配置文件
src: 用于存放编写的节点
include: 放置功能包中需要用到的头文件
在GitHub上找到一个7自由度KUKA-LWR的urdf模型文件 lwr.urdf,放在urdf文件夹下
check_urdf
check_urdf命令检查urdf文件没有问题
找到URDF模型后,可以在Rviz中将该模型显示出来
在lwr_description功能包下的launch文件夹中创建显示lwr.urdf模型的launch文件,display_lwr_urdf.launch
参考《ROS机器人开发实践》6.2.4,初步编写launch文件
<launch>
<param name="robot_description" textfile="$(find lwr_description)/urdf/lwr.urdf" />
<node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher" />
<node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" />
<node name="rviz" pkg="rviz" type="rviz" args="-d $(find lwr_description)/config/lwr_urdf.rviz" required="true" />
</launch>
通过launch文件启动
roslaunch lwr_description display_lwr_urdf.launch
可以看到KUKA-LWR URDF模型在Rviz中成功显示
创建一个发布连续变化 (按sin曲线变化) 的7自由度关节角信息的节点
#include <ros/ros.h> #include <sensor_msgs/JointState.h> //使用的消息类型为sensor_msgs::JointState #include <math.h> #include <string> #define PI acos(-1.0) int main(int argc, char** argv) { ros::init(argc, argv, "joint_state_publisher_sy"); //节点初始化 ros::NodeHandle nh; //创建节点句柄 ros::Publisher joint_state_pub1 = nh.advertise<sensor_msgs::JointState>("joint_states", 100); //创建发布者joint_state_pub1,发布话题joint_states,消息类型sensor_msgs::JointState ros::Rate loop_rate(0.5); //设置循环的频率0.5HZ(2s) //值设为10时有利于观察Rviz int count = 0; while (ros::ok()) { sensor_msgs::JointState joint_msg; //声明 sensor_msgs::JointState 类的对象为 joint_msg joint_msg.header.stamp = ros::Time::now(); //消息头的赋值 joint_msg.name.resize(7); joint_msg.position.resize(7); //7个joint对应数组长度为7 joint_msg.name[0] = "lwr_arm_0_joint"; joint_msg.position[0] = sin(count * PI / 18); joint_msg.name[1] = "lwr_arm_1_joint"; joint_msg.position[1] = sin(count * PI / 18); joint_msg.name[2] = "lwr_arm_2_joint"; joint_msg.position[2] = sin(count * PI / 18); joint_msg.name[3] = "lwr_arm_3_joint"; joint_msg.position[3] = sin(count * PI / 18); joint_msg.name[4] = "lwr_arm_4_joint"; joint_msg.position[4] = sin(count * PI / 18); joint_msg.name[5] = "lwr_arm_5_joint"; joint_msg.position[5] = sin(count * PI / 18); joint_msg.name[6] = "lwr_arm_6_joint"; joint_msg.position[6] = sin(count * PI / 18); joint_state_pub1.publish(joint_msg); //publish()方法 发布消息 ROS_INFO("publish success"); //输出 publish success loop_rate.sleep(); //按照循环频率延时 count++; } return 0; }
为了使创建的URDF机器人模型正确运动,必须给出robot_state_publisher 节点所需的sensor_msgs::JointState型topic: joint_states
sensor_msgs::JointState 消息格式为:
std_msgs/Header header
string[] name
float64[] position
float64[] velocity
float64[] effort
声明消息变量
sensor_msgs::JointState joint_state;
消息头的赋值
joint_state.header.stamp = ros::Time::now();
消息内容的赋值
从上述消息格式中可知,该消息数据包含多个不同含义的数组,并且该数组没有指定数组长度,因此在赋值时需要明确指定其数组长度,并赋值。方法有两种:
1.resize指定数组长度,再赋值
joint_state.name.resize(3); //指定name数组长度
joint_state.position.resize(3); //指定position数组长度
joint_state.name[0] ="joint1"; //name数组的第一个元素赋值
joint_state.position[0] = 0; //position数组的第一个元素赋值
2.{}赋值 该方法类似于C/C++声明数组并赋初值
joint_state.name={"joint1","joint2","joint3"}; //指定数组大小,并赋值
joint_state.position={0,0,0};
velocity和effort相同,经过上述两步赋值后,可正常发布消息
此处参考:
http://wiki.ros.org/robot_state_publisher?distro=melodic
https://wenku.baidu.com/view/e93ac74d551252d380eb6294dd88d0d233d43cab.html
#include <ros/ros.h> #include <std_msgs/String.h> #include <sensor_msgs/JointState.h> #include <string> void stateInfoCallback(const sensor_msgs::JointState::ConstPtr& msg) { ROS_INFO( "I heard: [%s],position: [%f],[%s],position: [%f][%s],position: [%f][%s],position: [%f][%s],position: [%f][%s],position: [%f][%s],position: [%f]", msg->name[0].c_str(), msg->position[0], msg->name[1].c_str(), msg->position[1], msg->name[2].c_str(), msg->position[2], msg->name[3].c_str(), msg->position[3], msg->name[4].c_str(), msg->position[4], msg->name[5].c_str(), msg->position[5], msg->name[6].c_str(), msg->position[6] //输出7个 joint的名字和position ); } int main(int argc, char **argv) { ros::init(argc, argv, "joint_state_subscriber_sy"); //节点初始化 ros::NodeHandle n; //创建节点句柄 ros::Subscriber joint_state_sub1 = n.subscribe("joint_states", 10, stateInfoCallback); //创建订阅者 joint_state_sub1 订阅话题 joint_states 注册回调函数 ros::spin(); //循环等待回调函数 return 0; }
add_executable(joint_state_publisher_sy src/joint_state_publisher_sy.cpp)
target_link_libraries(joint_state_publisher_sy ${catkin_LIBRARIES})
将joint_state_publisher_sy.cpp编译成可执行文件joint_state_publisher_sy
C++接口通过target_link_libraries库与ROS进行连接
add_executable(joint_state_subscriber_sy src/joint_state_subscriber_sy.cpp)
target_link_libraries(joint_state_subscriber_sy ${catkin_LIBRARIES})
cd catkin_ws
catkin_make
roscore
rosrun lwr_description joint_state_subscriber_sy
rosrun lwr_description joint_state_publisher_sy
可以看到相应的发布订阅信息
加入编写的发布订阅节点,使他们能够在屏幕中显示
<launch> <param name="robot_description" textfile="$(find lwr_description)/urdf/lwr.urdf" /> <!-- 运行joint_state_publisher节点,发布机器人的关节状态 --> <node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher" /> <!-- 运行robot_state_publisher节点,发布TF,没有这个无法看到模型 --> <node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" /> <!-- 运行编写的joint_state_subscriber_sy节点 --> <node name="joint_state_subscriber_sy" pkg="lwr_description" type="joint_state_subscriber_sy" output="screen" /> <!-- 运行编写的joint_state_publisher_sy节点 --> <node name="joint_state_publisher_sy" pkg="lwr_description" type="joint_state_publisher_sy" output="screen" /> <!-- 运行rviz可视化界面 --> <node name="rviz" pkg="rviz" type="rviz" args="-d $(find lwr_description)/config/lwr_urdf.rviz" required="true" /> </launch>
通过启动文件运行:
roslaunch lwr_description display_lwr_urdf.launch
可以看到机械臂在rviz中连续不断运动
URDF模型标签的解读:
origin: 定义joint的起点
axis: 定义该joint的旋转轴
截取 lwr.urdf 中的部分需要用到的代码 <joint name="base_joint" type="fixed"> <origin rpy="0 0 3.14159265359" xyz="0 0 0.02"/> <joint name="lwr_arm_0_joint" type="revolute"> <origin rpy="0 0 0" xyz="0 0 0.11"/> <axis xyz="0 0 1"/> <joint name="lwr_arm_1_joint" type="revolute"> <origin rpy="0 0 0" xyz="0 0 0.2005"/> <axis xyz="0 1 0"/> <joint name="lwr_arm_2_joint" type="revolute"> <origin rpy="0 0 0" xyz="0 0 0.20"/> <axis xyz="0 0 1"/> <joint name="lwr_arm_3_joint" type="revolute"> <origin rpy="0 0 0" xyz="0 0 0.20"/> <axis xyz="0 -1 0"/> <joint name="lwr_arm_4_joint" type="revolute"> <origin rpy="0 0 0" xyz="0 0 0.20"/> <axis xyz="0 0 1"/> <joint name="lwr_arm_5_joint" type="revolute"> <origin rpy="0 0 0" xyz="0 0 0.19"/> <axis xyz="0 1 0"/> <joint name="lwr_arm_6_joint" type="revolute"> <origin rpy="0 0 0" xyz="0 0 0.078"/> <axis xyz="0 0 1"/>
D-H table
i | α i-1 | a i-1 | θ i | d i |
---|---|---|---|---|
1 | 0 | 0 | θ1-PI | 0.11 |
2 | PI/2 | 0 | θ2 | 0 |
3 | -PI/2 | 0 | θ3 | 0.2 |
4 | -PI/2 | 0 | θ4+PI | 0 |
5 | -PI/2 | 0 | θ5-PI | 0.2 |
6 | PI/2 | 0 | θ6 | 0 |
7 | -PI/2 | 0 | θ7 | 0.078 |
通过公式求解末端位姿
正运动学解算方法参考:https://www.bilibili.com/video/BV1v4411H7ez?p=3&spm_id_from=pageDriver
修改订阅者代码使其能够输出末端位姿
#include <ros/ros.h> #include <std_msgs/String.h> #include <sensor_msgs/JointState.h> #include <string> #include<math.h> #define PI acos(-1.0) void personInfoCallback(const sensor_msgs::JointState::ConstPtr& msg) { float pos[7]; pos[0] = msg->position[0]; pos[1] = msg->position[1]; pos[2] = msg->position[2]; pos[3] = msg->position[3]; pos[4] = msg->position[4]; pos[5] = msg->position[5]; pos[6] = msg->position[6]; float theta[] = { pos[0] - PI , pos[1] , pos[2] , pos[3] , PI + pos[4] ,pos[5] - PI ,pos[6] }; float d1 = 0.11; float d3 = 0.2; float d5 = 0.2; float d7 = 0.078; //此处只是为了矩阵计算,应当有更好的计算方法,暂时不会,学习中,后续修改 float a11 = (cos(theta[0]) * cos(theta[1]) * cos(theta[2]) * cos(theta[3])) + (sin(theta[3]) * cos(theta[0]) * sin(theta[1])) - (sin(theta[0]) * sin(theta[2]) * cos(theta[3])); float a12 = (cos(theta[0]) * sin(theta[1]) * cos(theta[3])) - (cos(theta[0]) * cos(theta[1]) * cos(theta[2]) * sin(theta[3])) + (sin(theta[0]) * sin(theta[2]) * sin(theta[3])); float a13 = ((-1) * cos(theta[0]) * cos(theta[1]) * sin(theta[2])) - (sin(theta[0]) * cos(theta[2])); float a14 = ((-1) * cos(theta[0]) * sin(theta[1]) * d3); float a21 = (sin(theta[0]) * cos(theta[1]) * cos(theta[2]) * cos(theta[3])) + (sin(theta[0]) * sin(theta[1]) * sin(theta[3])) + (cos(theta[0]) * sin(theta[2]) * cos(theta[3])); float a22 = ((-1) * sin(theta[0]) * cos(theta[1]) * cos(theta[2]) * sin(theta[3])) + (sin(theta[0]) * sin(theta[1]) * cos(theta[3])) - (sin(theta[2]) * sin(theta[3]) * cos(theta[0])); float a23 = ((-1) * sin(theta[0]) * cos(theta[1]) * sin(theta[2])) + (cos(theta[0]) * cos(theta[2])); float a24 = ((-1) * sin(theta[0]) * sin(theta[1]) * d3); float a31 = (sin(theta[1]) * cos(theta[2]) * cos(theta[3])) - (sin(theta[3]) * cos(theta[1])); float a32 = ((-1) * sin(theta[1]) * cos(theta[2]) * sin(theta[3])) - (cos(theta[1]) * cos(theta[3])); float a33 = ((-1) * sin(theta[1]) * sin(theta[2])); float a34 = (cos(theta[1]) * d3) + d1; float a41 = 0; float a42 = 0; float a43 = 0; float a44 = 1; float b11 = (cos(theta[4]) * cos(theta[5]) * cos(theta[6])) - (sin(theta[4]) * sin(theta[6])); float b12 = ((-1) * cos(theta[4]) * cos(theta[5]) * sin(theta[6])) - (sin(theta[4]) * cos(theta[6])); float b13 = ((-1) * cos(theta[4]) * sin(theta[5])); float b14 = ((-1) * cos(theta[4]) * sin(theta[5]) * d7); float b21 = (sin(theta[5]) * cos(theta[6])); float b22 = ((-1) * sin(theta[5]) * sin(theta[6])); float b23 = cos(theta[5]); float b24 = (cos(theta[5]) * d7) + d5; float b31 = ((-1) * sin(theta[4]) * cos(theta[5]) * cos(theta[6])) - (cos(theta[4]) * sin(theta[6])); float b32 = (sin(theta[4]) * cos(theta[5]) * sin(theta[6])) - (cos(theta[4]) * cos(theta[6])); float b33 = sin(theta[4]) * sin(theta[5]); float b34 = sin(theta[4]) * sin(theta[5]) * d7; float b41 = 0; float b42 = 0; float b43 = 0; float b44 = 1; float c11 = (a11 * b11) + (a12 * b21) + (a13 * b31) + (a14 * b41); float c12 = (a11 * b12) + (a12 * b22) + (a13 * b32) + (a14 * b42); float c13 = (a11 * b13) + (a12 * b23) + (a13 * b33) + (a14 * b43); float c14 = (a11 * b14) + (a12 * b24) + (a13 * b34) + (a14 * b44); float c21 = (a21 * b11) + (a22 * b21) + (a23 * b31) + (a24 * b41); float c22 = (a21 * b12) + (a22 * b22) + (a23 * b32) + (a24 * b42); float c23 = (a21 * b13) + (a22 * b23) + (a23 * b33) + (a24 * b43); float c24 = (a21 * b14) + (a22 * b24) + (a23 * b34) + (a24 * b44); float c31 = (a31 * b11) + (a32 * b21) + (a33 * b31) + (a34 * b41); float c32 = (a31 * b12) + (a32 * b22) + (a33 * b32) + (a34 * b42); float c33 = (a31 * b13) + (a32 * b23) + (a33 * b33) + (a34 * b43); float c34 = (a31 * b14) + (a32 * b24) + (a33 * b34) + (a34 * b44); float c41 = (a41 * b11) + (a42 * b21) + (a43 * b31) + (a44 * b41); float c42 = (a41 * b12) + (a42 * b22) + (a43 * b32) + (a44 * b42); float c43 = (a41 * b13) + (a42 * b23) + (a43 * b33) + (a44 * b43); float c44 = (a41 * b14) + (a42 * b24) + (a43 * b34) + (a44 * b44); ROS_INFO( "I heard: [%s],position: [%f],[%s],position: [%f][%s],position: [%f][%s],position: [%f][%s],position: [%f][%s],position: [%f][%s],position: [%f]", msg->name[0].c_str(),msg->position[0], msg->name[1].c_str(),msg->position[1], msg->name[2].c_str(),msg->position[2], msg->name[3].c_str(),msg->position[3], msg->name[4].c_str(),msg->position[4], msg->name[5].c_str(),msg->position[5], msg->name[6].c_str(),msg->position[6] ); ROS_INFO("[%f] [%f] [%f]",c11,c12,c13); ROS_INFO("[%f] [%f] [%f]",c21,c22,c23); ROS_INFO("[%f] [%f] [%f]",c31,c32,c33); ROS_INFO("Px:[%f] Py:[%f] Pz:[%f]", c14, c24, c34); } int main(int argc, char** argv) { ros::init(argc, argv, "joint_state_subscriber_sy"); ros::NodeHandle n; ros::Subscriber joint_state_sub1 = n.subscribe("joint_states", 10, personInfoCallback); ros::spin(); return 0; }
此处可以参考:
https://www.freesion.com/article/7089625820/
启动launch文件后可以看到模型在Rviz中按照发布的关节角信息连续不断运动,终端显示发布的关节角度和对应的末端位姿。
在终端单独运行订阅者和发布者时,会看到发布订阅连续变化的关节角度,但在Rviz中显示时,模型每次运动完成后会回到起始位置,原因暂时没有搞清楚。
正运动学求解末端位姿处应该有更好的方法,可以参考:
https://blog.csdn.net/weixin_37834269/article/details/111183412
本篇没有使用MoveIt,后续会继续学习。
过了几天再次launch发现不会出现回到起始位置的情况了,很奇怪。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。