Hi twarczi, I am also new to ModelingToolkit but I found your problem interesting.
I played with using continous callbacks with a slope varible as well like D(output.u) ~ slope
. That way I was able to make the rising and falling sections of the pulse. But It keep getting MaxIters
warnings and stopping. I think what might of happened is it was sort of overdefined, like the slope set to non zero and with the output still defined as a constant and it would conflict, maybe just at the discontinuity.
I assume there is some reason you are not using an external julia function to generate the PWM (and avoiding callbacks alltogether). Do you want a PWM mtkmodel to allow control of the block by other blocks? I think that is still possible by using callbacks to update the parameters going to the external function, here is my idea:
using ModelingToolkit, OrdinaryDiffEq, Plots
using ModelingToolkitStandardLibrary.Electrical
using ModelingToolkitStandardLibrary.Blocks
using ModelingToolkit: t_nounits as t, D_nounits as D
function PWM_fun(t, T, duty, Vcc)
t_off = duty*T
t_cycle = t % T
if(t_cycle < t_off)
return Vcc
else
return 0
end
end
@register_symbolic PWM_fun(t, T, duty, Vcc)
@mtkmodel PWM begin
@parameters begin
#RC rise tim
t_rise_10_90=0.1
# 10 to 90 rise time is about 2.2 τ
τ = t_rise_10_90/2.2
Vcc= 5
T = 1
duty=0.3
end
@components begin
output = RealOutput(u_start=Vcc)
end
@variables begin
V(t)
end
@equations begin
V ~ PWM_fun(t, T, duty, Vcc)
D(output.u) ~ (V - output.u) / τ
end
@continuous_events begin
# we can change the parameters of the
# external PWM function on the fly
[t ~ 3] => [duty ~ 0.8]
[t ~ 6] => [duty ~ 0.2]
end
end
@mtkmodel PWM_test begin
@parameters begin
R = 1.0
V = 5.0
end
@components begin
resistor = Resistor(R = R)
VDD = Voltage()
pwm = PWM(Vcc = V)
ground = Ground()
end
@equations begin
connect(pwm.output, VDD.V)
connect(VDD.p, resistor.p)
connect(ground.g, VDD.n, resistor.n)
end
end
@mtkbuild sys = PWM_test()
unknowns(sys)
prob = ODEProblem(sys, [sys.pwm.output.u => 0.0], (0, 10.0))
sol = solve(prob, Tsit5())
plot(sol, idxs = [sys.resistor.i],
xticks=0:1:10,
title = "Circuit Demonstration",
minorgrid=true)
I used an external function to make the raw square wave PWM and then a single order lag / low pass filter to allow you to set the rise/fall time in a more realistic way.
I also built an external function to immatate the exact LTspice style “trapezoid” pulse.
function PWM_fun(t, T, duty, t_rise, t_fall, Vcc)
rise_slope = Vcc/t_rise
fall_slope = -Vcc/t_fall
t_off = duty*T
t_cycle = t % T
if(t_cycle < t_rise)
return t_cycle*rise_slope
elseif(t_cycle < t_off)
return Vcc
elseif(t_cycle < t_off + t_fall)
return Vcc + (t_cycle - t_off)*fall_slope
else
return 0
end
end
I was able to plug this in to the above example and use a fast rise time so that I only had a small smoothing of the corners of the pulse.
I feel like you should be able to just use the external functon to define the output directly without low a pass filter but I can’t seem to get that to work. Intuitively I guess that makes sense, it’s like I am trying to force a system to a certain state but it doesn’t actually have any degrees of freedom to allow it to reach that state, as if it is infinitly stiff. It seems like the low pass filter gives it enough slop to allow it to figure itself out.