Skip to content

Composing Standard Library Components

In this demo we will show how to:

  • build a DC motor model using Dyad standard library components

  • test the model with imposed boundary conditions

  • add a controller to the model and simulate varying the controller parameters

In this example, we will develop a simple model of a DC motor using standard electrical and rotational components. Following the implementation of the model, we will test the motor and also implement a controller.

The image below shows a schematic of the simple motor model: A schematic of a simple controlled DC motor

To get started, create a new component library, as indicated on the "Getting started" page.

Adding the Standard Libraries

We will create the DC motor model using electrical, rotational, and block components from the Dyad standard libraries. These libraries and the various components, parameters, interfaces, etc. are documented at [TODO: INSERT LINKS. This will come once the stdlib docs are integrated here.] For illustration purposes, we will show how to add the library packages to your environment even though the step above already included them. From the REPL, issue the following commands:

julia
using Pkg
Pkg.add([
    "BlockComponents",
    "RotationalComponents",
    "ElectricalComponents"
])

DC Motor

Now that the required libraries are in place, let's create the simple DC motor component.

The interface for the DC motor model consists of two electrical pin s p and n, the mechanical spline shaft, and a mechanical spline housing. The motor is modeled as a resistance and an inductance. The coupling between the electrical and rotational domains occurs via an electro-motive force (EMF) component. The voltage across the EMF is proportional to the angular velocity and the current is proportional to the torque. The motor mechanical component include viscous friction in, e.g., a bearing and the inertia of the shaft. The reaction torques on the friction and emf components are connected to the housing connector.

The code below is the motor model in dc_motor.dyad in the dyad folder.

dyad
component DCMotor
  p = Pin()
  n = Pin()
  shaft = Spline()
  housing = Spline()
  R1 = ElectricalComponents.Resistor(R=R)
  L1 = ElectricalComponents.Inductor(L=L)
  emf = ElectricalComponents.RotationalEMF(k=k)
  inertia = RotationalComponents.Inertia(J=J)
  friction = RotationalComponents.Damper(d=f)
  # Armature resistance
  parameter R::Resistance = 0.5
  # Armature inductance
  parameter L::Inductance = 4.5e-3
  # Motor constant
  parameter k::ElectricalTorqueConstant = 0.5
  # Motor inertia
  parameter J::Inertia = 0.02
  # Motor friction factor
  parameter f::RotationalDampingConstant = 0.01
relations
  connect(p, R1.p)
  connect(R1.n, L1.p)
  connect(L1.n, emf.p)
  connect(emf.n, n)
  connect(emf.rotor, inertia.spline_a, friction.spline_a)
  connect(friction.spline_b, emf.housing, housing)
  connect(inertia.spline_b, shaft)
end

Testing the Motor

Now that we have constructed the DC motor model, let's create a test for it. In this test, we will apply a constant voltage across the motor electrical pins. We will apply the motor load as a prescribed torque with a step input.

The code below is in test_dc_motor_load.dyad in the dyad folder. It includes the test model TestDCMotorLoad along with the analysis DCMotorLoad that specifies the transient test simulation and allows access to the parameter V in the analysis to set the motor voltage in the model.

dyad
analysis DCMotorLoadAnalysis
  extends TransientAnalysis(stop=4, abstol=1m, reltol=1m)
  model = TestDCMotorLoad(V_motor=V)
  parameter V::Voltage = 1.0
end

component TestDCMotorLoad
  motor = DCMotor()
  ground = ElectricalComponents.Ground()
  source = ElectricalComponents.VoltageSource()
  fixed = RotationalComponents.Fixed()
  load = RotationalComponents.TorqueSource()
  load_source = BlockComponents.Step(height=tau_load, start_time=load_step_start_time)
  voltage_source = BlockComponents.Constant(k=V_motor)
  # Motor voltage
  parameter V_motor::Voltage = 1
  # Amplitude of load torque step
  parameter tau_load::Torque = -0.3
  # Load step start time
  parameter load_step_start_time::Time = 2
relations
  initial motor.L1.i = 0
  initial motor.inertia.w = 0
  connect(voltage_source.y, source.V)
  connect(load_source.y, load.tau)
  connect(source.p, motor.p)
  connect(motor.n, source.n, ground.g)
  connect(motor.shaft, load.spline)
  connect(motor.housing, load.support, fixed.spline)
end

We can now run the DCMotorLoad analysis that we defined above. This analysis includes the start time, stop time, tolerances, etc. You can also modify parameters of the model when executing the analysis. For the first simulation, issuing the command below will run the analysis and generate some plots of the state variables in the system. At the beginning of the sim, there is no load and the motor acceleratse to a steady state speed (recall there is friction in the model). Once the load kicks in, the motor current increases to match the load torque and the speed drops.

julia
using Plots
plot(DCMotorLoadAnalysis())

If you want to see each plot on a separate graph, you can add some layout options to the plots. Note that you can use any of the options from Plots.jl or even use a different plotting backend like Makie.jl.

julia
plot(DCMotorLoadAnalysis(), layout=(3,1), size=(400, 400))

If you would like to plot other variables, you can use the symbolic indexing to reference the variables by name. For example, the command below will plot the motor torque:

julia
using DyadInterface

