Generating antiphase PWM signals with the dsPIC30F4011

I frequently receive queries from people who are using a dsPIC microcontroller to control power electronics of some kind, such as in an inverter, a voltage converter, or similar. Many of these queries relate to the generation of different combinations of pulse-width modulation (PWM) signals. In this article, I describe a simple example application in which the dsPIC30F4011 is used to generate two antiphase PWM signals with duty cycle varying between 10% and 45%. By antiphase, I mean that the two signals are 180 degrees (π radians) out of phase with each other. Apart from that, the two signals are identical.

I developed this example in response to a query from gunz which was posted as a comment on another article that I published on this blog a couple of years ago. In that article, I also generated antiphase PWM signals, but I used two of the dsPIC30F4011’s three PWM channels. Specifically, the signals were generated on pins PWM1H and PWM2L. Gunz asked how to generate the signals on the high and low pins of a single PWM channel (e.g. PWM1H and PWM1L), so that’s what I’m demonstrating in this example.

The signal specification is:

  • Two identical PWM signals 180 degrees out of phase.
  • The PWM frequency is 15kHz.
  • The duty cycle varies between 10% and 45% (hence the pulses on the two outputs never overlap).
  • The output pins are PWM1H and PWM1L.

The basic code is as follows:

//
// This dsPIC30F4011 example program generates PWM signals
// on PWM1H and PWM1L. The signals are identical except that
// they are 180 degrees out of phase. Duty cycle can vary
// between 0.1 and 0.45. PWM frequency is constant at 15kHz.
//
// The basic idea is to configure the PWM frequency to double
// what we want (30 kHz), but then only generate a pulse on
// each output every second period. We use the PWM interrupt
// (which is triggered every time the PWM timebase reaches
// the value PTPER and goes back to zero) to switch back and
// forth between PWM1H and PWM1L. i.e. The two pins take it
// in turns to output pulses.
//
// Written by Ted Burke - last updated 11-4-2015
//

#include <xc.h>
#include <libpic30.h>

// Configuration settings
_FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, Fcy=30MHz
_FWDT(WDT_OFF);                  // Watchdog timer off
_FBORPOR(MCLR_DIS);              // Disable reset pin

//
// The PWM interrupt is triggered each time PTMR reaches the same
// value as PTPER and resets to zero. We use this ISR to switch back
// and forth between outputting a pulse on PWM1H and outputting a pulse
// on PWM1L. During each PTMR period, we override one or other pin to
// lock it at 0, suppressing its pulse. Because the OSYNC bit is set,
// PWM override changes do not take effect until the next time PTMR
// resets to zero. Updates therefore don't take effect until the start
// of the the next cycle.
//
void __attribute__((interrupt, auto_psv)) _PWMInterrupt(void)
{
    // Reset PWM interrupt flag
    _PWMIF = 0;
    
    // Alternate between overriding PWM1L and overriding PWM1H.
    // Whichever pin is overridden will be driven low during
    // the next PWM period, suppressing its output pulse.
    if (_POVD1H)
    {
        _POVD1H = 0;
        _POVD1L = 1;
    }
    else
    {
        _POVD1L = 0;
        _POVD1H = 1;
    }
}

