PWM sin CCP


Esta publicación fue realizada con el fin de conseguir una segunda opción para los MCU que no poseen módulos PWM o bien para aquellos desarrollos que requieren mas de los que posee el MCU.
Si bien ya sabemos que los MCU mas baratos no poseen estos módulos PWM pero con esta simple rutina es posible modular una salida cualquiera para lograr el PWM.
Dejando de lado los micros que poseen PWM, como sabemos necesitamos setear el setup_timer_2(...), y luego mover un valor decimal a la función set_pwm1_duty(XX) donde XX es el valor que movemos y es el ciclo de trabajo pwm que entregara el MCU por su salida PWM, pero el problema es cuando necesitamos mas salidas, por ejemplo en esta nota he utilizado el PIC16F883 no es de alta gama pero es bastante completo y mantiene un costo bajo, pero a pesar de ello solo posee dos salidas PWM.
La idea de esta rutina es manipular tantas salidas como tenga el MCU si se quisiera.
La el programa es el siguiente:

#include <16F883.h>
#device adc=8
#FUSES NOWDT
#FUSES INTRC_IO
#FUSES NOPUT
#FUSES NOMCLR
#FUSES NOPROTECT
#FUSES NOCPD
#FUSES NOBROWNOUT
#FUSES IESO
#FUSES FCMEN
#FUSES NOLVP
#FUSES NODEBUG
#FUSES NOWRT
#FUSES BORV40
#use delay(int=8000000)
void main()
{
   setup_adc_ports(sAN0|VSS_VDD);
   setup_adc(ADC_CLOCK_DIV_2);
   int8 tOFF, tON;
   while(1){
      set_adc_channel(0);
      tOFF = read_adc();
      tON = 255 - tOFF;
      delay_us(tOFF);     
      output_low(PIN_C0);
      delay_us(tON);
      output_high(PIN_C0);
      }
}


El circuito es muy simple solamente se utiliza un pin de entrada y uno de salida, pero de todas formas lo subiré para completar la nota.





La primera parte del código (include del micro a utilizar, rango del ADC, configuración del MCU "FUSES", delay) es genérica, lo que nos interesa es el código marcado en color rojo, lo que podemos ver es que la lectura del ADC (alimentado por el potenciómetro) se guarda en tOFF, luego tON será el máximo del ADC como lo seteamos en 8bit será de 0 a 255, por ende el valor del ADC será restado del 255 y nos dará el tON, luego utilizaremos el tON como variable del delay_us() y de la misma forma el tOFF, esto quiere decir que primero tOFF tendrá el tiempo de apagado del Led output_low(PIN_C0) y luego tON tendrá el tiempo de encendido output_high(PIN_C0).
Un ejemplo de este calculo seria:  si por ejemplo situamos el potenciómetro en el centro el ADC estaría leyendo +/-2,5V que traducidos al ADC seria el valor 127, ese valor estaría en la variable tOFF, luego aparece el calculo 255 - tOFF = tON, entonces seria 255-127 = 128, tON valdría 128, podríamos decir que el ciclo es +/-50%, ahora si variamos el potenciómetro por ejemplo a 1,25V seria el valor 64 del ADC, tOFF seria igual a 64 y tON seria 255 - 64 = 191, entonces podemos decir que el ciclo de trabajo seria del 75%, de esa forma trabaja nuestra rutina, la cual se podría expandir para cada salida prolijamente dentro de una función o bien dentro de una interrupción.


DutyCycle 95%


DutyCycle 50%


DutyCycle 5%




 

