Autor Tema: Duty cycle fijo con PWM?  (Leído 2670 veces)

0 Usuarios y 1 Visitante están viendo este tema.

Desconectado jipc

  • PIC10
  • *
  • Mensajes: 3
Duty cycle fijo con PWM?
« en: 07 de Agosto de 2016, 14:04:48 »
Hola! llevo trasteando bastante con el PIC18F4550 y la programación del PWM por hardware.
Para mi proyecto necesito variar la frecuencia manteniendo el duty cycle de una de las salidas al 50% y la otra salida al 15%.

Aquí está mi problema, le he dado muchas vueltas y usando
 
Código: [Seleccionar]
setup_timer_2(t2_div_by_x,y,1);
set_pwm1_duty((int16)z);

me surge el problema de que para cada frecuencia cambia la resolución con la que definir el duty según esta fórmula:


y no se si puedo hacer, del alguna manera, que poniendo un valor donde la "z" del set_pwm1_duty((int16)z);  valga para todas las frecuencias.

Otra opción que  tengo en mente es usar la librería
Código: [Seleccionar]
#USE PWM(OUTPUT=PIN_c2, FREQUENCY=5kHz, DUTY=50) explicada aquí http://www.ccsinfo.com/newsdesk_info.php?newsdesk_id=182

que sería la opción perfecta si pudiera cambiar por separado los duty cycles de las dos salidas que tiene este PIC...


En resumen, mi objetivo es poder variar fácilmente las frecuencias del PWM teniendo las salidas con duty cycle fijos. ¿Cómo puedo hacerlo?

Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 8242
Re:Duty cycle fijo con PWM?
« Respuesta #1 en: 07 de Agosto de 2016, 14:23:18 »
Es una situacion bastante rara la que planteas, y si, solamente variar la el periodo no va a poder ser.

Se me ocurren 2 sola opciones.

Si son pocos pasos de cambio de periodo, seria tener una tabla con los valores de periodo-duty, entonces para cada valor de periodo que uses, tenes tu valor de duty correspondiente. ( seria la implementacion mas simple y pre calculadda) Incluso en el PEOR de los casos que quieras tener los 255 valores de periodo te llevaria unos 510 bytes (0.5Kb aprox - de los 32K que posee el micro-) ocupados que los podes poner en la memoria de programa

Si ya son demasiados pasos o buscas que si aumentas 1 en el periodo el duty tambien lo siga, deberias de calcularlo en el mismo programa.
« Última modificación: 07 de Agosto de 2016, 14:44:19 por KILLERJC »

Desconectado jipc

  • PIC10
  • *
  • Mensajes: 3
Re:Duty cycle fijo con PWM?
« Respuesta #2 en: 07 de Agosto de 2016, 20:55:27 »
Es una situacion bastante rara la que planteas, y si, solamente variar la el periodo no va a poder ser.

Se me ocurren 2 sola opciones.

Si son pocos pasos de cambio de periodo, seria tener una tabla con los valores de periodo-duty, entonces para cada valor de periodo que uses, tenes tu valor de duty correspondiente. ( seria la implementacion mas simple y pre calculadda) Incluso en el PEOR de los casos que quieras tener los 255 valores de periodo te llevaria unos 510 bytes (0.5Kb aprox - de los 32K que posee el micro-) ocupados que los podes poner en la memoria de programa

Si ya son demasiados pasos o buscas que si aumentas 1 en el periodo el duty tambien lo siga, deberias de calcularlo en el mismo programa.

Por lo segundo que dices, otra opción qué tenía en mente era usando la ecuación que puse antes, hacer 2^n siendo n la resolución en bits y ese número lo multiplicaría por una variable llamada duty de valor 0.5 por ejemplo si quiero el 50%.

Algo como esto:

Código: [Seleccionar]
#include <stdio.h>
#include <math.h>
#define Fosc 48000000 

int T2DIV;      // preescaler de la funcion setup_timer_2, puede ser 1, 4 o 16
int Fpwm;       // frecuencia que busco
float dutycycle;    // valor que introducimos en la funcion set_pwm1_duty(dutycycle)
float duty;         // de 0 a 1 me indica el % del duty cycle que queremos
 
int dutycalculator();        // función donde operamos para obtener el valor de la variable dutycycle
int calculo_de_prescaler();  // función donde operamos para calcular el preescaler adecuado a la frecuencia que necesitamos



int main(void) {

Fpwm= 5000;                          // ejemplo con 5 kHz
duty= 0.5;                           // duty 50%
calculo_de_prescaler();


return 0;
}




  int calculo_de_prescaler(){ 
     
   
   if (Fosc/(Fpwm*1*4)-1<0xFF) {
     //  setup_timer_2(T2_DIV_BY_1, (int8)(Fosc/(Fpwm*1*4))-1, 1);
       T2DIV=1;
   }
   
   else if (Fosc/(Fpwm*4*4)-1<0xFF) {
    //  setup_timer_2(T2_DIV_BY_4, (int8)(Fosc/(Fpwm*4*4))-1, 1);
      T2DIV=4;
   }
   
   else if (Fosc/(Fpwm*16*4)-1<0xFF) {
  // setup_timer_2(T2_DIV_BY_16, (int8)(Fosc/(Fpwm*16*4))-1, 1);
   T2DIV=16;
   }
   
   dutycalculator();
   
  }
 
  int dutycalculator(){
 
  int resolucion = log10(Fosc/(Fpwm*T2DIV))/log10(2);             // calculamos la resolucion de dicho valor de frecuencia
  dutycycle = pow(2,resolucion)*duty;                             // hacemos 2^(resolucion) y multiplicamos por variable duty
 
  printf("%d\n",T2DIV);       
  printf ("%d\n",resolucion);
  printf("%.2f",dutycycle);
   

 
  }

