2222#. ``examples/mujoco/scenarioArmWithThrusters.py``
2323
2424This script demonstrates how to simulate a spacecraft with solar panels deployed
25- using a Proportional-Derivative (PD ) controller. This script uses the MuJoCo-based
26- :ref:`DynamicObject<dynamicObject>` :ref:`MJScene<MJScene>`.
25+ using a Proportional-Integral- Derivative (PID ) controller. This script uses the
26+ MuJoCo-based :ref:`DynamicObject<dynamicObject>` :ref:`MJScene<MJScene>`.
2727
2828In ``mujoco/scenarioArmWithThrusters.py``, we saw how we can constrain joints to
2929follow a specific angle by letting the dynamic engine figure out and apply
3030the necessary torques. In this script, we are controlling the joints using a
31- PD controller. This is a more adequate simulation setup when you want to simulate
31+ PID controller. This is a more adequate simulation setup when you want to simulate
3232or design the control system for these joints. It is also generally more
3333computationally efficient than letting the dynamic engine figure out the torques.
3434
4545plots and in the 3D visualization how the panels never get deployed over their
4646joint limit.
4747
48- The deployment of the panels is controlled using a Proportional-Derivative (PD)
49- controller. The desired position and velocity profiles for the joints are
48+ The deployment of the panels is controlled using an analog PID controller.
49+ The desired position and velocity profiles for the joints are
5050generated using a trapezoidal/triangular velocity profile. These profiles are
51- then used as inputs to the PD controller, which computes the torque required to achieve
52- the desired motion.
51+ then used as inputs to the PID controller, which computes the torque required to achieve
52+ the desired motion. Note that the controller class extends ``StatefulSysModel``,
53+ instead of ``SysModel``, since we need to register the integral error as a
54+ continuous state.
5355
5456The simulation is run for 80 minutes and the state of the system is recorded.
5557The desired and achieved joint angles, as well as the torque applied to each
6567from Basilisk .simulation import mujoco
6668from Basilisk .utilities import SimulationBaseClass
6769from Basilisk .utilities import macros
68- from Basilisk .architecture import sysModel
70+ from Basilisk .simulation import StatefulSysModel
6971from Basilisk .architecture import messaging
7072from Basilisk .simulation import svIntegrators
7173
@@ -252,7 +254,7 @@ def run(initialSpin: bool = False, showPlots: bool = False, visualize: bool = Fa
252254 # the measured position and velocity of the joint (in this case the
253255 # exact values are used, but in a real system these may be the product
254256 # of a sensor), and the output is the torque to be applied to the joint.
255- pdController = PDController ()
257+ pdController = PIDController ()
256258 pdController .ModelTag = f"{ actuatorName } _controller"
257259
258260 # Connect the interpolators to the PD controller for the desired
@@ -385,13 +387,17 @@ def run(initialSpin: bool = False, showPlots: bool = False, visualize: bool = Fa
385387
386388# The following is an example of a Python-based SysModel that
387389# can be added to the dynamics task of a MJScene.
388- class PDController ( sysModel . SysModel ):
390+ class PIDController ( StatefulSysModel . StatefulSysModel ):
389391 """
390- A Proportional-Derivative (PD) Controller class for controlling joint states.
392+ A Proportional-Integral-Derivative (PID) Controller class for controlling joint states.
393+
394+ This models an analog PID controller, which means that its output evolves in continuous
395+ time, not discrete time. Thus, it should be used within the dynamics task of ``MJScene``.
391396
392397 Attributes:
393- K (float): Proportional gain.
394- P (float): Derivative gain.
398+ K_p (float): Proportional gain.
399+ K_d (float): Derivative gain.
400+ K_i (float): Integral gain.
395401
396402 measuredInMsg (messaging.ScalarJointStateMsgReader): Reader for the measured joint state.
397403 desiredInMsg (messaging.ScalarJointStateMsgReader): Reader for the desired joint state.
@@ -404,8 +410,9 @@ class PDController(sysModel.SysModel):
404410 def __init__ (self , * args : Any ):
405411 """Initialize"""
406412 super ().__init__ (* args )
407- self .K = 0.1
408- self .P = 0.002
413+ self .K_p = 0.1
414+ self .K_d = 0.002
415+ self .K_i = 0.0001
409416
410417 self .measuredInMsg = messaging .ScalarJointStateMsgReader ()
411418 self .desiredInMsg = messaging .ScalarJointStateMsgReader ()
@@ -415,15 +422,23 @@ def __init__(self, *args: Any):
415422
416423 self .outputOutMsg = messaging .SingleActuatorMsg ()
417424
425+ def registerStates (self , registerer : StatefulSysModel .DynParamRegisterer ):
426+ self .integralErrorState = registerer .registerState (1 , 1 , "integralError" )
427+ self .integralErrorState .setState ([[0 ]]) # explicitely zero initialize
428+
418429 def UpdateState (self , CurrentSimNanos : int ):
419430 """Computes the control command from the measured and desired
420431 joint position and velocity."""
421432 # Compute the error in the state and its derivative
422433 stateError = self .desiredInMsg ().state - self .measuredInMsg ().state
423434 stateDotError = self .desiredDotInMsg ().state - self .measuredDotInMsg ().state
435+ stateIntegralError = self .integralErrorState .getState ()[0 ][0 ]
424436
425437 # Compute the control output
426- control_output = self .K * stateError + self .P * stateDotError
438+ control_output = self .K_p * stateError + self .K_d * stateDotError + self .K_i * stateIntegralError
439+
440+ # Set the derivative of the integral error inner state
441+ self .integralErrorState .setDerivative ([[stateError ]])
427442
428443 # Write the control output to the output message
429444 payload = messaging .SingleActuatorMsgPayload ()
0 commit comments