Skip to content

Commit 4d56fe5

Browse files
author
Brad Carman
committed
Merge branch 'bgc/multi2d' into bgc/revolute
2 parents dad8645 + 516f3ab commit 4d56fe5

File tree

15 files changed

+405
-31
lines changed

15 files changed

+405
-31
lines changed

docs/pages.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pages = [
44
"RC Circuit" => "tutorials/rc_circuit.md",
55
"Custom Components" => "tutorials/custom_component.md",
66
"Thermal Conduction Model" => "tutorials/thermal_model.md",
7+
"DC Motor with Speed Controller" => "tutorials/dc_motor_pi.md",
78
],
89
"About Acausal Connections" => "connectors/connections.md",
910
"API" => [

docs/src/API/linear_analysis.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ L = P*(-C) # Add the minus sign to build the negative feedback into the controll
7979

8080
To obtain the transfer function between two analysis points, we call `linearize`
8181
```@example LINEAR_ANALYSIS
82+
using ModelingToolkit # hide
8283
matrices_PS = linearize(sys, :plant_input, :plant_output)[1]
8384
```
8485
this particular transfer function should be equivalent to the linear system `P(s)S(s)`, i.e., equivalent to

docs/src/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import Pkg; Pkg.add("ModelingToolkitStandardLibrary")
1717
- [RC Circuit](http://mtkstdlib.sciml.ai/dev/tutorials/rc_circuit/)
1818
- [Custom Component](http://mtkstdlib.sciml.ai/dev/tutorials/custom_component/)
1919
- [Thermal Model](http://mtkstdlib.sciml.ai/dev/tutorials/thermal_model/)
20+
- [DC Motor with PI-controller](http://mtkstdlib.sciml.ai/dev/tutorials/dc_motor_pi/)
2021

2122
## Libraries
2223

docs/src/tutorials/dc_motor_pi.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# DC Motor with PI-controller
2+
In this example a PI-controller is setup for speed control of a DC-motor. First the needed packages
3+
are imported and the parameters of the model defined.
4+
5+
```@example dc_motor_pi
6+
using ModelingToolkit
7+
using ModelingToolkitStandardLibrary.Electrical
8+
using ModelingToolkitStandardLibrary.Mechanical.Rotational
9+
using ModelingToolkitStandardLibrary.Blocks
10+
using OrdinaryDiffEq
11+
using Plots
12+
13+
@parameters t
14+
15+
R = 0.5 # [Ohm]
16+
L = 4.5e-3 # [H]
17+
k = 0.5 # [N.m/A]
18+
J = 0.02 # [kg.m²]
19+
f = 0.01 # [N.m.s/rad]
20+
tau_L_step = -3 # [N.m]
21+
nothing # hide
22+
```
23+
24+
The actual model can now be composed.
25+
26+
```@example dc_motor_pi
27+
@named ground = Ground()
28+
@named source = Voltage()
29+
@named ref = Blocks.Step(height = 1, start_time = 0)
30+
@named pi_controller = Blocks.LimPI(k = 1.1, T = 0.035, u_max = 0.6, Ta = 0.035)
31+
@named feedback = Blocks.Feedback()
32+
@named R1 = Resistor(R = R)
33+
@named L1 = Inductor(L = L)
34+
@named emf = EMF(k = k)
35+
@named fixed = Fixed()
36+
@named load = Torque(use_support = false)
37+
@named load_step = Blocks.Step(height = tau_L_step, start_time = 3)
38+
@named inertia = Inertia(J = J)
39+
@named friction = Damper(d = f)
40+
@named speed_sensor = SpeedSensor()
41+
42+
connections = [connect(fixed.flange, emf.support, friction.flange_b)
43+
connect(emf.flange, friction.flange_a, inertia.flange_a)
44+
connect(inertia.flange_b, load.flange)
45+
connect(inertia.flange_b, speed_sensor.flange)
46+
connect(load_step.output, load.tau)
47+
connect(ref.output, feedback.input1)
48+
connect(speed_sensor.w, feedback.input2)
49+
connect(feedback.output, pi_controller.err_input)
50+
connect(pi_controller.ctr_output, source.V)
51+
connect(source.p, R1.p)
52+
connect(R1.n, L1.p)
53+
connect(L1.n, emf.p)
54+
connect(emf.n, source.n, ground.g)]
55+
56+
@named model = ODESystem(connections, t,
57+
systems = [
58+
ground,
59+
ref,
60+
pi_controller,
61+
feedback,
62+
source,
63+
R1,
64+
L1,
65+
emf,
66+
fixed,
67+
load,
68+
load_step,
69+
inertia,
70+
friction,
71+
speed_sensor,
72+
])
73+
nothing # hide
74+
```
75+
76+
Now the model can be simulated. Typical rotational mechanical systems are described via `DAE`
77+
(differential algebraic equations), however in this case ModelingToolkit can simplify the model enough
78+
so that it can be represented as a system of `ODEs` (ordinary differential equations).
79+
80+
```@example dc_motor_pi
81+
sys = structural_simplify(model)
82+
prob = ODEProblem(sys, [], (0, 6.0))
83+
sol = solve(prob, Rodas4())
84+
85+
p1 = Plots.plot(sol.t, sol[inertia.w], ylabel = "Angular Vel. in rad/s",
86+
label = "Measurement", title = "DC Motor with Speed Controller")
87+
Plots.plot!(sol.t, sol[ref.output.u], label = "Reference")
88+
p2 = Plots.plot(sol.t, sol[load.tau.u], ylabel = "Disturbance in Nm", label = "")
89+
Plots.plot(p1, p2, layout = (2, 1))
90+
nothing # hide
91+
```
92+
93+
![DC Motor with speed controller](dc_motor_pi.png)

links.mp4

-253 KB
Binary file not shown.

src/Electrical/Analog/ideal_components.jl

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ See [OnePort](@ref)
2626
- `n` Negative pin
2727
2828
# Parameters:
29-
- `R`: [`Ω`] Resistance
29+
- `R`: [`Ohm`] Resistance
3030
"""
3131
function Resistor(; name, R)
3232
@named oneport = OnePort()
@@ -70,8 +70,7 @@ end
7070
Creates an ideal capacitor.
7171
7272
# States:
73-
- `v(t)`: [`V`]
74-
The voltage across the capacitor, given by `D(v) ~ p.i / C`
73+
- `v(t)`: [`V`] The voltage across the capacitor, given by `D(v) ~ p.i / C`
7574
7675
# Connectors:
7776
- `p` Positive pin
@@ -160,3 +159,73 @@ function Short(; name)
160159
eqs = [v ~ 0]
161160
extend(ODESystem(eqs, t, [], []; name = name), oneport)
162161
end
162+
163+
"""
164+
HeatingResistor(;name, R_ref=1.0, T_ref=300.15, alpha=0)
165+
166+
Temperature dependent electrical resistor
167+
168+
# States
169+
- See [OnePort](@ref)
170+
- `R(t)`: [`Ohm`] Temperature dependent resistance `R ~ R_ref*(1 + alpha*(heat_port.T(t) - T_ref))`
171+
172+
# Connectors
173+
- `p` Positive pin
174+
- `n` Negative pin
175+
176+
# Parameters:
177+
- `R_ref`: [`Ω`] Reference resistance
178+
- `T_ref`: [K] Reference temperature
179+
"""
180+
function HeatingResistor(; name, R_ref = 1.0, T_ref = 300.15, alpha = 0)
181+
@named oneport = OnePort()
182+
@unpack v, i = oneport
183+
@named heat_port = HeatPort()
184+
pars = @parameters begin
185+
R_ref = R_ref
186+
T_ref = T_ref
187+
alpha = alpha
188+
end
189+
@variables R(t) = R_ref
190+
eqs = [R ~ R_ref * (1 + alpha * (heat_port.T - T_ref))
191+
heat_port.Q_flow ~ -v * i # -LossPower
192+
v ~ i * R]
193+
extend(ODESystem(eqs, t, [R], pars; name = name, systems = [heat_port]), oneport)
194+
end
195+
196+
"""
197+
EMF(;name, k)
198+
199+
Electromotoric force (electric/mechanic transformer)
200+
201+
# States
202+
- `v(t)`: [`V`] The voltage across component `p.v - n.v`
203+
- `i(t)`: [`A`] The current passing through positive pin
204+
- `phi`: [`rad`] Rotation angle (=flange.phi - support.phi)
205+
- `w`: [`rad/s`] Angular velocity (= der(phi))
206+
207+
# Connectors
208+
- `p` [Pin](@ref) Positive pin
209+
- `n` [Pin](@ref) Negative pin
210+
- `flange` [Flange](@ref) Shaft of EMF shaft
211+
- `support` [Support](@ref) Support/housing of emf shaft
212+
213+
# Parameters:
214+
- `k`: [`N⋅m/A`] Transformation coefficient
215+
"""
216+
function EMF(; name, k)
217+
@named p = Pin()
218+
@named n = Pin()
219+
@named flange = Flange()
220+
@named support = Support()
221+
@parameters k = k
222+
@variables v(t)=0.0 i(t)=0.0 phi(t)=0.0 w(t)=0.0
223+
eqs = [v ~ p.v - n.v
224+
0 ~ p.i + n.i
225+
i ~ p.i
226+
phi ~ flange.phi - support.phi
227+
D(phi) ~ w
228+
k * w ~ v
229+
flange.tau ~ -k * i]
230+
ODESystem(eqs, t, [v, i, phi, w], [k]; name = name, systems = [p, n, flange, support])
231+
end

src/Electrical/Electrical.jl

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,26 @@ module Electrical
66

77
using ModelingToolkit, Symbolics, IfElse
88
using OffsetArrays
9+
using ..Thermal: HeatPort
10+
using ..Mechanical.Rotational: Flange, Support
11+
using ..Blocks: RealInput, RealOutput
912

1013
@parameters t
1114
D = Differential(t)
1215

13-
using ..Blocks: RealInput, RealOutput
14-
16+
export Pin, OnePort
1517
include("utils.jl")
18+
19+
export Capacitor, Ground, Inductor, Resistor, Conductor, Short, IdealOpAmp, EMF,
20+
HeatingResistor
1621
include("Analog/ideal_components.jl")
22+
23+
export CurrentSensor, PotentialSensor, VoltageSensor, PowerSensor, MultiSensor
1724
include("Analog/sensors.jl")
25+
26+
export Voltage, Current
1827
include("Analog/sources.jl")
28+
1929
# include("Digital/components.jl")
2030
# include("Digital/gates.jl")
2131
# include("Digital/tables.jl")
@@ -26,22 +36,4 @@ include("Analog/sources.jl")
2636
# - machines
2737
# - multi-phase
2838

29-
export #Interface
30-
Pin,
31-
# Analog Components
32-
Capacitor, Ground, Inductor, Resistor, Conductor,
33-
Short, IdealOpAmp,
34-
# Analog Sensors
35-
CurrentSensor, PotentialSensor, VoltageSensor,
36-
PowerSensor, MultiSensor,
37-
# Analog Sources
38-
Voltage, Current
39-
40-
# # Digital Gates
41-
# And, Or, Not, Xor, Nand, Nor, Xnor,
42-
# # Digital components
43-
# HalfAdder, FullAdder, MUX, DEMUX, Encoder, Decoder,
44-
# # Digital Sources
45-
# DigitalPin, Pulse, PulseDiff
46-
4739
end

src/Mechanical/Rotational/components.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ end
3939
function Inertia(; name, J, phi_start = 0.0, w_start = 0.0, a_start = 0.0)
4040
@named flange_a = Flange()
4141
@named flange_b = Flange()
42+
J > 0 || throw(ArgumentError("Expected `J` to be positive"))
4243
@parameters J = J
4344
sts = @variables begin
4445
phi(t) = phi_start
@@ -73,6 +74,7 @@ Linear 1D rotational spring
7374
function Spring(; name, c, phi_rel0 = 0.0)
7475
@named partial_comp = PartialCompliant()
7576
@unpack phi_rel, tau = partial_comp
77+
c > 0 || throw(ArgumentError("Expected `c` to be positive"))
7678
pars = @parameters begin
7779
c = c
7880
phi_rel0 = phi_rel0
@@ -102,6 +104,7 @@ Linear 1D rotational damper
102104
function Damper(; name, d)
103105
@named partial_comp = PartialCompliantWithRelativeStates()
104106
@unpack w_rel, tau = partial_comp
107+
d > 0 || throw(ArgumentError("Expected `d` to be positive"))
105108
pars = @parameters d = d
106109
eqs = [tau ~ d * w_rel]
107110
extend(ODESystem(eqs, t, [], pars; name = name), partial_comp)
@@ -130,6 +133,7 @@ This element characterizes any type of gear box which is fixed in the ground and
130133
function IdealGear(; name, ratio, use_support = false)
131134
@named partial_element = PartialElementaryTwoFlangesAndSupport2(use_support = use_support)
132135
@unpack phi_support, flange_a, flange_b = partial_element
136+
ratio > 0 || throw(ArgumentError("Expected `ratio` to be positive"))
133137
@parameters ratio = ratio
134138
sts = @variables phi_a(t)=0.0 phi_b(t)=0.0
135139
eqs = [phi_a ~ flange_a.phi - phi_support

src/Mechanical/Rotational/sources.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Torque(;name)
2+
Torque(; name, use_support=false)
33
44
Input signal acting as external torque on a flange
55

src/Mechanical/Rotational/utils.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ Partial model for a component with one rotational 1-dim. shaft flange and a supp
118118
function PartialElementaryOneFlangeAndSupport2(; name, use_support = false)
119119
@named flange = Flange()
120120
sys = [flange]
121-
@variables phi_support(t)
121+
@variables phi_support(t) = 0.0
122122
if use_support
123123
@named support = Support()
124124
eqs = [support.phi ~ phi_support

src/ModelingToolkitStandardLibrary.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ module ModelingToolkitStandardLibrary
22

33
include("Blocks/Blocks.jl")
44
include("Mechanical/Mechanical.jl")
5+
include("Thermal/Thermal.jl")
56
include("Electrical/Electrical.jl")
67
include("Magnetic/Magnetic.jl")
7-
include("Thermal/Thermal.jl")
88

99
end

test/Blocks/sources.jl

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ end
102102
sys = structural_simplify(iosys)
103103
prob = ODEProblem(sys, Pair[int.x => 0.0], (0.0, 10.0))
104104
sol = solve(prob, Rodas4())
105-
106105
@test sol.retcode == :Success
107106
@test sol[src.output.u]cosine.(sol.t, frequency, amplitude, phase, offset, start_time) atol=1e-3
108107

@@ -169,7 +168,6 @@ end
169168
sys = structural_simplify(iosys)
170169
prob = ODEProblem(sys, Pair[int.x => 0.0], (0.0, 10.0))
171170
sol = solve(prob, Rodas4())
172-
173171
@test sol.retcode == :Success
174172
@test sol[src.output.u]ramp.(sol.t, offset, height, duration, start_time) atol=1e-3
175173

@@ -365,7 +363,6 @@ end
365363
sys = structural_simplify(iosys)
366364
prob = ODEProblem(sys, Pair[int.x => 0.0], (0.0, 10.0))
367365
sol = solve(prob, Rodas4())
368-
369366
@test sol.retcode == :Success
370367
@test sol[src.output.u]exp_sine.(sol.t, amplitude, frequency, damping, phase,
371368
start_time) atol=1e-3

test/Electrical/analog.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,10 @@ end
157157
systems = [resistor, capacitor, source, ground, voltage])
158158
sys = structural_simplify(model)
159159
prob = ODAEProblem(sys, [capacitor.v => 10.0], (0.0, 10.0))
160-
sol = solve(prob, Rodas5())
161-
@test sol.retcode == :Success
162160
sol = solve(prob, Tsit5())
163161
@test sol.retcode == :Success
162+
sol = solve(prob, Rodas4())
163+
@test sol.retcode == :Success
164164

165165
# Plots.plot(sol; vars=[voltage.v, capacitor.v])
166166
end

0 commit comments

Comments
 (0)