Que cuando lo compilo me da lo que quiero:



el caso es que fijándome en los valores de resoluciones de esta web http://www.micro-examples.com/public/microex-navig/doc/097-pwm-calculator.html (para 48 MHz), no me queda claro cuándo la resolución es por ejemplo de 9 bits o 10 bits, ya que cuando el valor calculado resulta entre ambos (por ejemplo 9.9), para algunas frecuencias en 9 bits y para otras es 10...:

Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 8242
Re:Duty cycle fijo con PWM?
« Respuesta #3 en: 07 de Agosto de 2016, 22:14:44 »
Para mi, con un simple:

Código: C
  1. Periodo = 0x00; // Esto puede ir de 0x00 hasta 0xFF, es decir lo que iria en PR2 del TMR2
  2.  
  3. // Valores a cargar en el CCPRxL + 2bits del CCPxCON:
  4.  
  5. Duty1 = ((50 * Periodo) / 100) << 2;
  6. Duty2 = ((15 * Periodo) / 100) << 2;

Se podria simplificar estas ecuaciones en:

Código: C
  1. Duty1 = (2 * (uint16_t)Periodo);
  2. Duty2 = (6 * (uint16_t)Periodo) / 10;

Ahi, cuando periodo (PR2) es por ejemplo 150, el duty al 50% deberia valer 300.

Por que esto ?, El periodo se genera con los 8bits del PR2 desplazado a la izquierda 2 veces. Es decir multiplicado por 4, Si tenemos 150 es 600.
Esto se hace para que la comparacion sea de 10 bits. Ahora si quisiera generar un duty de 50% deberia cargar la mitad, es decir 300. Si quisiera 15% de duty, es decir el 15% de 600 es de 90.
Fin.. Eso fue todo el calculo que debiste en lo que es llenar los registros. Observa que no afecta en nada los preescalers ni nada por el estilo.

Ahora si queres una conversion desde una frecuencia como estas planteando, entonces deberiamos Primero llegar al duty, que es algo parecido a lo que hiciste, pero hubiera intentado que se realizen menos veces las divisiones y multiplicaciones.

Código: C
  1. void SetearPWM(int16 Fpwm, int8 Duty){
  2.  
  3.         int16   Periodo = (Fosc/4)/Fpwm;
  4.         int8    Preescaler = 1;
  5.         int8    DutyCalculado;
  6.         int8    i;
  7.  
  8.         for(i=0;i<3;i++)
  9.         {
  10.                 if( Periodo < 0x100)
  11.                 {
  12.                         break;
  13.                 }
  14.                 Periodo >>= 2
  15.                 Preescaler <<= 2;
  16.         }
  17.  
  18.         if( Preescaler > 16)
  19.         {
  20.                 return;         //Error, no hay valor posible
  21.         }
  22.        
  23.         Periodo--;
  24.         DutyCalculado = (Duty*4*Periodo) / 100;
  25.  
  26.         printf("%d\n",(int8)Periodo);      
  27.         printf("%d\n",DutyCalculado);
  28.         printf("%d\n",Preescaler);
  29. }


-------------------------------------

Segun la pagina que pasaste para 5000 Hz y 50% duty tendrias:

Código: [Seleccionar]
PR2 = Periodo = 149
Preescaler = 16
Duty = 299 ( Formado por CCPR1L + CCP1CON<5:4> )

CCPR1L = 0b01001010 ;
CCP1CON = 0b00111100 ;

O 0b0100101011 = 299d

Segun el codigo que pase, tendriamos al comienzo del script:

Periodo = 2400
Preescaler = 1

Los pasos en el for quedarian los siguientes:
i=0 , pasa completamente por que Periodo es mayor que 0x100 ( 256 )
i=1 , los valores quedan culpa de la anterior vuelta:

Periodo = 600
Preescaler = 4

Aun es grande, asi que pasa.

i=2, los valores al comenzar esta vuelta es de

Periodo = 150
Preescaler = 16

Ahora entra al if y procede a ejecutar el break y salir del for, no ejecutandose las lineas que siguen.

Luego hay una "proteccion" por si es que ni con un Preescaler de 16 se puede realizar. Se decrementa 1 de Periodo dejandolo en 149, como queremos.
Finalmente el duty, tendria un valor de (50 * 4 * 149) / 100 = 298

Por lo tanto tendrias como salida:

Código: [Seleccionar]
Periodo = 149
Preescaler = 16
Duty = 298