result = DCMotorLoadAnalysis()
model = artifacts(result, :SimplifiedSystem)
plot(result; idxs = model.motor.emf.tau, size=(400, 400))

Controlling the Motor

In the previous model, we applied a voltage to the motor and a load and saw the resulting speed response. Now let's add a controller to control the speed. To do so, we will use a PID controller and also wire a speed sensor to provide the motor speed to the controller. The desired motor speed is set as a constant.

The code below is in test_dc_motor_load_controlled.dyad in the dyad folder. It includes the test model TestDCMotorLoadControlled along with the analysis DCMotorLoadControlled that specifies the test simulation and allows easy access to vary the controller parameters in the analysis.

dyad
analysis DCMotorLoadControlledAnalysis
  extends TransientAnalysis(stop=4, abstol=1m, reltol=1m)
  model = TestDCMotorLoadControlled(k=k, Ti=Ti, Td=Td, Nd=Nd)
  # Controller gain
  parameter k::Real = 0.5
  # Controller time constant of the integrator block
  parameter Ti::Time = 0.1
  # Controller Time constant of the derivative block
  parameter Td::Time = 1e5
  # Controller filter parameter
  parameter Nd::Real = 10
end

component TestDCMotorLoadControlled
  motor = DCMotor()
  ground = ElectricalComponents.Ground()
  source = ElectricalComponents.VoltageSource()
  fixed = RotationalComponents.Fixed()
  load = RotationalComponents.TorqueSource()
  load_source = BlockComponents.Step(height=tau_load, start_time=load_step_start_time)
  speed_reference = BlockComponents.Constant(k=w_motor)
  controller = BlockComponents.LimPID(k=k, Ti=Ti, Td=Td, Nd=Nd, y_max=5, y_min=-5)
  signal_ff = BlockComponents.Constant(k=0)
  speed_sensor = RotationalComponents.VelocitySensor()
  # Motor desired speed
  parameter w_motor::AngularVelocity = 1
  # Amplitude of load torque step
  parameter tau_load::Torque = -0.3
  # Load step start time
  parameter load_step_start_time::Time = 2
  # Controller gain
  parameter k::Real = 0.5
  # Controller time constant of the integrator block
  parameter Ti::Time = 0.1
  # Controller Time constant of the derivative block
  parameter Td::Time = 1e5
  # Maximum derivative gain
  parameter Nd::Real = 10
relations
  initial motor.L1.i = 0
  initial motor.inertia.w = 0
  u: analysis_point(controller.y, source.V)
  y: analysis_point(speed_sensor.w, controller.u_m)
  r: analysis_point(speed_reference.y, controller.u_s)
  connect(load_source.y, load.tau)
  connect(source.p, motor.p)
  connect(motor.n, source.n, ground.g)
  connect(motor.shaft, load.spline)
  connect(motor.housing, load.support, fixed.spline)
  connect(speed_reference.y, controller.u_s)
  connect(speed_sensor.w, controller.u_m)
  connect(controller.y, source.V)
  connect(controller.u_ff, signal_ff.y)
  connect(speed_sensor.spline, motor.shaft)
end

Let's explore the influence of the controller parameters on the resulting speed response. Here is the analysis run with a relatively low controller gain.

julia
result = DCMotorLoadControlledAnalysis(k=0.1)
model = result.spec.model
kwargs = (idxs = [model.motor.inertia.w, model.controller.y], size=(800, 500), layout=(2,1))
plot(result; kwargs...)

Now let's re-run the analysis with a higher controller gain. Observe the much better speed control response.

julia
plot(DCMotorLoadControlledAnalysis(k=0.5); kwargs...)

To compare the two simulations, we can use the following command (note the plot! syntax for the second simulation to reuse the existing plot).

julia
plot(DCMotorLoadControlledAnalysis(k=0.1); kwargs...)
plot!(DCMotorLoadControlledAnalysis(k=0.5); kwargs...)

Tuning the controller automatically

To tune the controller parameters, we can use the PID autotuning analysis:

dyad
analysis DCMotorTuningAnalysis
  extends DyadControlSystems.PIDAutotuningAnalysis(
    measurement = "y",
    control_input = "u",
    Ts = 0.001,
    duration = 0.1,
    Ms = 1.2,
    Mt = 1.2,
    Mks = 400,
    wl = 1,
    wu = 1e4,
    tol = 1e-8
  )
  model = TestDCMotorLoadControlled()
end
julia
asol  = DCMotorTuningAnalysis() # Run the tuning analysis
opt_p = artifacts(asol, :OptimizedParameters)
k, Ti, Td, Tf = [opt_p[!, par][] for par in [:Kp_standard, :Ti_standard, :Td_standard, :Tf]]
Nd = Td / Tf
plot(DCMotorLoadControlledAnalysis(k=0.5); kwargs..., title=permutedims(kwargs.idxs), lab="Initial")
plot!(DCMotorLoadControlledAnalysis(; k, Ti, Td, Nd); kwargs..., lab="Tuned")

To learn more about controls design features in Dyad, check out DyadControlSystems as well as the Dyad control analyses.

Conclusion

In this tutorial, we have shown how to use components from the Dyad standard library to build a simple DC motor and add a controller. We also illustrated how easy it is to vary parameters in the simulation via the analysis specification.