Note: This tutorial assumes that you are in the middle of the tutorial: Implementing a realtime Cartesian controller.
(!) Please ask about problems and questions regarding this tutorial on answers.ros.org. Don't forget to include in your question the link to this page, the versions of your OS & ROS, and also add appropriate tags.

Coding a realtime Cartesian controller with Eigen

Description: This tutorial gives example code for a realtime Cartesian controller using KDL and Eigen

Tutorial Level: ADVANCED

Introduction

The following gives example code for a realtime Cartesian controller, to be run from the previous tutorial. It stands in contrast to the previous code example using KDL

Like the joint controller, we create a class which inherits from the controller::Controller class in the pr2_controller_interface package. As before, it overloads the init, starting, update, and stopping methods to provide the desired realtime functionality.

To access the joints, we use the Chain object, which builds a chain of joints from the specified root to tip frames and then reads the joint position or velocity vector as well as sets the joint torque vector.

Different to the example code using KDL, we perform the kinematic calculations using the KDL library, but immediately transform to Eigen vectors and matrices to allow general linear algebra.

The Header File

The following gives the header file, which should be replace (if it exists) the file include/my_controller_pkg/my_cart_controller_file.h:

   1 #include <pr2_controller_interface/controller.h>
   2 #include <pr2_mechanism_model/chain.h>
   3 #include <pr2_mechanism_model/robot.h>
   4 
   5 #include <boost/scoped_ptr.hpp>
   6 #include <kdl/chain.hpp>
   7 #include <kdl/chainjnttojacsolver.hpp>
   8 #include <kdl/chainfksolverpos_recursive.hpp>
   9 #include <kdl/frames.hpp>
  10 #include <kdl/jacobian.hpp>
  11 #include <kdl/jntarray.hpp>
  12 
  13 #include <pr2_mechanism_model/kinematichelpers.h>
  14 #include <Eigen/Geometry>
  15 
  16 
  17 namespace my_controller_ns{
  18 
  19 class MyCartControllerClass: public pr2_controller_interface::Controller
  20 {
  21   // Declare the number of joints.
  22   enum
  23   {
  24     Joints = 7
  25   };
  26 
  27   // Define the joint/cart vector types accordingly (using a fixed
  28   // size to avoid dynamic allocations and make the code realtime safe).
  29   typedef Eigen::Matrix<double, Joints, 1>  JointVector;
  30   typedef Eigen::Transform3d                CartPose;
  31   typedef Eigen::Matrix<double, 3, 1>       Cart3Vector;
  32   typedef Eigen::Matrix<double, 6, 1>       Cart6Vector;
  33   typedef Eigen::Matrix<double, 6, Joints>  JacobianMatrix;
  34 
  35   // Ensure 128-bit alignment for Eigen
  36   // See also http://eigen.tuxfamily.org/dox/StructHavingEigenMembers.html
  37  public:
  38   EIGEN_MAKE_ALIGNED_OPERATOR_NEW
  39 
  40  private:
  41   // The current robot state (to get the time stamp)
  42   pr2_mechanism_model::RobotState* robot_state_;
  43 
  44   // The chain of links and joints
  45   pr2_mechanism_model::Chain chain_;
  46 
  47   // KDL Solvers performing the actual computations
  48   boost::scoped_ptr<KDL::ChainFkSolverPos>    jnt_to_pose_solver_;
  49   boost::scoped_ptr<KDL::ChainJntToJacSolver> jnt_to_jac_solver_;
  50 
  51   // Temporary variables for KDL and helper functions
  52   // to convert to Eigen, etc.
  53   KDL::JntArray qtmp_;          // Joint vector
  54   KDL::Frame    xtmp_;          // Tip pose
  55   KDL::Jacobian Jtmp_;          // Jacobian
  56   pr2_mechanism_model::KinematicHelpers kin_;
  57  
  58   // The trajectory initial value
  59   CartPose  x0_;                // Tip initial pose
  60 
  61   // The controller parameters
  62   Cart6Vector  Kp_;             // Proportional gains
  63   Cart6Vector  Kd_;             // Derivative gains
  64 
  65   // The trajectory variables
  66   double    circle_phase_;      // Phase along the circle
  67   ros::Time last_time_;         // Time of the last servo cycle
  68 
  69  public:
  70   bool init(pr2_mechanism_model::RobotState *robot,
  71             ros::NodeHandle &n);
  72   void starting();
  73   void update();
  74   void stopping();
  75 };
  76 }