29 comentarios:

  1. buenaaaaaaaa!!! me salvas la vida j3j3j3

    ResponderBorrar
  2. Me alegro que te sirva, eh notado que en varios foros tenían el mismo problema en cuanto a la escasa cantidad de puertos PWM de los micros de bajo costo.
    Saludos.

    ResponderBorrar
  3. y si en ves de un potenciometro mando la señal desde el puerto de comunicacion ?? serviria??

    ResponderBorrar
  4. Hola como estas, si te sirve!, la señal que ingresas al microcontrolador tiene que estar entre 0 y 5V de corriente continua,. si vas a ingresar una tensión mayor a 5V tenes que hacerle algún divisor y si vas a ingresar corriente alterna tendrás que usar un diodo detector para que al microcontrolador solo le ingrese envolvente.
    En resumen de 0 a +5V en su entrada.
    Saludos.

    ResponderBorrar
  5. Hola muchas gracias te cuento estoy tratando de comunicar un android con un pic 18f452 mediante bluetooth, este se conecta mediante el puerto usart, bueno en resumen debo controlar un sistema de iluminación mediante este sistema y varias de estas luces deben poder regularse mediante el android por esto necesitaba generar mas de salidas pwm sin el ccp, si podrias darme alguna ayuda con el programa del pic seria de mucha ayuda...
    saludos gracias por responderme

    ResponderBorrar
  6. Hola, si, creo que lo tengo echo eso ya, dejame que lo busque, porque me lo pidieron acá antes pero no lo encontré en donde creí que estaba, asique tengo que buscarlo mejor, de todas formas te digo, el funcionamiento para mas salidas PWM es por censado, osea lees dato 1 movés a variable de PWM 1, dato dos a variable de PWM 2, tenes que hacer un barrido entre las salidas PWM y ir actualizando sus variables según corresponda, la técnica de censado es la misma que se usa para multiplexar displays o para matrices de led. Espero que te sirva.

    ResponderBorrar
    Respuestas
    1. Hola muchas gracias, creo que ya se mas o menos el camino que debo seguir ya no me siento tan desorientado ya entendi la logica a seguir de todos modos si tienes hecho esto, creeme que me salvarías la vida, ya que este proyecto debo tenerlo en el menor tiempo posible, si lo encontraras te agradeseria mucho que me lo mandaras se que es pedir mucho pero si no lo necesitara no estaria pidiéndotelo.
      gracias por guiarme seguire intentando ya que mi nivel de programación no es muy bueno siendo sincero, me manejo mas en java y ahora la programacion de android, y hace unas cuantas semanas comencé a aprender C18 para poder desarrollar esto.
      muchas gracias enserio

      Borrar
    2. Por nada, mira el programa que sirve para eso es este el siguiente, sino enviame un mail y te respondo con el adjunto de este codigo.

      NOTA: la variacion de PWM se hace con un potenciometro en cada entrada, osea 8 salidas PWM, 8 potenciometros, si vos queres manejarlo por USART tenes que ovbiamente sacar el read_adc() y en su lugar poner la variable que leera por serie. Saludos.

      #include <16f887.h>
      #fuses XT,NOWDT,NOPROTECT,PUT,NOLVP,NOMCLR
      #device ADC=8
      #use delay(clock=20000000)
      int16 maxPwm=12500,valPwm[8];
      int8 pwm=0;
      int1 flag=1,enaPwm[8]={1,1,1,1,1,1,1,1};
      #int_timer1
      void intTimer(void)
      {
      if(flag==1){
      if(pwm==0 && enaPwm[0]==1)
      output_high(PIN_B0);
      if(pwm==1 && enaPwm[1]==1)
      output_high(PIN_B1);
      if(pwm==2 && enaPwm[2]==1)
      output_high(PIN_B2);
      if(pwm==3 && enaPwm[3]==1)
      output_high(PIN_B3);
      if(pwm==4 && enaPwm[4]==1)
      output_high(PIN_B4);
      if(pwm==5 && enaPwm[5]==1)
      output_high(PIN_B5);
      if(pwm==6 && enaPwm[6]==1)
      output_high(PIN_B6);
      if(pwm==7 && enaPwm[7]==1)
      output_high(PIN_B7);
      set_timer1(65535-valPwm[pwm]);
      flag=0;
      }
      else if(flag==0){
      if(pwm==0 && enaPwm[0]==1)
      output_low(PIN_B0);
      if(pwm==1 && enaPwm[1]==1)
      output_low(PIN_B1);
      if(pwm==2 && enaPwm[2]==1)
      output_low(PIN_B2);
      if(pwm==3 && enaPwm[3]==1)
      output_low(PIN_B3);
      if(pwm==4 && enaPwm[4]==1)
      output_low(PIN_B4);
      if(pwm==5 && enaPwm[5]==1)
      output_low(PIN_B5);
      if(pwm==6 && enaPwm[6]==1)
      output_low(PIN_B6);
      if(pwm==7 && enaPwm[7]==1)
      output_low(PIN_B7);
      set_timer1(65535-maxPwm+valPwm[pwm]);
      pwm++;
      if(pwm>7)
      pwm=0;
      flag=1;
      }
      }
      void main()
      {
      setup_adc(ADC_CLOCK_INTERNAL);
      setup_adc_ports(ALL_ANALOG);
      enable_interrupts(INT_TIMER1);
      setup_counters(RTCC_INTERNAL,RTCC_DIV_2);
      setup_timer_1(T1_INTERNAL | T1_DIV_BY_1);
      enable_interrupts(GLOBAL);
      set_timer1(59999);
      set_tris_b(0x00);
      set_tris_a(0xFF);
      set_tris_e(0xFF);
      int8 i;
      while(TRUE){
      for(i=0;i<8;i++){
      if(enaPwm[i]==1)
      set_adc_channel(i);
      delay_ms(10);
      valPwm[i]=4750+(read_adc()*5);
      }
      }
      }

      Borrar
    3. disculpa no entiendo muy bien el codigo ya que muevo el potenciomentos pero las señales no son afectadas espero me puedas ayudar y de antemano gracias?

      Borrar
  7. Soy novato en los microcontroladores. Estoy usando el PIC16F873A y quisieta aprender a usar la función PWM. ¿Tendrás alguna dirección web, tutorial,... que me ayude?
    Gracias.

    ResponderBorrar
    Respuestas
    1. mmm estas usando C para programar?, hay un foro muy activo de pic, http://www.todopic.com.ar/foros/
      ahi tenes de todo tipo de proyectos con pics.
      Saludos

      Borrar
  8. Hola Sebastian ojala me puedas ayudar, yo estoy haciendo un programa como el que propones aca en el foro con la diferencia de que cuando el valor máximo ingresado a través del conversor ad es menor a 5v por ejemplo 3v tengo una etapa donde se procede a normalizar para lograr q a los 3v pueda obtener el 100% del pwm. La constante se obtiene : cte =1023.0/614, siendo 1023 la maxima excursion del adc (seteado en 10) y 614 el valor en digital de 3v, al dividir estos dos numeros obtengo la cte y me da un numero flotante el cual luego es usado para multiplicar los valores ingresados por el adc , dicho resultado se usa como ciclo de trabajo. pero aqui esta el problema q cuando se usa un numero flotante en el ciclo de trabajo del pwm obtengo un pwm inestable. que puedo hacer? por fa ayudame.
    gracias

    ResponderBorrar
    Respuestas
    1. Buenas! como estas? mira el tema con los ADC en cuanto a la resolución es que hay que trabajar con las tensiones de referencia, es decir, si vos tenes un fondo de escala a 3V, tenes que tener Vref a 3V, ya que de esa forma aprovechas toda la resolución del mismo y no generas esa metaestabilidad que se genera por histeresis en el comparador del ADC.
      El tema modificar el fondo de escala por firmware te genera esa fluctuación que te molesta, si bien se puede mejorar aplicando una función integral... estarías "inventando" el valor, lo mejor es bajar el fondo de escala a 3V mediante la tensión de referencia del microcontrolador. Los micros tienen un pin especial que es para dicho Vref. Por ejemplo los PIC usan pines de entrada análoga como +Vref y -Vref, de esa forma le marcas los limites al ADC, en tu caso -Vref iria a 0V y +Vref iria a 3V (que podes obtenerlos de un regulador o de una pila de 3V para mayor estabilidad). La configuración de esto es en lugar de: setup_adc_ports(sAN0|VSS_VDD); pones esto: setup_adc_ports(AN0_VREF_VREF); eso habilita los VREF que en el caso del 16F883 son los pines RA2 y RA3.
      Tenes otra forma que es ampliar la resolución con amplificadores operacionales, para que siempre el pic maneje de 0 a 5V en su entrada que provienen de la salida del AmpOp donde tendrás que tener un factor de multiplicación en el mismo, es decir, por ejemplo si a 3V de entrada lo multiplicas por 1.65 te va a dar casi 5V a la salida, entonces el AmpOp tiene que amplificar 1.65 Veces. Pero si podes usar los pines de Vref del micro es mucho mejor y mas sencillo.
      Saludos.!

      Borrar
    2. Sabes que me acordaba pero que no probe, que en la configuracion podes usar en lugar de setup_adc_ports(AN0_VREF_VREF); podes poner setup_adc_ports(AN0_VSS_VREF); de esta forma el pin de -VREF ya lo toma como el Vss del micro osea por defecto 0V y solo te queda restante el de +VREF, con eso tenes simplificado el tema del ADC para un fondo de escala de 3V

      Borrar
  9. Gracias sebastian pero creo q no me explique bien,necesito controlar le velocidad de un motor la misma tiene q ser proporcional al valor ingresado por RA0,ENTONCES a la entrada adc puedo tener 5V, 4V, 3V, 2V,1V, 0.5V. La idea es que asi como a los 5V genero un pwm al 100% lo pueda hacer si tengo a la entrada 4V,3V,2V,ETC.siendo por ej 4V o (3V,2V etc) la nueva maxima excursion del puerto adc. Es por eso que antes de generar el pwm tengo una etapa de calibracion en la cual obtengo una cte la misma q se multiplica x el valor leido por el puerto adc..ojala puedas ayudarme. saludos!

    ResponderBorrar
    Respuestas
    1. De nada, si necesitas una curva de respuesta, osea que le ingreses un determinado valor de tension y te de un determinado ciclo de trabajo PWM, tenes que hacer o bien una tabla de valores que te de esa curva de respuesta o bien tenes que hacer una regrecion estadistica que te de como resultado una ecuacion la cual puedas aplicar al programa, nose si usas el excel pero en el excel podes crear una tabla x,y donde en la columna x podes poner los posibles valores de tension que ingresas con la resolucion que quieras claramente... y en la columna y pones el porcentaje de ciclo de trabajo en funcion de ese valor de tension, luego haces un grafico de dispercion tomando esas dos columnas, y le aplicas una regrecion al grafico, dependiendo de como sea la curva podes elegir una regrecion logaritmica, exponencia, cuadratica, etc... y el excel te muestra una formula sencilla que responde a esa curva, esa formula la aplicas al programa y listo., si queres decime que valores mas o menos te interesan como puntos criticos y yo te hago el calculo de estadistica.
      Saludos!

      Borrar
    2. Mira esto es una regrecion lineal de los posibles valores del pwm en función de la entrada, tene en cuenta que los valores tomados tienen casi una respuesta lineal, si las fluctuaciones son mas fuertes probablemente la regresión requiera un polinomio mas enriquecido.
      http://img43.imageshack.us/img43/4255/538h.png

      Borrar
  10. mira el proyecto se trata de controlar un motor con señales mioelectricas sabemos q no todas las personas generamos una misma tensión, una vez tratada la señal obtenida de la persona quizás algunos lleguen a 5V otros 3V otros a 1v esto no es algo fijo depende de la persona q maneje el sistema. Lo q yo quiero hacer es q una persona q tiene de máxima contracción muscular de 1V pueda mover igual el motor q una persona que tiene 5v como máxima contraccion muscular . El movimiento del motor es proporcional a la contracción muscular del paciente. Por ejemplo mi maxima contraccion muscular es 1V entonces lo q yo tengo q tener es:
    a 1V un pwm al 100% el ciclo de trabajo
    a 0.5 V un pwm al 50% el ciclo de trabajo
    Ahora si es sistema es manejado por ti y tu máxima contracción muscular es 5V entonces obtendré:
    a 5 V un pwm al 100% el ciclo de trabajo
    a 2.5 V un pwm al 50% el ciclo de trabajo
    dejando en claro q no son estos dos puntos fijos, el motor varia de acuerdo a la contracción muscular .
    Yo lo resolvía de la siguiente manera:
    Primero una etapa de calibración en la cual obtenía la máxima contracción muscular (MCM) . Cte = 1023/MCM, una vez obtenida esta constante multiplicaba x cada valor ingresado por el conversor ad. ejemplo: un x paciente tiene de MCM 2.5 Q EN DIGITAL SERIA 511 entonces cte=1023/511 =2, entonces ahora todos los valores obtenidos para manejar el motor seran multiuplicados x esta cte. Mi problema nace cuanto la constantes es un numero flotante ej: 1.6 al multiplicar por el valor obtenido por el adc y luego usarlo como ciclo de trabajo obtengo un pwm inestable es como q el ciclo del pwm no pudiera ser numeros float, como puedo solucionar esto? espero q me haya podido explicar bien

    ResponderBorrar
    Respuestas
    1. Hola como estas?, si, creo que ya comprendo tu problema, dejando de lado el tema de electromedicina, primero tendrías que evaluar el concepto de manejar la tensión de referencia, ya que calculaste un MCM según cada persona, deberías modificar el Vref del micro en función al MCM, esto porque si vos dejas la misma Vref, por ejemplo para alguien que te entregue 1V como fondo de escala, y la Vref la tenes a 5V, vas a perder exactitud en aquellas personas, y la mayor exactitud la vas a tener en aquellos que te entreguen 5V (siempre que dividas el valor del ADC te va a generar inestabilidad por eso conviene trabajar con el Vref y no con el calculo interno), eso es lo primero para que sea lineal el cambio entre una persona de 1V y una de 5V. Una vez eso, tenes que tener en cuenta que el ADC no maneja punto flotante, es solo valor entero int8 o int16, tendrías que configurar el ADC y el modulo PWM en la misma resolución, por ejemplo 10bits, y que el valor del ADC sea el mismo que alimente el PWM sin tratarlo por ningún calculo, el único trato del MCM tendría que se externo (por hardware) manipulando el Vref del ADC, de esa forma debería funcionar igual para una persona de 1V o una de 5V. Si de esa forma te sigue generando inestabilidad tal vez haya que pensar en otro proceso, por ejemplo PID.
      Saludos!

      Borrar
  11. gracias sebastian! voy a poner en practica tus sugerencias! saludos

    ResponderBorrar
    Respuestas
    1. De nada!, para aprovechar el 100% de la resolucion del ADC siempre te conviene jugar con los Vref en lugar de usar calculos.
      Y si queres la mayor estabilidad, tal vez te convenga un PID que es un algoritmo muy eficiente para estos lazos cerrados.
      Saludos.

      Borrar
    2. Si de todas formas queres usar el calculo matematico y no usar el Vref ni el PID, podes separar el numero decimal en dos partes, entera y decimal, separando entre la parte baja y alta del numero usando la funcion make32, donde pones MSB y LSB, de ahi podes separa el numero y trabajar con valroes mas altos, pero vas a perder estabilidad ya que un calculo de esos en tiempo real esta cambiando constantemente y vas a obtener exactamente la inestabilidad que no queres... Pero todo se puede probar!

      Borrar
    3. Nose si usate alguna ves el casting de variables pero podes asignarle a la variable que quieras el tipo de variable que quieras que te muestre, te va a recortar la varibale redondeando. pero bueno la idea es por ejemplo si tenes float=1.7; te podes guardar entero=(int)float; lo cual te va a mostrar entero: 2. Te redondeo para arriba. tenelo en cuenta por si lo necesitas, de todas formas se puede compenzar, pero depende cuan suave queres que sea el PWM...

      Borrar
  12. Hola, quisiera una aclaración.
    En el ejemplo, primero se ejecuta el delay de OFF y despues se pone la salida en low, acto seguido se ejecuta el delay de ON y despues se pone la salida en high.
    Según eso, la salida esta en low durante el tiempo de on y esta en high lo durante el tiempo de OFF (mas el tiempo de ejecución del programa que es inapreciable).
    delay_us(tOFF);
    output_low(PIN_C0);
    delay_us(tON);
    output_high(PIN_C0);
    Mi pregunta es, es así por alguna razon o simplemente es un error de orden?
    En este caso funcionaria perfectamente solo que a la inversa con respecto al sentido de giro del potenciometro.

    Gracias!

    ResponderBorrar
    Respuestas
    1. Es como lo analizaste vos, esta al revés, no me di cuenta en el momento, tal vez puede que conecte al revés también el potenciometro y por eso no me di cuenta del orden.
      Gracias por la aclaración!
      Saludos.!

      Borrar
  13. ¿Como se puede hacer lo mismo pero en vez de potenciometro que haga una atenuación automática, meneje el PWM sólo por software, es decir que brille el LED al maximo, se valla atenuando hasta apagarse y luego se incremente de nuevo el brillo hasta volver al máximo y así?, gracias de antemano.

    ResponderBorrar
    Respuestas
    1. Hola mira este ejemplo con LED RGB. http://electgpl.blogspot.com.ar/2014/11/fade-led-rgb.html
      Saludos.

      Borrar
  14. Hola, no sabía que podía hacerse con delay. Por lo general siempre leí que el uso de esta función no estaba bien para la gestión de tiempos en los micros. Es posible hacer más de un pwm con delays en un mismo pic? En mí caso un pic12f629.

    ResponderBorrar