Estoy tratando de cambiar la frecuencia de salida de PWM aproximadamente una vez por milisegundo usando un dsPIC33FJ256GP710 y tengo resultados mixtos. Primero probé esto:
#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
}
}
El resultado es que PWM se cae durante aproximadamente 4 ms en lo que parece ser un intervalo repetible, aproximadamente alineado con mi pin de depuración (en otras palabras, cuando el código interfiere con los registros de período y ciclo de trabajo). Adjuntaré una foto de mi rastro de alcance. El canal 1 es PWM y el canal 2 es el pin de depuración que se activa cuando el código intenta ajustar la frecuencia.
De todos modos, comencé a pensar en los cambios de tiempo e hice algunas búsquedas en algunos foros. Se me ocurrieron algunas ideas basadas en algunas publicaciones que leí. La mejor idea parecía ser habilitar la interrupción del temporizador 2, desactivar el modo PWM en su interior y solo cambiar los registros de período y ciclo de trabajo dentro de la interrupción del temporizador 2. Entonces, escribí esto:
#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
}
Esto parece ser más estable por lo que puedo ver en mi alcance antiguo, pero ahora la forma de onda ya no tiene una forma regular (el ciclo de trabajo parece ser inexplicablemente inconsistente) y si me esfuerzo lo suficiente puedo convencerme de que todavía ver un milisegundo de caída de PWM cuando mi alcance está configurado en una base de tiempo de 5 o 10 milisegundos.
Ciertamente es mejor de lo que era, y puedo seguir jugando con él con la esperanza de arreglar la forma de onda irregular producida por el segundo bit de código, pero mi pregunta es:
¿Hay una forma "correcta" de hacer esto? ¿O al menos una mejor manera que el camino en el que estoy?
Cualquier ayuda sería muy, muy apreciada.
Seguimiento del alcance http://www.freeimagehosting.net/uploads/c132216a28.jpg
Para cualquiera que esté interesado, aquí está la solución a la que llegué hoy:
#include <p33fxxxx.h>
_FOSCSEL(FNOSC_PRIPLL);
_FOSC(FCKSM_CSDCMD & OSCIOFNC_OFF & POSCMD_XT);
_FWDT(FWDTEN_OFF);
static int curFreq = 0;
static int nextFreq = 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;
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
nextFreq = 1; // Signal frequency change to ISR
RESTORE_CPU_IPL(ipl); // Allow the Timer 2 interrupt
}
}
void __attribute__((__interrupt__)) _T2Interrupt(void)
{
IFS0bits.T2IF = 0; // Clear the Timer 2 interrupt flag
if (nextFreq)
{
nextFreq = 0; // Clear the frequency hop flag
OC7RS = PWM_TABLE[curFreq][1]; // Set the new PWM duty cycle
PR2 = PWM_TABLE[curFreq][0]; // Set the new PWM period
}
}
Confirmé con el alcance y un pin de depuración mi sospecha: el código original sufría una condición de carrera. El bucle principal no se molestó en sincronizar los cambios en PR2 con el estado real del contador TMR2, por lo que ocasionalmente establecería PR2 en un valor MENOR QUE (o tal vez igual) al valor TMR2 actual. Esto, a su vez, haría que TMR2 contara hasta que volviera, luego continuaría contando hasta que llegara a PR2 y generara un flanco ascendente. Durante el tiempo que TMR2 estuvo contando hasta 65535 para reinvertir, no se generó ninguna salida PWM. A 16 MIPS, el tiempo de reinicio para un temporizador de 16 bits como TMR2 es de aproximadamente 4 ms, lo que explica mi pérdida de PWM de 4 ms. Entonces, el código estaba haciendo exactamente lo que escribí para hacer :)
En el segundo fragmento, el código sincroniza correctamente los cambios en PR2 y el registro del ciclo de trabajo con el evento de transferencia de TMR2, por lo que la caída de 4 ms desapareció. Mencioné una forma de onda "extraña" asociada con ese ejemplo: se debió a que el pin RD6/OC7 estaba configurado como salida y tenía un valor bajo establecido en el registro LATD. El segundo fragmento en realidad desactiva el modo PWM dentro del ISR del temporizador 2: esto permite que la funcionalidad GPIO se haga cargo y reduce RD6/OC7 durante unos microsegundos antes de volver a habilitar PWM y generar un flanco ascendente, lo que lleva a una forma de onda de "hipo".
El segundo fragmento también tiene el problema de que reconfigura PR2 y el registro del ciclo de trabajo en cada reinicio del temporizador 2, independientemente de si el bucle principal ordenó un cambio de frecuencia o no. A partir de la observación, me parece que el temporizador da la vuelta y genera un flanco ascendente en el pin PWM y ENTONCES el ISR del temporizador 2 obtiene el control unos nanosegundos más tarde (debido a la latencia del vector, etcétera). Desactivar PWM y reajustar los registros cada vez no le da la frecuencia y el ciclo de trabajo correctos a largo plazo porque el hardware ya ha generado un flanco ascendente y comenzó a contar hasta el siguiente valor de comparación.
Lo que esto significa es que en el fragmento corregido que publiqué hoy, ¡el trabajo realizado en el Timer 2 ISR debe minimizarse! Debido a que estoy ejecutando PWM a una frecuencia tan alta, y debido a que hay una pequeña latencia entre el flanco ascendente generado por el hardware PWM y la invocación del Timer 2 ISR, cuando entro en el ISR TMR2 ya ha tenido tiempo contar hasta un número justo. Mi código necesita configurar PR2 y el registro del ciclo de trabajo de forma inmediata y directa (es decir, no hay llamadas de función, e incluso la búsqueda en la tabla lo está presionando), de lo contrario, corre el riesgo de perder la comparación y causar el error de reinversión de 4 ms que era mi original problema.
De todos modos, creo que esta es una descripción precisa de las cosas, y estoy ejecutando el código en mi aplicación "real" con resultados alentadores hasta ahora. Si algo más cambia, lo publicaré aquí y, por supuesto, cualquier corrección a lo anterior sería enormemente apreciada.
Gracias por tu ayuda, pingswept.
Intentaría ralentizar todo por un factor de 10 para que pueda ver con más detalle cuándo se está muriendo exactamente el PWM. También intentaría ajustar los valores en su tabla de período y ciclo de trabajo. ¿Quizás estás configurando mal el PWM en 4 de tus 7 ciclos? Noté que hay 4 valores de período PWM que están por encima de 128, ¿tal vez eso esté causando problemas?
Si eso no ayudara, buscaría un patrón más grande. ¿Con qué frecuencia se repite el intervalo de 4 ms?
Muy bien hecha la pregunta, por cierto.
No estoy seguro acerca del PWM en un dsPIC pero en un PIC16F1508, la documentación dice que cuando el TMR2 alcanza un ciclo de recarga, vuelve a cargar el registro TMR2 y luego carga los registros PWMxDC en los registros de ciclo de trabajo reales en el hardware PWM. es decir, la escritura del ciclo de trabajo PWM se sincroniza automáticamente con la recarga del TMR2. Entonces, cuando escribe el ciclo de trabajo, no tiene efecto hasta el siguiente ciclo de trabajo. Esto significa que su cambio en el ciclo de trabajo podría retrasarse tanto como un ciclo de recarga de TMR2, pero ese es el único cambio que debería ver. Mira en tu documentación.
barrido
indeleble