# PWM using Discrete Callbacks in Modeling Toolkit

**URL:** <https://discourse.julialang.org/t/pwm-using-discrete-callbacks-in-modeling-toolkit/127569>\
**Category:** Modelling & Simulations\
**Tags:** question, modelingtoolkit, circuits, simulations\
**Created:** [March 31, 2025, 7:13pm UTC](https://discourse.julialang.org/t/pwm-using-discrete-callbacks-in-modeling-toolkit/127569 "2025-03-31T19:13:23Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![twarczi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/twarczi/32/216100_2.png) [@twarczi](https://discourse.julialang.org/u/twarczi)\
**Post date:** [March 31, 2025, 7:13pm UTC](https://discourse.julialang.org/t/pwm-using-discrete-callbacks-in-modeling-toolkit/127569/1 "2025-03-31T19:13:23Z")

</div>

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:

```julia
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?

---

<div class="post-metadata">

**Author:** ![Bart\_van\_de\_Lint](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bart_van_de_lint/32/212161_2.png) [@Bart\_van\_de\_Lint](https://discourse.julialang.org/u/Bart_van_de_Lint)\
**Post date:** [March 31, 2025, 11:43pm UTC](https://discourse.julialang.org/t/pwm-using-discrete-callbacks-in-modeling-toolkit/127569/2 "2025-03-31T23:43:57Z")

</div>

You should replace

```julia
    @equations begin
        output.u ~ Vcc
    end

```

with

```julia
    @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:

```julia
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")

```

---

<div class="post-metadata">

**Author:** ![twarczi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/twarczi/32/216100_2.png) [@twarczi](https://discourse.julialang.org/u/twarczi)\
**Post date:** [April 1, 2025, 1:17pm UTC](https://discourse.julialang.org/t/pwm-using-discrete-callbacks-in-modeling-toolkit/127569/3 "2025-04-01T13:17:43Z")

</div>

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?

---

<div class="post-metadata">

**Author:** ![Bart\_van\_de\_Lint](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bart_van_de_lint/32/212161_2.png) [@Bart\_van\_de\_Lint](https://discourse.julialang.org/u/Bart_van_de_Lint)\
**Post date:** [April 1, 2025, 2:30pm UTC](https://discourse.julialang.org/t/pwm-using-discrete-callbacks-in-modeling-toolkit/127569/4 "2025-04-01T14:30:01Z")

</div>

> **[Basic Blocks · ModelingToolkitStandardLibrary.jl](https://docs.sciml.ai/ModelingToolkitStandardLibrary/stable/API/blocks/)**
>
> Documentation for ModelingToolkitStandardLibrary.jl.

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.

---

<div class="post-metadata">

**Author:** ![luke-pikaart](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/luke-pikaart/32/29361_2.png) [@luke-pikaart](https://discourse.julialang.org/u/luke-pikaart)\
**Post date:** [April 19, 2025, 10:51pm UTC](https://discourse.julialang.org/t/pwm-using-discrete-callbacks-in-modeling-toolkit/127569/5 "2025-04-19T22:51:46Z")

</div>

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:

```julia
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.

 ![PWM](https://global.discourse-cdn.com/julialang/original/3X/7/0/70fb0aca9100f30a303d16ef1bcace7353421667.png)

I also built an external function to immatate the exact LTspice style “trapezoid” pulse.

```julia
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.

---

<div class="post-metadata">

**Author:** ![Bart\_van\_de\_Lint](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bart_van_de_lint/32/212161_2.png) [@Bart\_van\_de\_Lint](https://discourse.julialang.org/u/Bart_van_de_Lint)\
**Post date:** [April 20, 2025, 9:46am UTC](https://discourse.julialang.org/t/pwm-using-discrete-callbacks-in-modeling-toolkit/127569/6 "2025-04-20T09:46:23Z")

</div>

> [@luke-pikaart](#):
>
> But It keep getting `MaxIters` warnings and stopping.

Have you tried using a stiff solver like `FBDF()`? In the example you are using `Tsit5()`, which isn’t necessarily good for stiff problems.