int main(void)
{
    // Configure RD0 as a digital output for an indicator LED
    TRISD = 0b1110;
 
    // Configure PWM
    //
    // Note: To provide higher PWM duty cycle resolution, the dsPIC's
    // PDCx unit is only half as long as its PTPER unit. For example,
    // when PDC1 = PTPER, the PWM1 duty cycle is actually only 50%.
    // Furthermore, in this example, since we are alternating back and
    // forth between pulses on two outputs, each output only produces
    // every second pulse. Hence, in this case PTPER is really only
    // half of the full signal period. Yes, it's potentially confusing!
    //
    _PMOD1 = 1;   // Enable PWM channel 1 in independent mode
    _PEN1H = 1;   // Enable PWM1H pin
    _PEN1L = 1;   // Enable PWM1L pin
    _POUT1L = 0;  // When PWM1L is overriden, set it low
    _POUT1H = 0;  // When PWM1H is overriden, set it low
    _POVD1L = 1;  // Initially, enable override on PWM1L
    _POVD1H = 0;  // Initially, disable override on PWM1H
    _OSYNC = 1;   // Synchronise PWM override changes with PTMR reset
    _PWMIE = 1;   // Enable PWM interrupt
    _PTCKPS = 0;  // prescale=1:64 (0=1:1, 1=1:4, 2=1:16, 3=1:64)
    PTPER = 999;  // Set PWM frequency to 30 kHz
    PDC1 = 400;   // Set duty cycle to 10%
    _PTEN = 1;    // Enable PWM time base
    
    // Now just blink an LED while the PWM ISR does the heavy lifting
    while(1)
    {
        _LATD0 = 1;          // Turn on LED on RD0
        __delay32(15000000); // 0.5 second delay
        _LATD0 = 0;          // Turn off LED on RD0
        __delay32(15000000); // 0.5 second delay
    }

    return 0;
}

I compiled the above code using Microchip’s free XC16 C compiler. This is my build script, which produces an binary file called “a.hex”:

xc16-gcc main.c -mcpu=30F4011 -Wl,--script=p30F4011.gld
if errorlevel 0 xc16-bin2hex a.out

I downloaded the file “a.hex” onto the dsPIC30F4011 using a PICkit 2 USB programmer. I don’t use MPLAB at all, so I just used the PICkit 2 software application (V 2.61 can be downloaded here).

The breadboard circuit I used for testing is shown below. In addition to the voltage supply connections I included in my circuit, I highly recommend connecting pin 20 (VSS) and pin 21 (VDD) to the voltage supply rails. In the past, I have tended not to bother connecting all voltage supply pins, but I have recently run into some very strange problems that it took me a long time to realise could be completely solved by connecting more voltage supply pins. Specifically, I had a lot of problems with mysterious resetting of the dsPIC when using PWM interrupts in parallel with mainline code (such as the LED flashing code I put in the main function in this example). I therefore highly recommend connecting all of the dsPIC’s voltage supply pins, even if it seems redundant.

An indicator LED (with 220\Omega; current-limiting resistor in series) is connected to RD0 (pin 23). The three wires which extend beyond the lower edge of the image are the oscilloscope connections – green is ground and the two blue wires connect PWM1H and PWM1L to different channels of the scope.

20150410_181128

This is how the circuit appeared once the code shown above was running. The LED on RD0 simply blinks on and off once a second.

I displayed the signals from PWM1H and PWM1L on the two channels of an oscilloscope. As shown below, the two signals are identical but 180 degrees out of phase.

Antiphase PWM signals displayed on oscilloscope.

The following modified version of the previous example shows how the PWM ISR can be used to update the duty cycle as well as alternating the generated pulses between the two outputs. Apart from comments, the only change from the previous example is the addition of lines 62-68 to the PWM ISR (the _PWMInterrupt function) which modulate the PWM duty cycle.

//
// This dsPIC30F4011 example program generates PWM signals
// on PWM1H and PWM1L. The signals are identical except that
// they are 180 degrees out of phase. This is a slightly
// modified version of the example in which duty cycle is
// modulated in a sawtooth pattern, increasing in small steps
// from 0.1 all the wat to 0.45, then resetting to 0.1 over
// and over again. PWM frequency is constant at 15kHz.
//
// The basic idea is to configure the PWM frequency to double
// what we want (30 kHz), but then only generate a pulse on
// each output every second period. We use the PWM interrupt
// (which is triggered every time the PWM timebase reaches
// the value PTPER and goes back to zero) to switch back and
// forth between PWM1H and PWM1L. i.e. The two pins take it
// in turns to output pulses.
//
// Written by Ted Burke - last updated 11-4-2015
//

#include <xc.h>
#include <libpic30.h>

// Configuration settings
_FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, Fcy=30MHz
_FWDT(WDT_OFF);                  // Watchdog timer off
_FBORPOR(MCLR_DIS);              // Disable reset pin

