Hola,
mis sugerencias son: si pensás interrumpir en intervalos muy pequeños de tiempo, configurar el Timer0 en modo 8 bits. De esa manera te ahorras tener que escribir un registro. El código que hiciste es bastante impreciso, especialmente cuando le agregues otras fases. No me parece la mejor forma de acercarse a lo que estás intentando hacer.
Entonces, y esto es sólo mi opinión y consideración:
1) utilizar la interrupción de alta prioridad para la generación de PWM por soft. Esto siempre es importante si a futuro se llega a agregar alguna otra. Necesita ser de alta prioridad en ese caso.
2) Si es la única interr. dentro del vector elegido, NO verificar el flag TMR0IF. Es evidente que el motivo de la interr. fue ese y no otro.
3) Lo primero que haces dentro del vector de interr. es precargar el valor del Timer0. De esta manera todo el codigo que agregues debajo no afectará la frecuencia del PWM. Todo el código que se ejecute antes de la precarga, afecta a la frecuencia del PWM. Incluso la cosa empeora cuando ponés IF, WHILE, FOR, etc antes de la precarga porque puede que no siempre se ejecute la mísma cantidad de instrucciones, lo que empeora la cosa porque puede hacer variar la frecuencia incluso entre interrupciones.
4) Hay veces, como en este caso, en que la generación del PWM es mejor que ocurra DENTRO de la interrupción. Eso garantiza sincronización e incluso, para este caso particular de PWM por software, optimización de las instrucciones necesarias para lograrlo. Hay que lograr que el PWM se genere con la menor cantidad de uso del CPU posible.
5) La mejor aproximación, a mi parecer (y llevo años haciendo PWM's por software de todo tipo en desarrollos comerciales) es esta:
Suponiendo que necesitás 3 fases de PWM @ 25Khz c/u y con posibilidad de phase-shifting, con una resolución de duty y phase-shift del 10%:
Con un micro como el 18F4550 corriendo a 48Mhz (frecuencia máxima del uC ese), necesitás interrumpir cada 1000000 / 25000 / 10 = 4 uS lo que en instrucciones son 48. Está muy jugada la cosa. No sé si va a funcionar, pero podemos intentarlo. El código debajo es usando C, si usás ASM seguro te ganás unas cuántas instrucciones.
//..
#include <stdint.h>
//..
//Definir los pines a usar como PWM
#define PWM1 LATBbits.LATB1
#define PWM2 LATBbits.LATB2
#define PWM3 LATBbits.LATB3
//..
//Declarar 3 contadores, uno por PWM. No sólo decidirán el duty de cada fase sino que además, la distancia entre ellos decidirá el phase-shifting:
uint8_t counter1, counter2, counter3;
//declaramos otras 3 variables, estas llevan el valor actual del PWM (en pasos de 10%, es decir, desde 0% al 100%. Son 11 valores pero el PWM se come un paso (si necesitás más explicación del por qué del paso que se come, la puedo exponer después. Por eso la división es por 10 y no por 11 en los cálculos del tiempo).
uint8_t PWM1value,PWM2value,PWM3value;
//en el código de la interr:
pragma interrupt Interrupt_ISR_TMR0
void Interrupt_ISR_TMR0 (void)
{
TMR0=230; //Hay que ajustarlo. Lo elegí para que quede un poco por debajo de 25Khz. Cuando menos interrumpa el timer, más tiempo de procesador para hacer otras cosas.
INTCONbits.TMR0IF=0; //Borramos bandera. Puede que el compilador lo haga automáticamente. Hay que verificarlo. Te podés ahorrar la instrucción en ese caso
if(++counter1 > 10) counter1 = 1; //nunca los hago valer 0, ese es el paso que me "como" del PWM para generar el duty del PWM fiel a lo que debe ser
if(++counter2 > 10) counter2 = 1;
if(++counter3 > 10) counter3 = 1;
if(PWM1value >= counter1) PWM1 = 1; else PWM1 = 0;
if(PWM2value >= counter2) PWM2 = 1; else PWM2 = 0;
if(PWM3value >= counter3) PWM3 = 1; else PWM3 = 0;
}
void main()
{
//falta código de inicialización, etc...
//inicializo contadores de PWM
counter1 = 1;
counter2 = 1;
counter3 = 1;
set_pwm_duty(1,4,9); //seteo los dutys de 3 fases: 10% para la fase 1, 40% para la fase 2 y 90% para la fase 3.
set_pwm_shift(0,0); //seteo phase-shift de 0% para la fase 2 y para la fase 3, ambas siempre con respecto a la fase 1. La fase 1 no se toca, ya que es la referencia
//falta config interr. Tmr0, etc. similar a lo que tenías en tu código, sólo que configurando el Tmr0 a 8 bits y el prescaler a 1:2
while(1)
{
delay_ms(2000); //demora de 2 segundos (mucho más en realidad por todo el % de tiempo de procesador que consume la interr del Timer)
set_pwm_duty(5,5,5); //seteo los dutys de las 3 fases al 50% para que se vea bien el phase-shift que quiero establecer justo debajo de esto
set_pwm_shift(3,7); //seteo phase-shift de 30% para la fase 2, y 70% para la fase 3, ambas siempre con respecto a la fase 1. La fase 1 no se toca, ya que es la referencia
delay_ms(2000); //demora de 2 segundos (mucho más en realidad por todo el % de tiempo de procesador que consume la interr del Timer)
set_pwm_duty(1,4,9); //seteo los dutys de 3 fases: 10% para la fase 1, 40% para la fase 2 y 90% para la fase 3.
set_pwm_shift(0,0); //seteo phase-shift de 0% para la fase 2 y para la fase 3, ambas siempre con respecto a la fase 1. La fase 1 no se toca, ya que es la referencia
}
}
inline void set_pwm_duty(uint8_t phase1Value, uint8_t phase2Value, uint8_t phase3Value)
{
PWM1value = phase1Value;
PWM2value = phase2Value;
PWM3value = phase3Value;
}
void set_pwm_shift(uint8_t phase2Shift, uint8_t phase3Shift)
{
uint8_t tmp;
tmp = (counter1+ phase2Shift) % 11;
if(!tmp) tmp++; //no dejar que valga nunca 0
counter2 = tmp;
tmp = (counter1+ phase3Shift) % 11;
if(!tmp) tmp++; //no dejar que valga nunca 0
counter3 = tmp;
}
Entonces, lo que deberías ver es durante varios segundos las 3 fases con phase shift 0 y con un duty del 10% la 1era, 40% la segunda y 90% la tercera.
Luego, cambia durante otros cuantos segundos y debería mostrar las 3 fases con un duty del 50% pero la fase 2 despazada un 30% con respecto a la fase 1 y la fase 3 despalzada un 70% con respecto a la fase 1.
Por supuesto que podés cambiar los valores de duty y shift a gusto en cualquier momento. No he testeado el código. Sólo lo estoy escribiendo mientras respondo, por ende puede tener errores. Probalo y nos contás si funciona.
Saludos.