Joint offset in Franka FR3 with impedance control

Hello. I’m using the robot Franka FR3 in Isaac Sim 5.1.0 with impedance control. The problem is that I have an offset in the joint 6 of the robot. I calculate the torques to reach a pose where the end effector is perpendicular to the ground: [0, -0.785, 0, -2.356, 0, 1.571, 0.785], but the joint 6 can’t reach that configuration, and it stays near 3 radians (an offset of near 80 degrees).

I don’t know if there’s a problem with my code, but I don’t think is the way I calculate the torques, because I’m using also the real robot with the same effort equation and this doesn’t happen. There’s someone with the same problem controlling with torques the Franka FR3?

def compute_impedance_torques(self):

# Get full matrixes

        M_full = self.robot_view.get_mass_matrices(clone=False)\[0\]

        C_full = self.robot_view.get_coriolis_and_centrifugal_forces(clone=False)\[0\]

        G_full = self.robot_view.get_generalized_gravity_forces(clone=False)\[0\]

# Cut to7x7 y 7x1

        M = M_full\[:7, :7\]  # *Matriz de inercia*

        C = C_full\[:7\]      # *Vector Coriolis*

        G = G_full\[:7\]      # *Vector Gravedad*

# Get actual state

        q = self.robot_view.get_joint_positions(clone=False)\[0\]\[:7\]

        dq = self.robot_view.get_joint_velocities(clone=False)\[0\]\[:7\]

# Calculate error

        position_error = self.target_positions - q

# Control law: M * (Kp * error - Kd * vel) + Coriolis + Gravity

# Isaac gives Cas the forces vector C(q,dq)*dq

        torques = M @ (self.stiffness \* position_error - self.damping \* dq) + C + G

return torques

franka.apply_action(ArticulationAction(joint_efforts=full_torques))

Thank you for posting this. The issue you’re experiencing—joint 6 of the Franka FR3 in Isaac Sim 5.1.0 consistently displaying a large offset when commanded via impedance control, despite using the same torque equation as on the real robot—has been observed by others and points to simulation-specific factors rather than a problem with your control law implementation.

Key Troubleshooting Steps

1. Joint Limits and Initial Configuration

  • Ensure the joint limit ranges for joint 6 are correctly set in your robot’s URDF or asset configuration in Isaac Sim.
  • The Franka FR3 typically has joint limits near [-2.897, 2.897] radians for most joints, but variations in simulated assets can occur. If Isaac Sim loads an incorrect asset or ignores limits, your controller might calculate a feasible target that the simulation’s kinematics can’t achieve.

2. Impedance Controller Parameters

  • Check your self.stiffness and self.damping parameters. Too low values can lead to the joint failing to reach the target due to insufficient corrective torque, while too high values may cause instability or oscillation.
  • For Franka arm impedance control, documented stiffness values typically range from 300–600 Nm/rad and damping from 40–70 Nm·s/rad. Try adjusting these parameters up or down in increments to see if joint 6 approaches the desired position more closely.

3. Collision, Self-Collision, and Constraints

  • Isaac Sim’s physics solver may enforce additional constraints (like self-collision or hard contacts) that do not precisely match the real robot’s capabilities. Double-check that joint 6 isn’t being limited by collision with other links or environment objects in your simulation scene.

4. Asset Version and Import Issues

  • Use the asset from the latest NVIDIA Isaac Sim release assets repository for Franka FR3 to ensure compatibility and correct parameterization.
  • If you converted a URDF or used a custom asset, double-check that joint origins, axis orientations, and angle offsets in the simulation match those in the real robot.

5. Target Pose Feasibility

  • The pose [0, -0.785, 0, -2.356, 0, 1.571, 0.785] may be close to or outside the kinematically reachable workspace if any joint limit or orientation in Isaac Sim deviates slightly from the real robot. Test joint 6 by manually commanding increments to see if it will reach 1.571 radians when other joints are held fixed.

Thank you for your reply.

From what I’ve seen, the URDF is correct, and the joint configuration is the robot’s home position, with the robot in a very “comfortable” pose, with no singularities nearby. Furthermore, I’m not getting any collision errors, and actually, by significantly increasing the stiffness in that joint and lowering the damping, I’m able to reduce the angular error. But the point is that I have to set a stiffness of 2000 or 3000 and a damping of 10, and it still has an error of about 0.2/0.3 radians.

This is quite far from reality with the actual robot, and that is also the problem. I understand, then, that it’s a problem with the simulation itself and its physics.

Hi, Your real robot likely has its own motor controller, friction, backlash, compliance, and current limits, which are not automatically modeled. Even if you send “the same force”, the real system’s effective stiffness/damping is emergent from motor + gear + controller, not just a single PD pair. In PhysX, drives are also constrained by maxForce and by the solver iterations and timestep, so the joint may saturate or converge with a steady-state error instead of exactly reaching the target. Because of this, sending the same numeric “force” to the Isaac Sim drive does not correspond to the same physical torque profile that the real actuator generates, so you see an offset.
The stiffness and damping parameters in Isaac Sim are controller gains for the physics solver, not necessarily the literal physical spring and damper constants of your hardware. Typical stable values for articulated robots in PhysX can be orders of magnitude larger than what you would measure as physical joint stiffness/damping.

1 Like

Hi,

I managed to solve the offset problem. It was an issue with the apply_action function. When you pass the manually calculated torques to the function, the controller itself continues to try to stabilise the robot in its initial pose (q = [0,0,0,0,0,0,0], with the arm extended upwards). The torques generated by that controller are added to those you pass to apply_action, and it is that sum that is applied to the robot.

That’s why, in some joints, I couldn’t reach the target joint configuration, because the torques for that target weren’t enough to counteract those of the initial pose. I solved this simply by sending some 0 gains to that controller:

franka = world.scene.get_object("franka")

controller = franka.get_articulation_controller()
controller.set_gains(np.array([0.0]*9), np.array([0.0]*9))

Thanks to this, I no longer have any offset in any joint.

On the other hand, you are right, there is a gap between the simulator and reality, as expected, and the calculated forces will not be applied in the same way, but that is a totally acceptable problem.

1 Like