//
// The PWM interrupt is triggered each time PTMR reaches the same
// value as PTPER and resets to zero. We use this ISR to switch back
// and forth between outputting a pulse on PWM1H and outputting a pulse
// on PWM1L. During each PTMR period, we override one or other pin to
// lock it at 0, suppressing its pulse. Because the OSYNC bit is set,
// PWM override changes do not take effect until the next time PTMR
// resets to zero. Updates therefore don't take effect until the start
// of the the next cycle.
//
void __attribute__((interrupt, auto_psv)) _PWMInterrupt(void)
{
    // Reset PWM interrupt flag
    _PWMIF = 0;
    
    // Alternate between overriding PWM1L and overriding PWM1H.
    // Whichever pin is overridden will be driven low during
    // the next PWM period, suppressing its output pulse.
    if (_POVD1H)
    {
        _POVD1H = 0;
        _POVD1L = 1;
    }
    else
    {
        _POVD1L = 0;
        _POVD1H = 1;
    }

    // This addition to the PWM ISR modulates the duty cycle in
    // a sawtooth pattern. Every 100th time the ISR runs, the
    // duty cycle is increased by incrementing PDC1. When the
    // duty cycle reaches 45%, it is reset to 10%.
    static int n=0;
    n = (n + 1) % 100;
    if (n==0)
    {
        PDC1 = PDC1 + 1;
        if (PDC1 > (0.45 * 4 * PTPER)) PDC1 = 0.1 * 4 * PTPER;
    }
}

int main(void)
{
    // Configure RD0 as a digital output for an indicator LED
    TRISD = 0b1110;
 
    // Configure PWM
    //
    // Note: To provide higher PWM duty cycle resolution, the dsPIC's
    // PDCx unit is only half as long as its PTPER unit. For example,
    // when PDC1 = PTPER, the PWM1 duty cycle is actually only 50%.
    // Furthermore, in this example, since we are alternating back and
    // forth between pulses on two outputs, each output only produces
    // every second pulse. Hence, in this case PTPER is really only
    // half of the full signal period. Yes, it's potentially confusing!
    //
    _PMOD1 = 1;   // Enable PWM channel 1 in independent mode
    _PEN1H = 1;   // Enable PWM1H pin
    _PEN1L = 1;   // Enable PWM1L pin
    _POUT1L = 0;  // When PWM1L is overriden, set it low
    _POUT1H = 0;  // When PWM1H is overriden, set it low
    _POVD1L = 1;  // Initially, enable override on PWM1L
    _POVD1H = 0;  // Initially, disable override on PWM1H
    _OSYNC = 1;   // Synchronise PWM override changes with PTMR reset
    _PWMIE = 1;   // Enable PWM interrupt
    _PTCKPS = 0;  // prescale=1:64 (0=1:1, 1=1:4, 2=1:16, 3=1:64)
    PTPER = 999;  // Set PWM frequency to 30 kHz
    PDC1 = 400;   // Set duty cycle to 10%
    _PTEN = 1;    // Enable PWM time base
    
    // Now just blink an LED while the PWM ISR does the heavy lifting
    while(1)
    {
        _LATD0 = 1;          // Turn on LED on RD0
        __delay32(15000000); // 0.5 second delay
        _LATD0 = 0;          // Turn off LED on RD0
        __delay32(15000000); // 0.5 second delay
    }

    return 0;
}

Here’s a short video showing the sawtooth modulation of the PWM duty cycle displayed on the oscilloscope.

This entry was posted in Uncategorized and tagged , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , . Bookmark the permalink.

2 Responses to Generating antiphase PWM signals with the dsPIC30F4011

  1. 1. Such a pity the power supply issue is not in the microchip documentation.
    2. Great post!

    • batchloaf says:

      Thanks Richard. It is curious that it doesn’t seem to be stated explicitly in the data sheet whether or not all power pins need to be connected. However, I did find some discussions about it online, such as this one…

      http://www.microchip.com/forums/tm.aspx?m=471413

      …in which the most consistent advice seems to be to always connect every available power pin. I think I’ll do that from now on anyway.

      Ted

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s