Bastante aproximado a lo que deberia ser.

-----------------------------------------------------------

Pero por supuesto, nada se compara respecto a velocidad a usar tablas, solo tendrias el trabajo de crearlas, que con un script de python lo harias en un ratito.
Y la otra creo que mi acercamiento es mas rapido que el tuyo, ya que no uso logaritmos ni nada raro, ademas trato de calcular lo menos posible. Asi es mas rapido.
« Última modificación: 07 de Agosto de 2016, 22:55:56 por KILLERJC »

Desconectado jipc

  • PIC10
  • *
  • Mensajes: 3
Re:Duty cycle fijo con PWM?
« Respuesta #4 en: 08 de Agosto de 2016, 05:33:55 »
Para mi, con un simple:

Código: C
  1. Periodo = 0x00; // Esto puede ir de 0x00 hasta 0xFF, es decir lo que iria en PR2 del TMR2
  2.  
  3. // Valores a cargar en el CCPRxL + 2bits del CCPxCON:
  4.  
  5. Duty1 = ((50 * Periodo) / 100) << 2;
  6. Duty2 = ((15 * Periodo) / 100) << 2;

Se podria simplificar estas ecuaciones en:

Código: C
  1. Duty1 = (2 * (uint16_t)Periodo);
  2. Duty2 = (6 * (uint16_t)Periodo) / 10;

Ahi, cuando periodo (PR2) es por ejemplo 150, el duty al 50% deberia valer 300.

Por que esto ?, El periodo se genera con los 8bits del PR2 desplazado a la izquierda 2 veces. Es decir multiplicado por 4, Si tenemos 150 es 600.
Esto se hace para que la comparacion sea de 10 bits. Ahora si quisiera generar un duty de 50% deberia cargar la mitad, es decir 300. Si quisiera 15% de duty, es decir el 15% de 600 es de 90.
Fin.. Eso fue todo el calculo que debiste en lo que es llenar los registros. Observa que no afecta en nada los preescalers ni nada por el estilo.

Ahora si queres una conversion desde una frecuencia como estas planteando, entonces deberiamos Primero llegar al duty, que es algo parecido a lo que hiciste, pero hubiera intentado que se realizen menos veces las divisiones y multiplicaciones.

Código: C
  1. void SetearPWM(int16 Fpwm, int8 Duty){
  2.  
  3.         int16   Periodo = (Fosc/4)/Fpwm;
  4.         int8    Preescaler = 1;
  5.         int8    DutyCalculado;
  6.         int8    i;
  7.  
  8.         for(i=0;i<3;i++)
  9.         {
  10.                 if( Periodo < 0x100)
  11.                 {
  12.                         break;
  13.                 }
  14.                 Periodo >>= 2
  15.                 Preescaler <<= 2;
  16.         }
  17.  
  18.         if( Preescaler > 16)
  19.         {
  20.                 return;         //Error, no hay valor posible
  21.         }
  22.        
  23.         Periodo--;
  24.         DutyCalculado = (Duty*4*Periodo) / 100;
  25.  
  26.         printf("%d\n",(int8)Periodo);      
  27.         printf("%d\n",DutyCalculado);
  28.         printf("%d\n",Preescaler);
  29. }


-------------------------------------

Segun la pagina que pasaste para 5000 Hz y 50% duty tendrias:

Código: [Seleccionar]
PR2 = Periodo = 149
Preescaler = 16
Duty = 299 ( Formado por CCPR1L + CCP1CON<5:4> )

CCPR1L = 0b01001010 ;
CCP1CON = 0b00111100 ;

O 0b0100101011 = 299d

Segun el codigo que pase, tendriamos al comienzo del script:

Periodo = 2400
Preescaler = 1

Los pasos en el for quedarian los siguientes:
i=0 , pasa completamente por que Periodo es mayor que 0x100 ( 256 )
i=1 , los valores quedan culpa de la anterior vuelta:

Periodo = 600
Preescaler = 4

Aun es grande, asi que pasa.

i=2, los valores al comenzar esta vuelta es de

Periodo = 150
Preescaler = 16

Ahora entra al if y procede a ejecutar el break y salir del for, no ejecutandose las lineas que siguen.

Luego hay una "proteccion" por si es que ni con un Preescaler de 16 se puede realizar. Se decrementa 1 de Periodo dejandolo en 149, como queremos.
Finalmente el duty, tendria un valor de (50 * 4 * 149) / 100 = 298

Por lo tanto tendrias como salida:

Código: [Seleccionar]
Periodo = 149
Preescaler = 16
Duty = 298

Bastante aproximado a lo que deberia ser.

-----------------------------------------------------------

Pero por supuesto, nada se compara respecto a velocidad a usar tablas, solo tendrias el trabajo de crearlas, que con un script de python lo harias en un ratito.
Y la otra creo que mi acercamiento es mas rapido que el tuyo, ya que no uso logaritmos ni nada raro, ademas trato de calcular lo menos posible. Asi es mas rapido.

Menuda respuesta, muchísimas gracias. En cuanto lo vea detenidamente y lo pruebe te digo cómo me ha ido.



 

anything