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.
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.
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().
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:
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:
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:
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 }