Sinusoidal variation of PWM duty cycle with dsPIC30F4011

Reader George Skepas wrote in from Greece with an interesting question about how to make a dsPIC30F4011 produce a PWM signal with sinusoidally varying duty cycle. He and his colleagues are trying to drive the MOSFETs in an inverter circuit, so I’m guessing that they want their sinusoidal variation to have a frequency of 50Hz (the mains supply frequency in Greece). The PWM frequency should therefore be considerably higher than this. I’m no expert on power electronics, but I have a vague recollection of seeing some of my DIT colleagues who work in that area driving circuits of this type at frequencies in the general ballpark of 10kHz, so that’s what I’ve picked for this example.

The PWM duty cycle is updated every 200µs to produce the sinusoidal variation. Every 100 updates (20ms), the cycle repeats. This produces a sinusoidal duty cycle variation with a frequency of 50Hz. The regular duty cycle updates are performed by the Timer 1 interrupt service routine.

The program is written for Microchip’s C30 compiler. Ok, here’s the complete code:

// This is a dsPIC30F4011 program to generate a PWM signal
// with a sinusoidally varying duty cycle. The frequency of
// the PWM signal is 10kHz and the period of the sinusoidal
// variation in the duty cycle is 50Hz.
// written by Ted Burke - last updated 2-5-2011

#include <libpic30.h>
#include <p30f4011.h>
#include <math.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

// Function prototypes
void setup();
unsigned int read_analog_channel(int n);

#define PI 3.1415926536

int n = 0;
double dc_min=0.0, dc_max=1.0;

int main()
	// Set up digital i/o, PWM, and Timer 1 interrupt

	// It doesn't really matter what we put here. It's really
	// just something to keep the main function busy while the
	// real action takes place in the background. I'm making
	// it flash an LED on RD0, just so that I can see that the
	// circuit is running.
	// The sinusoidal variation of the PWM output will actually
	// be controlled by the Timer 1 interrupt service routine
	// which is below, just after the end of the main function.
		_LATD0 = 1;
		_LATD0 = 0;

	return 0;

// Timer 1 interrupt service routine (runs automatically every 200us)
void __attribute__((__interrupt__, __auto_psv__)) _T1Interrupt(void)
	double dc, sine_term;

	// Clear Timer 1 interrupt flag
	_T1IF = 0;

	// Vary PWM duty cycle sinusoidally (in 100 steps)
	n = n + 1;
	if (n == 100) n = 0;

	// Calculate duty cycle
	sine_term = (1 + sin(2 * PI * n / 100.0)); // varies between 0 and 2
	dc = sine_term * dc_min + (2 - sine_term) * dc_max; // varies between 2*dc_min and 2*dc_max

	// Update the duty cycle register
	PDC1 = dc * PTPER;

// This function sets up digital i/o, PWM, and Timer 1 interrupt
void setup()
	// Configure all four port D pins (RD0, RD1, RD2, RD3)
	// as digital outputs
	LATD = 0;
	TRISD = 0b1111111111110000;

	// Configure PWM for 10kHz frequency
	//   PWM period = Tcy * prescale * PTPER = 0.333ns * 64 * PTPER
	//   PWM pulse width = (Tcy/2) * prescale * PDC
	PWMCON1 = 0x00FF; // Enable all PWM pairs in complementary mode
	PTCON = 0;
	_PTCKPS = 0;      // prescale=1:64 (0=1:1, 1=1:4, 2=1:16, 3=1:64)
	PTPER = 3000;     // 100us PWM period (15-bit period value)
	PDC1 = PTPER;     // 50% duty cycle on PWM channel 1
	PDC2 = PTPER;     // 50% duty cycle on PWM channel 2
	PDC3 = PTPER;     // 50% duty cycle on PWM channel 3
	PTMR = 0;         // Clear 15-bit PWM timer counter
	_PTEN = 1;        // Enable PWM time base

	// Configure Timer 1
	// In this example, I'm setting PR1 and TCKPS for 5kHz.
	// In other words, the Timer 1 interrupt service routine
	// will run 5000 times a second (i.e. every 200us).
	T1CON = 0;            // Clear the Timer 1 configuration
	TMR1 = 0;             // Reset Timer 1 counter
	PR1 = 6000;           // Set the Timer 1 period (max 65535)
	T1CONbits.TCS = 0;    // Select internal clock (Fosc/4)
	T1CONbits.TCKPS = 0;  // Prescaler (0=1:1, 1=1:8, 2=1:64, 3=1:256)
	_T1IP = 1;            // Set the Timer 1 interrupt priority
	_T1IF = 0;            // Clear the Timer 1 interrupt flag
	_T1IE = 1;            // Enable Timer 1 interrupt
	T1CONbits.TON = 1;    // Turn on Timer 1

