Torque control example

In the examples subfolder you will find a minimal example for commanding torques to the robot. It applies a sinusoidal torque to the 6th joint (index 5) while reading the joint positions. To run it make sure to

  • have an instance of a robot controller running at the configured IP address (or adapt the address to your needs). On URSim torque commands don’t have any effect.

  • have PolyScope version 5.25.1 / 10.12.1 or later installed on the robot.

  • run it from the package’s main folder (the one where the README.md file is stored), as for simplicity reasons it doesn’t use any sophisticated method to locate the required files.

This page will walk you through the direct_torque_control.cpp example.

Initialization

At first, we create a ExampleRobotWrapper object giving it the robot’s IP address, script file and RTDE recipes.

Listing 62 examples/direct_torque_control.cpp
75  bool headless_mode = true;
76  g_my_robot = std::make_unique<urcl::ExampleRobotWrapper>(robot_ip, OUTPUT_RECIPE, INPUT_RECIPE, headless_mode,
77                                                           "external_control.urp");
78  if (!g_my_robot->isHealthy())
79  {
80    URCL_LOG_ERROR("Something in the robot initialization went wrong. Exiting. Please check the output above.");
81    return 1;
82  }
83
84  // This example requires Software version 5.25.1 / 10.12.1 or higher. Skip gracefully on older
85  // software versions so that CI (which runs all examples) does not fail.
86  {
87    auto robot_version = g_my_robot->getUrDriver()->getVersion();
88    bool version_supported =
89        ((robot_version.major == 5) && (robot_version >= urcl::VersionInformation::fromString("5.25.1"))) ||
90        ((robot_version.major >= 10) && (robot_version >= urcl::VersionInformation::fromString("10.12.1")));
91    if (!version_supported)
92    {
93      URCL_LOG_INFO("This direct_torque control example requires a robot with at least version 5.25.1 / 10.12.1. Your "
94                    "robot has version %s. Skipping.",
95                    robot_version.toString().c_str());
96      return 0;
97    }
98  }
99  // --------------- INITIALIZATION END -------------------

Note

This example requires PolyScope version 5.25.1 / 10.12.1 or later, as it uses setFrictionScales() which is only available in these versions and later. If you try to run it on an older software version, this example will skip gracefully.

Friction compensation setup

Before starting the control loop, friction compensation scales are configured using setFrictionScales(). Since this example only controls a single joint (JOINT_INDEX), friction compensation for the other joints is set to zero to keep them more steady. The controlled joint gets full compensation (scale factor 1.0) for both viscous and Coulomb friction.

Listing 63 examples/direct_torque_control.cpp
104  // Scale each individual joint's friction compensation. This is supported from PolyScope 5.25.1 / PolyScope X 10.12.1
105  // and upwards.
106  urcl::vector6d_t viscous_scale = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
107  urcl::vector6d_t coulomb_scale = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
108
109  // As this example only applies to JOINT_INDEX, we can set the other joints to 0.0 to disable friction compensation to
110  // make them more steady.
111  viscous_scale[JOINT_INDEX] = 1.0;
112  coulomb_scale[JOINT_INDEX] = 1.0;
113  g_my_robot->getUrDriver()->setFrictionScales(viscous_scale, coulomb_scale);

Move to start position

Before entering the torque control loop, the robot is moved to a known start position using the InstructionExecutor. This ensures a predictable initial state for the sinusoidal motion. First, RTDE communication is started and the current joint positions are read. Then, the target position for JOINT_INDEX is set to start_position and the robot is moved there using moveJ().

Listing 64 examples/direct_torque_control.cpp
115  // Once RTDE communication is started, we have to make sure to read from the interface buffer, as
116  // otherwise we will get pipeline overflows. Therefor, do this directly before starting your main
117  // loop.
118  g_my_robot->getUrDriver()->startRTDECommunication();
119
120  urcl::rtde_interface::DataPackage data_pkg(g_my_robot->getUrDriver()->getRTDEOutputRecipe());
121  if (!g_my_robot->getUrDriver()->getDataPackage(data_pkg))
122  {
123    URCL_LOG_ERROR("Could not get fresh data package from robot");
124    return 1;
125  }
126  if (!data_pkg.getData("actual_q", g_joint_positions))
127  {
128    // This throwing should never happen unless misconfigured
129    std::string error_msg = "Did not find 'actual_q' in data sent from robot. This should not happen!";
130    throw std::runtime_error(error_msg);
131  }
132  // Use motion primitives to move JOINT_INDEX to start position
133  g_joint_positions[JOINT_INDEX] = START_POSITION;
134  auto instruction_executor = std::make_shared<urcl::InstructionExecutor>(g_my_robot->getUrDriver());
135  instruction_executor->optimoveJ(g_joint_positions);

Robot control loop

The main control loop applies a sinusoidal torque to the controlled joint for 10 full periods. In each cycle, the latest RTDE data package is read using getDataPackage(). This call blocks until new data arrives from the robot (with an internal timeout), so the robot’s RTDE cycle effectively controls the timing of this loop. The current joint positions are then extracted from the received package:

Listing 65 examples/direct_torque_control.cpp
141  // Run 10 periods of the sinusoidal
142  while (time < 10 * 2 * M_PI / OMEGA)
143  {
144    time += timestep;
145    // Read latest RTDE package. This will block for a hard-coded timeout (see UrDriver), so the
146    // robot will effectively be in charge of setting the frequency of this loop.
147    // In a real-world application this thread should be scheduled with real-time priority in order
148    // to ensure that this is called in time.
149    if (!g_my_robot->getUrDriver()->getDataPackage(data_pkg))
150    {
151      URCL_LOG_WARN("Could not get fresh data package from robot");
152      return 1;
153    }
154    // Read current joint positions from robot data
155    if (!data_pkg.getData("actual_q", g_joint_positions))
156    {
157      // This throwing should never happen unless misconfigured
158      std::string error_msg = "Did not find 'actual_q' in data sent from robot. This should not happen!";
159      throw std::runtime_error(error_msg);
160    }

The target torque for the controlled joint is computed as a sinusoidal function of time:

Listing 66 examples/direct_torque_control.cpp
162    // Open loop control. The target is incremented with a constant each control loop
163    target_torques[JOINT_INDEX] = AMPLITUDE * std::sin(OMEGA * time);

To send the control command, the robot’s ReverseInterface is used via the writeJointCommand() function:

Listing 67 examples/direct_torque_control.cpp
165    // Setting the RobotReceiveTimeout time is for example purposes only. This will make the example running more
166    // reliable on non-realtime systems. Use with caution in productive applications. Having it
167    // this high means that the robot will continue a motion for this given time if no new command
168    // is sent / the connection is interrupted.
169    bool ret = g_my_robot->getUrDriver()->writeJointCommand(target_torques, urcl::comm::ControlMode::MODE_TORQUE,
170                                                            urcl::RobotReceiveTimeout::millisec(100));
171    if (!ret)
172    {
173      URCL_LOG_ERROR("Could not send joint command. Make sure that the robot is in remote control mode and connected "
174                     "with a network cable.");
175      return 1;
176    }