Phase Shifted PWM Signals

I'm looking to create 4 PWM signals that I am capable of individually controlling the phase difference between each signal. Along with this I would like to control the frequency and duty of each signal however they would be the same for each.

e.g. Each signal operates at 0.48 duty at 100khz, 0.25 of a duty shifted phase from the last.

Is this possible with a teensy 3.6? If so, how would I achieve this?

Love and gratitude,

Piper

P.S This is with the intent of controlling 8 MOSFETS through VO2631 opptocopler sand IR2110 Highside Lowside Drivers, in a Quasi Resonant Dual Active Bridge configuration
 
Last edited:
I believe the FTM timer in Teensy 3.6 could do this. But as far as I know there aren't any ready-to-use libraries to control the timer this way. You would need to dive deep into the hardware registers to program it this way. Should be possible, but it's a steep learning curve. The info you'll need is in chapter 45 of the MK66 reference manual, starting on page 1127.
 
Thanks for a timely response,

Would it be any simpler if I sacrificed control of the phase shift and kept it at 0.25 between the four signals?

P.S I should have mentioned, I don't need real time control, just to be able to set the frequency and duty before the experiment. As long as the four signals are eaqualy spaced I may just get away with it

P.P.S I may even be able to be flexible with the frequency as long as it's within the switching range of my FETs. As long as there is width modulation capability and 4 quarter phase shifted signals I can run a primary experiment.

Thanks so much
 
Last edited:
While I have not personally used this mode, I believe you'll need the "combine" feature. It allows 2 of the PWM channels to work together. Each channel has only a single compare register. You'll need a pair of them to be able to control where in the counter's cycle the waveform begins and where it end.

Combine mode is documented in section 45.5.8, starting at the bottom of page 1201. To get full control over the waveforms, you might want "Asymmetrical PWM" described in 45.5.8.1 on page 1209.

The main register controlling combine mode is documented on page 1160.
 
Using the PWM capabilites of the timers is of course the most elegant and precise method. If you just need a quick and dirty method you could use 2 IntervalTimers to generate the signal (fixed 0.25% phase shift). It is by no means perfect, the duty cycle gets imprecise for high frequencies but maybe it helps getting you going:

