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);
   }
}




7 comentarios:

  1. okey muy bien!
    Me podrias explicar por favor la siguiente linea de codigo:

    set_timer0(255); //Seteo de interrupcion

    creo que la cargaste en la rutina de interrupcion de 3 canales
    y en el de 8 canales.
    mi duda es por que la cargas con ese valor partiendo que es
    de 8 bits y que va de 0 hasta 255,por que no le pusiste 64,127,etc.
    ¿por que ese valor?.Gracias por responder.

    ResponderBorrar
    Respuestas
    1. Hola, el valor seteado es el máximo para el timer de 8 bit ya que es para que demore la menor cantidad de tiempo posible, el numero que le pones al timer junto con la configuración del divisor y el clock, es el tiempo que demora el timer en desbordar, si le pones 255, el timer entra y desborda rápidamente, entonces ahí tenes la máxima velocidad, con el prescaler al mínimo. En esta aplicación de PWM es necesario que sea rápido ya que el muestreo demanda de muchos ciclos.
      Saludos.

      Borrar
  2. ¿Se puede variar el pwm que entra al timer0?

    ResponderBorrar
    Respuestas
    1. Hola como estas, que queres decir con variar el PWM que entra al timer0?, variar el ciclo de trabajo de alguna salida en especial o variar la frecuencia de operación PWM?
      En el primer ejemplo donde tenes 3 salidas PWM, solo tenes que darle valores mediante la función TIMER0_isr(10,45,73); donde cada variable corresponde a cada salida PWM, los valores van entre 0 y 99.
      Este otro post tiene un ejemplo variando los 3 valores del PWM para controlar un LED RGB
      http://electgpl.blogspot.com.ar/2014/11/fade-led-rgb.html
      Saludos!

      Borrar
  3. Hola¡¡ Mientras el programa se encuentra aumentando los contadores para los PWM, puedo activar la comunicación serial para variar mediante otro pic el ciclo de trabajo de cada PWM independientemente?

    Gracias muy buen post¡¡

    ResponderBorrar
    Respuestas
    1. Hola, si podes, proba la latencia, que no afecte el ciclo de trabajo de la funcion del timer. pero si podes.
      Saludos.

      Borrar
  4. Interesante, proyecto, cómo hacer para que los LEDS se enciendan y apaguen lentamente?

    ResponderBorrar