Now to explain the code.

There are three functions in this program:

  • The main function actually doesn’t do very much. It calls the setup function to configure the dsPIC and then just bides its time flashing an LED on RD0 (pin 23) once every two seconds.
  • The setup function configures the four port D pins as digital outputs, enables all three PWM channels in complementary mode with a frequency of 10kHz, enables Timer 1 with a frequency of 5kHz, and enables the Timer 1 interrupt so that its interrupt service routine, _T1Interrupt, will be called every 200µs.
  • The Timer 1 interrupt service routine, _T1Interrupt, is where most of the action really happens. This function is called automatically every 200µs. It updates the duty cycle of PWM channel 1. The sinusoidal variation repeats every 100 updates, which produces a frequency of 50Hz.

Of course, it’s difficult to observe the sinusoidal variation directly because it’s happening so fast (going through 50 cycles per second). In order to make it more obvious what’s happening, all you need to do is slow down the frequency of the Timer 1 interrupt service routine. To see the sinusoidal variation happening at a slower rate, connect an LED to PWM1H (pin 37)  and replace lines 104-106 with the following ones:

	PR1 = 9375;           // Set the Timer 1 period to 20 ms
	T1CONbits.TCS = 0;    // Select internal clock (Fosc/4)
	T1CONbits.TCKPS = 2;  // Set prescaler to 1:64

If you do that, you should see the LED brightness pulsating smoothly at 0.5Hz.

This entry was posted in Uncategorized. Bookmark the permalink.