Code:
[size=3][color=#000000]IntervalTimer t1[/color][color=#000000],[/color] [color=#000000]t2[/color][color=#000000];[/color]
[color=#000000][/color]
[color=#000000][/color][color=#001177]constexpr[/color] [color=#000000][/color][color=#0000ff]uint8_t[/color] [color=#000000]pins[/color][color=#000000][[/color][color=#000000][/color][color=#a52a2a]4[/color][color=#000000][/color][color=#000000]] = {[/color][color=#000000][/color][color=#a52a2a]0[/color][color=#000000][/color][color=#000000],[/color] [color=#000000][/color][color=#a52a2a]1[/color][color=#000000][/color][color=#000000],[/color] [color=#000000][/color][color=#a52a2a]2[/color][color=#000000][/color][color=#000000],[/color] [color=#000000][/color][color=#a52a2a]3[/color][color=#000000][/color][color=#000000]};[/color]
[color=#000000][/color]
[color=#000000][/color][color=#0000ff]void[/color] [color=#000000][/color][color=#000000][b]switchOn[/b][/color][color=#000000][/color][color=#000000]()[/color]
[color=#000000][/color][color=#000000]{[/color]
[color=#000000][/color]    [color=#0000ff]static unsigned[/color] [color=#000000]current[/color] [color=#000000]=[/color] [color=#000000][/color][color=#a52a2a]0[/color][color=#000000][/color][color=#000000];[/color]
[color=#000000][/color]    [color=#000000][b]digitalWriteFast[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]pins[/color][color=#000000][[/color][color=#000000]current[/color][color=#000000]],[/color] [color=#000000]HIGH[/color][color=#000000]);[/color]
[color=#000000]    current[/color] [color=#000000]= ([/color][color=#000000]current[/color] [color=#000000]+[/color] [color=#000000][/color][color=#a52a2a]1[/color][color=#000000][/color][color=#000000]) &[/color] [color=#000000][/color][color=#a52a2a]0b11[/color][color=#000000][/color][color=#000000];[/color]
[color=#000000][/color][color=#000000]}[/color]
[color=#000000][/color]
[color=#000000][/color][color=#0000ff]void[/color] [color=#000000][/color][color=#000000][b]switchOff[/b][/color][color=#000000][/color][color=#000000]()[/color]
[color=#000000][/color][color=#000000]{[/color]
[color=#000000][/color]    [color=#0000ff]static unsigned[/color] [color=#000000]current[/color] [color=#000000]=[/color] [color=#000000][/color][color=#a52a2a]0[/color][color=#000000][/color][color=#000000];[/color]
[color=#000000][/color]    [color=#000000][b]digitalWriteFast[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]pins[/color][color=#000000][[/color][color=#000000]current[/color][color=#000000]],[/color] [color=#000000]LOW[/color][color=#000000]);[/color]
[color=#000000]    current[/color] [color=#000000]= ([/color][color=#000000]current[/color] [color=#000000]+[/color] [color=#000000][/color][color=#a52a2a]1[/color][color=#000000][/color][color=#000000]) &[/color] [color=#000000][/color][color=#a52a2a]0b11[/color][color=#000000][/color][color=#000000];[/color]
[color=#000000][/color][color=#000000]}[/color]
[color=#000000][/color]
[color=#000000][/color][color=#0b810d]// settings[/color]
[color=#000000][/color][color=#001177]constexpr[/color] [color=#000000][/color][color=#0000ff]float[/color] [color=#000000]f[/color]           [color=#000000]=[/color] [color=#000000][/color][color=#a52a2a]10'000[/color][color=#000000][/color][color=#000000];[/color]
[color=#000000][/color][color=#001177]constexpr[/color] [color=#000000][/color][color=#0000ff]float[/color] [color=#000000]duty[/color]        [color=#000000]=[/color] [color=#000000][/color][color=#a52a2a]0.2[/color][color=#000000][/color][color=#000000];[/color]
[color=#000000][/color][color=#001177]constexpr[/color] [color=#000000][/color][color=#0000ff]unsigned[/color] [color=#000000]period[/color]   [color=#000000]=[/color] [color=#000000][/color][color=#a52a2a]0.25[/color] [color=#000000][/color][color=#000000]*[/color] [color=#000000][/color][color=#a52a2a]1[/color][color=#000000]E6[/color] [color=#000000]/[/color] [color=#000000]f[/color][color=#000000];[/color]
[color=#000000][/color][color=#001177]constexpr[/color] [color=#000000][/color][color=#0000ff]unsigned[/color] [color=#000000]highTime[/color] [color=#000000]=[/color] [color=#000000][/color][color=#a52a2a]4000.0[/color] [color=#000000][/color][color=#000000]*[/color] [color=#000000]duty[/color] [color=#000000]*[/color] [color=#000000]period[/color][color=#000000];[/color]
[color=#000000][/color]
[color=#000000][/color][color=#0000ff]void[/color] [color=#000000][/color][color=#000000][b]setup[/b][/color][color=#000000][/color][color=#000000]()[/color]
[color=#000000][/color][color=#000000]{[/color]
[color=#000000][/color]    [color=#001177]for[/color] [color=#000000][/color][color=#000000]([/color][color=#000000][/color][color=#0000ff]uint8_t[/color] [color=#000000]pin[/color] [color=#000000]:[/color] [color=#000000]pins[/color][color=#000000])[/color] [color=#000000][/color][color=#000000][b]pinMode[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]pin[/color][color=#000000],[/color] [color=#000000]OUTPUT[/color][color=#000000]);[/color]
[color=#000000][/color]
[color=#000000]    t1[/color][color=#000000].[/color][color=#000000][/color][color=#000000][b]begin[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]switchOn[/color][color=#000000],[/color] [color=#000000]period[/color][color=#000000]);[/color]
[color=#000000][/color]    [color=#000000][b]delayNanoseconds[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]highTime[/color][color=#000000]);[/color]
[color=#000000]    t2[/color][color=#000000].[/color][color=#000000][/color][color=#000000][b]begin[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]switchOff[/color][color=#000000],[/color] [color=#000000]period[/color][color=#000000]);[/color]
[color=#000000][/color][color=#000000]}[/color]
[color=#000000][/color]
[color=#000000][/color][color=#0000ff]void[/color] [color=#000000][/color][color=#000000][b]loop[/b][/color][color=#000000][/color][color=#000000]()[/color]
[color=#000000][/color][color=#000000]{[/color]
[color=#000000][/color][color=#000000]}[/color][color=#000000][/color]
[/size]

Screenshot 2023-04-14 100244.jpg
 
Last edited:
Thanks for this. With this "quick and dirty" method it also seems like I may be able to shift the waveforms a little further (half the dead time either side) so they are centre aligned. Do you have any idea how it would perform in the KHz range?
 
I tested it with 100kHz (@256MHz) with which is kind of borderline. Execution times get into the same order of magnitude as the delays so the duty cycle calculation need to be corrected.
 
Heres my attempt at a centralising the waveforms. I don't have access to that scope that you've used, but what do you think? Would you kindly be able to test it with the equipment you used (which is or is not a teensy 3.6?)
code:
Code:
IntervalTimer t1, t2;

constexpr uint8_t pins[4] = {0, 1, 2, 3};

void switchOn()
{
    static unsigned current = 0;
    digitalWriteFast(pins[current], HIGH);
    current = (current + 1) & 0b11;
}

void switchOff()
{
    static unsigned current = 0;
    digitalWriteFast(pins[current], LOW);
    current = (current + 1) & 0b11;
}

// settings
constexpr float f           = 50'000;
constexpr float duty        = 0.9;
constexpr unsigned period   = 0.25 * 1E6 / f;
constexpr unsigned highTime = 4000.0 * duty * period;
constexpr unsigned lowTime = 4000.0 * (1 - duty) * period;
constexpr unsigned breakTime = lowTime / 2;

void setup()
{
    for (uint8_t pin : pins) pinMode(pin, OUTPUT);
    delayNanoseconds(breakTime)
    t1.begin(switchOn, period);
    delayNanoseconds(highTime);
    t2.begin(switchOff, period);
    delayNanoseconds(breakTime);

}

void loop()
{
}

Thanks for all the invaluable help
 
Your code does not compile (breakTime clashes with some function defined in the core) I renamed it to brkTime:

Code:
[size=3][color=#000000]IntervalTimer t1[/color][color=#000000],[/color] [color=#000000]t2[/color][color=#000000];[/color]
[color=#000000][/color]
[color=#000000][/color][color=#001177]constexpr[/color] [color=#000000][/color][color=#0000ff]uint8_t[/color] [color=#000000]pins[/color][color=#000000][[/color][color=#000000][/color][color=#a52a2a]4[/color][color=#000000][/color][color=#000000]] = {[/color][color=#000000][/color][color=#a52a2a]0[/color][color=#000000][/color][color=#000000],[/color] [color=#000000][/color][color=#a52a2a]1[/color][color=#000000][/color][color=#000000],[/color] [color=#000000][/color][color=#a52a2a]2[/color][color=#000000][/color][color=#000000],[/color] [color=#000000][/color][color=#a52a2a]3[/color][color=#000000][/color][color=#000000]};[/color]
[color=#000000][/color]
[color=#000000][/color][color=#0000ff]void[/color] [color=#000000][/color][color=#000000][b]switchOn[/b][/color][color=#000000][/color][color=#000000]()[/color]
[color=#000000][/color][color=#000000]{[/color]
[color=#000000][/color]    [color=#0000ff]static unsigned[/color] [color=#000000]current[/color] [color=#000000]=[/color] [color=#000000][/color][color=#a52a2a]0[/color][color=#000000][/color][color=#000000];[/color]
[color=#000000][/color]    [color=#000000][b]digitalWriteFast[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]pins[/color][color=#000000][[/color][color=#000000]current[/color][color=#000000]],[/color] [color=#000000]HIGH[/color][color=#000000]);[/color]
[color=#000000]    current[/color] [color=#000000]= ([/color][color=#000000]current[/color] [color=#000000]+[/color] [color=#000000][/color][color=#a52a2a]1[/color][color=#000000][/color][color=#000000]) &[/color] [color=#000000][/color][color=#a52a2a]0b11[/color][color=#000000][/color][color=#000000];[/color]
[color=#000000][/color][color=#000000]}[/color]
[color=#000000][/color]
[color=#000000][/color][color=#0000ff]void[/color] [color=#000000][/color][color=#000000][b]switchOff[/b][/color][color=#000000][/color][color=#000000]()[/color]
[color=#000000][/color][color=#000000]{[/color]
[color=#000000][/color]    [color=#0000ff]static unsigned[/color] [color=#000000]current[/color] [color=#000000]=[/color] [color=#000000][/color][color=#a52a2a]0[/color][color=#000000][/color][color=#000000];[/color]
[color=#000000][/color]    [color=#000000][b]digitalWriteFast[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]pins[/color][color=#000000][[/color][color=#000000]current[/color][color=#000000]],[/color] [color=#000000]LOW[/color][color=#000000]);[/color]
[color=#000000]    current[/color] [color=#000000]= ([/color][color=#000000]current[/color] [color=#000000]+[/color] [color=#000000][/color][color=#a52a2a]1[/color][color=#000000][/color][color=#000000]) &[/color] [color=#000000][/color][color=#a52a2a]0b11[/color][color=#000000][/color][color=#000000];[/color]
[color=#000000][/color][color=#000000]}[/color]
[color=#000000][/color]
[color=#000000][/color][color=#0b810d]// settings[/color]
[color=#000000][/color][color=#001177]constexpr[/color] [color=#000000][/color][color=#0000ff]float[/color] [color=#000000]f[/color]           [color=#000000]=[/color] [color=#000000][/color][color=#a52a2a]50'000[/color][color=#000000][/color][color=#000000];[/color]
[color=#000000][/color][color=#001177]constexpr[/color] [color=#000000][/color][color=#0000ff]float[/color] [color=#000000]duty[/color]        [color=#000000]=[/color] [color=#000000][/color][color=#a52a2a]0.9[/color][color=#000000][/color][color=#000000];[/color]
[color=#000000][/color][color=#001177]constexpr[/color] [color=#000000][/color][color=#0000ff]unsigned[/color] [color=#000000]period[/color]   [color=#000000]=[/color] [color=#000000][/color][color=#a52a2a]0.25[/color] [color=#000000][/color][color=#000000]*[/color] [color=#000000][/color][color=#a52a2a]1[/color][color=#000000]E6[/color] [color=#000000]/[/color] [color=#000000]f[/color][color=#000000];[/color]
[color=#000000][/color][color=#001177]constexpr[/color] [color=#000000][/color][color=#0000ff]unsigned[/color] [color=#000000]highTime[/color] [color=#000000]=[/color] [color=#000000][/color][color=#a52a2a]4000.0[/color] [color=#000000][/color][color=#000000]*[/color] [color=#000000]duty[/color] [color=#000000]*[/color] [color=#000000]period[/color][color=#000000];[/color]
[color=#000000][/color][color=#001177]constexpr[/color] [color=#000000][/color][color=#0000ff]unsigned[/color] [color=#000000]lowTime[/color]  [color=#000000]=[/color] [color=#000000][/color][color=#a52a2a]4000.0[/color] [color=#000000][/color][color=#000000]* ([/color][color=#000000][/color][color=#a52a2a]1[/color] [color=#000000][/color][color=#000000]-[/color] [color=#000000]duty[/color][color=#000000]) *[/color] [color=#000000]period[/color][color=#000000];[/color]
[color=#000000][/color][color=#001177]constexpr[/color] [color=#000000][/color][color=#0000ff]unsigned[/color] [color=#000000]brkTime[/color]  [color=#000000]=[/color] [color=#000000]lowTime[/color] [color=#000000]/[/color] [color=#000000][/color][color=#a52a2a]2[/color][color=#000000][/color][color=#000000];[/color]
[color=#000000][/color]
[color=#000000][/color][color=#0000ff]void[/color] [color=#000000][/color][color=#000000][b]setup[/b][/color][color=#000000][/color][color=#000000]()[/color]
[color=#000000][/color][color=#000000]{[/color]
[color=#000000][/color]    [color=#001177]for[/color] [color=#000000][/color][color=#000000]([/color][color=#000000][/color][color=#0000ff]uint8_t[/color] [color=#000000]pin[/color] [color=#000000]:[/color] [color=#000000]pins[/color][color=#000000])[/color] [color=#000000][/color][color=#000000][b]pinMode[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]pin[/color][color=#000000],[/color] [color=#000000]OUTPUT[/color][color=#000000]);[/color]
[color=#000000][/color]    [color=#000000][b]delayNanoseconds[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]brkTime[/color][color=#000000]);[/color]  [color=#000000][/color][color=#0b810d]// the only effect this has is that the sketch starts a few microseconds later[/color]
[color=#000000]    t1[/color][color=#000000].[/color][color=#000000][/color][color=#000000][b]begin[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]switchOn[/color][color=#000000],[/color] [color=#000000]period[/color][color=#000000]);[/color]
[color=#000000][/color]    [color=#000000][b]delayNanoseconds[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]highTime[/color][color=#000000]);[/color]
[color=#000000]    t2[/color][color=#000000].[/color][color=#000000][/color][color=#000000][b]begin[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]switchOff[/color][color=#000000],[/color] [color=#000000]period[/color][color=#000000]);[/color]
[color=#000000][/color]    [color=#000000][b]delayNanoseconds[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]brkTime[/color][color=#000000]);[/color] [color=#000000][/color][color=#0b810d]// the only effect this has is, that loop starts a few microseconds later[/color]
[color=#000000][/color][color=#000000]}[/color]
[color=#000000][/color]
[color=#000000][/color][color=#0000ff]void[/color] [color=#000000][/color][color=#000000][b]loop[/b][/color][color=#000000][/color][color=#000000]()[/color]
[color=#000000][/color][color=#000000]{[/color]
[color=#000000][/color][color=#000000]}[/color][color=#000000][/color]
[/size]

However, the changes you made have no effect at all (see comments in the code)

Here the result:
Screenshot 2023-04-14 194833.jpg

I don't quite understand what you want to achieve? Can you sketch the waveform you would like to have? A photographed hand drawing should be good enough.
 
I think I see my mistake, try this:
Code:
IntervalTimer t1, t2;

constexpr uint8_t pins[4] = {0, 1, 2, 3};

void switchOn()
{
    static unsigned current = 0;
    digitalWriteFast(pins[current], HIGH);
    current = (current + 1) & 0b11;
}

void switchOff()
{
    static unsigned current = 0;
    digitalWriteFast(pins[current], LOW);
    current = (current + 1) & 0b11;
}

// settings
constexpr float f           = 50'000;
constexpr float duty        = 0.48;
constexpr unsigned period   = 0.25 * 1E6 / f;
constexpr unsigned highTime = 4000.0 * duty * period;
constexpr unsigned lowTime  = 4000.0 * (1 - duty) * period;
constexpr unsigned brkTime  = lowTime / 2;

void setup()
{
    for (uint8_t pin : pins) pinMode(pin, OUTPUT);
    delayNanoseconds(brkTime);  // the only effect this has is that the sketch starts a few microseconds later
    t1.begin(switchOn, period);
    delayNanoseconds(highTime);
    t2.begin(switchOff, period);
    delayNanoseconds(brkTime); // the only effect this has is, that loop starts a few microseconds later
}

void loop()
{
}

Here's what I'm after (I've swapped signals 2 and 3 around)

Photo on 14-04-2023 at 23.47.jpg

The loop starting a few nanoseconds later might just be the bootleg solution I'm after
 
Heres my attempt at a centralising the waveforms.
Why do you think the waveform is not centralized already? If you imagine vertical lines through the centers on both LA pictures above you will see exactly what you have drawn? (of course after swapping 2/3)

Starting loop later or delay before generating the signal train won't change anything. Delay before the begin functions is equivalent to start the whole sketch a few µs later. I.e. plug it in later or upload the program a few microseconds later. Delaying after the begin functions has no effect at all since the signals are generated in the background by timers. They are in no way synced to loop.

Screenshot 2023-04-18 072157.jpg
 
Back
Top