Multiple PWM sin CCP

Una forma de utilizar varios canales PWM de forma dinámica y simultánea es con los módulos CCP del MCU, pero como podrán ver, en la mayoría de los MCU de microchip de baja gama no encontraremos más de 2 o 3 módulos. 
Que pasa si usamos un MCU que solo posee 1 CCP o si quisiéramos por ejemplo 8 PWM. Aquí entran muchas maneras de realizar una virtualizacion del PWM una que se suele usar es el multiplexado del PWM. 

NOTA: Como sabrán los módulos ADC del MCU multiplexan sus entradas, por ejemplo en un MCU que posee 8 canales ADC, no es que internatemnte tenga 8 ADC, sino que tiene solo uno pero multiplexa las entradas, por eso siempre hay que esperar un tiempo de conversión y demás. 

De esta misma forma podemos hacer un multiplexado de salidas PWM, por ejemplo con un multiplexor de 8 canales (ej. 4051) podemos selecciona el canal deseado mediante un direccionamiento de 3bit que podremos manejarlo directamente con cualquier puerto, y luego con un solo PWM podemos sincronizar el valor que deseamos del ciclo de trabajo con el canal habilitado en el multiplexor. Esto tiene una demora y una respuesta en frecuencia que respetar, aparte de que necesitamos también de hardware externo.

Otra forma muy utilizada es una virtualizacion netamente por software.
En este post les mostrare esta última opción. Utilizaremos una técnica muy sencilla, dividermos el programa en dos partes.


1) Sección que se encargara de modificar dinámicamente el valor del ciclo de trabajo de cada canal PWM. (Por ejemplo, canal 1: 40%, canal 2: 30%, canal..... canal 8: 75%) esto lo realizaremos de forma muy sencilla, pasaremos cada valor a una función, y cada vez que modifiquemos estos valores se actualizaran los ciclos de trabajo del PWM.


2) La parte mas importante ocurrirá dentro de una interrupción por desbordamiento del timer0 (se puede usar el timer que ustedes quieran, yo he utilizado este porque fue lo primero que se me ocurrió).
Generamos una interrupción interna cada un determinado valor de tiempo (que podrán ajustar ustedes mismos, en mi caso lo tengo al rededor de los 3ms que serán algo asi como 300Hz).
Cada vez que se repita el ciclo dentro de la interrupción se sumara una unidad al contador (cont++) y moviendo los valores de ciclo a la función se comparara con el valor que tiene en ese momento el contador.


Por ejemplo tomemos un canal, como podemos ver tenemos que se pondrá a 1 la salida PWM si el valor del contador se encuentra entre 0 y el valor que enviamos por la función, si enviamos el valor 70, la salida comenzara encendida ya que se encuentra entre 0 y 70, una vez que el ciclo se repite 70 veces a la vez 71 saldrá del if y pondrá en 0 la salida PWM, de esta forma estaremos obteniendo un ciclo de trabajo de 70%, esto mismo lo calcula en todos los if, testeara cada uno de los if, si el valor que trae de la función es menor al del contador y de esa forma encenderá y apagar la salida en cuestión.


Por ejemplo para dos valores, si tenemos valor 1: 50%, y valor 2: 60%, tendremos dos if, uno que preguntara si el valor del contador se encuentra entre 0 y 50, y otro if que preguntara si se encuentra entre 0 y 60, de esta forma el primer if que se cumplirá es de 0 y 50 ya que cuando el contador tenga 51, pondrá a 0 el pin PWM del valor 1, y luego seguirá contando y cuando llegue a 61 pondrá a 0 el pin PWM del valor 2. 


Esta rutina no solo nos sirve para dos o tres canales sino para los canales que queramos.
Como se puede ver la resolución sera de 100 pasos. Se hace así para que sea lineal al ciclo de trabajo... Ej. Valor 10 = 10%, y también serán menos pasadas del timer por ende mayor frecuencia de salida en cada PWM.


Aqui mostrae el código fuente para 3 canales PWM, por ejemplo para manejar un led RGB.

#include <16F628A.h>             //MCU a utilizar
#FUSES NOWDT, HS, NOMCLR         //Fueses
#use delay(clock=20000000)       //Oscilador Externo
#int_TIMER0                      //LLamado a interrupcion
void TIMER0_isr(int pwm0,        //Funcion Interrupcion
                int pwm1,        //Variables de link
                int pwm2){       //PWM0 PWM1 PWM2
   int cont;                     //Variable de Contador
   if(cont>99)                   //Si cont 100 se inicializa
      cont=0;                    //Cont = 0
   else                          //Caso contrario
      cont++;                    //Incrementa en una unidad cont
   if(cont>0&&cont<pwm0)         //Si 0<Cont<PWM0
      output_high(PIN_B0);       //Salida PWM0 a nivel alto
   else                          //Caso contrario
      output_low(PIN_B0);        //Salida PWM0 a nivel bajo
   if(cont>0&&cont<pwm1)         //Si 0<Cont<PWM1
      output_high(PIN_B1);       //Salida PWM1 a nivel alto
   else                          //Caso contrario
      output_low(PIN_B1);        //Salida PWM1 a nivel bajo
   if(cont>0&&cont<pwm2)         //Si 0<Cont<PWM2
      output_high(PIN_B2);       //Salida PWM2 a nivel alto
   else                          //Caso contrario
      output_low(PIN_B2);        //Salida PWM2 a nivel bajo
   set_timer0(255);              //Seteo de interrupcion
}
void main(){                     //Funcion principal
   setup_timer_0(RTCC_INTERNAL|RTCC_DIV_2);  //Seteo timer0
   enable_interrupts(INT_TIMER0);            //Seteo timer0
   enable_interrupts(GLOBAL);                //Seteo timer0
   while(true){                  //Loop infinito
      TIMER0_isr(10,45,73);      //Se movera a la funcion
   }                             //PWM0=10%, PWM1=45%, PWM2=73%
}



Ahora realizaremos la actualizacion de este programa para 8 canales.

#include <16F883.h>
#FUSES NOWDT, HS, NOMCLR
#use delay(clock=20000000)
#int_TIMER0
void TIMER0_isr(int pwm0, int pwm1, int pwm2,
                int pwm3, int pwm4, int pwm5,
                int pwm6, int pwm7){
   int cont;
   if(cont>99)
      cont=0;
   else  
      cont++;
   if(cont>0&&cont<pwm0)
      output_high(PIN_B0);
   else
      output_low(PIN_B0);
   if(cont>0&&cont<pwm1)
      output_high(PIN_B1);
   else
      output_low(PIN_B1);
   if(cont>0&&cont<pwm2)
      output_high(PIN_B2);
   else
      output_low(PIN_B2);
   if(cont>0&&cont<pwm3)
      output_high(PIN_B3);
   else
      output_low(PIN_B3);
   if(cont>0&&cont<pwm4)
      output_high(PIN_B4);
   else
      output_low(PIN_B4);
   if(cont>0&&cont<pwm5)
      output_high(PIN_B5);
   else
      output_low(PIN_B5);     
   if(cont>0&&cont<pwm6)
      output_high(PIN_B6);
   else
      output_low(PIN_B6);
   if(cont>0&&cont<pwm7)
      output_high(PIN_B7);
   else
      output_low(PIN_B7);      
   set_timer0(255);
}
void main(){
   setup_timer_0(RTCC_INTERNAL|RTCC_DIV_2);
   enable_interrupts(INT_TIMER0);
   enable_interrupts(GLOBAL);
   while(true){
      TIMER0_isr(10,20,30,40,50,60,70,80);
   }
}