In comparison to the header file using KDL we notice the following changes:

  • Two additional include files

  13 #include <pr2_mechanism_model/kinematichelpers.h>
  14 #include <Eigen/Geometry>
  15 

to provide helper functions and Eigen objects.

  • The biggest change is the declaration of the number of joints and definition of types using Eigen vectors and matricies.

  21   // Declare the number of joints.
  22   enum
  23   {
  24     Joints = 7
  25   };
  26 
  27   // Define the joint/cart vector types accordingly (using a fixed
  28   // size to avoid dynamic allocations and make the code realtime safe).
  29   typedef Eigen::Matrix<double, Joints, 1>  JointVector;
  30   typedef Eigen::Transform3d                CartPose;
  31   typedef Eigen::Matrix<double, 3, 1>       Cart3Vector;
  32   typedef Eigen::Matrix<double, 6, 1>       Cart6Vector;
  33   typedef Eigen::Matrix<double, 6, Joints>  JacobianMatrix;
  34 
  35   // Ensure 128-bit alignment for Eigen
  36   // See also http://eigen.tuxfamily.org/dox/StructHavingEigenMembers.html
  37  public:
  38   EIGEN_MAKE_ALIGNED_OPERATOR_NEW

Notice that the vector/matrix dimensions are fixed. And be aware that Eigen requires 128 bit alignment of data, which is enforced with the EIGEN_MAKE_ALIGNED_OPERATOR_NEW macro.

  • Unlike the previous code, we don't need to declare and pre-allocated all variables. Only the KDL variables used in the KDL solvers, for which we create temporary space

  51   // Temporary variables for KDL and helper functions
  52   // to convert to Eigen, etc.
  53   KDL::JntArray qtmp_;          // Joint vector
  54   KDL::Frame    xtmp_;          // Tip pose
  55   KDL::Jacobian Jtmp_;          // Jacobian
  56   pr2_mechanism_model::KinematicHelpers kin_;

The KinematicHelpers class provides the helper functions, including KDL to Eigen conversion, Eigen to KDL conversion, as well as computation of a Cartesian error.

  • The only required variable is the initial pose, defined as an Eigen type. Notice also that the Kp/Kd gains are now a generic Eigen vector.

  58   // The trajectory initial value
  59   CartPose  x0_;                // Tip initial pose
  60 
  61   // The controller parameters
  62   Cart6Vector  Kp_;             // Proportional gains
  63   Cart6Vector  Kd_;             // Derivative gains
  64 

The Source Code

