I'm trying to change the PWM output frequency roughly once a millisecond using a dsPIC33FJ256GP710, and I'm having mixed results. I first tried this:
#include <p33fxxxx.h>
_FOSCSEL(FNOSC_PRIPLL);
_FOSC(FCKSM_CSDCMD & OSCIOFNC_OFF & POSCMD_XT);
_FWDT(FWDTEN_OFF);
static unsigned int PWM_TABLE[7][2] =
{
{132, 66}, {131, 66}, {130, 65}, {129, 65}, {128, 64}, {127, 64}, {126, 63} // Compare, 50% duty
};
static int curFreq = 0;
int main(void)
{
int i;
PLLFBD = 0x009E; // Set processor clock to 32 MHz (16 MIPS)
CLKDIV = 0x0048;
LATCbits.LATC1 = 0; // Make RC1 an output for a debug pin
TRISCbits.TRISC1 = 0;
LATDbits.LATD6 = 0; // Make RD6/OC7 an output (the PWM pin)
TRISDbits.TRISD6 = 0;
T2CONbits.TON = 0; // Disable Timer 2
OC7CONbits.OCM = 0b000; // Turn PWM mode off
PR2 = PWM_TABLE[curFreq][0]; // Set PWM period
OC7RS = PWM_TABLE[curFreq][1]; // Set PWM duty cycle
OC7CONbits.OCM = 0b110; // Turn PWM mode on
T2CONbits.TON = 1; // Enable Timer 2
while (1)
{
for (i = 0; i < 3200; i++) {} // Delay roughly 1 ms
curFreq = (curFreq + 1) % 7; // Bump to next frequency
PR2 = PWM_TABLE[curFreq][0]; // Set PWM period
OC7RS = PWM_TABLE[curFreq][1]; // Set PWM duty cycle
LATCbits.LATC1 = !LATCbits.LATC1; // Toggle debug pin so we know what's happening
}
}
The result is that PWM drops out for about 4 ms at what looks to be a repeatable interval, roughly aligned with my debug pin toggle (in other words, when the code is messing with the period and duty cycle registers). I'll attach a photo of my scope trace. Channel 1 is PWM and channel 2 is the debug pin that's toggled when the code attempts to adjust the frequency.
Anyway, I started thinking about timer rollovers, and I did some searching on a few forums. I came up with a few ideas based on a few posts I read. The best idea seemed to be to enable the Timer 2 interrupt, turn PWM mode off inside it, and only change the period and duty cycle registers inside the Timer 2 interrupt. So, I wrote this:
#include <p33fxxxx.h>
_FOSCSEL(FNOSC_PRIPLL);
_FOSC(FCKSM_CSDCMD & OSCIOFNC_OFF & POSCMD_XT);
_FWDT(FWDTEN_OFF);
static int curFreq = 0;
static unsigned int PWM_TABLE[7][2] =
{
{132, 66}, {131, 66}, {130, 65}, {129, 65}, {128, 64}, {127, 64}, {126, 63} // Compare, duty
};
int main(void)
{
int i, ipl;
PLLFBD = 0x009E; // Set processor clock to 32 MHz (16 MIPS)
CLKDIV = 0x0048;
LATCbits.LATC1 = 0; // Make RC1 an output for a debug pin
TRISCbits.TRISC1 = 0;
LATDbits.LATD6 = 0; // Make RD6/OC7 an output (the PWM pin)
TRISDbits.TRISD6 = 0;
OC7CONbits.OCM = 0b000; // Turn PWM mode off
OC7RS = PWM_TABLE[curFreq][1]; // Set PWM duty cycle
PR2 = PWM_TABLE[curFreq][0]; // Set PWM period
OC7CONbits.OCM = 0b110; // Turn PWM mode on
T2CONbits.TON = 0; // Disable Timer 2
TMR2 = 0; // Clear Timer 2 register
IPC1bits.T2IP = 1; // Set the Timer 2 interrupt priority level
IFS0bits.T2IF = 0; // Clear the Timer 2 interrupt flag
IEC0bits.T2IE = 1; // Enable the Timer 2 interrupt
T2CONbits.TON = 1; // Enable Timer 2
while (1)
{
for (i = 0; i < 1600; i++) {} // Delay roughly 1 ms
SET_AND_SAVE_CPU_IPL(ipl, 2); // Lock out the Timer 2 interrupt
curFreq = (curFreq + 1) % 7; // Bump to next frequency
RESTORE_CPU_IPL(ipl); // Allow the Timer 2 interrupt
LATCbits.LATC1 = !LATCbits.LATC1; // Toggle debug pin so we know what's happening
}
}
void __attribute__((__interrupt__)) _T2Interrupt(void)
{
T2CONbits.TON = 0; // Disable Timer 2
TMR2 = 0; // Clear Timer 2 register
OC7CONbits.OCM = 0b000; // Turn PWM mode off
OC7RS = PWM_TABLE[curFreq][1]; // Set the new PWM duty cycle
PR2 = PWM_TABLE[curFreq][0]; // Set the new PWM period
OC7CONbits.OCM = 0b110; // Turn PWM mode on
IFS0bits.T2IF = 0; // Clear the Timer 2 interrupt flag
T2CONbits.TON = 1; // Enable Timer 2
}
This looks to be more stable as far as I can tell on my ancient scope, but now the waveform is no longer regularly-shaped (the duty cycle seems to be inexplicably inconsistent) and if I try hard enough I can convince myself that I still see a millisecond of PWM dropout when my scope is set to a 5 or 10 millsecond timebase.
It's certainly better than it was, and I can continue to mess with it in the hopes of fixing the irregular waveform produced by the second bit of code, but my question is:
Is there a "right" way to do this? Or at least a better way than the path I'm on?
Any help would be thoroughly, thoroughly appreciated.