PWM using Discrete Callbacks in Modeling Toolkit

I’m trying to create a PWM voltage source using modeling toolkit. I tried used the @discrete_events macro within the @mtkmodel block but doing so throws an error: ERROR: KeyError: key pwm₊output₊u(t) not found when running prob = ODEProblem(sys, Pair[], (0, 1.0e-3))

Here is the full code I’m trying to run:

using ModelingToolkit, OrdinaryDiffEq, Plots
using ModelingToolkitStandardLibrary.Electrical
using ModelingToolkitStandardLibrary.Blocks
using ModelingToolkit: t_nounits as t, D_nounits as D


@mtkmodel PWM begin
    # @extend v, i = oneport = OnePort()
    @parameters begin
        T = 0.2e-3
        duty = 0.5
        Vcc = 5
    end
    @components begin
        output = RealOutput()
    end
    @equations begin
        output.u ~ Vcc
    end
    @discrete_events begin
        (t == T*duty) => [output.u ~ 0]
        (t == T) => [output.u ~ Vcc]
    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, Pair[], (0, 1.0e-3))
sol = solve(prob)

plot(sol, idxs = [sys.resistor.i],
    title = "Circuit Demonstration")

Any ideas on what is going wrong here?

You should replace

    @equations begin
        output.u ~ Vcc
    end

with

    @equations begin
        D(output.u) ~ 0
    end

As the callback should handle the changing output.u value. Otherwise this equation would immediately pull the voltage back.
And you should use a continous callback. Full example:

using ModelingToolkit, OrdinaryDiffEq, Plots
using ModelingToolkitStandardLibrary.Electrical
using ModelingToolkitStandardLibrary.Blocks
using ModelingToolkit: t_nounits as t, D_nounits as D


@mtkmodel PWM begin
    # @extend v, i = oneport = OnePort()
    @parameters begin
        T = 0.1
        duty = 0.5
        Vcc = 5
    end
    @components begin
        output = RealOutput(u_start=Vcc)
    end
    @equations begin
        D(output.u) ~ 0
    end
    @continuous_events begin
        (t ~ T*duty) => [output.u ~ 0]
        (t ~ T) => [output.u ~ Vcc]
    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, 1.0); dt=0.001)
sol = solve(prob, Tsit5())

plot(sol, idxs = [sys.resistor.i],
    title = "Circuit Demonstration")

Thanks for the quick reply!

This does solve my problem, but I quickly run into another; I cannot make these steps periodic. From the docs it seems periodic callbacks are only available using @discrete_events

My end goal is to make a PWM source with all the knobs to turn available in LTSpice.

  • V_initial
  • V_on
  • T_rise
  • T_fall
  • T_on
  • T_period

This supply would oscillate into t = infinity

With that in mind, what is the recommended approach?

You could maybe use a modulo block, where the remainder of the time divided by T_rise is your trigger for turning on the pwm, and the remainder of the time divided by T_fall is the trigger for going down again. Or you can make a trigger by having a variable called trigger and equation D(trigger) ~ 1, and when its value reaches T_rise, you set it back to zero with a continuous callback and trigger the PWM.