The following gives the source code file, which should be replace (if it exists) the file src/my_cart_controller_file.cpp:

   1 #include "my_controller_pkg/my_cart_controller_file.h"
   2 #include <pluginlib/class_list_macros.h>
   3 
   4 using namespace my_controller_ns;
   5 
   6 
   7 /// Controller initialization in non-realtime
   8 bool MyCartControllerClass::init(pr2_mechanism_model::RobotState *robot,
   9                                  ros::NodeHandle &n)
  10 {
  11   // Get the root and tip link names from parameter server.
  12   std::string root_name, tip_name;
  13   if (!n.getParam("root_name", root_name))
  14   {
  15     ROS_ERROR("No root name given in namespace: %s)",
  16               n.getNamespace().c_str());
  17     return false;
  18   }
  19   if (!n.getParam("tip_name", tip_name))
  20   {
  21     ROS_ERROR("No tip name given in namespace: %s)",
  22               n.getNamespace().c_str());
  23     return false;
  24   }
  25 
  26   // Construct a chain from the root to the tip and prepare the kinematics.
  27   // Note the joints must be calibrated.
  28   if (!chain_.init(robot, root_name, tip_name))
  29   {
  30     ROS_ERROR("MyCartController could not use the chain from '%s' to '%s'",
  31               root_name.c_str(), tip_name.c_str());
  32     return false;
  33   }
  34 
  35   // Store the robot handle for later use (to get time).
  36   robot_state_ = robot;
  37 
  38   // Construct the kdl solvers in non-realtime.
  39   KDL::Chain kdl_chain;
  40   chain_.toKDL(kdl_chain);
  41   jnt_to_pose_solver_.reset(new KDL::ChainFkSolverPos_recursive(kdl_chain));
  42   jnt_to_jac_solver_.reset(new KDL::ChainJntToJacSolver(kdl_chain));
  43 
  44   // Resize (pre-allocate) the temporary variables in non-realtime.
  45   qtmp_.resize(chain_.getNumOfJoints());
  46   Jtmp_.resize(chain_.getNumOfJoints());
  47 
  48   // Verify the number of joints.
  49   if (chain_.getNumOfJoints() != Joints)
  50   {
  51     ROS_ERROR("The chain from '%s' to '%s' does not contain %d joints",
  52               root_name.c_str(), tip_name.c_str(), Joints);
  53     return false;
  54   }
  55 
  56   // Pick the gains.
  57   Kp_(0) = 100.0;  Kd_(0) = 1.0;        // Translation x
  58   Kp_(1) = 100.0;  Kd_(1) = 1.0;        // Translation y
  59   Kp_(2) = 100.0;  Kd_(2) = 1.0;        // Translation z
  60   Kp_(3) = 100.0;  Kd_(3) = 1.0;        // Rotation x
  61   Kp_(4) = 100.0;  Kd_(4) = 1.0;        // Rotation y
  62   Kp_(5) = 100.0;  Kd_(5) = 1.0;        // Rotation z
  63 
  64   return true;
  65 }
  66 
  67 
  68 /// Controller startup in realtime
  69 void MyCartControllerClass::starting()
  70 {
  71   // Get the current joint values to compute the initial tip location.
  72   chain_.getJointPositions(qtmp_);
  73   jnt_to_pose_solver_->JntToCart(qtmp_, xtmp_);
  74   kin_.KDLtoEigen(xtmp_, x0_);
  75 
  76   // Initialize the phase of the circle as zero.
  77   circle_phase_ = 0.0;
  78 
  79   // Also reset the time-of-last-servo-cycle.
  80   last_time_ = robot_state_->getTime();
  81 }
  82 
  83 
  84 /// Controller update loop in realtime
  85 void MyCartControllerClass::update()
  86 {
  87   double       dt;              // Servo loop time step
  88   JointVector  q, qdot, tau;    // Joint position, velocity, torques
  89   CartPose     x, xd;           // Tip pose, desired pose
  90   Cart6Vector  xerr, xdot, F;   // Cart error,velocity,effort
  91   JacobianMatrix  J;            // Jacobian
  92 
  93   // Calculate the dt between servo cycles.
  94   dt = (robot_state_->getTime() - last_time_).toSec();
  95   last_time_ = robot_state_->getTime();
  96 
  97   // Get the current joint positions and velocities.
  98   chain_.getJointPositions(q);
  99   chain_.getJointVelocities(qdot);
 100 
 101   // Compute the forward kinematics and Jacobian (at this location).
 102   kin_.EigentoKDL(q, qtmp_);
 103   jnt_to_pose_solver_->JntToCart(qtmp_, xtmp_);
 104   jnt_to_jac_solver_->JntToJac(qtmp_, Jtmp_);
 105   kin_.KDLtoEigen(xtmp_, x);
 106   kin_.KDLtoEigen(Jtmp_, J);
 107 
 108   xdot = J * qdot;
 109 
 110   // Follow a circle of 10cm at 3 rad/sec.
 111   circle_phase_ += 3.0 * dt;
 112   Cart3Vector  circle(0,0,0);
 113   circle.x() = 0.1 * sin(circle_phase_);
 114   circle.y() = 0.1 * (1 - cos(circle_phase_));
 115 
 116   xd = x0_;
 117   xd.translation() += circle;
 118 
 119   // Calculate a Cartesian restoring force.
 120   kin_.computeCartError(x,xd,xerr);
 121   F = - Kp_.asDiagonal() * xerr - Kd_.asDiagonal() * xdot;
 122 
 123   // Convert the force into a set of joint torques.
 124   tau = J.transpose() * F;
 125 
 126   // And finally send these torques out.
 127   chain_.setJointEfforts(tau);
 128 }
 129 
 130 
 131 /// Controller stopping in realtime
 132 void MyCartControllerClass::stopping()
 133 {}
 134 
 135 
 136 
 137 /// Register controller to pluginlib
 138 PLUGINLIB_REGISTER_CLASS(MyCartControllerPlugin,
 139                          my_controller_ns::MyCartControllerClass,
 140                          pr2_controller_interface::Controller)

