Joint types and operationCompared to another object, a joint has two reference frames (visible only if the joint is selected). The first one is the regular reference frame that is fixed and that other objects also have. The second reference frame is not fixed, and will move relative to the first reference frame depending on the joint position (or joint value) that defines its configuration. Joint types4 types of joints are supported: [Revolute joint, prismatic joint, screw and spherical joint] [Two equivalent mechanisms (in this configuration): spherical joint (left) and 3 revolute joints (right)] [Two non-equivalent mechanisms: the right configuration is close to a singularity] A joint is used to allow for a relative movement between its parent and its children. When a parent-child relationship is built between a joint and an object, the object is attached to the joint's second reference frame, thus, a change of the joint's configuration (intrinsic position) will directly be reflected onto its children. New joints can be added to a scene with [Menu bar --> Add --> Joints]. Joint modesA joint can be in one of following modes: Joint controllersThere are many different ways a joint can be controlled. In following section, we differentiate betwen a loose controller and a precise controller: a loose joint controller will not be able to provide new control values in each possible regulation step (e.g. some regulation steps might/will be skipped, but control is still possible). A precise joint controller on the other hand, will be able to provide control values in each possible regulation step. First, the approach to take for controlling a joint will depend on the joint mode: The differentiation comes from the fact that a joint that operates in force/torque mode will be handled by the physics engine. And the physics engine will perform by default 10 times more calculation steps than the simulation loop: the simulation loop runs at 20Hz (in simulation time), while the physics engine runs at 200Hz (also in simulation time). That default behaviour can entirely be configured if required. If the joint is not in force/torque mode: if the joint is not in force/torque mode, then you can directly (and instantaneously) set its position via the sim.setJointPosition API function (or similar, e.g. simxSetJointPosition for the legacy remote API). You can do this from a child script, from a plugin, from a ROS node, or from a remote API client. If you do this from a child script, then it should be done inside of the actuation section of the child script. In following threaded child script example, the joint is controlled loosely in position, and there is no synchronization with the simulation loop:
-- Following script should run threaded:
jointHandle=sim.getObject('/Revolute_joint')
sim.setJointPosition(jointHandle,90*math.pi/180) -- set the position to 90 degrees
sim.wait(2) -- wait 2 seconds (in simulation time)
sim.setJointPosition(jointHandle,180*math.pi/180) -- set the position to 180 degrees
sim.wait(1) -- wait 1 second (in simulation time)
sim.setJointPosition(jointHandle,0*math.pi/180) -- set the position to 0 degrees
etc.
In following threaded child script example, the joint is controlled precisely in position in each simulation step, i.e. the thread is synchronized with the simulation loop:
-- Following script should run threaded:
local initialForbidLevel=sim.setThreadAutomaticSwitch(false) -- Automatic thread switching disabled
jointHandle=sim.getObject('/Revolute_joint')
sim.setJointPosition(jointHandle,90*math.pi/180) -- set the position to 90 degrees
sim.switchThread() -- the thread resumes in next simulation step (i.e. when t becomes t+dt)
sim.setJointPosition(jointHandle,180*math.pi/180) -- set the position to 180 degrees
sim.switchThread() -- the thread resumes in next simulation step
sim.setJointPosition(jointHandle,0*math.pi/180) -- set the position to 0 degrees
sim.switchThread() -- the thread resumes in next simulation step
-- etc.
sim.setThreadAutomaticSwitch(initialForbidLevel) -- Restore the automatic thread switching
-- In above code, a new joint position is applied in each simulation step
When you try to control a joint that is not in force/torque mode from an external application (e.g. via the remote API or ROS), then the external controller will run asynchronously to CoppeliaSim (i.e. similar to the non-synchronized code of a threaded child script). This is fine most of the time for loose control, but if you wish to control the position of the joint precisely in each simulation loop, you will have to run CoppeliaSim in stepped mode, and the external controller (e.g. the remote API client) will have to trigger each simulation step explicitely. Following illustrates a Python ZeroMQ remote API client that does this:
client.setstepping(True) # enable the stepped mode.
sim.startSimulation() # start the simulation
sim.setJointPosition(jointHandle,90*3.1415/180) # set the joint to 90 degrees
client.step() # trigger next simulation step. Above commands will be applied
sim.setJointPosition(jointHandle,180*3.1415/180) # set the joint to 180 degrees
client.step() # next simulation step executes. Above commands will be applied
sim.setJointPosition(jointHandle,0) # set the joint to 0 degrees
Following does the same, however with a C++ legacy remote API client:
simxSynchronous(clientId,1); // enable the stepped mode.
simxStartSimulation(clientId,simx_opmode_oneshot); // start the simulation
simxSetJointPosition(clientId,jointHandle,90.0f*3.1415f/180.0f,simx_opmode_oneshot); // set the joint to 90 degrees
simxSynchronousTrigger(clientId); // trigger next simulation step. Above commands will be applied
simxSetJointPosition(clientId,jointHandle,180.0f*3.1415f/180.0f,simx_opmode_oneshot); // set the joint to 180 degrees
simxSynchronousTrigger(clientId); // next simulation step executes. Above commands will be applied
simxSetJointPosition(clientId,jointHandle,0.0f*3.1415f/180.0f,simx_opmode_oneshot); // set the joint to 0 degrees
If the joint is in force/torque mode: if the joint operates in force/torque mode and is dynamically enabled, then it will be indirectly handled by the physics engine. If your joint's motor is not enabled, then your joint is not controlled (i.e. it will be free). Otherwise, your joint can be in following two dynamic modes: If your joint's motor is enabled, but the control loop is disabled, then the physics engine will apply the specified Maximum force/torque, and accelerate the joint until the target velocity is reached. If the load is small and/or the maximum force/torque high, that target velocity will be reached quickly. Otherwise, it will take some time, or, if the force/torque is not large enough, the target velocity will never be reached. You can programmatically adjust the target velocity with sim.setJointTargetVelocity (or for example, in case of the legacy remote API: simxSetJointTargetVelocity), and the maximum force/torque with sim.setJointMaxForce (or for example, in case of the the legacy remote API: simxSetJointMaxForce). You should be very careful before writing a precise joint controller for a joint in force/torque mode from a child script for following reason: By default, the simulation loop runs with a time step of 50ms (in simulation time). But the physics engine will run with a time step of 5ms, i.e. 10 times more often. A child script will be called in each simulation step, but not in each physics engine calculation step. This means that if you control a joint from a child script in a regular way, you will only be able to provide new control values once for 10 physics engine calculation steps: you will be missing 9 steps. One way to overcome this would be to change the default simulation settings and to specify a simulation time step of 5ms, instead of 50ms. This works fine, but remember that all other calculations (e.g. vision sensors, proximity sensors, distance calculations, IK, etc.) will also run 10 times more often, and finally slow down your simulation (most of the time you won't need such a high refresh rate for the other calculation modules. But the physics engine requires such a high refresh rate). Another, much better option, would be to use a joint callback function (or a dynamics callback function) as will be explained further down. If, one the other hand, you want to run a precise and regular joint controller externally (e.g. from a remote API client or a ROS node), then you have no other option than to set the simulation loop to the same rate as the physics engine rate, then run CoppeliaSim in stepped mode, and the external controller (e.g. the remote API client) will have to trigger each simulation step explicitely. Following illustrates a Python ZeroMQ remote API client that does this:
client.setstepping(True)
sim.startSimulation()
sim.setJointMaxForce(jointHandle,1.0)
sim.setJointTargetVelocity(jointHandle,180*3.1415/180)
client.step()
sim.setJointMaxForce(jointHandle,0.5)
sim.setJointTargetVelocity(jointHandle,180*3.1415/180)
client.step()
sim.setJointMaxForce(jointHandle,2.0)
sim.setJointTargetVelocity(jointHandle,180*3.1415/180) # set the joint target velocity
Following does the same, however with a C++ legacy remote API client:
simxSynchronous(clientId,1);
simxStartSimulation(clientId,simx_opmode_oneshot);
simxSetJointMaxForce(clientId,jointHandle,1.0f,simx_opmode_oneshot);
simxSetJointTargetVelocity(clientId,jointHandle,180.0f*3.1415f/180.0f,simx_opmode_oneshot);
simxSynchronousTrigger(clientId);
simxSetJointMaxForce(clientId,jointHandle,0.5f,simx_opmode_oneshot);
simxSetJointTargetVelocity(clientId,jointHandle,180.0f*3.1415f/180.0f,simx_opmode_oneshot);
simxSynchronousTrigger(clientId);
simxSetJointMaxForce(clientId,jointHandle,2.0f,simx_opmode_oneshot);
simxSetJointTargetVelocity(clientId,jointHandle,180.0f*3.1415f/180.0f,simx_opmode_oneshot);
If your joint's motor is enabled, and the control loop is also enabled, then the physics engine will handle the joint according to the setting: your joint can operate in position control (i.e. PID control), in a spring/damper mode, or in custom control. PID and spring/damper parameters can be updated from a child script, from a remote API client or from a ROS node. Refer to object parameter IDs 2002-2004, and 2018-2019. Desired target positions can be set with sim.setJointTargetPosition (or, for example, from a legacy remote API client: simxSetJointTargetPosition). When you need a precise custom controller, then you should use a joint callback function instead (or a dynamics callback function). Finally, if you need a precise PID or custom controller that is implemented in an external application, you need to make sure that the simulation step is the same as the physics engine calculation step: by default, CoppeliaSim's simulation loop runs at 20Hz (in simulation time), while the physics engine runs at 200Hz. You can adjust the simulation step size in the simulation setting. You also need to make sure you run CoppeliaSim in stepped mode. Following illustrates a Python ZeroMQ remote API client that does this:
client.setstepping(True)
sim.startSimulation()
sim.setJointTargetPosition(jointHandle,90*3.1415/180)
client.step()
sim.setJointTargetPosition(jointHandle,180*3.1415/180)
client.step()
sim.setJointTargetPosition(jointHandle,0)
Following does the same, however with a C++ legacy remote API client:
simxSynchronous(clientId,1);
simxStartSimulation(clientId,simx_opmode_oneshot);
simxSetJointTargetPosition(clientId,jointHandle,90.0f*3.1415f/180.0f,simx_opmode_oneshot);
simxSynchronousTrigger(clientId);
simxSetJointTargetPosition(clientId,jointHandle,180.0f*3.1415f/180.0f,simx_opmode_oneshot);
simxSynchronousTrigger(clientId);
simxSetJointTargetPosition(clientId,jointHandle,0.0f*3.1415f/180.0f,simx_opmode_oneshot);
You can also have a remote API client provide control values for a custom joint controller implemented in a joint callback function, by providing values, for instance via signals, to that joint callback function. For example, from a Python ZeroMQ remote API client:
client.setstepping(True)
sim.startSimulation()
sim.setFloatSignal("myDesiredTorque",1.0)
sim.setFloatSignal("myDesiredTarget",90.0*3.1415/180.0)
client.step()
Following does the same, however with a C++ legacy remote API client:
simxSynchronous(clientId,1);
simxStartSimulation(clientId,simx_opmode_oneshot);
simxSetFloatSignal(clientId,"myDesiredTorque",1.0f,simx_opmode_oneshot);
simxSetFloatSignal(clientId,"myDesiredTarget",90.0f*3.1415/180.0f,simx_opmode_oneshot);
simxSynchronousTrigger(clientId);
In above example, your joint callback function could fetch those two signals (with sim.getFloatSignal) before doing the control. |