The following example solves a qp problem expressed at the torque level such that:
\begin{equation}\begin{array}{ccc}\boldsymbol{\tau}^{opt} = & \underset{\boldsymbol{\tau}}{\mathrm{argmin}} & ||\boldsymbol{\dot{v}}(\boldsymbol{\tau}) - \boldsymbol{\dot{v}}^{target} || + \omega || \boldsymbol{\tau} - (\boldsymbol{g} - \boldsymbol{\dot{q}})||^2_{M^{-1}}\\& \textrm{s.t.} & \boldsymbol{\tau^{min}} \leq \boldsymbol{\tau} \leq \boldsymbol{\tau^{max}}. \\ & & \boldsymbol{q}^{min} \leq \boldsymbol{q}(\boldsymbol\tau) \leq \boldsymbol{q}^{max} \\ & & \boldsymbol{\dot{q}}^{min} \leq \boldsymbol{\dot{q}}(\boldsymbol\tau) \leq \boldsymbol{\dot{q}}^{max} \end{array} \end{equation}
.
The robot main tasks consists in following a simple trajectory defined in Cartesian space. The mujoco library is used to simulate the robot behaviour.
Simulation
To run this example run the following command from the build/examples
directory:
./torqueQontrol robot_name
where robot_name
can be either panda
or universal_robots_ur5e
Full code
15 #include "mujoco/mujoco_sim.h"
16 #include "Qontrol/Qontrol.hpp"
17 #include "trajectory_generation/trajectory_generation.h"
19 #define MUJOCO_PLUGIN_DIR "mujoco_plugin"
22 using namespace Qontrol;
24 class MujocoQontrol :
public MujocoSim
28 std::shared_ptr<Model::RobotModel<Model::RobotModelImplType::PINOCCHIO>> model;
29 std::shared_ptr<JointTorqueProblem> torque_problem;
30 std::shared_ptr<Task::CartesianAcceleration<ControlOutput::JointTorque>> main_task;
31 std::shared_ptr<Task::JointTorque<ControlOutput::JointTorque>> regularisation_task;
32 pinocchio::SE3 init_pose;
36 TrajectoryGeneration* traj;
37 std::string resource_path;
39 void initController()
override
44 torque_problem = std::make_shared<Qontrol::JointTorqueProblem>(model);
46 regularisation_task = torque_problem->task_set->add<
Task::JointTorque>(
"RegularisationTask",1e-5);
49 auto joint_velocity_constraint = torque_problem->constraint_set->add<
Constraint::JointVelocity>(
"JointVelocityConstraint");
50 auto joint_torque_constraint = torque_problem->constraint_set->add<
Constraint::JointTorque>(
"JointTorqueConstraint");
52 mju_copy(d->qpos, m->key_qpos, m->nu );
53 robot_state.joint_position.resize(model->getNrOfDegreesOfFreedom());
54 robot_state.joint_velocity.resize(model->getNrOfDegreesOfFreedom());
56 for (
int i=0; i<model->getNrOfDegreesOfFreedom() ; ++i)
58 robot_state.joint_position[i] = d->qpos[i];
59 robot_state.joint_velocity[i] = d->qvel[i];
61 model->setRobotState(robot_state);
63 traj =
new TrajectoryGeneration(resource_path+
"trajectory.csv", m->opt.timestep);
66 void updateController()
override
68 for (
int i=0; i<model->getNrOfDegreesOfFreedom() ; ++i)
70 robot_state.joint_position[i] = d->qpos[i];
71 robot_state.joint_velocity[i] = d->qvel[i];
73 model->setRobotState(robot_state);
76 pinocchio::SE3 traj_pose(traj->pose.matrix());
78 pinocchio::SE3 current_pose(model->getFramePose(model->getTipFrameName()).matrix());
79 const pinocchio::SE3 tipMdes = current_pose.actInv(traj_pose);
80 auto err = pinocchio::log6(tipMdes).toVector();
82 Eigen::Matrix<double, 6, 1> p_gains;
83 p_gains << 1000, 1000, 1000, 1000, 1000, 1000;
85 Eigen::Matrix<double, 6, 1> d_gains = 2.0 * p_gains.cwiseSqrt();
86 Eigen::Matrix<double, 6, 1> xdd_star =
87 p_gains.cwiseProduct(err) +
88 d_gains.cwiseProduct(traj->velocity - model->getFrameVelocity(model->getTipFrameName())) + traj->acceleration;
90 main_task->setTargetAcceleration(xdd_star);
91 regularisation_task->setTargetTorque(model->getJointGravityTorques() -
92 robot_state.joint_velocity);
93 regularisation_task->setWeightingMatrix(
94 model->getInverseJointInertiaMatrix());
95 torque_problem->update(m->opt.timestep);
97 if (torque_problem->solutionFound())
99 sendJointTorque(torque_problem->getJointTorqueCommand());
104 int main(
int argc,
const char** argv) {
105 MujocoQontrol mujoco_qontrol;
106 Qontrol::Log::Logger::parseArgv(argc, argv);
109 mjv_defaultScene(&scn);
112 mjv_defaultCamera(&cam);
115 mjv_defaultOption(&opt);
118 mjv_defaultPerturb(&pert);
121 auto sim = std::make_unique<mj::Simulate>(
122 std::make_unique<mj::GlfwAdapter>(),
123 &scn, &cam, &opt, &pert,
true
127 std::string robot = argv[1];
128 std::string mujoco_model =
"./resources/"+robot+
"/scene.xml";
129 mujoco_qontrol.resource_path =
"./resources/"+robot+
"/";
132 std::thread physicsthreadhandle( &MujocoQontrol::PhysicsThread, mujoco_qontrol, sim.get(), mujoco_model);
136 physicsthreadhandle.join();
Explanation of the code
Declaration
First we declare all the objects that will be used to define our problem.
0 std::shared_ptr<Model::RobotModel<Model::RobotModelImplType::PINOCCHIO>> model;
We use pinocchio for our model library.
28 std::shared_ptr<JointTorqueProblem> torque_problem;
The output of our qp controller is at the torque level.
29 std::shared_ptr<Task::CartesianAcceleration<ControlOutput::JointTorque>> main_task;
We then declare two tasks that will be updated every milliseconds.
The main task is expressed as a Cartesian Acceleration task.
30 std::shared_ptr<Task::JointTorque<ControlOutput::JointTorque>> regularisation_task;
And we add a regularisation task (also at the torque level).
Initialization
39 void initController()
override
42 Model::RobotModel<Model::RobotModelImplType::PINOCCHIO>::loadModelFromFile(resource_path +
"robot.urdf");
During initialization we instantiate the model with the robot urdf.
We initialize the problem by giving it the model. By default, the qpmad library is used.
44 torque_problem = std::make_shared<Qontrol::JointTorqueProblem>(model);
45 main_task = torque_problem->task_set->add<Task::CartesianAcceleration>(
"MainTask");
46 regularisation_task = torque_problem->task_set->add<Task::JointTorque>(
"RegularisationTask",1e-5);
We then fill the task set of torque_problem with the main task and the regularisation task. Each tasks is given a name and a relative weight \( \omega \). This weight can be modified at any time.
48 auto joint_configuration_constraint = torque_problem->constraint_set->add<Constraint::JointConfiguration>(
"JointConfigurationConstraint");
49 auto joint_velocity_constraint = torque_problem->constraint_set->add<Constraint::JointVelocity>(
"JointVelocityConstraint");
50 auto joint_torque_constraint = torque_problem->constraint_set->add<Constraint::JointTorque>(
"JointTorqueConstraint");
We then fill the constraint set of torque_problem with the three pre-implemented constraints. Each constraint is given a name. These constraints will automatically be updated during the update
of Qontrol.
52 mju_copy(d->qpos, m->key_qpos, m->nu );
53 robot_state.joint_position.resize(model->getNrOfDegreesOfFreedom());
54 robot_state.joint_velocity.resize(model->getNrOfDegreesOfFreedom());
56 for (
int i=0; i<model->getNrOfDegreesOfFreedom() ; ++i)
58 robot_state.joint_position[i] = d->qpos[i];
59 robot_state.joint_velocity[i] = d->qvel[i];
61 model->setRobotState(robot_state);
We also create the robot state and fill it with the simulated robot current state.
63 traj =
new TrajectoryGeneration(resource_path+
"trajectory.csv", m->opt.timestep);
We create a simple trajectory that has been precalculated and store in a csv file. This trajectory start at the robot current Cartesian pose and does a translation of (-0.1, -0,1, -0.1) m.
Update
67 void updateController()
override
69 for (
int i=0; i<model->getNrOfDegreesOfFreedom() ; ++i)
71 robot_state.joint_position[i] = d->qpos[i];
72 robot_state.joint_velocity[i] = d->qvel[i];
74 model->setRobotState(robot_state);
The update function is called every milliseconds. At the beginning of each update we fill the new robot state according to the simulated robot.
We also update the trajectory so that it gives the next Cartesian pose to reach in 1 ms.
77 pinocchio::SE3 traj_pose(traj->pose.matrix());
79 pinocchio::SE3 current_pose(model->getFramePose(model->getTipFrameName()).matrix());
We then compute the desired Cartesian acceleration using a simple PD controller. Pinocchio is used to compute the error between the desired Cartesian pose and the current Cartesian pose. This is done by the log6
function. The p_gains
and d_gains
variables are the gains of the PD controller.
80 const pinocchio::SE3 tipMdes = current_pose.actInv(traj_pose);
81 auto err = pinocchio::log6(tipMdes).toVector();
83 Eigen::Matrix<double, 6, 1> p_gains;
84 p_gains << 1000, 1000, 1000, 1000, 1000, 1000;
86 Eigen::Matrix<double, 6, 1> d_gains = 2.0 * p_gains.cwiseSqrt();
87 Eigen::Matrix<double, 6, 1> xdd_star =
88 p_gains.cwiseProduct(err) +
89 d_gains.cwiseProduct(traj->velocity - model->getFrameVelocity(model->getTipFrameName())) + traj->acceleration;
91 main_task->setTargetAcceleration(xdd_star);
92 regularisation_task->setTargetTorque(model->getJointGravityTorques() -
93 robot_state.joint_velocity);
94 regularisation_task->setWeightingMatrix(
95 model->getInverseJointInertiaMatrix());
The desired Cartesian acceleration is then fed to the main task
. The regularisation task is also updated so that it compensate for gravity plus a damping term. The resulting regularisation task would be written : \( || \boldsymbol{\tau} - (\boldsymbol{g} - \boldsymbol{\dot{q}})||^2_{M^{-1}}\)
96 torque_problem->update(m->opt.timestep);
98 if (torque_problem->solutionFound())
100 sendJointTorque(torque_problem->getJointTorqueCommand());
Once we updated the necassary tasks and constraints we can update the whole problem. If a solution to the problem exist we can then get it and send it to the simulated robot.
Main function
105 int main(
int argc,
const char** argv) {
106 MujocoQontrol mujoco_qontrol;
107 Qontrol::Log::Logger::parseArgv(argc, argv);
110 mjv_defaultScene(&scn);
113 mjv_defaultCamera(&cam);
116 mjv_defaultOption(&opt);
119 mjv_defaultPerturb(&pert);
122 auto sim = std::make_unique<mj::Simulate>(
123 std::make_unique<mj::GlfwAdapter>(),
124 &scn, &cam, &opt, &pert,
true
128 std::string robot = argv[1];
129 std::string mujoco_model =
"./resources/"+robot+
"/scene.xml";
130 mujoco_qontrol.resource_path =
"./resources/"+robot+
"/";
133 std::thread physicsthreadhandle( &MujocoQontrol::PhysicsThread, mujoco_qontrol, sim.get(), mujoco_model);
137 physicsthreadhandle.join();
The main function function fetches the robot name given in argv
and starts the Mujoco simulation.