In comparison to the source code using KDL we notice the following changes:

  • In the init() method we only need to pre-allocate the temporary space for the KDL solvers. Not the entire variable set.

  44   // Resize (pre-allocate) the temporary variables in non-realtime.
  45   qtmp_.resize(chain_.getNumOfJoints());
  46   Jtmp_.resize(chain_.getNumOfJoints());

  • However, init() should confirm the number of joint matches the expectation set in the header file.

  48   // Verify the number of joints.
  49   if (chain_.getNumOfJoints() != Joints)
  50   {
  51     ROS_ERROR("The chain from '%s' to '%s' does not contain %d joints",
  52               root_name.c_str(), tip_name.c_str(), Joints);
  53     return false;
  54   }

  • Also notice the Kp/Kd gains are set as generic 6x1 vectors without explicit translation and orientation fields

  56   // Pick the gains.
  57   Kp_(0) = 100.0;  Kd_(0) = 1.0;        // Translation x
  58   Kp_(1) = 100.0;  Kd_(1) = 1.0;        // Translation y
  59   Kp_(2) = 100.0;  Kd_(2) = 1.0;        // Translation z
  60   Kp_(3) = 100.0;  Kd_(3) = 1.0;        // Rotation x
  61   Kp_(4) = 100.0;  Kd_(4) = 1.0;        // Rotation y
  62   Kp_(5) = 100.0;  Kd_(5) = 1.0;        // Rotation z
  63 

  • In both the starting() and init() methods, we see that the KDL solvers are now operating in the temporary space and immediately converted to Eigen types.

  71   // Get the current joint values to compute the initial tip location.
  72   chain_.getJointPositions(qtmp_);
  73   jnt_to_pose_solver_->JntToCart(qtmp_, xtmp_);
  74   kin_.KDLtoEigen(xtmp_, x0_);

 101   // Compute the forward kinematics and Jacobian (at this location).
 102   kin_.EigentoKDL(q, qtmp_);
 103   jnt_to_pose_solver_->JntToCart(qtmp_, xtmp_);
 104   jnt_to_jac_solver_->JntToJac(qtmp_, Jtmp_);
 105   kin_.KDLtoEigen(xtmp_, x);
 106   kin_.KDLtoEigen(Jtmp_, J);

  • Though using Eigen, the calculations are now more straight forward. Both to calculate the tip velocity, tip force, and joint torques.

 108   xdot = J * qdot;

 119   // Calculate a Cartesian restoring force.
 120   kin_.computeCartError(x,xd,xerr);
 121   F = - Kp_.asDiagonal() * xerr - Kd_.asDiagonal() * xdot;
 122 
 123   // Convert the force into a set of joint torques.
 124   tau = J.transpose() * F;

  • And in Eigen, accessing sub fields of a pose is slightly different. So the trajectory calculations are equivalent but using a different notation.

 110   // Follow a circle of 10cm at 3 rad/sec.
 111   circle_phase_ += 3.0 * dt;
 112   Cart3Vector  circle(0,0,0);
 113   circle.x() = 0.1 * sin(circle_phase_);
 114   circle.y() = 0.1 * (1 - cos(circle_phase_));
 115 
 116   xd = x0_;
 117   xd.translation() += circle;

  • Finally, notice the kinematic helper functions include the computation of a Cartesian error.

 120   kin_.computeCartError(x,xd,xerr);

Compiling and Running

To compile and run the controller, go back to the previous tutorial.

Wiki: pr2_mechanism/Tutorials/Coding a realtime Cartesian controller with Eigen (last edited 2010-01-06 01:50:16 by GunterNiemeyer)