22 Responses to Sinusoidal variation of PWM duty cycle with dsPIC30F4011

  1. George Skepas says:

    Thanks a lot mr.Ted!!I will try the code to the lab and let you know the results!!Thanks a lot again!!!

  2. skegeo says:

    Dear mr.Ted,
    We tested today at the lab the source code you sent me and worked fine.We made the adjustments with the delay to be set at 0
    _LATD0 = 1;
    _LATD0 = 0;
    __delay32(00000000) in order to see better pulse at oscillator.I would like to know the purpose of this delay.We also added deadtime so we dont have problem with mosfets.Now the final adjustment we want to make is add a potentiometer to AN1 so that we can adjust the voltage but keep the frequency constant at 50hz as you did.So if you could send me this addon will be great!
    The frequency of the sinusoid we want to produce is 50Hz as you choose.
    The frequency for the PWM signal for the mosfet must be 20-50 KHZ.The maximum and minimum duty cycles must be 0-100 %.
    The next step is to test the whole circuit on breadboard.You will hear our news soon!!
    Thanks a lot in advance mr.Ted!

    • batchloaf says:

      Oh, and I forgot to mention that you can change the PWM frequency quite easily (to 20kHz or 50kHz or whatever you want). Just modify line 91 of the sample code as follows:

      For 20kHz PWM,

      PTPER = 1500;     // PWM period = 1500 x Tcy = 50us, i.e. 20kHz

      For 50kHz PWM,

      PTPER = 600;     // PWM period = 600 x Tcy = 20us, i.e. 50kHz

      Hopefully that will do the trick for you!

  3. batchloaf says:

    Hi George.

    The part you mention with the two delays is actually not important at all for the generation of the PWM signals. It’s just there to flash an LED on and off on pin RD0 if you wish. The length of the delays (the number is a multiple of the instruction cycle, Tcy, which is my program is 33.33ns) just controls the rate at which the LED flashes.

    The PWM signal is actually controlled by the Timer 1 interrupt which is triggered repeatedly in the background. You can almost think of it as running in parallel to the main function. We can do whatever we want in the main function (e.g. flash an LED) and it will have no effect at all on the PWM output.

    When I get a chance, I’ll have a look at adding in the AN0 input to control the voltage level. Basically, you just need a few lines like the following in your main function to read the input voltage and update the maximum duty cycle accordingly:

    int input_voltage;
    input_voltage = read_analog_channel(0);
    dc_max = input_voltage / 1023.0;
  4. skegeo says:

    No luck with adding the code the potentiometer to work with dsPIC.We are totally newbies!!

    • batchloaf says:

      Hi George. I’ve just read your previous comment again and I see now that you mentioned connecting the potentiometer to AN1 rather than AN0. My extra lines of code above assume that the pot is connected to ANO – hence “read_analog_channel(0)”.

      You either need to move the pot to pin ANO, or change the code so that it reads from channel 1 instead of 0 – i.e. “read_analog_channel(1)”. If it still isn’t working, check the analog input voltage with a multimeter to make sure that it’s really varying when you move the potentiometer.

  5. skegeo says:

    Hello mr.Ted,everything looks good after printing and the pcbs.I will let you know more details later!!!

  6. skegeo says:

    Something that has come up right now at the lab.In order not to have short circuit we want the duty cycle never reach 100%.For example the potentiometer would be at minimum price and the duty cycle does not exceed 95%.How is this possible?

    • batchloaf says:

      Hi George,

      Near the start of the example code I posted, you’ll find the following line:

      double dc_min=0.0, dc_max=1.0;

      This line defines two global variables – dc_min and dc_max – which set the minimum and maximum duty cycle values respectively. To set the maximum duty cycle to 95%, all you need to do is change that line to the following:

      double dc_min=0.0, dc_max=0.95;

      Hopefully that answers your question?


  7. daniel says:

    I have succed to use and develop your code for my application. It help me a lot. Thank you Ted!

  8. daniel says:

    Hello Ted,
    So, the actual programme will produce a sinusoidal wave of 50Hz. I am wondering if it is possible, by using the other two complementary PWM outputs, to produce three sinusoidal waves, at the same frequency (50Hz), but with a phase-shift in time of 2*pi/3 (or 120 degrees), meaning that we will produce a three-phase a.c. power source at 50Hz. (Normaly, the duty cycle should be obtained from 1 analog voltage that we get from a potentiometer.) If yes, could tou give me some tips for the implementation of the C code?
    Thanks in advance

  9. fatima says:

    which software should be used to simulate this code??

    • batchloaf says:

      Hi Fatima.

      I compiled this C code using Microchip’s C30 compiler. I downloaded it onto the dsPIC using the PICkit2 software utility. You should be able to compile it without difficulty using MPLAB, and you can probably simulate it using that too, but I haven’t done that myself.

      I’m now using the XC16 compiler (which seems to be an updated and renamed version of C30). If you’re using XC16, it might be best to change the line “#include ” to “#include ” which is the standard way using the newer compiler.


  10. sanju says:

    hai sir,
    i made a pwm sinewave using the code here with little modification,but in the isr rutine, this code takse around 165us, iam refreshing my timer at everu 200 us, so ican not get enough time for adc readings, can you convert the math rutine in to a const table?(slow start is the problem while my convertion) can i get the slow raise of output within 3 seconds? the code with v_min=.05 and v_max=.95, the sine table thru math is like this way:
    double duty, sine_term;
    const int sinetable[100]={1390,1309,1229,1150,1072,995,919,845,774,705,638,575,514,458,405,355,310,269,233,201,174,151,134,121,113,111

    void __attribute__((__interrupt__, __auto_psv__)) _T1Interrupt(void)

    PDC1 = sinetable[count];
    PDC2= (2*PTPER)-PDC1;
    count=count + 1;
    if (count == 100)
    count = 0;
    _T1IF = 0;

    can you help me?

  11. Võ Văn Tú says:

    Dear Mr Ted,
    My inverter worked well with your code. But now I am trying to measure the voltage of the inverter to control it.
    But when I read the ADC it can not switch the pulse anymore. It means that 2 interrupts are collapsed.
    Can you show the code to make the sinosoidal and measure the voltage?

  12. Able says:

    Please can someone send to me the code

  13. Dear Mr. Ted
    I Learned a lot from your blog in last 15 days about dsPIC30F4011, in my previous experience I have worked on AVR/PIC 8-bit microcontrollers but never worked on 16-bit.
    I have used most of the peripherals in dsPIC30F4011 like(ADC,TIMERS,PWM,I2C[RTC Interface],Both UART[receive and transmit through Interrupts]) and also 4X20 LCD display Interfacing. Thanks for such a blog Keep writing.

Leave a Reply

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

You are commenting using your 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